1 use num_traits::{CheckedAdd, CheckedMul, FromPrimitive, Zero};
2 
3 #[derive(Debug)]
4 pub enum DecodeHexError {
5     NotAscii,
6     Empty,
7     Overflow,
8     InvalidOutput,
9 }
10 
11 /// Decode a GDB dex string into the specified integer.
12 ///
13 /// GDB hex strings may include "xx", which represent "missing" data. This
14 /// method simply treats "xx" as 0x00.
decode_hex<I>(buf: &[u8]) -> Result<I, DecodeHexError> where I: FromPrimitive + Zero + CheckedAdd + CheckedMul,15 pub fn decode_hex<I>(buf: &[u8]) -> Result<I, DecodeHexError>
16 where
17     I: FromPrimitive + Zero + CheckedAdd + CheckedMul,
18 {
19     use DecodeHexError::*;
20 
21     let radix = I::from_u8(16).ok_or(InvalidOutput)?;
22 
23     if buf.is_empty() {
24         return Err(Empty);
25     }
26 
27     let mut result = I::zero();
28 
29     for &digit in buf {
30         let x = I::from_u8(ascii2byte(digit).ok_or(NotAscii)?).ok_or(InvalidOutput)?;
31         result = result.checked_mul(&radix).ok_or(Overflow)?;
32         result = result.checked_add(&x).ok_or(Overflow)?
33     }
34 
35     Ok(result)
36 }
37 
38 #[derive(Debug)]
39 pub enum DecodeHexBufError {
40     NotAscii,
41 }
42 
ascii2byte(c: u8) -> Option<u8>43 fn ascii2byte(c: u8) -> Option<u8> {
44     match c {
45         b'0'..=b'9' => Some(c - b'0'),
46         b'a'..=b'f' => Some(c - b'a' + 10),
47         b'A'..=b'F' => Some(c - b'A' + 10),
48         b'x' | b'X' => Some(0),
49         _ => None,
50     }
51 }
52 
53 /// Check if the byte `c` is a valid GDB hex digit `[0-9][a-f][A-F][xX]`
54 #[allow(clippy::match_like_matches_macro)]
is_hex(c: u8) -> bool55 pub fn is_hex(c: u8) -> bool {
56     match c {
57         b'0'..=b'9' => true,
58         b'a'..=b'f' => true,
59         b'A'..=b'F' => true,
60         b'x' | b'X' => true,
61         _ => false,
62     }
63 }
64 
65 /// Decode a GDB hex string into a byte slice _in place_.
66 ///
67 /// GDB hex strings may include "xx", which represent "missing" data. This
68 /// method simply treats "xx" as 0x00.
69 // TODO: maybe don't blindly translate "xx" as 0x00?
70 // TODO: rewrite this method to elide bound checks
decode_hex_buf(base_buf: &mut [u8]) -> Result<&mut [u8], DecodeHexBufError>71 pub fn decode_hex_buf(base_buf: &mut [u8]) -> Result<&mut [u8], DecodeHexBufError> {
72     use DecodeHexBufError::*;
73 
74     let odd_adust = base_buf.len() % 2;
75     if odd_adust != 0 {
76         base_buf[0] = ascii2byte(base_buf[0]).ok_or(NotAscii)?;
77     }
78     let buf = &mut base_buf[odd_adust..];
79 
80     let decoded_len = buf.len() / 2;
81     for i in 0..decoded_len {
82         let b = ascii2byte(buf[i * 2]).ok_or(NotAscii)? << 4
83             | ascii2byte(buf[i * 2 + 1]).ok_or(NotAscii)?;
84         buf[i] = b as u8;
85     }
86 
87     Ok(&mut base_buf[..decoded_len + odd_adust])
88 }
89 
90 #[allow(dead_code)]
91 #[derive(Debug)]
92 pub enum EncodeHexBufError {
93     SmallBuffer,
94 }
95 
96 /// Encode a GDB hex string into a byte slice _in place_.
97 ///
98 /// The data to be encoded should be copied into the buffer from
99 /// `buf[start_idx..]`. The buffer itself must be at least `data.len() * 2`
100 /// bytes in size, as each byte is expanded into a two byte hex string.
101 #[allow(dead_code)]
encode_hex_buf(buf: &mut [u8], start_idx: usize) -> Result<&mut [u8], EncodeHexBufError>102 pub fn encode_hex_buf(buf: &mut [u8], start_idx: usize) -> Result<&mut [u8], EncodeHexBufError> {
103     use EncodeHexBufError::*;
104 
105     let len = buf.len() - start_idx;
106     let encoded_len = len * 2;
107 
108     if buf.len() < encoded_len {
109         return Err(SmallBuffer);
110     }
111 
112     for i in 0..encoded_len {
113         let byte = buf[start_idx + i / 2];
114         let nybble = if i % 2 == 0 {
115             // high
116             (byte & 0xf0) >> 4
117         } else {
118             // low
119             byte & 0x0f
120         };
121 
122         buf[i] = match nybble {
123             0x0..=0x9 => b'0' + nybble,
124             0xa..=0xf => b'A' + (nybble - 0xa),
125             _ => unreachable!(), // could be unreachable_unchecked...
126         };
127     }
128 
129     Ok(&mut buf[..encoded_len])
130 }
131 
132 #[cfg(test)]
133 mod tests {
134     use super::*;
135 
136     #[test]
encode_hex_simple()137     fn encode_hex_simple() {
138         let payload = [0xde, 0xad, 0xbe, 0xef];
139         let mut buf = [0; 16];
140 
141         let start_idx = buf.len() - payload.len();
142 
143         // copy the payload into the buffer
144         buf[start_idx..].copy_from_slice(&payload);
145         let out = encode_hex_buf(&mut buf, start_idx).unwrap();
146 
147         assert_eq!(out, b"DEADBEEF");
148     }
149 
150     #[test]
encode_hex_in_chunks()151     fn encode_hex_in_chunks() {
152         let payload = (0..=255).collect::<Vec<u8>>();
153         let mut out = Vec::new();
154 
155         let mut buf = [0; 30];
156 
157         for c in payload.chunks(15) {
158             let start_idx = buf.len() - c.len();
159 
160             let data_buf = &mut buf[start_idx..];
161             data_buf[..c.len()].copy_from_slice(c);
162             out.extend_from_slice(encode_hex_buf(&mut buf, start_idx).unwrap());
163         }
164 
165         let expect = (0..=255).map(|b| format!("{:02X?}", b)).collect::<String>();
166 
167         assert_eq!(out, expect.as_bytes())
168     }
169 
170     #[test]
decode_hex_buf_odd()171     fn decode_hex_buf_odd() {
172         let mut payload = b"ffffff4".to_vec();
173         let res = decode_hex_buf(&mut payload).unwrap();
174         assert_eq!(res, [0xf, 0xff, 0xff, 0xf4]);
175     }
176 
177     #[test]
decode_hex_buf_2()178     fn decode_hex_buf_2() {
179         let mut payload = b"12345".to_vec();
180         let res = decode_hex_buf(&mut payload).unwrap();
181         assert_eq!(res, [0x1, 0x23, 0x45]);
182     }
183 
184     #[test]
decode_hex_buf_short()185     fn decode_hex_buf_short() {
186         let mut payload = b"1".to_vec();
187         let res = decode_hex_buf(&mut payload).unwrap();
188         assert_eq!(res, [0x1]);
189     }
190 }
191