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