kcode_server_object_envelopes/
provenance.rs1use 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#[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#[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
29pub 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
98pub 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}