1pub mod error;
2pub mod header;
3pub mod ops;
4
5pub use header::*;
6pub use ops::*;
7
8use log::error;
9use std::io::Read;
10use tracing::trace;
11
12#[doc(inline)]
13pub use error::{PDUError, PDUResult};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum PDUPayload {
18 Directive(Operations),
20 FileData(FileDataPDU),
22}
23impl PDUPayload {
24 pub fn encoded_len(&self, file_size_flag: FileSizeFlag) -> u16 {
26 match self {
27 Self::Directive(operation) => operation.encoded_len(file_size_flag),
28 Self::FileData(file_data) => file_data.encoded_len(file_size_flag),
29 }
30 }
31
32 pub fn encode(self, file_size_flag: FileSizeFlag) -> Vec<u8> {
34 match self {
35 Self::Directive(operation) => operation.encode(file_size_flag),
36 Self::FileData(data) => data.encode(file_size_flag),
37 }
38 }
39
40 pub fn decode<T: std::io::Read>(
42 buffer: &mut T,
43 pdu_type: PDUType,
44 file_size_flag: FileSizeFlag,
45 segmentation_flag: SegmentedData,
46 ) -> PDUResult<Self> {
47 match pdu_type {
48 PDUType::FileDirective => {
49 Ok(Self::Directive(Operations::decode(buffer, file_size_flag)?))
50 }
51 PDUType::FileData => Ok(Self::FileData(FileDataPDU::decode(
52 buffer,
53 segmentation_flag,
54 file_size_flag,
55 )?)),
56 }
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct PDU {
65 pub header: PDUHeader,
67 pub payload: PDUPayload,
69}
70impl PDUEncode for PDU {
71 type PDUType = Self;
72
73 fn encoded_len(&self) -> u16 {
74 self.header.encoded_len() + self.payload.encoded_len(self.header.large_file_flag)
75 }
76
77 fn encode(self) -> Vec<u8> {
78 trace!("Encoded PDU: {:?}", self);
79 let crc_flag = self.header.crc_flag;
80 let file_size_flag = self.header.large_file_flag;
81
82 let mut buffer = self.header.encode();
83 buffer.extend(self.payload.encode(file_size_flag));
84 match crc_flag {
85 CRCFlag::Present => buffer.extend(crc16_ibm_3740(buffer.as_slice()).to_be_bytes()),
86 CRCFlag::NotPresent => {}
87 }
88 buffer
89 }
90
91 fn decode<T: Read>(buffer: &mut T) -> PDUResult<Self::PDUType> {
92 let header = PDUHeader::decode(buffer)?;
93 trace!("Decoded header: {:?}", header);
94
95 let mut remaining_msg = vec![0_u8; header.pdu_data_field_length as usize];
96
97 buffer.read_exact(remaining_msg.as_mut_slice())?;
98 let remaining_buffer = &mut remaining_msg.as_slice();
99
100 let payload = PDUPayload::decode(
101 remaining_buffer,
102 header.pdu_type.clone(),
103 header.large_file_flag,
104 header.segment_metadata_flag,
105 )?;
106
107 let received_pdu = Self { header, payload };
108
109 match &received_pdu.header.crc_flag {
110 CRCFlag::NotPresent => {}
111 CRCFlag::Present => {
112 let mut u16_buffer = [0_u8; 2];
113 buffer.read_exact(&mut u16_buffer)?;
114 let crc16 = u16::from_be_bytes(u16_buffer);
115 let tmp_buffer = {
116 let input_pdu = received_pdu.clone();
117
118 let mut temp = input_pdu.encode();
119 temp.truncate(temp.len() - 2);
121 temp
122 };
123 let crc = crc16_ibm_3740(tmp_buffer.as_slice());
124 match crc == crc16 {
125 true => {}
126 false => {
127 error!(
128 "CRC FAILURE, {}, {}, {}",
129 crc,
130 crc16,
131 crc.overflowing_add(crc16).0
132 );
133 return Err(PDUError::CRCFailure(crc16, crc));
134 }
135 }
136 }
137 }
138
139 Ok(received_pdu)
140 }
141}
142
143fn crc16_ibm_3740(message: &[u8]) -> u16 {
144 message
145 .iter()
146 .fold(0xffff, |acc, digit| crc16(*digit as u16, acc))
147}
148
149fn crc16(in_char: u16, crc: u16) -> u16 {
150 let poly = 0x1021;
151 let shift_char = (in_char & 0x00FF) << 8;
152 let mut crc = crc ^ shift_char;
153 for _ in 0..8 {
154 match crc & 0x8000 > 0 {
155 true => crc = (crc << 1) ^ poly,
156 false => crc <<= 1,
157 };
158 }
159 crc
160}
161
162#[cfg(test)]
163mod test {
164 use super::*;
165
166 use crate::filestore::ChecksumType;
167
168 use rstest::rstest;
169
170 #[rstest]
171 #[case("123456789".as_bytes(), 0x29b1_u16)]
172 #[case(
173 &[
174 0x06, 0x00, 0x0c, 0xf0, 0x00, 0x04, 0x00, 0x55,
175 0x88, 0x73, 0xc9, 0x00, 0x00, 0x05, 0x21
176 ],
177 0x75FB
178 )]
179 fn crc16(#[case] input: &[u8], #[case] expected: u16) {
180 let recovered = crc16_ibm_3740(input);
181 assert_eq!(expected, recovered)
182 }
183
184 #[rstest]
185 #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
186 condition: Condition::NoError,
187 checksum: 13_u32,
188 file_size: 12_u64,
189 })))]
190 #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
191 condition: Condition::NoError,
192 checksum: 13_u32,
193 file_size: 12_u64,
194 })))]
195 #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
196 condition: Condition::NoError,
197 checksum: 13_u32,
198 file_size: 12_u64,
199 })))]
200 #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
201 condition: Condition::NoError,
202 checksum: 13_u32,
203 file_size: 12_u64,
204 })))]
205 #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
206 condition: Condition::NoError,
207 checksum: 13_u32,
208 file_size: 12_u64,
209 })))]
210 #[case(PDUPayload::Directive(
211 Operations::Metadata(
212 MetadataPDU {
213 checksum_type: ChecksumType::Modular,
214 file_size: 55_u64,
215 source_filename: "the input filename".into(),
216 destination_filename: "the output filename".into(),
217 }
218 )
219
220 ))]
221 #[case(PDUPayload::Directive(
222 Operations::Nak(
223 NakPDU {
224 start_of_scope: 12_u64,
225 end_of_scope: 239585_u64,
226 segment_requests: vec![
227 SegmentRequestForm{
228 start_offset:12,
229 end_offset: 64
230 },
231 SegmentRequestForm{
232 start_offset: 69,
233 end_offset: 4758
234 }
235 ]
236 }
237 )
238
239 ))]
240 #[case(PDUPayload::FileData(
241 FileDataPDU(
242 UnsegmentedFileData {
243 offset: 948,
244 file_data: (0..12).collect()
245 }
246 )
247 ))]
248 fn pdu_len(
249 #[case] payload: PDUPayload,
250 #[values(FileSizeFlag::Small, FileSizeFlag::Large)] file_size_flag: FileSizeFlag,
251 ) {
252 assert_eq!(
253 payload.encoded_len(file_size_flag),
254 payload.encode(file_size_flag).len() as u16,
255 )
256 }
257
258 #[rstest]
259 #[case(
260 PDUPayload::Directive(Operations::EoF(EndOfFile {
261 condition: Condition::NoError,
262 checksum: 123749_u32,
263 file_size: 7738949_u64,
264 }))
265 )]
266 #[case(
267 PDUPayload::FileData(FileDataPDU(UnsegmentedFileData{
268 offset: 16_u64,
269 file_data: "test some information".as_bytes().to_vec(),
270 }))
271 )]
272 fn pdu_encoding(
273 #[case] payload: PDUPayload,
274 #[values(CRCFlag::NotPresent, CRCFlag::Present)] crc_flag: CRCFlag,
275 ) -> PDUResult<()> {
276 let pdu_data_field_length = payload.encoded_len(FileSizeFlag::Large);
277 let pdu_type = match &payload {
278 PDUPayload::Directive(_) => PDUType::FileDirective,
279 PDUPayload::FileData(_) => PDUType::FileData,
280 };
281
282 let expected: PDU = PDU {
283 header: PDUHeader {
284 version: U3::Zero,
285 pdu_type,
286 direction: Direction::ToReceiver,
287 transmission_mode: TransmissionMode::Acknowledged,
288 crc_flag,
289 large_file_flag: FileSizeFlag::Large,
290 pdu_data_field_length,
291 segmentation_control: SegmentationControl::NotPreserved,
292 segment_metadata_flag: SegmentedData::NotPresent,
293 source_entity_id: EntityID::from(18_u16),
294 transaction_sequence_number: TransactionSeqNum::from(7533_u32),
295 destination_entity_id: EntityID::from(23_u16),
296 },
297 payload,
298 };
299 let buffer = expected.clone().encode();
300 let recovered = PDU::decode(&mut buffer.as_slice())?;
301 assert_eq!(expected, recovered);
302 Ok(())
303 }
304}