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