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 try_write(&self, out: &mut [u8]) -> crate::Result<()> {
358 if out.len() < HEADER_SIZE {
359 return Err(crate::Error::Encode("header buffer is too short"));
360 }
361 self.write(out);
362 Ok(())
363 }
364
365 pub fn write(&self, out: &mut [u8]) {
372 assert!(out.len() >= HEADER_SIZE, "header buffer is too short");
376 out[0..4].copy_from_slice(&MAGIC);
377 out[4..6].copy_from_slice(&self.major.to_le_bytes());
378 out[6..8].copy_from_slice(&self.minor.to_le_bytes());
379 out[8..10].copy_from_slice(&self.flags.to_le_bytes());
380 out[10..12].copy_from_slice(&self.header_size.to_le_bytes());
381 out[12..16].copy_from_slice(&0u32.to_le_bytes());
382 out[16..48].copy_from_slice(&self.content_hash);
383 out[48..56].copy_from_slice(&self.dialect_hash.to_le_bytes());
384 out[56..64].copy_from_slice(&self.total_size.to_le_bytes());
385 for (index, span) in self.sections.iter().enumerate() {
386 let base = 64 + index * 8;
387 out[base..base + 4].copy_from_slice(&span.offset.to_le_bytes());
388 out[base + 4..base + 8].copy_from_slice(&span.count.to_le_bytes());
389 }
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400pub struct Limits {
401 pub max_nodes: u32,
402 pub max_edges: u32,
403 pub max_children: u32,
404 pub max_fields: u32,
405 pub max_parts: u32,
406 pub max_strings: u32,
407 pub max_string_bytes: u32,
408 pub max_blobs: u32,
409 pub max_blob_bytes: u32,
410 pub max_annotations: u32,
411 pub max_units: u32,
412 pub max_types: u32,
413 pub max_aux: u32,
414 pub max_numbers: u32,
415 pub max_secrets: u32,
416 pub max_secret_bytes: u32,
417 pub max_extensions: u32,
418 pub max_prov_entries: u32,
419 pub max_total_bytes: u64,
420 pub max_depth: u32,
421}
422
423impl Default for Limits {
424 fn default() -> Self {
425 Self {
426 max_nodes: 1 << 22,
427 max_edges: 1 << 24,
428 max_children: 1 << 24,
429 max_fields: 1 << 24,
430 max_parts: 1 << 22,
431 max_strings: 1 << 22,
432 max_string_bytes: 64 << 20,
433 max_blobs: 1 << 20,
434 max_blob_bytes: 512 << 20,
435 max_annotations: 1 << 22,
436 max_units: 1 << 16,
437 max_types: 1 << 20,
438 max_aux: 1 << 24,
439 max_numbers: 1 << 24,
440 max_secrets: 1 << 20,
441 max_secret_bytes: 16 << 20,
442 max_extensions: 1 << 16,
443 max_prov_entries: 1 << 16,
444 max_total_bytes: 1 << 30,
445 max_depth: 4096,
446 }
447 }
448}
449
450impl Limits {
451 pub fn strict() -> Self {
453 Self {
454 max_nodes: 4096,
455 max_edges: 16384,
456 max_children: 16384,
457 max_fields: 16384,
458 max_parts: 4096,
459 max_strings: 4096,
460 max_string_bytes: 1 << 20,
461 max_blobs: 256,
462 max_blob_bytes: 1 << 20,
463 max_annotations: 4096,
464 max_units: 1024,
465 max_types: 1024,
466 max_aux: 16384,
467 max_numbers: 16384,
468 max_secrets: 256,
469 max_secret_bytes: 1 << 16,
470 max_extensions: 64,
471 max_prov_entries: 256,
472 max_total_bytes: 8 << 20,
473 max_depth: 128,
474 }
475 }
476
477 pub fn check(&self) -> Result<()> {
479 for (name, value) in [
480 ("max_nodes", self.max_nodes),
481 ("max_string_bytes", self.max_string_bytes),
482 ("max_blob_bytes", self.max_blob_bytes),
483 ("max_units", self.max_units),
484 ("max_depth", self.max_depth),
485 ] {
486 if value == 0 {
487 return Err(Error::InvalidArgument(match name {
488 "max_nodes" => "max_nodes must be non-zero",
489 "max_string_bytes" => "max_string_bytes must be non-zero",
490 "max_blob_bytes" => "max_blob_bytes must be non-zero",
491 "max_units" => "max_units must be non-zero",
492 _ => "max_depth must be non-zero",
493 }));
494 }
495 }
496 Ok(())
497 }
498}
499
500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub struct DecodeOpts {
503 pub limits: Limits,
504 pub verify_hash: bool,
507 pub verify_canonical: bool,
511}
512
513impl Default for DecodeOpts {
514 fn default() -> Self {
515 Self {
516 limits: Limits::default(),
517 verify_hash: true,
518 verify_canonical: false,
519 }
520 }
521}
522
523impl DecodeOpts {
524 pub fn paranoid() -> Self {
526 Self {
527 limits: Limits::strict(),
528 verify_hash: true,
529 verify_canonical: true,
530 }
531 }
532}
533
534#[derive(Debug, Clone, Copy, PartialEq, Eq)]
536pub struct EncodeOpts {
537 pub write_hash: bool,
539 pub mark_canonical: bool,
541 pub streamed: bool,
543}
544
545impl Default for EncodeOpts {
546 fn default() -> Self {
547 Self {
548 write_hash: true,
549 mark_canonical: true,
550 streamed: false,
551 }
552 }
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq)]
557pub struct Meta {
558 pub flags: u16,
559 pub major: u16,
560 pub minor: u16,
561 pub dialect_hash: u64,
562 pub content_hash: [u8; 32],
563 pub total_size: u64,
564 pub nodes: u32,
565 pub edges: u32,
566 pub strings: u32,
567 pub string_bytes: u32,
568 pub blobs: u32,
569 pub blob_bytes: u32,
570 pub annotations: u32,
571 pub units: u32,
572 pub types: u32,
573 pub secrets: u32,
574 pub extensions: u32,
575}
576
577impl Meta {
578 pub fn has_annotations(&self) -> bool {
579 self.annotations > 0
580 }
581
582 pub fn has_provenance(&self) -> bool {
583 self.flags & FLAG_PROVENANCE != 0
584 }
585}
586
587pub fn read_u32(buf: &[u8], offset: usize) -> Result<u32> {
589 let end = offset + 4;
590 if end > buf.len() {
591 return Err(Error::Truncated {
592 needed: end as u64,
593 found: buf.len() as u64,
594 });
595 }
596 Ok(u32::from_le_bytes([
597 buf[offset],
598 buf[offset + 1],
599 buf[offset + 2],
600 buf[offset + 3],
601 ]))
602}
603
604pub fn read_u64(buf: &[u8], offset: usize) -> Result<u64> {
606 let end = offset + 8;
607 if end > buf.len() {
608 return Err(Error::Truncated {
609 needed: end as u64,
610 found: buf.len() as u64,
611 });
612 }
613 let mut bytes = [0u8; 8];
614 bytes.copy_from_slice(&buf[offset..end]);
615 Ok(u64::from_le_bytes(bytes))
616}
617
618pub fn put_u16(out: &mut Vec<u8>, value: u16) {
620 out.extend_from_slice(&value.to_le_bytes());
621}
622
623pub fn put_u32(out: &mut Vec<u8>, value: u32) {
624 out.extend_from_slice(&value.to_le_bytes());
625}
626
627pub fn put_u64(out: &mut Vec<u8>, value: u64) {
628 out.extend_from_slice(&value.to_le_bytes());
629}
630
631pub fn pad8(out: &mut Vec<u8>) {
633 while out.len() % 8 != 0 {
634 out.push(0);
635 }
636}
637
638pub fn dialect_hash() -> u64 {
643 let mut hasher = blake3::Hasher::new();
644 hasher.update(b"active-infinity/dialect");
645 hasher.update(&FORMAT_MAJOR.to_le_bytes());
646 hasher.update(&FORMAT_MINOR.to_le_bytes());
647 hasher.update(&crate::unit::UNIT_MAX_BUILTIN.to_le_bytes());
648 hasher.update(b"node-kinds:0..=24");
649 hasher.update(b"aux-tags:0,1,2,3,4,10,11,12,20,21,22,23,24,25,26,30,31");
650 let digest = hasher.finalize();
651 let mut bytes = [0u8; 8];
652 bytes.copy_from_slice(&digest.as_bytes()[..8]);
653 u64::from_le_bytes(bytes)
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659
660 #[test]
661 fn header_round_trips() {
662 let mut header = Header {
663 major: FORMAT_MAJOR,
664 minor: FORMAT_MINOR,
665 flags: FLAG_CANONICAL,
666 header_size: HEADER_SIZE as u16,
667 content_hash: [7u8; 32],
668 dialect_hash: dialect_hash(),
669 total_size: 512,
670 sections: [SectionSpan::default(); SECTION_COUNT],
671 };
672 header.span_mut(Section::Nodes).offset = 192;
673 header.span_mut(Section::Nodes).count = 3;
674 let mut bytes = vec![0u8; HEADER_SIZE];
675 header.write(&mut bytes);
676 let parsed = Header::parse(&bytes).expect("parse");
677 assert_eq!(parsed, header);
678 }
679
680 #[test]
681 fn rejects_short_and_misversioned_input() {
682 assert_eq!(Header::parse(b"AI"), Err(Error::Magic));
683 assert_eq!(Header::parse(b"XXXX").unwrap_err(), Error::Magic);
684 let mut bytes = vec![0u8; HEADER_SIZE];
685 bytes[0..4].copy_from_slice(&MAGIC);
686 assert!(matches!(Header::parse(&bytes), Err(Error::VersionMajor(0))));
688 bytes[4..6].copy_from_slice(&FORMAT_MAJOR.to_le_bytes());
689 assert_eq!(Header::parse(&bytes).unwrap_err(), Error::HeaderSize(0));
691 bytes[10..12].copy_from_slice(&(HEADER_SIZE as u16).to_le_bytes());
692 assert!(Header::parse(&bytes).is_ok());
693 }
694
695 #[test]
696 fn alignment_and_rounding() {
697 assert_eq!(align8(0), 0);
698 assert_eq!(align8(1), 8);
699 assert_eq!(align8(8), 8);
700 assert_eq!(align8(9), 16);
701 }
702
703 #[test]
704 fn floats_canonicalize() {
705 assert_eq!(canonical_f64(-0.0).to_bits(), 0.0f64.to_bits());
706 assert_eq!(canonical_f64(f64::NAN).to_bits(), CANONICAL_NAN_BITS);
707 assert_eq!(canonical_f64(1.5), 1.5);
708 }
709
710 #[test]
711 fn dialect_hash_is_stable() {
712 assert_eq!(dialect_hash(), dialect_hash());
713 assert_ne!(dialect_hash(), 0);
714 }
715}