gcd_rs/
part_number.rs

1//! Part Number could represent a product or part of one, maybe a file format.
2//!
3//! The string representation of Part Number is "AAA-BCCCC-DD"
4//!
5//! Is composed of at least 4 parts. Is not know the real signification of
6//! each value, based on suposition they are possibly:
7//! A: Product Kind
8//! B: Hw Type
9//! C: Hw Id
10//! D: Release/Variation
11//!
12//! Is also possible the fields C-D vary with meaning based on the value of A.
13//!
14//! It is represented as [u8; 9], is basically a string but each char is
15//! (including '-') calculated but subtracting 0x20 and is 6 bits.
16
17use byteorder::ByteOrder;
18use nom::bytes::complete::*;
19use nom::character::is_digit;
20use nom::combinator::map_res;
21use nom::sequence::tuple;
22use nom::IResult;
23use serde::{Deserialize, Serialize};
24use std::{
25    io::{Error, ErrorKind, Result},
26    str::FromStr,
27};
28
29/// The only know representation of PartNumber
30#[derive(Debug, PartialEq, Hash, Eq, Clone, Serialize, Deserialize)]
31pub struct PnSimple {
32    kind: u16,
33    hw_kind: u8,
34    hw_id: u16,
35    rel: u8,
36}
37
38/// PartNumber could represent, software, device, or part of a device.
39#[derive(Debug, PartialEq, Hash, Eq, Clone, Serialize, Deserialize)]
40//TODO Simple is not good, I need to check more PNs.
41pub enum PartNumber {
42    /// The simple AAA-BCCCC-DD format
43    Simple(PnSimple),
44}
45impl PartNumber {
46    fn parse(input: &[u8]) -> IResult<&[u8], Self> {
47        //parsers
48        let sep = tag(b"-");
49        let is_kind = take_while_m_n(3, 3, is_digit);
50        let hw_kind = take(1usize);
51        let is_hw_id = take_while_m_n(4, 4, is_digit);
52        let hw_id = map_res(is_hw_id, |x: &[u8]| {
53            u16::from_str(&String::from_utf8_lossy(x))
54        });
55        let is_rel = take_while_m_n(2, 2, is_digit);
56
57        let (input, kind) = map_res(is_kind, |x: &[u8]| {
58            u16::from_str(&String::from_utf8_lossy(x))
59        })(input)?;
60        let (input, _) = sep(input)?;
61        let (input, (hw_kind, hw_id)) = tuple((hw_kind, hw_id))(input)?;
62        let (input, _) = sep(input)?;
63        let (input, rel) = map_res(is_rel, |x: &[u8]| {
64            u8::from_str(&String::from_utf8_lossy(x))
65        })(input)?;
66
67        Ok((
68            input,
69            PartNumber::Simple(PnSimple {
70                kind,
71                hw_kind: hw_kind[0] - b'0',
72                hw_id,
73                rel,
74            }),
75        ))
76    }
77
78    pub fn from_raw<B: ByteOrder>(x: &[u8]) -> Result<(&[u8], PartNumber)> {
79        if x.len() < 9 {
80            Err(Error::new(
81                ErrorKind::InvalidData,
82                "Part number buffer too small",
83            ))
84        } else {
85            const fn base6(x: u128, byte: u8) -> u8 {
86                (((x & (0b111111 << (6 * byte))) >> (6 * byte)) & 0xffu128)
87                    as u8
88            }
89            const fn get_value(x: u128) -> [u8; 12] {
90                let (mut ret, mut i) = ([0; 12], 0);
91                while i < 12 {
92                    ret[i] = base6(x, 11 - i as u8).wrapping_add(0x20);
93                    i += 1;
94                }
95                ret
96            }
97            let num = B::read_uint128(x, 9);
98            let buff = get_value(num);
99            let (_, ret) = PartNumber::parse(&buff).map_err(|_| {
100                Error::new(ErrorKind::InvalidData, "Unable to parse PartNumber")
101            })?;
102            Ok((&x[9..], ret))
103        }
104    }
105
106    pub fn from_str(s: &str) -> Result<Self> {
107        let bytes = s.as_bytes();
108        if bytes.len() < 12 {
109            return Err(Error::new(
110                ErrorKind::InvalidData,
111                "PartNumber Invalid size",
112            ));
113        }
114        let (_, ret) = PartNumber::parse(s.as_bytes()).map_err(|_| {
115            Error::new(ErrorKind::InvalidData, "Unable to parse PartNumber")
116        })?;
117        Ok(ret)
118    }
119}
120
121impl ToString for PartNumber {
122    fn to_string(&self) -> String {
123        match self {
124            PartNumber::Simple(PnSimple {
125                kind,
126                hw_kind,
127                hw_id,
128                rel,
129            }) => format!("{:03}-{}{:04}-{:02}", kind, hw_kind, hw_id, rel),
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use crate::PartNumber;
137
138    /// Check if Part number is decoding raw data correctly
139    #[test]
140    fn part_number_from_bytes() {
141        let bytes_little: Vec<u8> =
142            vec![0x10, 0xD4, 0x5C, 0x13, 0x04, 0x45, 0x0D, 0x14, 0x41];
143        let bytes_big: Vec<u8> = bytes_little.iter().rev().copied().collect();
144
145        let (_, pn_little) =
146            PartNumber::from_raw::<byteorder::LE>(&bytes_little).unwrap();
147        let (_, pn_big) =
148            PartNumber::from_raw::<byteorder::BE>(&bytes_big).unwrap();
149
150        for pn in [pn_little, pn_big].iter() {
151            assert_eq!(pn.to_string(), "010-10037-00");
152        }
153    }
154
155    /// Parse invalid text to partnumber
156    #[test]
157    #[should_panic]
158    fn part_number_invalid_str1() {
159        let text = "010-รง0037-00";
160        PartNumber::from_str(text).unwrap();
161    }
162    /// Parse invalid text to partnumber
163    #[test]
164    #[should_panic]
165    fn part_number_invalid_str2() {
166        let text = "010-0037-00";
167        PartNumber::from_str(text).unwrap();
168    }
169}