skrillax_codec/
lib.rs

1//! `skrillax-codec` is a crate to turn a raw stream of bytes into more meaningful frames in
2//! the format used by Silkroad Online. Framing is only the first step, as a frame is still
3//! quite a general object and does itself not provide many operations. Instead, operations
4//! are contained inside frames and will need to be decoded/encoded separately.
5//!
6//! This crate provides two things: the [SilkroadFrame] and [SilkroadCodec]. The latter,
7//! [SilkroadCodec], is expected to be used in combination with tokio's
8//! [tokio_util::codec::FramedWrite] & [tokio_util::codec::FramedRead]. It uses the former,
9//! [SilkroadFrame], as the type it produces. However, it is totally possible to use this
10//! crate without using the codec by using the [SilkroadFrame]'s serialization and deserialization
11//! functions.
12
13use byteorder::{ByteOrder, LittleEndian};
14use bytes::{Buf, BufMut, Bytes, BytesMut};
15
16const MASSIVE_PACKET_OPCODE: u16 = 0x600D;
17const ENCRYPTED_ALIGNMENT: usize = 8;
18
19/// Find the nearest block-aligned length.
20///
21/// Given the current length of data to encrypt, calculates the length of the encrypted output, which includes
22/// padding. Can at most increase by `ENCRYPTED_ALIGNMENT - 1`, which is `7`.
23fn find_encrypted_length(given_length: usize) -> usize {
24    let aligned_length = given_length % ENCRYPTED_ALIGNMENT;
25    if aligned_length == 0 {
26        // Already block-aligned, no need to pad
27        return given_length;
28    }
29
30    given_length + (8 - aligned_length) // Add padding
31}
32
33/// A 'frame' denotes the most fundamental block of data that can be sent between
34/// the client and the server in Silkroad Online. Any and all operations or data exchanges
35/// are built on top of a kind of frame.
36///
37/// There are two categories of frames; normal frames and massive frames. A normal
38/// frame is the most common frame denoting a single operation using a specified
39/// opcode. This frame may be encrypted, causing everything but the length to require
40/// decrypting before being usable. Massive frames are used to bundle similar
41/// operations together. A massive header is sent first, containing the amount
42/// of operations as well as their opcode, and is then followed by the specified
43/// amount of containers, which now only contain the data. Thus, massive frames
44/// cannot be encrypted.
45///
46/// Every frame, including an encrypted frame, contains two additional bytes:
47/// a crc checksum and a cryptographically random count. The former is used
48/// to check for bitflips/modifications and the count to prevent replay
49/// attacks.
50///
51/// To read a frame from a bytestream, you can use the [SilkroadFrame::parse]
52/// function to try and parse a frame from those bytes:
53/// ```
54/// # use bytes::Bytes;
55/// # use skrillax_codec::SilkroadFrame;
56/// let (_, frame) = SilkroadFrame::parse(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap();
57/// assert_eq!(
58///     frame,
59///     SilkroadFrame::Packet {
60///         count: 0,
61///         crc: 0,
62///         opcode: 1,
63///         data: Bytes::new(),
64///     }
65/// );
66/// ```
67///
68/// This works vice-versa, to write a frame into a byte stream, using
69/// [SilkroadFrame::serialize]:
70/// ```
71/// # use bytes::Bytes;
72/// # use skrillax_codec::SilkroadFrame;
73/// let bytes = SilkroadFrame::Packet {
74///     count: 0,
75///     crc: 0,
76///     opcode: 1,
77///     data: Bytes::new()
78/// }.serialize();
79/// assert_eq!(bytes.as_ref(), &[0x00, 0x00, 0x01, 0x00, 0x00, 0x00]);
80/// ```
81#[derive(Eq, PartialEq, Debug)]
82pub enum SilkroadFrame {
83    /// The most basic frame containing exactly one operation identified
84    /// by its opcode.
85    Packet {
86        count: u8,
87        crc: u8,
88        opcode: u16,
89        data: Bytes,
90    },
91    /// A [SilkroadFrame::Packet] which is, however, still encrypted. This
92    /// contains the encrypted data and will first need to be decrypted (for
93    /// example, using the `skrillax-security` crate).
94    Encrypted {
95        content_size: usize,
96        encrypted_data: Bytes,
97    },
98    /// The header portion of a massive packet which contains information
99    /// that is necessary for the identification and usage of the followed
100    /// [SilkroadFrame::MassiveContainer] frame(s).
101    MassiveHeader {
102        count: u8,
103        crc: u8,
104        contained_opcode: u16,
105        contained_count: u16,
106    },
107    /// The data container portion of a massive packet. Must come after
108    /// a [SilkroadFrame::MassiveHeader]. Given the opcode and included
109    /// count specified in the header frame, contains the data for `n`
110    /// operations of the same opcode.
111    MassiveContainer { count: u8, crc: u8, inner: Bytes },
112}
113
114impl SilkroadFrame {
115    /// Tries to parse the first possible frame from the given data slice.
116    /// In addition to the created frame, it will also return the size of
117    /// consumed bytes by the frame. If not enough data is available, it
118    /// will return [Err] with the bytes required to finish the frame.
119    pub fn parse(data: &[u8]) -> Result<(usize, SilkroadFrame), usize> {
120        if data.len() < 2 {
121            return Err(2 - data.len());
122        }
123
124        let length = LittleEndian::read_u16(&data[0..2]);
125        let encrypted = length & 0x8000 != 0;
126        let content_size = (length & 0x7FFF) as usize;
127        let total_size = if encrypted {
128            find_encrypted_length(content_size + 4)
129        } else {
130            content_size + 4
131        };
132
133        let data = &data[2..];
134        if data.len() < total_size {
135            return Err(total_size - data.len());
136        }
137
138        let total_consumed = total_size + 2;
139        let data = Bytes::copy_from_slice(&data[0..total_size]);
140        if encrypted {
141            return Ok((
142                total_consumed,
143                SilkroadFrame::Encrypted {
144                    content_size,
145                    encrypted_data: data,
146                },
147            ));
148        }
149
150        Ok((total_consumed, Self::from_data(&data)))
151    }
152
153    /// Creates a [SilkroadFrame] given the received data. Generally, this will result
154    /// in a [SilkroadFrame::Packet], unless we encounter a packet with the opcode
155    /// `0x600D`, which is reserved for a massive packet, consisting of a
156    /// [SilkroadFrame::MassiveHeader] and multiple [SilkroadFrame::MassiveContainer]s.
157    ///
158    /// This assumes the data is well-formed, i.e. first two bytes opcode, one byte
159    /// security count, one byte crc, and the rest data. If the data represents a
160    /// massive frame, it's also expected that the massive information has the
161    /// correct format. In other cases, this will currently _panic_.
162    pub fn from_data(data: &[u8]) -> SilkroadFrame {
163        assert!(data.len() >= 4);
164        let opcode = LittleEndian::read_u16(&data[0..2]);
165        let count = data[2];
166        let crc = data[3];
167
168        if opcode == MASSIVE_PACKET_OPCODE {
169            assert!(data.len() >= 5);
170            let mode = data[4];
171            if mode == 1 {
172                assert!(data.len() >= 10);
173                // 1 == Header
174                let inner_amount = LittleEndian::read_u16(&data[5..7]);
175                let inner_opcode = LittleEndian::read_u16(&data[7..9]);
176                SilkroadFrame::MassiveHeader {
177                    count,
178                    crc,
179                    contained_opcode: inner_opcode,
180                    contained_count: inner_amount,
181                }
182            } else {
183                SilkroadFrame::MassiveContainer {
184                    count,
185                    crc,
186                    inner: Bytes::copy_from_slice(&data[5..]),
187                }
188            }
189        } else {
190            SilkroadFrame::Packet {
191                count,
192                crc,
193                opcode,
194                data: Bytes::copy_from_slice(&data[4..]),
195            }
196        }
197    }
198
199    /// Computes the size that should be used for the length header field.
200    /// Depending on the type of frame this is either:
201    /// - The size of the contained data (basic frame)
202    /// - Encrypted size without header, but possibly padding (encrypted frame)
203    /// - A fixed size (massive header frame)
204    /// - Container and data size (massive container frame)
205    pub fn content_size(&self) -> usize {
206        match &self {
207            SilkroadFrame::Packet { data, .. } => data.len(),
208            SilkroadFrame::Encrypted { content_size, .. } => *content_size,
209            SilkroadFrame::MassiveHeader { .. } => {
210                // Massive headers have a fixed length because they're always:
211                // 1 Byte 'is header', 2 Bytes 'amount of packets', 2 Bytes 'opcode', 1 Byte unknown
212                6
213            }
214            SilkroadFrame::MassiveContainer { inner, .. } => {
215                // 1 at the start to denote that this is container packet
216                // 1 in each content to denote there's more
217                1 + inner.len()
218            }
219        }
220    }
221
222    /// Computes the total size of the network packet for this frame.
223    /// This is different from [Self::content_size] as it includes
224    /// the size of the header as well as the correct size for
225    /// encrypted packets.
226    pub fn packet_size(&self) -> usize {
227        match self {
228            SilkroadFrame::Encrypted { content_size, .. } => {
229                find_encrypted_length(*content_size + 4) + 2
230            }
231            _ => 6 + self.content_size(),
232        }
233    }
234
235    /// Tries to fetch the opcode of the frame, unless the packet
236    /// is encrypted, which returns [None].
237    pub fn opcode(&self) -> Option<u16> {
238        match &self {
239            SilkroadFrame::Packet { opcode, .. } => Some(*opcode),
240            SilkroadFrame::Encrypted { .. } => None,
241            _ => Some(0x600D),
242        }
243    }
244
245    /// Tries to serialize this frame into a byte stream. It will allocate
246    /// a buffer that matches the packet size into which it will serialize
247    /// itself.
248    pub fn serialize(&self) -> Bytes {
249        let mut output = BytesMut::with_capacity(self.packet_size());
250
251        match &self {
252            SilkroadFrame::Packet {
253                count,
254                crc,
255                opcode,
256                data,
257            } => {
258                output.put_u16_le(self.content_size() as u16);
259                output.put_u16_le(*opcode);
260                output.put_u8(*count);
261                output.put_u8(*crc);
262                output.put_slice(data);
263            }
264            SilkroadFrame::Encrypted {
265                content_size,
266                encrypted_data,
267            } => {
268                output.put_u16_le((*content_size | 0x8000) as u16);
269                output.put_slice(encrypted_data);
270            }
271            SilkroadFrame::MassiveHeader {
272                count,
273                crc,
274                contained_opcode,
275                contained_count,
276            } => {
277                output.put_u16_le(self.content_size() as u16);
278                output.put_u16_le(MASSIVE_PACKET_OPCODE);
279                output.put_u8(*count);
280                output.put_u8(*crc);
281                output.put_u8(1);
282                output.put_u16_le(*contained_count);
283                output.put_u16_le(*contained_opcode);
284                output.put_u8(0);
285            }
286            SilkroadFrame::MassiveContainer { count, crc, inner } => {
287                output.put_u16_le(self.content_size() as u16);
288                output.put_u16_le(MASSIVE_PACKET_OPCODE);
289                output.put_u8(*count);
290                output.put_u8(*crc);
291                output.put_u8(0);
292                output.put_slice(inner);
293            }
294        }
295
296        output.freeze()
297    }
298}
299
300#[cfg(feature = "codec")]
301pub use codec::*;
302
303#[cfg(feature = "codec")]
304mod codec {
305    use super::*;
306    use std::io;
307    use tokio_util::codec::{Decoder, Encoder};
308
309    /// A codec to read and write [SilkroadFrame] from/onto a byte stream.
310    /// This implements [Encoder] and [Decoder] to be used in combination
311    /// with tokio framed read/write. Essentially, this wraps the
312    /// [SilkroadFrame::serialize] and [SilkroadFrame::parse] functions
313    /// to serialize & deserialize the frames.
314    pub struct SilkroadCodec;
315
316    impl Encoder<SilkroadFrame> for SilkroadCodec {
317        type Error = io::Error;
318
319        fn encode(&mut self, item: SilkroadFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
320            let bytes = item.serialize();
321            dst.extend_from_slice(&bytes);
322            Ok(())
323        }
324    }
325
326    impl Decoder for SilkroadCodec {
327        type Item = SilkroadFrame;
328        type Error = io::Error;
329
330        fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
331            match SilkroadFrame::parse(src) {
332                Ok((bytes_read, frame)) => {
333                    src.advance(bytes_read);
334                    Ok(Some(frame))
335                }
336                Err(_) => Ok(None),
337            }
338        }
339    }
340}
341
342#[cfg(test)]
343mod test {
344    use crate::{SilkroadCodec, SilkroadFrame};
345    use bytes::{Bytes, BytesMut};
346    use tokio_util::codec::Decoder;
347
348    #[test]
349    fn test_parse_empty() {
350        let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
351        let (consumed, packet) =
352            SilkroadFrame::parse(&data).expect("Should parse empty, valid data");
353        assert_eq!(6, consumed);
354        assert_eq!(
355            SilkroadFrame::Packet {
356                count: 0,
357                crc: 0,
358                opcode: 0,
359                data: Bytes::new(),
360            },
361            packet
362        );
363    }
364
365    #[test]
366    fn test_parse_incomplete() {
367        let data = [0x00, 0x00, 0x00, 0x00, 0x00];
368        let res = SilkroadFrame::parse(&data);
369        assert!(matches!(res, Err(1)));
370
371        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
372        let res = SilkroadFrame::parse(&data);
373        assert!(matches!(res, Err(1)));
374    }
375
376    #[test]
377    fn test_parse_content() {
378        let data = [0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01];
379        let (consumed, packet) = SilkroadFrame::parse(&data).expect("Should parse valid data");
380        assert_eq!(8, consumed);
381        assert_eq!(
382            SilkroadFrame::Packet {
383                count: 0,
384                crc: 0,
385                opcode: 0x0001,
386                data: Bytes::from_static(&[0x01, 0x01]),
387            },
388            packet
389        );
390    }
391
392    #[test]
393    fn test_parse_encrypted() {
394        let data = [0x02, 0x80, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01];
395        let (consumed, packet) = SilkroadFrame::parse(&data).expect("Should parse valid data");
396        assert_eq!(10, consumed);
397        assert_eq!(
398            SilkroadFrame::Encrypted {
399                content_size: 2,
400                encrypted_data: Bytes::from_static(&[
401                    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
402                ]),
403            },
404            packet
405        );
406    }
407
408    #[test]
409    fn test_parse_massive() {
410        let header = [
411            0x06, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x01, 0x01, 0x00, 0x42, 0x00, 0x00,
412        ];
413        let (consumed, packet) = SilkroadFrame::parse(&header).expect("Should parse valid data");
414        assert_eq!(12, consumed);
415        assert_eq!(
416            SilkroadFrame::MassiveHeader {
417                count: 0,
418                crc: 0,
419                contained_opcode: 0x42,
420                contained_count: 1,
421            },
422            packet
423        );
424
425        let header = [0x02, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x00, 0x01];
426        let (consumed, packet) = SilkroadFrame::parse(&header).expect("Should parse valid data");
427        assert_eq!(8, consumed);
428        assert_eq!(
429            SilkroadFrame::MassiveContainer {
430                count: 0,
431                crc: 0,
432                inner: Bytes::from_static(&[0x01]),
433            },
434            packet
435        );
436    }
437
438    #[test]
439    fn test_decoder() {
440        let mut codec = SilkroadCodec;
441        let mut buffer = BytesMut::new();
442        buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
443        let decoded = codec.decode(&mut buffer);
444        assert!(matches!(decoded, Ok(None)));
445
446        buffer.extend_from_slice(&[0x00, 0x00]);
447        let decoded = codec.decode_eof(&mut buffer).unwrap();
448        assert_eq!(
449            Some(SilkroadFrame::Packet {
450                count: 0,
451                crc: 0,
452                opcode: 0,
453                data: Bytes::new(),
454            }),
455            decoded
456        );
457    }
458
459    #[test]
460    fn test_serialize_empty() {
461        let data = SilkroadFrame::Packet {
462            count: 0,
463            crc: 0,
464            opcode: 0,
465            data: Bytes::new(),
466        }
467        .serialize();
468        assert_eq!(data.as_ref(), &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
469    }
470
471    #[test]
472    fn test_serialize_encrypted() {
473        let data = SilkroadFrame::Encrypted {
474            content_size: 0,
475            encrypted_data: Bytes::from_static(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
476        }
477        .serialize();
478        assert_eq!(
479            data.as_ref(),
480            &[0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
481        );
482    }
483
484    #[test]
485    fn test_serialize_massive() {
486        let data = SilkroadFrame::MassiveHeader {
487            count: 0,
488            crc: 0,
489            contained_opcode: 0x42,
490            contained_count: 1,
491        }
492        .serialize();
493        assert_eq!(
494            data.as_ref(),
495            &[0x06, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x01, 0x01, 0x00, 0x42, 0x00, 0x00]
496        );
497
498        let data = SilkroadFrame::MassiveContainer {
499            count: 0,
500            crc: 0,
501            inner: Bytes::new(),
502        }
503        .serialize();
504        assert_eq!(data.as_ref(), &[0x01, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x00]);
505    }
506}