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
31 .strip_prefix("0X")
32 .unwrap_or(&s);
33 const_hex::decode(stripped).ok()
34 }
35
36 pub struct Hex<T: AsRef<[u8]>>(pub T);
57
58 impl<T: AsRef<[u8]>> From<T> for Hex<T> {
59 fn from(value: T) -> Self {
60 Self(value)
61 }
62 }
63
64 impl<T: AsRef<[u8]>> fmt::Display for Hex<T> {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write_hex(self.0.as_ref(), f)
67 }
68 }
69
70 impl<T: AsRef<[u8]>> fmt::Debug for Hex<T> {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write_hex(self.0.as_ref(), f)
73 }
74 }
75
76 fn write_hex(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 const CHUNK: usize = 64;
83 let mut buf = [0u8; CHUNK * 2];
84 for slice in bytes.chunks(CHUNK) {
85 let out = &mut buf[..slice.len() * 2];
86 const_hex::encode_to_slice(slice, out).expect("slice fits in buffer");
87 let s = unsafe { core::str::from_utf8_unchecked(out) };
89 f.write_str(s)?;
90 }
91 Ok(())
92 }
93});
94
95#[cfg(test)]
96mod tests {
97 use crate::{from_hex, Hex};
98
99 #[test]
100 fn test_hex_roundtrip() {
101 for (bytes, encoded) in [
102 (&[][..], ""),
103 (&[0x01][..], "01"),
104 (&[0x01, 0x02, 0x03][..], "010203"),
105 ] {
106 assert_eq!(crate::hex(bytes), encoded);
107 assert_eq!(from_hex(encoded).unwrap(), bytes.to_vec());
108 }
109 }
110
111 #[test]
112 fn test_from_hex() {
113 let expected: Vec<u8> = vec![0x01, 0x02, 0x03];
114
115 assert_eq!(from_hex("010203").unwrap(), expected);
117
118 assert_eq!(from_hex("01 02 03").unwrap(), expected);
120
121 assert_eq!(from_hex("0x010203").unwrap(), expected);
123
124 assert_eq!(from_hex("0X010203").unwrap(), expected);
126
127 let h = " \n\n0x\r\n01
129 02\t03\n";
130 assert_eq!(from_hex(h).unwrap(), expected);
131
132 assert_eq!(from_hex(""), Some(vec![]));
134
135 assert!(from_hex("0102030").is_none());
137
138 assert!(from_hex("01g3").is_none());
140
141 assert!(from_hex("+123").is_none());
143 }
144
145 #[test]
146 fn test_from_hex_utf8_char_boundaries() {
147 const MISALIGNMENT_CASE: &str = "쀘\n";
149 assert!(from_hex(MISALIGNMENT_CASE).is_none());
150 }
151
152 #[test]
153 fn test_hex_newtype_display() {
154 let bytes = [0x01u8, 0x02, 0xab, 0xcd];
155 let s = format!("{}", Hex(&bytes[..]));
156 assert_eq!(s, "0102abcd");
157
158 let v = bytes.to_vec();
160 assert_eq!(format!("{}", Hex(v)), "0102abcd");
161
162 assert_eq!(format!("{}", Hex::<&[u8]>(&[])), "");
164
165 let big: Vec<u8> = (0..200u16).map(|i| i as u8).collect();
167 let formatted = format!("{}", Hex(&big));
168 assert_eq!(formatted, super::hex(&big));
169 }
170
171 #[test]
172 fn test_hex_newtype_debug() {
173 let bytes = [0xff, 0x00];
174 assert_eq!(format!("{:?}", Hex(&bytes[..])), "ff00");
175 }
176
177 #[test]
178 fn test_hex_newtype_from() {
179 let bytes = [0x01u8, 0x02, 0xab, 0xcd];
180
181 let from_slice: Hex<&[u8]> = (&bytes[..]).into();
183 assert_eq!(format!("{from_slice}"), "0102abcd");
184 let from_owned: Hex<Vec<u8>> = bytes.to_vec().into();
185 assert_eq!(format!("{from_owned}"), "0102abcd");
186 }
187}