1use mirai_annotations::debug_checked_precondition;
9use serde::{Serialize, Serializer};
10use static_assertions::const_assert;
11use std::{fmt, str};
12use thiserror::Error;
13
14#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
21pub struct ShortHexStr([u8; ShortHexStr::LENGTH]);
22
23#[derive(Error, Debug)]
24#[error("Input bytes are too short")]
25pub struct InputTooShortError;
26
27impl ShortHexStr {
28 pub const LENGTH: usize = 2 * ShortHexStr::SOURCE_LENGTH;
29 pub const SOURCE_LENGTH: usize = 4;
30
31 pub fn try_from_bytes(
36 src_bytes: &[u8],
37 ) -> Result<ShortHexStr, InputTooShortError> {
38 if src_bytes.len() >= ShortHexStr::SOURCE_LENGTH {
39 let src_short_bytes = &src_bytes[0..ShortHexStr::SOURCE_LENGTH];
40 let mut dest_bytes = [0u8; ShortHexStr::LENGTH];
41
42 hex_encode(&src_short_bytes, &mut dest_bytes);
46 Ok(Self(dest_bytes))
47 } else {
48 Err(InputTooShortError)
49 }
50 }
51
52 pub fn as_str(&self) -> &str {
53 str::from_utf8(&self.0).expect(
57 "This can never fail since &self.0 will only ever contain the \
58 following characters: '0123456789abcdef', which are all valid \
59 ASCII characters and therefore all valid UTF-8",
60 )
61 }
62}
63
64impl fmt::Debug for ShortHexStr {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str(self.as_str())
67 }
68}
69
70impl fmt::Display for ShortHexStr {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.write_str(self.as_str())
73 }
74}
75
76impl Serialize for ShortHexStr {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where S: Serializer {
79 serializer.serialize_str(self.as_str())
80 }
81}
82
83const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
85
86#[inline(always)]
89fn byte2hex(byte: u8) -> (u8, u8) {
90 let hi = HEX_CHARS_LOWER[((byte >> 4) & 0x0f) as usize];
91 let lo = HEX_CHARS_LOWER[(byte & 0x0f) as usize];
92 (hi, lo)
93}
94
95#[inline(always)]
97fn hex_encode(src: &[u8], dst: &mut [u8]) {
98 debug_checked_precondition!(dst.len() == 2 * src.len());
99
100 for (byte, out) in src.iter().zip(dst.chunks_mut(2)) {
101 let (hi, lo) = byte2hex(*byte);
102 out[0] = hi;
103 out[1] = lo;
104 }
105}
106
107pub trait AsShortHexStr {
108 fn short_str(&self) -> ShortHexStr;
109}
110
111impl AsShortHexStr for [u8; 16] {
112 fn short_str(&self) -> ShortHexStr {
113 const_assert!(16 >= ShortHexStr::SOURCE_LENGTH);
114 ShortHexStr::try_from_bytes(self).expect(
115 "This can never fail since 16 >= ShortHexStr::SOURCE_LENGTH",
116 )
117 }
118}
119
120impl AsShortHexStr for [u8; 32] {
121 fn short_str(&self) -> ShortHexStr {
122 const_assert!(32 >= ShortHexStr::SOURCE_LENGTH);
123 ShortHexStr::try_from_bytes(self).expect(
124 "This can never fail since 32 >= ShortHexStr::SOURCE_LENGTH",
125 )
126 }
127}
128
129#[cfg(test)]
130mod test {
131 use super::*;
132 use proptest::prelude::*;
133 use std::{str, u8};
134
135 #[test]
136 fn test_hex_encode() {
137 let src = [0x12_u8, 0x34, 0xfe, 0xba];
138 let mut actual = [0u8; 8];
139 hex_encode(&src, &mut actual);
140 let expected = b"1234feba";
141 assert_eq!(&actual, expected);
142 }
143
144 #[test]
145 fn test_byte2hex_equivalence() {
146 for byte in 0..=u8::MAX {
147 let (hi, lo) = byte2hex(byte);
148 let formatted_bytes = [hi, lo];
149 let actual = str::from_utf8(&formatted_bytes[..]).unwrap();
150 let expected = hex::encode(&[byte][..]);
151 assert_eq!(actual, expected.as_str());
152 }
153 }
154
155 proptest! {
156 #[test]
157 fn test_address_short_str_equivalence(addr in any::<[u8; 16]>()) {
158 let short_str_old = hex::encode(&addr[0..ShortHexStr::SOURCE_LENGTH]);
159 let short_str_new = ShortHexStr::try_from_bytes(&addr).unwrap();
160 prop_assert_eq!(short_str_old.as_str(), short_str_new.as_str());
161 }
162 }
163}