Skip to main content

harmos_domain/
artifacts.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17
18// Third Party Imports
19use memchr::{memchr, memmem};
20use serde::Deserialize;
21use serde::de::DeserializeOwned;
22// Local Imports
23
24/* === Constants === */
25
26const FAMILY: &[u8] = b"\0HARMOS:ARTIFACT:\0";
27pub const PREFIX: &[u8] = b"\0HARMOS:ARTIFACT:V1:";
28
29pub const FRAGMENT_LIMIT: usize = 64 * 1024;
30
31pub const KINDS: [&str; 17] = [
32    "capabilities",
33    "contracts",
34    "contributions",
35    "guest",
36    "listeners",
37    "observers",
38    "operations",
39    "plugin",
40    "publications",
41    "requires",
42    "restart",
43    "route",
44    "routes",
45    "schema",
46    "sidecar",
47    "stream",
48    "subscribe",
49];
50
51/* === Macros === */
52
53/* === Type Aliases === */
54
55/* === Structs === */
56
57#[derive(Deserialize)]
58struct Kinded {
59    kind: String,
60}
61
62/* === Enums === */
63
64#[derive(Debug, thiserror::Error)]
65pub enum ScanError {
66    #[error("artifact metadata exceeds the schema preparation memory or work budget")]
67    Budget,
68
69    #[error("a Harmos metadata fragment exceeds its {FRAGMENT_LIMIT}-byte bound")]
70    Oversized,
71
72    #[error("a Harmos metadata fragment is invalid: {source}")]
73    Malformed {
74        #[source]
75        source: serde_json::Error,
76    },
77
78    #[error("a Harmos metadata fragment declares the unknown kind `{kind}`")]
79    UnknownKind { kind: String },
80}
81
82/* === Traits === */
83
84/* === Implementations === */
85
86/* === Public Functions === */
87
88pub fn fragments<F: DeserializeOwned>(artifact: &[u8], owned: &[&str]) -> Result<Vec<F>, ScanError> {
89    fragments_with_settings(artifact, owned, &mut crate::config::SchemaSettings::default())
90}
91
92pub(crate) fn fragments_with_settings<F: DeserializeOwned>(
93    artifact: &[u8], owned: &[&str], settings: &mut crate::config::SchemaSettings,
94) -> Result<Vec<F>, ScanError> {
95    // Keep Complete Search Markers Out Of Reader Binaries Themselves.
96    let family = &FAMILY[..FAMILY.len() - 1];
97    let mut prefix = [0; PREFIX.len()];
98    prefix[..family.len()].copy_from_slice(std::hint::black_box(family));
99    // A Terminator Separates The Family Literal From Adjacent Version Literals.
100    prefix[family.len()] = std::hint::black_box(b'V');
101    prefix[family.len() + 1] = std::hint::black_box(b'1');
102    prefix[family.len() + 2] = std::hint::black_box(b':');
103    let current = memmem::Finder::new(&prefix);
104    let mut found = Vec::new();
105    let mut cursor = 0;
106    let mut remaining_bytes = settings.preparation_bytes.get();
107    let mut remaining_work = settings.preparation_work.get();
108
109    while let Some(relative) = current.find(&artifact[cursor..]) {
110        let start = cursor + relative + PREFIX.len();
111        let bounded = &artifact[start..artifact.len().min(start + FRAGMENT_LIMIT + 1)];
112        let Some(length) = memchr(0, bounded) else {
113            return Err(ScanError::Oversized);
114        };
115        let encoded = &bounded[..length];
116        cursor = start + length + 1;
117
118        remaining_work = remaining_work.checked_sub(encoded.len()).ok_or(ScanError::Budget)?;
119        let charge =
120            encoded.len().checked_mul(16).and_then(|bytes| bytes.checked_add(4 * std::mem::size_of::<F>())).ok_or(ScanError::Budget)?;
121        remaining_bytes = remaining_bytes.checked_sub(charge).ok_or(ScanError::Budget)?;
122        let Kinded { kind } = serde_json::from_slice(encoded).map_err(|source| ScanError::Malformed { source })?;
123        if !KINDS.contains(&kind.as_str()) {
124            return Err(ScanError::UnknownKind { kind });
125        }
126        if !owned.contains(&kind.as_str()) {
127            continue;
128        }
129
130        found.push(serde_json::from_slice(encoded).map_err(|source| ScanError::Malformed { source })?);
131    }
132
133    settings.preparation_bytes = std::num::NonZeroUsize::new(remaining_bytes).ok_or(ScanError::Budget)?;
134    settings.preparation_work = std::num::NonZeroUsize::new(remaining_work).ok_or(ScanError::Budget)?;
135    Ok(found)
136}
137
138/* === Private Functions === */
139
140/* === Tests === */