Skip to main content

ud_core/
lib.rs

1//! Core types shared across univdreams crates.
2//!
3//! This crate is intentionally minimal at Phase 0. It will grow as the
4//! pipeline does, but the surface here should stay narrow: types every
5//! crate above it agrees on (addresses, ranges, errors), and nothing
6//! that belongs in a more specific crate.
7
8use std::path::PathBuf;
9
10/// Crate result alias.
11pub type Result<T, E = Error> = std::result::Result<T, E>;
12
13/// Errors surfaced through the public API.
14///
15/// Variants are deliberately coarse for now. As real pipelines come online
16/// (lifter, encoder, format reader/writer) each will introduce its own
17/// error type and convert at the boundary; this enum is for things that
18/// genuinely belong at the top of the stack.
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21    #[error("I/O error on {path}: {source}")]
22    Io {
23        path: PathBuf,
24        #[source]
25        source: std::io::Error,
26    },
27
28    #[error(
29        "round-trip failed: output differs from input at byte {offset} \
30         (input=0x{input_byte:02x}, output=0x{output_byte:02x}); {extra} more byte(s) differ"
31    )]
32    RoundTripMismatch {
33        offset: usize,
34        input_byte: u8,
35        output_byte: u8,
36        extra: usize,
37    },
38
39    #[error("round-trip failed: output length {output_len} differs from input length {input_len}")]
40    RoundTripLengthMismatch { input_len: usize, output_len: usize },
41}
42
43/// A virtual address in a target binary.
44///
45/// Kept as a thin newtype so it can't be mixed up with file offsets or
46/// host addresses. Will gain an arch-bits parameter when 16/32-bit
47/// targets land.
48#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct VAddr(pub u64);
50
51impl std::fmt::Debug for VAddr {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        write!(f, "VAddr(0x{:x})", self.0)
54    }
55}
56
57impl std::fmt::Display for VAddr {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "0x{:x}", self.0)
60    }
61}
62
63/// Compare two byte buffers and produce a [`Error::RoundTripMismatch`] /
64/// [`Error::RoundTripLengthMismatch`] when they differ.
65///
66/// Used by the round-trip harness; lives here so both the CLI and any
67/// future test crate can call it.
68pub fn assert_bytes_equal(input: &[u8], output: &[u8]) -> Result<()> {
69    if input.len() != output.len() {
70        return Err(Error::RoundTripLengthMismatch {
71            input_len: input.len(),
72            output_len: output.len(),
73        });
74    }
75    let Some(offset) = input.iter().zip(output).position(|(a, b)| a != b) else {
76        return Ok(());
77    };
78    let extra = input[offset + 1..]
79        .iter()
80        .zip(&output[offset + 1..])
81        .filter(|(a, b)| a != b)
82        .count();
83    Err(Error::RoundTripMismatch {
84        offset,
85        input_byte: input[offset],
86        output_byte: output[offset],
87        extra,
88    })
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn equal_buffers_pass() {
97        assert!(assert_bytes_equal(b"abc", b"abc").is_ok());
98    }
99
100    #[test]
101    fn length_mismatch_reports_lengths() {
102        let err = assert_bytes_equal(b"abc", b"abcd").unwrap_err();
103        assert!(matches!(
104            err,
105            Error::RoundTripLengthMismatch {
106                input_len: 3,
107                output_len: 4
108            }
109        ));
110    }
111
112    #[test]
113    fn byte_mismatch_reports_offset_and_count() {
114        let err = assert_bytes_equal(b"abcde", b"abXdY").unwrap_err();
115        match err {
116            Error::RoundTripMismatch {
117                offset,
118                input_byte,
119                output_byte,
120                extra,
121            } => {
122                assert_eq!(offset, 2);
123                assert_eq!(input_byte, b'c');
124                assert_eq!(output_byte, b'X');
125                assert_eq!(extra, 1);
126            }
127            other => panic!("unexpected error: {other:?}"),
128        }
129    }
130}