amf_rs/amf0/
nested.rs

1use crate::amf0::boolean::BooleanType;
2use crate::amf0::marker::{NullType, UndefinedType};
3use crate::amf0::number::NumberType;
4use crate::amf0::object_end::ObjectEndType;
5use crate::amf0::string::{LongStringType, StringType};
6use crate::amf0::type_marker::TypeMarker;
7use crate::amf0::unsupported::{
8    DateType, MovieClipType, RecordsetType, ReferenceType, StrictArrayType, TypedObjectType,
9    UnsupportedType, XmlDocumentType,
10};
11use crate::amf0::utf8::Utf8;
12use crate::errors::AmfError;
13use crate::traits::{Marshall, MarshallLength, Unmarshall};
14use indexmap::IndexMap;
15use std::borrow::Borrow;
16use std::fmt::Display;
17use std::io;
18use std::ops::Deref;
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum Amf0TypedValue {
22    Number(NumberType),
23    Boolean(BooleanType),
24    String(StringType),
25    Object(ObjectType),
26    MovieClip(MovieClipType),
27    Null(NullType),
28    Undefined(UndefinedType),
29    Reference(ReferenceType),
30    EcmaArray(EcmaArrayType),
31    ObjectEnd(ObjectEndType),
32    StrictArray(StrictArrayType),
33    Date(DateType),
34    LongString(LongStringType),
35    Unsupported(UnsupportedType),
36    Recordset(RecordsetType),
37    XmlDocument(XmlDocumentType),
38    TypedObject(TypedObjectType),
39}
40
41impl Marshall for Amf0TypedValue {
42    fn marshall(&self) -> Result<Vec<u8>, AmfError> {
43        match self {
44            Amf0TypedValue::Number(v) => v.marshall(),
45            Amf0TypedValue::Boolean(v) => v.marshall(),
46            Amf0TypedValue::String(v) => v.marshall(),
47            Amf0TypedValue::Object(v) => v.marshall(),
48            Amf0TypedValue::MovieClip(v) => v.marshall(),
49            Amf0TypedValue::Null(v) => v.marshall(),
50            Amf0TypedValue::Undefined(v) => v.marshall(),
51            Amf0TypedValue::Reference(v) => v.marshall(),
52            Amf0TypedValue::EcmaArray(v) => v.marshall(),
53            Amf0TypedValue::ObjectEnd(v) => v.marshall(),
54            Amf0TypedValue::StrictArray(v) => v.marshall(),
55            Amf0TypedValue::Date(v) => v.marshall(),
56            Amf0TypedValue::LongString(v) => v.marshall(),
57            Amf0TypedValue::Unsupported(v) => v.marshall(),
58            Amf0TypedValue::Recordset(v) => v.marshall(),
59            Amf0TypedValue::XmlDocument(v) => v.marshall(),
60            Amf0TypedValue::TypedObject(v) => v.marshall(),
61        }
62    }
63}
64
65impl MarshallLength for Amf0TypedValue {
66    fn marshall_length(&self) -> usize {
67        match self {
68            Amf0TypedValue::Number(v) => v.marshall_length(),
69            Amf0TypedValue::Boolean(v) => v.marshall_length(),
70            Amf0TypedValue::String(v) => v.marshall_length(),
71            Amf0TypedValue::Object(v) => v.marshall_length(),
72            Amf0TypedValue::MovieClip(v) => v.marshall_length(),
73            Amf0TypedValue::Null(v) => v.marshall_length(),
74            Amf0TypedValue::Undefined(v) => v.marshall_length(),
75            Amf0TypedValue::Reference(v) => v.marshall_length(),
76            Amf0TypedValue::EcmaArray(v) => v.marshall_length(),
77            Amf0TypedValue::ObjectEnd(v) => v.marshall_length(),
78            Amf0TypedValue::StrictArray(v) => v.marshall_length(),
79            Amf0TypedValue::Date(v) => v.marshall_length(),
80            Amf0TypedValue::LongString(v) => v.marshall_length(),
81            Amf0TypedValue::Unsupported(v) => v.marshall_length(),
82            Amf0TypedValue::Recordset(v) => v.marshall_length(),
83            Amf0TypedValue::XmlDocument(v) => v.marshall_length(),
84            Amf0TypedValue::TypedObject(v) => v.marshall_length(),
85        }
86    }
87}
88
89impl Unmarshall for Amf0TypedValue {
90    fn unmarshall(buf: &[u8]) -> Result<(Self, usize), AmfError> {
91        if buf.is_empty() {
92            return Err(AmfError::Custom("Buffer is empty".to_string()));
93        }
94        if buf.len() >= 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x09 {
95            return Ok((Amf0TypedValue::ObjectEnd(ObjectEndType::default()), 3));
96        }
97
98        let type_marker = TypeMarker::try_from(buf[0])?;
99        match type_marker {
100            TypeMarker::Number => {
101                NumberType::unmarshall(buf).map(|v| (Amf0TypedValue::Number(v.0), v.1))
102            }
103            TypeMarker::Boolean => {
104                BooleanType::unmarshall(buf).map(|v| (Amf0TypedValue::Boolean(v.0), v.1))
105            }
106            TypeMarker::String => {
107                StringType::unmarshall(buf).map(|v| (Amf0TypedValue::String(v.0), v.1))
108            }
109            TypeMarker::Object => {
110                ObjectType::unmarshall(buf).map(|v| (Amf0TypedValue::Object(v.0), v.1))
111            }
112            TypeMarker::MovieClip => {
113                MovieClipType::unmarshall(buf).map(|v| (Amf0TypedValue::MovieClip(v.0), v.1))
114            }
115            TypeMarker::Null => NullType::unmarshall(buf).map(|v| (Amf0TypedValue::Null(v.0), v.1)),
116            TypeMarker::Undefined => {
117                UndefinedType::unmarshall(buf).map(|v| (Amf0TypedValue::Undefined(v.0), v.1))
118            }
119            TypeMarker::Reference => {
120                ReferenceType::unmarshall(buf).map(|v| (Amf0TypedValue::Reference(v.0), v.1))
121            }
122            TypeMarker::EcmaArray => {
123                EcmaArrayType::unmarshall(buf).map(|v| (Amf0TypedValue::EcmaArray(v.0), v.1))
124            }
125            TypeMarker::ObjectEnd => {
126                panic!("cannot happen")
127            }
128            TypeMarker::StrictArray => {
129                StrictArrayType::unmarshall(buf).map(|v| (Amf0TypedValue::StrictArray(v.0), v.1))
130            }
131            TypeMarker::Date => DateType::unmarshall(buf).map(|v| (Amf0TypedValue::Date(v.0), v.1)),
132            TypeMarker::LongString => {
133                LongStringType::unmarshall(buf).map(|v| (Amf0TypedValue::LongString(v.0), v.1))
134            }
135            TypeMarker::Unsupported => {
136                UnsupportedType::unmarshall(buf).map(|v| (Amf0TypedValue::Unsupported(v.0), v.1))
137            }
138            TypeMarker::Recordset => {
139                RecordsetType::unmarshall(buf).map(|v| (Amf0TypedValue::Recordset(v.0), v.1))
140            }
141            TypeMarker::XmlDocument => {
142                XmlDocumentType::unmarshall(buf).map(|v| (Amf0TypedValue::XmlDocument(v.0), v.1))
143            }
144            TypeMarker::TypedObject => {
145                TypedObjectType::unmarshall(buf).map(|v| (Amf0TypedValue::TypedObject(v.0), v.1))
146            }
147        }
148    }
149}
150
151impl TryFrom<&[u8]> for Amf0TypedValue {
152    type Error = AmfError;
153
154    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
155        Self::unmarshall(value).map(|(o, _)| o)
156    }
157}
158
159impl TryFrom<Vec<u8>> for Amf0TypedValue {
160    type Error = AmfError;
161
162    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
163        Self::try_from(value.as_slice())
164    }
165}
166
167impl TryFrom<Amf0TypedValue> for Vec<u8> {
168    type Error = AmfError;
169
170    fn try_from(value: Amf0TypedValue) -> Result<Self, Self::Error> {
171        value.marshall()
172    }
173}
174
175impl Display for Amf0TypedValue {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        match self {
178            Amf0TypedValue::Number(v) => v.fmt(f),
179            Amf0TypedValue::Boolean(v) => v.fmt(f),
180            Amf0TypedValue::String(v) => v.fmt(f),
181            Amf0TypedValue::Object(v) => v.fmt(f),
182            Amf0TypedValue::MovieClip(v) => v.fmt(f),
183            Amf0TypedValue::Null(v) => v.fmt(f),
184            Amf0TypedValue::Undefined(v) => v.fmt(f),
185            Amf0TypedValue::Reference(v) => v.fmt(f),
186            Amf0TypedValue::EcmaArray(v) => v.fmt(f),
187            Amf0TypedValue::ObjectEnd(v) => v.fmt(f),
188            Amf0TypedValue::StrictArray(v) => v.fmt(f),
189            Amf0TypedValue::Date(v) => v.fmt(f),
190            Amf0TypedValue::LongString(v) => v.fmt(f),
191            Amf0TypedValue::Unsupported(v) => v.fmt(f),
192            Amf0TypedValue::Recordset(v) => v.fmt(f),
193            Amf0TypedValue::XmlDocument(v) => v.fmt(f),
194            Amf0TypedValue::TypedObject(v) => v.fmt(f),
195        }
196    }
197}
198
199#[derive(Debug, Clone, PartialEq)]
200pub struct NestedType<const LBW: usize, const TM: u8> {
201    length: Option<u32>,
202    properties: IndexMap<Utf8, Amf0TypedValue>,
203    object_end: ObjectEndType,
204}
205
206impl<const LBW: usize, const TM: u8> NestedType<LBW, TM> {
207    pub fn new(properties: IndexMap<Utf8, Amf0TypedValue>) -> Self {
208        let length = if LBW == 4 {
209            Some(properties.len() as u32)
210        } else {
211            None
212        };
213        Self {
214            length,
215            properties,
216            object_end: ObjectEndType::default(),
217        }
218    }
219}
220
221impl<const LBW: usize, const TM: u8> Marshall for NestedType<LBW, TM> {
222    fn marshall(&self) -> Result<Vec<u8>, AmfError> {
223        let mut vec = Vec::with_capacity(self.marshall_length());
224        vec.push(TM);
225
226        if let Some(length) = self.length {
227            let length_bytes = length.to_be_bytes();
228            vec.extend_from_slice(&length_bytes);
229        }
230
231        self.properties
232            .iter()
233            .try_for_each(|(k, v)| -> io::Result<()> {
234                let k_vec = k
235                    .marshall()
236                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
237                vec.extend_from_slice(&k_vec);
238                let v_vec = v
239                    .marshall()
240                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
241                vec.extend_from_slice(&v_vec);
242                Ok(())
243            })?;
244
245        let object_end_vec = self.object_end.marshall()?;
246        vec.extend_from_slice(&object_end_vec);
247
248        Ok(vec)
249    }
250}
251
252impl<const LBW: usize, const TM: u8> MarshallLength for NestedType<LBW, TM> {
253    fn marshall_length(&self) -> usize {
254        let mut size = 1; // 1 byte for type marker
255        size += LBW;
256        let properties_bytes_size: usize = self
257            .properties
258            .iter()
259            .map(|(k, v)| k.marshall_length() + v.marshall_length())
260            .sum();
261        size += properties_bytes_size;
262        size += self.object_end.marshall_length();
263        size
264    }
265}
266
267impl<const LBW: usize, const TM: u8> Unmarshall for NestedType<LBW, TM> {
268    fn unmarshall(buf: &[u8]) -> Result<(Self, usize), AmfError> {
269        let required_size = 1 + LBW + 3; // 1 byte for type marker, LBW bytes(maybe 0) for optional properties length,  3 bytes for object end
270        if buf.len() < required_size {
271            // 1 byte for type marker, LBW bytes(maybe 0) for optional properties length,  3 bytes for object end
272            return Err(AmfError::BufferTooSmall {
273                want: required_size,
274                got: buf.len(),
275            });
276        }
277
278        if buf[0] != TM {
279            return Err(AmfError::TypeMarkerValueMismatch {
280                want: TM,
281                got: buf[0],
282            });
283        }
284
285        let mut length = 0u32;
286        if LBW == 4 {
287            length = u32::from_be_bytes(
288                buf[1..1 + LBW]
289                    .try_into()
290                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
291            );
292        }
293
294        let mut properties = IndexMap::new();
295        let mut offset = 1 + LBW;
296        while offset < buf.len() {
297            if offset <= buf.len() - 3 {
298                // 找到了 object end 则退出循环
299                if buf[offset] == 0x00 && buf[offset + 1] == 0x00 && buf[offset + 2] == 0x09 {
300                    break;
301                }
302            }
303
304            let (k, k_len) = Utf8::unmarshall(&buf[offset..])?;
305            offset += k_len;
306            let (v, v_len) = Amf0TypedValue::unmarshall(&buf[offset..])?;
307            offset += v_len;
308            properties.insert(k, v);
309        }
310
311        // 校验 object end 存在
312        if buf[buf.len() - 3..] != [0x00, 0x00, 0x09] {
313            return Err(AmfError::Custom(
314                "Invalid object, expected object end, got end of buffer".to_string(),
315            ));
316        }
317
318        // 仅在 EcmaArray 情况下(也就是 LBW == 4 的情况下)校验长度
319        if LBW == 4 && properties.len() != length as usize {
320            return Err(AmfError::Custom(format!(
321                "Invalid properties length, want {}, got {}",
322                length,
323                properties.len()
324            )));
325        }
326
327        let read_size = if offset == buf.len() {
328            offset
329        } else if offset == buf.len() - 3 {
330            offset + 3
331        } else {
332            buf.len()
333        };
334        Ok((Self::new(properties), read_size))
335    }
336}
337
338impl<const LBW: usize, const TM: u8> TryFrom<&[u8]> for NestedType<LBW, TM> {
339    type Error = AmfError;
340
341    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
342        Self::unmarshall(value).map(|(v, _)| v)
343    }
344}
345
346impl<const LBW: usize, const TM: u8> TryFrom<Vec<u8>> for NestedType<LBW, TM> {
347    type Error = AmfError;
348
349    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
350        Self::try_from(value.as_slice())
351    }
352}
353
354impl<const LBW: usize, const TM: u8> TryFrom<NestedType<LBW, TM>> for Vec<u8> {
355    type Error = AmfError;
356
357    fn try_from(value: NestedType<LBW, TM>) -> Result<Self, Self::Error> {
358        value.marshall()
359    }
360}
361
362impl<K, V, const LBW: usize, const TM: u8> From<IndexMap<K, V>> for NestedType<LBW, TM>
363where
364    K: Into<Utf8>,
365    V: Into<Amf0TypedValue>,
366{
367    fn from(value: IndexMap<K, V>) -> Self {
368        let properties = value
369            .into_iter()
370            .map(|(k, v)| (k.into(), v.into()))
371            .collect();
372        Self::new(properties)
373    }
374}
375
376impl<const LBW: usize, const TM: u8> AsRef<IndexMap<Utf8, Amf0TypedValue>> for NestedType<LBW, TM> {
377    fn as_ref(&self) -> &IndexMap<Utf8, Amf0TypedValue> {
378        &self.properties
379    }
380}
381
382impl<const LBW: usize, const TM: u8> Deref for NestedType<LBW, TM> {
383    type Target = IndexMap<Utf8, Amf0TypedValue>;
384
385    fn deref(&self) -> &Self::Target {
386        self.as_ref()
387    }
388}
389
390impl<const LBW: usize, const TM: u8> Borrow<IndexMap<Utf8, Amf0TypedValue>>
391    for NestedType<LBW, TM>
392{
393    fn borrow(&self) -> &IndexMap<Utf8, Amf0TypedValue> {
394        self.as_ref()
395    }
396}
397
398impl<const LBW: usize, const TM: u8> Display for NestedType<LBW, TM> {
399    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400        write!(f, "{{")?; // 写入开头的 "{"
401        // 使用 peeking iterator 来优雅地处理逗号
402        let mut iter = self.properties.iter().peekable();
403        while let Some((key, value)) = iter.next() {
404            // 写入 "key": value
405            // 注意 key 和 value 会自动使用它们自己的 Display 实现
406            write!(f, "\"{}\":{}", key, value)?;
407            // 如果这不是最后一个元素,就写入一个逗号和空格
408            if iter.peek().is_some() {
409                write!(f, ",")?;
410            }
411        }
412        write!(f, "}}") // 写入结尾的 "}"
413    }
414}
415
416impl<const LBW: usize, const TM: u8> Default for NestedType<LBW, TM> {
417    fn default() -> Self {
418        Self::new(IndexMap::new())
419    }
420}
421
422impl<K, V, const LBW: usize, const TM: u8> FromIterator<(K, V)> for NestedType<LBW, TM>
423where
424    K: Into<Utf8>,
425    V: Into<Amf0TypedValue>,
426{
427    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
428        let properties = iter
429            .into_iter()
430            .map(|(k, v)| (k.into(), v.into()))
431            .collect();
432        Self::new(properties)
433    }
434}
435
436impl<const LBW: usize, const TM: u8> IntoIterator for NestedType<LBW, TM> {
437    type Item = (Utf8, Amf0TypedValue);
438    type IntoIter = indexmap::map::IntoIter<Utf8, Amf0TypedValue>;
439
440    fn into_iter(self) -> Self::IntoIter {
441        self.properties.into_iter()
442    }
443}
444
445//	The AMF 0 Object type is used to encoded anonymous ActionScript objects. Any typed
446//	object that does not have a registered class should be treated as an anonymous
447//	ActionScript object. If the same object instance appears in an object graph it should be
448//	sent by reference using an AMF 0.
449//	Use the reference type to reduce redundant information from being serialized and infinite
450//	loops from cyclical references.
451pub type ObjectType = NestedType<0, { TypeMarker::Object as u8 }>;
452
453// An ECMA Array or 'associative' Array is used when an ActionScript Array contains non-ordinal indices.
454// This type is considered a complex type and thus reoccurring instancescan be sent by reference.
455// All indices. ordinal or otherwise, are treated as string keysinstead of integers.
456// For the purposes of serialization this type is very similar to ananonymous Obiect.
457pub type EcmaArrayType = NestedType<4, { TypeMarker::EcmaArray as u8 }>;
458
459#[cfg(test)]
460mod tests {
461    use super::*;
462    use indexmap::IndexMap;
463
464    // Helper function to create a sample IndexMap for NestedType tests
465    fn sample_properties() -> IndexMap<Utf8, Amf0TypedValue> {
466        let mut props = IndexMap::new();
467        props.insert(
468            Utf8::new_from_str("key1").unwrap(),
469            Amf0TypedValue::Number(NumberType::new(42.0)),
470        );
471        props.insert(
472            Utf8::new_from_str("key2").unwrap(),
473            Amf0TypedValue::String(StringType::try_from("value").unwrap()),
474        );
475        props
476    }
477
478    // Tests for Amf0TypedValue variants
479    #[test]
480    fn test_number() {
481        let original = Amf0TypedValue::Number(NumberType::new(42.0));
482        let marshalled = original.marshall().unwrap();
483        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
484        assert_eq!(original, unmarshalled);
485    }
486
487    #[test]
488    fn test_boolean() {
489        let original = Amf0TypedValue::Boolean(BooleanType::new(true));
490        let marshalled = original.marshall().unwrap();
491        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
492        assert_eq!(original, unmarshalled);
493    }
494
495    #[test]
496    fn test_string() {
497        let original = Amf0TypedValue::String(StringType::new_from_str("hello").unwrap());
498        let marshalled = original.marshall().unwrap();
499        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
500        assert_eq!(original, unmarshalled);
501    }
502
503    #[test]
504    fn test_object() {
505        let props = sample_properties();
506        let object_type = ObjectType::new(props);
507        let original = Amf0TypedValue::Object(object_type);
508        let marshalled = original.marshall().unwrap();
509        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
510        assert_eq!(original, unmarshalled);
511    }
512
513    #[test]
514    fn test_null() {
515        let original = Amf0TypedValue::Null(NullType);
516        let marshalled = original.marshall().unwrap();
517        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
518        assert_eq!(original, unmarshalled);
519    }
520
521    #[test]
522    fn test_undefined() {
523        let original = Amf0TypedValue::Undefined(UndefinedType);
524        let marshalled = original.marshall().unwrap();
525        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
526        assert_eq!(original, unmarshalled);
527    }
528
529    #[test]
530    fn test_ecma_array() {
531        let props = sample_properties();
532        let ecma_array_type = EcmaArrayType::new(props);
533        let original = Amf0TypedValue::EcmaArray(ecma_array_type);
534        let marshalled = original.marshall().unwrap();
535        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
536        assert_eq!(original, unmarshalled);
537    }
538
539    #[test]
540    fn test_object_end() {
541        let original = Amf0TypedValue::ObjectEnd(ObjectEndType::default());
542        let marshalled = original.marshall().unwrap();
543        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
544        assert_eq!(original, unmarshalled);
545    }
546
547    #[test]
548    fn test_long_string() {
549        let original =
550            Amf0TypedValue::LongString(LongStringType::new_from_string("a".repeat(65536)).unwrap());
551        let marshalled = original.marshall().unwrap();
552        let (unmarshalled, _) = Amf0TypedValue::unmarshall(&marshalled).unwrap();
553        assert_eq!(original, unmarshalled);
554    }
555
556    // Tests for Clone and PartialEq on Amf0TypedValue
557    #[test]
558    fn test_amf0_typed_value_clone() {
559        let original = Amf0TypedValue::Object(ObjectType::new(sample_properties()));
560        let cloned = original.clone();
561        assert_eq!(original, cloned);
562    }
563
564    #[test]
565    fn test_amf0_typed_value_partial_eq() {
566        let num1 = Amf0TypedValue::Number(NumberType::new(42.0));
567        let num2 = Amf0TypedValue::Number(NumberType::new(42.0));
568        let num3 = Amf0TypedValue::Number(NumberType::new(43.0));
569        assert_eq!(num1, num2);
570        assert_ne!(num1, num3);
571
572        let obj = Amf0TypedValue::Object(ObjectType::new(sample_properties()));
573        let bool_val = Amf0TypedValue::Boolean(BooleanType::new(false));
574        assert_ne!(obj, bool_val);
575    }
576
577    // Tests for NestedType (ObjectType and EcmaArrayType)
578    #[test]
579    fn test_object_type() {
580        let props = sample_properties();
581        let original = ObjectType::new(props);
582        let marshalled = original.marshall().unwrap();
583        let (unmarshalled, _) = ObjectType::unmarshall(&marshalled).unwrap();
584        assert_eq!(original, unmarshalled);
585    }
586
587    #[test]
588    fn test_ecma_array_type() {
589        let props = sample_properties();
590        let original = EcmaArrayType::new(props);
591        let marshalled = original.marshall().unwrap();
592        let (unmarshalled, _) = EcmaArrayType::unmarshall(&marshalled).unwrap();
593        assert_eq!(original, unmarshalled);
594    }
595
596    #[test]
597    fn test_nested_type_clone() {
598        let original = ObjectType::new(sample_properties());
599        let cloned = original.clone();
600        assert_eq!(original, cloned);
601    }
602
603    #[test]
604    fn test_nested_type_partial_eq() {
605        let props1 = sample_properties();
606        let obj1 = ObjectType::new(props1.clone());
607        let obj2 = ObjectType::new(props1);
608        assert_eq!(obj1, obj2);
609
610        let mut props2 = IndexMap::new();
611        props2.insert(
612            Utf8::try_from("key1").unwrap(),
613            Amf0TypedValue::Number(NumberType::new(43.0)),
614        );
615        let obj3 = ObjectType::new(props2);
616        assert_ne!(obj1, obj3);
617    }
618
619    // Error case tests
620    #[test]
621    fn test_unmarshall_invalid_type_marker() {
622        let buf = [0xff]; // Invalid type marker
623        let result = Amf0TypedValue::unmarshall(&buf);
624        assert!(result.is_err());
625    }
626
627    #[test]
628    fn test_nested_type_buffer_too_small() {
629        let buf = [TypeMarker::Object as u8];
630        let result = ObjectType::unmarshall(&buf);
631        assert!(matches!(result, Err(AmfError::BufferTooSmall { .. })));
632    }
633}