scale_value/string_impls/custom_parsers/
hex.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::prelude::*;
17use crate::{
18    stringify::{ParseError, ParseErrorKind},
19    Value,
20};
21
22/// Attempt to parse a hex string into a [`Value<()>`] (or more specifically,
23/// an unnamed composite).
24///
25/// - Returns an error if we see a leading `0x` and then see invalid hex
26///   characters after this.
27/// - Returns `None` if no `0x` is seen first.
28/// - Returns `Some(value)` if parsing was successful. In this case, the string
29///   reference given is wound forwards to consume what was parsed.
30pub fn parse_hex(s: &mut &str) -> Option<Result<Value<()>, ParseError>> {
31    if !s.starts_with("0x") {
32        return None;
33    }
34
35    let bytes = s.as_bytes();
36    let mut composite_values = vec![];
37
38    // find all valid hex chars after 0x:
39    let mut idx = 2;
40    let mut last_nibble = None;
41    loop {
42        // Break if we hit end of string.
43        let Some(b) = bytes.get(idx) else {
44            break;
45        };
46
47        // Break as soon as we hit some non-alphanumeric char.
48        if !b.is_ascii_alphanumeric() {
49            break;
50        }
51
52        // Turn 4-bit hex char into nibble:
53        let hex_nibble = match *b {
54            b'A'..=b'F' => b - b'A' + 10,
55            b'a'..=b'f' => b - b'a' + 10,
56            b'0'..=b'9' => b - b'0',
57            b => {
58                return Some(Err(
59                    ParseErrorKind::custom(ParseHexError::InvalidChar(b as char)).at(idx)
60                ))
61            }
62        };
63
64        match last_nibble {
65            None => {
66                // The first of 2 chars that make a single byte; keep hold of:
67                last_nibble = Some(hex_nibble)
68            }
69            Some(n) => {
70                // The second; combine and push byte to output:
71                let byte = n * 16 + hex_nibble;
72                composite_values.push(Value::u128(byte as u128));
73                last_nibble = None;
74            }
75        }
76
77        idx += 1;
78    }
79
80    // We have leftovers; wrong length!
81    if last_nibble.is_some() {
82        return Some(Err(ParseErrorKind::custom(ParseHexError::WrongLength).between(0, idx)));
83    }
84
85    // Consume the "used" up bytes and return our Value.
86    //
87    // # Safety
88    //
89    // We have consumed only ASCII chars to get this far, so
90    // we know the bytes following them make up a valid str.
91    *s = unsafe { core::str::from_utf8_unchecked(&bytes[idx..]) };
92    Some(Ok(Value::unnamed_composite(composite_values)))
93}
94
95#[derive(Debug, PartialEq, Clone, thiserror::Error)]
96#[allow(missing_docs)]
97pub enum ParseHexError {
98    #[error("Invalid hex character: {0}")]
99    InvalidChar(char),
100    #[error("Hex string is the wrong length; should be an even length")]
101    WrongLength,
102}
103
104#[cfg(test)]
105mod test {
106    use super::*;
107
108    #[test]
109    fn parses_same_as_hex_crate() {
110        let expects = ["0x", "0x00", "0x000102030405060708090A0B", "0xDEADBEEF", "0x00BAB10C"];
111
112        for input in expects {
113            let expected_hex = hex::decode(input.trim_start_matches("0x")).expect("valid hex");
114            let cursor = &mut &*input;
115            let hex = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
116
117            assert_eq!(hex, Value::from_bytes(expected_hex), "values should match");
118        }
119    }
120
121    #[test]
122    fn consumes_parsed_hex() {
123        let expects =
124            [("0x foo", " foo"), ("0x00,bar", ",bar"), ("0x123456-2", "-2"), ("0xDEADBEEF ", " ")];
125
126        for (input, expected_remaining) in expects {
127            let cursor = &mut &*input;
128            let _ = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
129
130            assert_eq!(*cursor, expected_remaining);
131        }
132    }
133
134    #[test]
135    fn err_wrong_length() {
136        let expects = ["0x1", "0x123"];
137
138        for input in expects {
139            let cursor = &mut &*input;
140            let err =
141                parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
142
143            assert_eq!(err.start_loc, 0);
144            assert_eq!(err.end_loc, Some(input.len()));
145
146            let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
147
148            assert_eq!(err, ParseHexError::WrongLength.to_string());
149            assert_eq!(input, *cursor);
150        }
151    }
152
153    #[test]
154    fn err_invalid_char() {
155        let expects = [("0x12345x", 'x', 7), ("0x123h4", 'h', 5), ("0xG23h4", 'G', 2)];
156
157        for (input, bad_char, pos) in expects {
158            let cursor = &mut &*input;
159            let err =
160                parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
161
162            assert_eq!(err.start_loc, pos);
163            assert!(err.end_loc.is_none());
164
165            let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
166
167            assert_eq!(err, ParseHexError::InvalidChar(bad_char).to_string());
168            assert_eq!(input, *cursor);
169        }
170    }
171
172    #[test]
173    fn empty_string_doesnt_panic() {
174        assert!(parse_hex(&mut "").is_none());
175        assert!(parse_hex(&mut "0").is_none());
176    }
177}