ethportal_api/utils/
bytes.rs1use hex::FromHexError;
2use rand::{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 rand::thread_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 rand::thread_rng().gen_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 rand::thread_rng()
79 .gen_range(min_nonzero_byte_value..min_nonzero_byte_value.saturating_mul(2))
80 };
81
82 bytes
83}
84
85#[cfg(test)]
86#[allow(clippy::unwrap_used)]
87mod test {
88 use super::*;
89
90 #[test]
91 fn test_hex_encode() {
92 let to_encode = vec![176, 15];
93 let encoded = hex_encode(to_encode);
94 assert_eq!(encoded, "0xb00f");
95 }
96
97 #[test]
98 fn test_hex_decode() {
99 let to_decode = "0xb00f";
100 let decoded = hex_decode(to_decode).unwrap();
101 assert_eq!(decoded, vec![176, 15]);
102 }
103
104 #[test]
105 fn test_hex_decode_invalid_start() {
106 let to_decode = "b00f";
107 let result = hex_decode(to_decode);
108 assert!(result.is_err());
109 let error = result.unwrap_err();
110 assert_eq!(
111 error.to_string(),
112 "Hex string starts with b0, expected 0x".to_string()
113 );
114 assert_eq!(
115 error,
116 ByteUtilsError::WrongPrefix {
117 first_two: "b0".to_string()
118 }
119 );
120 }
121
122 #[test]
123 fn test_hex_decode_invalid_char() {
124 let to_decode = "0xb00g";
125 let result = hex_decode(to_decode);
126 assert!(result.is_err());
127 let error = result.unwrap_err();
128 assert_eq!(
129 error.to_string(),
130 "Unable to decode hex string 0xb00g due to Invalid character 'g' at position 3"
131 .to_string()
132 );
133 assert_eq!(
134 error,
135 ByteUtilsError::HexDecode {
136 source: FromHexError::InvalidHexCharacter { c: 'g', index: 3 },
137 data: "0xb00g".to_string()
138 }
139 );
140 }
141
142 #[test]
143 fn test_hex_decode_empty_string() {
144 let to_decode = "";
145 let result = hex_decode(to_decode);
146 assert!(result.is_err());
147 let error = result.unwrap_err();
148 assert_eq!(
149 error.to_string(),
150 "Hex string is '', expected to start with 0x".to_string()
151 );
152 assert_eq!(
153 error,
154 ByteUtilsError::NoPrefix {
155 data: "".to_string()
156 }
157 );
158 }
159
160 #[test]
161 fn test_hex_decode_no_prefix() {
162 let to_decode = "0";
163 let result = hex_decode(to_decode);
164 assert!(result.is_err());
165 let error = result.unwrap_err();
166 assert_eq!(
167 error.to_string(),
168 "Hex string is '0', expected to start with 0x".to_string()
169 );
170 assert_eq!(
171 error,
172 ByteUtilsError::NoPrefix {
173 data: "0".to_string()
174 }
175 );
176 }
177
178 #[test]
179 fn test_hex_decode_prefix_only_returns_empty_byte_vector() {
180 let to_decode = "0x";
181 let result = hex_decode(to_decode).unwrap();
182 assert_eq!(result, vec![] as Vec<u8>);
183 assert_eq!(hex::decode("").unwrap(), vec![] as Vec<u8>);
185 }
186
187 #[test]
188 fn test_hex_decode_odd_count() {
189 let to_decode = "0x0";
190 let result = hex_decode(to_decode);
191 assert!(result.is_err());
192 let error = result.unwrap_err();
193 assert_eq!(
194 error.to_string(),
195 "Unable to decode hex string 0x0 due to Odd number of digits".to_string()
196 );
197 assert_eq!(
198 error,
199 ByteUtilsError::HexDecode {
200 source: FromHexError::OddLength,
201 data: "0x0".to_string()
202 }
203 );
204 }
205
206 #[test]
207 fn test_random_32byte_array_1() {
208 let bytes = random_32byte_array(17);
209
210 assert_eq!(bytes.len(), 32);
211 assert_eq!(bytes[0..2], vec![0, 0]);
212 assert!((bytes[2] >= 64) && (bytes[2] < 128));
213 }
214
215 #[test]
216 fn test_random_32byte_array_2() {
217 let bytes = random_32byte_array(16);
218
219 assert_eq!(bytes.len(), 32);
220 assert_eq!(bytes[0..2], vec![0, 0]);
221 assert!(bytes[2] >= 128);
222 }
223
224 #[test]
225 fn test_random_32byte_array_3() {
226 let bytes = random_32byte_array(15);
227
228 assert_eq!(bytes.len(), 32);
229 assert_eq!(bytes[0], 0);
230 assert_eq!(bytes[1], 1);
231 }
232}