Skip to main content

os_dev_toolkit/
fmt.rs

1//! Formatting helpers for diagnostics in `no_std` environments.
2//!
3//! The goal of this module is to provide deterministic, allocation-free formatting helpers that
4//! are frequently useful during OS development:
5//!
6//! - [`crate::fmt::HexSlice`] for stable, compact `Debug` printing of byte slices.
7//! - [`crate::fmt::hexdump_to_sink`] for a classic hex dump layout (offset/hex/ascii).
8//! - [`crate::fmt::ByteFmt`] for human-friendly binary unit formatting.
9//! - [`crate::fmt::Addr`] for consistent pointer/address formatting.
10
11use core::fmt;
12use core::fmt::Write;
13
14use crate::log::LogSink;
15
16/// Wrapper type providing a stable [`Debug`](core::fmt::Debug) representation for `&[u8]`.
17///
18/// This is intentionally compact and deterministic:
19///
20/// ```text
21/// [00 01 02 ff]
22/// ```
23///
24/// This is handy in kernel logs where `{:?}` output should be predictable.
25pub struct HexSlice<'a>(pub &'a [u8]);
26
27impl fmt::Debug for HexSlice<'_> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "[")?;
30        for (i, b) in self.0.iter().enumerate() {
31            if i != 0 {
32                write!(f, " ")?;
33            }
34            write!(f, "{:02x}", b)?;
35        }
36        write!(f, "]")
37    }
38}
39
40/// Writes a classic hex dump to a [`LogSink`].
41///
42/// Each line is formatted as:
43///
44/// ```text
45/// 0x00000000: 01 02 03 ... |....|
46/// ```
47///
48/// - `bytes`: input data to render.
49/// - `sink`: output destination.
50/// - `columns`: number of bytes per line. If `0`, defaults to `16`.
51///
52/// This function is best-effort: it ignores formatting errors because the underlying sink is
53/// infallible by contract.
54pub fn hexdump_to_sink(bytes: &[u8], sink: &mut dyn LogSink, columns: usize) {
55    let cols = if columns == 0 { 16 } else { columns };
56
57    let mut offset = 0usize;
58    while offset < bytes.len() {
59        let end = core::cmp::min(offset + cols, bytes.len());
60        let chunk = &bytes[offset..end];
61
62        sink.write_str("0x");
63        {
64            struct Adapter<'a>(&'a mut dyn LogSink);
65            impl fmt::Write for Adapter<'_> {
66                fn write_str(&mut self, s: &str) -> fmt::Result {
67                    self.0.write_str(s);
68                    Ok(())
69                }
70            }
71            let mut a = Adapter(sink);
72            let _ = write!(a, "{:08x}: ", offset);
73            for b in chunk {
74                let _ = write!(a, "{:02x} ", b);
75            }
76
77            if end - offset < cols {
78                for _ in 0..(cols - (end - offset)) {
79                    let _ = write!(a, "   ");
80                }
81            }
82
83            let _ = write!(a, "|");
84            for &b in chunk {
85                let c = if (0x20..=0x7e).contains(&b) {
86                    b as char
87                } else {
88                    '.'
89                };
90                let _ = write!(a, "{c}");
91            }
92            if end - offset < cols {
93                for _ in 0..(cols - (end - offset)) {
94                    let _ = write!(a, " ");
95                }
96            }
97            let _ = write!(a, "|");
98        }
99        sink.write_str("\n");
100        offset = end;
101    }
102}
103
104/// Formats a byte count using binary units (KiB, MiB, GiB).
105///
106/// The output keeps three decimals for larger units and rounds down (truncation), which is often
107/// preferable in diagnostics.
108///
109/// Examples:
110///
111/// - `ByteFmt(999)` -> `"999 B"`
112/// - `ByteFmt(1024)` -> `"1.000 KiB"`
113/// - `ByteFmt(1024*1024)` -> `"1.000 MiB"`
114pub struct ByteFmt(pub u64);
115
116impl fmt::Display for ByteFmt {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        const KIB: u64 = 1024;
119        const MIB: u64 = 1024 * 1024;
120        const GIB: u64 = 1024 * 1024 * 1024;
121
122        let b = self.0;
123        if b >= GIB {
124            write!(f, "{}.{:03} GiB", b / GIB, (b % GIB) * 1000 / GIB)
125        } else if b >= MIB {
126            write!(f, "{}.{:03} MiB", b / MIB, (b % MIB) * 1000 / MIB)
127        } else if b >= KIB {
128            write!(f, "{}.{:03} KiB", b / KIB, (b % KIB) * 1000 / KIB)
129        } else {
130            write!(f, "{b} B")
131        }
132    }
133}
134
135/// Formats an address as lower-hex with `0x` prefix.
136///
137/// This is mainly a readability wrapper for kernel logs.
138pub struct Addr(pub usize);
139
140impl fmt::LowerHex for Addr {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "0x{:x}", self.0)
143    }
144}