forest/utils/encoding/
hex.rs1#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
10#[error(transparent)]
11pub struct DecodeError(#[from] faster_hex_private::Error);
12
13pub fn encode(data: impl AsRef<[u8]>) -> String {
15 faster_hex_private::hex_string(data.as_ref())
16}
17
18pub fn encode_prefixed(data: impl AsRef<[u8]>) -> String {
20 let data = data.as_ref();
21 let mut buf = vec![0; 2 + data.len() * 2];
22 let (prefix, digits) = buf.split_at_mut(2);
23 prefix.copy_from_slice(b"0x");
24 faster_hex_private::hex_encode(data, digits).expect("output buffer is sized to fit");
25 debug_assert!(buf.is_ascii());
26 unsafe { String::from_utf8_unchecked(buf) }
28}
29
30pub fn decode(input: impl AsRef<[u8]>) -> Result<Vec<u8>, DecodeError> {
32 let input = input.as_ref();
33 let mut out = vec![0; input.len() / 2];
34 faster_hex_private::hex_decode(input, &mut out)?;
35 Ok(out)
36}
37
38pub mod serde {
41 use serde::{Deserialize as _, Deserializer, Serializer, de};
42
43 pub fn serialize<S: Serializer>(
44 data: impl AsRef<[u8]>,
45 serializer: S,
46 ) -> Result<S::Ok, S::Error> {
47 serializer.serialize_str(&super::encode(data))
48 }
49
50 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
51 where
52 D: Deserializer<'de>,
53 T: TryFrom<Vec<u8>>,
54 {
55 let s = String::deserialize(deserializer)?;
56 let bytes = super::decode(&s).map_err(de::Error::custom)?;
57 let len = bytes.len();
58 T::try_from(bytes).map_err(|_| de::Error::custom(format!("invalid length {len}")))
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use quickcheck_macros::quickcheck;
66
67 #[quickcheck]
68 fn encode_matches_hex_crate(data: Vec<u8>) -> bool {
69 encode(&data) == ::hex::encode(&data)
70 }
71
72 #[quickcheck]
73 fn encode_prefixed_is_ascii_with_prefix(data: Vec<u8>) -> bool {
74 let s = encode_prefixed(&data);
75 s.is_ascii() && s.strip_prefix("0x") == Some(encode(&data).as_str())
76 }
77
78 #[quickcheck]
81 fn decode_matches_hex_crate(data: Vec<u8>, junk: String) -> bool {
82 [::hex::encode(&data), junk]
83 .iter()
84 .all(|s| match (decode(s), ::hex::decode(s)) {
85 (Ok(ours), Ok(theirs)) => ours == theirs,
86 (Err(_), Err(_)) => true,
87 _ => false,
88 })
89 }
90
91 #[quickcheck]
92 fn decode_roundtrip_any_case(data: Vec<u8>, flips: Vec<bool>) -> bool {
93 let s: String = encode(&data)
94 .chars()
95 .zip(flips.into_iter().chain(std::iter::repeat(false)))
96 .map(|(c, up)| if up { c.to_ascii_uppercase() } else { c })
97 .collect();
98 decode(&s).unwrap() == data
99 }
100
101 #[quickcheck]
102 fn decode_no_panic(input: Vec<u8>) {
103 let _ = decode(&input);
104 }
105
106 #[quickcheck]
107 fn serde_matches_hex_crate(data: Vec<u8>) -> bool {
108 #[derive(::serde::Serialize, ::serde::Deserialize)]
109 struct Ours(#[serde(with = "crate::utils::encoding::hex::serde")] Vec<u8>);
110 #[derive(::serde::Serialize)]
111 struct Theirs(#[serde(with = "::hex::serde")] Vec<u8>);
112
113 let ours = serde_json::to_string(&Ours(data.clone())).unwrap();
114 ours == serde_json::to_string(&Theirs(data.clone())).unwrap()
115 && serde_json::from_str::<Ours>(&ours).unwrap().0 == data
116 }
117
118 #[test]
119 fn encode_matches_expectations() {
120 assert_eq!(encode([]), "");
121 assert_eq!(encode([0x00, 0xab, 0xff]), "00abff");
122 assert_eq!(encode_prefixed([]), "0x");
123 assert_eq!(encode_prefixed([0x00, 0xab, 0xff]), "0x00abff");
124 }
125
126 #[test]
127 fn decode_accepts_mixed_case() {
128 assert_eq!(decode("").unwrap(), Vec::<u8>::new());
129 assert_eq!(decode("00abff").unwrap(), [0x00, 0xab, 0xff]);
130 assert_eq!(decode("00AbFF").unwrap(), [0x00, 0xab, 0xff]);
131 }
132
133 #[test]
134 fn decode_rejects_invalid_input() {
135 for invalid in ["abc", "00gg", "0x00"] {
136 assert!(decode(invalid).is_err(), "{invalid:?} should be rejected");
137 }
138 }
139}