agentic_reality/format/
header.rs1use crate::types::error::{RealityError, RealityResult};
4use crate::types::{AREAL_MAGIC, FORMAT_VERSION, HEADER_SIZE};
5use serde::{Deserialize, Serialize};
6
7pub mod flags {
9 pub const COMPRESSED: u64 = 1 << 0;
10 pub const SENSITIVE: u64 = 1 << 1;
11 pub const ENCRYPTED: u64 = 1 << 2;
12 pub const MIGRATED: u64 = 1 << 3;
13 pub const SNAPSHOT: u64 = 1 << 4;
14 pub const FULL_TOPOLOGY: u64 = 1 << 5;
15 pub const HAS_INCARNATION_MEMORY: u64 = 1 << 6;
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[repr(u8)]
21pub enum Compression {
22 None = 0,
23 Lz4 = 1,
24 Zstd = 2,
25}
26
27#[derive(Debug, Clone)]
29pub struct ArealHeader {
30 pub magic: [u8; 4],
31 pub version: u32,
32 pub flags: u64,
33 pub incarnation_id: [u8; 16],
34 pub created: i64,
35 pub modified: i64,
36 pub section_count: u32,
37 pub section_table_offset: u64,
38 pub compression: Compression,
39 pub checksum: [u8; 8],
40}
41
42impl ArealHeader {
43 pub fn new(incarnation_id: [u8; 16]) -> Self {
44 let now = chrono::Utc::now().timestamp();
45 Self {
46 magic: AREAL_MAGIC,
47 version: FORMAT_VERSION,
48 flags: 0,
49 incarnation_id,
50 created: now,
51 modified: now,
52 section_count: 0,
53 section_table_offset: HEADER_SIZE as u64,
54 compression: Compression::None,
55 checksum: [0u8; 8],
56 }
57 }
58
59 pub fn write_to(&self, buf: &mut Vec<u8>) -> RealityResult<()> {
60 buf.extend_from_slice(&self.magic);
61 buf.extend_from_slice(&self.version.to_le_bytes());
62 buf.extend_from_slice(&self.flags.to_le_bytes());
63 buf.extend_from_slice(&self.incarnation_id);
64 buf.extend_from_slice(&self.created.to_le_bytes());
65 buf.extend_from_slice(&self.modified.to_le_bytes());
66 buf.extend_from_slice(&self.section_count.to_le_bytes());
67 buf.extend_from_slice(&self.section_table_offset.to_le_bytes());
68 buf.push(self.compression as u8);
69 let current = buf.len();
71 let target = HEADER_SIZE - 8;
72 if current < target {
73 buf.resize(target, 0);
74 }
75 buf.extend_from_slice(&self.checksum);
76 Ok(())
77 }
78
79 pub fn read_from(data: &[u8]) -> RealityResult<Self> {
80 if data.len() < HEADER_SIZE {
81 return Err(RealityError::InvalidFormat("header too short".into()));
82 }
83 let magic: [u8; 4] = data[0..4]
84 .try_into()
85 .map_err(|_| RealityError::InvalidMagic)?;
86 if magic != AREAL_MAGIC {
87 return Err(RealityError::InvalidMagic);
88 }
89 let version = u32::from_le_bytes(
90 data[4..8]
91 .try_into()
92 .map_err(|_| RealityError::InvalidFormat("version".into()))?,
93 );
94 if version != FORMAT_VERSION {
95 return Err(RealityError::VersionMismatch {
96 expected: FORMAT_VERSION,
97 got: version,
98 });
99 }
100 let flags = u64::from_le_bytes(
101 data[8..16]
102 .try_into()
103 .map_err(|_| RealityError::InvalidFormat("flags".into()))?,
104 );
105 let incarnation_id: [u8; 16] = data[16..32]
106 .try_into()
107 .map_err(|_| RealityError::InvalidFormat("incarnation_id".into()))?;
108 let created = i64::from_le_bytes(
109 data[32..40]
110 .try_into()
111 .map_err(|_| RealityError::InvalidFormat("created".into()))?,
112 );
113 let modified = i64::from_le_bytes(
114 data[40..48]
115 .try_into()
116 .map_err(|_| RealityError::InvalidFormat("modified".into()))?,
117 );
118 let section_count = u32::from_le_bytes(
119 data[48..52]
120 .try_into()
121 .map_err(|_| RealityError::InvalidFormat("section_count".into()))?,
122 );
123 let section_table_offset = u64::from_le_bytes(
124 data[52..60]
125 .try_into()
126 .map_err(|_| RealityError::InvalidFormat("section_table_offset".into()))?,
127 );
128 let compression = match data[60] {
129 0 => Compression::None,
130 1 => Compression::Lz4,
131 2 => Compression::Zstd,
132 v => {
133 return Err(RealityError::InvalidFormat(format!(
134 "unknown compression: {}",
135 v
136 )))
137 }
138 };
139 let checksum: [u8; 8] = data[HEADER_SIZE - 8..HEADER_SIZE]
140 .try_into()
141 .map_err(|_| RealityError::InvalidFormat("checksum".into()))?;
142
143 Ok(Self {
144 magic,
145 version,
146 flags,
147 incarnation_id,
148 created,
149 modified,
150 section_count,
151 section_table_offset,
152 compression,
153 checksum,
154 })
155 }
156}