srex/srecord/
utils.rs

1use std::num::Wrapping;
2
3use crate::srecord::error::{ErrorType, SRecordParseError};
4use crate::srecord::record_type::RecordType;
5
6/// Parses a record type from `record_str` and returns it, or error message
7#[inline]
8pub(crate) fn parse_record_type(record_str: &str) -> Result<RecordType, SRecordParseError> {
9    let mut chars = record_str.chars();
10    match chars.next() {
11        Some('S') => match chars.next() {
12            Some('0') => Ok(RecordType::S0),
13            Some('1') => Ok(RecordType::S1),
14            Some('2') => Ok(RecordType::S2),
15            Some('3') => Ok(RecordType::S3),
16            Some('4') => Err(SRecordParseError {
17                error_type: ErrorType::S4Reserved,
18            }),
19            Some('5') => Ok(RecordType::S5),
20            Some('6') => Ok(RecordType::S6),
21            Some('7') => Ok(RecordType::S7),
22            Some('8') => Ok(RecordType::S8),
23            Some('9') => Ok(RecordType::S9),
24            Some(_) => Err(SRecordParseError {
25                error_type: ErrorType::InvalidRecordType,
26            }),
27            None => Err(SRecordParseError {
28                error_type: ErrorType::EolWhileParsingRecordType,
29            }),
30        },
31        Some(_) => Err(SRecordParseError {
32            error_type: ErrorType::InvalidFirstCharacter,
33        }),
34        None => Err(SRecordParseError {
35            error_type: ErrorType::EolWhileParsingRecordType,
36        }),
37    }
38}
39
40/// Parses byte count from `record_str` and returns it, or error message
41#[inline]
42pub(crate) fn parse_byte_count(record_str: &str) -> Result<u8, SRecordParseError> {
43    match record_str.get(2..4) {
44        Some(byte_count_str) => match u8::from_str_radix(byte_count_str, 16) {
45            Ok(i) => Ok(i),
46            Err(_) => Err(SRecordParseError {
47                error_type: ErrorType::InvalidByteCount,
48            }),
49        },
50        None => Err(SRecordParseError {
51            error_type: ErrorType::EolWhileParsingByteCount,
52        }),
53    }
54}
55
56/// Parses address from `record_str` and returns it, or error message
57#[inline]
58pub(crate) fn parse_address(
59    record_str: &str,
60    record_type: &RecordType,
61) -> Result<u64, SRecordParseError> {
62    let num_address_bytes = record_type.num_address_bytes();
63    let num_address_chars = num_address_bytes * 2;
64    let address_start_index = 4;
65    let address_end_index = address_start_index + num_address_chars;
66
67    match record_str.get(address_start_index..address_end_index) {
68        Some(address_str) => match u64::from_str_radix(address_str, 16) {
69            Ok(i) => Ok(i),
70            Err(_) => Err(SRecordParseError {
71                error_type: ErrorType::InvalidAddress,
72            }),
73        },
74        None => Err(SRecordParseError {
75            error_type: ErrorType::EolWhileParsingAddress,
76        }),
77    }
78}
79
80/// Parses data and sets slice inside record
81///
82/// Data is written to `data`.
83#[inline]
84pub(crate) fn parse_data_and_checksum(
85    record_str: &str,
86    record_type: &RecordType,
87    byte_count: &u8,
88    address: &u64,
89    data: &mut [u8],
90) -> Result<(), SRecordParseError> {
91    // TODO: Validate record type?
92
93    let num_address_bytes = record_type.num_address_bytes();
94    let num_data_bytes = match (*byte_count as usize).checked_sub(num_address_bytes + 1) {
95        Some(i) => i,
96        None => {
97            return Err(SRecordParseError {
98                error_type: ErrorType::ByteCountTooLowForRecordType,
99            })
100        }
101    };
102    let data = &mut data[..num_data_bytes];
103
104    // Parse data
105    let data_start_index = 2 + 2 + 2 * num_address_bytes; // S* + byte count + address
106    let data_end_index = data_start_index + num_data_bytes * 2;
107    match record_str.get(data_start_index..data_end_index) {
108        Some(data_str) => match hex::decode_to_slice(data_str, data) {
109            Ok(_) => {}
110            Err(_) => {
111                return Err(SRecordParseError {
112                    error_type: ErrorType::InvalidData,
113                })
114            }
115        },
116        None => {
117            return Err(SRecordParseError {
118                error_type: ErrorType::EolWhileParsingData,
119            })
120        }
121    };
122
123    // Next, parse and validate checksum
124    let checksum_start_index = data_end_index;
125    let checksum_end_index = checksum_start_index + 2;
126    let checksum: u8 = match record_str.get(checksum_start_index..checksum_end_index) {
127        Some(checksum_str) => match u8::from_str_radix(checksum_str, 16) {
128            Ok(i) => i,
129            Err(_) => {
130                return Err(SRecordParseError {
131                    error_type: ErrorType::InvalidChecksum,
132                });
133            }
134        },
135        None => {
136            return Err(SRecordParseError {
137                error_type: ErrorType::EolWhileParsingChecksum,
138            });
139        }
140    };
141    let expected_checksum = calculate_checksum(byte_count, address, data);
142    if checksum != expected_checksum {
143        return Err(SRecordParseError {
144            error_type: ErrorType::CalculatedChecksumNotMatchingParsedChecksum,
145        });
146    }
147
148    // Finally, validate that we are at the end of the record str
149    if record_str.len() != checksum_end_index {
150        return Err(SRecordParseError {
151            error_type: ErrorType::LineNotTerminatedAfterChecksum,
152        });
153    }
154
155    Ok(())
156}
157
158/// Calculate the checksum for a single record (line).
159///
160/// The checksum is calculated from the sum of all the individual bytes, from `byte_count`,
161/// individual `address` bytes and all bytes in `data`. All but the least significant byte is
162/// discarded, and is then bitwise inverted.
163///
164/// # Examples
165///
166/// ```
167/// use srex::srecord::utils::calculate_checksum;
168///
169/// let address = 0x12345678;
170/// let data = [0x01, 0x02, 0x03, 0x04];
171/// let byte_count = 9;
172/// assert_eq!(calculate_checksum(&byte_count, &address, &data), 0xD8);
173/// ```
174// TODO: Pub????
175pub fn calculate_checksum(byte_count: &u8, address: &u64, data: &[u8]) -> u8 {
176    let mut checksum = Wrapping(*byte_count);
177    for byte in address.to_be_bytes().iter() {
178        checksum += byte;
179    }
180    for byte in data.iter() {
181        checksum += byte;
182    }
183    0xFF - checksum.0
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_calculate_checksum() {
192        assert_eq!(
193            calculate_checksum(
194                &0x13,
195                &0x7AF0,
196                &[0x0A, 0x0A, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
197            ),
198            0x61
199        );
200        assert_eq!(
201            calculate_checksum(
202                &0x0F,
203                &0x0000,
204                &[0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x20, 0x20, 0x20, 0x20, 0, 0]
205            ),
206            0x3C
207        );
208        assert_eq!(
209            calculate_checksum(
210                &0x1F,
211                &0x0000,
212                &[
213                    0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0xF0, 0x7C,
214                    0x6C, 0x1B, 0x78, 0x7C, 0x8C, 0x23, 0x78, 0x3C, 0x60, 0x00, 0x00, 0x38, 0x63,
215                    0x00, 0x00
216                ]
217            ),
218            0x26
219        );
220        assert_eq!(
221            calculate_checksum(
222                &0x1F,
223                &0x001C,
224                &[
225                    0x4B, 0xFF, 0xFF, 0xE5, 0x39, 0x80, 0x00, 0x00, 0x7D, 0x83, 0x63, 0x78, 0x80,
226                    0x01, 0x00, 0x14, 0x38, 0x21, 0x00, 0x10, 0x7C, 0x08, 0x03, 0xA6, 0x4E, 0x80,
227                    0x00, 0x20
228                ]
229            ),
230            0xE9
231        );
232        assert_eq!(
233            calculate_checksum(
234                &0x11,
235                &0x0038,
236                &[
237                    0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x2E, 0x0A,
238                    0x00
239                ]
240            ),
241            0x42
242        );
243        assert_eq!(calculate_checksum(&0x03, &0x0003, &[]), 0xF9);
244        assert_eq!(calculate_checksum(&0x03, &0x0000, &[]), 0xFC);
245    }
246}