-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Deserialization #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,6 +182,7 @@ impl MessageDefinition { | |
}) | ||
.collect(); | ||
|
||
// Serialization part | ||
let mut sum_quote = None; | ||
let mut variables_serialized: Vec<TokenStream> = vec![]; | ||
let mut sum = 0; | ||
|
@@ -236,6 +237,78 @@ impl MessageDefinition { | |
|
||
let sum_quote = sum_quote.or_else(|| Some(quote! { #sum })); | ||
|
||
// Descerialization part | ||
let mut b: usize = 0; // current byte | ||
let variables_deserialized: Vec<TokenStream> = self | ||
.payload | ||
.iter() | ||
.map(|field| { | ||
let name = ident!(field.name); | ||
match &field.typ { | ||
PayloadType::I8 | PayloadType::U8 | PayloadType::CHAR => { | ||
let value = quote! { | ||
#name: payload[#b].into(), | ||
}; | ||
b += field.typ.to_size(); | ||
value | ||
} | ||
PayloadType::U16 | PayloadType::I16 | PayloadType::U32 | PayloadType::I32 | PayloadType::F32 => { | ||
let data_type = field.typ.to_rust(); | ||
let data_size = field.typ.to_size(); | ||
let field_token = quote! { | ||
#name: #data_type::from_le_bytes(payload[#b..#b + #data_size].try_into().expect("Wrong slice length")), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should do |
||
}; | ||
b += data_size; | ||
field_token | ||
} | ||
PayloadType::VECTOR(vector) => { | ||
let data_type = vector.data_type.to_rust(); | ||
let data_size = vector.data_type.to_size(); | ||
if let Some(size_type) = &vector.size_type { | ||
let length_name = quote::format_ident!("{}_length", field.name); | ||
let length_type = size_type.to_rust(); | ||
let length = self.payload.len(); | ||
let field_token = { | ||
let value = match vector.data_type { | ||
PayloadType::CHAR | | ||
PayloadType::U8 | | ||
PayloadType::I8 => quote! { | ||
payload[#b..#b + payload.len()].to_vec() | ||
}, | ||
PayloadType::U16 | | ||
PayloadType::U32 | | ||
PayloadType::I16 | | ||
PayloadType::I32 | | ||
PayloadType::F32 => quote! { | ||
payload[#b..#b + payload.len()] | ||
.chunks_exact(#data_size) | ||
.into_iter() | ||
.map(|a| u16::from_le_bytes((*a).try_into().expect("Wrong slice length"))) | ||
.collect::<Vec<#data_type>>() | ||
}, | ||
PayloadType::VECTOR(_) => unimplemented!("Vector of vectors are not supported"), | ||
}; | ||
|
||
quote! { | ||
#length_name: payload.len() as #length_type, | ||
#name: #value, | ||
} | ||
}; | ||
b += length; | ||
field_token | ||
} else { | ||
let length = self.payload.len(); | ||
let field_token = quote! { | ||
#name: String::from_utf8(payload[#b..#b + payload.len()].to_vec()).unwrap(), | ||
}; | ||
b += length; | ||
field_token | ||
} | ||
} | ||
} | ||
}) | ||
.collect(); | ||
|
||
quote! { | ||
#[derive(Debug, Clone, PartialEq, Default)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
|
@@ -245,11 +318,19 @@ impl MessageDefinition { | |
} | ||
|
||
impl Serialize for #struct_name { | ||
fn serialize(self, buffer: &mut [u8]) -> usize { | ||
fn serialize(&self, buffer: &mut [u8]) -> usize { | ||
#(#variables_serialized)* | ||
#sum_quote | ||
} | ||
} | ||
|
||
impl Deserialize for #struct_name { | ||
fn deserialize(payload: &[u8]) -> Result<Self, &'static str> { | ||
Ok(Self { | ||
#(#variables_deserialized)* | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -301,6 +382,19 @@ fn emit_ping_message(messages: HashMap<&String, &MessageDefinition>) -> TokenStr | |
}) | ||
.collect::<Vec<TokenStream>>(); | ||
|
||
let message_enums_deserialize = messages | ||
.iter() | ||
.map(|(name, message)| { | ||
let pascal_message_name = ident!(name.to_case(Case::Pascal)); | ||
let struct_name = quote::format_ident!("{}Struct", pascal_message_name); | ||
let id = message.id; | ||
|
||
quote! { | ||
#id => Messages::#pascal_message_name(#struct_name::deserialize(payload)?), | ||
} | ||
}) | ||
.collect::<Vec<TokenStream>>(); | ||
|
||
quote! { | ||
impl PingMessage for Messages { | ||
fn message_name(&self) -> &'static str { | ||
|
@@ -321,13 +415,45 @@ fn emit_ping_message(messages: HashMap<&String, &MessageDefinition>) -> TokenStr | |
_ => Err("Invalid message name."), | ||
} | ||
} | ||
} | ||
|
||
fn serialize(self, buffer: &mut [u8]) -> usize { | ||
impl Serialize for Messages { | ||
fn serialize(&self, buffer: &mut [u8]) -> usize { | ||
match self { | ||
#(#message_enums_serialize)* | ||
} | ||
} | ||
} | ||
|
||
impl Deserialize for Messages { | ||
fn deserialize(buffer: &[u8]) -> Result<Self, &'static str> { | ||
// Parse start1 and start2 | ||
if !((buffer[0] == b'B') && (buffer[1] == b'R')) { | ||
return Err("Message should start with \"BR\" ASCII sequence"); | ||
} | ||
|
||
// Get the package data | ||
let payload_length = u16::from_le_bytes([buffer[2], buffer[3]]); | ||
let message_id = u16::from_le_bytes([buffer[4], buffer[5]]); | ||
let _src_device_id = buffer[6]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should think how to handle it in a better way, maybe returning two structures, one that encapsulate the message and another one for the header or everything else, maybe a complete message. |
||
let _dst_device_id = buffer[7]; | ||
let payload = &buffer[8..(8 + payload_length) as usize]; | ||
let _checksum = u16::from_le_bytes([ | ||
buffer[(payload_length + 1) as usize], | ||
buffer[(payload_length + 2) as usize], | ||
]); | ||
|
||
// Parse the payload | ||
Ok(match message_id { | ||
#(#message_enums_deserialize)* | ||
_ => { | ||
return Err(&"Unknown message id"); | ||
} | ||
}) | ||
} | ||
} | ||
|
||
|
||
} | ||
} | ||
|
||
|
@@ -368,6 +494,8 @@ pub fn generate<R: Read, W: Write>(input: &mut R, output_rust: &mut W) { | |
let code = quote! { | ||
use crate::serialize::PingMessage; | ||
use crate::serialize::Serialize; | ||
use crate::serialize::Deserialize; | ||
use std::convert::TryInto; | ||
|
||
#[cfg(feature = "serde")] | ||
use serde::{Deserialize, Serialize}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
include!(concat!(env!("OUT_DIR"), "/mod.rs")); | ||
|
||
use crate::serialize::PingMessage; | ||
use crate::serialize::{Deserialize, PingMessage}; | ||
|
||
const PAYLOAD_SIZE: usize = 255; | ||
|
||
use std::fmt; | ||
use std::io::Write; | ||
use std::{convert::TryFrom, io::Write}; | ||
|
||
pub mod serialize; | ||
|
||
|
@@ -21,6 +21,59 @@ impl Default for PingMessagePack { | |
} | ||
} | ||
|
||
impl<T: PingMessage> From<&T> for PingMessagePack { | ||
fn from(message: &T) -> Self { | ||
let mut new: Self = Default::default(); | ||
new.set_message(message); | ||
new | ||
} | ||
} | ||
|
||
pub enum Messages { | ||
Bluebps(bluebps::Messages), | ||
Common(common::Messages), | ||
Ping1d(ping1d::Messages), | ||
Ping360(ping360::Messages), | ||
} | ||
|
||
impl TryFrom<&Vec<u8>> for Messages { | ||
type Error = &'static str; // TODO: define error types for each kind of failure | ||
|
||
fn try_from(buffer: &Vec<u8>) -> Result<Self, Self::Error> { | ||
// Parse start1 and start2 | ||
if !((buffer[0] == b'B') && (buffer[1] == b'R')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should have a function and variables to compare the header and save B and R. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a generic deserializer that stores the state of the parser. If we receive B, than R, than 3 more bytes, until the message is complete. |
||
return Err("Message should start with \"BR\" ASCII sequence"); | ||
} | ||
|
||
// Get the package data | ||
let payload_length = u16::from_le_bytes([buffer[2], buffer[3]]); | ||
let _message_id = u16::from_le_bytes([buffer[4], buffer[5]]); | ||
let _src_device_id = buffer[6]; | ||
let _dst_device_id = buffer[7]; | ||
let payload = &buffer[8..(8 + payload_length) as usize]; | ||
let _checksum = u16::from_le_bytes([ | ||
buffer[(payload_length + 1) as usize], | ||
buffer[(payload_length + 2) as usize], | ||
]); | ||
|
||
// Try to parse with each module | ||
if let Ok(message) = bluebps::Messages::deserialize(buffer) { | ||
return Ok(Messages::Bluebps(message)); | ||
} | ||
if let Ok(message) = common::Messages::deserialize(buffer) { | ||
return Ok(Messages::Common(message)); | ||
} | ||
if let Ok(message) = ping1d::Messages::deserialize(buffer) { | ||
return Ok(Messages::Ping1d(message)); | ||
} | ||
if let Ok(message) = ping360::Messages::deserialize(buffer) { | ||
return Ok(Messages::Ping360(message)); | ||
} | ||
|
||
Err("Unknown message") | ||
} | ||
} | ||
|
||
impl PingMessagePack { | ||
/** | ||
* Message Format | ||
|
@@ -45,13 +98,7 @@ impl PingMessagePack { | |
Default::default() | ||
} | ||
|
||
pub fn from(message: impl PingMessage) -> Self { | ||
patrickelectric marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let mut new: Self = Default::default(); | ||
new.set_message(message); | ||
new | ||
} | ||
|
||
pub fn set_message(&mut self, message: impl PingMessage) { | ||
pub fn set_message(&mut self, message: &impl PingMessage) { | ||
let message_id = message.message_id(); | ||
let (left, right) = self.0.split_at_mut(Self::HEADER_SIZE); | ||
let length = message.serialize(right) as u16; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
pub trait PingMessage | ||
where | ||
Self: Sized, | ||
Self: Sized + Serialize + Deserialize, | ||
{ | ||
fn message_id(&self) -> u16; | ||
fn message_name(&self) -> &'static str; | ||
|
||
fn message_id_from_name(name: &str) -> Result<u16, &'static str>; | ||
|
||
fn serialize(self, buffer: &mut [u8]) -> usize; | ||
} | ||
|
||
pub trait Serialize { | ||
fn serialize(self, buffer: &mut [u8]) -> usize; | ||
fn serialize(&self, buffer: &mut [u8]) -> usize; | ||
} | ||
|
||
pub trait Deserialize | ||
where | ||
Self: Sized, | ||
{ | ||
fn deserialize(buffer: &[u8]) -> Result<Self, &'static str>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use std::convert::TryFrom; | ||
|
||
use ping_rs::common::Messages as common_messages; | ||
use ping_rs::{common, Messages}; | ||
|
||
#[test] | ||
fn test_simple_deserialization() { | ||
let general_request = | ||
common_messages::GeneralRequest(common::GeneralRequestStruct { requested_id: 5 }); | ||
|
||
let buffer: Vec<u8> = vec![ | ||
0x42, 0x52, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0xa1, 0x00, | ||
]; | ||
let Messages::Common(parsed) = Messages::try_from(&buffer).unwrap() else { | ||
panic!(""); | ||
}; | ||
|
||
// From official ping protocol documentation | ||
assert_eq!(general_request, parsed); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ use ping_rs::{common, PingMessagePack}; | |
fn test_simple_serialization() { | ||
let general_request = | ||
common_messages::GeneralRequest(common::GeneralRequestStruct { requested_id: 5 }); | ||
let message = PingMessagePack::from(general_request); | ||
let message = PingMessagePack::from(&general_request); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This appears unnecessary on the current version |
||
|
||
// From official ping protocol documentation | ||
assert_eq!( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can check the initial size with
payload.len()
, try_into and everything else will be unnecessary,