1use core::fmt;
4use std::io;
5
6#[derive(Copy, Clone, Debug, PartialEq, Eq)]
27pub enum ByteParser {
28    Decimal = 10,
29    Hexadecimal = 16,
30}
31
32macro_rules! unwrap {
34    ($expr:expr) => {
35        match $expr {
36            Some(value) => value,
37            None => return None,
38        }
39    };
40}
41
42impl ByteParser {
43    const fn digit(&self, byte: u8) -> Option<u8> {
45        let value = match byte {
46            0x30..=0x39 => byte - 0x30,
47            0x41..=0x46 => byte - 0x41 + 10,
48            0x61..=0x66 => byte - 0x61 + 10,
49            _ => return None,
50        };
51
52        if (*self as u8) <= value {
53            return None;
54        }
55
56        Some(value)
57    }
58
59    pub const fn to_u16(&self, bytes: &[u8]) -> Option<u16> {
61        let value = unwrap!(self.to_u32(bytes));
62        if value <= 0xffff {
63            Some(value as u16)
64        } else {
65            None
66        }
67    }
68
69    pub const fn to_u32(&self, bytes: &[u8]) -> Option<u32> {
71        let mut value: u32 = 0;
72        let mut index = 0;
73
74        while index < bytes.len() {
75            let digit = unwrap!(self.digit(bytes[index]));
76            value = unwrap!(value.checked_mul(*self as u32));
77            value = unwrap!(value.checked_add(digit as u32));
78            index += 1;
79        }
80
81        Some(value)
82    }
83}
84
85#[derive(Debug)]
112pub enum ByteFormat<'a> {
113    Concise(&'a [u8]),
117    Nicely(&'a [u8]),
123    Hexdump(&'a [u8]),
127}
128
129const C0: [&str; 32] = [
130    "‹NUL›",
131    "‹SOH›",
132    "‹STX›",
133    "‹ETX›",
134    "‹EOT›",
135    "‹ENQ›",
136    "‹ACK›",
137    "‹BEL›",
138    "‹BS›",
139    "‹HT›",
140    "‹LF›",
141    "‹VT›",
142    "‹FF›",
143    "‹CR›",
144    "‹SO›",
145    "‹SI›",
146    "‹DLE›",
147    "‹DC1›",
148    "‹DC2›",
149    "‹DC3›",
150    "‹DC4›",
151    "‹NAK›",
152    "‹SYN›",
153    "‹ETB›",
154    "‹CAN›",
155    "‹EM›",
156    "‹SUB›",
157    "‹ESC›",
158    "‹FS›",
159    "‹GS›",
160    "‹RS›",
161    "‹US›",
162];
163
164const C1: [&str; 5] = ["‹CSI›", "‹ST›", "‹OSC›", "‹PM›", "‹APC›"];
165
166impl ByteFormat<'_> {
167    pub fn render<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, fmt::Error> {
178        match *self {
179            ByteFormat::Concise(bytes) => ByteFormat::render_concise(bytes, writer),
180            ByteFormat::Nicely(bytes) => ByteFormat::render_nicely(bytes, writer),
181            ByteFormat::Hexdump(bytes) => ByteFormat::render_hexdump(bytes, writer),
182        }
183    }
184
185    fn render_concise<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
186    where
187        W: fmt::Write + ?Sized,
188    {
189        for byte in bytes {
190            let display = match *byte {
191                0x00..=0x1f => {
192                    char::from_u32(0x2400_u32 + *byte as u32).expect("known good Unicode character")
193                }
194                0x20..=0x7e => *byte as char,
195                0x7f => char::from_u32(0x2421).expect("known good Unicode character"),
196                _ => '.',
197            };
198            writer.write_char(display)?;
199        }
200
201        Ok(bytes.len())
202    }
203
204    fn render_nicely<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
205    where
206        W: fmt::Write + ?Sized,
207    {
208        let mut ascii = [0; 1];
209        let mut characters = 0;
210
211        for &byte in bytes {
212            let display = match byte {
213                0x00..=0x1f => C0[byte as usize],
214                0x20..=0x7e => {
215                    ascii[0] = byte;
216                    core::str::from_utf8(&ascii).expect("ASCII characters are valid UTF-8, too")
218                }
219                0x7f => "‹DEL›",
220                0x90 => "‹DCS›",
221                0x98 => "‹SOS›",
222                0x9b..=0x9f => C1[(byte - 0x9b) as usize],
223                _ => "",
224            };
225
226            if display.is_empty() {
227                writer.write_fmt(format_args!("「{:02X}」", byte))?;
228                characters += 4;
229            } else {
230                writer.write_str(display)?;
231                characters += match display.len() {
232                    n @ (1 | 2) => n,
233                    n => n - 6 + 2,
234                };
235            }
236        }
237
238        Ok(characters)
239    }
240
241    #[allow(clippy::missing_asserts_for_indexing)]
244    fn render_hexdump<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
245    where
246        W: fmt::Write + ?Sized,
247    {
248        const CHUNK_SIZE: usize = 16;
249        let compact = bytes.len() < CHUNK_SIZE;
250        let mut chunk_index = 0;
251        let mut characters = 0;
252
253        for chunk in bytes.chunks(CHUNK_SIZE) {
254            if 0 < chunk_index {
255                writer.write_char('\n')?;
256            }
257
258            write!(writer, "{:04x}:  ", chunk_index)?;
259            characters = 7; for pair in chunk.chunks(2) {
262                write!(writer, "{:02x}", pair[0])?;
264                if pair.len() == 1 {
265                    write!(writer, "   ")?;
266                } else {
267                    write!(writer, "{:02x} ", pair[1])?;
268                }
269                characters += 5;
270            }
271
272            if !compact {
273                for _ in 0..(CHUNK_SIZE - chunk.len()) / 2 {
274                    writer.write_str("     ")?;
276                    characters += 5;
277                }
278            }
279
280            writer.write_str(" ")?;
282            characters += 1;
283
284            ByteFormat::render_concise(chunk, writer)?;
285
286            chunk_index += chunk.len();
287            characters += chunk.len();
288        }
289
290        Ok(characters)
291    }
292}
293
294impl fmt::Display for ByteFormat<'_> {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        self.render(f)?;
297        Ok(())
298    }
299}
300
301pub struct Rewriter<'a, W: ?Sized + 'a> {
334    writer: &'a mut W,
335    result: io::Result<()>,
336}
337
338impl<'a, W: ?Sized + 'a> Rewriter<'a, W> {
339    pub fn new(writer: &'a mut W) -> Self {
341        Self {
342            writer,
343            result: Ok(()),
344        }
345    }
346
347    pub fn is_err(&self) -> bool {
349        self.result.is_err()
350    }
351
352    pub fn into_err(self) -> io::Error {
361        match self.result {
362            Err(err) => err,
363            Ok(_) => panic!("display trait returned error without underlying I/O error"),
364        }
365    }
366}
367
368impl<W: io::Write + ?Sized> fmt::Write for Rewriter<'_, W> {
369    fn write_str(&mut self, s: &str) -> fmt::Result {
370        self.writer.write_all(s.as_bytes()).map_err(|err| {
371            self.result = Err(err);
372            fmt::Error
373        })
374    }
375}
376
377#[cfg(test)]
380mod test {
381    use super::*;
382    use std::io::{Cursor, Error, Write};
383
384    #[test]
385    fn test_radix_parse() {
386        assert_eq!(ByteParser::Decimal.to_u16(b"665"), Some(665));
387        assert_eq!(ByteParser::Decimal.to_u16(b"65536"), None);
388        assert_eq!(ByteParser::Decimal.to_u16(b"665A"), None);
389        assert_eq!(ByteParser::Hexadecimal.to_u16(b"665"), Some(1_637));
390        assert_eq!(ByteParser::Hexadecimal.to_u16(b"665A"), Some(26_202));
391        assert_eq!(ByteParser::Hexadecimal.to_u16(b"fFfF"), Some(0xffff));
392        assert_eq!(ByteParser::Hexadecimal.to_u16(b"10000"), None);
393
394        assert_eq!(ByteParser::Decimal.to_u32(b"665"), Some(665));
395        assert_eq!(ByteParser::Decimal.to_u32(b"65536"), Some(65_536));
396        assert_eq!(ByteParser::Decimal.to_u32(b"665A"), None);
397        assert_eq!(ByteParser::Hexadecimal.to_u32(b"665"), Some(1_637));
398        assert_eq!(ByteParser::Hexadecimal.to_u32(b"665A"), Some(26_202));
399        assert_eq!(
400            ByteParser::Hexadecimal.to_u32(b"fFfFfFfF"),
401            Some(0xffff_ffff)
402        );
403        assert_eq!(ByteParser::Hexadecimal.to_u32(b"100000000"), None);
404    }
405
406    #[test]
407    fn test_format() -> std::io::Result<()> {
408        let mut buffer = Cursor::new(vec![0; 500]);
409        write!(
410            buffer,
411            "{}",
412            ByteFormat::Hexdump(b"\x1bP>|Terminal\x07\x1bP>|Name\x1b\\")
413        )?;
414
415        assert_eq!(
416            &buffer.get_ref()[0..buffer.position() as usize],
417            b"0000:  1b50 3e7c 5465 726d 696e 616c 071b 503e  \xe2\x90\x9bP>|Terminal\
418                                                                    \xe2\x90\x87\
419                                                                    \xe2\x90\x9bP>\n\
420              0010:  7c4e 616d 651b 5c                        |Name\xe2\x90\x9b\\"
421        );
422        Ok(())
423    }
424
425    #[test]
426    fn test_nicely() -> std::io::Result<()> {
427        let mut buffer = Cursor::new(vec![0; 100]);
428        let mut writer = Rewriter::new(&mut buffer);
429
430        assert_eq!(ByteFormat::Nicely(b"R").render(&mut writer), Ok(1));
431        assert_eq!(ByteFormat::Nicely(b"\x1b").render(&mut writer), Ok(5));
432        assert_eq!(ByteFormat::Nicely(b"#").render(&mut writer), Ok(1));
433        assert_eq!(ByteFormat::Nicely(b"\xaf").render(&mut writer), Ok(4));
434        assert_eq!(ByteFormat::Nicely(b"\\").render(&mut writer), Ok(1));
435        assert_eq!(ByteFormat::Nicely(b"\"").render(&mut writer), Ok(1));
436
437        assert_eq!(
438            &buffer.get_ref()[0..buffer.position() as usize],
439            "R‹ESC›#「AF」\\\"".as_bytes()
440        );
441        assert_eq!(buffer.position(), 21);
442        Ok::<(), Error>(())
443    }
444}