darkfi_sdk/
hex.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use crate::{util::Itertools, ContractError, GenericResult};
20
21/// Creates a hex formatted string of the data
22#[inline]
23pub fn hex_from_iter<I: Iterator<Item = u8>>(iter: I) -> String {
24    let mut repr = String::new();
25    for b in iter {
26        repr += &format!("{:02x}", b);
27    }
28    repr
29}
30
31/// Decode hex string into bytes
32pub fn decode_hex(hex: &str) -> HexDecodeIter {
33    HexDecodeIter { hex, curr: 0 }
34}
35
36pub struct HexDecodeIter<'a> {
37    hex: &'a str,
38    curr: usize,
39}
40
41impl Iterator for HexDecodeIter<'_> {
42    type Item = GenericResult<u8>;
43
44    // FromIterator auto converts [Result<u8>, ...] into Result<[u8, ...]>
45    // https://stackoverflow.com/a/26370894
46    fn next(&mut self) -> Option<Self::Item> {
47        // Stop iteration
48        if self.curr == self.hex.len() {
49            return None
50        }
51
52        // End of next 2 chars is past the end of the hex string
53        if self.curr + 2 > self.hex.len() {
54            return Some(Err(ContractError::HexFmtErr))
55        }
56
57        // Decode the next 2 chars
58        let Ok(byte) = u8::from_str_radix(&self.hex[self.curr..self.curr + 2], 16) else {
59            return Some(Err(ContractError::HexFmtErr))
60        };
61
62        self.curr += 2;
63
64        Some(Ok(byte))
65    }
66}
67
68pub fn decode_hex_arr<const N: usize>(hex: &str) -> GenericResult<[u8; N]> {
69    let decoded: Vec<_> = decode_hex(hex).try_collect()?;
70    let bytes: [u8; N] = decoded.try_into().map_err(|_| ContractError::HexFmtErr)?;
71    Ok(bytes)
72}
73
74pub trait AsHex {
75    fn hex(&self) -> String;
76}
77
78impl<T: AsRef<[u8]>> AsHex for T {
79    /// Creates a hex formatted string of the data (big endian)
80    fn hex(&self) -> String {
81        hex_from_iter(self.as_ref().iter().cloned())
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_hex_encode_decode() {
91        let decoded = decode_hex("0a00").collect::<GenericResult<Vec<_>>>().unwrap();
92        assert_eq!(decoded, vec![10, 0]);
93        assert!(decode_hex("0x").collect::<GenericResult<Vec<_>>>().is_err());
94        assert!(decode_hex("0a1").collect::<GenericResult<Vec<_>>>().is_err());
95        assert_eq!(hex_from_iter([10u8, 0].into_iter()), "0a00");
96    }
97}