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
10pub const MAX_METADATA_NAMESPACES: usize = 16;
12pub const MAX_METADATA_BYTES: usize = 16 * 1024;
14pub const MAX_METADATA_DEPTH: usize = 8;
16
17#[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 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 #[must_use]
65 pub fn len(&self) -> usize {
66 self.0.len()
67 }
68
69 #[must_use]
71 pub fn is_empty(&self) -> bool {
72 self.0.is_empty()
73 }
74
75 #[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#[derive(Debug, Error)]
128pub enum ProtocolMetadataError {
129 #[error("metadata namespace must contain at least two lowercase domain-style labels")]
131 InvalidNamespace,
132 #[error("metadata contains too many namespaces")]
134 TooManyNamespaces,
135 #[error("metadata exceeds the encoded byte limit")]
137 TooLarge,
138 #[error("metadata exceeds the nesting-depth limit")]
140 TooDeep,
141 #[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}