• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use std::collections::{hash_map::Entry, HashMap};
16 
17 use crate::ast;
18 use crate::parser;
19 
20 pub struct Schema<'a> {
21     pub packets_and_structs: HashMap<&'a str, PacketOrStruct<'a>>,
22     pub enums: HashMap<&'a str, Enum<'a>>,
23 }
24 
25 pub struct PacketOrStruct<'a> {
26     pub computed_offsets: HashMap<ComputedOffsetId<'a>, ComputedOffset<'a>>,
27     pub computed_values: HashMap<ComputedValueId<'a>, ComputedValue<'a>>,
28     /// whether the parse of this packet needs to know its length,
29     /// or if the packet can determine its own length
30     pub length: PacketOrStructLength,
31 }
32 
33 pub enum PacketOrStructLength {
34     Static(usize),
35     Dynamic,
36     NeedsExternal,
37 }
38 
39 pub struct Enum<'a> {
40     pub tags: &'a [ast::Tag],
41     pub width: usize,
42 }
43 
44 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
45 pub enum ComputedValueId<'a> {
46     // needed for array fields + varlength structs - note that this is in OCTETS, not BITS
47     // this always works since array entries are either structs (which are byte-aligned) or integer-octet-width scalars
48     FieldSize(&'a str),
49 
50     // needed for arrays with fixed element size (otherwise codegen will loop!)
51     FieldElementSize(&'a str), // note that this is in OCTETS, not BITS
52     FieldCount(&'a str),
53 
54     Custom(u16),
55 }
56 
57 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
58 pub enum ComputedOffsetId<'a> {
59     // these quantities are known by the runtime
60     HeaderStart,
61 
62     // if the packet needs its length, this will be supplied. otherwise it will be computed
63     PacketEnd,
64 
65     // these quantities will be computed and stored in computed_values
66     FieldOffset(&'a str),    // needed for all fields, measured in BITS
67     FieldEndOffset(&'a str), // needed only for Payload + Body fields, as well as variable-size structs (not arrays), measured in BITS
68     Custom(u16),
69     TrailerStart,
70 }
71 
72 pub enum ComputedValue<'a> {
73     Constant(usize),
74     CountStructsUpToSize {
75         base_id: ComputedOffsetId<'a>,
76         size: ComputedValueId<'a>,
77         struct_type: &'a str,
78     },
79     SizeOfNStructs {
80         base_id: ComputedOffsetId<'a>,
81         n: ComputedValueId<'a>,
82         struct_type: &'a str,
83     },
84     Product(ComputedValueId<'a>, ComputedValueId<'a>),
85     Divide(ComputedValueId<'a>, ComputedValueId<'a>),
86     Difference(ComputedOffsetId<'a>, ComputedOffsetId<'a>),
87     ValueAt {
88         offset: ComputedOffsetId<'a>,
89         width: usize,
90     },
91 }
92 
93 #[derive(Copy, Clone)]
94 pub enum ComputedOffset<'a> {
95     ConstantPlusOffsetInBits(ComputedOffsetId<'a>, i64),
96     SumWithOctets(ComputedOffsetId<'a>, ComputedValueId<'a>),
97     Alias(ComputedOffsetId<'a>),
98 }
99 
generate(file: &parser::ast::File) -> Result<Schema, String>100 pub fn generate(file: &parser::ast::File) -> Result<Schema, String> {
101     let mut schema = Schema { packets_and_structs: HashMap::new(), enums: HashMap::new() };
102     match file.endianness.value {
103         ast::EndiannessValue::LittleEndian => {}
104         _ => unimplemented!("Only little_endian endianness supported"),
105     };
106 
107     for decl in &file.declarations {
108         process_decl(&mut schema, decl);
109     }
110 
111     Ok(schema)
112 }
113 
process_decl<'a>(schema: &mut Schema<'a>, decl: &'a parser::ast::Decl)114 fn process_decl<'a>(schema: &mut Schema<'a>, decl: &'a parser::ast::Decl) {
115     match &decl.desc {
116         ast::DeclDesc::Enum { id, tags, width, .. } => process_enum(schema, id, tags, *width),
117         ast::DeclDesc::Packet { id, fields, .. } | ast::DeclDesc::Struct { id, fields, .. } => {
118             process_packet_or_struct(schema, id, fields)
119         }
120         ast::DeclDesc::Group { .. } => todo!(),
121         _ => unimplemented!("type {decl:?} not supported"),
122     }
123 }
124 
process_enum<'a>(schema: &mut Schema<'a>, id: &'a str, tags: &'a [ast::Tag], width: usize)125 fn process_enum<'a>(schema: &mut Schema<'a>, id: &'a str, tags: &'a [ast::Tag], width: usize) {
126     schema.enums.insert(id, Enum { tags, width });
127     schema.packets_and_structs.insert(
128         id,
129         PacketOrStruct {
130             computed_offsets: HashMap::new(),
131             computed_values: HashMap::new(),
132             length: PacketOrStructLength::Static(width),
133         },
134     );
135 }
136 
process_packet_or_struct<'a>( schema: &mut Schema<'a>, id: &'a str, fields: &'a [parser::ast::Field], )137 fn process_packet_or_struct<'a>(
138     schema: &mut Schema<'a>,
139     id: &'a str,
140     fields: &'a [parser::ast::Field],
141 ) {
142     schema.packets_and_structs.insert(id, compute_getters(schema, fields));
143 }
144 
compute_getters<'a>( schema: &Schema<'a>, fields: &'a [parser::ast::Field], ) -> PacketOrStruct<'a>145 fn compute_getters<'a>(
146     schema: &Schema<'a>,
147     fields: &'a [parser::ast::Field],
148 ) -> PacketOrStruct<'a> {
149     let mut prev_pos_id = None;
150     let mut curr_pos_id = ComputedOffsetId::HeaderStart;
151     let mut computed_values = HashMap::new();
152     let mut computed_offsets = HashMap::new();
153 
154     let mut cnt = 0;
155 
156     let one_id = ComputedValueId::Custom(cnt);
157     let one_val = ComputedValue::Constant(1);
158     cnt += 1;
159     computed_values.insert(one_id, one_val);
160 
161     let mut needs_length = false;
162 
163     for field in fields {
164         // populate this only if we are an array with a knowable size
165         let mut next_prev_pos_id = None;
166 
167         let next_pos = match &field.desc {
168             ast::FieldDesc::Reserved { width } => {
169                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
170             }
171             ast::FieldDesc::Scalar { id, width } => {
172                 computed_offsets
173                     .insert(ComputedOffsetId::FieldOffset(id), ComputedOffset::Alias(curr_pos_id));
174                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
175             }
176             ast::FieldDesc::FixedScalar { width, .. } => {
177                 let offset = *width;
178                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, offset as i64)
179             }
180             ast::FieldDesc::FixedEnum { enum_id, .. } => {
181                 let offset = schema.enums[enum_id.as_str()].width;
182                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, offset as i64)
183             }
184             ast::FieldDesc::Size { field_id, width } => {
185                 computed_values.insert(
186                     ComputedValueId::FieldSize(field_id),
187                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
188                 );
189                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
190             }
191             ast::FieldDesc::Count { field_id, width } => {
192                 computed_values.insert(
193                     ComputedValueId::FieldCount(field_id.as_str()),
194                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
195                 );
196                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
197             }
198             ast::FieldDesc::ElementSize { field_id, width } => {
199                 computed_values.insert(
200                     ComputedValueId::FieldElementSize(field_id),
201                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
202                 );
203                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
204             }
205             ast::FieldDesc::Group { .. } => {
206                 unimplemented!("this should be removed by the linter...")
207             }
208             ast::FieldDesc::Checksum { .. } => unimplemented!("checksum not supported"),
209             ast::FieldDesc::Body => {
210                 computed_offsets.insert(
211                     ComputedOffsetId::FieldOffset("_body_"),
212                     ComputedOffset::Alias(curr_pos_id),
213                 );
214                 let computed_size_id = ComputedValueId::FieldSize("_body_");
215                 let end_offset = if computed_values.contains_key(&computed_size_id) {
216                     ComputedOffset::SumWithOctets(curr_pos_id, computed_size_id)
217                 } else {
218                     if needs_length {
219                         panic!("only one variable-length field can exist")
220                     }
221                     needs_length = true;
222                     ComputedOffset::Alias(ComputedOffsetId::TrailerStart)
223                 };
224                 computed_offsets.insert(ComputedOffsetId::FieldEndOffset("_body_"), end_offset);
225                 end_offset
226             }
227             ast::FieldDesc::Payload { size_modifier } => {
228                 if size_modifier.is_some() {
229                     unimplemented!("size modifiers not supported")
230                 }
231                 computed_offsets.insert(
232                     ComputedOffsetId::FieldOffset("_payload_"),
233                     ComputedOffset::Alias(curr_pos_id),
234                 );
235                 let computed_size_id = ComputedValueId::FieldSize("_payload_");
236                 let end_offset = if computed_values.contains_key(&computed_size_id) {
237                     ComputedOffset::SumWithOctets(curr_pos_id, computed_size_id)
238                 } else {
239                     if needs_length {
240                         panic!("only one variable-length field can exist")
241                     }
242                     needs_length = true;
243                     ComputedOffset::Alias(ComputedOffsetId::TrailerStart)
244                 };
245                 computed_offsets.insert(ComputedOffsetId::FieldEndOffset("_payload_"), end_offset);
246                 end_offset
247             }
248             ast::FieldDesc::Array {
249                 id,
250                 width,
251                 type_id,
252                 size_modifier,
253                 size: statically_known_count,
254             } => {
255                 if size_modifier.is_some() {
256                     unimplemented!("size modifiers not supported")
257                 }
258 
259                 computed_offsets
260                     .insert(ComputedOffsetId::FieldOffset(id), ComputedOffset::Alias(curr_pos_id));
261 
262                 // there are a few parameters to consider when parsing arrays
263                 // 1: the count of elements
264                 // 2: the total byte size (possibly by subtracting out the len of the trailer)
265                 // 3: whether the structs know their own lengths
266                 // parsing is possible if we know (1 OR 2) AND 3
267 
268                 if let Some(count) = statically_known_count {
269                     computed_values
270                         .insert(ComputedValueId::FieldCount(id), ComputedValue::Constant(*count));
271                 }
272 
273                 let statically_known_width_in_bits = if let Some(type_id) = type_id {
274                     if let PacketOrStructLength::Static(len) =
275                         schema.packets_and_structs[type_id.as_str()].length
276                     {
277                         Some(len)
278                     } else {
279                         None
280                     }
281                 } else if let Some(width) = width {
282                     Some(*width)
283                 } else {
284                     unreachable!()
285                 };
286 
287                 // whether the count is known *prior* to parsing the field
288                 let is_count_known = computed_values.contains_key(&ComputedValueId::FieldCount(id));
289                 // whether the total field size is explicitly specified
290                 let is_total_size_known =
291                     computed_values.contains_key(&ComputedValueId::FieldSize(id));
292 
293                 let element_size = if let Some(type_id) = type_id {
294                     match schema.packets_and_structs[type_id.as_str()].length {
295                         PacketOrStructLength::Static(width) => {
296                             assert!(width % 8 == 0);
297                             Some(width / 8)
298                         }
299                         PacketOrStructLength::Dynamic => None,
300                         PacketOrStructLength::NeedsExternal => None,
301                     }
302                 } else if let Some(width) = width {
303                     assert!(width % 8 == 0);
304                     Some(width / 8)
305                 } else {
306                     unreachable!()
307                 };
308                 if let Some(element_size) = element_size {
309                     computed_values.insert(
310                         ComputedValueId::FieldElementSize(id),
311                         ComputedValue::Constant(element_size),
312                     );
313                 }
314 
315                 // whether we can know the length of each element in the array by greedy parsing,
316                 let structs_know_length = if let Some(type_id) = type_id {
317                     match schema.packets_and_structs[type_id.as_str()].length {
318                         PacketOrStructLength::Static(_) => true,
319                         PacketOrStructLength::Dynamic => true,
320                         PacketOrStructLength::NeedsExternal => {
321                             computed_values.contains_key(&ComputedValueId::FieldElementSize(id))
322                         }
323                     }
324                 } else {
325                     width.is_some()
326                 };
327 
328                 if !structs_know_length {
329                     panic!("structs need to know their own length, if they live in an array")
330                 }
331 
332                 let mut out = None;
333                 if let Some(count) = statically_known_count {
334                     if let Some(width) = statically_known_width_in_bits {
335                         // the fast path, if the count and width are statically known, is to just immediately multiply
336                         // otherwise this becomes a dynamic computation
337                         assert!(width % 8 == 0);
338                         computed_values.insert(
339                             ComputedValueId::FieldSize(id),
340                             ComputedValue::Constant(count * width / 8),
341                         );
342                         out = Some(ComputedOffset::ConstantPlusOffsetInBits(
343                             curr_pos_id,
344                             (count * width) as i64,
345                         ));
346                     }
347                 }
348 
349                 // note: this introduces a forward dependency with the total_size_id
350                 // however, the FieldSize(id) only depends on the FieldElementSize(id) if FieldCount() == true
351                 // thus, there will never be an infinite loop, since the FieldElementSize(id) only depends on the
352                 // FieldSize() if the FieldCount() is not unknown
353                 if !is_count_known {
354                     // the count is not known statically, or from earlier in the packet
355                     // thus, we must compute it from the total size of the field, known either explicitly or implicitly via the trailer
356                     // the fast path is to do a divide, but otherwise we need to loop over the TLVs
357                     computed_values.insert(
358                         ComputedValueId::FieldCount(id),
359                         if computed_values.contains_key(&ComputedValueId::FieldElementSize(id)) {
360                             ComputedValue::Divide(
361                                 ComputedValueId::FieldSize(id),
362                                 ComputedValueId::FieldElementSize(id),
363                             )
364                         } else {
365                             ComputedValue::CountStructsUpToSize {
366                                 base_id: curr_pos_id,
367                                 size: ComputedValueId::FieldSize(id),
368                                 struct_type: type_id.as_ref().unwrap(),
369                             }
370                         },
371                     );
372                 }
373 
374                 if let Some(out) = out {
375                     // we are paddable if the total size is known
376                     next_prev_pos_id = Some(curr_pos_id);
377                     out
378                 } else if is_total_size_known {
379                     // we are paddable if the total size is known
380                     next_prev_pos_id = Some(curr_pos_id);
381                     ComputedOffset::SumWithOctets(curr_pos_id, ComputedValueId::FieldSize(id))
382                 } else if is_count_known {
383                     // we are paddable if the total count is known, since structs know their lengths
384                     next_prev_pos_id = Some(curr_pos_id);
385 
386                     computed_values.insert(
387                         ComputedValueId::FieldSize(id),
388                         if computed_values.contains_key(&ComputedValueId::FieldElementSize(id)) {
389                             ComputedValue::Product(
390                                 ComputedValueId::FieldCount(id),
391                                 ComputedValueId::FieldElementSize(id),
392                             )
393                         } else {
394                             ComputedValue::SizeOfNStructs {
395                                 base_id: curr_pos_id,
396                                 n: ComputedValueId::FieldCount(id),
397                                 struct_type: type_id.as_ref().unwrap(),
398                             }
399                         },
400                     );
401                     ComputedOffset::SumWithOctets(curr_pos_id, ComputedValueId::FieldSize(id))
402                 } else {
403                     // we can try to infer the total size if we are still in the header
404                     // however, we are not paddable in this case
405                     next_prev_pos_id = None;
406 
407                     if needs_length {
408                         panic!("either the total size, or the count of elements in an array, must be known")
409                     }
410                     // now we are in the trailer
411                     computed_values.insert(
412                         ComputedValueId::FieldSize(id),
413                         ComputedValue::Difference(ComputedOffsetId::TrailerStart, curr_pos_id),
414                     );
415                     needs_length = true;
416                     ComputedOffset::Alias(ComputedOffsetId::TrailerStart)
417                 }
418             }
419             ast::FieldDesc::Padding { size } => {
420                 if let Some(prev_pos_id) = prev_pos_id {
421                     ComputedOffset::ConstantPlusOffsetInBits(prev_pos_id, *size as i64)
422                 } else {
423                     panic!("padding must follow array field with known total size")
424                 }
425             }
426             ast::FieldDesc::Typedef { id, type_id } => {
427                 computed_offsets
428                     .insert(ComputedOffsetId::FieldOffset(id), ComputedOffset::Alias(curr_pos_id));
429 
430                 match schema.packets_and_structs[type_id.as_str()].length {
431                     PacketOrStructLength::Static(len) => {
432                         ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, len as i64)
433                     }
434                     PacketOrStructLength::Dynamic => {
435                         computed_values.insert(
436                             ComputedValueId::FieldSize(id),
437                             ComputedValue::SizeOfNStructs {
438                                 base_id: curr_pos_id,
439                                 n: one_id,
440                                 struct_type: type_id,
441                             },
442                         );
443                         ComputedOffset::SumWithOctets(curr_pos_id, ComputedValueId::FieldSize(id))
444                     }
445                     PacketOrStructLength::NeedsExternal => {
446                         let end_offset = if let Entry::Vacant(entry) =
447                             computed_values.entry(ComputedValueId::FieldSize(id))
448                         {
449                             // its size is presently unknown
450                             if needs_length {
451                                 panic!(
452                                         "cannot have multiple variable-length fields in a single packet/struct"
453                                     )
454                             }
455                             // we are now in the trailer
456                             entry.insert(ComputedValue::Difference(
457                                 ComputedOffsetId::TrailerStart,
458                                 curr_pos_id,
459                             ));
460                             needs_length = true;
461                             ComputedOffset::Alias(ComputedOffsetId::TrailerStart)
462                         } else {
463                             ComputedOffset::SumWithOctets(
464                                 curr_pos_id,
465                                 ComputedValueId::FieldSize(id),
466                             )
467                         };
468                         computed_offsets.insert(ComputedOffsetId::FieldEndOffset(id), end_offset);
469                         end_offset
470                     }
471                 }
472 
473                 // it is possible to size a struct in this variant of PDL, even though the linter doesn't allow it
474             }
475         };
476 
477         prev_pos_id = next_prev_pos_id;
478         curr_pos_id = ComputedOffsetId::Custom(cnt);
479         cnt += 1;
480         computed_offsets.insert(curr_pos_id, next_pos);
481     }
482 
483     // TODO(aryarahul): simplify compute graph to improve trailer resolution?
484 
485     // we are now at the end of the packet
486     let length = if needs_length {
487         // if we needed the length, use the PacketEnd and length to reconstruct the TrailerStart
488         let trailer_length =
489             compute_length_to_goal(&computed_offsets, curr_pos_id, ComputedOffsetId::TrailerStart)
490                 .expect("trailers should have deterministic length");
491         computed_offsets.insert(
492             ComputedOffsetId::TrailerStart,
493             ComputedOffset::ConstantPlusOffsetInBits(ComputedOffsetId::PacketEnd, -trailer_length),
494         );
495         PacketOrStructLength::NeedsExternal
496     } else {
497         // otherwise, try to reconstruct the full length, if possible
498         let full_length =
499             compute_length_to_goal(&computed_offsets, curr_pos_id, ComputedOffsetId::HeaderStart);
500         if let Some(full_length) = full_length {
501             computed_offsets.insert(
502                 ComputedOffsetId::PacketEnd,
503                 ComputedOffset::ConstantPlusOffsetInBits(
504                     ComputedOffsetId::HeaderStart,
505                     full_length,
506                 ),
507             );
508             PacketOrStructLength::Static(full_length as usize)
509         } else {
510             computed_offsets
511                 .insert(ComputedOffsetId::PacketEnd, ComputedOffset::Alias(curr_pos_id));
512             PacketOrStructLength::Dynamic
513         }
514     };
515 
516     PacketOrStruct { computed_values, computed_offsets, length }
517 }
518 
compute_length_to_goal( computed_offsets: &HashMap<ComputedOffsetId, ComputedOffset>, start: ComputedOffsetId, goal: ComputedOffsetId, ) -> Option<i64>519 fn compute_length_to_goal(
520     computed_offsets: &HashMap<ComputedOffsetId, ComputedOffset>,
521     start: ComputedOffsetId,
522     goal: ComputedOffsetId,
523 ) -> Option<i64> {
524     let mut out = 0;
525     let mut pos = start;
526     while pos != goal {
527         match computed_offsets.get(&pos).ok_or_else(|| format!("key {pos:?} not found")).unwrap() {
528             ComputedOffset::ConstantPlusOffsetInBits(base_id, offset) => {
529                 out += offset;
530                 pos = *base_id;
531             }
532             ComputedOffset::Alias(alias) => pos = *alias,
533             ComputedOffset::SumWithOctets { .. } => return None,
534         }
535     }
536     Some(out)
537 }
538