I want to create TerminatedString struct that will behave like String, but will add zero to the end of the string. So it should behave like this:
#[derive(Debug, Default, Clone)]
pub struct TerminatedString(String);
// creation
let str = TerminatedString(String::from("Test"));
// output
println!("{:?}", str); // should display: Test0
Could somebody explain, does it possible to implement such behavior using traits ? Or I should use simple struct instead and implement this behavior in ::new method ?
I implemented BinaryConverter trait for each type I use on struct fields, so when macro generates the struct fields, it calls the method provided by BinaryConverter. Not sure if Debug will works here, so I am sorry for confusion.
Just in case this is full code of my macro:
#[macro_export]
macro_rules! packet {
(
$(@option[opcode=$opcode_value:expr])?
$(@option[no_size:$no_size:expr])?
$(@option[compressed:$compressed_value:expr])?
$(#[$outer:meta])*
$vis:vis struct $PacketStruct:ident {
$($field_vis:vis $field_name:ident: $field_type:ty),*$(,)?
}
$($PacketStructImpl: item)*
) => {
$(#[$outer])*
#[derive(Clone, Debug, Default)]
$vis struct $PacketStruct {
$($field_vis $field_name: $field_type),*
}
$($PacketStructImpl)*
impl $PacketStruct {
// income
pub fn from_binary(buffer: &Vec<u8>) -> Self {
let mut omit_bytes: usize = $crate::crypto::decryptor::INCOMING_HEADER_LENGTH;
$(
if $no_size {
// because no_size packets are only on login server
omit_bytes = 1;
}
)?
$(
if $compressed_value {
// 4 bytes uncompressed + 2 bytes used by zlib
omit_bytes += 6;
}
)?
let mut internal_buffer: Vec<u8> = Vec::new();
$(
if $compressed_value {
let data = &buffer[omit_bytes..];
let mut decoder = flate2::read::DeflateDecoder::new(data);
std::io::Read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();
}
)?
let buffer = if internal_buffer.is_empty() {
buffer[omit_bytes..].to_vec()
} else {
internal_buffer
};
let mut reader = std::io::Cursor::new(&buffer);
Self {
$(
$field_name: $crate::traits::binary_converter::BinaryConverter::read_from(
&mut reader
).unwrap()
),*
}
}
// outcome
pub fn to_binary(&mut self) -> Vec<u8> {
let mut packet = Vec::new();
$(
$crate::traits::binary_converter::BinaryConverter::write_into(
&mut self.$field_name,
&mut packet
).unwrap();
)*
let header = Self::_build_header(&packet);
[header, packet].concat()
}
fn _build_header(body: &Vec<u8>) -> Vec<u8> {
let mut header: Vec<u8> = Vec::new();
let mut no_size = false;
$(
no_size = $no_size;
)?
if no_size {
$(
byteorder::WriteBytesExt::write_u8(
&mut header,
$opcode_value as u8
).unwrap();
)?
} else {
$(
let size = body.len() + $crate::crypto::encryptor::OUTCOMING_OPCODE_LENGTH;
byteorder::WriteBytesExt::write_u16::<byteorder::BigEndian>(
&mut header,
size as u16,
).unwrap();
byteorder::WriteBytesExt::write_u32::<byteorder::LittleEndian>(
&mut header,
$opcode_value as u32
).unwrap();
)?
}
header
}
$(
pub fn unpack(&mut self) -> $crate::types::PacketOutcome {
($opcode_value as u32, self.to_binary())
}
)?
}
};
}
Probably it would works with just String, but some packets contains strings without null-termination, so I have no choice than implement separate type for null-termination.
Note that "abc0" is not a null-terminated string, "abc\0" is. '0' is 48 in ASCII/ANSI, while '\0' is 0. Iād recommend to use something like CString from alloc/std crate.