usb_rfid_decoder/
lib.rs

1use std::collections::HashMap;
2enum HidCharacter {
3    Shift,
4    CarriageReturn,
5    Character(char),
6    Unknown,
7}
8
9fn decode_char(char: u8, shift: bool) -> HidCharacter {
10    if 0x28 == char {
11        return HidCharacter::CarriageReturn;
12    } else if 0x02 == char {
13        return HidCharacter::Shift;
14    }
15
16    let hid: HashMap<u8, char> = [
17        (4, 'a'),
18        (5, 'b'),
19        (6, 'c'),
20        (7, 'd'),
21        (8, 'e'),
22        (9, 'f'),
23        (10, 'g'),
24        (11, 'h'),
25        (12, 'i'),
26        (13, 'j'),
27        (14, 'k'),
28        (15, 'l'),
29        (16, 'm'),
30        (17, 'n'),
31        (18, 'o'),
32        (19, 'p'),
33        (20, 'q'),
34        (21, 'r'),
35        (22, 's'),
36        (23, 't'),
37        (24, 'u'),
38        (25, 'v'),
39        (26, 'w'),
40        (27, 'x'),
41        (28, 'y'),
42        (29, 'z'),
43        (30, '1'),
44        (31, '2'),
45        (32, '3'),
46        (33, '4'),
47        (34, '5'),
48        (35, '6'),
49        (36, '7'),
50        (37, '8'),
51        (38, '9'),
52        (39, '0'),
53        (44, ' '),
54        (45, '-'),
55        (46, '='),
56        (47, '['),
57        (48, ']'),
58        (49, '\\'),
59        (51, ';'),
60        (52, '\''),
61        (53, '~'),
62        (54, ','),
63        (55, '.'),
64        (56, '/'),
65    ]
66    .iter()
67    .cloned()
68    .collect();
69
70    let hid2: HashMap<u8, char> = [
71        (4, 'A'),
72        (5, 'B'),
73        (6, 'C'),
74        (7, 'D'),
75        (8, 'E'),
76        (9, 'F'),
77        (10, 'G'),
78        (11, 'H'),
79        (12, 'I'),
80        (13, 'J'),
81        (14, 'K'),
82        (15, 'L'),
83        (16, 'M'),
84        (17, 'N'),
85        (18, 'O'),
86        (19, 'P'),
87        (20, 'Q'),
88        (21, 'R'),
89        (22, 'S'),
90        (23, 'T'),
91        (24, 'U'),
92        (25, 'V'),
93        (26, 'W'),
94        (27, 'X'),
95        (28, 'Y'),
96        (29, 'Z'),
97        (30, '!'),
98        (31, '@'),
99        (32, '#'),
100        (33, '$'),
101        (34, '%'),
102        (35, '^'),
103        (36, '&'),
104        (37, '*'),
105        (38, '('),
106        (39, ')'),
107        (44, ' '),
108        (45, '_'),
109        (46, '+'),
110        (47, '{'),
111        (48, '}'),
112        (49, '|'),
113        (51, ':'),
114        (52, '"'),
115        (53, '~'),
116        (54, '<'),
117        (55, '>'),
118        (56, '?'),
119    ]
120    .iter()
121    .cloned()
122    .collect();
123
124    let map = if shift { &hid2 } else { &hid };
125
126    match map.get(&char) {
127        Some(c) => HidCharacter::Character(*c),
128        None => HidCharacter::Unknown,
129    }
130}
131
132pub fn decode(card: &[u8]) -> Result<String, String> {
133    let mut result = String::new();
134    let mut shift = false;
135    for character in card.iter() {
136        let character = *character;
137        if character != 0 {
138            match decode_char(character, shift) {
139                HidCharacter::Shift => {
140                    shift = !shift;
141                }
142                HidCharacter::CarriageReturn => return Ok(result),
143                HidCharacter::Character(c) => {
144                    result.push_str(&format!("{}", c));
145                }
146                HidCharacter::Unknown => {
147                    return Err(format!("Unknown character: {}", character));
148                }
149            }
150        }
151    }
152    Err("No carriage return found".to_string())
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn valid_card() {
161        const CARD: [u8; 88] = [
162            0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00,
163            0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
164            0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
165            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
166            0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00,
167            0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
168            0x00, 0x00, 0x00, 0x00,
169        ];
170        let result = decode(&CARD);
171        assert_eq!(result, Ok("0002530785".to_string()));
172    }
173    #[test]
174    fn invalid_card() {
175        const CARD: [u8; 88] = [
176            0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00,
177            0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
178            0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
179            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180            0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00,
181            0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
182            0x00, 0x00, 0x00, 0x00,
183        ];
184        let result = decode(&CARD);
185        assert_eq!(result, Err("Unknown character: 3".to_string()));
186    }
187    #[test]
188    fn no_carriage_return() {
189        const CARD: [u8; 88] = [
190            0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00,
191            0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
192            0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00,
193            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
194            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
195            0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00,
196            0x00, 0x00, 0x00, 0x00,
197        ];
198        let result = decode(&CARD);
199        assert_eq!(result, Err("No carriage return found".to_string()));
200    }
201}