harmos_domain/model/storage/
catalog.rs1use std::fmt;
18use std::num::{NonZeroU32, NonZeroUsize};
19use crate::catalog::{Catalog, Contains, Member, Projection, Visitor};
22use crate::model::storage::{ObjectRetention, ObjectValue, SchemaKey, check_length, invalid, object_key};
23use crate::{Error, Identified, Identifier, MemorySize, Result, schema};
24
25const MAX_DEFINITIONS: usize = 4096;
28const MAX_IDENTITY_BYTES: usize = 1024;
29
30pub const LOOKUP_MEMORY_BYTES: usize = MAX_IDENTITY_BYTES * 2;
31const VALIDATION_BYTES: usize = 8 * 1024 * 1024;
32
33#[derive(Clone, Copy, Debug)]
40pub struct ObjectDefinition {
41 schema: fn() -> Result<SchemaKey>,
42 declaration: schema::declaration::Definition,
43 retention: ObjectRetention,
44 verify: fn(&ObjectValue, NonZeroUsize) -> Result<()>,
45
46 verify_admitted: fn(&ObjectValue, NonZeroUsize, usize) -> Result<()>,
47}
48
49pub struct ObjectDefinitions;
50
51struct KeyMatch<'a> {
52 expected: &'a str,
53 offset: usize,
54}
55
56pub trait StoredObject: Identified + schema::Described + crate::Message + MemorySize + 'static {
61 type Key: fmt::Display + ?Sized;
62
63 const RETENTION: ObjectRetention;
64
65 fn key(&self) -> &Self::Key;
66
67 fn validate(&self) -> Result<()> {
68 Ok(())
69 }
70}
71
72pub trait ObjectCatalog: Catalog<ObjectDefinitions> + Send + Sync + 'static {
73 fn definitions() -> <Self as Catalog<ObjectDefinitions>>::Entries {
74 <Self as Catalog<ObjectDefinitions>>::ENTRIES
75 }
76
77 fn definition_count() -> usize {
78 <<Self as Catalog<ObjectDefinitions>>::Entries as Projection<ObjectDefinition>>::LEN
79 }
80
81 fn validate() -> Result<()> {
82 let count = Self::definition_count();
83 if count > MAX_DEFINITIONS {
84 return Err(invalid("object catalog exceeds its definition limit"));
85 }
86 let entries = Self::definitions();
87 let mut visited = 0usize;
88 entries.try_for_each(|_| {
89 visited += 1;
90 if visited > count {
91 return Err(invalid("object catalog projection count differs"));
92 }
93 Ok(())
94 })?;
95 if visited != count {
96 return Err(invalid("object catalog projection count differs"));
97 }
98 let bytes = count
99 .checked_mul(std::mem::size_of::<(SchemaKey, ObjectRetention)>() + MAX_IDENTITY_BYTES)
100 .and_then(|bytes| bytes.checked_add(MAX_IDENTITY_BYTES + std::mem::size_of::<Vec<(SchemaKey, ObjectRetention)>>()))
101 .ok_or_else(|| invalid("object catalog validation size overflow"))?;
102 if bytes > VALIDATION_BYTES {
103 return Err(invalid("object catalog exceeds its validation byte limit"));
104 }
105 let mut identities = Vec::with_capacity(count);
106 entries.try_for_each(|entry| {
107 if identities.len() >= count {
108 return Err(invalid("object catalog projection count differs"));
109 }
110 let key = entry.schema()?;
111 if key.id.as_str().len() > MAX_IDENTITY_BYTES {
112 return Err(invalid("object schema identity exceeds its byte limit"));
113 }
114 let mut settings = crate::config::SchemaSettings::default();
115 settings.preparation_bytes = NonZeroUsize::new(settings.preparation_bytes.get().min(VALIDATION_BYTES - bytes))
116 .ok_or_else(|| invalid("object schema preparation exceeds allowance"))?;
117 entry.graph(&settings)?;
118 identities.push((key, entry.retention));
119 Ok(())
120 })?;
121 if identities.len() != count {
122 return Err(invalid("object catalog projection count differs"));
123 }
124 identities.sort_unstable_by(|(left, _), (right, _)| (left.id.as_str(), left.version).cmp(&(right.id.as_str(), right.version)));
125 for pair in identities.windows(2) {
126 if pair[0].0.id == pair[1].0.id {
127 if pair[0].1 != pair[1].1 {
128 return Err(invalid("codec versions disagree on object retention"));
129 }
130 if pair[0].0.version == pair[1].0.version {
131 return Err(invalid("duplicate object schema identity and version"));
132 }
133 }
134 }
135 Ok(())
136 }
137
138 fn encode<T: StoredObject>(value: &T, max_payload_bytes: NonZeroUsize) -> Result<ObjectValue>
139 where
140 Self: Contains<T>,
141 {
142 Self::validate()?;
143 value.validate()?;
144 let schema = object_schema::<T>()?;
145 let item = object_key(value.key(), max_payload_bytes)?;
146 let length = value.encoded_len();
147 check_length(length, max_payload_bytes)?;
148 let payload = value.encode_to_vec();
149 if payload.len() != length {
150 return Err(invalid("object codec reported an inconsistent length"));
151 }
152 Ok(ObjectValue { schema, item, payload })
153 }
154
155 fn decode<T: StoredObject>(value: &ObjectValue, max_payload_bytes: NonZeroUsize) -> Result<T>
156 where
157 Self: Contains<T>,
158 {
159 Self::validate()?;
160 decode::<T>(value, max_payload_bytes)
161 }
162
163 fn validate_value(value: &ObjectValue, max_payload_bytes: NonZeroUsize) -> Result<()> {
164 Self::validate()?;
165 check_length(value.payload.len(), max_payload_bytes)?;
166 let mut verify = None;
167 Self::definitions().try_for_each(|entry| {
168 if verify.is_none() && entry.schema()? == value.schema {
169 verify = Some(entry.verify);
170 }
171 Ok::<(), Error>(())
172 })?;
173 match verify {
174 Some(verify) => verify(value, max_payload_bytes),
175 None => Err(invalid("object schema is absent from the catalog")),
176 }
177 }
178}
179
180impl ObjectDefinition {
183 pub fn retention(&self) -> ObjectRetention {
184 self.retention
185 }
186
187 pub fn schema(&self) -> Result<SchemaKey> {
188 (self.schema)()
189 }
190
191 pub fn declaration(&self) -> schema::declaration::Definition {
192 self.declaration
193 }
194
195 pub fn graph(&self, settings: &crate::config::SchemaSettings) -> Result<schema::graph::Schema> {
196 schema::graph::Schema::from_declaration(self.declaration, settings)
197 .map_err(|_| invalid("invalid object schema or preparation allowance"))
198 }
199
200 pub fn validate_admitted(&self, value: &ObjectValue, payload: NonZeroUsize, allowance: usize) -> Result<()> {
201 check_length(value.payload.len(), payload)?;
202 check_length(value.item.as_str().len(), payload)?;
203 (self.verify_admitted)(value, payload, allowance)
204 }
205}
206
207impl Visitor for ObjectDefinitions {
208 type Entry = ObjectDefinition;
209}
210
211impl<T: StoredObject> Member<T> for ObjectDefinitions {
212 const ENTRY: ObjectDefinition = ObjectDefinition {
213 schema: object_schema::<T>,
214 declaration: T::DEFINITION,
215 retention: T::RETENTION,
216 verify: verify::<T>,
217
218 verify_admitted: verify_admitted::<T>,
219 };
220}
221
222impl<C: Catalog<ObjectDefinitions> + Send + Sync + 'static> ObjectCatalog for C {}
223
224impl fmt::Write for KeyMatch<'_> {
225 fn write_str(&mut self, value: &str) -> fmt::Result {
226 let rest = self.expected.get(self.offset..).ok_or(fmt::Error)?;
227 if !rest.starts_with(value) {
228 return Err(fmt::Error);
229 }
230 self.offset += value.len();
231 Ok(())
232 }
233}
234
235pub fn definition<C: ObjectCatalog>(key: &SchemaKey) -> Result<ObjectDefinition> {
238 let mut found = None;
239 C::definitions().try_for_each(|entry| {
240 if found.is_none() && entry.schema()? == *key {
241 found = Some(*entry);
242 }
243 Ok::<(), Error>(())
244 })?;
245 found.ok_or_else(|| invalid("object schema is absent from the catalog"))
246}
247
248pub fn object_schema<T: StoredObject>() -> Result<SchemaKey> {
249 let (id, version) = T::definition();
250 if id.as_str().len() > MAX_IDENTITY_BYTES {
251 return Err(invalid("object schema identity exceeds its byte limit"));
252 }
253 Ok(SchemaKey {
254 payload_encoding: crate::model::storage::PayloadEncoding::PostcardV1,
255 id: Identifier::new(id.as_str()).map_err(|_| invalid("invalid object schema identity"))?,
256 version: NonZeroU32::new(version).ok_or_else(|| invalid("zero object schema version"))?,
257 })
258}
259
260pub fn decode_admitted<T: StoredObject>(value: &ObjectValue, schema: &SchemaKey) -> Result<T> {
261 if &value.schema != schema {
262 return Err(invalid("object schema does not match the requested type"));
263 }
264 let decoded = T::decode(&value.payload).map_err(|error| Error::Corrupt { operation: "decode object", source: error })?;
265 decoded.validate()?;
266 let mut key = KeyMatch { expected: value.item.as_str(), offset: 0 };
267 fmt::write(&mut key, format_args!("{}", decoded.key())).map_err(|_| invalid("decoded object identity differs from stored key"))?;
268 if key.offset != value.item.as_str().len() {
269 return Err(invalid("decoded object identity differs from stored key"));
270 }
271 Ok(decoded)
272}
273
274fn decode<T: StoredObject>(value: &ObjectValue, max_payload_bytes: NonZeroUsize) -> Result<T> {
277 check_length(value.payload.len(), max_payload_bytes)?;
278 check_length(value.item.as_str().len(), max_payload_bytes)?;
279 decode_admitted::<T>(value, &object_schema::<T>()?)
280}
281
282fn verify<T: StoredObject>(value: &ObjectValue, max_payload_bytes: NonZeroUsize) -> Result<()> {
283 decode::<T>(value, max_payload_bytes).map(|_| ())
284}
285
286fn verify_admitted<T: StoredObject>(value: &ObjectValue, payload: NonZeroUsize, allowance: usize) -> Result<()> {
287 let overhead = std::mem::size_of::<crate::MessageError>() + MAX_IDENTITY_BYTES * 2;
288 let native = allowance.checked_sub(overhead).ok_or_else(verification_limit)?;
289 if std::mem::size_of::<T>() > native {
290 return Err(verification_limit());
291 }
292 let required = T::decoding_memory(&value.payload).map_err(|error| Error::Corrupt { operation: "preflight object", source: error })?;
293 if required > native {
294 return Err(verification_limit());
295 }
296 let decoded = decode::<T>(value, payload)?;
297 if decoded.memory_size() > required {
298 return Err(verification_limit());
299 }
300 Ok(())
301}
302
303fn verification_limit() -> Error {
304 Error::ResourceExhausted { resource: "storage object verification" }
305}
306
307