Skip to main content

kcode_server_object_envelopes/
provenance.rs

1use chrono::{DateTime, Utc};
2use kcode_kweb_db::ObjectId;
3
4use crate::codec::{Reader, push_string};
5use crate::{Error, Result};
6
7const PROVENANCE_MAGIC: &[u8; 8] = b"KPROV\0\x01\0";
8
9/// One object referenced by a stored provenance payload.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct StoredArtifact {
12    pub object_id: ObjectId,
13    pub original_filename: String,
14    pub media_type: String,
15    pub role: String,
16    pub byte_length: u64,
17    pub sha256: [u8; 32],
18}
19
20/// KennedyServer provenance stored as one opaque Kweb object payload.
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct StoredProvenance {
23    pub data: String,
24    pub source: String,
25    pub source_created_at: DateTime<Utc>,
26    pub artifacts: Vec<StoredArtifact>,
27}
28
29/// Encodes one provenance payload without storing it.
30pub fn encode_provenance(value: &StoredProvenance) -> Result<Vec<u8>> {
31    let artifact_count = u64::try_from(value.artifacts.len())
32        .map_err(|_| Error::new("too many provenance artifacts"))?;
33    let mut encoded_length = PROVENANCE_MAGIC
34        .len()
35        .checked_add(8 + 4)
36        .ok_or_else(|| Error::new("provenance encoded length overflow"))?;
37    encoded_length = checked_string_length(encoded_length, &value.source, "provenance source")?;
38    encoded_length = checked_string_length(encoded_length, &value.data, "provenance data")?;
39    encoded_length = encoded_length
40        .checked_add(8)
41        .ok_or_else(|| Error::new("provenance encoded length overflow"))?;
42
43    for artifact in &value.artifacts {
44        encoded_length = encoded_length
45            .checked_add(artifact.object_id.to_bytes().len())
46            .ok_or_else(|| Error::new("provenance encoded length overflow"))?;
47        encoded_length = checked_string_length(
48            encoded_length,
49            &artifact.original_filename,
50            "provenance artifact filename",
51        )?;
52        encoded_length = checked_string_length(
53            encoded_length,
54            &artifact.media_type,
55            "provenance artifact media type",
56        )?;
57        encoded_length =
58            checked_string_length(encoded_length, &artifact.role, "provenance artifact role")?;
59        encoded_length = encoded_length
60            .checked_add(8 + 32)
61            .ok_or_else(|| Error::new("provenance encoded length overflow"))?;
62    }
63
64    let mut output = Vec::new();
65    output
66        .try_reserve_exact(encoded_length)
67        .map_err(|_| Error::new("unable to allocate encoded provenance object"))?;
68    output.extend_from_slice(PROVENANCE_MAGIC);
69    output.extend_from_slice(&value.source_created_at.timestamp().to_be_bytes());
70    output.extend_from_slice(
71        &value
72            .source_created_at
73            .timestamp_subsec_nanos()
74            .to_be_bytes(),
75    );
76    push_string(&mut output, &value.source, "provenance source")?;
77    push_string(&mut output, &value.data, "provenance data")?;
78    output.extend_from_slice(&artifact_count.to_be_bytes());
79    for artifact in &value.artifacts {
80        output.extend_from_slice(&artifact.object_id.to_bytes());
81        push_string(
82            &mut output,
83            &artifact.original_filename,
84            "provenance artifact filename",
85        )?;
86        push_string(
87            &mut output,
88            &artifact.media_type,
89            "provenance artifact media type",
90        )?;
91        push_string(&mut output, &artifact.role, "provenance artifact role")?;
92        output.extend_from_slice(&artifact.byte_length.to_be_bytes());
93        output.extend_from_slice(&artifact.sha256);
94    }
95    Ok(output)
96}
97
98/// Decodes one provenance payload without reading its Kweb object.
99pub fn decode_provenance(bytes: &[u8]) -> Result<StoredProvenance> {
100    let mut input = Reader::new(bytes);
101    let magic = input.take(PROVENANCE_MAGIC.len(), "provenance marker")?;
102    if magic != PROVENANCE_MAGIC {
103        return Err(Error::new("provenance object has unknown format"));
104    }
105    let seconds = i64::from_be_bytes(input.array("provenance timestamp seconds")?);
106    let nanos = u32::from_be_bytes(input.array("provenance timestamp nanoseconds")?);
107    let source_created_at = DateTime::<Utc>::from_timestamp(seconds, nanos)
108        .ok_or_else(|| Error::new("provenance object has invalid timestamp"))?;
109    let source = input.string("provenance source")?;
110    let data = input.string("provenance data")?;
111    let artifact_count = usize::try_from(input.u64("provenance artifact count")?)
112        .map_err(|_| Error::new("provenance artifact count exceeds usize"))?;
113    if artifact_count > bytes.len() / 50 {
114        return Err(Error::new("provenance artifact count is impossible"));
115    }
116    let mut artifacts = Vec::new();
117    artifacts
118        .try_reserve_exact(artifact_count)
119        .map_err(|_| Error::new("unable to allocate provenance artifacts"))?;
120    for _ in 0..artifact_count {
121        let object_id = ObjectId::from_bytes(input.array("provenance artifact object ID")?)
122            .map_err(|error| Error::new(error.to_string()))?;
123        artifacts.push(StoredArtifact {
124            object_id,
125            original_filename: input.string("provenance artifact filename")?,
126            media_type: input.string("provenance artifact media type")?,
127            role: input.string("provenance artifact role")?,
128            byte_length: input.u64("provenance artifact byte length")?,
129            sha256: input.array("provenance artifact SHA-256")?,
130        });
131    }
132    input.finish("provenance object")?;
133    Ok(StoredProvenance {
134        data,
135        source,
136        source_created_at,
137        artifacts,
138    })
139}
140
141fn checked_string_length(current: usize, value: &str, label: &str) -> Result<usize> {
142    u32::try_from(value.len()).map_err(|_| Error::new(format!("{label} exceeds u32")))?;
143    current
144        .checked_add(4)
145        .and_then(|length| length.checked_add(value.len()))
146        .ok_or_else(|| Error::new(format!("{label} encoded length overflow")))
147}