feat(secure): add RmcSerialize macro for convenience serializing structs

This commit is contained in:
DJMrTV 2025-02-05 15:03:08 +01:00
commit 7479105157
7 changed files with 275 additions and 11 deletions

5
.gitignore vendored
View file

@ -1,4 +1,5 @@
/target
target
.idea
.env
log
log
reports

47
macros/Cargo.lock generated Normal file
View file

@ -0,0 +1,47 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "macros"
version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"

16
macros/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "macros"
version = "0.0.0"
authors = ["DJMrTV <tvnebel@gmail.com>"]
description = "A `cargo generate` template for quick-starting a procedural macro crate"
keywords = ["template", "proc_macro", "procmacro"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "1"
proc-macro2 = "1.0"
syn = "1.0"

185
macros/src/lib.rs Normal file
View file

@ -0,0 +1,185 @@
extern crate proc_macro;
use proc_macro2::TokenTree;
use quote::__private::ext::RepToTokensExt;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Data};
use quote::{quote, TokenStreamExt};
/// Example of user-defined [derive mode macro][1]
///
/// [1]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-mode-macros
#[proc_macro_derive(RmcSerialize, attributes(extends, rmc_struct))]
pub fn rmc_serialize(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput);
let struct_attr = derive_input.attrs.iter()
.find(|a| a.path.segments.len() == 1 &&
a.path.segments.first().is_some_and(|p| p.ident.to_string() == "rmc_struct"));
let Data::Struct(s) = derive_input.data else {
panic!("rmc struct type MUST be a struct");
};
/// generate base data
let serialize_base_content = {
let mut serialize_content = quote! {};
for f in &s.fields{
if f.attrs.iter()
.any(|a| a.path.segments.len() == 1 &&
a.path.segments.first().is_some_and(|p| p.ident.to_string() == "extends")){
continue;
}
let ident = f.ident.as_ref().unwrap();
serialize_content.append_all(quote!{
self.#ident.serialize(writer)?;
})
}
quote!{
#serialize_content
Ok(())
}
};
let struct_ctor = {
let mut structure_content = quote! {};
for f in &s.fields {
let ident = f.ident.as_ref().unwrap();
structure_content.append_all(quote!{#ident, });
}
quote!{
Ok(Self{
#structure_content
})
}
};
let deserialize_base_content = {
let mut deserialize_content = quote! {};
for f in &s.fields{
if f.attrs.iter()
.any(|a| a.path.segments.len() == 1 &&
a.path.segments.first().is_some_and(|p| p.ident.to_string() == "extends")){
continue;
}
let ident = f.ident.as_ref().unwrap();
let ty = &f.ty;
deserialize_content.append_all(quote!{
let #ident = <#ty> :: deserialize(reader)?;
})
}
quote!{
#deserialize_content
#struct_ctor
}
};
/// generate base with extends stuff
let serialize_base_content = if let Some(attr) = struct_attr{
let tokens = attr.tokens.clone();
let token = tokens.into_iter().next().unwrap();
let version = match token {
TokenTree::Group(g) => {
match g.stream().into_iter().next().unwrap(){
TokenTree::Literal(l) => l,
_ => panic!("expected literal")
}
},
_ => panic!("expected group")
};
let pre_inner = if let Some(f) = s.fields.iter().find(|f| {
f.attrs.iter()
.any(|a| a.path.segments.len() == 1 &&
a.path.segments.first().is_some_and(|p| p.ident.to_string() == "extends"))
}){
let ident= f.ident.as_ref().unwrap();
quote! {
self.#ident.serialize(writer)?;
}
} else {
quote! {}
};
quote! {
#pre_inner
crate::rmc::structures::rmc_struct::write_struct(writer, #version, |mut writer|{
#serialize_base_content
})?;
Ok(())
}
} else {
serialize_base_content
};
let deserialize_base_content = if let Some(attr) = struct_attr{
let tokens = attr.tokens.clone();
let token = tokens.into_iter().next().unwrap();
let version = match token {
TokenTree::Group(g) => {
match g.stream().into_iter().next().unwrap(){
TokenTree::Literal(l) => l,
_ => panic!("expected literal")
}
},
_ => panic!("expected group")
};
let pre_inner = if let Some(f) = s.fields.iter().find(|f| {
f.attrs.iter()
.any(|a| a.path.segments.len() == 1 &&
a.path.segments.first().is_some_and(|p| p.ident.to_string() == "extends"))
}){
let ident= f.ident.as_ref().unwrap();
let ty= &f.ty;
quote! {
let #ident = <#ty> :: deserialize(reader)?;
}
} else {
quote! {}
};
quote! {
#pre_inner
Ok(crate::rmc::structures::rmc_struct::read_struct(reader, #version, move |mut reader|{
#deserialize_base_content
})?)
}
} else {
deserialize_base_content
};
let ident = derive_input.ident;
let tokens = quote! {
impl crate::rmc::structures::RmcSerialize for #ident{
fn serialize(&self, writer: &mut dyn ::std::io::Write) -> crate::rmc::structures::Result<()>{
#serialize_base_content
}
fn deserialize(reader: &mut dyn ::std::io::Read) -> crate::rmc::structures::Result<Self>{
#deserialize_base_content
}
}
};
tokens.into()
}

View file

@ -284,9 +284,3 @@ mod test{
println!("packet: {:?}", packet);
}
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
struct MatchmakeParam{
params: Vec<(String, Variant)>
}

View file

@ -17,6 +17,8 @@ impl<'a> RmcSerialize for ConnectionData<'a>{
self.special_protocols.serialize(v).expect("unable to write special protocols");
self.special_station_url.serialize(v).expect("unable to write special station url");
v.write_all(bytes_of(&self.date_time)).expect("unable to write date time");
Ok(())
})
}

View file

@ -1,5 +1,7 @@
use std::io::Write;
use std::io::{Cursor, Read, Write};
use bytemuck::bytes_of;
use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::Error::VersionMismatch;
use crate::rmc::structures::Result;
#[repr(C, packed)]
@ -8,12 +10,12 @@ struct StructureHeader{
length: u32
}
pub fn write_struct(mut writer: &mut dyn Write, version: u8, pred: impl Fn(&mut Vec<u8>)) -> Result<()> {
pub fn write_struct(mut writer: &mut dyn Write, version: u8, pred: impl FnOnce(&mut Vec<u8>) -> Result<()> ) -> Result<()> {
writer.write_all(&[version])?;
let mut scratch_space: Vec<u8> = Vec::new();
(pred)(&mut scratch_space);
(pred)(&mut scratch_space)?;
let u32_size= scratch_space.len() as u32;
@ -23,3 +25,20 @@ pub fn write_struct(mut writer: &mut dyn Write, version: u8, pred: impl Fn(&mut
Ok(())
}
pub fn read_struct<T: Sized>(mut reader: &mut dyn Read, version: u8, pred: impl FnOnce(&mut Cursor<Vec<u8>>) -> Result<T>) -> Result<T> {
let ver: u8 = reader.read_struct(IS_BIG_ENDIAN)?;
if ver != version{
return Err(VersionMismatch(ver));
}
let size: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut vec = vec![0u8; size as usize];
reader.read_exact(&mut vec)?;
let mut cursor = Cursor::new(vec);
Ok(pred(&mut cursor)?)
}