1use anyhow::Result;
5use std::io::Write;
6
7pub mod classic;
8pub mod hex;
9pub mod json;
10
11#[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, Deeper, Back, Summary, }
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, }
42 }
43}
44
45pub trait QuantumDecoder: Send {
47 fn init(&mut self, writer: &mut dyn Write) -> Result<()>;
49
50 fn decode_entry(&mut self, entry: &QuantumEntry, writer: &mut dyn Write) -> Result<()>;
52
53 fn finish(&mut self, writer: &mut dyn Write) -> Result<()>;
55}
56
57pub 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 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
79fn 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 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 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 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 if offset < data.len() {
125 entry.traversal = data[offset].into();
126 offset += 1;
127 }
128
129 Ok((Some(entry), offset))
130}
131
132fn is_traversal_code(byte: u8) -> bool {
134 matches!(byte, 0x0B | 0x0E | 0x0F | 0x0C)
135}
136
137fn 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 if (0xA0..=0xAF).contains(&prefix) {
189 let size = match prefix {
191 0xA0 => 0, 0xA1 => 512, 0xA2 => 50 * 1024, 0xA3 => 5 * 1024 * 1024, 0xA4 => 50 * 1024 * 1024, _ => 0,
197 };
198 Ok((size, offset + 1))
199 } else {
200 anyhow::bail!("Invalid size prefix: 0x{:02x}", prefix);
201 }
202 }
203 }
204}