nsap_address/
bcd.rs

1//! Binary-Coded Decimal (BCD) handling
2//!
3//! This isn't generally useful for all uses cases: this is specifically
4//! designed for X.213 NSAP addresses
5use core::iter::{FusedIterator, Iterator};
6
7/// Buffer for writing Binary-Coded Decimal (BCD)
8///
9/// Binary-Coded Decimal (BCD) is extensively used by X.213 NSAP addresses. It
10/// is always used for the Initial Domain Identifier (IDI), but is often used
11/// for the Domain Specific Part (DSP) as well.
12///
13/// This uses a fixed-length buffer of 20 bytes, because NSAP addresses are
14/// forbidden from exceeding 20 bytes, with an exception for URLs established in
15/// ITU-T Rec. X.519. Despite this one exception, no decimal encoding of an NSAP
16/// address exceeds 20 bytes.
17pub struct BCDBuffer {
18    pub bytes: [u8; 20],
19    pub i: u8,
20}
21
22impl BCDBuffer {
23    /// Create a new BCD buffer
24    #[inline]
25    pub fn new() -> Self {
26        BCDBuffer {
27            bytes: [0; 20],
28            i: 0,
29        }
30    }
31
32    /// Push a string of ASCII digits to the BCD buffer.
33    ///
34    /// Each character MUST return `true` from `u8::is_ascii_digit`.
35    pub fn push_str(&mut self, s: &str) {
36        debug_assert!(s.is_ascii(), "non-ascii passed into BCDBuffer::push_str");
37        s.bytes().for_each(|b| self.push_digit_u8(b));
38    }
39
40    /// Push a u8 slice of ASCII digits to the BCD buffer.
41    ///
42    /// The entire slice MUST return `true` from `u8::is_ascii_digit`.
43    pub fn push_ascii_bytes(&mut self, bytes: &[u8]) {
44        debug_assert!(
45            bytes.is_ascii(),
46            "non-ascii passed into BCDBuffer::push_ascii_bytes"
47        );
48        bytes.iter().for_each(|b| self.push_digit_u8(*b));
49    }
50
51    /// Push a single ASCII digit into the BCD buffer.
52    ///
53    /// `b` MUST return `true` from `u8::is_ascii_digit`.
54    pub fn push_digit_u8(&mut self, b: u8) {
55        debug_assert!(
56            b.is_ascii_digit(),
57            "non-ascii digit passed into BCDBuffer::push_digit_u8"
58        );
59        let nybble: u8 = b.saturating_sub(0x30);
60        self.push_nybble(nybble);
61    }
62
63    /// Push an arbitrary nybble into the BCD buffer
64    ///
65    /// This does not check if the nybble is a binary-coded decimal.
66    /// This is particularly useful for pushing the padding nybble `0b1111`
67    /// that is used to pad an odd number of digits to an integral number of
68    /// octets.
69    pub fn push_nybble(&mut self, n: u8) {
70        let byte_index = self.i >> 1;
71        if (self.i % 2) > 0 {
72            // least significant nybble
73            self.bytes[byte_index as usize] |= n;
74        } else {
75            self.bytes[byte_index as usize] |= n << 4;
76        }
77        self.i += 1;
78        self.i = self.i.clamp(0, 39);
79    }
80
81    /// Push a full byte into the BCD buffer
82    ///
83    /// If the last nybble prior to pushing is unset, it stays unset at 0.
84    ///
85    /// In other words, if the buffer contains `012` and you use this function
86    /// to push `0x34`, the BCD buffer will then contain `012034`.
87    pub fn push_byte(&mut self, byte: u8) {
88        let byte_index = self.len_in_bytes();
89        self.bytes[byte_index] = byte;
90        self.i += if (self.i % 2) > 0 { 3 } else { 2 };
91        self.i = self.i.clamp(0, 39);
92    }
93
94    /// Get the length of the BCD in bytes.
95    pub fn len_in_bytes(&self) -> usize {
96        ((self.i >> 1) + (self.i % 2)) as usize
97    }
98}
99
100impl AsRef<[u8]> for BCDBuffer {
101    /// Returns the BCD bytes. Use this function to obtain the output of the
102    /// BCD buffer.
103    fn as_ref(&self) -> &[u8] {
104        &self.bytes[0..self.len_in_bytes()]
105    }
106}
107
108/// BCD Digits Iterator
109#[derive(Debug, Clone)]
110pub struct BCDDigitsIter<'a> {
111    bytes: &'a [u8],
112    least_sig_nybble: bool,
113    leading_0_sig: bool,
114    processing_leading_digits: bool,
115    ignore_last_nybble: bool,
116}
117
118impl<'a> BCDDigitsIter<'a> {
119    /// Create a new BCD digits iterator
120    #[inline]
121    pub fn new(
122        bytes: &'a [u8],
123        leading_0_sig: bool,
124        ignore_last_nybble: bool,
125        least_sig_nybble: bool,
126        processing_leading_digits: bool,
127    ) -> BCDDigitsIter<'a> {
128        BCDDigitsIter {
129            bytes,
130            leading_0_sig,
131            ignore_last_nybble,
132            processing_leading_digits, // Start off handling leading digits
133            least_sig_nybble,          // Start off on the MSn
134        }
135    }
136}
137
138/// An ASCII digit (`0x30..=0x39`)
139///
140/// This SHOULD BE an ASCII digit, but might not be. It is on the caller to
141/// check this and determine what to do if this has a non-digit value.
142pub type ShouldBeASCIIDigit = u8;
143
144impl<'a> Iterator for BCDDigitsIter<'a> {
145    type Item = ShouldBeASCIIDigit;
146
147    /// This implementation does NOT handle malformed digits. The caller MUST
148    /// check for non-ASCII digits being returned
149    fn next(&mut self) -> Option<Self::Item> {
150        while self.bytes.len() > 0 {
151            let nybble: u8 = if self.least_sig_nybble {
152                self.bytes[0] & 0b0000_1111
153            } else {
154                (self.bytes[0] & 0b1111_0000) >> 4
155            };
156            if self.least_sig_nybble {
157                self.least_sig_nybble = false;
158                self.bytes = &self.bytes[1..];
159            } else {
160                self.least_sig_nybble = true;
161            }
162            if self.processing_leading_digits {
163                let leading_digit: u8 = if self.leading_0_sig { 1 } else { 0 };
164                if nybble == leading_digit {
165                    continue;
166                } else {
167                    self.processing_leading_digits = false;
168                }
169            }
170            // If the last nybble is 0b1111, it is padding.
171            // If the DSP is in decimal digits, the last nybble of the
172            if self.bytes.len() == 0 && (nybble == 0b1111 || self.ignore_last_nybble) {
173                return None;
174            }
175            return Some(nybble);
176        }
177        None
178    }
179
180    fn size_hint(&self) -> (usize, Option<usize>) {
181        let mut max_digits = self.bytes.len() << 1; // Double it
182        if self.least_sig_nybble {
183            max_digits = max_digits.saturating_sub(1);
184        }
185        if self.ignore_last_nybble {
186            max_digits = max_digits.saturating_sub(1);
187        }
188        // Every digit could be a leading digit
189        (0, Some(max_digits))
190    }
191}
192
193impl<'a> FusedIterator for BCDDigitsIter<'a> {}
194
195#[cfg(test)]
196mod tests {
197    use crate::bcd::BCDBuffer;
198
199    #[test]
200    fn test_bcd_buffer_1() {
201        let mut bcd = BCDBuffer::new();
202        assert_eq!(bcd.len_in_bytes(), 0);
203        bcd.push_digit_u8(b'9');
204        assert_eq!(bcd.len_in_bytes(), 1);
205        bcd.push_digit_u8(0x37);
206        assert_eq!(bcd.len_in_bytes(), 1);
207        bcd.push_nybble(0x05);
208        assert_eq!(bcd.len_in_bytes(), 2);
209        bcd.push_byte(0x33);
210        assert_eq!(bcd.len_in_bytes(), 3);
211        assert_eq!(bcd.as_ref(), [0x97, 0x50, 0x33].as_slice());
212        assert_eq!(bcd.len_in_bytes(), 3);
213    }
214
215    #[test]
216    fn test_bcd_buffer_2() {
217        let mut bcd = BCDBuffer::new();
218        assert_eq!(bcd.len_in_bytes(), 0);
219        bcd.push_digit_u8(0x39);
220        assert_eq!(bcd.len_in_bytes(), 1);
221        bcd.push_ascii_bytes([0x37, 0x35].as_slice());
222        assert_eq!(bcd.len_in_bytes(), 2);
223        bcd.push_str("31");
224        assert_eq!(bcd.len_in_bytes(), 3);
225        bcd.push_nybble(0xF);
226        assert_eq!(bcd.len_in_bytes(), 3);
227        assert_eq!(bcd.as_ref(), [0x97, 0x53, 0x1F].as_slice());
228        assert_eq!(bcd.len_in_bytes(), 3);
229    }
230}