3 unstable releases
new 0.2.0 | Oct 27, 2024 |
---|---|
0.1.1 | Apr 28, 2024 |
0.1.0 | Apr 21, 2024 |
#9 in #silkroad
70 downloads per month
Used in 3 crates
(via skrillax-serde)
43KB
864 lines
skrillax-serde-derive
This is a #[derive]
macro that implements Serialize
, Deserialize
, and ByteSize
from
skrillax-serde for a given type. For examples and documentation, please check the
module documentation. You may also check out the tests in the derive test directory.
lib.rs
:
Generally it should be enough to simply #[derive(Deserialize)]
or
whichever trait you need. Just like the more general serde
crate, this
will handle most common things, like fields of different types, including
references to other structures. However, there are a few things to
keep in mind. Silkroad Online packets are not self-specifying, and thus we
often need to provide just a little bit of help to serialize/deserialize
some kinds of data. In general, you can provide additional options through
the #[silkroad]
tag. Which options are available for which elements will
be explained in the following section.
Enums
Enums are generally serialized as one byte discriminant, followed by the
content of that variant without further details. Currently, we don't
automatically map the index of the enum variant to the discriminant. As
such, you need to define a value manually. This can be done using
#[silkroad(value = 1)]
to set the variants byte value to 1
:
#[derive(Serialize, Deserialize)]
enum Hello {
#[silkroad(value = 1)]
ClientHello(String),
#[silkroad(value = 2)]
ServerHello(String),
}
In some cases it may be necessary for the discriminant to be two bytes wide,
which you can specify using #[silkroad(size = 2)]
on the enum itself:
#[derive(Serialize, Deserialize)]
#[silkroad(size = 2)]
enum Hello {
#[silkroad(value = 0x400D)]
ClientHello(String),
}
Structs
Structs are always serialized/deserialized by serializing/deserializing their fields. A unit struct therefor has length zero. There are also no options currently to alter the behavior for structs themselves, only their fields.
#[derive(Serialize, Deserialize)]
struct Hello(String);
Fields
The serialization/deserialization of fields is identical between structs and enums. Each field is serialized one after another without any separators. Therefor, it is necessary to match the size exactly to the consumed bytes. Fields are serialized and deserialized in the order they are defined.
#[derive(Serialize, Deserialize)]
struct Hello {
one_byte: u8,
two_bytes: u16,
}
Collections
Collections (i.e. vectors) are encoded using one byte length followed by the
elements of the collection without a separator. If the size is larger, this
needs to be denoted using the #[silkroad(size = 2)]
attribute.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(size = 2)]
greetings: Vec<String>,
}
The default size is 1 with a size of up to 4 being supported.
Additionally, you may change the type of encoding for a collection using the
list_type
attribute. This accepts one of three options: length
(default), break
, and has-more
. break
and has-more
specify before
each element if another element will follow using different values. break
uses 1
for 'has more values' and 2
for finished, while has-more
uses 1
for more elements and 0
for being finished.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(list_type = "break")]
greetings: Vec<String>,
}
Strings
Generally a string is encoded using two bytes length and then the UTF-8
representation of that string. In some cases, Silkroad however uses two byte
wide characters (UTF-16) in strings. This can be configured by using a
size
of 2.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(size = 2)]
greeting: String,
}
Optional
Optional values will be encoded using a byte denoting the presence (1) or absence (0), following the underlying value if it is present. In some cases, due to previous knowledge, optional values may just appear (or be missing) without the presence indicator. This makes them impossible to deserialize (currently), but this is unfortunately current necessary. To achieve this, you can set the size of the field to 0.
#[derive(Serialize)]
struct Hello {
#[silkroad(size = 0)]
greeting: Option<String>,
}
Alternatively, if there is an indication in the data whether the value will
be present or not, you can use the when
attribute to specify a condition.
In that case the presence byte will be omitted as well, but makes it
possible to be deserialized. This does not make any checks for serialization
and will always append a present value, ignoring the condition. The
condition in when
should denote an expression which returns a boolean,
showing if the values is present in the packet or not. It is possible to
access any previous values, but is currently limited to expressions without
imports.
#[derive(Deserialize, Serialize)]
struct Hello {
condition: u8
#[silkroad(when = "condition == 1")]
greeting: Option<String>
}
Dependencies
~0.6–1.1MB
~25K SLoC