1use alloc::string::String;
48use alloc::vec::Vec;
49
50use crate::clock::Timestamp;
51use crate::error::{Error, Result};
52use crate::hash::{Digest, HASH_LEN};
53use crate::owned::OwnedRecord;
54use crate::record::{Outcome, Record, RecordId};
55
56pub const FORMAT_MAGIC: &[u8; 8] = b"AUDTRAIL";
58
59pub const FORMAT_VERSION: u8 = 0x01;
61
62pub const FILE_HEADER_LEN: usize = 16;
64
65const RECORD_FIXED_LEN: usize = 8 + 8 + 1 + HASH_LEN + HASH_LEN;
68
69pub fn write_file_header(out: &mut Vec<u8>) {
73 out.extend_from_slice(FORMAT_MAGIC);
74 out.push(FORMAT_VERSION);
75 out.extend_from_slice(&[0u8; 7]);
76}
77
78pub fn verify_file_header(bytes: &[u8]) -> Result<()> {
85 if bytes.len() < FILE_HEADER_LEN {
86 return Err(Error::Truncated);
87 }
88 if &bytes[0..8] != FORMAT_MAGIC {
89 return Err(Error::InvalidFormat);
90 }
91 if bytes[8] != FORMAT_VERSION {
92 return Err(Error::InvalidFormat);
93 }
94 Ok(())
95}
96
97pub fn encode_record(record: &Record<'_>, out: &mut Vec<u8>) -> Result<()> {
110 let actor_bytes = record.actor().as_str().as_bytes();
111 let action_bytes = record.action().as_str().as_bytes();
112 let target_bytes = record.target().as_str().as_bytes();
113
114 if actor_bytes.len() > u32::MAX as usize
115 || action_bytes.len() > u32::MAX as usize
116 || target_bytes.len() > u32::MAX as usize
117 {
118 return Err(Error::InvalidFormat);
119 }
120
121 let body_len =
122 RECORD_FIXED_LEN + 4 + actor_bytes.len() + 4 + action_bytes.len() + 4 + target_bytes.len();
123 if body_len > u32::MAX as usize {
124 return Err(Error::InvalidFormat);
125 }
126
127 out.reserve(4 + body_len);
128
129 out.extend_from_slice(&(body_len as u32).to_be_bytes());
130 out.extend_from_slice(&record.id().as_u64().to_be_bytes());
131 out.extend_from_slice(&record.timestamp().as_nanos().to_be_bytes());
132 out.push(record.outcome().as_u8());
133 out.extend_from_slice(record.prev_hash().as_bytes());
134 out.extend_from_slice(record.hash().as_bytes());
135
136 out.extend_from_slice(&(actor_bytes.len() as u32).to_be_bytes());
137 out.extend_from_slice(actor_bytes);
138 out.extend_from_slice(&(action_bytes.len() as u32).to_be_bytes());
139 out.extend_from_slice(action_bytes);
140 out.extend_from_slice(&(target_bytes.len() as u32).to_be_bytes());
141 out.extend_from_slice(target_bytes);
142
143 Ok(())
144}
145
146pub fn decode_record(bytes: &[u8]) -> Result<(OwnedRecord, usize)> {
156 if bytes.len() < 4 {
157 return Err(Error::Truncated);
158 }
159 let body_len = read_u32(&bytes[0..4]) as usize;
160 let frame_end = 4usize.checked_add(body_len).ok_or(Error::InvalidFormat)?;
161 if bytes.len() < frame_end {
162 return Err(Error::Truncated);
163 }
164
165 let body = &bytes[4..frame_end];
166 if body.len() < RECORD_FIXED_LEN {
167 return Err(Error::InvalidFormat);
168 }
169
170 let id = RecordId::from_u64(read_u64(&body[0..8]));
171 let timestamp = Timestamp::from_nanos(read_u64(&body[8..16]));
172 let outcome = decode_outcome(body[16])?;
173 let mut prev_hash = [0u8; HASH_LEN];
174 prev_hash.copy_from_slice(&body[17..17 + HASH_LEN]);
175 let mut hash = [0u8; HASH_LEN];
176 hash.copy_from_slice(&body[17 + HASH_LEN..17 + HASH_LEN + HASH_LEN]);
177
178 let mut cursor = RECORD_FIXED_LEN;
179 let actor = read_string_field(body, &mut cursor)?;
180 let action = read_string_field(body, &mut cursor)?;
181 let target = read_string_field(body, &mut cursor)?;
182
183 if cursor != body.len() {
184 return Err(Error::InvalidFormat);
185 }
186
187 let record = OwnedRecord {
188 id,
189 timestamp,
190 actor,
191 action,
192 target,
193 outcome,
194 prev_hash: Digest::from_bytes(prev_hash),
195 hash: Digest::from_bytes(hash),
196 };
197 Ok((record, frame_end))
198}
199
200fn decode_outcome(byte: u8) -> Result<Outcome> {
201 match byte {
202 0 => Ok(Outcome::Success),
203 1 => Ok(Outcome::Failure),
204 2 => Ok(Outcome::Denied),
205 3 => Ok(Outcome::Error),
206 _ => Err(Error::InvalidFormat),
207 }
208}
209
210fn read_string_field(body: &[u8], cursor: &mut usize) -> Result<String> {
211 if body.len() < cursor.checked_add(4).ok_or(Error::InvalidFormat)? {
212 return Err(Error::InvalidFormat);
213 }
214 let len = read_u32(&body[*cursor..*cursor + 4]) as usize;
215 *cursor += 4;
216 let end = cursor.checked_add(len).ok_or(Error::InvalidFormat)?;
217 if body.len() < end {
218 return Err(Error::InvalidFormat);
219 }
220 let bytes = &body[*cursor..end];
221 let s = core::str::from_utf8(bytes).map_err(|_| Error::InvalidFormat)?;
222 *cursor = end;
223 Ok(String::from(s))
224}
225
226fn read_u32(bytes: &[u8]) -> u32 {
227 let mut buf = [0u8; 4];
228 buf.copy_from_slice(&bytes[0..4]);
229 u32::from_be_bytes(buf)
230}
231
232fn read_u64(bytes: &[u8]) -> u64 {
233 let mut buf = [0u8; 8];
234 buf.copy_from_slice(&bytes[0..8]);
235 u64::from_be_bytes(buf)
236}