1mod reader;
31mod writer;
32
33pub use reader::{Reader, SignatureStatus, VerifyReport};
34pub use writer::{PackStats, Writer};
35
36use cavs_hash::ChunkHash;
37
38pub const MAGIC: [u8; 4] = *b"CAVS";
40pub const VERSION_MAJOR: u16 = 1;
41pub const VERSION_MINOR: u16 = 0;
42pub const SUPERBLOCK_LEN: u64 = 64;
44pub const SECTION_DIR_ENTRY_LEN: usize = 52;
46pub const MAX_CHUNK_RAW: u64 = 256 * 1024 * 1024;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53#[repr(u32)]
54pub enum SectionType {
55 Tracks = 1,
56 Dict = 2,
57 Chunks = 3,
58 Segments = 4,
59 Data = 5,
60 Integrity = 6,
61 Meta = 7,
62}
63
64impl SectionType {
65 pub fn from_u32(v: u32) -> Option<Self> {
66 Some(match v {
67 1 => SectionType::Tracks,
68 2 => SectionType::Dict,
69 3 => SectionType::Chunks,
70 4 => SectionType::Segments,
71 5 => SectionType::Data,
72 6 => SectionType::Integrity,
73 7 => SectionType::Meta,
74 _ => return None,
75 })
76 }
77}
78
79pub const COMPRESSION_NONE: u8 = 0;
81pub const COMPRESSION_ZSTD: u8 = 1;
82
83pub const CHUNK_FLAG_ZSTD: u32 = 1 << 0;
85
86pub const SEGMENT_FLAG_RANDOM_ACCESS: u32 = 1 << 0;
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[repr(u8)]
92pub enum TrackKind {
93 Video = 0,
94 Audio = 1,
95 Subtitle = 2,
96 Data = 3,
98}
99
100impl TrackKind {
101 pub fn from_u8(v: u8) -> Option<Self> {
102 Some(match v {
103 0 => TrackKind::Video,
104 1 => TrackKind::Audio,
105 2 => TrackKind::Subtitle,
106 3 => TrackKind::Data,
107 _ => return None,
108 })
109 }
110
111 pub fn label(&self) -> &'static str {
112 match self {
113 TrackKind::Video => "video",
114 TrackKind::Audio => "audio",
115 TrackKind::Subtitle => "subtitle",
116 TrackKind::Data => "data",
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct Superblock {
124 pub version_major: u16,
125 pub version_minor: u16,
126 pub feature_flags: u32,
127 pub hash_algo: u8,
128 pub compression_algo: u8,
129 pub asset_uuid: [u8; 16],
130 pub timescale: u32,
131 pub section_count: u32,
132 pub section_dir_offset: u64,
133 pub file_size: u64,
134}
135
136#[derive(Debug, Clone)]
138pub struct SectionEntry {
139 pub section_type: SectionType,
140 pub offset: u64,
141 pub length: u64,
142 pub hash: ChunkHash,
144}
145
146#[derive(Debug, Clone)]
148pub struct ChunkRecord {
149 pub hash: ChunkHash,
150 pub data_offset: u64,
151 pub len_raw: u32,
152 pub len_stored: u32,
153 pub flags: u32,
154}
155
156#[derive(Debug, Clone)]
158pub struct TrackRecord {
159 pub track_id: u32,
160 pub kind: TrackKind,
161 pub flags: u8,
162 pub codec: String,
163 pub name: String,
165 pub timescale: u32,
166 pub init_chunks: Vec<u32>,
168}
169
170#[derive(Debug, Clone)]
173pub struct SegmentRecord {
174 pub segment_id: u64,
175 pub track_id: u32,
176 pub pts_start: u64,
177 pub duration: u32,
178 pub flags: u32,
179 pub chunks: Vec<u32>,
180}
181
182#[derive(Debug, Clone)]
184pub struct Integrity {
185 pub merkle_root: ChunkHash,
187 pub chunk_count: u64,
188 pub total_raw: u64,
189 pub total_stored: u64,
190}
191
192#[derive(Debug, thiserror::Error)]
193pub enum FormatError {
194 #[error("io error: {0}")]
195 Io(#[from] std::io::Error),
196 #[error("not a CAVS file (bad magic)")]
197 BadMagic,
198 #[error("unsupported CAVS major version {0}")]
199 UnsupportedVersion(u16),
200 #[error("truncated or malformed {0} section")]
201 Malformed(&'static str),
202 #[error("missing required section {0:?}")]
203 MissingSection(SectionType),
204 #[error("unknown enum value {value} for {what}")]
205 UnknownValue { what: &'static str, value: u32 },
206 #[error("chunk {index} hash mismatch (corrupted payload)")]
207 ChunkHashMismatch { index: u32 },
208 #[error("section {0:?} hash mismatch (corrupted section)")]
209 SectionHashMismatch(SectionType),
210 #[error("merkle root mismatch (chunk table tampered)")]
211 MerkleMismatch,
212 #[error("embedded content signature is invalid")]
213 SignatureInvalid,
214 #[error("chunk index {0} out of range")]
215 ChunkIndexOutOfRange(u32),
216 #[error("track {0} not found")]
217 TrackNotFound(u32),
218 #[error("zstd error: {0}")]
219 Zstd(std::io::Error),
220}
221
222pub type Result<T> = std::result::Result<T, FormatError>;
223
224pub(crate) mod wire {
229 use super::{FormatError, Result};
230
231 pub fn put_u16(buf: &mut Vec<u8>, v: u16) {
232 buf.extend_from_slice(&v.to_le_bytes());
233 }
234 pub fn put_u32(buf: &mut Vec<u8>, v: u32) {
235 buf.extend_from_slice(&v.to_le_bytes());
236 }
237 pub fn put_u64(buf: &mut Vec<u8>, v: u64) {
238 buf.extend_from_slice(&v.to_le_bytes());
239 }
240 pub fn put_str(buf: &mut Vec<u8>, s: &str) {
242 let bytes = s.as_bytes();
243 assert!(bytes.len() <= u16::MAX as usize, "string too long for wire");
244 put_u16(buf, bytes.len() as u16);
245 buf.extend_from_slice(bytes);
246 }
247 pub fn put_bytes32(buf: &mut Vec<u8>, b: &[u8]) {
249 put_u32(buf, b.len() as u32);
250 buf.extend_from_slice(b);
251 }
252
253 pub struct Cursor<'a> {
255 buf: &'a [u8],
256 pos: usize,
257 what: &'static str,
258 }
259
260 impl<'a> Cursor<'a> {
261 pub fn new(buf: &'a [u8], what: &'static str) -> Self {
262 Self { buf, pos: 0, what }
263 }
264
265 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
266 if self.pos + n > self.buf.len() {
267 return Err(FormatError::Malformed(self.what));
268 }
269 let s = &self.buf[self.pos..self.pos + n];
270 self.pos += n;
271 Ok(s)
272 }
273
274 pub fn u8(&mut self) -> Result<u8> {
275 Ok(self.take(1)?[0])
276 }
277 pub fn u16(&mut self) -> Result<u16> {
278 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
279 }
280 pub fn u32(&mut self) -> Result<u32> {
281 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
282 }
283 pub fn u64(&mut self) -> Result<u64> {
284 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
285 }
286 pub fn hash(&mut self) -> Result<[u8; 32]> {
287 Ok(self.take(32)?.try_into().unwrap())
288 }
289 pub fn str16(&mut self) -> Result<String> {
290 let len = self.u16()? as usize;
291 let bytes = self.take(len)?;
292 String::from_utf8(bytes.to_vec()).map_err(|_| FormatError::Malformed(self.what))
293 }
294 pub fn bytes32(&mut self) -> Result<Vec<u8>> {
295 let len = self.u32()? as usize;
296 Ok(self.take(len)?.to_vec())
297 }
298 }
299}