stackforge_core/utils/
hex.rs1use std::fmt::Write;
4
5#[must_use]
14pub fn hexdump(data: &[u8]) -> String {
15 let mut output = String::new();
16 let mut offset = 0;
17
18 for chunk in data.chunks(16) {
19 write!(output, "{offset:08x} ").unwrap();
21
22 for (i, byte) in chunk.iter().enumerate() {
24 if i == 8 {
25 output.push(' ');
26 }
27 write!(output, "{byte:02x} ").unwrap();
28 }
29
30 if chunk.len() < 16 {
32 for i in chunk.len()..16 {
33 if i == 8 {
34 output.push(' ');
35 }
36 output.push_str(" ");
37 }
38 }
39
40 output.push(' ');
42 output.push('|');
43 for byte in chunk {
44 if byte.is_ascii_graphic() || *byte == b' ' {
45 output.push(*byte as char);
46 } else {
47 output.push('.');
48 }
49 }
50 output.push('|');
51 output.push('\n');
52
53 offset += 16;
54 }
55
56 output
57}
58
59#[must_use]
61pub fn hexstr(data: &[u8]) -> String {
62 let mut output = String::with_capacity(data.len() * 2);
63 for byte in data {
64 write!(output, "{byte:02x}").unwrap();
65 }
66 output
67}
68
69#[must_use]
71pub fn hexstr_sep(data: &[u8], sep: &str) -> String {
72 data.iter()
73 .map(|b| format!("{b:02x}"))
74 .collect::<Vec<_>>()
75 .join(sep)
76}
77
78pub fn parse_hex(s: &str) -> Result<Vec<u8>, String> {
80 let s = s.trim().replace([' ', ':', '-'], "");
81
82 if !s.len().is_multiple_of(2) {
83 return Err("hex string must have even length".to_string());
84 }
85
86 (0..s.len())
87 .step_by(2)
88 .map(|i| {
89 u8::from_str_radix(&s[i..i + 2], 16)
90 .map_err(|e| format!("invalid hex at position {i}: {e}"))
91 })
92 .collect()
93}
94
95#[must_use]
97pub fn pretty_bytes(data: &[u8], indent: usize) -> String {
98 let indent_str = " ".repeat(indent);
99 let mut output = String::new();
100
101 for (i, chunk) in data.chunks(16).enumerate() {
102 if i > 0 {
103 output.push('\n');
104 }
105 output.push_str(&indent_str);
106
107 for byte in chunk {
108 write!(output, "{byte:02x} ").unwrap();
109 }
110 }
111
112 output
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_hexdump() {
121 let data = b"Hello, World!";
122 let dump = hexdump(data);
123 assert!(dump.contains("48 65 6c 6c")); assert!(dump.contains("|Hello, World!|"));
125 }
126
127 #[test]
128 fn test_hexstr() {
129 let data = [0xde, 0xad, 0xbe, 0xef];
130 assert_eq!(hexstr(&data), "deadbeef");
131 assert_eq!(hexstr_sep(&data, ":"), "de:ad:be:ef");
132 }
133
134 #[test]
135 fn test_parse_hex() {
136 assert_eq!(parse_hex("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
137 assert_eq!(
138 parse_hex("de:ad:be:ef").unwrap(),
139 vec![0xde, 0xad, 0xbe, 0xef]
140 );
141 assert_eq!(
142 parse_hex("de ad be ef").unwrap(),
143 vec![0xde, 0xad, 0xbe, 0xef]
144 );
145 assert!(parse_hex("dea").is_err()); }
147}