#![allow(missing_docs)]
use crate::error::{Error, Result};
pub const MAGIC: [u8; 4] = *b"AINF";
pub const STREAM_MAGIC: [u8; 4] = *b"AINS";
pub const FORMAT_MAJOR: u16 = 1;
pub const FORMAT_MINOR: u16 = 0;
pub const HEADER_SIZE: usize = 224;
pub const NODE_RECORD_SIZE: usize = 32;
pub const CHILD_SIZE: usize = 4;
pub const FIELD_SIZE: usize = 16;
pub const PART_SIZE: usize = 8;
pub const EDGE_SIZE: usize = 8;
pub const BLOB_ENTRY_SIZE: usize = 40;
pub const ANNOTATION_SIZE: usize = 16;
pub const UNIT_SIZE: usize = 8;
pub const TYPE_SIZE: usize = 16;
pub const AUX_SIZE: usize = 8;
pub const NUM_SIZE: usize = 8;
pub const EXT_DIR_SIZE: usize = 16;
pub const PROV_HEADER_SIZE: usize = 96;
pub const PROV_ENTRY_SIZE: usize = 16;
pub const SECRET_ENTRY_SIZE: usize = 8;
pub const AUX_CONSTRAINT_MIN: u32 = 0;
pub const AUX_CONSTRAINT_MAX: u32 = 1;
pub const AUX_CONSTRAINT_ENUM_ITEM: u32 = 2;
pub const AUX_CONSTRAINT_PATTERN: u32 = 3;
pub const AUX_CONSTRAINT_CUSTOM: u32 = 4;
pub const AUX_PATCH_PATH: u32 = 10;
pub const AUX_PATCH_VALUE: u32 = 11;
pub const AUX_PATCH_RENAME_TO: u32 = 12;
pub const AUX_SCHEMA_MIN: u32 = 20;
pub const AUX_SCHEMA_MAX: u32 = 21;
pub const AUX_SCHEMA_MIN_LEN: u32 = 22;
pub const AUX_SCHEMA_MAX_LEN: u32 = 23;
pub const AUX_SCHEMA_PATTERN: u32 = 24;
pub const AUX_SCHEMA_DEFAULT: u32 = 25;
pub const AUX_SCHEMA_DEPRECATED: u32 = 26;
pub const AUX_TYPE_ENUM_ITEM: u32 = 30;
pub const AUX_TYPE_TABLE: u32 = 31;
pub const CONSTRAINT_KIND_MIN_MAX: u32 = 0;
pub const CONSTRAINT_KIND_ENUM: u32 = 1;
pub const CONSTRAINT_KIND_PATTERN: u32 = 2;
pub const CONSTRAINT_KIND_ODD: u32 = 3;
pub const CONSTRAINT_KIND_CUSTOM: u32 = 4;
pub const PATCH_KIND_SET: u32 = 0;
pub const PATCH_KIND_REMOVE: u32 = 1;
pub const PATCH_KIND_RENAME: u32 = 2;
pub const ANN_KIND_INTENT: u32 = 0;
pub const ANN_KIND_CONSTRAINT: u32 = 1;
pub const ANN_KIND_AI_IMMUTABLE: u32 = 2;
pub const PART_LITERAL: u32 = 0;
pub const PART_EXPR: u32 = 1;
pub const FLAG_CANONICAL: u16 = 1 << 0;
pub const FLAG_ANNOTATIONS: u16 = 1 << 1;
pub const FLAG_PROVENANCE: u16 = 1 << 2;
pub const FLAG_EXTENSIONS: u16 = 1 << 3;
pub const FLAG_STREAMED: u16 = 1 << 4;
pub const FLAG_SECRETS: u16 = 1 << 5;
pub const FLAG_KNOWN_MASK: u16 = FLAG_CANONICAL
| FLAG_ANNOTATIONS
| FLAG_PROVENANCE
| FLAG_EXTENSIONS
| FLAG_STREAMED
| FLAG_SECRETS;
pub const NODE_FLAG_0: u8 = 1 << 0;
pub const NODE_FLAG_1: u8 = 1 << 1;
pub const NODE_FLAG_2: u8 = 1 << 2;
pub const NODE_FLAG_3: u8 = 1 << 3;
pub const NODE_FLAG_4: u8 = 1 << 4;
pub const NODE_FLAG_5: u8 = 1 << 5;
pub const NODE_FLAG_6: u8 = 1 << 6;
pub const NODE_FLAG_KNOWN: u8 =
NODE_FLAG_0 | NODE_FLAG_1 | NODE_FLAG_2 | NODE_FLAG_3 | NODE_FLAG_4 | NODE_FLAG_5 | NODE_FLAG_6;
pub const SECTION_COUNT: usize = 17;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Section {
Nodes,
Children,
Fields,
Parts,
Edges,
Strings,
Blobs,
Annotations,
Provenance,
Extensions,
Units,
Types,
Aux,
Numbers,
Secrets,
Hashes,
Bindings,
}
impl Section {
pub const ALL: [Section; SECTION_COUNT] = [
Section::Nodes,
Section::Children,
Section::Fields,
Section::Parts,
Section::Edges,
Section::Strings,
Section::Blobs,
Section::Annotations,
Section::Provenance,
Section::Extensions,
Section::Units,
Section::Types,
Section::Aux,
Section::Numbers,
Section::Secrets,
Section::Hashes,
Section::Bindings,
];
pub const fn name(self) -> &'static str {
match self {
Section::Nodes => "nodes",
Section::Children => "children",
Section::Fields => "fields",
Section::Parts => "parts",
Section::Edges => "edges",
Section::Strings => "strings",
Section::Blobs => "blobs",
Section::Annotations => "annotations",
Section::Provenance => "provenance",
Section::Extensions => "extensions",
Section::Units => "units",
Section::Types => "types",
Section::Aux => "aux",
Section::Numbers => "numbers",
Section::Secrets => "secrets",
Section::Hashes => "hashes",
Section::Bindings => "bindings",
}
}
}
pub const fn align8(value: u64) -> u64 {
(value + 7) & !7
}
pub fn canonical_f64(value: f64) -> f64 {
if value.is_nan() {
f64::from_bits(CANONICAL_NAN_BITS)
} else if value == 0.0 {
0.0
} else {
value
}
}
pub const CANONICAL_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub major: u16,
pub minor: u16,
pub flags: u16,
pub header_size: u16,
pub content_hash: [u8; 32],
pub dialect_hash: u64,
pub total_size: u64,
pub sections: [SectionSpan; SECTION_COUNT],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SectionSpan {
pub offset: u32,
pub count: u32,
}
impl SectionSpan {
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn bytes(&self, stride: usize) -> u64 {
self.count as u64 * stride as u64
}
}
impl Header {
pub fn span(&self, section: Section) -> SectionSpan {
self.sections[section as usize]
}
pub fn span_mut(&mut self, section: Section) -> &mut SectionSpan {
&mut self.sections[section as usize]
}
pub fn parse(buf: &[u8]) -> Result<Header> {
if buf.len() < 4 || buf[0..4] != MAGIC {
return Err(Error::Magic);
}
if buf.len() < HEADER_SIZE {
return Err(Error::HeaderTruncated);
}
let major = u16::from_le_bytes([buf[4], buf[5]]);
let minor = u16::from_le_bytes([buf[6], buf[7]]);
let flags = u16::from_le_bytes([buf[8], buf[9]]);
let header_size = u16::from_le_bytes([buf[10], buf[11]]);
if major != FORMAT_MAJOR {
return Err(Error::VersionMajor(major));
}
if minor > FORMAT_MINOR {
return Err(Error::VersionMinor(minor));
}
if header_size as usize != HEADER_SIZE {
return Err(Error::HeaderSize(header_size as u8));
}
if flags & !FLAG_KNOWN_MASK != 0 {
return Err(Error::UnknownFlags(flags & !FLAG_KNOWN_MASK));
}
if read_u32(buf, 12)? != 0 {
return Err(Error::ReservedNonZero {
section: "header",
offset: 12,
});
}
let mut content_hash = [0u8; 32];
content_hash.copy_from_slice(&buf[16..48]);
let dialect_hash = read_u64(buf, 48)?;
let total_size = read_u64(buf, 56)?;
let mut sections = [SectionSpan::default(); SECTION_COUNT];
for (index, span) in sections.iter_mut().enumerate() {
let base = 64 + index * 8;
span.offset = read_u32(buf, base)?;
span.count = read_u32(buf, base + 4)?;
}
let reserved_start = 64 + SECTION_COUNT * 8;
if let Some(position) = (reserved_start..HEADER_SIZE).find(|offset| buf[*offset] != 0) {
return Err(Error::ReservedNonZero {
section: "header",
offset: position as u64,
});
}
Ok(Header {
major,
minor,
flags,
header_size,
content_hash,
dialect_hash,
total_size,
sections,
})
}
pub fn write(&self, out: &mut [u8]) {
debug_assert!(out.len() >= HEADER_SIZE);
out[0..4].copy_from_slice(&MAGIC);
out[4..6].copy_from_slice(&self.major.to_le_bytes());
out[6..8].copy_from_slice(&self.minor.to_le_bytes());
out[8..10].copy_from_slice(&self.flags.to_le_bytes());
out[10..12].copy_from_slice(&self.header_size.to_le_bytes());
out[12..16].copy_from_slice(&0u32.to_le_bytes());
out[16..48].copy_from_slice(&self.content_hash);
out[48..56].copy_from_slice(&self.dialect_hash.to_le_bytes());
out[56..64].copy_from_slice(&self.total_size.to_le_bytes());
for (index, span) in self.sections.iter().enumerate() {
let base = 64 + index * 8;
out[base..base + 4].copy_from_slice(&span.offset.to_le_bytes());
out[base + 4..base + 8].copy_from_slice(&span.count.to_le_bytes());
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limits {
pub max_nodes: u32,
pub max_edges: u32,
pub max_children: u32,
pub max_fields: u32,
pub max_parts: u32,
pub max_strings: u32,
pub max_string_bytes: u32,
pub max_blobs: u32,
pub max_blob_bytes: u32,
pub max_annotations: u32,
pub max_units: u32,
pub max_types: u32,
pub max_aux: u32,
pub max_numbers: u32,
pub max_secrets: u32,
pub max_secret_bytes: u32,
pub max_extensions: u32,
pub max_prov_entries: u32,
pub max_total_bytes: u64,
pub max_depth: u32,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_nodes: 1 << 22,
max_edges: 1 << 24,
max_children: 1 << 24,
max_fields: 1 << 24,
max_parts: 1 << 22,
max_strings: 1 << 22,
max_string_bytes: 64 << 20,
max_blobs: 1 << 20,
max_blob_bytes: 512 << 20,
max_annotations: 1 << 22,
max_units: 1 << 16,
max_types: 1 << 20,
max_aux: 1 << 24,
max_numbers: 1 << 24,
max_secrets: 1 << 20,
max_secret_bytes: 16 << 20,
max_extensions: 1 << 16,
max_prov_entries: 1 << 16,
max_total_bytes: 1 << 30,
max_depth: 4096,
}
}
}
impl Limits {
pub fn strict() -> Self {
Self {
max_nodes: 4096,
max_edges: 16384,
max_children: 16384,
max_fields: 16384,
max_parts: 4096,
max_strings: 4096,
max_string_bytes: 1 << 20,
max_blobs: 256,
max_blob_bytes: 1 << 20,
max_annotations: 4096,
max_units: 1024,
max_types: 1024,
max_aux: 16384,
max_numbers: 16384,
max_secrets: 256,
max_secret_bytes: 1 << 16,
max_extensions: 64,
max_prov_entries: 256,
max_total_bytes: 8 << 20,
max_depth: 128,
}
}
pub fn check(&self) -> Result<()> {
for (name, value) in [
("max_nodes", self.max_nodes),
("max_string_bytes", self.max_string_bytes),
("max_blob_bytes", self.max_blob_bytes),
("max_units", self.max_units),
("max_depth", self.max_depth),
] {
if value == 0 {
return Err(Error::InvalidArgument(match name {
"max_nodes" => "max_nodes must be non-zero",
"max_string_bytes" => "max_string_bytes must be non-zero",
"max_blob_bytes" => "max_blob_bytes must be non-zero",
"max_units" => "max_units must be non-zero",
_ => "max_depth must be non-zero",
}));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeOpts {
pub limits: Limits,
pub verify_hash: bool,
pub verify_canonical: bool,
}
impl Default for DecodeOpts {
fn default() -> Self {
Self {
limits: Limits::default(),
verify_hash: true,
verify_canonical: false,
}
}
}
impl DecodeOpts {
pub fn paranoid() -> Self {
Self {
limits: Limits::strict(),
verify_hash: true,
verify_canonical: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncodeOpts {
pub write_hash: bool,
pub mark_canonical: bool,
pub streamed: bool,
}
impl Default for EncodeOpts {
fn default() -> Self {
Self {
write_hash: true,
mark_canonical: true,
streamed: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Meta {
pub flags: u16,
pub major: u16,
pub minor: u16,
pub dialect_hash: u64,
pub content_hash: [u8; 32],
pub total_size: u64,
pub nodes: u32,
pub edges: u32,
pub strings: u32,
pub string_bytes: u32,
pub blobs: u32,
pub blob_bytes: u32,
pub annotations: u32,
pub units: u32,
pub types: u32,
pub secrets: u32,
pub extensions: u32,
}
impl Meta {
pub fn has_annotations(&self) -> bool {
self.annotations > 0
}
pub fn has_provenance(&self) -> bool {
self.flags & FLAG_PROVENANCE != 0
}
}
pub fn read_u32(buf: &[u8], offset: usize) -> Result<u32> {
let end = offset + 4;
if end > buf.len() {
return Err(Error::Truncated {
needed: end as u64,
found: buf.len() as u64,
});
}
Ok(u32::from_le_bytes([
buf[offset],
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
]))
}
pub fn read_u64(buf: &[u8], offset: usize) -> Result<u64> {
let end = offset + 8;
if end > buf.len() {
return Err(Error::Truncated {
needed: end as u64,
found: buf.len() as u64,
});
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&buf[offset..end]);
Ok(u64::from_le_bytes(bytes))
}
pub fn put_u16(out: &mut Vec<u8>, value: u16) {
out.extend_from_slice(&value.to_le_bytes());
}
pub fn put_u32(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_le_bytes());
}
pub fn put_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_le_bytes());
}
pub fn pad8(out: &mut Vec<u8>) {
while out.len() % 8 != 0 {
out.push(0);
}
}
pub fn dialect_hash() -> u64 {
let mut hasher = blake3::Hasher::new();
hasher.update(b"active-infinity/dialect");
hasher.update(&FORMAT_MAJOR.to_le_bytes());
hasher.update(&FORMAT_MINOR.to_le_bytes());
hasher.update(&crate::unit::UNIT_MAX_BUILTIN.to_le_bytes());
hasher.update(b"node-kinds:0..=24");
hasher.update(b"aux-tags:0,1,2,3,4,10,11,12,20,21,22,23,24,25,26,30,31");
let digest = hasher.finalize();
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&digest.as_bytes()[..8]);
u64::from_le_bytes(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_round_trips() {
let mut header = Header {
major: FORMAT_MAJOR,
minor: FORMAT_MINOR,
flags: FLAG_CANONICAL,
header_size: HEADER_SIZE as u16,
content_hash: [7u8; 32],
dialect_hash: dialect_hash(),
total_size: 512,
sections: [SectionSpan::default(); SECTION_COUNT],
};
header.span_mut(Section::Nodes).offset = 192;
header.span_mut(Section::Nodes).count = 3;
let mut bytes = vec![0u8; HEADER_SIZE];
header.write(&mut bytes);
let parsed = Header::parse(&bytes).expect("parse");
assert_eq!(parsed, header);
}
#[test]
fn rejects_short_and_misversioned_input() {
assert_eq!(Header::parse(b"AI"), Err(Error::Magic));
assert_eq!(Header::parse(b"XXXX").unwrap_err(), Error::Magic);
let mut bytes = vec![0u8; HEADER_SIZE];
bytes[0..4].copy_from_slice(&MAGIC);
assert!(matches!(Header::parse(&bytes), Err(Error::VersionMajor(0))));
bytes[4..6].copy_from_slice(&FORMAT_MAJOR.to_le_bytes());
assert_eq!(Header::parse(&bytes).unwrap_err(), Error::HeaderSize(0));
bytes[10..12].copy_from_slice(&(HEADER_SIZE as u16).to_le_bytes());
assert!(Header::parse(&bytes).is_ok());
}
#[test]
fn alignment_and_rounding() {
assert_eq!(align8(0), 0);
assert_eq!(align8(1), 8);
assert_eq!(align8(8), 8);
assert_eq!(align8(9), 16);
}
#[test]
fn floats_canonicalize() {
assert_eq!(canonical_f64(-0.0).to_bits(), 0.0f64.to_bits());
assert_eq!(canonical_f64(f64::NAN).to_bits(), CANONICAL_NAN_BITS);
assert_eq!(canonical_f64(1.5), 1.5);
}
#[test]
fn dialect_hash_is_stable() {
assert_eq!(dialect_hash(), dialect_hash());
assert_ne!(dialect_hash(), 0);
}
}