Skip to main content

tea_protocol/
metadata.rs

1use std::collections::BTreeMap;
2use std::ops::Deref;
3
4use serde::{Deserialize, Deserializer, Serialize};
5use serde_json::{Value, json};
6use thiserror::Error;
7
8use crate::envelope::deserialize_unique_value;
9
10/// Maximum number of metadata namespaces on one protocol value.
11pub const MAX_METADATA_NAMESPACES: usize = 16;
12/// Maximum encoded JSON bytes for one metadata map.
13pub const MAX_METADATA_BYTES: usize = 16 * 1024;
14/// Maximum JSON container nesting depth inside metadata values.
15pub const MAX_METADATA_DEPTH: usize = 8;
16
17/// Bounded extension metadata keyed by collision-resistant namespaces.
18#[derive(Debug, Clone, PartialEq, Serialize, Default)]
19#[serde(transparent)]
20pub struct ProtocolMetadata(BTreeMap<String, Value>);
21
22impl ProtocolMetadata {
23    pub(crate) fn protocol_version_details(received_version: &str) -> Self {
24        Self(BTreeMap::from([(
25            "dev.tea-rs.protocol".to_owned(),
26            json!({
27                "supportedProtocol": ">=1.0 <2.0",
28                "receivedProtocol": received_version,
29            }),
30        )]))
31    }
32
33    pub(crate) fn protocol_compatibility_details(unsupported_type: Option<&str>) -> Self {
34        let value = match unsupported_type {
35            Some(unsupported_type) => json!({
36                "supportedProtocol": ">=1.0 <2.0",
37                "unsupportedType": unsupported_type,
38            }),
39            None => json!({"supportedProtocol": ">=1.0 <2.0"}),
40        };
41        Self(BTreeMap::from([("dev.tea-rs.protocol".to_owned(), value)]))
42    }
43
44    /// Builds and validates metadata from namespace/value entries.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error for an invalid namespace or when count, byte, or
49    /// nesting limits are exceeded.
50    pub fn from_entries<K, I>(entries: I) -> Result<Self, ProtocolMetadataError>
51    where
52        K: Into<String>,
53        I: IntoIterator<Item = (K, Value)>,
54    {
55        Self::try_from(
56            entries
57                .into_iter()
58                .map(|(namespace, value)| (namespace.into(), value))
59                .collect::<BTreeMap<_, _>>(),
60        )
61    }
62
63    /// Returns the number of namespaces.
64    #[must_use]
65    pub fn len(&self) -> usize {
66        self.0.len()
67    }
68
69    /// Returns whether the metadata map is empty.
70    #[must_use]
71    pub fn is_empty(&self) -> bool {
72        self.0.is_empty()
73    }
74
75    /// Returns a metadata value by namespace.
76    #[must_use]
77    pub fn get(&self, namespace: &str) -> Option<&Value> {
78        self.0.get(namespace)
79    }
80}
81
82impl Deref for ProtocolMetadata {
83    type Target = BTreeMap<String, Value>;
84
85    fn deref(&self) -> &Self::Target {
86        &self.0
87    }
88}
89
90impl TryFrom<BTreeMap<String, Value>> for ProtocolMetadata {
91    type Error = ProtocolMetadataError;
92
93    fn try_from(values: BTreeMap<String, Value>) -> Result<Self, Self::Error> {
94        if values.len() > MAX_METADATA_NAMESPACES {
95            return Err(ProtocolMetadataError::TooManyNamespaces);
96        }
97        for (namespace, value) in &values {
98            validate_namespace(namespace)?;
99            if json_depth(value) > MAX_METADATA_DEPTH {
100                return Err(ProtocolMetadataError::TooDeep);
101            }
102        }
103        if serde_json::to_vec(&values)
104            .map_err(ProtocolMetadataError::Serialization)?
105            .len()
106            > MAX_METADATA_BYTES
107        {
108            return Err(ProtocolMetadataError::TooLarge);
109        }
110        Ok(Self(values))
111    }
112}
113
114impl<'de> Deserialize<'de> for ProtocolMetadata {
115    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116    where
117        D: Deserializer<'de>,
118    {
119        let value = deserialize_unique_value(deserializer)?;
120        let values = serde_json::from_value::<BTreeMap<String, Value>>(value)
121            .map_err(serde::de::Error::custom)?;
122        Self::try_from(values).map_err(serde::de::Error::custom)
123    }
124}
125
126/// Error returned when validating extension metadata.
127#[derive(Debug, Error)]
128pub enum ProtocolMetadataError {
129    /// A namespace does not follow the required reverse-domain form.
130    #[error("metadata namespace must contain at least two lowercase domain-style labels")]
131    InvalidNamespace,
132    /// The map contains too many namespaces.
133    #[error("metadata contains too many namespaces")]
134    TooManyNamespaces,
135    /// The encoded map exceeds the byte limit.
136    #[error("metadata exceeds the encoded byte limit")]
137    TooLarge,
138    /// A value exceeds the nesting-depth limit.
139    #[error("metadata exceeds the nesting-depth limit")]
140    TooDeep,
141    /// JSON size validation unexpectedly failed.
142    #[error("metadata could not be encoded for validation: {0}")]
143    Serialization(serde_json::Error),
144}
145
146pub(crate) fn validate_json_bounds(
147    value: &Value,
148    max_bytes: usize,
149    max_depth: usize,
150) -> Result<(), ProtocolMetadataError> {
151    if json_depth(value) > max_depth {
152        return Err(ProtocolMetadataError::TooDeep);
153    }
154    if serde_json::to_vec(value)
155        .map_err(ProtocolMetadataError::Serialization)?
156        .len()
157        > max_bytes
158    {
159        return Err(ProtocolMetadataError::TooLarge);
160    }
161    Ok(())
162}
163
164fn validate_namespace(namespace: &str) -> Result<(), ProtocolMetadataError> {
165    let labels = namespace.split('.').collect::<Vec<_>>();
166    if labels.len() < 2 || labels.iter().any(|label| !valid_label(label)) {
167        return Err(ProtocolMetadataError::InvalidNamespace);
168    }
169    Ok(())
170}
171
172fn valid_label(label: &str) -> bool {
173    let bytes = label.as_bytes();
174    !bytes.is_empty()
175        && bytes[0].is_ascii_lowercase()
176        && bytes[bytes.len() - 1].is_ascii_alphanumeric()
177        && bytes
178            .iter()
179            .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
180}
181
182fn json_depth(value: &Value) -> usize {
183    match value {
184        Value::Array(values) => 1 + values.iter().map(json_depth).max().unwrap_or(0),
185        Value::Object(values) => 1 + values.values().map(json_depth).max().unwrap_or(0),
186        _ => 0,
187    }
188}