1#![deny(missing_docs)]
12
13pub mod ie_id;
14pub mod ie_parsers;
15pub mod procedure_code;
16
17use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
18use packet_dissector_core::error::PacketError;
19use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue};
20use packet_dissector_core::packet::DissectBuffer;
21use packet_dissector_core::util::read_be_u16;
22
23static FD_INLINE_CRITICALITY: FieldDescriptor = FieldDescriptor {
24 name: "criticality",
25 display_name: "Criticality",
26 field_type: FieldType::U8,
27 optional: false,
28 children: None,
29 display_fn: Some(|v, _siblings| match v {
30 FieldValue::U8(c) => Some(criticality_name(*c)),
31 _ => None,
32 }),
33 format_fn: None,
34};
35
36static FD_INLINE_ID: FieldDescriptor = FieldDescriptor {
37 name: "id",
38 display_name: "ID",
39 field_type: FieldType::U16,
40 optional: false,
41 children: None,
42 display_fn: Some(|v, _siblings| match v {
43 FieldValue::U16(id) => Some(ie_id::ie_id_name(*id)),
44 _ => None,
45 }),
46 format_fn: None,
47};
48
49static FD_IE: FieldDescriptor = FieldDescriptor {
56 name: "ie",
57 display_name: "IE",
58 field_type: FieldType::Object,
59 optional: false,
60 children: None,
61 display_fn: Some(|v, children| match v {
62 FieldValue::Object(_) => children.iter().find_map(|f| match (f.name(), &f.value) {
63 ("id", FieldValue::U16(id)) => Some(ie_id::ie_id_name(*id)),
64 _ => None,
65 }),
66 _ => None,
67 }),
68 format_fn: None,
69};
70
71static FD_INLINE_LENGTH: FieldDescriptor = FieldDescriptor::new("length", "Length", FieldType::U32);
72
73const MIN_HEADER_SIZE: usize = 3;
81
82const FD_PDU_TYPE: usize = 0;
84const FD_PROCEDURE_CODE: usize = 1;
85const FD_CRITICALITY: usize = 2;
86const FD_VALUE_LENGTH: usize = 3;
87const FD_IES: usize = 4;
88
89#[cfg(test)]
91const CFD_ID: usize = 0;
92#[cfg(test)]
93const CFD_CRITICALITY: usize = 1;
94#[cfg(test)]
95const CFD_LENGTH: usize = 2;
96#[cfg(test)]
97const CFD_VALUE: usize = 3;
98
99static IE_CHILD_FIELDS: &[FieldDescriptor] = &[
103 FieldDescriptor {
104 name: "id",
105 display_name: "ID",
106 field_type: FieldType::U16,
107 optional: false,
108 children: None,
109 display_fn: Some(|v, _siblings| match v {
110 FieldValue::U16(id) => Some(ie_id::ie_id_name(*id)),
111 _ => None,
112 }),
113 format_fn: None,
114 },
115 FieldDescriptor {
116 name: "criticality",
117 display_name: "Criticality",
118 field_type: FieldType::U8,
119 optional: false,
120 children: None,
121 display_fn: Some(|v, _siblings| match v {
122 FieldValue::U8(c) => Some(criticality_name(*c)),
123 _ => None,
124 }),
125 format_fn: None,
126 },
127 FieldDescriptor::new("length", "Length", FieldType::U32),
128 FieldDescriptor::new("value", "Value", FieldType::Bytes),
129];
130
131static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
132 FieldDescriptor {
133 name: "pdu_type",
134 display_name: "PDU Type",
135 field_type: FieldType::U8,
136 optional: false,
137 children: None,
138 display_fn: Some(|v, _siblings| match v {
139 FieldValue::U8(t) => Some(pdu_type_name(*t)),
140 _ => None,
141 }),
142 format_fn: None,
143 },
144 FieldDescriptor {
145 name: "procedure_code",
146 display_name: "Procedure Code",
147 field_type: FieldType::U8,
148 optional: false,
149 children: None,
150 display_fn: Some(|v, _siblings| match v {
151 FieldValue::U8(c) => Some(procedure_code::procedure_code_name(*c)),
152 _ => None,
153 }),
154 format_fn: None,
155 },
156 FieldDescriptor {
157 name: "criticality",
158 display_name: "Criticality",
159 field_type: FieldType::U8,
160 optional: false,
161 children: None,
162 display_fn: Some(|v, _siblings| match v {
163 FieldValue::U8(c) => Some(criticality_name(*c)),
164 _ => None,
165 }),
166 format_fn: None,
167 },
168 FieldDescriptor::new("value_length", "Value Length", FieldType::U32),
169 FieldDescriptor::new("ies", "Information Elements", FieldType::Array)
170 .optional()
171 .with_children(IE_CHILD_FIELDS),
172];
173
174fn pdu_type_name(pdu_type: u8) -> &'static str {
178 match pdu_type {
179 0 => "initiatingMessage",
180 1 => "successfulOutcome",
181 2 => "unsuccessfulOutcome",
182 _ => "Unknown",
183 }
184}
185
186fn criticality_name(criticality: u8) -> &'static str {
190 match criticality {
191 0 => "reject",
192 1 => "ignore",
193 2 => "notify",
194 _ => "Unknown",
195 }
196}
197
198pub fn read_aper_length(data: &[u8], pos: usize) -> Result<(u32, usize), PacketError> {
204 if pos >= data.len() {
205 return Err(PacketError::Truncated {
206 expected: pos + 1,
207 actual: data.len(),
208 });
209 }
210 let first = data[pos];
211 if first & 0x80 == 0 {
212 Ok((u32::from(first), 1))
214 } else if first & 0xC0 == 0x80 {
215 if pos + 2 > data.len() {
217 return Err(PacketError::Truncated {
218 expected: pos + 2,
219 actual: data.len(),
220 });
221 }
222 let len = u32::from(first & 0x3F) << 8 | u32::from(data[pos + 1]);
223 Ok((len, 2))
224 } else {
225 Err(PacketError::InvalidHeader(
227 "APER fragmented length determinant not supported",
228 ))
229 }
230}
231
232fn parse_ies<'pkt>(
239 buf: &mut DissectBuffer<'pkt>,
240 data: &'pkt [u8],
241 base_offset: usize,
242) -> Result<usize, PacketError> {
243 if data.len() < 2 {
245 return Err(PacketError::Truncated {
246 expected: 2,
247 actual: data.len(),
248 });
249 }
250 let ie_count = read_be_u16(data, 0)? as usize;
251 let mut pos: usize = 2;
252
253 for _ in 0..ie_count {
254 if pos + 3 > data.len() {
256 break;
257 }
258
259 let ie_id = read_be_u16(data, pos)?;
260 let ie_criticality = (data[pos + 2] >> 6) & 0x03;
261 let ie_start = base_offset + pos;
262 pos += 3;
263
264 let (ie_value_len, len_bytes) = read_aper_length(data, pos)?;
266 pos += len_bytes;
267
268 let ie_value_len_usize = ie_value_len as usize;
269 if pos + ie_value_len_usize > data.len() {
270 break;
271 }
272
273 let ie_value_data = &data[pos..pos + ie_value_len_usize];
274 let ie_end = base_offset + pos + ie_value_len_usize;
275 let ie_value_offset = base_offset + pos;
276
277 let obj_idx = buf.begin_container(&FD_IE, FieldValue::Object(0..0), ie_start..ie_end);
279
280 buf.push_field(
281 &FD_INLINE_ID,
282 FieldValue::U16(ie_id),
283 ie_start..ie_start + 2,
284 );
285 buf.push_field(
286 &FD_INLINE_CRITICALITY,
287 FieldValue::U8(ie_criticality),
288 ie_start + 2..ie_start + 3,
289 );
290 buf.push_field(
291 &FD_INLINE_LENGTH,
292 FieldValue::U32(ie_value_len),
293 base_offset + pos - len_bytes..base_offset + pos,
294 );
295
296 ie_parsers::push_ie_value(buf, ie_id, ie_value_data, ie_value_offset);
298
299 buf.end_container(obj_idx);
300
301 pos += ie_value_len_usize;
302 }
303
304 Ok(pos)
305}
306
307pub struct NgapDissector;
315
316impl Dissector for NgapDissector {
317 fn name(&self) -> &'static str {
318 "NG Application Protocol"
319 }
320
321 fn short_name(&self) -> &'static str {
322 "NGAP"
323 }
324
325 fn field_descriptors(&self) -> &'static [FieldDescriptor] {
326 FIELD_DESCRIPTORS
327 }
328
329 fn dissect<'pkt>(
330 &self,
331 data: &'pkt [u8],
332 buf: &mut DissectBuffer<'pkt>,
333 offset: usize,
334 ) -> Result<DissectResult, PacketError> {
335 if data.len() < MIN_HEADER_SIZE {
336 return Err(PacketError::Truncated {
337 expected: MIN_HEADER_SIZE,
338 actual: data.len(),
339 });
340 }
341
342 let pdu_byte = data[0];
348 let extension = (pdu_byte >> 7) & 0x01;
349 let pdu_type = (pdu_byte >> 5) & 0x03;
350
351 if extension != 0 {
352 return Err(PacketError::InvalidHeader(
353 "NGAP-PDU extension not supported",
354 ));
355 }
356 if pdu_type > 2 {
357 return Err(PacketError::InvalidFieldValue {
358 field: "pdu_type",
359 value: u32::from(pdu_type),
360 });
361 }
362
363 let proc_code = data[1];
367
368 let crit = (data[2] >> 6) & 0x03;
371
372 let mut pos: usize = 3;
374 let (value_length, len_bytes) = read_aper_length(data, pos)?;
375 pos += len_bytes;
376
377 let value_length_usize = value_length as usize;
378 if pos + value_length_usize > data.len() {
379 return Err(PacketError::Truncated {
380 expected: pos + value_length_usize,
381 actual: data.len(),
382 });
383 }
384
385 let total_consumed = pos + value_length_usize;
386
387 let value_data = &data[pos..pos + value_length_usize];
389 let ie_base_offset = offset + pos;
390
391 buf.begin_layer(
392 "NGAP",
393 None,
394 FIELD_DESCRIPTORS,
395 offset..offset + total_consumed,
396 );
397
398 buf.push_field(
399 &FIELD_DESCRIPTORS[FD_PDU_TYPE],
400 FieldValue::U8(pdu_type),
401 offset..offset + 1,
402 );
403 buf.push_field(
404 &FIELD_DESCRIPTORS[FD_PROCEDURE_CODE],
405 FieldValue::U8(proc_code),
406 offset + 1..offset + 2,
407 );
408 buf.push_field(
409 &FIELD_DESCRIPTORS[FD_CRITICALITY],
410 FieldValue::U8(crit),
411 offset + 2..offset + 3,
412 );
413 buf.push_field(
414 &FIELD_DESCRIPTORS[FD_VALUE_LENGTH],
415 FieldValue::U32(value_length),
416 offset + 3..offset + 3 + len_bytes,
417 );
418
419 const SEQUENCE_PREAMBLE_SIZE: usize = 1;
428
429 if value_data.len() > SEQUENCE_PREAMBLE_SIZE {
431 let ie_data = &value_data[SEQUENCE_PREAMBLE_SIZE..];
432 let ie_offset = ie_base_offset + SEQUENCE_PREAMBLE_SIZE;
433
434 let arr_idx = buf.begin_container(
435 &FIELD_DESCRIPTORS[FD_IES],
436 FieldValue::Array(0..0),
437 ie_offset..ie_offset + ie_data.len(),
438 );
439 match parse_ies(buf, ie_data, ie_offset) {
440 Ok(_) => {}
441 Err(_) => {
442 }
446 }
447 buf.end_container(arr_idx);
448 }
449
450 buf.end_layer();
451
452 Ok(DissectResult::new(total_consumed, DispatchHint::End))
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
471
472 fn build_ngap_pdu(pdu_type: u8, proc_code: u8, crit: u8, value: &[u8]) -> Vec<u8> {
475 let mut pdu = Vec::new();
476 pdu.push(pdu_type << 5);
478 pdu.push(proc_code);
480 pdu.push(crit << 6);
482 if value.len() < 128 {
484 pdu.push(value.len() as u8);
485 } else {
486 let len = value.len() as u16;
487 pdu.push(0x80 | ((len >> 8) as u8 & 0x3F));
488 pdu.push((len & 0xFF) as u8);
489 }
490 pdu.extend_from_slice(value);
491 pdu
492 }
493
494 fn build_ie_container(ies: &[(u16, u8, &[u8])]) -> Vec<u8> {
498 let mut container = Vec::new();
499 container.push(0x00);
501 container.push((ies.len() >> 8) as u8);
503 container.push((ies.len() & 0xFF) as u8);
504 for (id, crit, value) in ies {
505 container.push((*id >> 8) as u8);
507 container.push((*id & 0xFF) as u8);
508 container.push(*crit << 6);
510 if value.len() < 128 {
512 container.push(value.len() as u8);
513 } else {
514 let len = value.len() as u16;
515 container.push(0x80 | ((len >> 8) as u8 & 0x3F));
516 container.push((len & 0xFF) as u8);
517 }
518 container.extend_from_slice(value);
519 }
520 container
521 }
522
523 #[test]
524 fn parse_ngap_initiating_message() {
525 let container = build_ie_container(&[]);
527 let data = build_ngap_pdu(0, 21, 0, &container);
528
529 let mut buf = DissectBuffer::new();
530 let result = NgapDissector.dissect(&data, &mut buf, 0).unwrap();
531
532 assert_eq!(result.bytes_consumed, data.len());
533
534 let layer = buf.layer_by_name("NGAP").unwrap();
535 assert_eq!(
536 buf.field_by_name(layer, "pdu_type").unwrap().value,
537 FieldValue::U8(0)
538 );
539 assert_eq!(
540 buf.resolve_display_name(layer, "pdu_type_name"),
541 Some("initiatingMessage")
542 );
543 assert_eq!(
544 buf.field_by_name(layer, "procedure_code").unwrap().value,
545 FieldValue::U8(21)
546 );
547 assert_eq!(
548 buf.resolve_display_name(layer, "procedure_code_name"),
549 Some("NGSetup")
550 );
551 assert_eq!(
552 buf.field_by_name(layer, "criticality").unwrap().value,
553 FieldValue::U8(0)
554 );
555 assert_eq!(
556 buf.resolve_display_name(layer, "criticality_name"),
557 Some("reject")
558 );
559 }
560
561 #[test]
562 fn parse_ngap_successful_outcome() {
563 let container = build_ie_container(&[]);
564 let data = build_ngap_pdu(1, 21, 0, &container);
565
566 let mut buf = DissectBuffer::new();
567 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
568
569 let layer = buf.layer_by_name("NGAP").unwrap();
570 assert_eq!(
571 buf.resolve_display_name(layer, "pdu_type_name"),
572 Some("successfulOutcome")
573 );
574 }
575
576 #[test]
577 fn parse_ngap_unsuccessful_outcome() {
578 let container = build_ie_container(&[]);
579 let data = build_ngap_pdu(2, 14, 0, &container);
580
581 let mut buf = DissectBuffer::new();
582 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
583
584 let layer = buf.layer_by_name("NGAP").unwrap();
585 assert_eq!(
586 buf.resolve_display_name(layer, "pdu_type_name"),
587 Some("unsuccessfulOutcome")
588 );
589 assert_eq!(
590 buf.resolve_display_name(layer, "procedure_code_name"),
591 Some("InitialContextSetup")
592 );
593 }
594
595 #[test]
596 fn parse_ngap_invalid_pdu_type() {
597 let data = [0x60, 0x15, 0x00, 0x02, 0x00, 0x00];
599 let mut buf = DissectBuffer::new();
600 let result = NgapDissector.dissect(&data, &mut buf, 0);
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn parse_ngap_truncated() {
606 let data = [0x00, 0x15];
607 let mut buf = DissectBuffer::new();
608 let result = NgapDissector.dissect(&data, &mut buf, 0);
609 assert!(matches!(result, Err(PacketError::Truncated { .. })));
610 }
611
612 #[test]
613 fn parse_ngap_empty_ie_container() {
614 let container = build_ie_container(&[]);
615 let data = build_ngap_pdu(0, 21, 0, &container);
616
617 let mut buf = DissectBuffer::new();
618 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
619
620 let layer = buf.layer_by_name("NGAP").unwrap();
621 let fields = buf.layer_fields(layer);
622 assert_eq!(fields.len(), 5);
624 if let FieldValue::Array(ref range) = fields[4].value {
626 assert!(range.is_empty());
627 } else {
628 panic!("expected Array");
629 }
630 }
631
632 #[test]
633 fn parse_ngap_with_ies() {
634 let ie_value_1 = [0x01, 0x02, 0x03]; let ie_value_2 = [0x04, 0x05]; let container = build_ie_container(&[
637 (10, 0, &ie_value_1), (85, 0, &ie_value_2), ]);
640 let data = build_ngap_pdu(0, 15, 0, &container); let mut buf = DissectBuffer::new();
643 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
644
645 let layer = buf.layer_by_name("NGAP").unwrap();
646 assert_eq!(
647 buf.resolve_display_name(layer, "procedure_code_name"),
648 Some("InitialUEMessage")
649 );
650
651 let fields = buf.layer_fields(layer);
653 let ie_objects: Vec<_> = fields
654 .iter()
655 .filter(|f| matches!(f.value, FieldValue::Object(_)))
656 .collect();
657 assert_eq!(ie_objects.len(), 2);
658
659 if let FieldValue::Object(ref range) = ie_objects[0].value {
661 let ie_fields = buf.nested_fields(range);
662 let id_field = ie_fields.iter().find(|f| f.name() == "id").unwrap();
663 assert_eq!(id_field.value, FieldValue::U16(10));
664 let display_fn = id_field.descriptor.display_fn.unwrap();
665 assert_eq!(
666 display_fn(&id_field.value, ie_fields),
667 Some("AMF-UE-NGAP-ID")
668 );
669 let val_field = ie_fields.iter().find(|f| f.name() == "value").unwrap();
670 assert_eq!(val_field.value, FieldValue::Bytes(&[0x01, 0x02, 0x03]));
671 } else {
672 panic!("expected Object");
673 }
674
675 if let FieldValue::Object(ref range) = ie_objects[1].value {
677 let ie_fields = buf.nested_fields(range);
678 let id_field = ie_fields.iter().find(|f| f.name() == "id").unwrap();
679 assert_eq!(id_field.value, FieldValue::U16(85));
680 let display_fn = id_field.descriptor.display_fn.unwrap();
681 assert_eq!(
682 display_fn(&id_field.value, ie_fields),
683 Some("RAN-UE-NGAP-ID")
684 );
685 } else {
686 panic!("expected Object");
687 }
688 }
689
690 #[test]
691 fn ie_container_resolves_to_ie_name() {
692 let ie_value = [0x01, 0x02, 0x03];
693 let container = build_ie_container(&[(10, 0, &ie_value)]); let data = build_ngap_pdu(0, 15, 0, &container);
695
696 let mut buf = DissectBuffer::new();
697 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
698
699 let (ie_idx, ie_field) = buf
702 .fields()
703 .iter()
704 .enumerate()
705 .find(|(_, f)| matches!(f.value, FieldValue::Object(_)))
706 .expect("IE container not found");
707 assert_eq!(ie_field.name(), "ie");
708 assert_eq!(ie_field.display_name(), "IE");
709 assert_eq!(
710 buf.resolve_container_display_name(ie_idx as u32),
711 Some("AMF-UE-NGAP-ID")
712 );
713 }
714
715 #[test]
716 fn parse_ngap_with_offset() {
717 let container = build_ie_container(&[(10, 0, &[0x01])]);
718 let data = build_ngap_pdu(0, 21, 0, &container);
719
720 let mut buf = DissectBuffer::new();
721 let base_offset = 100;
722 NgapDissector.dissect(&data, &mut buf, base_offset).unwrap();
723
724 let layer = buf.layer_by_name("NGAP").unwrap();
725 assert_eq!(layer.range.start, base_offset);
726 assert_eq!(layer.range.end, base_offset + data.len());
727 }
728
729 #[test]
730 fn parse_ngap_long_length_determinant() {
731 let ie_value = vec![0xAA; 200];
733 let container = build_ie_container(&[(38, 0, &ie_value)]); let data = build_ngap_pdu(0, 4, 1, &container); let mut buf = DissectBuffer::new();
737 NgapDissector.dissect(&data, &mut buf, 0).unwrap();
738
739 let layer = buf.layer_by_name("NGAP").unwrap();
740 assert_eq!(
741 buf.resolve_display_name(layer, "procedure_code_name"),
742 Some("DownlinkNASTransport")
743 );
744 assert_eq!(
745 buf.resolve_display_name(layer, "criticality_name"),
746 Some("ignore")
747 );
748
749 let fields = buf.layer_fields(layer);
751 let ie_objects: Vec<_> = fields
752 .iter()
753 .filter(|f| matches!(f.value, FieldValue::Object(_)))
754 .collect();
755 assert_eq!(ie_objects.len(), 1);
756 if let FieldValue::Object(ref range) = ie_objects[0].value {
757 let ie_fields = buf.nested_fields(range);
758 let val_field = ie_fields.iter().find(|f| f.name() == "value").unwrap();
759 if let FieldValue::Bytes(bytes) = &val_field.value {
760 assert_eq!(bytes.len(), 200);
761 } else {
762 panic!("expected Bytes");
763 }
764 }
765 }
766
767 #[test]
768 fn field_descriptors_accessible() {
769 let d = NgapDissector;
770 assert_eq!(d.field_descriptors().len(), 5);
771 assert_eq!(
772 d.field_descriptors()[FD_IES].children,
773 Some(IE_CHILD_FIELDS)
774 );
775 }
776
777 #[test]
778 #[allow(unused_variables)]
779 fn unused_child_field_indices_compile() {
780 let _ = IE_CHILD_FIELDS[CFD_ID];
782 let _ = IE_CHILD_FIELDS[CFD_CRITICALITY];
783 let _ = IE_CHILD_FIELDS[CFD_LENGTH];
784 let _ = IE_CHILD_FIELDS[CFD_VALUE];
785 }
786}