Skip to main content

st/decoders/
mod.rs

1// Decoder framework - Convert quantum format to other representations
2// All formats are now just views into the quantum stream
3
4use anyhow::Result;
5use std::io::Write;
6
7pub mod classic;
8pub mod hex;
9pub mod json;
10
11/// Quantum entry components after parsing
12#[derive(Debug, Clone)]
13pub struct QuantumEntry {
14    pub header: u8,
15    pub size: Option<u64>,
16    pub perms_delta: Option<u16>,
17    pub time_delta: Option<i64>,
18    pub owner_delta: Option<(u32, u32)>,
19    pub name: String,
20    pub is_dir: bool,
21    pub is_link: bool,
22    pub traversal: TraversalCode,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum TraversalCode {
27    Same,    // Continue at same level
28    Deeper,  // Enter directory
29    Back,    // Exit directory
30    Summary, // Summary follows
31}
32
33impl From<u8> for TraversalCode {
34    fn from(byte: u8) -> Self {
35        match byte {
36            0x0B => TraversalCode::Same,
37            0x0E => TraversalCode::Deeper,
38            0x0F => TraversalCode::Back,
39            0x0C => TraversalCode::Summary,
40            _ => TraversalCode::Same, // Default
41        }
42    }
43}
44
45/// Base trait for all quantum decoders
46pub trait QuantumDecoder: Send {
47    /// Initialize the decoder
48    fn init(&mut self, writer: &mut dyn Write) -> Result<()>;
49
50    /// Process a quantum entry
51    fn decode_entry(&mut self, entry: &QuantumEntry, writer: &mut dyn Write) -> Result<()>;
52
53    /// Finalize output
54    fn finish(&mut self, writer: &mut dyn Write) -> Result<()>;
55}
56
57/// Parse a quantum stream and decode to target format
58pub fn decode_quantum_stream<D: QuantumDecoder>(
59    quantum_data: &[u8],
60    decoder: &mut D,
61    writer: &mut dyn Write,
62) -> Result<()> {
63    decoder.init(writer)?;
64
65    // Parse quantum entries from binary data
66    let mut offset = 0;
67    while offset < quantum_data.len() {
68        let (entry, new_offset) = parse_quantum_entry(quantum_data, offset)?;
69        if let Some(entry) = entry {
70            decoder.decode_entry(&entry, writer)?;
71        }
72        offset = new_offset;
73    }
74
75    decoder.finish(writer)?;
76    Ok(())
77}
78
79/// Parse a single quantum entry from binary data
80fn parse_quantum_entry(data: &[u8], offset: usize) -> Result<(Option<QuantumEntry>, usize)> {
81    if offset >= data.len() {
82        return Ok((None, offset));
83    }
84
85    let header = data[offset];
86    let mut offset = offset + 1;
87
88    let mut entry = QuantumEntry {
89        header,
90        size: None,
91        perms_delta: None,
92        time_delta: None,
93        owner_delta: None,
94        name: String::new(),
95        is_dir: (header & 0x10) != 0,
96        is_link: (header & 0x20) != 0,
97        traversal: TraversalCode::Same,
98    };
99
100    // Parse size if present
101    if (header & 0x01) != 0 {
102        let (size, new_offset) = decode_size(data, offset)?;
103        entry.size = Some(size);
104        offset = new_offset;
105    }
106
107    // Parse permissions delta if present
108    if (header & 0x02) != 0 && offset + 2 <= data.len() {
109        entry.perms_delta = Some((data[offset] as u16) << 8 | data[offset + 1] as u16);
110        offset += 2;
111    }
112
113    // TODO: Parse time, owner/group deltas
114
115    // Parse name (ends with traversal code)
116    let name_start = offset;
117    while offset < data.len() && !is_traversal_code(data[offset]) {
118        offset += 1;
119    }
120
121    entry.name = String::from_utf8_lossy(&data[name_start..offset]).into_owned();
122
123    // Parse traversal code
124    if offset < data.len() {
125        entry.traversal = data[offset].into();
126        offset += 1;
127    }
128
129    Ok((Some(entry), offset))
130}
131
132/// Check if a byte is a traversal code
133fn is_traversal_code(byte: u8) -> bool {
134    matches!(byte, 0x0B | 0x0E | 0x0F | 0x0C)
135}
136
137/// Decode variable-length size encoding
138fn decode_size(data: &[u8], offset: usize) -> Result<(u64, usize)> {
139    if offset >= data.len() {
140        anyhow::bail!("Unexpected end of data while decoding size");
141    }
142
143    let prefix = data[offset];
144    match prefix {
145        0x00 => {
146            if offset + 1 >= data.len() {
147                anyhow::bail!("Incomplete size encoding");
148            }
149            Ok((data[offset + 1] as u64, offset + 2))
150        }
151        0x01 => {
152            if offset + 2 >= data.len() {
153                anyhow::bail!("Incomplete size encoding");
154            }
155            let size = u16::from_le_bytes([data[offset + 1], data[offset + 2]]) as u64;
156            Ok((size, offset + 3))
157        }
158        0x02 => {
159            if offset + 4 >= data.len() {
160                anyhow::bail!("Incomplete size encoding");
161            }
162            let size = u32::from_le_bytes([
163                data[offset + 1],
164                data[offset + 2],
165                data[offset + 3],
166                data[offset + 4],
167            ]) as u64;
168            Ok((size, offset + 5))
169        }
170        0x03 => {
171            if offset + 8 >= data.len() {
172                anyhow::bail!("Incomplete size encoding");
173            }
174            let size = u64::from_le_bytes([
175                data[offset + 1],
176                data[offset + 2],
177                data[offset + 3],
178                data[offset + 4],
179                data[offset + 5],
180                data[offset + 6],
181                data[offset + 7],
182                data[offset + 8],
183            ]);
184            Ok((size, offset + 9))
185        }
186        _ => {
187            // Check if it's a size token
188            if (0xA0..=0xAF).contains(&prefix) {
189                // Size range tokens
190                let size = match prefix {
191                    0xA0 => 0,                // TOKEN_SIZE_ZERO
192                    0xA1 => 512,              // TOKEN_SIZE_TINY (average)
193                    0xA2 => 50 * 1024,        // TOKEN_SIZE_SMALL (average)
194                    0xA3 => 5 * 1024 * 1024,  // TOKEN_SIZE_MEDIUM (average)
195                    0xA4 => 50 * 1024 * 1024, // TOKEN_SIZE_LARGE (average)
196                    _ => 0,
197                };
198                Ok((size, offset + 1))
199            } else {
200                anyhow::bail!("Invalid size prefix: 0x{:02x}", prefix);
201            }
202        }
203    }
204}