Add bitfield decoder
This commit is contained in:
parent
5a2b7e81fa
commit
2d8174678b
@ -16,3 +16,7 @@ proc-macro = true
|
||||
proc-macro2 = "1.0"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
minecraft-protocol = { version = "0.1.0", path = "../protocol" }
|
||||
byteorder = "1"
|
||||
|
@ -5,7 +5,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, TokenStreamExt};
|
||||
use std::iter::FromIterator;
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta};
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta, Type};
|
||||
|
||||
#[proc_macro_derive(Packet, attributes(packet))]
|
||||
pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 {
|
||||
@ -19,6 +19,8 @@ pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 {
|
||||
let encoder = impl_encoder_trait(name, fields);
|
||||
let decoder = impl_decoder_trait(name, fields);
|
||||
|
||||
println!("{}", decoder);
|
||||
|
||||
TokenStream1::from(quote! {
|
||||
#encoder
|
||||
|
||||
@ -64,6 +66,10 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
|
||||
}
|
||||
|
||||
fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
|
||||
let mut bitfields = vec![];
|
||||
let total_fields = fields.iter().count();
|
||||
let mut current = 0;
|
||||
|
||||
let decode = quote_field(fields, |field| {
|
||||
let name = &field.ident;
|
||||
let ty = &field.ty;
|
||||
@ -71,27 +77,57 @@ fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
|
||||
let unparsed_meta = get_packet_field_meta(field);
|
||||
let parsed_meta = parse_packet_field_meta(&unparsed_meta);
|
||||
|
||||
// This is special case because max length are used only for strings.
|
||||
if let Some(max_length) = parsed_meta.max_length {
|
||||
return quote! {
|
||||
let #name = crate::DecoderReadExt::read_string(reader, #max_length)?;
|
||||
let mut result = quote!();
|
||||
|
||||
// When we encounter a bitfield, we skip writing until we get
|
||||
// to the last bit field or the last field.
|
||||
match &parsed_meta.bitfield {
|
||||
Some(bitfield) => {
|
||||
bitfields.push((field.clone(), bitfield.clone()));
|
||||
}
|
||||
None => {
|
||||
result.append_all(quote_decoder_bitfield(&bitfields));
|
||||
bitfields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
current += 1;
|
||||
|
||||
if !bitfields.is_empty() {
|
||||
return if current == total_fields {
|
||||
result.append_all(quote_decoder_bitfield(&bitfields));
|
||||
bitfields.clear();
|
||||
result
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
}
|
||||
|
||||
match parsed_meta.module {
|
||||
Some(module) => {
|
||||
let module_ident = Ident::new(&module, Span::call_site());
|
||||
// This is special case because max length are used only for strings.
|
||||
if let Some(max_length) = parsed_meta.max_length {
|
||||
result.append_all(quote! {
|
||||
let #name = crate::DecoderReadExt::read_string(reader, #max_length)?;
|
||||
});
|
||||
|
||||
quote! {
|
||||
let #name = crate::#module_ident::decode(reader)?;
|
||||
return result;
|
||||
}
|
||||
|
||||
match &parsed_meta.module {
|
||||
Some(module) => {
|
||||
let module_ident = Ident::new(module, Span::call_site());
|
||||
|
||||
result.append_all(quote! {
|
||||
let #name = crate::#module_ident::decode(reader)?;
|
||||
})
|
||||
}
|
||||
None => {
|
||||
quote! {
|
||||
result.append_all(quote! {
|
||||
let #name = <#ty as crate::Decoder>::decode(reader)?;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
let create = quote_field(fields, |field| {
|
||||
@ -122,11 +158,148 @@ fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
|
||||
struct PacketFieldMeta {
|
||||
module: Option<String>,
|
||||
max_length: Option<u16>,
|
||||
bitfield: Option<Bitfield>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Bitfield {
|
||||
size: usize,
|
||||
}
|
||||
|
||||
macro_rules! decode_bitmask_value (
|
||||
($value: expr, $name: ident) => (
|
||||
fn $name(
|
||||
result: &mut TokenStream2,
|
||||
name: &Option<Ident>,
|
||||
ty: &Type,
|
||||
bitfield_size: usize,
|
||||
current: usize,
|
||||
signed: bool,
|
||||
) {
|
||||
let mask = (2 << (bitfield_size - 1)) - $value;
|
||||
let bool = match ty {
|
||||
Type::Path(type_path) => type_path
|
||||
.path
|
||||
.get_ident()
|
||||
.expect("Failed to get ident")
|
||||
.to_string() == "bool",
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if bool {
|
||||
if current > 0 {
|
||||
result.append_all(quote! {
|
||||
let mut #name = ((encoded >> #current) & #mask) != 0;
|
||||
});
|
||||
} else {
|
||||
result.append_all(quote! {
|
||||
let mut #name = (encoded & #mask) != 0;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if current > 0 {
|
||||
result.append_all(quote! {
|
||||
let mut #name = ((encoded >> #current) & #mask) as #ty;
|
||||
});
|
||||
} else {
|
||||
result.append_all(quote! {
|
||||
let mut #name = (encoded & #mask) as #ty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if signed {
|
||||
let subtract = mask + 1;
|
||||
let overflow = subtract >> 1;
|
||||
|
||||
result.append_all(quote! {
|
||||
if #name > #overflow as #ty {
|
||||
#name -= #subtract as #ty;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
decode_bitmask_value!(1i64, decode_i64_bitmask_value);
|
||||
decode_bitmask_value!(1i32, decode_i32_bitmask_value);
|
||||
decode_bitmask_value!(1i16, decode_i16_bitmask_value);
|
||||
decode_bitmask_value!(1i8, decode_i8_bitmask_value);
|
||||
|
||||
fn quote_decoder_bitfield(bitfields: &Vec<(Field, Bitfield)>) -> TokenStream2 {
|
||||
if bitfields.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let total_size: usize = bitfields.iter().map(|(_, b)| b.size).sum();
|
||||
|
||||
let mut result = read_decoder_bitfield_quote(total_size);
|
||||
let mut current = total_size;
|
||||
|
||||
for (field, bitfield) in bitfields {
|
||||
current -= bitfield.size;
|
||||
|
||||
let name = &field.ident;
|
||||
let ty = &field.ty;
|
||||
|
||||
let signed = match ty {
|
||||
Type::Path(type_path) => type_path
|
||||
.path
|
||||
.get_ident()
|
||||
.expect("Failed to get ident")
|
||||
.to_string()
|
||||
.starts_with("i"),
|
||||
_ => panic!("Unexpected bitfield type"),
|
||||
};
|
||||
|
||||
match total_size {
|
||||
_ if total_size == 64 => {
|
||||
decode_i64_bitmask_value(&mut result, name, ty, bitfield.size, current, signed)
|
||||
}
|
||||
_ if total_size == 32 => {
|
||||
decode_i32_bitmask_value(&mut result, name, ty, bitfield.size, current, signed)
|
||||
}
|
||||
_ if total_size == 16 => {
|
||||
decode_i16_bitmask_value(&mut result, name, ty, bitfield.size, current, signed)
|
||||
}
|
||||
_ => decode_i8_bitmask_value(&mut result, name, ty, bitfield.size, current, signed),
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn read_decoder_bitfield_quote(total_size: usize) -> TokenStream2 {
|
||||
match total_size {
|
||||
_ if total_size == 64 => {
|
||||
quote! {
|
||||
let encoded = ::byteorder::ReadBytesExt::read_i64::<::byteorder::BigEndian>(reader)?;
|
||||
}
|
||||
}
|
||||
_ if total_size == 32 => {
|
||||
quote! {
|
||||
let encoded = ::byteorder::ReadBytesExt::read_i32::<::byteorder::BigEndian>(reader)?;
|
||||
}
|
||||
}
|
||||
_ if total_size == 16 => {
|
||||
quote! {
|
||||
let encoded = ::byteorder::ReadBytesExt::read_i16::<::byteorder::BigEndian>(reader)?;
|
||||
}
|
||||
}
|
||||
_ if total_size == 8 => {
|
||||
quote! {
|
||||
let encoded = ::byteorder::ReadBytesExt::read_i8(reader)?;
|
||||
}
|
||||
}
|
||||
_ => panic!("Bitfield size must be aligned to 8, 16, 32 or 64"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_packet_field_meta(meta_list: &Vec<NestedMeta>) -> PacketFieldMeta {
|
||||
let mut module = None;
|
||||
let mut max_length = None;
|
||||
let mut bitfield = None;
|
||||
|
||||
for meta in meta_list {
|
||||
match meta {
|
||||
@ -147,14 +320,46 @@ fn parse_packet_field_meta(meta_list: &Vec<NestedMeta>) -> PacketFieldMeta {
|
||||
},
|
||||
path => panic!(
|
||||
"Received unrecognized attribute : \"{}\"",
|
||||
path.get_ident().unwrap()
|
||||
path.get_ident().expect("Failed to get ident")
|
||||
),
|
||||
},
|
||||
_ => panic!("Expected only named meta values"),
|
||||
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("bitfield") => {
|
||||
let mut size = None;
|
||||
|
||||
for meta in &meta_list.nested {
|
||||
match meta {
|
||||
NestedMeta::Meta(Meta::NameValue(named_meta)) => match &named_meta.path {
|
||||
path if path.is_ident("size") => match &named_meta.lit {
|
||||
Lit::Int(lit_int) => {
|
||||
size = Some(
|
||||
lit_int
|
||||
.base10_parse::<usize>()
|
||||
.expect("Failed to parse size attribute"),
|
||||
)
|
||||
}
|
||||
_ => panic!("\"size\" attribute value must be integer"),
|
||||
},
|
||||
path => panic!(
|
||||
"Received unrecognized attribute : \"{}\"",
|
||||
path.get_ident().expect("Failed to get ident")
|
||||
),
|
||||
},
|
||||
_ => panic!("Unexpected field meta"),
|
||||
}
|
||||
}
|
||||
|
||||
PacketFieldMeta { module, max_length }
|
||||
let size = size.unwrap_or_else(|| panic!("Size must be specified in bitfield"));
|
||||
bitfield = Some(Bitfield { size })
|
||||
}
|
||||
_ => panic!("Unexpected field meta"),
|
||||
}
|
||||
}
|
||||
|
||||
PacketFieldMeta {
|
||||
module,
|
||||
max_length,
|
||||
bitfield,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_packet_field_meta(field: &Field) -> Vec<NestedMeta> {
|
||||
@ -171,7 +376,7 @@ fn get_packet_field_meta(field: &Field) -> Vec<NestedMeta> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn quote_field<F: Fn(&Field) -> TokenStream2>(fields: &Fields, func: F) -> TokenStream2 {
|
||||
fn quote_field<F: FnMut(&Field) -> TokenStream2>(fields: &Fields, mut func: F) -> TokenStream2 {
|
||||
let mut output = quote!();
|
||||
|
||||
match fields {
|
||||
|
60
protocol-derive/tests/bitfield_abilities.rs
Normal file
60
protocol-derive/tests/bitfield_abilities.rs
Normal file
@ -0,0 +1,60 @@
|
||||
#[macro_use]
|
||||
extern crate minecraft_protocol_derive;
|
||||
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
|
||||
#[derive(Packet)]
|
||||
pub struct Abilities {
|
||||
#[packet(bitfield(size = 4))]
|
||||
pub _unused: u8,
|
||||
#[packet(bitfield(size = 1))]
|
||||
pub creative_mode: bool,
|
||||
#[packet(bitfield(size = 1))]
|
||||
pub allow_flying: bool,
|
||||
#[packet(bitfield(size = 1))]
|
||||
pub flying: bool,
|
||||
#[packet(bitfield(size = 1))]
|
||||
pub invulnerable: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Abilities;
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_encode_abilities_i8_bitfield() {
|
||||
let abilities = Abilities {
|
||||
_unused: 0,
|
||||
creative_mode: true,
|
||||
allow_flying: true,
|
||||
flying: true,
|
||||
invulnerable: true,
|
||||
};
|
||||
let mut vec = Vec::new();
|
||||
|
||||
abilities
|
||||
.encode(&mut vec)
|
||||
.expect("Failed to encode abilities");
|
||||
assert_eq!(vec, [15]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_abilities_i8_bitfield() {
|
||||
let value = 15i8;
|
||||
|
||||
let vec = value.to_be_bytes().to_vec();
|
||||
let mut cursor = Cursor::new(vec);
|
||||
|
||||
let abilities = Abilities::decode(&mut cursor).expect("Failed to decode abilities");
|
||||
assert!(abilities.invulnerable);
|
||||
assert!(abilities.flying);
|
||||
assert!(abilities.allow_flying);
|
||||
assert!(abilities.creative_mode);
|
||||
}
|
||||
}
|
107
protocol-derive/tests/bitfield_half.rs
Normal file
107
protocol-derive/tests/bitfield_half.rs
Normal file
@ -0,0 +1,107 @@
|
||||
#[macro_use]
|
||||
extern crate minecraft_protocol_derive;
|
||||
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
|
||||
#[derive(Packet)]
|
||||
pub struct HalfLong {
|
||||
#[packet(bitfield(size = 32))]
|
||||
pub _unused: u32,
|
||||
#[packet(bitfield(size = 32))]
|
||||
pub value: u32,
|
||||
}
|
||||
|
||||
#[derive(Packet)]
|
||||
pub struct HalfInt {
|
||||
#[packet(bitfield(size = 16))]
|
||||
pub _unused: u16,
|
||||
#[packet(bitfield(size = 16))]
|
||||
pub value: u16,
|
||||
}
|
||||
|
||||
#[derive(Packet)]
|
||||
pub struct HalfShort {
|
||||
#[packet(bitfield(size = 8))]
|
||||
pub _unused: u8,
|
||||
#[packet(bitfield(size = 8))]
|
||||
pub value: u8,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{HalfInt, HalfLong, HalfShort};
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_encode_half_long_u64_bitfield() {
|
||||
let half = HalfLong {
|
||||
_unused: 0,
|
||||
value: u32::MAX,
|
||||
};
|
||||
let mut vec = Vec::new();
|
||||
|
||||
half.encode(&mut vec).expect("Failed to encode half");
|
||||
assert_eq!(vec, u32::MAX.to_be_bytes().to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_half_long_u64_bitfield() {
|
||||
let value = u32::MAX as u64;
|
||||
|
||||
let vec = value.to_be_bytes().to_vec();
|
||||
let mut cursor = Cursor::new(vec);
|
||||
|
||||
let half = HalfLong::decode(&mut cursor).expect("Failed to decode half");
|
||||
assert_eq!(half.value, u32::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_half_int_u32_bitfield() {
|
||||
let half = HalfInt {
|
||||
_unused: 0,
|
||||
value: u16::MAX,
|
||||
};
|
||||
let mut vec = Vec::new();
|
||||
|
||||
half.encode(&mut vec).expect("Failed to encode half");
|
||||
assert_eq!(vec, u16::MAX.to_be_bytes().to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_half_int_u32_bitfield() {
|
||||
let value = u16::MAX as u32;
|
||||
|
||||
let vec = value.to_be_bytes().to_vec();
|
||||
let mut cursor = Cursor::new(vec);
|
||||
|
||||
let half = HalfInt::decode(&mut cursor).expect("Failed to decode half");
|
||||
assert_eq!(half.value, u16::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_half_short_u8_bitfield() {
|
||||
let half = HalfShort {
|
||||
_unused: 0,
|
||||
value: u8::MAX,
|
||||
};
|
||||
let mut vec = Vec::new();
|
||||
|
||||
half.encode(&mut vec).expect("Failed to encode half");
|
||||
assert_eq!(vec, u8::MAX.to_be_bytes().to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_half_short_u8_bitfield() {
|
||||
let value = u8::MAX as u16;
|
||||
let vec = value.to_be_bytes().to_vec();
|
||||
let mut cursor = Cursor::new(vec);
|
||||
|
||||
let half = HalfShort::decode(&mut cursor).expect("Failed to decode half");
|
||||
assert_eq!(half.value, u8::MAX);
|
||||
}
|
||||
}
|
54
protocol-derive/tests/bitfield_position.rs
Normal file
54
protocol-derive/tests/bitfield_position.rs
Normal file
@ -0,0 +1,54 @@
|
||||
#[macro_use]
|
||||
extern crate minecraft_protocol_derive;
|
||||
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
|
||||
#[derive(Packet)]
|
||||
pub struct Position {
|
||||
#[packet(bitfield(size = 26))]
|
||||
pub x: i32,
|
||||
#[packet(bitfield(size = 26))]
|
||||
pub z: i32,
|
||||
#[packet(bitfield(size = 12))]
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Position;
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::error::{DecodeError, EncodeError};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_encode_position_i64_bitfield() {
|
||||
let position = Position {
|
||||
x: 1000,
|
||||
y: 64,
|
||||
z: -1000,
|
||||
};
|
||||
|
||||
let mut vec = Vec::new();
|
||||
|
||||
position
|
||||
.encode(&mut vec)
|
||||
.expect("Failed to encode position");
|
||||
|
||||
assert_eq!(vec, 275152780755008i64.to_be_bytes().to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_position_i64_bitfield() {
|
||||
let value = -137164079660992i64;
|
||||
let vec = value.to_be_bytes().to_vec();
|
||||
let mut cursor = Cursor::new(vec);
|
||||
let position = Position::decode(&mut cursor).expect("Failed to decode position");
|
||||
|
||||
assert_eq!(position.x, -500);
|
||||
assert_eq!(position.y, 64);
|
||||
assert_eq!(position.z, -1000);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use num_traits::FromPrimitive;
|
||||
use std::io::Read;
|
||||
use uuid::Uuid;
|
||||
|
||||
trait Decoder {
|
||||
pub trait Decoder {
|
||||
type Output;
|
||||
|
||||
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError>;
|
||||
@ -109,6 +109,14 @@ impl Decoder for u8 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for i16 {
|
||||
type Output = Self;
|
||||
|
||||
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
|
||||
Ok(reader.read_i16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for i32 {
|
||||
type Output = Self;
|
||||
|
||||
@ -117,6 +125,14 @@ impl Decoder for i32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for u16 {
|
||||
type Output = Self;
|
||||
|
||||
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
|
||||
Ok(reader.read_u16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for u32 {
|
||||
type Output = Self;
|
||||
|
||||
|
@ -5,7 +5,7 @@ use num_traits::ToPrimitive;
|
||||
use std::io::Write;
|
||||
use uuid::Uuid;
|
||||
|
||||
trait Encoder {
|
||||
pub trait Encoder {
|
||||
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError>;
|
||||
}
|
||||
|
||||
@ -103,12 +103,24 @@ impl Encoder for u8 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for i16 {
|
||||
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
|
||||
Ok(writer.write_i16::<BigEndian>(*self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for i32 {
|
||||
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
|
||||
Ok(writer.write_i32::<BigEndian>(*self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for u16 {
|
||||
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
|
||||
Ok(writer.write_u16::<BigEndian>(*self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for u32 {
|
||||
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
|
||||
Ok(writer.write_u32::<BigEndian>(*self)?)
|
||||
|
Loading…
x
Reference in New Issue
Block a user