Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 755e1725 authored by Henri Chataing's avatar Henri Chataing
Browse files

pdl: Implement support for array padding

Bug: 279494407
Test: cargo test
Change-Id: If27672842425ba09fc0be5d28ada25e3204587db
parent 11a7eeb0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ filegroup {
        "tests/generated/packet_decl_array_unknown_element_width_dynamic_count_little_endian.rs",
        "tests/generated/packet_decl_array_unknown_element_width_dynamic_size_big_endian.rs",
        "tests/generated/packet_decl_array_unknown_element_width_dynamic_size_little_endian.rs",
        "tests/generated/packet_decl_array_with_padding_big_endian.rs",
        "tests/generated/packet_decl_array_with_padding_little_endian.rs",
        "tests/generated/packet_decl_child_packets_big_endian.rs",
        "tests/generated/packet_decl_child_packets_little_endian.rs",
        "tests/generated/packet_decl_complex_scalars_big_endian.rs",
+47 −11
Original line number Diff line number Diff line
@@ -54,6 +54,9 @@ pub mod ast {
    pub struct FieldAnnotation {
        // Size of field.
        pub size: Size,
        // Size of field with padding bytes.
        // This information exists only for array fields.
        pub padded_size: Option<usize>,
    }

    #[derive(Default, Debug, Clone)]
@@ -65,6 +68,12 @@ pub mod ast {
        pub payload_size: Size,
    }

    impl FieldAnnotation {
        pub fn new(size: Size) -> Self {
            FieldAnnotation { size, padded_size: None }
        }
    }

    impl std::ops::Add for Size {
        type Output = Size;
        fn add(self, rhs: Size) -> Self::Output {
@@ -1320,7 +1329,7 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
    ) -> ast::Field {
        field.annotate(match &field.desc {
            FieldDesc::Checksum { .. } | FieldDesc::Padding { .. } => {
                ast::FieldAnnotation { size: ast::Size::Static(0) }
                ast::FieldAnnotation::new(ast::Size::Static(0))
            }
            FieldDesc::Size { width, .. }
            | FieldDesc::Count { width, .. }
@@ -1328,7 +1337,7 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
            | FieldDesc::FixedScalar { width, .. }
            | FieldDesc::Reserved { width }
            | FieldDesc::Scalar { width, .. } => {
                ast::FieldAnnotation { size: ast::Size::Static(*width) }
                ast::FieldAnnotation::new(ast::Size::Static(*width))
            }
            FieldDesc::Body | FieldDesc::Payload { .. } => {
                let has_payload_size = decl.fields().any(|field| match &field.desc {
@@ -1337,22 +1346,24 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
                    }
                    _ => false,
                });
                ast::FieldAnnotation {
                    size: if has_payload_size { ast::Size::Dynamic } else { ast::Size::Unknown },
                }
                ast::FieldAnnotation::new(if has_payload_size {
                    ast::Size::Dynamic
                } else {
                    ast::Size::Unknown
                })
            }
            FieldDesc::Typedef { type_id, .. }
            | FieldDesc::FixedEnum { enum_id: type_id, .. }
            | FieldDesc::Group { group_id: type_id, .. } => {
                let type_annot = scope.get(type_id).unwrap();
                ast::FieldAnnotation { size: type_annot.size + type_annot.payload_size }
                ast::FieldAnnotation::new(type_annot.size + type_annot.payload_size)
            }
            FieldDesc::Array { width: Some(width), size: Some(size), .. } => {
                ast::FieldAnnotation { size: ast::Size::Static(*size * *width) }
                ast::FieldAnnotation::new(ast::Size::Static(*size * *width))
            }
            FieldDesc::Array { width: None, size: Some(size), type_id: Some(type_id), .. } => {
                let type_annot = scope.get(type_id).unwrap();
                ast::FieldAnnotation { size: (type_annot.size + type_annot.payload_size) * *size }
                ast::FieldAnnotation::new((type_annot.size + type_annot.payload_size) * *size)
            }
            FieldDesc::Array { id, size: None, .. } => {
                // The element does not matter when the size of the array is
@@ -1364,9 +1375,11 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
                    }
                    _ => false,
                });
                ast::FieldAnnotation {
                    size: if has_array_size { ast::Size::Dynamic } else { ast::Size::Unknown },
                }
                ast::FieldAnnotation::new(if has_array_size {
                    ast::Size::Dynamic
                } else {
                    ast::Size::Unknown
                })
            }
            FieldDesc::Array { .. } => unreachable!(),
        })
@@ -1394,6 +1407,28 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
    }
}

/// Inline padding fields.
/// The padding information is added directly to the targeted fields.
fn inline_paddings(file: &mut ast::File) {
    for decl in file.declarations.iter_mut() {
        match &mut decl.desc {
            DeclDesc::Struct { fields, .. }
            | DeclDesc::Packet { fields, .. }
            | DeclDesc::Group { fields, .. } => {
                let mut padding = None;
                for field in fields.iter_mut().rev() {
                    field.annot.padded_size = padding;
                    padding = match &field.desc {
                        FieldDesc::Padding { size } => Some(*size),
                        _ => None,
                    };
                }
            }
            _ => (),
        }
    }
}

/// Inline group fields and remove group declarations.
fn inline_groups(file: &mut ast::File) -> Result<(), Diagnostics> {
    fn inline_fields<'a>(
@@ -1474,6 +1509,7 @@ pub fn analyze(file: &parser_ast::File) -> Result<ast::File, Diagnostics> {
    check_padding_fields(file)?;
    check_checksum_fields(file, &scope)?;
    let mut file = compute_field_sizes(file);
    inline_paddings(&mut file);
    inline_groups(&mut file)?;
    Ok(file)
}
+15 −0
Original line number Diff line number Diff line
@@ -1329,6 +1329,21 @@ mod tests {
        "
    );

    test_pdl!(
        packet_decl_array_with_padding,
        "
          struct Foo {
            _count_(a): 40,
            a: 16[],
          }

          packet Bar {
            a: Foo[],
            _padding_ [128],
          }
        "
    );

    test_pdl!(
        packet_decl_reserved_field,
        "
+26 −14
Original line number Diff line number Diff line
@@ -63,12 +63,13 @@ impl<'a> FieldParser<'a> {
    pub fn add(&mut self, field: &'a analyzer_ast::Field) {
        match &field.desc {
            _ if self.scope.is_bitfield(field) => self.add_bit_field(field),
            ast::FieldDesc::Padding { .. } => todo!("Padding fields are not supported"),
            ast::FieldDesc::Padding { .. } => (),
            ast::FieldDesc::Array { id, width, type_id, size, .. } => self.add_array_field(
                id,
                *width,
                type_id.as_deref(),
                *size,
                field.annot.padded_size,
                self.scope.get_field_declaration(field),
            ),
            ast::FieldDesc::Typedef { id, type_id } => self.add_typedef_field(id, type_id),
@@ -91,7 +92,7 @@ impl<'a> FieldParser<'a> {
        let end_offset = self.offset + size;

        let wanted = proc_macro2::Literal::usize_unsuffixed(size);
        self.check_size(&quote!(#wanted));
        self.check_size(self.span, &quote!(#wanted));

        let chunk_type = types::Integer::new(self.shift);
        // TODO(mgeisler): generate Rust variable names which cannot
@@ -252,9 +253,8 @@ impl<'a> FieldParser<'a> {
        Some(offset)
    }

    fn check_size(&mut self, wanted: &proc_macro2::TokenStream) {
    fn check_size(&mut self, span: &proc_macro2::Ident, wanted: &proc_macro2::TokenStream) {
        let packet_name = &self.packet_name;
        let span = self.span;
        self.code.push(quote! {
            if #span.get().remaining() < #wanted {
                return Err(Error::InvalidLengthError {
@@ -277,6 +277,7 @@ impl<'a> FieldParser<'a> {
        // `size`: the size of the array in number of elements (if
        // known). If None, the array is a Vec with a dynamic size.
        size: Option<usize>,
        padding_size: Option<usize>,
        decl: Option<&analyzer_ast::Decl>,
    ) {
        enum ElementWidth {
@@ -312,18 +313,29 @@ impl<'a> FieldParser<'a> {

        // TODO size modifier

        // TODO padded_size
        let span = match padding_size {
            Some(padding_size) => {
                let span = self.span;
                self.check_size(span, &quote!(#padding_size));
                self.code.push(quote! {
                    let (head, tail) = #span.get().split_at(#padding_size);
                    let mut head = &mut Cell::new(head);
                    #span.replace(tail);
                });
                format_ident!("head")
            }
            None => self.span.clone(),
        };

        let id = format_ident!("{id}");
        let span = self.span;

        let parse_element = self.parse_array_element(self.span, width, type_id, decl);
        let parse_element = self.parse_array_element(&span, width, type_id, decl);
        match (element_width, &array_shape) {
            (ElementWidth::Unknown, ArrayShape::SizeField(size_field)) => {
                // The element width is not known, but the array full
                // octet size is known by size field. Parse elements
                // item by item as a vector.
                self.check_size(&quote!(#size_field));
                self.check_size(&span, &quote!(#size_field));
                let parse_element =
                    self.parse_array_element(&format_ident!("head"), width, type_id, decl);
                self.code.push(quote! {
@@ -383,7 +395,7 @@ impl<'a> FieldParser<'a> {
                    let element_width = syn::Index::from(element_width);
                    quote!(#count * #element_width)
                };
                self.check_size(&array_size);
                self.check_size(&span, &quote! { #array_size });
                self.code.push(quote! {
                    // TODO(mgeisler): use
                    // https://doc.rust-lang.org/std/array/fn.try_from_fn.html
@@ -395,10 +407,10 @@ impl<'a> FieldParser<'a> {
                        .map_err(|_| Error::InvalidPacketError)?;
                });
            }
            (ElementWidth::Static(_), ArrayShape::CountField(count_field)) => {
            (ElementWidth::Static(element_width), ArrayShape::CountField(count_field)) => {
                // The element width is known, and the array element
                // count is known dynamically by the count field.
                self.check_size(&quote!(#count_field));
                self.check_size(&span, &quote!(#count_field * #element_width));
                self.code.push(quote! {
                    let #id = (0..#count_field)
                        .map(|_| #parse_element)
@@ -411,7 +423,7 @@ impl<'a> FieldParser<'a> {
                // is known by size field, or unknown (in which case
                // it is the remaining span length).
                let array_size = if let ArrayShape::SizeField(size_field) = &array_shape {
                    self.check_size(&quote!(#size_field));
                    self.check_size(&span, &quote!(#size_field));
                    quote!(#size_field)
                } else {
                    quote!(#span.get().remaining())
@@ -531,7 +543,7 @@ impl<'a> FieldParser<'a> {
            // payload and update the span in case fields are placed
            // after the payload.
            let size_field = size_field_ident(field_id);
            self.check_size(&quote!(#size_field ));
            self.check_size(self.span, &quote!(#size_field ));
            self.code.push(quote! {
                let payload = &#span.get()[..#size_field];
                #span.get_mut().advance(#size_field);
@@ -553,7 +565,7 @@ impl<'a> FieldParser<'a> {
                "Payload field offset from end of packet is not a multiple of 8"
            );
            let offset_from_end = syn::Index::from(offset_from_end / 8);
            self.check_size(&quote!(#offset_from_end));
            self.check_size(self.span, &quote!(#offset_from_end));
            self.code.push(quote! {
                let payload = &#span.get()[..#span.get().len() - #offset_from_end];
                #span.get_mut().advance(payload.len());
+30 −10
Original line number Diff line number Diff line
@@ -55,15 +55,20 @@ impl<'a> FieldSerializer<'a> {
    pub fn add(&mut self, field: &analyzer_ast::Field) {
        match &field.desc {
            _ if self.scope.is_bitfield(field) => self.add_bit_field(field),
            ast::FieldDesc::Array { id, width, .. } => {
                self.add_array_field(id, *width, self.scope.get_field_declaration(field))
            }
            ast::FieldDesc::Array { id, width, .. } => self.add_array_field(
                id,
                *width,
                field.annot.padded_size,
                self.scope.get_field_declaration(field),
            ),
            ast::FieldDesc::Typedef { id, type_id } => {
                self.add_typedef_field(id, type_id);
            }
            ast::FieldDesc::Payload { .. } | ast::FieldDesc::Body { .. } => {
                self.add_payload_field()
            }
            // Padding field handled in serialization of associated array field.
            ast::FieldDesc::Padding { .. } => (),
            _ => todo!("Cannot yet serialize {field:?}"),
        }
    }
@@ -258,10 +263,10 @@ impl<'a> FieldSerializer<'a> {
        &mut self,
        id: &str,
        width: Option<usize>,
        padding_size: Option<usize>,
        decl: Option<&analyzer_ast::Decl>,
    ) {
        // TODO: padding

        let span = format_ident!("{}", self.span);
        let serialize = match width {
            Some(width) => {
                let value = quote!(*elem);
@@ -277,7 +282,6 @@ impl<'a> FieldSerializer<'a> {
                        self.span,
                    )
                } else {
                    let span = format_ident!("{}", self.span);
                    quote! {
                        elem.write_to(#span)
                    }
@@ -286,10 +290,26 @@ impl<'a> FieldSerializer<'a> {
        };

        let id = format_ident!("{id}");
        self.code.push(quote! {

        self.code.push(match padding_size {
            Some(padding_size) =>
                quote! {
                    let current_size = #span.len();
                    for elem in &self.#id {
                        #serialize;
                    }
                    let array_size = #span.len() - current_size;
                    if array_size > #padding_size {
                        panic!("attempted to serialize an array larger than the enclosing padding size");
                    }
                    #span.put_bytes(0, #padding_size - array_size);
                },
            None =>
                quote! {
                    for elem in &self.#id {
                        #serialize;
                    }
                }
        });
    }

Loading