hsmtool/util/
escape.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum EscapeError {
10    #[error("Incomplete escape sequence")]
11    IncompleteEscape,
12    #[error("Invalid character in escape sequence")]
13    InvalidCharacter(char),
14    #[error("Invalid character in hex escape sequence")]
15    InvalidHexCharacter(char),
16}
17
18const HEX: &[u8; 16] = b"0123456789ABCDEF";
19
20pub fn escape(val: &[u8]) -> String {
21    let mut data = String::new();
22    for ch in val {
23        match ch {
24            b'\t' => data.push_str("\\t"),
25            b'\r' => data.push_str("\\r"),
26            b'\n' => data.push_str("\\n"),
27            b'\'' => data.push_str("\\'"),
28            b'\"' => data.push_str("\\\""),
29            b'\\' => data.push_str("\\\\"),
30            0x20..=0x7e => data.push(*ch as char),
31            _ => {
32                data.push_str("\\x");
33                data.push(HEX[(ch >> 4) as usize] as char);
34                data.push(HEX[(ch & 15) as usize] as char);
35            }
36        }
37    }
38    data
39}
40
41pub fn unhex(ch: char) -> Result<u8> {
42    match ch {
43        '0'..='9' => Ok(ch as u8 - b'0'),
44        'A'..='F' => Ok(ch as u8 - b'A' + 10),
45        'a'..='f' => Ok(ch as u8 - b'a' + 10),
46        _ => Err(EscapeError::InvalidHexCharacter(ch).into()),
47    }
48}
49
50pub fn unescape(val: &str) -> Result<Vec<u8>> {
51    let mut data = Vec::new();
52    let mut it = val.chars();
53    while let Some(ch) = it.next() {
54        if !ch.is_ascii() {
55            return Err(EscapeError::InvalidCharacter(ch).into());
56        }
57        if ch == '\\' {
58            let ch = it.next().ok_or(EscapeError::IncompleteEscape)?;
59            let decoded = match ch {
60                't' => b'\t',
61                'r' => b'\r',
62                'n' => b'\n',
63                '"' => b'\"',
64                '\'' => b'\'',
65                '\\' => b'\\',
66                'x' => {
67                    let mut v = 0;
68                    v = (v << 4) | unhex(it.next().ok_or(EscapeError::IncompleteEscape)?)?;
69                    v = (v << 4) | unhex(it.next().ok_or(EscapeError::IncompleteEscape)?)?;
70                    v
71                }
72                _ => return Err(EscapeError::InvalidCharacter(ch).into()),
73            };
74            data.push(decoded);
75        } else {
76            data.push(ch as u8);
77        }
78    }
79    Ok(data)
80}
81
82pub fn as_hex(v: &[u8]) -> String {
83    let mut s = String::with_capacity(v.len() * 3);
84    for (i, &byte) in v.iter().enumerate() {
85        if i > 0 {
86            s.push(':');
87        }
88        s.push(HEX[(byte >> 4) as usize] as char);
89        s.push(HEX[(byte & 15) as usize] as char);
90    }
91    s
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_escape() -> Result<()> {
100        let bytes = b"The quick brown fox\n\tjumped over the \"lazy\" dog.";
101        assert_eq!(
102            escape(bytes),
103            r#"The quick brown fox\n\tjumped over the \"lazy\" dog."#
104        );
105
106        let bytes = b"\t\r\n\"\'\\\x00\x7F\xFF";
107        assert_eq!(escape(bytes), r#"\t\r\n\"\'\\\x00\x7F\xFF"#);
108        Ok(())
109    }
110
111    #[test]
112    fn test_unescape() -> Result<()> {
113        let s = r#"The quick brown fox\n\tjumped over the \"lazy\" dog."#;
114        assert_eq!(
115            unescape(s)?,
116            b"The quick brown fox\n\tjumped over the \"lazy\" dog."
117        );
118
119        let s = r#"\t\r\n\"\'\\\x00\x7F\xFF"#;
120        assert_eq!(unescape(s)?, b"\t\r\n\"\'\\\x00\x7F\xFF");
121        Ok(())
122    }
123
124    #[test]
125    fn test_unescape_err() -> Result<()> {
126        let pounds = "\u{00A3}";
127        assert!(unescape(pounds).is_err());
128
129        let invalid_escape = "\\z";
130        assert!(unescape(invalid_escape).is_err());
131
132        let incomplete_escape = "\\";
133        assert!(unescape(incomplete_escape).is_err());
134
135        let bad_hex = "\\xzz";
136        assert!(unescape(bad_hex).is_err());
137        Ok(())
138    }
139}