1#![allow(missing_docs)]
11
12use crate::error::{Error, Result};
13
14pub const MAGIC: [u8; 4] = *b"AINF";
16pub const STREAM_MAGIC: [u8; 4] = *b"AINS";
18pub const FORMAT_MAJOR: u16 = 1;
20pub const FORMAT_MINOR: u16 = 0;
22pub const HEADER_SIZE: usize = 224;
24pub const NODE_RECORD_SIZE: usize = 32;
26pub const CHILD_SIZE: usize = 4;
28pub const FIELD_SIZE: usize = 16;
30pub const PART_SIZE: usize = 8;
32pub const EDGE_SIZE: usize = 8;
34pub const BLOB_ENTRY_SIZE: usize = 40;
36pub const ANNOTATION_SIZE: usize = 16;
38pub const UNIT_SIZE: usize = 8;
40pub const TYPE_SIZE: usize = 16;
42pub const AUX_SIZE: usize = 8;
44pub const NUM_SIZE: usize = 8;
46pub const EXT_DIR_SIZE: usize = 16;
48pub const PROV_HEADER_SIZE: usize = 96;
50pub const PROV_ENTRY_SIZE: usize = 16;
52pub const SECRET_ENTRY_SIZE: usize = 8;
54
55pub const AUX_CONSTRAINT_MIN: u32 = 0;
58pub const AUX_CONSTRAINT_MAX: u32 = 1;
60pub const AUX_CONSTRAINT_ENUM_ITEM: u32 = 2;
62pub const AUX_CONSTRAINT_PATTERN: u32 = 3;
64pub const AUX_CONSTRAINT_CUSTOM: u32 = 4;
66pub const AUX_PATCH_PATH: u32 = 10;
68pub const AUX_PATCH_VALUE: u32 = 11;
70pub const AUX_PATCH_RENAME_TO: u32 = 12;
72pub const AUX_SCHEMA_MIN: u32 = 20;
74pub const AUX_SCHEMA_MAX: u32 = 21;
76pub const AUX_SCHEMA_MIN_LEN: u32 = 22;
78pub const AUX_SCHEMA_MAX_LEN: u32 = 23;
80pub const AUX_SCHEMA_PATTERN: u32 = 24;
82pub const AUX_SCHEMA_DEFAULT: u32 = 25;
84pub const AUX_SCHEMA_DEPRECATED: u32 = 26;
86pub const AUX_TYPE_ENUM_ITEM: u32 = 30;
88pub const AUX_TYPE_TABLE: u32 = 31;
90
91pub const CONSTRAINT_KIND_MIN_MAX: u32 = 0;
93pub const CONSTRAINT_KIND_ENUM: u32 = 1;
95pub const CONSTRAINT_KIND_PATTERN: u32 = 2;
97pub const CONSTRAINT_KIND_ODD: u32 = 3;
99pub const CONSTRAINT_KIND_CUSTOM: u32 = 4;
101
102pub const PATCH_KIND_SET: u32 = 0;
104pub const PATCH_KIND_REMOVE: u32 = 1;
106pub const PATCH_KIND_RENAME: u32 = 2;
108
109pub const ANN_KIND_INTENT: u32 = 0;
111pub const ANN_KIND_CONSTRAINT: u32 = 1;
113pub const ANN_KIND_AI_IMMUTABLE: u32 = 2;
115
116pub const PART_LITERAL: u32 = 0;
118pub const PART_EXPR: u32 = 1;
120
121pub const FLAG_CANONICAL: u16 = 1 << 0;
124pub const FLAG_ANNOTATIONS: u16 = 1 << 1;
126pub const FLAG_PROVENANCE: u16 = 1 << 2;
128pub const FLAG_EXTENSIONS: u16 = 1 << 3;
130pub const FLAG_STREAMED: u16 = 1 << 4;
132pub const FLAG_SECRETS: u16 = 1 << 5;
134pub const FLAG_KNOWN_MASK: u16 = FLAG_CANONICAL
136 | FLAG_ANNOTATIONS
137 | FLAG_PROVENANCE
138 | FLAG_EXTENSIONS
139 | FLAG_STREAMED
140 | FLAG_SECRETS;
141
142pub const NODE_FLAG_0: u8 = 1 << 0;
144pub const NODE_FLAG_1: u8 = 1 << 1;
146pub const NODE_FLAG_2: u8 = 1 << 2;
148pub const NODE_FLAG_3: u8 = 1 << 3;
150pub const NODE_FLAG_4: u8 = 1 << 4;
152pub const NODE_FLAG_5: u8 = 1 << 5;
154pub const NODE_FLAG_6: u8 = 1 << 6;
156pub const NODE_FLAG_KNOWN: u8 =
158 NODE_FLAG_0 | NODE_FLAG_1 | NODE_FLAG_2 | NODE_FLAG_3 | NODE_FLAG_4 | NODE_FLAG_5 | NODE_FLAG_6;
159
160pub const SECTION_COUNT: usize = 17;
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum Section {
167 Nodes,
168 Children,
169 Fields,
170 Parts,
171 Edges,
172 Strings,
173 Blobs,
174 Annotations,
175 Provenance,
176 Extensions,
177 Units,
178 Types,
179 Aux,
180 Numbers,
181 Secrets,
182 Hashes,
183 Bindings,
185}
186
187impl Section {
188 pub const ALL: [Section; SECTION_COUNT] = [
189 Section::Nodes,
190 Section::Children,
191 Section::Fields,
192 Section::Parts,
193 Section::Edges,
194 Section::Strings,
195 Section::Blobs,
196 Section::Annotations,
197 Section::Provenance,
198 Section::Extensions,
199 Section::Units,
200 Section::Types,
201 Section::Aux,
202 Section::Numbers,
203 Section::Secrets,
204 Section::Hashes,
205 Section::Bindings,
206 ];
207
208 pub const fn name(self) -> &'static str {
209 match self {
210 Section::Nodes => "nodes",
211 Section::Children => "children",
212 Section::Fields => "fields",
213 Section::Parts => "parts",
214 Section::Edges => "edges",
215 Section::Strings => "strings",
216 Section::Blobs => "blobs",
217 Section::Annotations => "annotations",
218 Section::Provenance => "provenance",
219 Section::Extensions => "extensions",
220 Section::Units => "units",
221 Section::Types => "types",
222 Section::Aux => "aux",
223 Section::Numbers => "numbers",
224 Section::Secrets => "secrets",
225 Section::Hashes => "hashes",
226 Section::Bindings => "bindings",
227 }
228 }
229}
230
231pub const fn align8(value: u64) -> u64 {
233 (value + 7) & !7
234}
235
236pub fn canonical_f64(value: f64) -> f64 {
239 if value.is_nan() {
240 f64::from_bits(CANONICAL_NAN_BITS)
241 } else if value == 0.0 {
242 0.0
243 } else {
244 value
245 }
246}
247
248pub const CANONICAL_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub struct Header {
254 pub major: u16,
255 pub minor: u16,
256 pub flags: u16,
257 pub header_size: u16,
258 pub content_hash: [u8; 32],
259 pub dialect_hash: u64,
260 pub total_size: u64,
261 pub sections: [SectionSpan; SECTION_COUNT],
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
266pub struct SectionSpan {
267 pub offset: u32,
268 pub count: u32,
269}
270
271impl SectionSpan {
272 pub fn is_empty(&self) -> bool {
273 self.count == 0
274 }
275
276 pub fn bytes(&self, stride: usize) -> u64 {
278 self.count as u64 * stride as u64
279 }
280}
281
282impl Header {
283 pub fn span(&self, section: Section) -> SectionSpan {
284 self.sections[section as usize]
285 }
286
287 pub fn span_mut(&mut self, section: Section) -> &mut SectionSpan {
288 &mut self.sections[section as usize]
289 }
290
291 pub fn parse(buf: &[u8]) -> Result<Header> {
296 if buf.len() < 4 || buf[0..4] != MAGIC {
297 return Err(Error::Magic);
298 }
299 if buf.len() < HEADER_SIZE {
300 return Err(Error::HeaderTruncated);
301 }
302 let major = u16::from_le_bytes([buf[4], buf[5]]);
303 let minor = u16::from_le_bytes([buf[6], buf[7]]);
304 let flags = u16::from_le_bytes([buf[8], buf[9]]);
305 let header_size = u16::from_le_bytes([buf[10], buf[11]]);
306 if major != FORMAT_MAJOR {
307 return Err(Error::VersionMajor(major));
308 }
309 if minor > FORMAT_MINOR {
310 return Err(Error::VersionMinor(minor));
311 }
312 if header_size as usize != HEADER_SIZE {
313 return Err(Error::HeaderSize(header_size as u8));
314 }
315 if flags & !FLAG_KNOWN_MASK != 0 {
316 return Err(Error::UnknownFlags(flags & !FLAG_KNOWN_MASK));
317 }
318 if read_u32(buf, 12)? != 0 {
319 return Err(Error::ReservedNonZero {
320 section: "header",
321 offset: 12,
322 });
323 }
324 let mut content_hash = [0u8; 32];
325 content_hash.copy_from_slice(&buf[16..48]);
326 let dialect_hash = read_u64(buf, 48)?;
327 let total_size = read_u64(buf, 56)?;
328 let mut sections = [SectionSpan::default(); SECTION_COUNT];
329 for (index, span) in sections.iter_mut().enumerate() {
330 let base = 64 + index * 8;
331 span.offset = read_u32(buf, base)?;
332 span.count = read_u32(buf, base + 4)?;
333 }
334 let reserved_start = 64 + SECTION_COUNT * 8;
335 if let Some(position) = (reserved_start..HEADER_SIZE).find(|offset| buf[*offset] != 0) {
336 return Err(Error::ReservedNonZero {
337 section: "header",
338 offset: position as u64,
339 });
340 }
341 Ok(Header {
342 major,
343 minor,
344 flags,
345 header_size,
346 content_hash,
347 dialect_hash,
348 total_size,
349 sections,
350 })
351 }
352
353 pub fn write(&self, out: &mut [u8]) {
355 debug_assert!(out.len() >= HEADER_SIZE);
356 out[0..4].copy_from_slice(&MAGIC);
357 out[4..6].copy_from_slice(&self.major.to_le_bytes());
358 out[6..8].copy_from_slice(&self.minor.to_le_bytes());
359 out[8..10].copy_from_slice(&self.flags.to_le_bytes());
360 out[10..12].copy_from_slice(&self.header_size.to_le_bytes());
361 out[12..16].copy_from_slice(&0u32.to_le_bytes());
362 out[16..48].copy_from_slice(&self.content_hash);
363 out[48..56].copy_from_slice(&self.dialect_hash.to_le_bytes());
364 out[56..64].copy_from_slice(&self.total_size.to_le_bytes());
365 for (index, span) in self.sections.iter().enumerate() {
366 let base = 64 + index * 8;
367 out[base..base + 4].copy_from_slice(&span.offset.to_le_bytes());
368 out[base + 4..base + 8].copy_from_slice(&span.count.to_le_bytes());
369 }
370 }
371}
372
373#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub struct Limits {
381 pub max_nodes: u32,
382 pub max_edges: u32,
383 pub max_children: u32,
384 pub max_fields: u32,
385 pub max_parts: u32,
386 pub max_strings: u32,
387 pub max_string_bytes: u32,
388 pub max_blobs: u32,
389 pub max_blob_bytes: u32,
390 pub max_annotations: u32,
391 pub max_units: u32,
392 pub max_types: u32,
393 pub max_aux: u32,
394 pub max_numbers: u32,
395 pub max_secrets: u32,
396 pub max_secret_bytes: u32,
397 pub max_extensions: u32,
398 pub max_prov_entries: u32,
399 pub max_total_bytes: u64,
400 pub max_depth: u32,
401}
402
403impl Default for Limits {
404 fn default() -> Self {
405 Self {
406 max_nodes: 1 << 22,
407 max_edges: 1 << 24,
408 max_children: 1 << 24,
409 max_fields: 1 << 24,
410 max_parts: 1 << 22,
411 max_strings: 1 << 22,
412 max_string_bytes: 64 << 20,
413 max_blobs: 1 << 20,
414 max_blob_bytes: 512 << 20,
415 max_annotations: 1 << 22,
416 max_units: 1 << 16,
417 max_types: 1 << 20,
418 max_aux: 1 << 24,
419 max_numbers: 1 << 24,
420 max_secrets: 1 << 20,
421 max_secret_bytes: 16 << 20,
422 max_extensions: 1 << 16,
423 max_prov_entries: 1 << 16,
424 max_total_bytes: 1 << 30,
425 max_depth: 4096,
426 }
427 }
428}
429
430impl Limits {
431 pub fn strict() -> Self {
433 Self {
434 max_nodes: 4096,
435 max_edges: 16384,
436 max_children: 16384,
437 max_fields: 16384,
438 max_parts: 4096,
439 max_strings: 4096,
440 max_string_bytes: 1 << 20,
441 max_blobs: 256,
442 max_blob_bytes: 1 << 20,
443 max_annotations: 4096,
444 max_units: 1024,
445 max_types: 1024,
446 max_aux: 16384,
447 max_numbers: 16384,
448 max_secrets: 256,
449 max_secret_bytes: 1 << 16,
450 max_extensions: 64,
451 max_prov_entries: 256,
452 max_total_bytes: 8 << 20,
453 max_depth: 128,
454 }
455 }
456
457 pub fn check(&self) -> Result<()> {
459 for (name, value) in [
460 ("max_nodes", self.max_nodes),
461 ("max_string_bytes", self.max_string_bytes),
462 ("max_blob_bytes", self.max_blob_bytes),
463 ("max_units", self.max_units),
464 ("max_depth", self.max_depth),
465 ] {
466 if value == 0 {
467 return Err(Error::InvalidArgument(match name {
468 "max_nodes" => "max_nodes must be non-zero",
469 "max_string_bytes" => "max_string_bytes must be non-zero",
470 "max_blob_bytes" => "max_blob_bytes must be non-zero",
471 "max_units" => "max_units must be non-zero",
472 _ => "max_depth must be non-zero",
473 }));
474 }
475 }
476 Ok(())
477 }
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub struct DecodeOpts {
483 pub limits: Limits,
484 pub verify_hash: bool,
487 pub verify_canonical: bool,
491}
492
493impl Default for DecodeOpts {
494 fn default() -> Self {
495 Self {
496 limits: Limits::default(),
497 verify_hash: true,
498 verify_canonical: false,
499 }
500 }
501}
502
503impl DecodeOpts {
504 pub fn paranoid() -> Self {
506 Self {
507 limits: Limits::strict(),
508 verify_hash: true,
509 verify_canonical: true,
510 }
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516pub struct EncodeOpts {
517 pub write_hash: bool,
519 pub mark_canonical: bool,
521 pub streamed: bool,
523}
524
525impl Default for EncodeOpts {
526 fn default() -> Self {
527 Self {
528 write_hash: true,
529 mark_canonical: true,
530 streamed: false,
531 }
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub struct Meta {
538 pub flags: u16,
539 pub major: u16,
540 pub minor: u16,
541 pub dialect_hash: u64,
542 pub content_hash: [u8; 32],
543 pub total_size: u64,
544 pub nodes: u32,
545 pub edges: u32,
546 pub strings: u32,
547 pub string_bytes: u32,
548 pub blobs: u32,
549 pub blob_bytes: u32,
550 pub annotations: u32,
551 pub units: u32,
552 pub types: u32,
553 pub secrets: u32,
554 pub extensions: u32,
555}
556
557impl Meta {
558 pub fn has_annotations(&self) -> bool {
559 self.annotations > 0
560 }
561
562 pub fn has_provenance(&self) -> bool {
563 self.flags & FLAG_PROVENANCE != 0
564 }
565}
566
567pub fn read_u32(buf: &[u8], offset: usize) -> Result<u32> {
569 let end = offset + 4;
570 if end > buf.len() {
571 return Err(Error::Truncated {
572 needed: end as u64,
573 found: buf.len() as u64,
574 });
575 }
576 Ok(u32::from_le_bytes([
577 buf[offset],
578 buf[offset + 1],
579 buf[offset + 2],
580 buf[offset + 3],
581 ]))
582}
583
584pub fn read_u64(buf: &[u8], offset: usize) -> Result<u64> {
586 let end = offset + 8;
587 if end > buf.len() {
588 return Err(Error::Truncated {
589 needed: end as u64,
590 found: buf.len() as u64,
591 });
592 }
593 let mut bytes = [0u8; 8];
594 bytes.copy_from_slice(&buf[offset..end]);
595 Ok(u64::from_le_bytes(bytes))
596}
597
598pub fn put_u16(out: &mut Vec<u8>, value: u16) {
600 out.extend_from_slice(&value.to_le_bytes());
601}
602
603pub fn put_u32(out: &mut Vec<u8>, value: u32) {
604 out.extend_from_slice(&value.to_le_bytes());
605}
606
607pub fn put_u64(out: &mut Vec<u8>, value: u64) {
608 out.extend_from_slice(&value.to_le_bytes());
609}
610
611pub fn pad8(out: &mut Vec<u8>) {
613 while out.len() % 8 != 0 {
614 out.push(0);
615 }
616}
617
618pub fn dialect_hash() -> u64 {
623 let mut hasher = blake3::Hasher::new();
624 hasher.update(b"active-infinity/dialect");
625 hasher.update(&FORMAT_MAJOR.to_le_bytes());
626 hasher.update(&FORMAT_MINOR.to_le_bytes());
627 hasher.update(&crate::unit::UNIT_MAX_BUILTIN.to_le_bytes());
628 hasher.update(b"node-kinds:0..=24");
629 hasher.update(b"aux-tags:0,1,2,3,4,10,11,12,20,21,22,23,24,25,26,30,31");
630 let digest = hasher.finalize();
631 let mut bytes = [0u8; 8];
632 bytes.copy_from_slice(&digest.as_bytes()[..8]);
633 u64::from_le_bytes(bytes)
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639
640 #[test]
641 fn header_round_trips() {
642 let mut header = Header {
643 major: FORMAT_MAJOR,
644 minor: FORMAT_MINOR,
645 flags: FLAG_CANONICAL,
646 header_size: HEADER_SIZE as u16,
647 content_hash: [7u8; 32],
648 dialect_hash: dialect_hash(),
649 total_size: 512,
650 sections: [SectionSpan::default(); SECTION_COUNT],
651 };
652 header.span_mut(Section::Nodes).offset = 192;
653 header.span_mut(Section::Nodes).count = 3;
654 let mut bytes = vec![0u8; HEADER_SIZE];
655 header.write(&mut bytes);
656 let parsed = Header::parse(&bytes).expect("parse");
657 assert_eq!(parsed, header);
658 }
659
660 #[test]
661 fn rejects_short_and_misversioned_input() {
662 assert_eq!(Header::parse(b"AI"), Err(Error::Magic));
663 assert_eq!(Header::parse(b"XXXX").unwrap_err(), Error::Magic);
664 let mut bytes = vec![0u8; HEADER_SIZE];
665 bytes[0..4].copy_from_slice(&MAGIC);
666 assert!(matches!(Header::parse(&bytes), Err(Error::VersionMajor(0))));
668 bytes[4..6].copy_from_slice(&FORMAT_MAJOR.to_le_bytes());
669 assert_eq!(Header::parse(&bytes).unwrap_err(), Error::HeaderSize(0));
671 bytes[10..12].copy_from_slice(&(HEADER_SIZE as u16).to_le_bytes());
672 assert!(Header::parse(&bytes).is_ok());
673 }
674
675 #[test]
676 fn alignment_and_rounding() {
677 assert_eq!(align8(0), 0);
678 assert_eq!(align8(1), 8);
679 assert_eq!(align8(8), 8);
680 assert_eq!(align8(9), 16);
681 }
682
683 #[test]
684 fn floats_canonicalize() {
685 assert_eq!(canonical_f64(-0.0).to_bits(), 0.0f64.to_bits());
686 assert_eq!(canonical_f64(f64::NAN).to_bits(), CANONICAL_NAN_BITS);
687 assert_eq!(canonical_f64(1.5), 1.5);
688 }
689
690 #[test]
691 fn dialect_hash_is_stable() {
692 assert_eq!(dialect_hash(), dialect_hash());
693 assert_ne!(dialect_hash(), 0);
694 }
695}