scuffle_amf0/
encode.rs

1use std::borrow::Cow;
2use std::io;
3
4use byteorder::{BigEndian, WriteBytesExt};
5
6use super::define::Amf0Marker;
7use super::{Amf0Value, Amf0WriteError};
8
9/// AMF0 encoder.
10///
11/// Allows for encoding an AMF0 to some writer.
12pub struct Amf0Encoder;
13
14impl Amf0Encoder {
15    /// Encode a generic AMF0 value
16    pub fn encode(writer: &mut impl io::Write, value: &Amf0Value) -> Result<(), Amf0WriteError> {
17        match value {
18            Amf0Value::Boolean(val) => Self::encode_bool(writer, *val),
19            Amf0Value::Null => Self::encode_null(writer),
20            Amf0Value::Number(val) => Self::encode_number(writer, *val),
21            Amf0Value::String(val) => Self::encode_string(writer, val),
22            Amf0Value::Object(val) => Self::encode_object(writer, val),
23            _ => Err(Amf0WriteError::UnsupportedType(value.marker())),
24        }
25    }
26
27    fn object_eof(writer: &mut impl io::Write) -> Result<(), Amf0WriteError> {
28        writer.write_u24::<BigEndian>(Amf0Marker::ObjectEnd as u32)?;
29        Ok(())
30    }
31
32    /// Encode an AMF0 number
33    pub fn encode_number(writer: &mut impl io::Write, value: f64) -> Result<(), Amf0WriteError> {
34        writer.write_u8(Amf0Marker::Number as u8)?;
35        writer.write_f64::<BigEndian>(value)?;
36        Ok(())
37    }
38
39    /// Encode an AMF0 boolean
40    pub fn encode_bool(writer: &mut impl io::Write, value: bool) -> Result<(), Amf0WriteError> {
41        writer.write_u8(Amf0Marker::Boolean as u8)?;
42        writer.write_u8(value as u8)?;
43        Ok(())
44    }
45
46    /// Encode an AMF0 string
47    pub fn encode_string(writer: &mut impl io::Write, value: &str) -> Result<(), Amf0WriteError> {
48        if value.len() > (u16::MAX as usize) {
49            return Err(Amf0WriteError::NormalStringTooLong);
50        }
51
52        writer.write_u8(Amf0Marker::String as u8)?;
53        writer.write_u16::<BigEndian>(value.len() as u16)?;
54        writer.write_all(value.as_bytes())?;
55        Ok(())
56    }
57
58    /// Encode an AMF0 null
59    pub fn encode_null(writer: &mut impl io::Write) -> Result<(), Amf0WriteError> {
60        writer.write_u8(Amf0Marker::Null as u8)?;
61        Ok(())
62    }
63
64    /// Encode an AMF0 object
65    pub fn encode_object(
66        writer: &mut impl io::Write,
67        properties: &[(Cow<'_, str>, Amf0Value<'_>)],
68    ) -> Result<(), Amf0WriteError> {
69        writer.write_u8(Amf0Marker::Object as u8)?;
70        for (key, value) in properties {
71            writer.write_u16::<BigEndian>(key.len() as u16)?;
72            writer.write_all(key.as_bytes())?;
73            Self::encode(writer, value)?;
74        }
75
76        Self::object_eof(writer)?;
77        Ok(())
78    }
79}
80
81#[cfg(test)]
82#[cfg_attr(all(test, coverage_nightly), coverage(off))]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_write_number() {
88        let mut amf0_number = vec![0x00];
89        amf0_number.extend_from_slice(&772.161_f64.to_be_bytes());
90
91        let mut vec = Vec::<u8>::new();
92
93        Amf0Encoder::encode_number(&mut vec, 772.161).unwrap();
94
95        assert_eq!(vec, amf0_number);
96    }
97
98    #[test]
99    fn test_write_boolean() {
100        let amf0_boolean = vec![0x01, 0x01];
101
102        let mut vec = Vec::<u8>::new();
103
104        Amf0Encoder::encode_bool(&mut vec, true).unwrap();
105
106        assert_eq!(vec, amf0_boolean);
107    }
108
109    #[test]
110    fn test_write_string() {
111        let mut amf0_string = vec![0x02, 0x00, 0x0b];
112        amf0_string.extend_from_slice(b"Hello World");
113
114        let mut vec = Vec::<u8>::new();
115
116        Amf0Encoder::encode_string(&mut vec, "Hello World").unwrap();
117
118        assert_eq!(vec, amf0_string);
119    }
120
121    #[test]
122    fn test_write_null() {
123        let amf0_null = vec![0x05];
124
125        let mut vec = Vec::<u8>::new();
126
127        Amf0Encoder::encode_null(&mut vec).unwrap();
128
129        assert_eq!(vec, amf0_null);
130    }
131
132    #[test]
133    fn test_write_object() {
134        let mut amf0_object = vec![0x03, 0x00, 0x04];
135        amf0_object.extend_from_slice(b"test");
136        amf0_object.extend_from_slice(&[0x05]);
137        amf0_object.extend_from_slice(&[0x00, 0x00, 0x09]);
138
139        let mut vec = Vec::<u8>::new();
140
141        Amf0Encoder::encode_object(&mut vec, &[("test".into(), Amf0Value::Null)]).unwrap();
142
143        assert_eq!(vec, amf0_object);
144    }
145
146    #[test]
147    fn test_encode_boolean() {
148        let amf0_boolean_true = vec![Amf0Marker::Boolean as u8, 0x01];
149        let amf0_boolean_false = vec![Amf0Marker::Boolean as u8, 0x00];
150
151        let mut vec_true = Vec::<u8>::new();
152        let mut vec_false = Vec::<u8>::new();
153
154        Amf0Encoder::encode(&mut vec_true, &Amf0Value::Boolean(true)).unwrap();
155        assert_eq!(vec_true, amf0_boolean_true);
156        Amf0Encoder::encode(&mut vec_false, &Amf0Value::Boolean(false)).unwrap();
157        assert_eq!(vec_false, amf0_boolean_false);
158    }
159
160    #[test]
161    fn test_encode_object() {
162        let mut amf0_object = vec![Amf0Marker::Object as u8, 0x00, 0x04];
163        amf0_object.extend_from_slice(b"test");
164        amf0_object.push(Amf0Marker::Null as u8);
165        amf0_object.extend_from_slice(&[0x00, 0x00, 0x09]);
166        let mut vec = Vec::<u8>::new();
167
168        Amf0Encoder::encode(&mut vec, &Amf0Value::Object(vec![("test".into(), Amf0Value::Null)].into())).unwrap();
169        assert_eq!(vec, amf0_object);
170    }
171
172    #[test]
173    fn test_encode_generic_error_unsupported_type() {
174        let mut writer = Vec::<u8>::new();
175        let unsupported_marker = Amf0Value::ObjectEnd;
176        let result = Amf0Encoder::encode(&mut writer, &unsupported_marker);
177        assert!(matches!(result, Err(Amf0WriteError::UnsupportedType(_))));
178    }
179
180    #[test]
181    fn test_encode_string_too_long() {
182        let long_string = "a".repeat(u16::MAX as usize + 1);
183        let mut writer = Vec::<u8>::new();
184        let result = Amf0Encoder::encode_string(&mut writer, &long_string);
185        assert!(matches!(result, Err(Amf0WriteError::NormalStringTooLong)));
186    }
187}