commonware_formatting/
lib.rs1#![doc(
4 html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
5 html_favicon_url = "https://commonware.xyz/favicon.ico"
6)]
7#![cfg_attr(not(any(feature = "std", test)), no_std)]
8
9commonware_macros::stability_mod!(BETA, pub mod hex_literal);
13
14commonware_macros::stability_scope!(BETA {
15 extern crate alloc;
16
17 use alloc::{string::String, vec::Vec};
18 use core::fmt;
19
20 pub fn hex(bytes: &[u8]) -> String {
22 const_hex::encode(bytes)
23 }
24
25 pub fn from_hex(s: &str) -> Option<Vec<u8>> {
28 let s = s.replace(['\t', '\n', '\r', ' '], "");
30 let stripped = s.strip_prefix("0X").unwrap_or(&s);
31 const_hex::decode(stripped).ok()
32 }
33
34 pub struct Hex<T: AsRef<[u8]>>(pub T);
55
56 impl<T: AsRef<[u8]>> From<T> for Hex<T> {
57 fn from(value: T) -> Self {
58 Self(value)
59 }
60 }
61
62 impl<T: AsRef<[u8]>> fmt::Display for Hex<T> {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write_hex(self.0.as_ref(), f)
65 }
66 }
67
68 impl<T: AsRef<[u8]>> fmt::Debug for Hex<T> {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write_hex(self.0.as_ref(), f)
71 }
72 }
73
74 fn write_hex(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 const CHUNK: usize = 64;
81 let mut buf = [0u8; CHUNK * 2];
82 for slice in bytes.chunks(CHUNK) {
83 let out = &mut buf[..slice.len() * 2];
84 const_hex::encode_to_slice(slice, out).expect("slice fits in buffer");
85 let s = unsafe { core::str::from_utf8_unchecked(out) };
87 f.write_str(s)?;
88 }
89 Ok(())
90 }
91});
92
93#[cfg(test)]
94mod tests {
95 use crate::{from_hex, Hex};
96
97 #[test]
98 fn test_hex_roundtrip() {
99 for (bytes, encoded) in [
100 (&[][..], ""),
101 (&[0x01][..], "01"),
102 (&[0x01, 0x02, 0x03][..], "010203"),
103 ] {
104 assert_eq!(crate::hex(bytes), encoded);
105 assert_eq!(from_hex(encoded).unwrap(), bytes.to_vec());
106 }
107 }
108
109 #[test]
110 fn test_from_hex() {
111 let expected: Vec<u8> = vec![0x01, 0x02, 0x03];
112
113 assert_eq!(from_hex("010203").unwrap(), expected);
115
116 assert_eq!(from_hex("01 02 03").unwrap(), expected);
118
119 assert_eq!(from_hex("0x010203").unwrap(), expected);
121
122 assert_eq!(from_hex("0X010203").unwrap(), expected);
124
125 let h = " \n\n0x\r\n01
127 02\t03\n";
128 assert_eq!(from_hex(h).unwrap(), expected);
129
130 assert_eq!(from_hex(""), Some(vec![]));
132
133 assert!(from_hex("0102030").is_none());
135
136 assert!(from_hex("01g3").is_none());
138
139 assert!(from_hex("+123").is_none());
141 }
142
143 #[test]
144 fn test_from_hex_utf8_char_boundaries() {
145 const MISALIGNMENT_CASE: &str = "쀘\n";
147 assert!(from_hex(MISALIGNMENT_CASE).is_none());
148 }
149
150 #[test]
151 fn test_hex_newtype_display() {
152 let bytes = [0x01u8, 0x02, 0xab, 0xcd];
153 let s = format!("{}", Hex(&bytes[..]));
154 assert_eq!(s, "0102abcd");
155
156 let v = bytes.to_vec();
158 assert_eq!(format!("{}", Hex(v)), "0102abcd");
159
160 assert_eq!(format!("{}", Hex::<&[u8]>(&[])), "");
162
163 let big: Vec<u8> = (0..200u16).map(|i| i as u8).collect();
165 let formatted = format!("{}", Hex(&big));
166 assert_eq!(formatted, super::hex(&big));
167 }
168
169 #[test]
170 fn test_hex_newtype_debug() {
171 let bytes = [0xff, 0x00];
172 assert_eq!(format!("{:?}", Hex(&bytes[..])), "ff00");
173 }
174
175 #[test]
176 fn test_hex_newtype_from() {
177 let bytes = [0x01u8, 0x02, 0xab, 0xcd];
178
179 let from_slice: Hex<&[u8]> = (&bytes[..]).into();
181 assert_eq!(format!("{from_slice}"), "0102abcd");
182 let from_owned: Hex<Vec<u8>> = bytes.to_vec().into();
183 assert_eq!(format!("{from_owned}"), "0102abcd");
184 }
185}