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}