Skip to main content

oi4_dnp_encoding/
encode.rs

1use core::fmt;
2
3#[inline]
4pub(crate) const fn is_unreserved(b: u8) -> bool {
5    matches!(b,
6        b'A'..=b'Z' |
7        b'a'..=b'z' |
8        b'0'..=b'9' |
9        b'-' | b'.' | b'_' | b'~'
10    )
11}
12
13const HEX_UPPER: &[u8; 16] = b"0123456789ABCDEF";
14
15/// Compute encoded length without allocating.
16pub fn encoded_len(input: &str) -> usize {
17    let bytes = input.as_bytes();
18    let mut i = 0usize;
19    let mut len = 0usize;
20    while i < bytes.len() {
21        let b = bytes[i];
22        if b < 0x80 {
23            // ASCII
24            if is_unreserved(b) {
25                len += 1;
26            } else {
27                len += 3;
28            }
29            i += 1;
30        } else {
31            // multi-byte UTF-8 sequence – copy verbatim
32            // Determine sequence length from first byte (UTF-8 invariant; input is &str)
33            let seq_len = if b & 0b1110_0000 == 0b1100_0000 {
34                2
35            } else if b & 0b1111_0000 == 0b1110_0000 {
36                3
37            } else {
38                4
39            };
40            len += seq_len;
41            i += seq_len;
42        }
43    }
44    len
45}
46
47/// Encode into provided writer (zero-allocation path other than writer itself).
48pub fn encode_into(input: &str, out: &mut impl fmt::Write) -> fmt::Result {
49    let mut buf = [0u8; 4];
50    for ch in input.chars() {
51        if ch.is_ascii() {
52            let b = ch as u8;
53            if is_unreserved(b) {
54                out.write_char(ch)?;
55            } else {
56                let hi = HEX_UPPER[(b >> 4) as usize] as char;
57                let lo = HEX_UPPER[(b & 0x0F) as usize] as char;
58                out.write_char(',')?;
59                out.write_char(hi)?;
60                out.write_char(lo)?;
61            }
62        } else {
63            let s = ch.encode_utf8(&mut buf);
64            out.write_str(s)?;
65        }
66    }
67    Ok(())
68}
69
70#[cfg(feature = "alloc")]
71pub fn encode(input: &str) -> alloc::string::String {
72    let mut s = alloc::string::String::with_capacity(encoded_len(input));
73    encode_into(input, &mut s).expect("write to String cannot fail");
74    s
75}