Skip to main content

moq_transport/coding/
track_namespace.rs

1use super::{Decode, DecodeError, Encode, EncodeError, TupleField};
2use core::hash::{Hash, Hasher};
3use std::convert::TryFrom;
4use std::fmt;
5use thiserror::Error;
6
7/// Error type for TrackNamespace conversion failures
8#[derive(Debug, Clone, Error, PartialEq, Eq)]
9pub enum TrackNamespaceError {
10    #[error("too many fields: {0} exceeds maximum of {1}")]
11    TooManyFields(usize, usize),
12
13    #[error("field too large: {0} bytes exceeds maximum of {1}")]
14    FieldTooLarge(usize, usize),
15}
16
17/// TrackNamespace
18#[derive(Clone, Default, Eq, PartialEq)]
19pub struct TrackNamespace {
20    pub fields: Vec<TupleField>,
21}
22
23impl TrackNamespace {
24    pub const MAX_FIELDS: usize = 32;
25
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    pub fn add(&mut self, field: TupleField) {
31        self.fields.push(field);
32    }
33
34    pub fn clear(&mut self) {
35        self.fields.clear();
36    }
37
38    pub fn from_utf8_path(path: &str) -> Self {
39        let mut tuple = TrackNamespace::new();
40        for part in path.split('/') {
41            tuple.add(TupleField::from_utf8(part));
42        }
43        tuple
44    }
45
46    pub fn to_utf8_path(&self) -> String {
47        let mut path = String::new();
48        for field in &self.fields {
49            path.push('/');
50            path.push_str(&String::from_utf8_lossy(&field.value));
51        }
52        path
53    }
54}
55
56impl Hash for TrackNamespace {
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        self.fields.hash(state);
59    }
60}
61
62impl Decode for TrackNamespace {
63    fn decode<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
64        let count = usize::decode(r)?;
65        if count > Self::MAX_FIELDS {
66            return Err(DecodeError::FieldBoundsExceeded(
67                "TrackNamespace tuples".to_string(),
68            ));
69        }
70
71        let mut fields = Vec::new();
72        for _ in 0..count {
73            fields.push(TupleField::decode(r)?);
74        }
75        Ok(Self { fields })
76    }
77}
78
79impl Encode for TrackNamespace {
80    fn encode<W: bytes::BufMut>(&self, w: &mut W) -> Result<(), EncodeError> {
81        if self.fields.len() > Self::MAX_FIELDS {
82            return Err(EncodeError::FieldBoundsExceeded(
83                "TrackNamespace tuples".to_string(),
84            ));
85        }
86        self.fields.len().encode(w)?;
87        for field in &self.fields {
88            field.encode(w)?;
89        }
90        Ok(())
91    }
92}
93
94impl fmt::Debug for TrackNamespace {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        // Just reuse the Display formatting
97        write!(f, "{self}")
98    }
99}
100
101impl fmt::Display for TrackNamespace {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{0}", self.to_utf8_path())
104    }
105}
106
107impl TryFrom<Vec<TupleField>> for TrackNamespace {
108    type Error = TrackNamespaceError;
109
110    fn try_from(fields: Vec<TupleField>) -> Result<Self, Self::Error> {
111        if fields.len() > Self::MAX_FIELDS {
112            return Err(TrackNamespaceError::TooManyFields(
113                fields.len(),
114                Self::MAX_FIELDS,
115            ));
116        }
117        for field in &fields {
118            if field.value.len() > TupleField::MAX_VALUE_SIZE {
119                return Err(TrackNamespaceError::FieldTooLarge(
120                    field.value.len(),
121                    TupleField::MAX_VALUE_SIZE,
122                ));
123            }
124        }
125        Ok(Self { fields })
126    }
127}
128
129impl TryFrom<&str> for TrackNamespace {
130    type Error = TrackNamespaceError;
131
132    fn try_from(path: &str) -> Result<Self, Self::Error> {
133        let fields: Vec<TupleField> = path
134            .split('/')
135            .filter(|s| !s.is_empty())
136            .map(TupleField::from_utf8)
137            .collect();
138        Self::try_from(fields)
139    }
140}
141
142impl TryFrom<String> for TrackNamespace {
143    type Error = TrackNamespaceError;
144
145    fn try_from(path: String) -> Result<Self, Self::Error> {
146        Self::try_from(path.as_str())
147    }
148}
149
150impl TryFrom<Vec<&str>> for TrackNamespace {
151    type Error = TrackNamespaceError;
152
153    fn try_from(parts: Vec<&str>) -> Result<Self, Self::Error> {
154        let fields: Vec<TupleField> = parts.into_iter().map(TupleField::from_utf8).collect();
155        Self::try_from(fields)
156    }
157}
158
159impl TryFrom<Vec<String>> for TrackNamespace {
160    type Error = TrackNamespaceError;
161
162    fn try_from(parts: Vec<String>) -> Result<Self, Self::Error> {
163        let fields: Vec<TupleField> = parts.iter().map(|s| TupleField::from_utf8(s)).collect();
164        Self::try_from(fields)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use bytes::Bytes;
172    use bytes::BytesMut;
173    use std::convert::TryInto;
174
175    #[test]
176    fn encode_decode() {
177        let mut buf = BytesMut::new();
178
179        let t = TrackNamespace::from_utf8_path("test/path/to/resource");
180        t.encode(&mut buf).unwrap();
181        #[rustfmt::skip]
182        assert_eq!(
183            buf.to_vec(),
184            vec![
185                0x04, // 4 tuple fields
186                // Field 1: "test"
187                0x04, 0x74, 0x65, 0x73, 0x74,
188                // Field 2: "path"
189                0x04, 0x70, 0x61, 0x74, 0x68,
190                // Field 3: "to"
191                0x02, 0x74, 0x6f,
192                // Field 4: "resource"
193                0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65
194            ]
195        );
196        let decoded = TrackNamespace::decode(&mut buf).unwrap();
197        assert_eq!(decoded, t);
198
199        // Alternate construction
200        let mut t = TrackNamespace::new();
201        t.add(TupleField::from_utf8("test"));
202        t.encode(&mut buf).unwrap();
203        assert_eq!(
204            buf.to_vec(),
205            vec![
206                0x01, // 1 tuple field
207                // Field 1: "test"
208                0x04, 0x74, 0x65, 0x73, 0x74
209            ]
210        );
211        let decoded = TrackNamespace::decode(&mut buf).unwrap();
212        assert_eq!(decoded, t);
213    }
214
215    #[test]
216    fn encode_too_large() {
217        let mut buf = BytesMut::new();
218
219        let mut t = TrackNamespace::new();
220        for i in 0..TrackNamespace::MAX_FIELDS + 1 {
221            t.add(TupleField::from_utf8(&format!("field{}", i)));
222        }
223
224        let encoded = t.encode(&mut buf);
225        assert!(matches!(
226            encoded.unwrap_err(),
227            EncodeError::FieldBoundsExceeded(_)
228        ));
229    }
230
231    #[test]
232    fn decode_too_large() {
233        let mut data: Vec<u8> = vec![0x00; 256]; // Create a vector with 256 bytes
234        data[0] = (TrackNamespace::MAX_FIELDS + 1) as u8; // Set first byte (count) to 33 as a VarInt
235        let mut buf: Bytes = data.into();
236        let decoded = TrackNamespace::decode(&mut buf);
237        assert!(matches!(
238            decoded.unwrap_err(),
239            DecodeError::FieldBoundsExceeded(_)
240        ));
241    }
242
243    #[test]
244    fn try_from_str() {
245        let ns: TrackNamespace = "test/path/to/resource".try_into().unwrap();
246        assert_eq!(ns.fields.len(), 4);
247        assert_eq!(ns.to_utf8_path(), "/test/path/to/resource");
248    }
249
250    #[test]
251    fn try_from_string() {
252        let path = String::from("test/path");
253        let ns: TrackNamespace = path.try_into().unwrap();
254        assert_eq!(ns.fields.len(), 2);
255        assert_eq!(ns.to_utf8_path(), "/test/path");
256    }
257
258    #[test]
259    fn try_from_vec_str() {
260        let parts = vec!["test", "path", "to", "resource"];
261        let ns: TrackNamespace = parts.try_into().unwrap();
262        assert_eq!(ns.fields.len(), 4);
263        assert_eq!(ns.to_utf8_path(), "/test/path/to/resource");
264    }
265
266    #[test]
267    fn try_from_vec_string() {
268        let parts = vec![String::from("test"), String::from("path")];
269        let ns: TrackNamespace = parts.try_into().unwrap();
270        assert_eq!(ns.fields.len(), 2);
271        assert_eq!(ns.to_utf8_path(), "/test/path");
272    }
273
274    #[test]
275    fn try_from_vec_tuple_field() {
276        let fields = vec![TupleField::from_utf8("test"), TupleField::from_utf8("path")];
277        let ns: TrackNamespace = fields.try_into().unwrap();
278        assert_eq!(ns.fields.len(), 2);
279        assert_eq!(ns.to_utf8_path(), "/test/path");
280    }
281
282    #[test]
283    fn try_from_too_many_fields() {
284        let mut fields = Vec::new();
285        for i in 0..TrackNamespace::MAX_FIELDS + 1 {
286            fields.push(TupleField::from_utf8(&format!("field{}", i)));
287        }
288        let result: Result<TrackNamespace, _> = fields.try_into();
289        assert!(matches!(
290            result.unwrap_err(),
291            TrackNamespaceError::TooManyFields(33, 32)
292        ));
293    }
294
295    #[test]
296    fn try_from_field_too_large() {
297        let large_value = "x".repeat(TupleField::MAX_VALUE_SIZE + 1);
298        let fields = vec![TupleField {
299            value: large_value.into_bytes(),
300        }];
301        let result: Result<TrackNamespace, _> = fields.try_into();
302        assert!(matches!(
303            result.unwrap_err(),
304            TrackNamespaceError::FieldTooLarge(4097, 4096)
305        ));
306    }
307}