1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::io;

pub struct HexTable {
    columns: usize,
    offset: usize,
    header: bool,
    ascii: bool,
    zeros: bool,
}

impl Default for HexTable {
    fn default() -> Self {
        HexTable { columns: 16, offset: 0, header: false, ascii: true, zeros: true }
    }
}

impl HexTable {
    pub fn new(columns: usize, offset: usize, header: bool, ascii: bool, zeros: bool) -> HexTable {
        HexTable { columns, offset, header, ascii, zeros }
    }

    pub fn format<Writer: io::Write>(&self, data: &[u8], out: &mut Writer) -> io::Result<()> {
        let mut col_pos = 0;
        let mut row_pos = 0;
        let mut ascii_pos = 0;

        let data_len = data.len();
        let col_pad_len = Self::count_hex_digits(self.columns).max(2);
        let columns_remaining = data_len % self.columns;

        let columns_empty = if columns_remaining != 0 {
            self.columns - columns_remaining
        } else {
            0
        };

        let offset_str_len = Self::count_hex_digits(
            if columns_remaining != 0 {
                self.offset + data.len() - columns_remaining
            } else {
                self.offset + data.len() - columns_remaining - 1
            }
        );

        let last_row_pad_len = columns_empty * (col_pad_len + 1);
        let table_len_needed = data_len + columns_empty;

        if self.header {
            let offset = " ".repeat(offset_str_len + 2);
            let line = "-".repeat((self.columns * (col_pad_len + 1)) - 1);

            write!(out, "{}", offset)?;

            for i in 0..self.columns {
                write!(out, "{:0>1$X} ", i, col_pad_len)?;
            }

            write!(out, "\n{}{}\n", offset, line)?;
        }

        for _ in 0..(table_len_needed / self.columns) {
            write!(out, "{:0>1$X}: ", self.offset + row_pos, offset_str_len)?;

            for _ in 0..self.columns {
                if !self.zeros && data[col_pos] == 0 {
                    write!(out, "{} ", ".".repeat(col_pad_len))?;
                } else {
                    write!(out, "{:0>1$X} ", data[col_pos], col_pad_len)?;
                }

                col_pos += 1;

                if col_pos >= data_len {
                    break
                }
            }

            if self.ascii {
                if col_pos >= data_len {
                    write!(out, "{}", " ".repeat(last_row_pad_len))?;
                }

                write!(out, "| ")?;

                for _ in 0..self.columns {
                    if data[ascii_pos].is_ascii_alphanumeric() {
                        write!(out, "{}", data[ascii_pos] as char)?;
                    } else {
                        write!(out, ".")?;
                    }

                    ascii_pos += 1;

                    if ascii_pos >= data_len {
                        break
                    }
                }
            }

            row_pos += self.columns;

            if row_pos >= data_len {
                break
            }

            write!(out, "\n")?;
        }

        write!(out, "\n")?;

        Ok(())
    }

    // This was fun. It counts the number of hex digits with log16(x) + 1 for whole numbers. It uses
    // the algorithm log2(x) = size_of(x) - 1 - clz(x) then a base conversion log2(x) / log2(16).
    fn count_hex_digits(x: usize) -> usize {
        (8 * std::mem::size_of::<usize>() - 1 - x.leading_zeros() as usize) / (7 - 16u8.leading_zeros() as usize) + 1
    }
}