1use std::io::Write;
2
3use crate::{
4 value::{ArrayKey, SessionEntry, Value},
5 ObjectPropertyVisibility,
6};
7
8pub fn serialize<W: Write>(w: &mut W, value: &Value) -> std::result::Result<usize, std::io::Error> {
14 match value {
15 Value::Null => w.write(b"N;"),
16 Value::Boolean(false) => w.write(b"b:0;"),
17 Value::Boolean(true) => w.write(b"b:1;"),
18 Value::Integer(n) => w.write(format!("i:{n};").as_bytes()),
19 Value::Decimal(d) => {
20 if d.is_nan() {
21 w.write(b"d:NAN;")
22 } else if d.is_infinite() {
23 if d.is_sign_positive() {
24 w.write(b"d:INF;")
25 } else {
26 w.write(b"d:-INF;")
27 }
28 } else {
29 w.write(format!("d:{d};").as_bytes())
30 }
31 }
32 Value::String(string) => {
33 let mut count = 0;
34 count += w.write(format!("s:{}:\"", string.len()).as_bytes())?;
35 count += w.write(string)?;
36 count += w.write(b"\";")?;
37 Ok(count)
38 }
39 Value::Array(items) => {
40 let mut count = 0;
41 count += w.write(format!("a:{}:{{", items.len()).as_bytes())?;
42 for (key, value) in items {
43 match key {
44 ArrayKey::Integer(key) => {
45 count += w.write(format!("i:{key};").as_bytes())?;
46 }
47 ArrayKey::String(key) => {
48 count += w.write(format!("s:{}:\"", key.len()).as_bytes())?;
49 count += w.write(key)?;
50 count += w.write(b"\";")?;
51 }
52 }
53 count += serialize(w, value)?;
54 }
55 count += w.write(b"}")?;
56 Ok(count)
57 }
58 Value::ValueReference(idx) => w.write(format!("R:{idx};").as_bytes()),
59 Value::ObjectReference(idx) => w.write(format!("r:{idx};").as_bytes()),
60 Value::Object {
61 class_name,
62 properties,
63 } => {
64 let mut count = 0;
65 count += w.write(format!("O:{}:\"", class_name.len()).as_bytes())?;
66 count += w.write(class_name)?;
67 count += w.write(format!("\":{}:{{", properties.len()).as_bytes())?;
68 for property in properties {
69 use ObjectPropertyVisibility::{Private, Protected, Public};
70
71 match property.visibility {
72 Public => {
73 count += w.write(format!("s:{}:\"", property.name.len()).as_bytes())?;
74 }
75 Protected => {
76 count +=
77 w.write(format!("s:{}:\"\0*\0", property.name.len() + 3).as_bytes())?;
78 }
79 Private => {
80 count += w.write(
81 format!("s:{}:\"\0", property.name.len() + 2 + class_name.len())
82 .as_bytes(),
83 )?;
84 count += w.write(class_name)?;
85 count += w.write(b"\0")?;
86 }
87 }
88 count += w.write(property.name)?;
89 count += w.write(b"\";")?;
90 count += serialize(w, &property.value)?;
91 }
92 count += w.write(b"}")?;
93 Ok(count)
94 }
95 Value::CustomObject { class_name, data } => {
96 let mut count = 0;
97 count += w.write(format!("C:{}:\"", class_name.len()).as_bytes())?;
98 count += w.write(class_name)?;
99 count += w.write(format!("\":{}:{{", data.len()).as_bytes())?;
100 count += w.write(data)?;
101 count += w.write(b"}")?;
102 Ok(count)
103 }
104 }
105}
106
107pub fn session_encode<W: Write>(
113 w: &mut W,
114 session: &[SessionEntry],
115) -> std::result::Result<usize, std::io::Error> {
116 let mut count = 0;
117 for entry in session {
118 count += w.write(entry.key)?;
119 count += w.write(b"|")?;
120 count += serialize(w, &entry.value)?;
121 }
122 Ok(count)
123}
124
125#[cfg(test)]
126mod tests {
127 use std::num::NonZeroUsize;
128
129 use crate::ObjectProperty;
130
131 use super::*;
132
133 fn run_encode_cases(cases: &[(Value, &[u8])]) {
134 let mut buffer = Vec::<u8>::new();
135 for (input, expected) in cases {
136 buffer.clear();
137 let count = serialize(&mut buffer, input).unwrap();
138 assert_eq!(&buffer.as_slice(), expected);
139 assert_eq!(count, expected.len());
140 }
141 }
142
143 fn run_session_encode_cases(cases: &[(Vec<SessionEntry>, &[u8])]) {
144 let mut buffer = Vec::<u8>::new();
145 for (input, expected) in cases {
146 buffer.clear();
147 let count = session_encode(&mut buffer, input).unwrap();
148 assert_eq!(&buffer.as_slice(), expected);
149 assert_eq!(count, expected.len());
150 }
151 }
152
153 #[test]
154 fn encode_value_null() {
155 let mut buffer = Vec::<u8>::new();
156 serialize(&mut buffer, &Value::Null).unwrap();
157 assert_eq!(buffer.as_slice(), b"N;");
158 }
159
160 #[test]
161 fn encode_value_boolean() {
162 let cases = [
163 (Value::Boolean(false), b"b:0;".as_slice()),
164 (Value::Boolean(true), b"b:1;".as_slice()),
165 ];
166 run_encode_cases(&cases);
167 }
168
169 #[test]
170 fn encode_value_integer() {
171 let cases = [
172 (Value::Integer(0), b"i:0;".as_slice()),
173 (Value::Integer(123), b"i:123;".as_slice()),
174 (Value::Integer(-23), b"i:-23;".as_slice()),
175 (Value::Integer(-23_432_123), b"i:-23432123;".as_slice()),
176 ];
177 run_encode_cases(&cases);
178 }
179
180 #[test]
181 fn encode_value_decimal() {
182 let cases = [
183 (Value::Decimal(0.0), b"d:0;".as_slice()),
184 (Value::Decimal(0.2), b"d:0.2;".as_slice()),
185 (Value::Decimal(-0.2), b"d:-0.2;".as_slice()),
186 (Value::Decimal(f64::NAN), b"d:NAN;".as_slice()),
187 (Value::Decimal(f64::INFINITY), b"d:INF;".as_slice()),
188 (Value::Decimal(f64::NEG_INFINITY), b"d:-INF;".as_slice()),
189 ];
190 run_encode_cases(&cases);
191 }
192
193 #[test]
194 fn encode_value_string() {
195 let cases = [
196 (Value::String(b"".as_slice()), b"s:0:\"\";".as_slice()),
197 (Value::String(b"foo".as_slice()), b"s:3:\"foo\";".as_slice()),
198 ];
199 run_encode_cases(&cases);
200 }
201
202 #[test]
203 fn encode_value_references() {
204 let cases = [
205 (
206 Value::ValueReference(NonZeroUsize::new(10).unwrap()),
207 b"R:10;".as_slice(),
208 ),
209 (
210 Value::ObjectReference(NonZeroUsize::new(42).unwrap()),
211 b"r:42;".as_slice(),
212 ),
213 ];
214 run_encode_cases(&cases);
215 }
216
217 #[test]
218 fn encode_object() {
219 let cases = [
220 (
221 Value::Object {
222 class_name: b"Test".as_slice(),
223 properties: vec![
224 ObjectProperty {
225 name: b"public".as_slice(),
226 visibility: ObjectPropertyVisibility::Public,
227 value: Value::Integer(1),
228 },
229 ObjectProperty {
230 name: b"protected".as_slice(),
231 visibility: ObjectPropertyVisibility::Protected,
232 value: Value::Integer(2),
233 },
234 ObjectProperty {
235 name: b"private".as_slice(),
236 visibility: ObjectPropertyVisibility::Private,
237 value: Value::Integer(3),
238 },
239 ],
240 },
241 b"O:4:\"Test\":3:{s:6:\"public\";i:1;s:12:\"\0*\0protected\";i:2;s:13:\"\0Test\0private\";i:3;}".as_slice(),
242 ),
243 (
244 Value::ObjectReference(NonZeroUsize::new(42).unwrap()),
245 b"r:42;".as_slice(),
246 ),
247 ];
248 run_encode_cases(&cases);
249 }
250
251 #[test]
252 fn encode_custom_object() {
253 let cases = [(
254 Value::CustomObject {
255 class_name: b"CustomSerializableClass".as_slice(),
256 data: b"foobar".as_slice(),
257 },
258 b"C:23:\"CustomSerializableClass\":6:{foobar}".as_slice(),
259 )];
260 run_encode_cases(&cases);
261 }
262
263 #[test]
264 fn encode_value_array() {
265 let cases = [
266 (Value::Array(vec![]), b"a:0:{}".as_slice()),
267 (
268 Value::Array(vec![(ArrayKey::String(b"foo"), Value::String(b"bar"))]),
269 b"a:1:{s:3:\"foo\";s:3:\"bar\";}".as_slice(),
270 ),
271 (
272 Value::Array(vec![(
273 ArrayKey::Integer(0),
274 Value::Array(vec![(
275 ArrayKey::Integer(0),
276 Value::Array(vec![(ArrayKey::Integer(0), Value::Array(vec![]))]),
277 )]),
278 )]),
279 b"a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}".as_slice(),
280 ),
281 (
282 Value::Array(vec![
283 (ArrayKey::Integer(0), Value::Integer(1)),
284 (ArrayKey::Integer(1), Value::Integer(1)),
285 (ArrayKey::Integer(2), Value::Integer(2)),
286 (ArrayKey::Integer(3), Value::Integer(3)),
287 (ArrayKey::Integer(4), Value::Integer(5)),
288 ]),
289 b"a:5:{i:0;i:1;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:5;}".as_slice(),
290 ),
291 ];
292 run_encode_cases(&cases);
293 }
294
295 #[test]
296 fn encode_session() {
297 let cases = [
298 (vec![], b"".as_slice()),
299 (
300 vec![SessionEntry {
301 key: b"foo",
302 value: Value::Integer(42),
303 }],
304 b"foo|i:42;".as_slice(),
305 ),
306 (
307 vec![
308 SessionEntry {
309 key: b"foo",
310 value: Value::Integer(42),
311 },
312 SessionEntry {
313 key: b"bar",
314 value: Value::String(b"baz".as_slice()),
315 },
316 ],
317 b"foo|i:42;bar|s:3:\"baz\";".as_slice(),
318 ),
319 (
320 vec![
321 SessionEntry {
322 key: b"foo",
323 value: Value::Integer(42),
324 },
325 SessionEntry {
326 key: b"bar",
327 value: Value::String(b"baz|qux".as_slice()),
328 },
329 SessionEntry {
330 key: b"pub",
331 value: Value::Integer(1337),
332 },
333 ],
334 b"foo|i:42;bar|s:7:\"baz|qux\";pub|i:1337;".as_slice(),
335 ),
336 ];
337 run_session_encode_cases(&cases);
338 }
339}