ethportal_api/utils/
bytes.rs1use hex::FromHexError;
2use rand::{rng, Rng, RngCore};
3use thiserror::Error;
4
5#[derive(Clone, Debug, Error, PartialEq)]
7pub enum ByteUtilsError {
8 #[error("Hex string starts with {first_two}, expected 0x")]
9 WrongPrefix { first_two: String },
10
11 #[error("Unable to decode hex string {data} due to {source}")]
12 HexDecode { source: FromHexError, data: String },
13
14 #[error("Hex string is '{data}', expected to start with 0x")]
15 NoPrefix { data: String },
16}
17
18pub fn hex_encode<T: AsRef<[u8]>>(data: T) -> String {
20 format!("0x{}", hex::encode(data))
21}
22
23pub fn hex_decode(data: &str) -> Result<Vec<u8>, ByteUtilsError> {
25 let first_two = data.get(..2).ok_or_else(|| ByteUtilsError::NoPrefix {
26 data: data.to_string(),
27 })?;
28
29 if first_two.to_lowercase() != "0x" {
30 return Err(ByteUtilsError::WrongPrefix {
31 first_two: first_two.to_string(),
32 });
33 }
34
35 let post_prefix = data.get(2..).unwrap_or("");
36
37 hex::decode(post_prefix).map_err(|e| ByteUtilsError::HexDecode {
38 source: e,
39 data: data.to_string(),
40 })
41}
42
43pub fn hex_encode_compact<T: AsRef<[u8]>>(data: T) -> String {
45 if data.as_ref().len() <= 8 {
46 hex_encode(data)
47 } else {
48 let hex = hex::encode(data);
49 format!("0x{}..{}", &hex[0..4], &hex[hex.len() - 4..])
50 }
51}
52
53pub fn hex_encode_upper<T: AsRef<[u8]>>(data: T) -> String {
55 format!("0x{}", hex::encode_upper(data))
56}
57
58pub fn random_32byte_array(leading_bit_zeros: u8) -> [u8; 32] {
60 let first_zero_bytes: usize = leading_bit_zeros as usize / 8;
61 let first_nonzero_byte_leading_zeros = leading_bit_zeros % 8u8;
62
63 let mut bytes = [0; 32];
64 rng().fill_bytes(&mut bytes[first_zero_bytes..]);
65
66 if first_zero_bytes == 32 {
67 return bytes;
68 }
69
70 bytes[first_zero_bytes] = if first_nonzero_byte_leading_zeros == 0 {
71 rng().random_range(128..=255)
73 } else {
74 let min_nonzero_byte_value =
77 (128_f32 * 0.5_f32.powi(first_nonzero_byte_leading_zeros as i32)) as u8;
78 rng().random_range(min_nonzero_byte_value..min_nonzero_byte_value.saturating_mul(2))
79 };
80
81 bytes
82}
83
84#[cfg(test)]
85#[allow(clippy::unwrap_used)]
86mod test {
87 use super::*;
88
89 #[test]
90 fn test_hex_encode() {
91 let to_encode = vec![176, 15];
92 let encoded = hex_encode(to_encode);
93 assert_eq!(encoded, "0xb00f");
94 }
95
96 #[test]
97 fn test_hex_decode() {
98 let to_decode = "0xb00f";
99 let decoded = hex_decode(to_decode).unwrap();
100 assert_eq!(decoded, vec![176, 15]);
101 }
102
103 #[test]
104 fn test_hex_decode_invalid_start() {
105 let to_decode = "b00f";
106 let result = hex_decode(to_decode);
107 assert!(result.is_err());
108 let error = result.unwrap_err();
109 assert_eq!(
110 error.to_string(),
111 "Hex string starts with b0, expected 0x".to_string()
112 );
113 assert_eq!(
114 error,
115 ByteUtilsError::WrongPrefix {
116 first_two: "b0".to_string()
117 }
118 );
119 }
120
121 #[test]
122 fn test_hex_decode_invalid_char() {
123 let to_decode = "0xb00g";
124 let result = hex_decode(to_decode);
125 assert!(result.is_err());
126 let error = result.unwrap_err();
127 assert_eq!(
128 error.to_string(),
129 "Unable to decode hex string 0xb00g due to Invalid character 'g' at position 3"
130 .to_string()
131 );
132 assert_eq!(
133 error,
134 ByteUtilsError::HexDecode {
135 source: FromHexError::InvalidHexCharacter { c: 'g', index: 3 },
136 data: "0xb00g".to_string()
137 }
138 );
139 }
140
141 #[test]
142 fn test_hex_decode_empty_string() {
143 let to_decode = "";
144 let result = hex_decode(to_decode);
145 assert!(result.is_err());
146 let error = result.unwrap_err();
147 assert_eq!(
148 error.to_string(),
149 "Hex string is '', expected to start with 0x".to_string()
150 );
151 assert_eq!(
152 error,
153 ByteUtilsError::NoPrefix {
154 data: "".to_string()
155 }
156 );
157 }
158
159 #[test]
160 fn test_hex_decode_no_prefix() {
161 let to_decode = "0";
162 let result = hex_decode(to_decode);
163 assert!(result.is_err());
164 let error = result.unwrap_err();
165 assert_eq!(
166 error.to_string(),
167 "Hex string is '0', expected to start with 0x".to_string()
168 );
169 assert_eq!(
170 error,
171 ByteUtilsError::NoPrefix {
172 data: "0".to_string()
173 }
174 );
175 }
176
177 #[test]
178 fn test_hex_decode_prefix_only_returns_empty_byte_vector() {
179 let to_decode = "0x";
180 let result = hex_decode(to_decode).unwrap();
181 assert_eq!(result, vec![] as Vec<u8>);
182 assert_eq!(hex::decode("").unwrap(), vec![] as Vec<u8>);
184 }
185
186 #[test]
187 fn test_hex_decode_odd_count() {
188 let to_decode = "0x0";
189 let result = hex_decode(to_decode);
190 assert!(result.is_err());
191 let error = result.unwrap_err();
192 assert_eq!(
193 error.to_string(),
194 "Unable to decode hex string 0x0 due to Odd number of digits".to_string()
195 );
196 assert_eq!(
197 error,
198 ByteUtilsError::HexDecode {
199 source: FromHexError::OddLength,
200 data: "0x0".to_string()
201 }
202 );
203 }
204
205 #[test]
206 fn test_random_32byte_array_1() {
207 let bytes = random_32byte_array(17);
208
209 assert_eq!(bytes.len(), 32);
210 assert_eq!(bytes[0..2], vec![0, 0]);
211 assert!((bytes[2] >= 64) && (bytes[2] < 128));
212 }
213
214 #[test]
215 fn test_random_32byte_array_2() {
216 let bytes = random_32byte_array(16);
217
218 assert_eq!(bytes.len(), 32);
219 assert_eq!(bytes[0..2], vec![0, 0]);
220 assert!(bytes[2] >= 128);
221 }
222
223 #[test]
224 fn test_random_32byte_array_3() {
225 let bytes = random_32byte_array(15);
226
227 assert_eq!(bytes.len(), 32);
228 assert_eq!(bytes[0], 0);
229 assert_eq!(bytes[1], 1);
230 }
231}