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,
489}
490
491impl Default for DecodeOpts {
492 fn default() -> Self {
493 Self {
494 limits: Limits::default(),
495 verify_hash: true,
496 verify_canonical: false,
497 }
498 }
499}
500
501impl DecodeOpts {
502 pub fn paranoid() -> Self {
504 Self {
505 limits: Limits::strict(),
506 verify_hash: true,
507 verify_canonical: true,
508 }
509 }
510}
511
512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
514pub struct EncodeOpts {
515 pub write_hash: bool,
517 pub mark_canonical: bool,
519 pub streamed: bool,
521}
522
523impl Default for EncodeOpts {
524 fn default() -> Self {
525 Self {
526 write_hash: true,
527 mark_canonical: true,
528 streamed: false,
529 }
530 }
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
535pub struct Meta {
536 pub flags: u16,
537 pub major: u16,
538 pub minor: u16,
539 pub dialect_hash: u64,
540 pub content_hash: [u8; 32],
541 pub total_size: u64,
542 pub nodes: u32,
543 pub edges: u32,
544 pub strings: u32,
545 pub string_bytes: u32,
546 pub blobs: u32,
547 pub blob_bytes: u32,
548 pub annotations: u32,
549 pub units: u32,
550 pub types: u32,
551 pub secrets: u32,
552 pub extensions: u32,
553}
554
555impl Meta {
556 pub fn has_annotations(&self) -> bool {
557 self.annotations > 0
558 }
559
560 pub fn has_provenance(&self) -> bool {
561 self.flags & FLAG_PROVENANCE != 0
562 }
563}
564
565pub fn read_u32(buf: &[u8], offset: usize) -> Result<u32> {
567 let end = offset + 4;
568 if end > buf.len() {
569 return Err(Error::Truncated {
570 needed: end as u64,
571 found: buf.len() as u64,
572 });
573 }
574 Ok(u32::from_le_bytes([
575 buf[offset],
576 buf[offset + 1],
577 buf[offset + 2],
578 buf[offset + 3],
579 ]))
580}
581
582pub fn read_u64(buf: &[u8], offset: usize) -> Result<u64> {
584 let end = offset + 8;
585 if end > buf.len() {
586 return Err(Error::Truncated {
587 needed: end as u64,
588 found: buf.len() as u64,
589 });
590 }
591 let mut bytes = [0u8; 8];
592 bytes.copy_from_slice(&buf[offset..end]);
593 Ok(u64::from_le_bytes(bytes))
594}
595
596pub fn put_u16(out: &mut Vec<u8>, value: u16) {
598 out.extend_from_slice(&value.to_le_bytes());
599}
600
601pub fn put_u32(out: &mut Vec<u8>, value: u32) {
602 out.extend_from_slice(&value.to_le_bytes());
603}
604
605pub fn put_u64(out: &mut Vec<u8>, value: u64) {
606 out.extend_from_slice(&value.to_le_bytes());
607}
608
609pub fn pad8(out: &mut Vec<u8>) {
611 while out.len() % 8 != 0 {
612 out.push(0);
613 }
614}
615
616pub fn dialect_hash() -> u64 {
621 let mut hasher = blake3::Hasher::new();
622 hasher.update(b"active-infinity/dialect");
623 hasher.update(&FORMAT_MAJOR.to_le_bytes());
624 hasher.update(&FORMAT_MINOR.to_le_bytes());
625 hasher.update(&crate::unit::UNIT_MAX_BUILTIN.to_le_bytes());
626 hasher.update(b"node-kinds:0..=24");
627 hasher.update(b"aux-tags:0,1,2,3,4,10,11,12,20,21,22,23,24,25,26,30,31");
628 let digest = hasher.finalize();
629 let mut bytes = [0u8; 8];
630 bytes.copy_from_slice(&digest.as_bytes()[..8]);
631 u64::from_le_bytes(bytes)
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 #[test]
639 fn header_round_trips() {
640 let mut header = Header {
641 major: FORMAT_MAJOR,
642 minor: FORMAT_MINOR,
643 flags: FLAG_CANONICAL,
644 header_size: HEADER_SIZE as u16,
645 content_hash: [7u8; 32],
646 dialect_hash: dialect_hash(),
647 total_size: 512,
648 sections: [SectionSpan::default(); SECTION_COUNT],
649 };
650 header.span_mut(Section::Nodes).offset = 192;
651 header.span_mut(Section::Nodes).count = 3;
652 let mut bytes = vec![0u8; HEADER_SIZE];
653 header.write(&mut bytes);
654 let parsed = Header::parse(&bytes).expect("parse");
655 assert_eq!(parsed, header);
656 }
657
658 #[test]
659 fn rejects_short_and_misversioned_input() {
660 assert_eq!(Header::parse(b"AI"), Err(Error::Magic));
661 assert_eq!(Header::parse(b"XXXX").unwrap_err(), Error::Magic);
662 let mut bytes = vec![0u8; HEADER_SIZE];
663 bytes[0..4].copy_from_slice(&MAGIC);
664 assert!(matches!(Header::parse(&bytes), Err(Error::VersionMajor(0))));
666 bytes[4..6].copy_from_slice(&FORMAT_MAJOR.to_le_bytes());
667 assert_eq!(Header::parse(&bytes).unwrap_err(), Error::HeaderSize(0));
669 bytes[10..12].copy_from_slice(&(HEADER_SIZE as u16).to_le_bytes());
670 assert!(Header::parse(&bytes).is_ok());
671 }
672
673 #[test]
674 fn alignment_and_rounding() {
675 assert_eq!(align8(0), 0);
676 assert_eq!(align8(1), 8);
677 assert_eq!(align8(8), 8);
678 assert_eq!(align8(9), 16);
679 }
680
681 #[test]
682 fn floats_canonicalize() {
683 assert_eq!(canonical_f64(-0.0).to_bits(), 0.0f64.to_bits());
684 assert_eq!(canonical_f64(f64::NAN).to_bits(), CANONICAL_NAN_BITS);
685 assert_eq!(canonical_f64(1.5), 1.5);
686 }
687
688 #[test]
689 fn dialect_hash_is_stable() {
690 assert_eq!(dialect_hash(), dialect_hash());
691 assert_ne!(dialect_hash(), 0);
692 }
693}