Skip to main content

nodedb_types/backup_envelope/
write.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `EnvelopeWriter` — builds a plaintext backup envelope byte-by-byte.
4
5use super::types::{
6    DEFAULT_MAX_SECTION_BYTES, DEFAULT_MAX_TOTAL_BYTES, HEADER_LEN, MAGIC, SECTION_OVERHEAD,
7    TRAILER_LEN, VERSION,
8};
9use super::types::{EnvelopeError, EnvelopeMeta, Section};
10
11/// Build an envelope by pushing sections one at a time, then `finalize()`.
12pub struct EnvelopeWriter {
13    pub(super) meta: EnvelopeMeta,
14    pub(super) sections: Vec<Section>,
15    pub(super) max_total: u64,
16    pub(super) max_section: u64,
17    pub(super) framed_size: u64,
18}
19
20impl EnvelopeWriter {
21    pub fn new(meta: EnvelopeMeta) -> Self {
22        Self::with_caps(meta, DEFAULT_MAX_TOTAL_BYTES, DEFAULT_MAX_SECTION_BYTES)
23    }
24
25    pub fn with_caps(meta: EnvelopeMeta, max_total: u64, max_section: u64) -> Self {
26        Self {
27            meta,
28            sections: Vec::new(),
29            max_total,
30            max_section,
31            framed_size: HEADER_LEN as u64 + TRAILER_LEN as u64,
32        }
33    }
34
35    pub fn push_section(
36        &mut self,
37        origin_node_id: u64,
38        body: Vec<u8>,
39    ) -> Result<(), EnvelopeError> {
40        if body.len() as u64 > self.max_section {
41            return Err(EnvelopeError::OverSizeSection {
42                cap: self.max_section,
43            });
44        }
45        let added = SECTION_OVERHEAD as u64 + body.len() as u64;
46        if self.framed_size + added > self.max_total {
47            return Err(EnvelopeError::OverSizeTotal {
48                cap: self.max_total,
49            });
50        }
51        if self.sections.len() >= u16::MAX as usize {
52            return Err(EnvelopeError::TooManySections(u16::MAX));
53        }
54        self.framed_size += added;
55        self.sections.push(Section {
56            origin_node_id,
57            body,
58        });
59        Ok(())
60    }
61
62    /// Finalize without encryption. Produces a version-1 envelope.
63    pub fn finalize(self) -> Vec<u8> {
64        let mut out = Vec::with_capacity(self.framed_size as usize);
65        write_header(&mut out, &self.meta, self.sections.len() as u16, VERSION);
66        for section in &self.sections {
67            write_section(&mut out, section);
68        }
69        // Trailer crc covers header bytes + every section's framed bytes.
70        let trailer_crc = crc32c::crc32c(&out);
71        out.extend_from_slice(&trailer_crc.to_le_bytes());
72        out
73    }
74}
75
76pub(super) fn write_header(
77    out: &mut Vec<u8>,
78    meta: &EnvelopeMeta,
79    section_count: u16,
80    version: u8,
81) {
82    let start = out.len();
83    out.extend_from_slice(MAGIC); // [0..4]
84    out.push(version); // [4]
85    out.extend_from_slice(&[0u8; 3]); // [5..8]  _reserved
86    out.extend_from_slice(&meta.tenant_id.to_le_bytes()); // [8..16] tenant_id (u64)
87    out.extend_from_slice(&meta.source_vshard_count.to_le_bytes()); // [16..18]
88    out.extend_from_slice(&[0u8; 6]); // [18..24] _reserved
89    out.extend_from_slice(&meta.hash_seed.to_le_bytes()); // [24..32]
90    out.extend_from_slice(&meta.snapshot_watermark.to_le_bytes()); // [32..40]
91    out.extend_from_slice(&section_count.to_le_bytes()); // [40..42]
92    out.extend_from_slice(&[0u8; 6]); // [42..48] _reserved
93    let header_crc = crc32c::crc32c(&out[start..]);
94    out.extend_from_slice(&header_crc.to_le_bytes()); // [48..52]
95}
96
97pub(super) fn write_section(out: &mut Vec<u8>, section: &Section) {
98    out.extend_from_slice(&section.origin_node_id.to_le_bytes());
99    out.extend_from_slice(&(section.body.len() as u32).to_le_bytes());
100    out.extend_from_slice(&section.body);
101    let body_crc = crc32c::crc32c(&section.body);
102    out.extend_from_slice(&body_crc.to_le_bytes());
103}