Skip to content

Commit

Permalink
Implement support for payload field size modifiers
Browse files Browse the repository at this point in the history
Fixes #83

Enables the following declaration in the
rust generator.

```
struct Test {
  _size_(_payload_) : 8,
  _payload_ : [+2],
}
```
  • Loading branch information
hchataing committed Nov 8, 2023
1 parent 632c584 commit bb25b31
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 24 deletions.
10 changes: 10 additions & 0 deletions pdl-compiler/src/backends/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,16 @@ mod tests {
"
);

test_pdl!(
payload_with_size_modifier,
"
packet Test {
_size_(_payload_): 8,
_payload_ : [+1],
}
"
);

// TODO(mgeisler): enable this test when we have an approach to
// struct fields with parents.
//
Expand Down
41 changes: 20 additions & 21 deletions pdl-compiler/src/backends/rust/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,35 +570,34 @@ impl<'a> FieldParser<'a> {
let payload_size_field = self.decl.payload_size();
let offset_from_end = self.payload_field_offset_from_end();

if size_modifier.is_some() {
todo!(
"Unsupported size modifier for {packet}: {size_modifier:?}",
packet = self.packet_name
);
}

if self.shift != 0 {
if payload_size_field.is_some() {
panic!("Unexpected payload size for non byte aligned payload");
}

//let rounded_size = self.shift / 8 + if self.shift % 8 == 0 { 0 } else { 1 };
//let padding_bits = 8 * rounded_size - self.shift;
//let reserved_field =
// ast::Field::Reserved { loc: ast::SourceRange::default(), width: padding_bits };
//TODO: self.add_bit_field(&reserved_field); --
// reserved_field does not live long enough.

// TODO: consume span of rounded size
} else {
// TODO: consume span
todo!("Unexpected non byte aligned payload");
}

if let Some(ast::FieldDesc::Size { field_id, .. }) = &payload_size_field.map(|f| &f.desc) {
// The payload or body has a known size. Consume the
// payload and update the span in case fields are placed
// after the payload.
let size_field = size_field_ident(field_id);
if let Some(size_modifier) = size_modifier {
let size_modifier = proc_macro2::Literal::usize_unsuffixed(
size_modifier.parse::<usize>().expect("failed to parse the size modifier"),
);
let packet_name = &self.packet_name;
// Push code to check that the size is greater than the size
// modifier. Required to safely substract the modifier from the
// size.
self.code.push(quote! {
if #size_field < #size_modifier {
return Err(Error::InvalidLengthError {
obj: #packet_name.to_string(),
wanted: #size_modifier,
got: #size_field,
});
}
let #size_field = #size_field - #size_modifier;
});
}
self.check_size(self.span, &quote!(#size_field ));
self.code.push(quote! {
let payload = &#span.get()[..#size_field];
Expand Down
14 changes: 13 additions & 1 deletion pdl-compiler/src/backends/rust/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl<'a> FieldSerializer<'a> {
self.add_typedef_field(id, type_id);
}
ast::FieldDesc::Payload { .. } | ast::FieldDesc::Body { .. } => {
self.add_payload_field()
self.add_payload_field();
}
// Padding field handled in serialization of associated array field.
ast::FieldDesc::Padding { .. } => (),
Expand Down Expand Up @@ -225,6 +225,18 @@ impl<'a> FieldSerializer<'a> {
let field_size_name = format_ident!("{field_id}_size");
let array_size = match (&value_field.desc, value_field_decl.map(|decl| &decl.desc))
{
(ast::FieldDesc::Payload { size_modifier: Some(size_modifier) }, _) => {
let size_modifier = proc_macro2::Literal::usize_unsuffixed(
size_modifier
.parse::<usize>()
.expect("failed to parse the size modifier"),
);
if let ast::DeclDesc::Packet { .. } = &decl.desc {
quote! { (self.child.get_total_size() + #size_modifier) }
} else {
quote! { (self.payload.len() + #size_modifier) }
}
}
(ast::FieldDesc::Payload { .. } | ast::FieldDesc::Body { .. }, _) => {
if let ast::DeclDesc::Packet { .. } = &decl.desc {
quote! { self.child.get_total_size() }
Expand Down
1 change: 1 addition & 0 deletions pdl-compiler/src/bin/generate-canonical-tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ fn main() {
"Packet_Payload_Field_UnknownSize",
"Packet_Payload_Field_UnknownSize_Terminal",
"Packet_Payload_Field_VariableSize",
"Packet_Payload_Field_SizeModifier",
"Packet_Reserved_Field",
"Packet_Scalar_Field",
"Packet_Size_Field",
Expand Down
187 changes: 187 additions & 0 deletions pdl-compiler/tests/generated/payload_with_size_modifier_big_endian.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#![rustfmt::skip]
/// @generated rust packets from test.
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::convert::{TryFrom, TryInto};
use std::cell::Cell;
use std::fmt;
use pdl_runtime::{Error, Packet};
type Result<T> = std::result::Result<T, Error>;
/// Private prevents users from creating arbitrary scalar values
/// in situations where the value needs to be validated.
/// Users can freely deref the value, but only the backend
/// may create it.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Private<T>(T);
impl<T> std::ops::Deref for Private<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestDataChild {
Payload(Bytes),
None,
}
impl TestDataChild {
fn get_total_size(&self) -> usize {
match self {
TestDataChild::Payload(bytes) => bytes.len(),
TestDataChild::None => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestChild {
Payload(Bytes),
None,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TestData {
child: TestDataChild,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Test {
#[cfg_attr(feature = "serde", serde(flatten))]
test: TestData,
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TestBuilder {
pub payload: Option<Bytes>,
}
impl TestData {
fn conforms(bytes: &[u8]) -> bool {
bytes.len() >= 1
}
fn parse(bytes: &[u8]) -> Result<Self> {
let mut cell = Cell::new(bytes);
let packet = Self::parse_inner(&mut cell)?;
Ok(packet)
}
fn parse_inner(mut bytes: &mut Cell<&[u8]>) -> Result<Self> {
if bytes.get().remaining() < 1 {
return Err(Error::InvalidLengthError {
obj: "Test".to_string(),
wanted: 1,
got: bytes.get().remaining(),
});
}
let payload_size = bytes.get_mut().get_u8() as usize;
if payload_size < 1 {
return Err(Error::InvalidLengthError {
obj: "Test".to_string(),
wanted: 1,
got: payload_size,
});
}
let payload_size = payload_size - 1;
if bytes.get().remaining() < payload_size {
return Err(Error::InvalidLengthError {
obj: "Test".to_string(),
wanted: payload_size,
got: bytes.get().remaining(),
});
}
let payload = &bytes.get()[..payload_size];
bytes.get_mut().advance(payload_size);
let child = match () {
_ if !payload.is_empty() => {
TestDataChild::Payload(Bytes::copy_from_slice(payload))
}
_ => TestDataChild::None,
};
Ok(Self { child })
}
fn write_to(&self, buffer: &mut BytesMut) {
if (self.child.get_total_size() + 1) > 0xff {
panic!(
"Invalid length for {}::{}: {} > {}", "Test", "_payload_", (self.child
.get_total_size() + 1), 0xff
);
}
buffer.put_u8((self.child.get_total_size() + 1) as u8);
match &self.child {
TestDataChild::Payload(payload) => buffer.put_slice(payload),
TestDataChild::None => {}
}
}
fn get_total_size(&self) -> usize {
self.get_size()
}
fn get_size(&self) -> usize {
1 + self.child.get_total_size()
}
}
impl Packet for Test {
fn to_bytes(self) -> Bytes {
let mut buffer = BytesMut::with_capacity(self.test.get_size());
self.test.write_to(&mut buffer);
buffer.freeze()
}
fn to_vec(self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl From<Test> for Bytes {
fn from(packet: Test) -> Self {
packet.to_bytes()
}
}
impl From<Test> for Vec<u8> {
fn from(packet: Test) -> Self {
packet.to_vec()
}
}
impl Test {
pub fn parse(bytes: &[u8]) -> Result<Self> {
let mut cell = Cell::new(bytes);
let packet = Self::parse_inner(&mut cell)?;
Ok(packet)
}
fn parse_inner(mut bytes: &mut Cell<&[u8]>) -> Result<Self> {
let data = TestData::parse_inner(&mut bytes)?;
Self::new(data)
}
pub fn specialize(&self) -> TestChild {
match &self.test.child {
TestDataChild::Payload(payload) => TestChild::Payload(payload.clone()),
TestDataChild::None => TestChild::None,
}
}
fn new(test: TestData) -> Result<Self> {
Ok(Self { test })
}
pub fn get_payload(&self) -> &[u8] {
match &self.test.child {
TestDataChild::Payload(bytes) => &bytes,
TestDataChild::None => &[],
}
}
fn write_to(&self, buffer: &mut BytesMut) {
self.test.write_to(buffer)
}
pub fn get_size(&self) -> usize {
self.test.get_size()
}
}
impl TestBuilder {
pub fn build(self) -> Test {
let test = TestData {
child: match self.payload {
None => TestDataChild::None,
Some(bytes) => TestDataChild::Payload(bytes),
},
};
Test::new(test).unwrap()
}
}
impl From<TestBuilder> for Test {
fn from(builder: TestBuilder) -> Test {
builder.build().into()
}
}
Loading

0 comments on commit bb25b31

Please sign in to comment.