Skip to main content

cu29_logstream/
recovery.rs

1//! Semantic keyframe and recovery point records used to restart exact reconstruction.
2
3use crate::{DecodedRecord, Error, RecordKind, Result, decode_record, encode_record};
4use alloc::{string::ToString, vec::Vec};
5use bincode::{Decode, Encode};
6use cu29_runtime::curuntime::KeyFrame;
7
8/// A stream restart boundary binding a manifest and a task-state keyframe.
9///
10/// The keyframe restores component state immediately before `copperlist_id`.
11/// The manifest supplies the continuous-lane FEC and schema configuration.
12/// Receivers verify both references before resuming at this recovery point.
13#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
14pub struct RecoveryPoint {
15    pub manifest_object_id: u64,
16    pub manifest_record_digest: [u8; 32],
17    pub copperlist_id: u64,
18    pub keyframe_object_id: u64,
19    pub keyframe_record_digest: [u8; 32],
20}
21
22impl RecoveryPoint {
23    /// Checks that a recovered keyframe is exactly the record referenced here.
24    pub fn references_keyframe(&self, record: DecodedRecord<'_>) -> bool {
25        record.kind == RecordKind::KeyFrame
26            && record.object_id == self.keyframe_object_id
27            && record.digest == self.keyframe_record_digest
28    }
29
30    /// Checks that a recovered manifest is exactly the record referenced here.
31    pub fn references_manifest(&self, record: DecodedRecord<'_>) -> bool {
32        record.kind == RecordKind::Manifest
33            && record.object_id == self.manifest_object_id
34            && record.digest == self.manifest_record_digest
35    }
36}
37
38/// Encodes a runtime keyframe as its canonical bincode semantic payload.
39pub fn encode_keyframe(keyframe: &KeyFrame) -> Result<Vec<u8>> {
40    bincode::encode_to_vec(keyframe, bincode::config::standard())
41        .map_err(|error| Error::Codec(error.to_string()))
42}
43
44/// Decodes a complete keyframe payload and rejects trailing bytes.
45pub fn decode_keyframe(payload: &[u8]) -> Result<KeyFrame> {
46    decode_exact(payload, "keyframe")
47}
48
49/// Encodes a recovery point as its canonical bincode semantic payload.
50pub fn encode_recovery_point(recovery_point: &RecoveryPoint) -> Result<Vec<u8>> {
51    bincode::encode_to_vec(recovery_point, bincode::config::standard())
52        .map_err(|error| Error::Codec(error.to_string()))
53}
54
55/// Decodes a complete recovery point payload and rejects trailing bytes.
56pub fn decode_recovery_point(payload: &[u8]) -> Result<RecoveryPoint> {
57    decode_exact(payload, "recovery point")
58}
59
60/// Creates the separately transportable keyframe and recovery point records.
61///
62/// Both use the CopperList boundary as their family-local object identifier.
63pub fn encode_keyframe_and_recovery_point(
64    keyframe: &KeyFrame,
65    manifest_object_id: u64,
66    manifest_record_digest: [u8; 32],
67) -> Result<(Vec<u8>, Vec<u8>)> {
68    let keyframe_payload = encode_keyframe(keyframe)?;
69    let keyframe_record =
70        encode_record(RecordKind::KeyFrame, keyframe.culistid, &keyframe_payload)?;
71    let keyframe_decoded = decode_record(&keyframe_record)?;
72    let recovery_point = RecoveryPoint {
73        manifest_object_id,
74        manifest_record_digest,
75        copperlist_id: keyframe.culistid,
76        keyframe_object_id: keyframe.culistid,
77        keyframe_record_digest: keyframe_decoded.digest,
78    };
79    let recovery_point_payload = encode_recovery_point(&recovery_point)?;
80    let recovery_point_record = encode_record(
81        RecordKind::RecoveryPoint,
82        keyframe.culistid,
83        &recovery_point_payload,
84    )?;
85    Ok((keyframe_record, recovery_point_record))
86}
87
88fn decode_exact<T: Decode<()>>(payload: &[u8], name: &'static str) -> Result<T> {
89    let (value, consumed) = bincode::decode_from_slice(payload, bincode::config::standard())
90        .map_err(|error| Error::Codec(error.to_string()))?;
91    if consumed != payload.len() {
92        return Err(Error::Codec(alloc::format!("{name} has trailing bytes")));
93    }
94    Ok(value)
95}
96
97/// Reads keyframe metadata while borrowing its task-state bytes.
98pub(crate) fn keyframe_id(payload: &[u8]) -> Result<u64> {
99    #[derive(bincode::BorrowDecode)]
100    struct View<'a> {
101        culistid: u64,
102        _timestamp: cu29_clock::CuTime,
103        _tasks: &'a [u8],
104    }
105    let (view, used): (View<'_>, usize) =
106        bincode::borrow_decode_from_slice(payload, bincode::config::standard())
107            .map_err(|e| Error::Codec(e.to_string()))?;
108    if used != payload.len() {
109        return Err(Error::Codec("keyframe has trailing bytes".into()));
110    }
111    Ok(view.culistid)
112}