pdl_dissector/
lib.rs

1mod comments;
2pub mod diagnostics;
3#[cfg(test)]
4mod fakes;
5mod indent_write;
6mod len_info;
7pub mod pdml;
8mod utils;
9
10use ::indent_write::io::IndentWriter;
11use codespan_reporting::diagnostic::Diagnostic;
12use comments::ToLuaExpr;
13use diagnostics::Diagnostics;
14use indent_write::IoWriteExt;
15use indoc::writedoc;
16use len_info::{FType, RuntimeLenInfo};
17use log::debug;
18use pdl_compiler::{
19    analyzer::{self, Scope},
20    ast::{
21        Annotation, Constraint, Decl, DeclDesc, EndiannessValue, Field, FieldDesc, SourceDatabase,
22        Tag, TagOther, TagRange, TagValue,
23    },
24};
25use std::{collections::HashMap, io::Write, path::PathBuf};
26use utils::{buffer_value_lua_function, lua_if_then_else};
27
28use crate::{
29    comments::{find_comments_on_same_line, unwrap_comment},
30    len_info::BitLen,
31};
32
33#[derive(Clone, Debug)]
34struct FieldContext<'a> {
35    num_fixed: usize,
36    num_reserved: usize,
37    optional_decl: HashMap<String, (String, usize)>,
38    scope: &'a Scope<'a, analyzer::ast::Annotation>,
39}
40
41impl<'a> FieldContext<'a> {
42    pub fn new(scope: &'a Scope<'a, analyzer::ast::Annotation>) -> Self {
43        Self {
44            num_fixed: 0,
45            num_reserved: 0,
46            optional_decl: HashMap::default(),
47            scope,
48        }
49    }
50}
51
52trait DeclExt {
53    fn to_dissector_info(&self, scope: &Scope) -> DeclDissectorInfo;
54}
55
56#[derive(Debug, Clone)]
57pub enum DeclDissectorInfo {
58    Sequence {
59        name: String,
60        fields: Vec<FieldDissectorInfo>,
61        children: Vec<DeclDissectorInfo>,
62        constraints: Vec<ConstraintDissectorInfo>,
63    },
64    Enum {
65        name: String,
66        values: Vec<Tag>,
67        len: BitLen,
68    },
69    Checksum {
70        name: String,
71        len: BitLen,
72    },
73}
74
75impl DeclDissectorInfo {
76    fn to_comments(&self) -> String {
77        if log::log_enabled!(log::Level::Debug) {
78            format!("{self:?}")
79        } else {
80            match self {
81                DeclDissectorInfo::Sequence {
82                    name,
83                    fields,
84                    children,
85                    constraints,
86                } => format!(
87                    "Sequence: {name} ({} fields, {} children, {} constraints)",
88                    fields.len(),
89                    children.len(),
90                    constraints.len()
91                ),
92                DeclDissectorInfo::Enum { .. } => format!("{self:?}"),
93                DeclDissectorInfo::Checksum { .. } => format!("{self:?}"),
94            }
95        }
96    }
97
98    fn write_proto_fields(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
99        match self {
100            DeclDissectorInfo::Sequence {
101                name,
102                fields,
103                children,
104                constraints: _,
105            } => {
106                writeln!(writer, r#"function {name}_protocol_fields(fields, path)"#)?;
107                for field in fields {
108                    field.field_declaration(&mut writer.indent())?;
109                }
110                for child in children {
111                    let child_name = child.name();
112                    writeln!(
113                        writer.indent(),
114                        r#"{child_name}_protocol_fields(fields, path .. ".{child_name}")"#
115                    )?;
116                }
117                writeln!(writer, r#"end"#)?;
118            }
119            DeclDissectorInfo::Enum {
120                name,
121                values,
122                len: _,
123            } => {
124                writeln!(writer, r#"{name}_enum = ProtoEnum:new()"#)?;
125                for tag in values {
126                    match tag {
127                        Tag::Value(TagValue { id, loc: _, value }) => {
128                            writeln!(writer, r#"{name}_enum:define("{id}", {value})"#)?;
129                        }
130                        Tag::Range(TagRange {
131                            id: range_id,
132                            loc: _,
133                            range,
134                            tags,
135                        }) => {
136                            for TagValue { id, loc: _, value } in tags {
137                                writeln!(
138                                    writer,
139                                    r#"{name}_enum:define("{range_id}: {id}", {value})"#
140                                )?;
141                            }
142                            let range_start = range.start();
143                            let range_end = range.end();
144                            writeln!(
145                                writer,
146                                r#"{name}_enum:define("{range_id}", {{{range_start}, {range_end}}})"#
147                            )?;
148                        }
149                        Tag::Other(TagOther { id, loc: _ }) => {
150                            writeln!(writer, r#"{name}_enum:define("{id}", nil)"#)?;
151                        }
152                    }
153                }
154            }
155            DeclDissectorInfo::Checksum { name: _, len: _ } => {}
156        }
157        Ok(())
158    }
159
160    pub fn write_main_dissector(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
161        match self {
162            DeclDissectorInfo::Sequence { name, .. } => {
163                writedoc!(
164                    writer,
165                    r#"
166                    {name}_protocol_fields_table = {{}}
167                    function {name}_protocol.dissector(buffer, pinfo, tree)
168                        pinfo.cols.protocol = "{name}"
169                        local subtree = tree:add({name}_protocol, buffer(), "{name}")
170                        local i = {name}_dissect(buffer, pinfo, subtree, {name}_protocol_fields_table, "{name}")
171                        if buffer(i):len() > 0 then
172                            local remaining_bytes = buffer:len() - i
173                            if math.floor(remaining_bytes) == remaining_bytes then
174                                subtree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: " .. remaining_bytes .. " undissected bytes remaining")
175                            else
176                                subtree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: " .. (remaining_bytes * 8) .. " undissected bits remaining")
177                            end
178                        end
179                    end
180                    {name}_protocol_fields({name}_protocol_fields_table, "{name}")
181                    for name,field in pairs({name}_protocol_fields_table) do
182                        {name}_protocol.fields[name] = field.field
183                    end
184                    "#,
185                )?;
186            }
187            DeclDissectorInfo::Enum { .. } => unreachable!(),
188            DeclDissectorInfo::Checksum { .. } => unreachable!(),
189        }
190        Ok(())
191    }
192
193    /// The length of this declaration, which is the sum of the lengths of all
194    /// of its fields.
195    pub fn decl_len(&self) -> RuntimeLenInfo {
196        match self {
197            DeclDissectorInfo::Sequence { fields, .. } => {
198                let mut field_len = RuntimeLenInfo::empty();
199                for field in fields {
200                    field_len.add(&field.len());
201                }
202                field_len
203            }
204            DeclDissectorInfo::Enum {
205                name: _,
206                values: _,
207                len,
208            } => RuntimeLenInfo::fixed(*len),
209            DeclDissectorInfo::Checksum { name: _, len } => RuntimeLenInfo::fixed(*len),
210        }
211    }
212
213    pub fn write_dissect_fn(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
214        match self {
215            DeclDissectorInfo::Sequence {
216                name,
217                fields,
218                children: _,
219                constraints,
220            } => {
221                writedoc!(
222                    writer,
223                    r#"
224                    -- {comments}
225                    function {name}_dissect(buffer, pinfo, tree, fields, path)
226                        local i = 0
227                        local field_values = {{}}
228                    "#,
229                    comments = self.to_comments(),
230                )?;
231                for field in fields {
232                    field.write_dissect_fn(&mut writer.indent())?;
233                }
234                writedoc!(
235                    writer,
236                    r#"
237                        return i
238                    end
239                    "#
240                )?;
241                let constraints_lua = if constraints.is_empty() {
242                    String::from("true")
243                } else {
244                    constraints
245                        .iter()
246                        .map(|c| c.to_lua_expr())
247                        .collect::<Vec<_>>()
248                        .join(" and ")
249                };
250                writedoc!(
251                    writer,
252                    r#"
253                    function {name}_match_constraints(field_values, path)
254                        return {constraints_lua}
255                    end
256                    "#
257                )?;
258            }
259            DeclDissectorInfo::Enum { .. } => {}
260            DeclDissectorInfo::Checksum { .. } => {}
261        }
262        Ok(())
263    }
264
265    fn name(&self) -> &str {
266        match self {
267            DeclDissectorInfo::Sequence { name, .. } => name,
268            DeclDissectorInfo::Enum { name, .. } => name,
269            DeclDissectorInfo::Checksum { name, .. } => name,
270        }
271    }
272}
273
274impl DeclExt for Decl<analyzer::ast::Annotation> {
275    fn to_dissector_info(&self, scope: &Scope) -> DeclDissectorInfo {
276        match &self.desc {
277            DeclDesc::Enum { id, tags, width } => DeclDissectorInfo::Enum {
278                name: id.clone(),
279                values: tags.clone(),
280                len: BitLen(*width),
281            },
282            DeclDesc::Checksum { id, width, .. } => DeclDissectorInfo::Checksum {
283                name: id.clone(),
284                len: BitLen(*width),
285            },
286            DeclDesc::CustomField { id, .. }
287            | DeclDesc::Packet { id, .. }
288            | DeclDesc::Struct { id, .. }
289            | DeclDesc::Group { id, .. } => {
290                let mut bit_offset = BitLen(0);
291                DeclDissectorInfo::Sequence {
292                    name: id.clone(),
293                    fields: {
294                        let mut field_dissector_infos = vec![];
295                        let mut ctx = FieldContext::new(scope);
296                        for field in self.fields() {
297                            if let Some(dissector_info) = field.to_dissector_info(
298                                &mut ctx,
299                                &bit_offset,
300                                self,
301                                field_dissector_infos.last_mut(),
302                            ) {
303                                bit_offset.0 =
304                                    (bit_offset.0 + dissector_info.len().bit_offset().0) % 8;
305                                field_dissector_infos.push(dissector_info);
306                            }
307                        }
308                        field_dissector_infos
309                    },
310                    children: scope
311                        .iter_children(self)
312                        .map(|child| child.to_dissector_info(scope))
313                        .collect::<Vec<_>>(),
314                    constraints: self
315                        .constraints()
316                        .map(|constraint| constraint.to_dissector_info(scope, self))
317                        .collect::<Vec<_>>(),
318                }
319            }
320            DeclDesc::Test { .. } => unimplemented!(),
321        }
322    }
323}
324
325trait ConstraintExt {
326    fn to_dissector_info(
327        &self,
328        scope: &Scope,
329        decl: &Decl<analyzer::ast::Annotation>,
330    ) -> ConstraintDissectorInfo;
331}
332
333impl ConstraintExt for Constraint {
334    fn to_dissector_info(
335        &self,
336        scope: &Scope,
337        decl: &Decl<analyzer::ast::Annotation>,
338    ) -> ConstraintDissectorInfo {
339        match self {
340            Constraint {
341                id,
342                loc: _,
343                value: Some(v),
344                tag_id: None,
345            } => ConstraintDissectorInfo::ValueMatch {
346                field: id.clone(),
347                value: *v,
348            },
349            Constraint {
350                id,
351                loc: _,
352                value: None,
353                tag_id: Some(enum_tag),
354            } => {
355                fn find_ancestor_field<'a>(
356                    scope: &'a Scope,
357                    decl: &'a Decl<analyzer::ast::Annotation>,
358                    predicate: impl Fn(&Field<analyzer::ast::Annotation>) -> bool,
359                ) -> Option<&'a Field<analyzer::ast::Annotation>> {
360                    match decl.fields().find(|f| predicate(f)) {
361                        Some(x) => Some(x),
362                        None => scope
363                            .get_parent(decl)
364                            .and_then(|parent| find_ancestor_field(scope, parent, predicate)),
365                    }
366                }
367                let parent_decl = scope.get_parent(decl).unwrap();
368                ConstraintDissectorInfo::EnumMatch {
369                    field: id.clone(),
370                    enum_type: find_ancestor_field(scope, parent_decl, |f| f.id() == Some(id))
371                        .map(|f| match &f.desc {
372                            FieldDesc::Typedef { id: _, type_id } => type_id.clone(),
373                            _ => unreachable!(),
374                        })
375                        .unwrap_or_else(|| {
376                            panic!("Unable to find field `{id}` in parent {parent_decl:?}")
377                        }),
378                    enum_value: enum_tag.clone(),
379                }
380            }
381            _ => unreachable!(),
382        }
383    }
384}
385
386#[derive(Debug, Clone)]
387pub enum ConstraintDissectorInfo {
388    EnumMatch {
389        field: String,
390        enum_type: String,
391        enum_value: String,
392    },
393    ValueMatch {
394        field: String,
395        value: usize,
396    },
397}
398
399impl ConstraintDissectorInfo {
400    pub fn to_lua_expr(&self) -> String {
401        match self {
402            ConstraintDissectorInfo::EnumMatch {
403                field,
404                enum_type,
405                enum_value,
406            } => {
407                format!(
408                    r#"{enum_type}_enum:match("{enum_value}", field_values[path .. ".{field}"])"#
409                )
410            }
411            ConstraintDissectorInfo::ValueMatch { field, value } => {
412                format!(r#"field_values[path .. ".{field}"] == {value}"#)
413            }
414        }
415    }
416}
417
418trait FieldExt {
419    fn to_dissector_info(
420        &self,
421        ctx: &mut FieldContext,
422        bit_offset: &BitLen,
423        decl: &Decl<pdl_compiler::analyzer::ast::Annotation>,
424        last_field: Option<&mut FieldDissectorInfo>,
425    ) -> Option<FieldDissectorInfo>;
426}
427
428#[derive(Debug, Clone)]
429pub struct CommonFieldDissectorInfo {
430    /// Actual name of the field (the string that appears in the tree).
431    display_name: String,
432    /// Filter name of the field (the string that is used in filters).
433    abbr: String,
434    bit_offset: BitLen,
435    endian: EndiannessValue,
436    comments: Option<String>,
437}
438
439#[derive(Debug, Clone)]
440pub struct ArrayFieldDissectorInfo {
441    /// Number of items in the array, or `None` if the array is unbounded
442    count: Option<usize>,
443    size_modifier: Option<String>,
444    pad_to_size: Option<usize>,
445    has_size_field: bool,
446    has_count_field: bool,
447}
448
449#[derive(Debug, Clone)]
450pub enum FieldDissectorInfo {
451    Scalar {
452        common: CommonFieldDissectorInfo,
453        ftype: FType,
454        /// The length this field takes before repetition.
455        len: RuntimeLenInfo,
456        /// A lua-expression that yields a boolean value. If the boolean result
457        /// is false, a warning will be shown in the dissected info. `value` is
458        /// a variable that can be used to get the value of this field.
459        validate_expr: Option<String>,
460        /// (optional field name, match value)
461        optional_field: Option<(String, usize)>,
462    },
463    Payload {
464        common: CommonFieldDissectorInfo,
465        ftype: FType,
466        /// The length this field takes before repetition.
467        len: RuntimeLenInfo,
468        children: Vec<String>,
469    },
470    Typedef {
471        common: CommonFieldDissectorInfo,
472        decl: Box<DeclDissectorInfo>,
473        /// (optional field name, match value)
474        optional_field: Option<(String, usize)>,
475    },
476    TypedefArray {
477        common: CommonFieldDissectorInfo,
478        decl: Box<DeclDissectorInfo>,
479        array_info: ArrayFieldDissectorInfo,
480    },
481    ScalarArray {
482        common: CommonFieldDissectorInfo,
483        ftype: FType,
484        item_len: BitLen,
485        array_info: ArrayFieldDissectorInfo,
486    },
487}
488
489impl FieldDissectorInfo {
490    pub fn to_comments(&self) -> String {
491        if log::log_enabled!(log::Level::Debug) {
492            format!("{self:?}")
493        } else {
494            match self {
495                FieldDissectorInfo::Scalar {
496                    common: CommonFieldDissectorInfo { display_name, .. },
497                    ..
498                } => format!("Scalar: {display_name}"),
499                FieldDissectorInfo::Payload {
500                    common: CommonFieldDissectorInfo { display_name, .. },
501                    ..
502                } => format!("Payload: {display_name}"),
503                FieldDissectorInfo::Typedef {
504                    common: CommonFieldDissectorInfo { display_name, .. },
505                    ..
506                } => format!("Typedef: {display_name}"),
507                FieldDissectorInfo::TypedefArray {
508                    common: CommonFieldDissectorInfo { display_name, .. },
509                    ..
510                } => format!("TypedefArray: {display_name}"),
511                FieldDissectorInfo::ScalarArray {
512                    common: CommonFieldDissectorInfo { display_name, .. },
513                    ..
514                } => format!("ScalarArray: {display_name}"),
515            }
516        }
517    }
518
519    pub fn is_unaligned(&self) -> bool {
520        match self {
521            FieldDissectorInfo::Scalar { common, ftype, .. }
522            | FieldDissectorInfo::Payload { common, ftype, .. } => {
523                common.bit_offset.0 % 8 != 0
524                    || ftype.0.map(|len| len.0 % 8 != 0).unwrap_or_default()
525            }
526            _ => false,
527        }
528    }
529
530    /// Returns a tuple (field_name, lua_expression, field_length), or `None` if no ProtoFields
531    /// need to be defined.
532    pub fn field_declaration(
533        &self,
534        writer: &mut impl std::io::Write,
535    ) -> Result<(), std::io::Error> {
536        match self {
537            FieldDissectorInfo::Scalar { common, ftype, .. }
538            | FieldDissectorInfo::ScalarArray { common, ftype, .. }
539            | FieldDissectorInfo::Payload { common, ftype, .. } => {
540                let CommonFieldDissectorInfo {
541                    display_name,
542                    abbr,
543                    bit_offset,
544                    endian,
545                    comments,
546                } = common;
547                let bitlen = ftype
548                    .0
549                    .map(|v| v.to_string())
550                    .unwrap_or_else(|| String::from("nil"));
551                if self.is_unaligned() {
552                    writedoc!(
553                        writer,
554                        r#"
555                        fields[path .. ".{abbr}"] = UnalignedProtoField:new({{
556                            name = "{display_name}",
557                            abbr = path .. ".{abbr}",
558                            ftype = {ftype},
559                            bitoffset = {bit_offset},
560                            bitlen = {bitlen},
561                            is_little_endian = {is_le},
562                            description = {description},
563                        }})
564                        "#,
565                        ftype = ftype.to_lua_expr(),
566                        is_le = *endian == EndiannessValue::LittleEndian,
567                        description = comments.as_deref().to_lua_expr(),
568                    )?;
569                } else {
570                    writedoc!(
571                        writer,
572                        r#"
573                        fields[path .. ".{abbr}"] = AlignedProtoField:new({{
574                            name = "{display_name}",
575                            abbr = path .. ".{abbr}",
576                            ftype = {ftype},
577                            bitlen = {bitlen},
578                            is_little_endian = {is_le},
579                            description = {description},
580                        }})
581                        "#,
582                        ftype = ftype.to_lua_expr(),
583                        is_le = *endian == EndiannessValue::LittleEndian,
584                        description = comments.as_deref().to_lua_expr(),
585                    )?;
586                }
587            }
588            FieldDissectorInfo::Typedef { common, decl, .. }
589            | FieldDissectorInfo::TypedefArray { common, decl, .. } => {
590                let CommonFieldDissectorInfo {
591                    display_name,
592                    abbr,
593                    bit_offset,
594                    endian,
595                    comments,
596                } = common;
597                match decl.as_ref() {
598                    DeclDissectorInfo::Sequence { fields, .. } => {
599                        for field in fields {
600                            field.field_declaration(writer)?;
601                        }
602                    }
603                    DeclDissectorInfo::Enum {
604                        name: type_name,
605                        values: _,
606                        len,
607                    } => {
608                        let ftype = FType(Some(*len));
609                        if len.0 % 8 == 0 {
610                            writedoc!(
611                                writer,
612                                r#"
613                            fields[path .. ".{abbr}"] = AlignedProtoField:new({{
614                                name = "{display_name}",
615                                abbr = path .. ".{abbr}",
616                                ftype = {ftype},
617                                valuestring = {type_name}_enum.matchers,
618                                base = base.RANGE_STRING,
619                                is_little_endian = {is_le},
620                                description = {description},
621                            }})
622                            "#,
623                                ftype = ftype.to_lua_expr(),
624                                is_le = *endian == EndiannessValue::LittleEndian,
625                                description = comments.as_deref().to_lua_expr(),
626                            )?;
627                        } else {
628                            writedoc!(
629                                writer,
630                                r#"
631                                fields[path .. ".{abbr}"] = UnalignedProtoField:new({{
632                                    name = "{display_name}",
633                                    abbr = path .. ".{abbr}",
634                                    ftype = {ftype},
635                                    valuestring = {type_name}_enum.matchers,
636                                    bitoffset = {bit_offset},
637                                    bitlen = {len},
638                                    is_little_endian = {is_le},
639                                }})
640                                "#,
641                                ftype = ftype.to_lua_expr(),
642                                is_le = *endian == EndiannessValue::LittleEndian,
643                            )?;
644                        }
645                    }
646                    DeclDissectorInfo::Checksum {
647                        name: _type_name,
648                        len,
649                    } => {
650                        let ftype = FType(Some(*len));
651                        writedoc!(
652                            writer,
653                            r#"
654                            fields[path .. ".{abbr}"] = AlignedProtoField:new({{
655                                name = "{display_name}",
656                                abbr = path .. ".{abbr}",
657                                ftype = {ftype},
658                                base = base.HEX,
659                                is_little_endian = {is_le},
660                            }})
661                            "#,
662                            ftype = ftype.to_lua_expr(),
663                            is_le = *endian == EndiannessValue::LittleEndian,
664                        )?;
665                    }
666                }
667            }
668        }
669        Ok(())
670    }
671
672    pub fn len(&self) -> RuntimeLenInfo {
673        match self {
674            Self::Scalar { len, .. } => len.clone(),
675            Self::Payload { len, .. } => len.clone(),
676            Self::Typedef { decl, .. } => decl.decl_len(),
677            Self::TypedefArray { decl, .. } => decl.decl_len(),
678            Self::ScalarArray { item_len, .. } => RuntimeLenInfo::Bounded {
679                referenced_fields: vec![],
680                constant_factor: *item_len,
681            },
682        }
683    }
684
685    pub fn write_dissect_fn(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
686        match self {
687            FieldDissectorInfo::Scalar {
688                common,
689                validate_expr,
690                optional_field,
691                ..
692            } => match optional_field {
693                Some((optional_field, optional_match_value)) => {
694                    writedoc!(
695                        writer,
696                        r#"
697                        if field_values[path .. ".{optional_field}"] == {optional_match_value} then
698                        "#
699                    )?;
700                    self.write_scalar_dissect(
701                        &mut writer.indent(),
702                        &common.abbr,
703                        &[],
704                        validate_expr.clone(),
705                    )?;
706                    writeln!(writer, "end")?;
707                }
708                None => {
709                    self.write_scalar_dissect(writer, &common.abbr, &[], validate_expr.clone())?
710                }
711            },
712            FieldDissectorInfo::Payload {
713                common, children, ..
714            } => {
715                self.write_scalar_dissect(writer, &common.abbr, children, None)?;
716            }
717            FieldDissectorInfo::Typedef {
718                common,
719                decl,
720                optional_field,
721            } => match optional_field {
722                Some((optional_field, optional_match_value)) => {
723                    writedoc!(
724                        writer,
725                        r#"
726                        if field_values[path .. ".{optional_field}"] == {optional_match_value} then
727                        "#
728                    )?;
729                    self.write_typedef_dissect(
730                        &mut writer.indent(),
731                        decl,
732                        &common.display_name,
733                        &common.abbr,
734                        common.endian,
735                    )?;
736                    writeln!(writer, "end")?;
737                }
738                None => self.write_typedef_dissect(
739                    writer,
740                    decl,
741                    &common.display_name,
742                    &common.abbr,
743                    common.endian,
744                )?,
745            },
746            FieldDissectorInfo::TypedefArray {
747                common,
748                decl,
749                array_info,
750            } => {
751                let CommonFieldDissectorInfo {
752                    display_name, abbr, ..
753                } = common;
754                self.write_array_dissect(writer, common, array_info, |w| {
755                    self.write_typedef_dissect(w, decl, display_name, abbr, common.endian)
756                })?;
757                if let Some(octet_size) = array_info.pad_to_size {
758                    writedoc!(
759                        writer,
760                        r#"
761                        if i - initial_i < {octet_size} then
762                            tree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: Expected a minimum of {octet_size} octets in field `{display_name}`")
763                        end
764                        "#
765                    )?;
766                }
767            }
768            FieldDissectorInfo::ScalarArray {
769                common, array_info, ..
770            } => {
771                self.write_array_dissect(writer, common, array_info, |w| {
772                    self.write_scalar_dissect(w, &common.abbr, &[], None)
773                })?;
774            }
775        }
776        Ok(())
777    }
778
779    pub fn write_scalar_dissect(
780        &self,
781        writer: &mut impl std::io::Write,
782        abbr: &str,
783        children: &[String],
784        validate_expr: Option<String>,
785    ) -> std::io::Result<()> {
786        let len_expr = self.len().to_lua_expr();
787        writedoc!(
788            writer,
789            r#"
790            -- {comments}
791            local field_len = enforce_len_limit({len_expr}, buffer(i):len(), tree)
792            "#,
793            comments = self.to_comments()
794        )?;
795        lua_if_then_else(
796            &mut *writer,
797            children.iter().map(|child_name| {
798                (
799                    format!("{child_name}_match_constraints(field_values, path)"),
800                    move |w: &mut dyn std::io::Write| writedoc!(
801                        w,
802                        r#"
803                        local subtree = tree:add("{child_name}")
804                        local dissected_len = {child_name}_dissect(buffer(i, field_len), pinfo, subtree, fields, path .. ".{child_name}")
805                        i = i + dissected_len
806                        "#,
807                    )
808                )
809            }),
810            Some(|w: &mut dyn std::io::Write| {
811                writedoc!(
812                    w,
813                    r#"
814                    subtree, field_values[path .. ".{abbr}"], bitlen = fields[path .. ".{abbr}"]:dissect(tree, buffer(i), field_len)
815                    i = i + bitlen / 8
816                    "#,
817                )?;
818                if let Some(validate) = validate_expr.as_ref() {
819                    writedoc!(
820                        w,
821                        r#"
822                        local value = field_values[path .. ".{abbr}"]
823                        if not ({validate}) then
824                            subtree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: Expected `{validate_escaped}` where value=" .. tostring(value))
825                        end
826                        "#,
827                        validate_escaped = validate.replace('\\', "\\\\").replace('"', "\\\"")
828                    )?;
829                }
830                Ok(())
831            }))?;
832        Ok(())
833    }
834
835    pub fn write_typedef_dissect(
836        &self,
837        writer: &mut impl std::io::Write,
838        decl: &DeclDissectorInfo,
839        name: &str,
840        abbr: &str,
841        endian: EndiannessValue,
842    ) -> std::io::Result<()> {
843        match decl {
844            DeclDissectorInfo::Sequence {
845                name: type_name, ..
846            } => {
847                let len_expr = decl.decl_len().to_lua_expr();
848                writedoc!(
849                    writer,
850                    r#"
851                    -- {comments}
852                    local field_len = enforce_len_limit({len_expr}, buffer(i):len(), tree)
853                    local subtree = tree:add(buffer(i, field_len), "{name}")
854                    local dissected_len = {type_name}_dissect(buffer(i, field_len), pinfo, subtree, fields, path)
855                    subtree:set_len(dissected_len)
856                    i = i + dissected_len
857                    "#,
858                    comments = self.to_comments(),
859                )?;
860            }
861            DeclDissectorInfo::Enum {
862                name: type_name, ..
863            } => {
864                let len_expr = self.len().to_lua_expr();
865                writedoc!(
866                    writer,
867                    r#"
868                    -- {comments}
869                    local field_len = enforce_len_limit(math.ceil({len_expr}), buffer(i):len(), tree)
870                    subtree, field_values[path .. ".{name}"], bitlen = fields[path .. ".{abbr}"]:dissect(tree, buffer(i), field_len)
871                    if {type_name}_enum.by_value[field_values[path .. ".{name}"]] == nil then
872                        tree:add_expert_info(PI_MALFORMED, PI_WARN, "Unknown enum value: " .. field_values[path .. ".{name}"])
873                    end
874                    i = i + bitlen / 8
875                    "#,
876                    comments = self.to_comments(),
877                )?;
878            }
879            DeclDissectorInfo::Checksum {
880                name: _type_name,
881                len,
882            } => {
883                let add_fn = match endian {
884                    EndiannessValue::LittleEndian => "add_le",
885                    EndiannessValue::BigEndian => "add",
886                };
887                let len_expr = self.len().to_lua_expr();
888                let buffer_value_function =
889                    buffer_value_lua_function(endian, &RuntimeLenInfo::fixed(*len));
890                writedoc!(
891                    writer,
892                    r#"
893                    -- {comments}
894                    local field_len = enforce_len_limit({len_expr}, buffer(i):len(), tree)
895                    field_values[path .. ".{name}"] = buffer(i, field_len):{buffer_value_function}
896                    if field_len ~= 0 then
897                        tree:{add_fn}(fields[path .. ".{abbr}"].field, buffer(i, field_len))
898                        i = i + field_len
899                    end
900                    "#,
901                    comments = self.to_comments(),
902                )?;
903            }
904        }
905        Ok(())
906    }
907
908    fn write_array_dissect<W: std::io::Write>(
909        &self,
910        writer: &mut W,
911        common_info: &CommonFieldDissectorInfo,
912        array_info: &ArrayFieldDissectorInfo,
913        write_item_dissect: impl Fn(&mut IndentWriter<&mut W>) -> Result<(), std::io::Error>,
914    ) -> Result<(), std::io::Error> {
915        let CommonFieldDissectorInfo {
916            display_name, abbr, ..
917        } = common_info;
918        let ArrayFieldDissectorInfo {
919            count,
920            size_modifier,
921            ..
922        } = array_info;
923        if size_modifier.is_some() {
924            assert!(
925                array_info.has_size_field,
926                "Size modifier is defined but a size field is not found for `{abbr}`",
927                abbr = common_info.abbr,
928            );
929        }
930        if count.is_some() {
931            assert!(
932                !array_info.has_count_field,
933                "Count field is defined for `{abbr}`, but it has fixed item count",
934                abbr = common_info.abbr,
935            );
936        }
937        assert!(
938            !((count.is_some() || array_info.has_count_field) && array_info.has_size_field),
939            "Size and count cannot be specified for the same array `{abbr}`"
940        );
941        writedoc!(
942            writer,
943            r#"
944            -- {comments}
945            local initial_i = i
946            "#,
947            comments = self.to_comments(),
948        )?;
949        if array_info.has_count_field {
950            writedoc!(
951                writer,
952                r#"
953                for j=1,field_values[path .. ".{abbr}_count"] do
954                    -- Warn if there isn't enough elements to fit the expected count
955                    if i >= buffer:len() and j <= {count} then
956                        tree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: Expected " .. {count} .. " `{display_name}` items but only found " .. (j - 1))
957                        break
958                    end
959                "#,
960                count = format!(r#"field_values[path .. ".{abbr}_count"]"#),
961            )?;
962        } else if let Some(count) = count {
963            writedoc!(
964                writer,
965                r#"
966                for j=1,{count} do
967                    -- Warn if there isn't enough elements to fit the expected count
968                    if i >= buffer:len() and j <= {count} then
969                        tree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: Expected {count} `{display_name}` items but only found " .. (j - 1))
970                        break
971                    end
972                "#
973            )?;
974        } else if array_info.has_size_field {
975            // Check that the array doesn't exceed the size() field
976            writedoc!(
977                writer,
978                r#"
979                if initial_i + field_values[path .. ".{abbr}_size"]{size_modifier} > buffer:len() then
980                    tree:add_expert_info(PI_MALFORMED, PI_WARN, "Error: Size({display_name}) is greater than the number of remaining bytes")
981                end
982                while i < buffer:len() and i - initial_i < field_values[path .. ".{abbr}_size"]{size_modifier} do
983                "#,
984                size_modifier = size_modifier.as_deref().unwrap_or_default(),
985            )?;
986        } else {
987            writedoc!(writer, r#"while i < buffer:len() do"#)?;
988        }
989        write_item_dissect(&mut writer.indent())?;
990        writeln!(writer, "end")?;
991        Ok(())
992    }
993}
994
995impl FieldExt for Field<analyzer::ast::Annotation> {
996    fn to_dissector_info(
997        &self,
998        ctx: &mut FieldContext,
999        bit_offset: &BitLen,
1000        decl: &Decl<pdl_compiler::analyzer::ast::Annotation>,
1001        last_field: Option<&mut FieldDissectorInfo>,
1002    ) -> Option<FieldDissectorInfo> {
1003        debug!(
1004            "Write field: {:?}\nannot={:?}\ndecl={:?}",
1005            self, self.annot, decl
1006        );
1007        match &self.desc {
1008            FieldDesc::Checksum { field_id: _ } => {
1009                // This is the `_checksum_start_` field.
1010                // Actual checksum field is a TypeDef.
1011                None
1012            }
1013            FieldDesc::Padding { size: octet_size } => {
1014                match last_field.unwrap() {
1015                    FieldDissectorInfo::TypedefArray {
1016                        common, array_info, ..
1017                    }
1018                    | FieldDissectorInfo::ScalarArray {
1019                        common, array_info, ..
1020                    } => {
1021                        common.display_name = format!(
1022                            "{display_name} (Padded)",
1023                            display_name = common.display_name
1024                        );
1025                        array_info.pad_to_size = Some(*octet_size);
1026                    }
1027                    _ => unreachable!(),
1028                }
1029                None
1030            }
1031            FieldDesc::Size { field_id, width } => {
1032                let ftype = FType::from(self.annot.size);
1033                Some(FieldDissectorInfo::Scalar {
1034                    common: CommonFieldDissectorInfo {
1035                        display_name: format!(
1036                            "Size({field_name})",
1037                            field_name = match field_id.as_str() {
1038                                "_payload_" => "Payload",
1039                                _ => field_id,
1040                            }
1041                        ),
1042                        abbr: format!("{field_id}_size"),
1043                        bit_offset: *bit_offset,
1044                        endian: ctx.scope.file.endianness.value,
1045                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1046                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1047                    },
1048                    ftype,
1049                    len: RuntimeLenInfo::fixed(BitLen(*width)),
1050                    validate_expr: None,
1051                    optional_field: None,
1052                })
1053            }
1054            FieldDesc::Count { field_id, width } => {
1055                let ftype = FType::from(self.annot.size);
1056                Some(FieldDissectorInfo::Scalar {
1057                    common: CommonFieldDissectorInfo {
1058                        display_name: format!(
1059                            "Count({field_id})",
1060                            field_id = match field_id.as_str() {
1061                                "_payload_" => "Payload",
1062                                _ => field_id,
1063                            }
1064                        ),
1065                        abbr: format!("{field_id}_count"),
1066                        bit_offset: *bit_offset,
1067                        endian: ctx.scope.file.endianness.value,
1068                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1069                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1070                    },
1071                    ftype,
1072                    len: RuntimeLenInfo::fixed(BitLen(width * 8)),
1073                    validate_expr: None,
1074                    optional_field: None,
1075                })
1076            }
1077            FieldDesc::ElementSize { .. } => {
1078                // This `_elementsize_` field is undocumented (in
1079                // https://github.com/google/pdl/blob/main/doc/reference.md) and untested in the PDL
1080                // repo. Ignore it for now.
1081                unimplemented!()
1082            }
1083            FieldDesc::Body => {
1084                let children = ctx
1085                    .scope
1086                    .iter_children(decl)
1087                    .filter_map(|child_decl| child_decl.id().map(|c| c.to_string()))
1088                    .collect::<Vec<_>>();
1089                let ftype = FType::from(self.annot.size);
1090                let mut field_len = RuntimeLenInfo::empty();
1091                field_len.add_len_field("_body__size".into(), BitLen(0));
1092                Some(FieldDissectorInfo::Payload {
1093                    common: CommonFieldDissectorInfo {
1094                        display_name: String::from("Body"),
1095                        abbr: "_body_".into(),
1096                        bit_offset: *bit_offset,
1097                        endian: ctx.scope.file.endianness.value,
1098                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1099                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1100                    },
1101                    ftype,
1102                    len: field_len,
1103                    children,
1104                })
1105            }
1106            FieldDesc::Payload { size_modifier } => {
1107                let mut field_len = RuntimeLenInfo::empty();
1108                field_len.add_len_field(
1109                    "_payload__size".into(),
1110                    size_modifier
1111                        .as_ref()
1112                        .map(|s| BitLen(s.parse::<usize>().unwrap() * 8))
1113                        .unwrap_or_default(),
1114                );
1115                Some(FieldDissectorInfo::Payload {
1116                    common: CommonFieldDissectorInfo {
1117                        display_name: String::from("Payload"),
1118                        abbr: "_payload_".into(),
1119                        bit_offset: *bit_offset,
1120                        endian: ctx.scope.file.endianness.value,
1121                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1122                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1123                    },
1124                    ftype: FType::from(self.annot.size),
1125                    len: field_len,
1126                    children: vec![],
1127                })
1128            }
1129            FieldDesc::FixedScalar { width, value } => {
1130                ctx.num_fixed += 1;
1131                Some(FieldDissectorInfo::Scalar {
1132                    common: CommonFieldDissectorInfo {
1133                        display_name: "Fixed value".into(),
1134                        abbr: format!("_fixed_{}", ctx.num_fixed - 1),
1135                        bit_offset: *bit_offset,
1136                        endian: ctx.scope.file.endianness.value,
1137                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1138                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1139                    },
1140                    ftype: FType::from(self.annot.size),
1141                    len: RuntimeLenInfo::fixed(BitLen(*width)),
1142                    validate_expr: Some(format!("value == {value}")),
1143                    optional_field: None,
1144                })
1145            }
1146            FieldDesc::FixedEnum { enum_id, tag_id } => {
1147                ctx.num_fixed += 1;
1148                let referenced_enum = ctx.scope.typedef[enum_id].to_dissector_info(ctx.scope);
1149                let ftype = FType::from(self.annot.size);
1150                Some(FieldDissectorInfo::Scalar {
1151                    common: CommonFieldDissectorInfo {
1152                        display_name: format!("Fixed value: {tag_id}"),
1153                        abbr: format!("_fixed_{}", ctx.num_fixed - 1),
1154                        bit_offset: *bit_offset,
1155                        endian: ctx.scope.file.endianness.value,
1156                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1157                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1158                    },
1159                    ftype,
1160                    len: referenced_enum.decl_len(),
1161                    validate_expr: Some(format!(r#"{enum_id}_enum:match("{tag_id}", value)"#)),
1162                    optional_field: None,
1163                })
1164            }
1165            FieldDesc::Reserved { width } => {
1166                ctx.num_reserved += 1;
1167                Some(FieldDissectorInfo::Scalar {
1168                    common: CommonFieldDissectorInfo {
1169                        display_name: String::from("Reserved"),
1170                        abbr: format!("_reserved_{}", ctx.num_reserved - 1),
1171                        bit_offset: *bit_offset,
1172                        endian: ctx.scope.file.endianness.value,
1173                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1174                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1175                    },
1176                    ftype: FType(Some(BitLen(*width))),
1177                    len: RuntimeLenInfo::fixed(BitLen(*width)),
1178                    validate_expr: None,
1179                    optional_field: None,
1180                })
1181            }
1182            FieldDesc::Array {
1183                id,
1184                width,
1185                type_id,
1186                size_modifier,
1187                size,
1188            } => match (width, type_id) {
1189                (None, Some(type_id)) => Some(FieldDissectorInfo::TypedefArray {
1190                    common: CommonFieldDissectorInfo {
1191                        display_name: id.clone(),
1192                        abbr: id.clone(),
1193                        bit_offset: *bit_offset,
1194                        endian: ctx.scope.file.endianness.value,
1195                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1196                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1197                    },
1198                    decl: Box::new(
1199                        ctx.scope
1200                            .typedef
1201                            .get(type_id)
1202                            .copied()
1203                            .expect("Unresolved typedef")
1204                            .to_dissector_info(ctx.scope),
1205                    ),
1206                    array_info: ArrayFieldDissectorInfo {
1207                        size_modifier: size_modifier.clone(),
1208                        count: *size,
1209                        pad_to_size: None,
1210                        has_size_field: has_size_field(decl, id),
1211                        has_count_field: has_count_field(decl, id),
1212                    },
1213                }),
1214                (Some(width), None) => Some(FieldDissectorInfo::ScalarArray {
1215                    common: CommonFieldDissectorInfo {
1216                        display_name: id.clone(),
1217                        abbr: id.clone(),
1218                        bit_offset: BitLen::default(),
1219                        endian: ctx.scope.file.endianness.value,
1220                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1221                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1222                    },
1223                    array_info: ArrayFieldDissectorInfo {
1224                        count: *size,
1225                        size_modifier: size_modifier.clone(),
1226                        pad_to_size: None,
1227                        has_size_field: has_size_field(decl, id),
1228                        has_count_field: has_count_field(decl, id),
1229                    },
1230                    ftype: FType(Some(BitLen(*width))),
1231                    item_len: BitLen(*width),
1232                }),
1233                _ => unreachable!(),
1234            },
1235            FieldDesc::Scalar { id, width } => Some(FieldDissectorInfo::Scalar {
1236                common: CommonFieldDissectorInfo {
1237                    display_name: String::from(id),
1238                    abbr: id.into(),
1239                    bit_offset: *bit_offset,
1240                    endian: ctx.scope.file.endianness.value,
1241                    comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1242                        .map(|comment| unwrap_comment(&comment.text).to_string()),
1243                },
1244                ftype: FType(Some(BitLen(*width))),
1245                len: RuntimeLenInfo::fixed(BitLen(*width)),
1246                validate_expr: None,
1247                optional_field: ctx.optional_decl.get(id).cloned(),
1248            }),
1249            FieldDesc::Flag {
1250                id,
1251                optional_field_id,
1252                set_value,
1253            } => {
1254                ctx.optional_decl
1255                    .insert(optional_field_id.clone(), (id.clone(), *set_value));
1256                Some(FieldDissectorInfo::Scalar {
1257                    common: CommonFieldDissectorInfo {
1258                        display_name: String::from(id),
1259                        abbr: id.into(),
1260                        bit_offset: *bit_offset,
1261                        endian: ctx.scope.file.endianness.value,
1262                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1263                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1264                    },
1265                    ftype: FType::from(self.annot.size),
1266                    len: RuntimeLenInfo::fixed(BitLen(1)),
1267                    validate_expr: None,
1268                    optional_field: None,
1269                })
1270            }
1271            FieldDesc::Typedef { id, type_id } => {
1272                let dissector_info = ctx
1273                    .scope
1274                    .typedef
1275                    .get(type_id)
1276                    .copied()
1277                    .expect("Unresolved typedef")
1278                    .to_dissector_info(ctx.scope);
1279                Some(FieldDissectorInfo::Typedef {
1280                    common: CommonFieldDissectorInfo {
1281                        display_name: id.into(),
1282                        abbr: id.into(),
1283                        bit_offset: *bit_offset,
1284                        endian: ctx.scope.file.endianness.value,
1285                        comments: find_comments_on_same_line(ctx.scope.file, &self.loc)
1286                            .map(|comment| unwrap_comment(&comment.text).to_string()),
1287                    },
1288                    decl: Box::new(dissector_info),
1289                    optional_field: ctx.optional_decl.get(id).cloned(),
1290                })
1291            }
1292            FieldDesc::Group { .. } => unreachable!(), // Groups are inlined by the time they reach here
1293        }
1294    }
1295}
1296
1297fn has_size_field(decl: &Decl<analyzer::ast::Annotation>, id: &str) -> bool {
1298    decl.fields().any(|field| match &field.desc {
1299        FieldDesc::Size { field_id, .. } => field_id == id,
1300        _ => false,
1301    })
1302}
1303
1304fn has_count_field(decl: &Decl<analyzer::ast::Annotation>, id: &str) -> bool {
1305    decl.fields().any(|field| match &field.desc {
1306        FieldDesc::Count { field_id, .. } => field_id == id,
1307        _ => false,
1308    })
1309}
1310
1311/// Command line arguments for this tool.
1312#[derive(clap::Parser)]
1313pub struct Args {
1314    /// The PDL file to generate the Wireshark dissector from. See
1315    /// https://github.com/google/pdl/blob/main/doc/reference.md.
1316    pub pdl_file: PathBuf,
1317    /// The type in the PDL file to generate dissector for.
1318    ///
1319    /// Since a PDL file can contain multiple packet declarations, this
1320    /// specifies which packet the dissector should be generated for.
1321    pub target_packets: Vec<String>,
1322}
1323
1324fn get_desc_id<A: Annotation>(desc: &DeclDesc<A>) -> Option<String> {
1325    match desc {
1326        DeclDesc::Checksum { id, .. }
1327        | DeclDesc::CustomField { id, .. }
1328        | DeclDesc::Enum { id, .. }
1329        | DeclDesc::Packet { id, .. }
1330        | DeclDesc::Struct { id, .. }
1331        | DeclDesc::Group { id, .. } => Some(id.clone()),
1332        DeclDesc::Test { .. } => None,
1333    }
1334}
1335
1336fn generate_for_decl(
1337    decl_name: &str,
1338    decl: &Decl<pdl_compiler::analyzer::ast::Annotation>,
1339    scope: &Scope<pdl_compiler::analyzer::ast::Annotation>,
1340    writer: &mut impl std::io::Write,
1341) -> std::io::Result<()> {
1342    let target_dissector_info = decl.to_dissector_info(scope);
1343
1344    writedoc!(
1345        writer,
1346        r#"
1347        -- Protocol definition for "{decl_name}"
1348        {decl_name}_protocol = Proto("{decl_name}",  "{decl_name}")
1349        "#,
1350    )?;
1351
1352    target_dissector_info.write_main_dissector(writer)?;
1353    Ok(())
1354}
1355
1356pub fn run(
1357    args: Args,
1358    sources: &mut SourceDatabase,
1359    writer: &mut impl std::io::Write,
1360) -> Result<(), Diagnostics> {
1361    let _ = env_logger::try_init();
1362
1363    let file = pdl_compiler::parser::parse_file(
1364        sources,
1365        args.pdl_file
1366            .to_str()
1367            .expect("pdl_file path should be a valid string"),
1368    )?;
1369    let analyzed_file = analyzer::analyze(&file)?;
1370    let scope = Scope::new(&analyzed_file)?;
1371    if args.target_packets.is_empty() {
1372        Err(Diagnostic::error().with_message("Target packet must be specified"))?
1373    }
1374
1375    write!(writer, "{}", include_str!("utils.lua"))?;
1376    for target_packet in args.target_packets {
1377        for decl in analyzed_file.declarations.iter() {
1378            let decl_dissector_info = decl.to_dissector_info(&scope);
1379            decl_dissector_info.write_proto_fields(writer)?;
1380            decl_dissector_info.write_dissect_fn(writer)?;
1381        }
1382        if target_packet == "_all_" {
1383            for decl in analyzed_file.declarations.iter() {
1384                if matches!(decl.desc, DeclDesc::Packet { .. }) {
1385                    generate_for_decl(decl.id().unwrap(), decl, &scope, writer)?;
1386                }
1387            }
1388        } else {
1389            let target_decl = analyzed_file.declarations.iter().find(|decl| {
1390                get_desc_id(&decl.desc)
1391                    .map(|id| id == target_packet)
1392                    .unwrap_or(false)
1393            });
1394            if let Some(decl) = target_decl {
1395                generate_for_decl(&target_packet, decl, &scope, writer)?;
1396            } else {
1397                Err(Diagnostic::error()
1398                    .with_message(format!("Unable to find declaration {target_packet:?}")))?;
1399            }
1400        }
1401    }
1402    Ok(())
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407    use std::{io::BufWriter, path::PathBuf};
1408
1409    use pdl_compiler::ast::SourceDatabase;
1410
1411    use crate::{fakes::wireshark_lua, run, Args};
1412
1413    #[test]
1414    fn test_bluetooth_hci() -> anyhow::Result<()> {
1415        let args = Args {
1416            pdl_file: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1417                .join("tests/compilation_test/bluetooth_hci.pdl"),
1418            target_packets: vec!["_all_".into()],
1419        };
1420        let lua = wireshark_lua()?;
1421        lua.load(run_with_args(args)).exec()?;
1422        Ok(())
1423    }
1424
1425    #[test]
1426    fn test_le_test_file() -> anyhow::Result<()> {
1427        // Copied from pdl-compiler/tests/canonical/le_test_file.pdl
1428        let args = Args {
1429            pdl_file: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1430                .join("tests/compilation_test/le_test_file.pdl"),
1431            target_packets: vec!["_all_".into()],
1432        };
1433        let lua = wireshark_lua()?;
1434        lua.load(run_with_args(args)).exec()?;
1435        Ok(())
1436    }
1437
1438    #[test]
1439    fn test_format_bitstring() -> anyhow::Result<()> {
1440        let lua = wireshark_lua()?;
1441        lua.load(include_str!("utils.lua")).exec()?;
1442        lua.load(mlua::chunk! {
1443            function assert_eq(expected, actual)
1444                assert(expected == actual, "Expected \"" .. tostring(expected) .. "\" but was \"" .. tostring(actual) .. "\"")
1445            end
1446            assert_eq("0010 0101 01", format_bitstring("0010010101"));
1447            assert_eq("0010 0101", format_bitstring("00100101"));
1448            assert_eq("...0 0100 101. .... ..", format_bitstring("...00100101......."));
1449        })
1450        .exec()?;
1451        Ok(())
1452    }
1453
1454    fn run_with_args(args: Args) -> Vec<u8> {
1455        let mut writer = BufWriter::new(Vec::new());
1456        run(args, &mut SourceDatabase::new(), &mut writer).unwrap();
1457        writer.into_inner().unwrap()
1458    }
1459}