1use crate::wire::Cursor;
4use crate::{
5 ChunkRecord, FormatError, Integrity, Result, SectionEntry, SectionType, SegmentRecord,
6 Superblock, TrackKind, TrackRecord, CHUNK_FLAG_ZSTD, MAGIC, SECTION_DIR_ENTRY_LEN,
7 SUPERBLOCK_LEN, VERSION_MAJOR,
8};
9use cavs_hash::{content_signature_message, hash_chunk, merkle_root, Hasher};
10use std::fs::File;
11use std::io::{Read as _, Seek, SeekFrom};
12use std::path::Path;
13
14pub struct Reader {
15 file: File,
16 superblock: Superblock,
17 sections: Vec<SectionEntry>,
18 tracks: Vec<TrackRecord>,
19 dict: Vec<u32>,
20 chunks: Vec<ChunkRecord>,
21 segments: Vec<SegmentRecord>,
22 meta: Vec<(String, String)>,
23 integrity: Integrity,
24 data_offset: u64,
25 data_len: u64,
26}
27
28#[derive(Debug, Clone)]
30pub struct VerifyReport {
31 pub chunks_verified: u64,
32 pub bytes_verified: u64,
33 pub merkle_ok: bool,
34 pub data_section_ok: bool,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum SignatureStatus {
40 Unsigned,
42 Valid([u8; 32]),
44}
45
46impl Reader {
47 pub fn open(path: &Path) -> Result<Self> {
48 let mut file = File::open(path)?;
49 let file_len = file.metadata()?.len();
52
53 let mut sb_bytes = [0u8; SUPERBLOCK_LEN as usize];
54 file.read_exact(&mut sb_bytes)?;
55 if sb_bytes[0..4] != MAGIC {
56 return Err(FormatError::BadMagic);
57 }
58 let mut cur = Cursor::new(&sb_bytes[4..], "superblock");
59 let superblock = Superblock {
60 version_major: cur.u16()?,
61 version_minor: cur.u16()?,
62 feature_flags: cur.u32()?,
63 hash_algo: cur.u8()?,
64 compression_algo: cur.u8()?,
65 asset_uuid: {
66 cur.u16()?; let mut uuid = [0u8; 16];
68 for b in uuid.iter_mut() {
69 *b = cur.u8()?;
70 }
71 uuid
72 },
73 timescale: cur.u32()?,
74 section_count: cur.u32()?,
75 section_dir_offset: cur.u64()?,
76 file_size: cur.u64()?,
77 };
78 if superblock.version_major != VERSION_MAJOR {
79 return Err(FormatError::UnsupportedVersion(superblock.version_major));
80 }
81 if cavs_hash::HashAlgo::from_u8(superblock.hash_algo).is_none() {
86 return Err(FormatError::UnknownValue {
87 what: "hash algorithm",
88 value: superblock.hash_algo as u32,
89 });
90 }
91 if superblock.compression_algo > crate::COMPRESSION_ZSTD {
92 return Err(FormatError::UnknownValue {
93 what: "compression algorithm",
94 value: superblock.compression_algo as u32,
95 });
96 }
97 if superblock.file_size != file_len {
98 return Err(FormatError::Malformed("declared file size"));
99 }
100
101 let dir_len = superblock.section_count as u64 * SECTION_DIR_ENTRY_LEN as u64;
104 if superblock.section_dir_offset > file_len
105 || dir_len > file_len - superblock.section_dir_offset
106 {
107 return Err(FormatError::Malformed("section directory"));
108 }
109 file.seek(SeekFrom::Start(superblock.section_dir_offset))?;
110 let mut dir_bytes = vec![0u8; dir_len as usize];
111 file.read_exact(&mut dir_bytes)?;
112 let mut cur = Cursor::new(&dir_bytes, "section directory");
113 let mut sections = Vec::with_capacity(superblock.section_count as usize);
114 for _ in 0..superblock.section_count {
115 let ty_raw = cur.u32()?;
116 let section_type = SectionType::from_u32(ty_raw).ok_or(FormatError::UnknownValue {
117 what: "section type",
118 value: ty_raw,
119 })?;
120 sections.push(SectionEntry {
121 section_type,
122 offset: cur.u64()?,
123 length: cur.u64()?,
124 hash: cur.hash()?,
125 });
126 }
127
128 let read_section = |file: &mut File, ty: SectionType| -> Result<Vec<u8>> {
129 let entry = sections
130 .iter()
131 .find(|s| s.section_type == ty)
132 .ok_or(FormatError::MissingSection(ty))?;
133 if entry.offset > file_len || entry.length > file_len - entry.offset {
134 return Err(FormatError::Malformed("section bounds"));
135 }
136 file.seek(SeekFrom::Start(entry.offset))?;
137 let mut buf = vec![0u8; entry.length as usize];
138 file.read_exact(&mut buf)?;
139 if hash_chunk(&buf) != entry.hash {
141 return Err(FormatError::SectionHashMismatch(ty));
142 }
143 Ok(buf)
144 };
145
146 let tracks = decode_tracks(&read_section(&mut file, SectionType::Tracks)?)?;
147 let dict = decode_dict(&read_section(&mut file, SectionType::Dict)?)?;
148 let chunks = decode_chunks(&read_section(&mut file, SectionType::Chunks)?)?;
149 let segments = decode_segments(&read_section(&mut file, SectionType::Segments)?)?;
150 let meta = decode_meta(&read_section(&mut file, SectionType::Meta)?)?;
151 let integrity = decode_integrity(&read_section(&mut file, SectionType::Integrity)?)?;
152
153 let data_entry = sections
154 .iter()
155 .find(|s| s.section_type == SectionType::Data)
156 .ok_or(FormatError::MissingSection(SectionType::Data))?;
157 let (data_offset, data_len) = (data_entry.offset, data_entry.length);
158
159 Ok(Self {
160 file,
161 superblock,
162 sections,
163 tracks,
164 dict,
165 chunks,
166 segments,
167 meta,
168 integrity,
169 data_offset,
170 data_len,
171 })
172 }
173
174 pub fn superblock(&self) -> &Superblock {
175 &self.superblock
176 }
177 pub fn sections(&self) -> &[SectionEntry] {
178 &self.sections
179 }
180 pub fn tracks(&self) -> &[TrackRecord] {
181 &self.tracks
182 }
183 pub fn dict(&self) -> &[u32] {
184 &self.dict
185 }
186 pub fn chunks(&self) -> &[ChunkRecord] {
187 &self.chunks
188 }
189 pub fn segments(&self) -> &[SegmentRecord] {
190 &self.segments
191 }
192 pub fn meta(&self) -> &[(String, String)] {
193 &self.meta
194 }
195 pub fn integrity(&self) -> &Integrity {
196 &self.integrity
197 }
198
199 pub fn track(&self, track_id: u32) -> Result<&TrackRecord> {
200 self.tracks
201 .iter()
202 .find(|t| t.track_id == track_id)
203 .ok_or(FormatError::TrackNotFound(track_id))
204 }
205
206 pub fn segments_for_track(&self, track_id: u32) -> Vec<&SegmentRecord> {
208 let mut segs: Vec<&SegmentRecord> = self
209 .segments
210 .iter()
211 .filter(|s| s.track_id == track_id)
212 .collect();
213 segs.sort_by_key(|s| (s.pts_start, s.segment_id));
214 segs
215 }
216
217 pub fn read_chunk_stored(&mut self, index: u32) -> Result<(Vec<u8>, u32, u32)> {
222 let rec = self
223 .chunks
224 .get(index as usize)
225 .ok_or(FormatError::ChunkIndexOutOfRange(index))?
226 .clone();
227 if rec.data_offset > self.data_len
229 || rec.len_stored as u64 > self.data_len - rec.data_offset
230 || rec.len_raw as u64 > crate::MAX_CHUNK_RAW
231 {
232 return Err(FormatError::Malformed("chunk bounds"));
233 }
234 self.file
235 .seek(SeekFrom::Start(self.data_offset + rec.data_offset))?;
236 let mut stored = vec![0u8; rec.len_stored as usize];
237 self.file.read_exact(&mut stored)?;
238 Ok((stored, rec.flags, rec.len_raw))
239 }
240
241 pub fn read_chunk(&mut self, index: u32) -> Result<Vec<u8>> {
243 let (stored, flags, len_raw) = self.read_chunk_stored(index)?;
244 let raw = if flags & CHUNK_FLAG_ZSTD != 0 {
245 zstd::bulk::decompress(&stored, len_raw as usize).map_err(FormatError::Zstd)?
248 } else {
249 stored
250 };
251 let rec = &self.chunks[index as usize];
252 if raw.len() != len_raw as usize || hash_chunk(&raw) != rec.hash {
253 return Err(FormatError::ChunkHashMismatch { index });
254 }
255 Ok(raw)
256 }
257
258 pub fn segment_bytes(&mut self, segment: &SegmentRecord) -> Result<Vec<u8>> {
260 let mut out = Vec::new();
261 for &c in &segment.chunks {
262 out.extend_from_slice(&self.read_chunk(c)?);
263 }
264 Ok(out)
265 }
266
267 pub fn track_init_bytes(&mut self, track_id: u32) -> Result<Vec<u8>> {
269 let init_chunks = self.track(track_id)?.init_chunks.clone();
270 let mut out = Vec::new();
271 for c in init_chunks {
272 out.extend_from_slice(&self.read_chunk(c)?);
273 }
274 Ok(out)
275 }
276
277 pub fn embedded_signature(&self) -> Option<([u8; 64], [u8; 32])> {
279 let hex_bytes = |key: &str, len: usize| -> Option<Vec<u8>> {
280 let value = self.meta.iter().find(|(k, _)| k == key).map(|(_, v)| v)?;
281 if value.len() != len * 2 {
282 return None;
283 }
284 (0..len)
285 .map(|i| u8::from_str_radix(&value[i * 2..i * 2 + 2], 16).ok())
286 .collect()
287 };
288 let sig: [u8; 64] = hex_bytes("sig.ed25519", 64)?.try_into().ok()?;
289 let pk: [u8; 32] = hex_bytes("sig.pubkey", 32)?.try_into().ok()?;
290 Some((sig, pk))
291 }
292
293 pub fn verify_signature(&self) -> Result<SignatureStatus> {
298 let Some((sig, pk)) = self.embedded_signature() else {
299 return Ok(SignatureStatus::Unsigned);
300 };
301 let key = ed25519_dalek::VerifyingKey::from_bytes(&pk)
302 .map_err(|_| FormatError::SignatureInvalid)?;
303 let message =
304 content_signature_message(&self.integrity.merkle_root, self.integrity.chunk_count);
305 use ed25519_dalek::Verifier;
306 key.verify(&message, &ed25519_dalek::Signature::from_bytes(&sig))
307 .map_err(|_| FormatError::SignatureInvalid)?;
308 Ok(SignatureStatus::Valid(pk))
309 }
310
311 pub fn verify(&mut self) -> Result<VerifyReport> {
314 let mut bytes = 0u64;
315 for i in 0..self.chunks.len() as u32 {
316 bytes += self.read_chunk(i)?.len() as u64;
317 }
318
319 let hashes: Vec<_> = self.chunks.iter().map(|c| c.hash).collect();
320 if merkle_root(&hashes) != self.integrity.merkle_root
321 || self.integrity.chunk_count != self.chunks.len() as u64
322 {
323 return Err(FormatError::MerkleMismatch);
324 }
325
326 let data_entry_hash = self
328 .sections
329 .iter()
330 .find(|s| s.section_type == SectionType::Data)
331 .map(|s| s.hash)
332 .ok_or(FormatError::MissingSection(SectionType::Data))?;
333 self.file.seek(SeekFrom::Start(self.data_offset))?;
334 let mut hasher = Hasher::new();
335 let mut remaining = self.data_len;
336 let mut buf = vec![0u8; 1 << 20];
337 while remaining > 0 {
338 let n = remaining.min(buf.len() as u64) as usize;
339 self.file.read_exact(&mut buf[..n])?;
340 hasher.update(&buf[..n]);
341 remaining -= n as u64;
342 }
343 if hasher.finalize() != data_entry_hash {
344 return Err(FormatError::SectionHashMismatch(SectionType::Data));
345 }
346
347 Ok(VerifyReport {
348 chunks_verified: self.chunks.len() as u64,
349 bytes_verified: bytes,
350 merkle_ok: true,
351 data_section_ok: true,
352 })
353 }
354}
355
356fn decode_tracks(buf: &[u8]) -> Result<Vec<TrackRecord>> {
357 let mut cur = Cursor::new(buf, "tracks");
358 let count = cur.u32()?;
359 let mut out = Vec::with_capacity((count as usize).min(buf.len()));
360 for _ in 0..count {
361 let track_id = cur.u32()?;
362 let kind_raw = cur.u8()?;
363 let kind = TrackKind::from_u8(kind_raw).ok_or(FormatError::UnknownValue {
364 what: "track kind",
365 value: kind_raw as u32,
366 })?;
367 let flags = cur.u8()?;
368 let codec = cur.str16()?;
369 let name = cur.str16()?;
370 let timescale = cur.u32()?;
371 let n = cur.u32()?;
372 let mut init_chunks = Vec::with_capacity((n as usize).min(buf.len()));
373 for _ in 0..n {
374 init_chunks.push(cur.u32()?);
375 }
376 out.push(TrackRecord {
377 track_id,
378 kind,
379 flags,
380 codec,
381 name,
382 timescale,
383 init_chunks,
384 });
385 }
386 Ok(out)
387}
388
389fn decode_dict(buf: &[u8]) -> Result<Vec<u32>> {
390 let mut cur = Cursor::new(buf, "dict");
391 let count = cur.u32()?;
392 let mut out = Vec::with_capacity((count as usize).min(buf.len()));
393 for _ in 0..count {
394 out.push(cur.u32()?);
395 }
396 Ok(out)
397}
398
399fn decode_chunks(buf: &[u8]) -> Result<Vec<ChunkRecord>> {
400 let mut cur = Cursor::new(buf, "chunks");
401 let count = cur.u32()?;
402 let mut out = Vec::with_capacity((count as usize).min(buf.len()));
403 for _ in 0..count {
404 out.push(ChunkRecord {
405 hash: cur.hash()?,
406 data_offset: cur.u64()?,
407 len_raw: cur.u32()?,
408 len_stored: cur.u32()?,
409 flags: cur.u32()?,
410 });
411 }
412 Ok(out)
413}
414
415fn decode_segments(buf: &[u8]) -> Result<Vec<SegmentRecord>> {
416 let mut cur = Cursor::new(buf, "segments");
417 let count = cur.u32()?;
418 let mut out = Vec::with_capacity((count as usize).min(buf.len()));
419 for _ in 0..count {
420 let segment_id = cur.u64()?;
421 let track_id = cur.u32()?;
422 let pts_start = cur.u64()?;
423 let duration = cur.u32()?;
424 let flags = cur.u32()?;
425 let n = cur.u32()?;
426 let mut chunks = Vec::with_capacity((n as usize).min(buf.len()));
427 for _ in 0..n {
428 chunks.push(cur.u32()?);
429 }
430 out.push(SegmentRecord {
431 segment_id,
432 track_id,
433 pts_start,
434 duration,
435 flags,
436 chunks,
437 });
438 }
439 Ok(out)
440}
441
442fn decode_meta(buf: &[u8]) -> Result<Vec<(String, String)>> {
443 let mut cur = Cursor::new(buf, "meta");
444 let count = cur.u32()?;
445 let mut out = Vec::with_capacity((count as usize).min(buf.len()));
446 for _ in 0..count {
447 let key = cur.str16()?;
448 let value_bytes = cur.bytes32()?;
449 let value = String::from_utf8(value_bytes).map_err(|_| FormatError::Malformed("meta"))?;
450 out.push((key, value));
451 }
452 Ok(out)
453}
454
455fn decode_integrity(buf: &[u8]) -> Result<Integrity> {
456 let mut cur = Cursor::new(buf, "integrity");
457 Ok(Integrity {
458 merkle_root: cur.hash()?,
459 chunk_count: cur.u64()?,
460 total_raw: cur.u64()?,
461 total_stored: cur.u64()?,
462 })
463}