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 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 display_name: String,
432 abbr: String,
434 bit_offset: BitLen,
435 endian: EndiannessValue,
436 comments: Option<String>,
437}
438
439#[derive(Debug, Clone)]
440pub struct ArrayFieldDissectorInfo {
441 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 len: RuntimeLenInfo,
456 validate_expr: Option<String>,
460 optional_field: Option<(String, usize)>,
462 },
463 Payload {
464 common: CommonFieldDissectorInfo,
465 ftype: FType,
466 len: RuntimeLenInfo,
468 children: Vec<String>,
469 },
470 Typedef {
471 common: CommonFieldDissectorInfo,
472 decl: Box<DeclDissectorInfo>,
473 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 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 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 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 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!(), }
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#[derive(clap::Parser)]
1313pub struct Args {
1314 pub pdl_file: PathBuf,
1317 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 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}