Skip to main content

skrillax_codec/
lib.rs

1//! `skrillax-codec` is a crate to turn a raw stream of bytes into more
2//! meaningful frames in the format used by Silkroad Online. Framing is only the
3//! first step, as a frame is still quite a general object and does itself not
4//! provide many operations. Instead, operations are contained inside frames and
5//! will need to be decoded/encoded separately.
6//!
7//! This crate provides two things: the [SilkroadFrame] and [SilkroadCodec]. The
8//! latter, [SilkroadCodec], is expected to be used in combination with tokio's
9//! [tokio_util::codec::FramedWrite] & [tokio_util::codec::FramedRead]. It uses
10//! the former, [SilkroadFrame], as the type it produces. However, it is totally
11//! possible to use this crate without using the codec by using the
12//! [SilkroadFrame]'s serialization and deserialization functions.
13
14use byteorder::{ByteOrder, LittleEndian};
15#[cfg(feature = "codec")]
16use bytes::Buf;
17use bytes::{BufMut, Bytes, BytesMut};
18use thiserror::Error;
19
20/// An error encountered while encoding a [`SilkroadFrame`].
21#[derive(Clone, Debug, Eq, Error, PartialEq)]
22#[non_exhaustive]
23pub enum FrameEncodeError {
24    /// A frame's content length cannot fit in the 15-bit wire field.
25    #[error("frame content has {actual} bytes, but the maximum is {maximum}")]
26    ContentTooLarge { actual: usize, maximum: usize },
27    /// Encrypted bytes do not match the block-aligned size declared by the
28    /// frame.
29    #[error(
30        "encrypted content size {content_size} requires {expected} encrypted bytes, but {actual} \
31         were provided"
32    )]
33    EncryptedDataLengthMismatch {
34        content_size: usize,
35        expected: usize,
36        actual: usize,
37    },
38}
39
40/// An error encountered while parsing bytes into a [`SilkroadFrame`].
41#[derive(Clone, Debug, Eq, Error, PartialEq)]
42pub enum FrameParseError {
43    /// The length prefix or declared frame body has not been received in full.
44    #[error("frame requires at least {additional} additional byte(s)")]
45    Incomplete { additional: usize },
46    /// Frame data does not contain the opcode, security count, and CRC fields.
47    #[error("frame data requires at least 4 bytes, but only {actual} were provided")]
48    FrameTooShort { actual: usize },
49    /// A massive frame does not contain its required mode byte.
50    #[error("massive frame is missing its mode byte")]
51    MissingMassiveMode,
52    /// A massive frame contains a mode outside the protocol-defined values.
53    #[error("invalid massive frame mode {mode}")]
54    InvalidMassiveMode { mode: u8 },
55    /// A massive header does not contain all of its required fields.
56    #[error("massive header requires at least 10 bytes, but only {actual} were provided")]
57    MassiveHeaderTooShort { actual: usize },
58}
59
60/// Opcode reserved for massive packet headers and containers.
61pub const MASSIVE_PACKET_OPCODE: u16 = 0x600D;
62/// Mode byte identifying a massive packet header.
63pub const MASSIVE_HEADER_MODE: u8 = 1;
64/// Mode byte identifying a massive packet data container.
65pub const MASSIVE_CONTAINER_MODE: u8 = 0;
66const ENCRYPTED_FRAME_FLAG: u16 = 0x8000;
67const FRAME_CONTENT_LENGTH_MASK: u16 = 0x7FFF;
68const ENCRYPTED_ALIGNMENT: usize = 8;
69
70/// The largest content length representable without setting the encrypted-frame
71/// flag.
72pub const MAX_FRAME_CONTENT_SIZE: usize = FRAME_CONTENT_LENGTH_MASK as usize;
73/// The largest payload a massive container can carry after its one-byte mode
74/// field.
75pub const MAX_MASSIVE_CONTAINER_INNER_SIZE: usize = MAX_FRAME_CONTENT_SIZE - 1;
76
77/// A frame content length proven to fit in the protocol's 15-bit wire field.
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct FrameContentSize(u16);
80
81impl TryFrom<usize> for FrameContentSize {
82    type Error = FrameEncodeError;
83
84    fn try_from(value: usize) -> Result<Self, Self::Error> {
85        if value > MAX_FRAME_CONTENT_SIZE {
86            return Err(FrameEncodeError::ContentTooLarge {
87                actual: value,
88                maximum: MAX_FRAME_CONTENT_SIZE,
89            });
90        }
91
92        let wire_value = u16::try_from(value).map_err(|_| FrameEncodeError::ContentTooLarge {
93            actual: value,
94            maximum: MAX_FRAME_CONTENT_SIZE,
95        })?;
96        Ok(Self(wire_value))
97    }
98}
99
100impl FrameContentSize {
101    /// Returns the checked wire value.
102    pub const fn get(self) -> u16 {
103        self.0
104    }
105
106    /// Returns the checked value as a platform-sized integer.
107    pub const fn as_usize(self) -> usize {
108        self.0 as usize
109    }
110
111    /// Returns the wire value with the encrypted-frame marker set.
112    pub const fn encrypted_wire_value(self) -> u16 {
113        self.0 | ENCRYPTED_FRAME_FLAG
114    }
115
116    /// Returns the block-aligned ciphertext length for this content size.
117    pub fn encrypted_data_len(self) -> usize {
118        find_encrypted_length(self.as_usize() + 4)
119    }
120}
121
122/// Find the nearest block-aligned length.
123///
124/// Given the current length of data to encrypt, calculates the length of the
125/// encrypted output, which includes padding. Can at most increase by
126/// `ENCRYPTED_ALIGNMENT - 1`, which is `7`.
127fn find_encrypted_length(given_length: usize) -> usize {
128    let aligned_length = given_length % ENCRYPTED_ALIGNMENT;
129    if aligned_length == 0 {
130        // Already block-aligned, no need to pad
131        return given_length;
132    }
133
134    given_length + (8 - aligned_length) // Add padding
135}
136
137/// A 'frame' denotes the most fundamental block of data that can be sent
138/// between the client and the server in Silkroad Online. Any and all operations
139/// or data exchanges are built on top of something like a frame.
140///
141/// There are two categories of frames; normal frames and massive frames. A
142/// normal frame is the most common frame denoting a single operation using a
143/// specified opcode. This frame may be encrypted, causing everything but the
144/// length to require decrypting before being usable. Massive frames are used to
145/// carry a larger logical payload. A massive header is sent first, containing
146/// the number of following container frames as well as the logical packet's
147/// opcode, and is then followed by the specified number of containers, which
148/// contain the data. The container count does not describe how many application
149/// objects can be deserialized from that data. Massive frames cannot be
150/// encrypted.
151///
152/// Every frame, including an encrypted frame, contains two additional bytes:
153/// a crc checksum and a cryptographically random count. The former is used
154/// to check for bitflips/modifications and the count to prevent replay
155/// attacks.
156///
157/// To read a frame from a byte-stream, you can use the [SilkroadFrame::parse]
158/// function to try and parse a frame from those bytes:
159/// ```
160/// # use bytes::Bytes;
161/// # use skrillax_codec::SilkroadFrame;
162/// let (_, frame) = SilkroadFrame::parse(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap();
163/// assert_eq!(
164///     frame,
165///     SilkroadFrame::Packet {
166///         count: 0,
167///         crc: 0,
168///         opcode: 1,
169///         data: Bytes::new(),
170///     }
171/// );
172/// ```
173///
174/// This works vice versa, to write a frame into a byte stream, using
175/// [SilkroadFrame::serialize]:
176/// ```
177/// # use bytes::Bytes;
178/// # use skrillax_codec::SilkroadFrame;
179/// let bytes = SilkroadFrame::Packet {
180///     count: 0,
181///     crc: 0,
182///     opcode: 1,
183///     data: Bytes::new(),
184/// }
185/// .serialize()
186/// .expect("the frame is representable");
187/// assert_eq!(bytes.as_ref(), &[0x00, 0x00, 0x01, 0x00, 0x00, 0x00]);
188/// ```
189#[derive(Eq, PartialEq, Debug)]
190pub enum SilkroadFrame {
191    /// The most basic frame containing exactly one operation identified
192    /// by its opcode.
193    Packet {
194        count: u8,
195        crc: u8,
196        opcode: u16,
197        data: Bytes,
198    },
199    /// A [SilkroadFrame::Packet] which is, however, still encrypted. This
200    /// contains the encrypted data and will first need to be decrypted (for
201    /// example, using the `skrillax-security` crate).
202    Encrypted {
203        content_size: usize,
204        encrypted_data: Bytes,
205    },
206    /// The header portion of a massive packet which contains information
207    /// that is necessary for the identification and usage of the following
208    /// [SilkroadFrame::MassiveContainer] frame(s). `contained_count` is the
209    /// number of following container frames, not a count of application
210    /// objects.
211    MassiveHeader {
212        count: u8,
213        crc: u8,
214        contained_opcode: u16,
215        contained_count: u16,
216    },
217    /// The data container portion of a massive packet. Must come after
218    /// a [SilkroadFrame::MassiveHeader] and contributes data to the logical
219    /// payload identified by that header.
220    MassiveContainer { count: u8, crc: u8, inner: Bytes },
221}
222
223impl SilkroadFrame {
224    /// Tries to parse the first possible frame from the given data slice.
225    /// In addition to the created frame, it will also return the size of
226    /// consumed bytes by the frame. If not enough data is available, it
227    /// returns [`FrameParseError::Incomplete`]. The returned value in
228    /// `FrameParseError::Incomplete` is not guaranteed to be the exact
229    /// necessary bytes; it is possible to get it multiple times, even
230    /// when ensuring at least the amount of returned requested bytes were
231    /// added. This is because if we _need_ the header info first and
232    /// only then can we know the expected size.
233    /// Complete but malformed frames return a structural parsing error.
234    pub fn parse(data: &[u8]) -> Result<(usize, SilkroadFrame), FrameParseError> {
235        if data.len() < 2 {
236            return Err(FrameParseError::Incomplete {
237                additional: 2 - data.len(),
238            });
239        }
240
241        let length = LittleEndian::read_u16(&data[0..2]);
242        let encrypted = length & ENCRYPTED_FRAME_FLAG != 0;
243        let content_size = (length & FRAME_CONTENT_LENGTH_MASK) as usize;
244        let total_size = if encrypted {
245            find_encrypted_length(content_size + 4)
246        } else {
247            content_size + 4
248        };
249
250        let data = &data[2..];
251        if data.len() < total_size {
252            return Err(FrameParseError::Incomplete {
253                additional: total_size - data.len(),
254            });
255        }
256
257        let total_consumed = total_size + 2;
258        let data = Bytes::copy_from_slice(&data[0..total_size]);
259        if encrypted {
260            return Ok((
261                total_consumed,
262                SilkroadFrame::Encrypted {
263                    content_size,
264                    encrypted_data: data,
265                },
266            ));
267        }
268
269        Ok((total_consumed, Self::from_data(&data)?))
270    }
271
272    /// Creates a [SilkroadFrame] given the received data. Generally, this will
273    /// result in a [SilkroadFrame::Packet], unless we encounter a packet
274    /// with the opcode `0x600D`, which is reserved for a massive packet,
275    /// consisting of a [SilkroadFrame::MassiveHeader] and multiple
276    /// [SilkroadFrame::MassiveContainer]s.
277    ///
278    /// The data must contain the two-byte opcode, one-byte security count,
279    /// one-byte CRC, and any frame-specific fields. Malformed data is returned
280    /// as a typed error.
281    pub fn from_data(data: &[u8]) -> Result<SilkroadFrame, FrameParseError> {
282        if data.len() < 4 {
283            return Err(FrameParseError::FrameTooShort { actual: data.len() });
284        }
285
286        let opcode = LittleEndian::read_u16(&data[0..2]);
287        let count = data[2];
288        let crc = data[3];
289
290        if opcode == MASSIVE_PACKET_OPCODE {
291            let mode = *data.get(4).ok_or(FrameParseError::MissingMassiveMode)?;
292            match mode {
293                MASSIVE_HEADER_MODE => {
294                    if data.len() < 10 {
295                        return Err(FrameParseError::MassiveHeaderTooShort { actual: data.len() });
296                    }
297                    let inner_amount = LittleEndian::read_u16(&data[5..7]);
298                    let inner_opcode = LittleEndian::read_u16(&data[7..9]);
299                    Ok(SilkroadFrame::MassiveHeader {
300                        count,
301                        crc,
302                        contained_opcode: inner_opcode,
303                        contained_count: inner_amount,
304                    })
305                },
306                MASSIVE_CONTAINER_MODE => Ok(SilkroadFrame::MassiveContainer {
307                    count,
308                    crc,
309                    inner: Bytes::copy_from_slice(&data[5..]),
310                }),
311                mode => Err(FrameParseError::InvalidMassiveMode { mode }),
312            }
313        } else {
314            Ok(SilkroadFrame::Packet {
315                count,
316                crc,
317                opcode,
318                data: Bytes::copy_from_slice(&data[4..]),
319            })
320        }
321    }
322
323    /// Computes the value represented by the 15-bit length header field.
324    /// Depending on the type of frame, this is either:
325    /// - The application payload size (basic frame)
326    /// - The unpadded application payload size, excluding the encrypted opcode,
327    ///   count, and CRC fields (encrypted frame)
328    /// - A fixed size (massive header frame)
329    /// - The mode byte and payload size (massive container frame)
330    pub fn content_size(&self) -> Result<FrameContentSize, FrameEncodeError> {
331        let size = match &self {
332            SilkroadFrame::Packet { data, .. } => data.len(),
333            SilkroadFrame::Encrypted { content_size, .. } => *content_size,
334            SilkroadFrame::MassiveHeader { .. } => {
335                // Massive headers have a fixed length because they're always:
336                // 1 Byte 'is header', 2 Bytes 'number of packets', 2 Bytes 'opcode', 1 Byte
337                // unknown
338                6
339            },
340            SilkroadFrame::MassiveContainer { inner, .. } => {
341                inner
342                    .len()
343                    .checked_add(1)
344                    .ok_or(FrameEncodeError::ContentTooLarge {
345                        actual: usize::MAX,
346                        maximum: MAX_FRAME_CONTENT_SIZE,
347                    })?
348            },
349        };
350
351        FrameContentSize::try_from(size)
352    }
353
354    fn validated_content_size(&self) -> Result<FrameContentSize, FrameEncodeError> {
355        let content_size = self.content_size()?;
356        if let SilkroadFrame::Encrypted {
357            content_size: declared_content_size,
358            encrypted_data,
359        } = self
360        {
361            let expected = content_size.encrypted_data_len();
362            if encrypted_data.len() != expected {
363                return Err(FrameEncodeError::EncryptedDataLengthMismatch {
364                    content_size: *declared_content_size,
365                    expected,
366                    actual: encrypted_data.len(),
367                });
368            }
369        }
370
371        Ok(content_size)
372    }
373
374    /// Validates that this frame has a representable length and coherent shape.
375    pub fn validate(&self) -> Result<(), FrameEncodeError> {
376        self.validated_content_size().map(|_| ())
377    }
378
379    /// Computes the total encoded size of this frame.
380    ///
381    /// This includes the length header and, for encrypted frames, the fixed
382    /// encrypted fields and block padding.
383    pub fn packet_size(&self) -> Result<usize, FrameEncodeError> {
384        let content_size = self.validated_content_size()?;
385        Ok(match self {
386            SilkroadFrame::Encrypted { .. } => content_size.encrypted_data_len() + 2,
387            _ => 6 + content_size.as_usize(),
388        })
389    }
390
391    /// Tries to fetch the opcode of the frame, unless the packet
392    /// is encrypted, which returns [None].
393    pub fn opcode(&self) -> Option<u16> {
394        match &self {
395            SilkroadFrame::Packet { opcode, .. } => Some(*opcode),
396            SilkroadFrame::Encrypted { .. } => None,
397            _ => Some(0x600D),
398        }
399    }
400
401    /// Tries to serialize this frame into a byte stream.
402    ///
403    /// Frames whose content length cannot be represented by the protocol's
404    /// 15-bit length field are rejected before any output is produced.
405    pub fn serialize(&self) -> Result<Bytes, FrameEncodeError> {
406        let content_size = self.validated_content_size()?;
407        let packet_size = match self {
408            SilkroadFrame::Encrypted { .. } => content_size.encrypted_data_len() + 2,
409            _ => 6 + content_size.as_usize(),
410        };
411        let mut output = BytesMut::with_capacity(packet_size);
412
413        match &self {
414            SilkroadFrame::Packet {
415                count,
416                crc,
417                opcode,
418                data,
419            } => {
420                output.put_u16_le(content_size.get());
421                output.put_u16_le(*opcode);
422                output.put_u8(*count);
423                output.put_u8(*crc);
424                output.put_slice(data);
425            },
426            SilkroadFrame::Encrypted { encrypted_data, .. } => {
427                output.put_u16_le(content_size.encrypted_wire_value());
428                output.put_slice(encrypted_data);
429            },
430            SilkroadFrame::MassiveHeader {
431                count,
432                crc,
433                contained_opcode,
434                contained_count,
435            } => {
436                output.put_u16_le(content_size.get());
437                output.put_u16_le(MASSIVE_PACKET_OPCODE);
438                output.put_u8(*count);
439                output.put_u8(*crc);
440                output.put_u8(MASSIVE_HEADER_MODE);
441                output.put_u16_le(*contained_count);
442                output.put_u16_le(*contained_opcode);
443                output.put_u8(0);
444            },
445            SilkroadFrame::MassiveContainer { count, crc, inner } => {
446                output.put_u16_le(content_size.get());
447                output.put_u16_le(MASSIVE_PACKET_OPCODE);
448                output.put_u8(*count);
449                output.put_u8(*crc);
450                output.put_u8(MASSIVE_CONTAINER_MODE);
451                output.put_slice(inner);
452            },
453        }
454
455        Ok(output.freeze())
456    }
457}
458
459#[cfg(feature = "codec")]
460pub use codec::*;
461
462#[cfg(feature = "codec")]
463mod codec {
464    use super::*;
465    use std::io;
466    use tokio_util::codec::{Decoder, Encoder};
467
468    /// A codec to read and write [SilkroadFrame] from/onto a byte stream.
469    /// This implements [Encoder] and [Decoder] to be used in combination
470    /// with tokio framed read/write. Essentially, this wraps the
471    /// [SilkroadFrame::serialize] and [SilkroadFrame::parse] functions
472    /// to serialize and deserialize the frames.
473    pub struct SilkroadCodec;
474
475    impl Encoder<SilkroadFrame> for SilkroadCodec {
476        type Error = io::Error;
477
478        fn encode(&mut self, item: SilkroadFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
479            let bytes = item
480                .serialize()
481                .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
482            dst.extend_from_slice(&bytes);
483            Ok(())
484        }
485    }
486
487    impl Decoder for SilkroadCodec {
488        type Item = SilkroadFrame;
489        type Error = io::Error;
490
491        fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
492            match SilkroadFrame::parse(src) {
493                Ok((bytes_read, frame)) => {
494                    src.advance(bytes_read);
495                    Ok(Some(frame))
496                },
497                Err(FrameParseError::Incomplete { .. }) => Ok(None),
498                Err(error) => Err(io::Error::new(io::ErrorKind::InvalidData, error)),
499            }
500        }
501    }
502}
503
504#[cfg(test)]
505mod test {
506    #[cfg(feature = "codec")]
507    use crate::SilkroadCodec;
508    use crate::{FrameEncodeError, FrameParseError, MAX_FRAME_CONTENT_SIZE, SilkroadFrame};
509    use bytes::Bytes;
510    #[cfg(feature = "codec")]
511    use bytes::BytesMut;
512    #[cfg(feature = "codec")]
513    use tokio_util::codec::{Decoder, Encoder};
514
515    #[test]
516    fn test_parse_empty() {
517        let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
518        let (consumed, packet) =
519            SilkroadFrame::parse(&data).expect("Should parse empty, valid data");
520        assert_eq!(6, consumed);
521        assert_eq!(
522            SilkroadFrame::Packet {
523                count: 0,
524                crc: 0,
525                opcode: 0,
526                data: Bytes::new(),
527            },
528            packet
529        );
530    }
531
532    #[test]
533    fn test_parse_incomplete() {
534        assert_eq!(
535            Err(FrameParseError::Incomplete { additional: 2 }),
536            SilkroadFrame::parse(&[])
537        );
538        assert_eq!(
539            Err(FrameParseError::Incomplete { additional: 1 }),
540            SilkroadFrame::parse(&[0x00])
541        );
542
543        let data = [0x00, 0x00, 0x00, 0x00, 0x00];
544        let res = SilkroadFrame::parse(&data);
545        assert_eq!(Err(FrameParseError::Incomplete { additional: 1 }), res);
546
547        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
548        let res = SilkroadFrame::parse(&data);
549        assert_eq!(Err(FrameParseError::Incomplete { additional: 1 }), res);
550    }
551
552    #[test]
553    fn test_parse_rejects_massive_frame_without_mode() {
554        let data = [0x00, 0x00, 0x0D, 0x60, 0x00, 0x00];
555
556        let result = SilkroadFrame::parse(&data);
557
558        assert_eq!(Err(FrameParseError::MissingMassiveMode), result);
559    }
560
561    #[test]
562    fn peer_massive_mode_outside_protocol_is_rejected() {
563        // A massive frame may only use mode 0 (container) or mode 1 (header).
564        // This peer frame uses mode 2 with one payload byte.
565        let wire = [0x02, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x02, 0xAA];
566
567        let result = SilkroadFrame::parse(&wire);
568
569        assert_eq!(Err(FrameParseError::InvalidMassiveMode { mode: 2 }), result);
570    }
571
572    #[test]
573    fn test_parse_rejects_truncated_massive_header() {
574        for content_size in 1usize..6 {
575            let mut data = vec![content_size as u8, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x01];
576            data.resize(6 + content_size, 0);
577
578            let result = SilkroadFrame::parse(&data);
579
580            assert_eq!(
581                Err(FrameParseError::MassiveHeaderTooShort {
582                    actual: 4 + content_size,
583                }),
584                result
585            );
586        }
587    }
588
589    #[test]
590    fn test_from_data_rejects_short_frame() {
591        for actual in 0..4 {
592            let data = [0; 3];
593
594            assert_eq!(
595                Err(FrameParseError::FrameTooShort { actual }),
596                SilkroadFrame::from_data(&data[..actual])
597            );
598        }
599    }
600
601    #[test]
602    fn test_parse_content() {
603        let data = [0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01];
604        let (consumed, packet) = SilkroadFrame::parse(&data).expect("Should parse valid data");
605        assert_eq!(8, consumed);
606        assert_eq!(
607            SilkroadFrame::Packet {
608                count: 0,
609                crc: 0,
610                opcode: 0x0001,
611                data: Bytes::from_static(&[0x01, 0x01]),
612            },
613            packet
614        );
615    }
616
617    #[test]
618    fn test_parse_encrypted() {
619        let data = [0x02, 0x80, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01];
620        let (consumed, packet) = SilkroadFrame::parse(&data).expect("Should parse valid data");
621        assert_eq!(10, consumed);
622        assert_eq!(
623            SilkroadFrame::Encrypted {
624                content_size: 2,
625                encrypted_data: Bytes::from_static(&[
626                    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
627                ]),
628            },
629            packet
630        );
631    }
632
633    #[test]
634    fn test_parse_massive() {
635        let header = [
636            0x06, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x01, 0x01, 0x00, 0x42, 0x00, 0x00,
637        ];
638        let (consumed, packet) = SilkroadFrame::parse(&header).expect("Should parse valid data");
639        assert_eq!(12, consumed);
640        assert_eq!(
641            SilkroadFrame::MassiveHeader {
642                count: 0,
643                crc: 0,
644                contained_opcode: 0x42,
645                contained_count: 1,
646            },
647            packet
648        );
649
650        let header = [0x02, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x00, 0x01];
651        let (consumed, packet) = SilkroadFrame::parse(&header).expect("Should parse valid data");
652        assert_eq!(8, consumed);
653        assert_eq!(
654            SilkroadFrame::MassiveContainer {
655                count: 0,
656                crc: 0,
657                inner: Bytes::from_static(&[0x01]),
658            },
659            packet
660        );
661    }
662
663    #[test]
664    fn massive_header_count_round_trip_preserves_full_range() {
665        for contained_count in [0, u16::MAX] {
666            let frame = SilkroadFrame::MassiveHeader {
667                count: 0,
668                crc: 0,
669                contained_opcode: 0x42,
670                contained_count,
671            };
672            let wire = frame
673                .serialize()
674                .expect("a massive header should be representable");
675
676            let (consumed, parsed) =
677                SilkroadFrame::parse(&wire).expect("a massive header count should round-trip");
678
679            assert_eq!(wire.len(), consumed);
680            assert_eq!(frame, parsed);
681        }
682    }
683
684    #[cfg(feature = "codec")]
685    #[test]
686    fn test_decoder() {
687        let mut codec = SilkroadCodec;
688        let mut buffer = BytesMut::new();
689        buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
690        let decoded = codec.decode(&mut buffer);
691        assert!(matches!(decoded, Ok(None)));
692
693        buffer.extend_from_slice(&[0x00, 0x00]);
694        let decoded = codec.decode_eof(&mut buffer).unwrap();
695        assert_eq!(
696            Some(SilkroadFrame::Packet {
697                count: 0,
698                crc: 0,
699                opcode: 0,
700                data: Bytes::new(),
701            }),
702            decoded
703        );
704    }
705
706    #[cfg(feature = "codec")]
707    #[test]
708    fn test_decoder_rejects_malformed_frame() {
709        let mut codec = SilkroadCodec;
710        let mut buffer = BytesMut::from(&[0x00, 0x00, 0x0D, 0x60, 0x00, 0x00][..]);
711
712        let error = codec
713            .decode(&mut buffer)
714            .expect_err("malformed frame should be rejected");
715
716        assert_eq!(std::io::ErrorKind::InvalidData, error.kind());
717    }
718
719    #[test]
720    fn test_serialize_empty() {
721        let data = SilkroadFrame::Packet {
722            count: 0,
723            crc: 0,
724            opcode: 0,
725            data: Bytes::new(),
726        }
727        .serialize()
728        .expect("an empty frame should be representable");
729        assert_eq!(data.as_ref(), &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
730    }
731
732    #[test]
733    fn maximum_plain_frame_length_round_trips_without_setting_encryption_flag() {
734        let frame = SilkroadFrame::Packet {
735            count: 7,
736            crc: 9,
737            opcode: 0x1234,
738            data: Bytes::from(vec![0xAB; MAX_FRAME_CONTENT_SIZE]),
739        };
740
741        let wire = frame
742            .serialize()
743            .expect("the maximum 15-bit content length is representable");
744        let (consumed, decoded) =
745            SilkroadFrame::parse(&wire).expect("the maximum plain frame should parse");
746
747        assert_eq!([0xFF, 0x7F], wire[..2]);
748        assert_eq!(wire.len(), consumed);
749        assert_eq!(frame, decoded);
750    }
751
752    #[test]
753    fn oversized_plain_frame_is_rejected() {
754        let frame = SilkroadFrame::Packet {
755            count: 0,
756            crc: 0,
757            opcode: 0x1234,
758            data: Bytes::from(vec![0; MAX_FRAME_CONTENT_SIZE + 1]),
759        };
760
761        let error = frame
762            .serialize()
763            .expect_err("a plain frame cannot use the encryption marker as length");
764
765        assert_eq!(
766            FrameEncodeError::ContentTooLarge {
767                actual: MAX_FRAME_CONTENT_SIZE + 1,
768                maximum: MAX_FRAME_CONTENT_SIZE,
769            },
770            error
771        );
772    }
773
774    #[cfg(feature = "codec")]
775    #[test]
776    fn codec_rejection_does_not_modify_the_destination_buffer() {
777        let frame = SilkroadFrame::Packet {
778            count: 0,
779            crc: 0,
780            opcode: 0x1234,
781            data: Bytes::from(vec![0; MAX_FRAME_CONTENT_SIZE + 1]),
782        };
783        let mut codec = SilkroadCodec;
784        let mut destination = BytesMut::from(&b"canary"[..]);
785
786        let error = codec
787            .encode(frame, &mut destination)
788            .expect_err("the codec must reject an unrepresentable frame");
789
790        assert_eq!(std::io::ErrorKind::InvalidInput, error.kind());
791        assert_eq!(&b"canary"[..], destination.as_ref());
792    }
793
794    #[test]
795    fn test_serialize_encrypted() {
796        let data = SilkroadFrame::Encrypted {
797            content_size: 0,
798            encrypted_data: Bytes::from_static(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
799        }
800        .serialize()
801        .expect("an empty encrypted frame should be representable");
802        assert_eq!(
803            data.as_ref(),
804            &[0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
805        );
806    }
807
808    #[test]
809    fn encrypted_frame_with_mismatched_ciphertext_length_is_rejected() {
810        for actual in [7, 16] {
811            let frame = SilkroadFrame::Encrypted {
812                content_size: 1,
813                encrypted_data: Bytes::from(vec![0; actual]),
814            };
815
816            let expected_error = FrameEncodeError::EncryptedDataLengthMismatch {
817                content_size: 1,
818                expected: 8,
819                actual,
820            };
821
822            assert_eq!(
823                expected_error,
824                frame
825                    .packet_size()
826                    .expect_err("an invalid frame has no encoded packet size")
827            );
828            assert_eq!(
829                expected_error,
830                frame
831                    .serialize()
832                    .expect_err("ciphertext must match the padded encrypted content length")
833            );
834        }
835    }
836
837    #[test]
838    fn oversized_encrypted_frame_is_rejected() {
839        let frame = SilkroadFrame::Encrypted {
840            content_size: MAX_FRAME_CONTENT_SIZE + 1,
841            encrypted_data: Bytes::new(),
842        };
843
844        let error = frame
845            .serialize()
846            .expect_err("encrypted content must fit the 15-bit length field");
847
848        assert_eq!(
849            FrameEncodeError::ContentTooLarge {
850                actual: MAX_FRAME_CONTENT_SIZE + 1,
851                maximum: MAX_FRAME_CONTENT_SIZE,
852            },
853            error
854        );
855    }
856
857    #[test]
858    fn test_serialize_massive() {
859        let data = SilkroadFrame::MassiveHeader {
860            count: 0,
861            crc: 0,
862            contained_opcode: 0x42,
863            contained_count: 1,
864        }
865        .serialize()
866        .expect("a massive header should be representable");
867        assert_eq!(
868            data.as_ref(),
869            &[
870                0x06, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x01, 0x01, 0x00, 0x42, 0x00, 0x00
871            ]
872        );
873
874        let data = SilkroadFrame::MassiveContainer {
875            count: 0,
876            crc: 0,
877            inner: Bytes::new(),
878        }
879        .serialize()
880        .expect("an empty massive container should be representable");
881        assert_eq!(data.as_ref(), &[0x01, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x00]);
882    }
883}