1use rmp::decode::{read_bin_len, read_int, read_map_len, read_str_from_slice};
5use serde::Serialize;
6
7use super::{GameDataEncoding, PlayerId};
8
9#[derive(Debug, Serialize, PartialEq, Eq)]
11pub struct V3BinaryGameDataFrame {
12 pub from_player: PlayerId,
13 pub encoding: GameDataEncoding,
14 #[serde(with = "serde_bytes")]
15 pub payload: Vec<u8>,
16 pub seq: u64,
17 pub epoch: u32,
18}
19
20#[derive(Debug, Serialize, PartialEq, Eq)]
22pub struct V2BinaryGameDataFrame {
23 pub from_player: PlayerId,
24 pub encoding: GameDataEncoding,
25 #[serde(with = "serde_bytes")]
26 pub payload: Vec<u8>,
27}
28
29pub fn decode_v2_binary_game_data(wire: &[u8]) -> Result<V2BinaryGameDataFrame, String> {
31 let mut remaining = wire;
32 let field_count = read_map_len(&mut remaining)
33 .map_err(|error| format!("v2 binary GameData envelope is not a map: {error}"))?;
34 let mut from_player = None;
35 let mut encoding = None;
36 let mut payload = None;
37
38 for _ in 0..field_count {
39 let key = read_string(&mut remaining, "envelope key", "v2")?;
40 match key {
41 "from_player" => {
42 reject_duplicate(&from_player, key, "v2")?;
43 let bytes = read_binary(&mut remaining, key, "v2")?;
44 let bytes: [u8; 16] = bytes.try_into().map_err(|_| {
45 "v2 binary GameData from_player must be a 16-byte binary UUID".to_string()
46 })?;
47 from_player = Some(PlayerId::from_bytes(bytes));
48 }
49 "encoding" => {
50 reject_duplicate(&encoding, key, "v2")?;
51 encoding = Some(match read_string(&mut remaining, key, "v2")? {
52 "message_pack" => GameDataEncoding::MessagePack,
53 value => {
54 return Err(format!(
55 "v2 binary GameData encoding must be message_pack, found {value:?}"
56 ));
57 }
58 });
59 }
60 "payload" => {
61 reject_duplicate(&payload, key, "v2")?;
62 payload = Some(read_binary(&mut remaining, key, "v2")?.to_vec());
63 }
64 unknown => {
65 return Err(format!(
66 "v2 binary GameData envelope contains unknown field {unknown:?}"
67 ));
68 }
69 }
70 }
71 if !remaining.is_empty() {
72 return Err("v2 binary GameData envelope contains trailing bytes".to_string());
73 }
74 Ok(V2BinaryGameDataFrame {
75 from_player: require_field(from_player, "from_player", "v2")?,
76 encoding: require_field(encoding, "encoding", "v2")?,
77 payload: require_field(payload, "payload", "v2")?,
78 })
79}
80
81pub fn decode_v3_binary_game_data(wire: &[u8]) -> Result<V3BinaryGameDataFrame, String> {
87 let mut remaining = wire;
88 let field_count = read_map_len(&mut remaining)
89 .map_err(|error| format!("v3 binary GameData envelope is not a map: {error}"))?;
90
91 let mut from_player = None;
92 let mut encoding = None;
93 let mut payload = None;
94 let mut seq = None;
95 let mut epoch = None;
96
97 for _ in 0..field_count {
98 let key = read_string(&mut remaining, "envelope key", "v3")?;
99 match key {
100 "from_player" => {
101 reject_duplicate(&from_player, key, "v3")?;
102 let bytes = read_binary(&mut remaining, key, "v3")?;
103 let bytes: [u8; 16] = bytes.try_into().map_err(|_| {
104 "v3 binary GameData from_player must be a 16-byte binary UUID".to_string()
105 })?;
106 from_player = Some(PlayerId::from_bytes(bytes));
107 }
108 "encoding" => {
109 reject_duplicate(&encoding, key, "v3")?;
110 encoding = Some(match read_string(&mut remaining, key, "v3")? {
111 "json" => GameDataEncoding::Json,
112 "message_pack" => GameDataEncoding::MessagePack,
113 "rkyv" => GameDataEncoding::Rkyv,
114 value => {
115 return Err(format!(
116 "v3 binary GameData encoding has unknown token {value:?}"
117 ));
118 }
119 });
120 }
121 "payload" => {
122 reject_duplicate(&payload, key, "v3")?;
123 payload = Some(read_binary(&mut remaining, key, "v3")?.to_vec());
124 }
125 "seq" => {
126 reject_duplicate(&seq, key, "v3")?;
127 let value: u64 = read_int(&mut remaining).map_err(|error| {
128 format!("v3 binary GameData seq is not a u64 integer: {error}")
129 })?;
130 if value == 0 {
131 return Err("v3 binary GameData seq must be non-zero".to_string());
132 }
133 seq = Some(value);
134 }
135 "epoch" => {
136 reject_duplicate(&epoch, key, "v3")?;
137 let value: u32 = read_int(&mut remaining).map_err(|error| {
138 format!("v3 binary GameData epoch is not a u32 integer: {error}")
139 })?;
140 if value == 0 {
141 return Err("v3 binary GameData epoch must be non-zero".to_string());
142 }
143 epoch = Some(value);
144 }
145 unknown => {
146 return Err(format!(
147 "v3 binary GameData envelope contains unknown field {unknown:?}"
148 ));
149 }
150 }
151 }
152
153 if !remaining.is_empty() {
154 return Err("v3 binary GameData envelope contains trailing bytes".to_string());
155 }
156
157 Ok(V3BinaryGameDataFrame {
158 from_player: require_field(from_player, "from_player", "v3")?,
159 encoding: require_field(encoding, "encoding", "v3")?,
160 payload: require_field(payload, "payload", "v3")?,
161 seq: require_field(seq, "seq", "v3")?,
162 epoch: require_field(epoch, "epoch", "v3")?,
163 })
164}
165
166fn read_string<'a>(
167 remaining: &mut &'a [u8],
168 field: &str,
169 version: &str,
170) -> Result<&'a str, String> {
171 let (value, tail) = read_str_from_slice(*remaining)
172 .map_err(|error| format!("{version} binary GameData {field} is not a string: {error}"))?;
173 *remaining = tail;
174 Ok(value)
175}
176
177fn read_binary<'a>(
178 remaining: &mut &'a [u8],
179 field: &str,
180 version: &str,
181) -> Result<&'a [u8], String> {
182 let len = read_bin_len(remaining).map_err(|error| {
183 format!("{version} binary GameData {field} is not binary data: {error}")
184 })?;
185 let len = usize::try_from(len)
186 .map_err(|_| format!("{version} binary GameData {field} length does not fit usize"))?;
187 if remaining.len() < len {
188 return Err(format!(
189 "{version} binary GameData {field} is truncated: declared {len} bytes, found {}",
190 remaining.len()
191 ));
192 }
193 let (value, tail) = (*remaining).split_at(len);
194 *remaining = tail;
195 Ok(value)
196}
197
198fn reject_duplicate<T>(slot: &Option<T>, field: &str, version: &str) -> Result<(), String> {
199 if slot.is_some() {
200 Err(format!(
201 "{version} binary GameData envelope contains duplicate field {field:?}"
202 ))
203 } else {
204 Ok(())
205 }
206}
207
208fn require_field<T>(slot: Option<T>, field: &str, version: &str) -> Result<T, String> {
209 slot.ok_or_else(|| format!("{version} binary GameData envelope is missing field {field:?}"))
210}
211
212#[cfg(test)]
213#[allow(clippy::expect_used, clippy::indexing_slicing)]
214mod tests {
215 use super::*;
216
217 const PLAYER_ID: u128 = 0x00112233445566778899aabbccddeeff;
218
219 #[test]
220 fn decodes_every_opaque_payload_encoding() {
221 let from_player = PlayerId::from_u128(PLAYER_ID);
222 for encoding in [
223 GameDataEncoding::Json,
224 GameDataEncoding::MessagePack,
225 GameDataEncoding::Rkyv,
226 ] {
227 let expected = V3BinaryGameDataFrame {
228 from_player,
229 encoding,
230 payload: vec![0, 1, 2, 0xff],
231 seq: 9,
232 epoch: 3,
233 };
234 let wire = rmp_serde::to_vec_named(&expected).expect("serialize fixture");
235 assert_eq!(
236 decode_v3_binary_game_data(&wire).expect("decode v3 envelope"),
237 expected
238 );
239 }
240 }
241
242 #[test]
243 fn decodes_only_the_frozen_v2_message_pack_envelope() {
244 let expected = V2BinaryGameDataFrame {
245 from_player: PlayerId::from_u128(PLAYER_ID),
246 encoding: GameDataEncoding::MessagePack,
247 payload: vec![0, 1, 2, 0xff],
248 };
249 let canonical = rmp_serde::to_vec_named(&expected).expect("serialize v2 fixture");
250 assert_eq!(
251 decode_v2_binary_game_data(&canonical).expect("decode v2 envelope"),
252 expected
253 );
254
255 let positional = rmp_serde::to_vec(&(
256 PlayerId::from_u128(PLAYER_ID),
257 GameDataEncoding::MessagePack,
258 serde_bytes::ByteBuf::from(vec![1, 2]),
259 ))
260 .expect("serialize positional fixture");
261 assert!(decode_v2_binary_game_data(&positional).is_err());
262
263 let mut trailing = canonical.clone();
264 trailing.push(0);
265 assert!(decode_v2_binary_game_data(&trailing).is_err());
266
267 let v3 = V3BinaryGameDataFrame {
268 from_player: PlayerId::from_u128(PLAYER_ID),
269 encoding: GameDataEncoding::MessagePack,
270 payload: vec![1],
271 seq: 1,
272 epoch: 1,
273 };
274 assert!(decode_v2_binary_game_data(
275 &rmp_serde::to_vec_named(&v3).expect("serialize v3 fixture")
276 )
277 .is_err());
278 }
279
280 #[test]
281 fn rejects_noncanonical_message_pack() {
282 type Entry = (Vec<u8>, Vec<u8>);
283
284 fn encoded<T: Serialize + ?Sized>(value: &T) -> Vec<u8> {
285 rmp_serde::to_vec(value).expect("serialize fixture value")
286 }
287
288 fn valid_entries() -> Vec<Entry> {
289 vec![
290 (
291 encoded("from_player"),
292 encoded(&PlayerId::from_u128(PLAYER_ID)),
293 ),
294 (encoded("encoding"), encoded(&GameDataEncoding::Json)),
295 (
296 encoded("payload"),
297 encoded(&serde_bytes::Bytes::new(b"opaque")),
298 ),
299 (encoded("seq"), encoded(&9u64)),
300 (encoded("epoch"), encoded(&3u32)),
301 ]
302 }
303
304 fn map(entries: &[Entry]) -> Vec<u8> {
305 let mut wire = Vec::new();
306 rmp::encode::write_map_len(&mut wire, entries.len() as u32)
307 .expect("serialize fixture map length");
308 for (key, value) in entries {
309 wire.extend_from_slice(key);
310 wire.extend_from_slice(value);
311 }
312 wire
313 }
314
315 let canonical = map(&valid_entries());
316 assert!(decode_v3_binary_game_data(&canonical).is_ok());
317
318 let mut cases: Vec<(&str, Vec<u8>)> = vec![(
319 "positional array",
320 encoded(&(PlayerId::from_u128(PLAYER_ID), "json", b"opaque", 9, 3)),
321 )];
322
323 let mut entries = valid_entries();
324 entries[0].0 = encoded(&7u8);
325 cases.push(("non-string key", map(&entries)));
326
327 let mut entries = valid_entries();
328 entries[1].1 = encoded(&7u8);
329 cases.push(("numeric encoding", map(&entries)));
330
331 let mut entries = valid_entries();
332 entries[0].1 = encoded(&vec![0u8; 16]);
333 cases.push(("array UUID", map(&entries)));
334
335 let mut entries = valid_entries();
336 entries[0].1 = encoded(&serde_bytes::Bytes::new(&[0u8; 15]));
337 cases.push(("short binary UUID", map(&entries)));
338
339 let mut entries = valid_entries();
340 entries[2].1 = encoded(&vec![1u8, 2, 3]);
341 cases.push(("array payload", map(&entries)));
342
343 for missing in 0..5 {
344 let mut entries = valid_entries();
345 entries.remove(missing);
346 cases.push((
347 [
348 "missing from_player",
349 "missing encoding",
350 "missing payload",
351 "missing seq",
352 "missing epoch",
353 ][missing],
354 map(&entries),
355 ));
356 }
357
358 let mut entries = valid_entries();
359 entries.push(entries[3].clone());
360 cases.push(("duplicate key", map(&entries)));
361
362 let mut entries = valid_entries();
363 entries[4].0 = encoded("unexpected");
364 cases.push(("unknown key", map(&entries)));
365
366 let mut entries = valid_entries();
367 entries[3].1 = encoded(&0u8);
368 cases.push(("zero seq", map(&entries)));
369
370 let mut entries = valid_entries();
371 entries[4].1 = encoded(&0u8);
372 cases.push(("zero epoch", map(&entries)));
373
374 let mut entries = valid_entries();
375 entries[4].1 = encoded(&(u64::from(u32::MAX) + 1));
376 cases.push(("epoch overflow", map(&entries)));
377
378 cases.push(("truncated map", canonical[..canonical.len() - 1].to_vec()));
379
380 let mut trailing_scalar = canonical.clone();
381 trailing_scalar.extend(encoded(&1u8));
382 cases.push(("trailing scalar", trailing_scalar));
383
384 let mut concatenated_map = canonical.clone();
385 concatenated_map.extend(&canonical);
386 cases.push(("concatenated map", concatenated_map));
387
388 for (name, wire) in cases {
389 assert!(
390 decode_v3_binary_game_data(&wire).is_err(),
391 "noncanonical {name} envelope was accepted: {wire:?}"
392 );
393 }
394 }
395}