harmos_domain/model/storage/files/
codec.rs1use std::borrow::Cow;
18use std::num::NonZeroUsize;
19use std::path::PathBuf;
20use super::{FileMetadata, FileReference};
24
25use crate::model::storage::StorageCursor;
26use crate::model::storage::position::codec::{decode_bounded, encode_bounded};
27use crate::schema::declaration::{Field, Kind};
28use crate::schema::{Cardinality, Described};
29use crate::{Error, MessageError, Result};
30
31crate::model::storage::codec::product!(super::FileSession {
36 history: crate::model::storage::codec::Nested<crate::model::storage::StoreHistory> => 1,
37 id: crate::model::storage::codec::Id => 2,
38 generation: crate::model::storage::codec::Unsigned => 3,
39});
40
41crate::model::storage::codec::product!(super::StagedFile {
42 history: crate::model::storage::codec::Nested<crate::model::storage::StoreHistory> => 1,
43 id: crate::model::storage::codec::Id => 2,
44});
45crate::model::storage::codec::product!(super::FileProgress {
46 length: crate::model::storage::codec::Unsigned => 1,
47 phase: Phase => 2,
48});
49
50struct Phase;
56
57pub struct LocalPath;
59pub struct Digest;
60
61impl crate::model::storage::codec::Value for Digest {
68 const NAME: &'static str = concat!(module_path!(), "::Digest");
69 type Native = [u8; 32];
70 const KIND: Kind = Kind::Bytes;
71 fn postcard_write(value: &[u8; 32], bytes: &mut Vec<u8>) {
72 bytes.push(32);
73 bytes.extend_from_slice(value);
74 }
75 fn postcard_size(_: &[u8; 32]) -> usize {
76 33
77 }
78 fn postcard_memory(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<usize, MessageError> {
79 Self::postcard_read(input)?;
80 Ok(0)
81 }
82 fn postcard_read(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<[u8; 32], MessageError> {
83 if input.value::<usize>()? != 32 {
84 return Err(MessageError::borrowed("SHA-256 requires exactly 32 bytes"));
85 }
86 input.take(32)?.try_into().map_err(|_| MessageError::borrowed("SHA-256 requires exactly 32 bytes"))
87 }
88}
89
90impl crate::model::storage::codec::Value for LocalPath {
91 const NAME: &'static str = concat!(module_path!(), "::LocalPath");
92 type Native = PathBuf;
93 const KIND: Kind = Kind::Bytes;
94
95 fn postcard_write(value: &PathBuf, bytes: &mut Vec<u8>) {
96 let path = value.as_os_str().as_encoded_bytes();
97 crate::__private::PostcardValue::write(&(path.len() as u64), bytes);
98 bytes.extend_from_slice(path);
99 }
100
101 fn postcard_size(value: &PathBuf) -> usize {
102 let length = value.as_os_str().as_encoded_bytes().len();
103 crate::__private::PostcardValue::size(&(length as u64)).saturating_add(length)
104 }
105
106 fn postcard_memory(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<usize, MessageError> {
107 <crate::model::storage::codec::Bytes as crate::model::storage::codec::Value>::postcard_memory(input)
108 }
109
110 fn postcard_read(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<PathBuf, MessageError> {
111 let bytes = <crate::model::storage::codec::Bytes as crate::model::storage::codec::Value>::postcard_read(input)?;
112 let path = String::from_utf8(bytes).map_err(|_| MessageError::borrowed("transported local paths must be UTF-8"))?;
113 Ok(PathBuf::from(path))
114 }
115}
116
117impl crate::model::storage::codec::Value for Phase {
118 const NAME: &'static str = concat!(module_path!(), "::", stringify!(Phase));
119 fn postcard_write(value: &Self::Native, bytes: &mut Vec<u8>) {
120 crate::__private::PostcardValue::write(
121 &match value {
122 super::FilePhase::Producing => 0u64,
123 super::FilePhase::Finished => 1u64,
124 super::FilePhase::Abandoned => 2u64,
125 },
126 bytes,
127 );
128 }
129
130 fn postcard_size(_: &Self::Native) -> usize {
131 1
132 }
133
134 fn postcard_memory(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<usize, MessageError> {
135 Self::postcard_read(input)?;
136 Ok(0)
137 }
138
139 fn postcard_read(input: &mut crate::__private::PostcardReader<'_>) -> std::result::Result<Self::Native, MessageError> {
140 match input.value::<u64>()? {
141 0 => Ok(super::FilePhase::Producing),
142 1 => Ok(super::FilePhase::Finished),
143 2 => Ok(super::FilePhase::Abandoned),
144 _ => Err(MessageError::borrowed("invalid storage selector")),
145 }
146 }
147
148 type Native = super::FilePhase;
149 const KIND: Kind = Kind::U64;
150}
151
152impl FileMetadata {
153 pub fn encode_bounded(&self, max_bytes: NonZeroUsize) -> Result<Vec<u8>> {
154 encode_bounded(self, max_bytes)
155 }
156
157 pub fn decode_bounded(bytes: &[u8], max_bytes: NonZeroUsize) -> Result<Self> {
158 decode_bounded(bytes, max_bytes)
159 }
160}
161
162impl FileReference {
163 pub fn encode_bounded(&self, max_bytes: NonZeroUsize) -> Result<Vec<u8>> {
164 self.check_native().map_err(|_| Error::InvalidInput {
165 field: "storage file codec",
166 reason: "invalid file reference",
167 })?;
168 encode_bounded(self, max_bytes)
169 }
170
171 pub fn decode_bounded(bytes: &[u8], max_bytes: NonZeroUsize) -> Result<Self> {
172 decode_bounded(bytes, max_bytes)
173 }
174
175 pub fn check_native(&self) -> std::result::Result<(), MessageError> {
176 if self.position.sequence == 0
177 || self.metadata.expected_length.is_some_and(|length| length != self.length)
178 || self.metadata.expected_sha256.is_some_and(|hash| hash != self.sha256)
179 {
180 return Err(MessageError::borrowed("file reference conflicts with its position or integrity requirements"));
181 }
182 Ok(())
183 }
184}
185
186crate::postcard_product!(FileMetadata {
187 id: crate::Identifier,
188 scope: Option<crate::Identifier>,
189 media_type: String,
190 expected_length: Option<u64>,
191 expected_sha256: Option<[u8; 32]>,
192});
193
194crate::postcard_product!(
195 FileReference {
196 position: StorageCursor,
197 metadata: FileMetadata,
198 length: u64,
199 sha256: [u8; 32]
200 },
201 validate = FileReference::check_native
202);
203
204impl Described for FileMetadata {
205 const NAME: &'static str = concat!(module_path!(), "::", stringify!(FileMetadata));
206 const FIELDS: &'static [Field] = &[
207 Field {
208 name: Cow::Borrowed("id"),
209 tag: 1,
210 kind: Kind::String,
211 cardinality: Cardinality::Required,
212 },
213 Field {
214 name: Cow::Borrowed("scope"),
215 tag: 2,
216 kind: Kind::String,
217 cardinality: Cardinality::Optional,
218 },
219 Field {
220 name: Cow::Borrowed("media_type"),
221 tag: 3,
222 kind: Kind::String,
223 cardinality: Cardinality::Required,
224 },
225 Field {
226 name: Cow::Borrowed("expected_length"),
227 tag: 4,
228 kind: Kind::U64,
229 cardinality: Cardinality::Optional,
230 },
231 Field {
232 name: Cow::Borrowed("expected_sha256"),
233 tag: 5,
234 kind: Kind::Bytes,
235 cardinality: Cardinality::Optional,
236 },
237 ];
238
239 const MARKER: &'static [u8] = &crate::schema::declaration::encode::<
240 { crate::schema::declaration::encoded_len(Self::NAME, Self::FIELDS) },
241 >(Self::NAME, Self::FIELDS);
242}
243
244impl Described for FileReference {
245 const NAME: &'static str = concat!(module_path!(), "::", stringify!(FileReference));
246 const FIELDS: &'static [Field] = &[
247 Field {
248 name: Cow::Borrowed("position"),
249 tag: 1,
250 kind: Kind::Message { definition: StorageCursor::DEFINITION },
251 cardinality: Cardinality::Required,
252 },
253 Field {
254 name: Cow::Borrowed("metadata"),
255 tag: 2,
256 kind: Kind::Message { definition: FileMetadata::DEFINITION },
257 cardinality: Cardinality::Required,
258 },
259 Field {
260 name: Cow::Borrowed("length"),
261 tag: 3,
262 kind: Kind::U64,
263 cardinality: Cardinality::Required,
264 },
265 Field {
266 name: Cow::Borrowed("sha256"),
267 tag: 4,
268 kind: Kind::Bytes,
269 cardinality: Cardinality::Required,
270 },
271 ];
272
273 const MARKER: &'static [u8] = &crate::schema::declaration::encode::<
274 { crate::schema::declaration::encoded_len(Self::NAME, Self::FIELDS) },
275 >(Self::NAME, Self::FIELDS);
276}
277
278