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