Skip to main content

aven_protocol/
record.rs

1//! Exact encrypted-record framing and descriptor-bound headers.
2
3use crate::{codec, context::LocalSharedStatePackageContext};
4use anyhow::{Context, Result, ensure};
5
6pub const CHUNK_HEADER_BYTES: usize = 198;
7pub const CHUNK_RECORD_OVERHEAD: usize = 222;
8
9#[allow(clippy::too_many_arguments)]
10pub fn chunk_header(
11    context: LocalSharedStatePackageContext,
12    stream_id: [u8; 32],
13    artifact_id: [u8; 32],
14    family: u8,
15    class: u8,
16    index: u32,
17    count: u32,
18    total: u64,
19    nonce: [u8; 24],
20) -> Result<Vec<u8>> {
21    let mut header = Vec::with_capacity(CHUNK_HEADER_BYTES);
22    header.extend_from_slice(b"AVEN");
23    header.push(2);
24    header.extend_from_slice(&1_u16.to_be_bytes());
25    header.push(1);
26    codec::bytes(&mut header, &context.vault_id);
27    codec::bytes(&mut header, &stream_id);
28    codec::bytes(&mut header, &context.generation_id);
29    codec::bytes(&mut header, &artifact_id);
30    header.push(family);
31    header.push(class);
32    header.extend_from_slice(&index.to_be_bytes());
33    header.extend_from_slice(&count.to_be_bytes());
34    header.extend_from_slice(&total.to_be_bytes());
35    codec::bytes(&mut header, &nonce);
36    ensure!(header.len() == CHUNK_HEADER_BYTES, "invalid chunk header");
37    Ok(header)
38}
39
40#[allow(clippy::too_many_arguments)]
41pub fn validate_chunk_header(
42    header: &[u8],
43    context: LocalSharedStatePackageContext,
44    stream_id: [u8; 32],
45    artifact_id: [u8; 32],
46    family: u8,
47    class: u8,
48    index: u32,
49    count: u32,
50    total: u64,
51) -> Result<[u8; 24]> {
52    ensure!(
53        header.len() == CHUNK_HEADER_BYTES,
54        "error encrypted-local-shared-package-header-size"
55    );
56    let nonce_offset = CHUNK_HEADER_BYTES - 28;
57    ensure!(
58        u32::from_be_bytes(header[nonce_offset..nonce_offset + 4].try_into()?) == 24,
59        "error encrypted-local-shared-package-nonce-size"
60    );
61    let nonce: [u8; 24] = header[nonce_offset + 4..].try_into()?;
62    let expected = chunk_header(
63        context,
64        stream_id,
65        artifact_id,
66        family,
67        class,
68        index,
69        count,
70        total,
71        nonce,
72    )?;
73    ensure!(
74        header == expected,
75        "error encrypted-local-shared-package-header-mismatch"
76    );
77    Ok(nonce)
78}
79
80pub fn split_record(record: &[u8]) -> Result<(&[u8], &[u8])> {
81    ensure!(
82        record.len() >= CHUNK_RECORD_OVERHEAD,
83        "error encrypted-local-shared-package-record-truncated"
84    );
85    let header_len = usize::try_from(u32::from_be_bytes(record[0..4].try_into()?))?;
86    ensure!(
87        header_len == CHUNK_HEADER_BYTES,
88        "error encrypted-local-shared-package-header-size"
89    );
90    let body_len_offset = 4_usize
91        .checked_add(header_len)
92        .context("record offset overflow")?;
93    let body_start = body_len_offset
94        .checked_add(4)
95        .context("record offset overflow")?;
96    ensure!(
97        record.len() >= body_start,
98        "error encrypted-local-shared-package-record-truncated"
99    );
100    let body_len = usize::try_from(u32::from_be_bytes(
101        record[body_len_offset..body_start].try_into()?,
102    ))?;
103    ensure!(
104        body_len >= 16 && body_start.checked_add(body_len) == Some(record.len()),
105        "error encrypted-local-shared-package-record-length"
106    );
107    Ok((&record[4..body_len_offset], &record[body_start..]))
108}