1use super::aper::{self, AperError, Reader, Writer};
26use serde_json::{json, Map, Value};
27
28pub const PPID: u32 = 60;
30pub const SCTP_PORT: u16 = 38412;
32pub const MAX_PDU_LEN: usize = 65_535;
35pub const MAX_IES: usize = 64;
38pub const MAX_NAS_PDU_LEN: usize = 8_192;
42pub const MAX_LIST_ITEMS: usize = 1_024;
44
45pub const PROC_AMF_CONFIGURATION_UPDATE: u8 = 0;
47pub const PROC_DOWNLINK_NAS_TRANSPORT: u8 = 4;
48pub const PROC_ERROR_INDICATION: u8 = 9;
49pub const PROC_INITIAL_UE_MESSAGE: u8 = 15;
50pub const PROC_NG_RESET: u8 = 20;
51pub const PROC_NG_SETUP: u8 = 21;
52pub const PROC_RAN_CONFIGURATION_UPDATE: u8 = 35;
53pub const PROC_UE_CONTEXT_RELEASE: u8 = 41;
54pub const PROC_UPLINK_NAS_TRANSPORT: u8 = 46;
55
56pub const IE_AMF_NAME: u16 = 1;
58pub const IE_AMF_SET_ID: u16 = 3;
59pub const IE_AMF_UE_NGAP_ID: u16 = 10;
60pub const IE_CAUSE: u16 = 15;
61pub const IE_CRITICALITY_DIAGNOSTICS: u16 = 19;
62pub const IE_DEFAULT_PAGING_DRX: u16 = 21;
63pub const IE_FIVE_G_S_TMSI: u16 = 26;
64pub const IE_GLOBAL_RAN_NODE_ID: u16 = 27;
65pub const IE_NAS_PDU: u16 = 38;
66pub const IE_PLMN_SUPPORT_LIST: u16 = 80;
67pub const IE_RAN_NODE_NAME: u16 = 82;
68pub const IE_RAN_UE_NGAP_ID: u16 = 85;
69pub const IE_RELATIVE_AMF_CAPACITY: u16 = 86;
70pub const IE_RESET_TYPE: u16 = 88;
71pub const IE_RRC_ESTABLISHMENT_CAUSE: u16 = 90;
72pub const IE_SERVED_GUAMI_LIST: u16 = 96;
73pub const IE_SUPPORTED_TA_LIST: u16 = 102;
74pub const IE_TIME_TO_WAIT: u16 = 107;
75pub const IE_UE_ASSOCIATED_LOGICAL_NG_CONNECTION_LIST: u16 = 111;
76pub const IE_UE_CONTEXT_REQUEST: u16 = 112;
77pub const IE_UE_NGAP_IDS: u16 = 114;
78pub const IE_USER_LOCATION_INFORMATION: u16 = 121;
79
80pub const MAX_AMF_UE_NGAP_ID: u64 = (1 << 40) - 1;
81pub const MAX_RAN_UE_NGAP_ID: u64 = u32::MAX as u64;
82
83pub fn procedure_name(code: u8) -> Option<&'static str> {
85 Some(match code {
86 0 => "AMFConfigurationUpdate",
87 1 => "AMFStatusIndication",
88 4 => "DownlinkNASTransport",
89 9 => "ErrorIndication",
90 12 => "HandoverPreparation",
91 14 => "InitialContextSetup",
92 15 => "InitialUEMessage",
93 20 => "NGReset",
94 21 => "NGSetup",
95 24 => "Paging",
96 25 => "PathSwitchRequest",
97 26 => "PDUSessionResourceModify",
98 29 => "PDUSessionResourceRelease",
99 30 => "PDUSessionResourceSetup",
100 35 => "RANConfigurationUpdate",
101 40 => "UEContextModification",
102 41 => "UEContextRelease",
103 42 => "UEContextReleaseRequest",
104 44 => "UERadioCapabilityInfoIndication",
105 46 => "UplinkNASTransport",
106 _ => return None,
107 })
108}
109
110pub fn ie_name(id: u16) -> Option<&'static str> {
112 Some(match id {
113 IE_AMF_NAME => "AMFName",
114 IE_AMF_SET_ID => "AMFSetID",
115 IE_AMF_UE_NGAP_ID => "AMF-UE-NGAP-ID",
116 IE_CAUSE => "Cause",
117 IE_CRITICALITY_DIAGNOSTICS => "CriticalityDiagnostics",
118 IE_DEFAULT_PAGING_DRX => "DefaultPagingDRX",
119 IE_FIVE_G_S_TMSI => "FiveG-S-TMSI",
120 IE_GLOBAL_RAN_NODE_ID => "GlobalRANNodeID",
121 IE_NAS_PDU => "NAS-PDU",
122 IE_PLMN_SUPPORT_LIST => "PLMNSupportList",
123 IE_RAN_NODE_NAME => "RANNodeName",
124 IE_RAN_UE_NGAP_ID => "RAN-UE-NGAP-ID",
125 IE_RELATIVE_AMF_CAPACITY => "RelativeAMFCapacity",
126 IE_RESET_TYPE => "ResetType",
127 IE_RRC_ESTABLISHMENT_CAUSE => "RRCEstablishmentCause",
128 IE_SERVED_GUAMI_LIST => "ServedGUAMIList",
129 IE_SUPPORTED_TA_LIST => "SupportedTAList",
130 IE_TIME_TO_WAIT => "TimeToWait",
131 IE_UE_ASSOCIATED_LOGICAL_NG_CONNECTION_LIST => "UE-associatedLogicalNG-connectionList",
132 IE_UE_CONTEXT_REQUEST => "UEContextRequest",
133 IE_UE_NGAP_IDS => "UE-NGAP-IDs",
134 IE_USER_LOCATION_INFORMATION => "UserLocationInformation",
135 _ => return None,
136 })
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ProtocolError {
147 pub cause: Cause,
148 pub detail: String,
149}
150
151impl std::fmt::Display for ProtocolError {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "{} ({})", self.detail, self.cause.label())
154 }
155}
156
157impl std::error::Error for ProtocolError {}
158
159impl From<AperError> for ProtocolError {
160 fn from(e: AperError) -> Self {
161 ProtocolError {
162 cause: Cause::protocol(PROTOCOL_TRANSFER_SYNTAX_ERROR),
163 detail: e.0,
164 }
165 }
166}
167
168fn abstract_reject(detail: impl Into<String>) -> ProtocolError {
169 ProtocolError {
170 cause: Cause::protocol(PROTOCOL_ABSTRACT_SYNTAX_ERROR_REJECT),
171 detail: detail.into(),
172 }
173}
174
175fn semantic(detail: impl Into<String>) -> ProtocolError {
176 ProtocolError {
177 cause: Cause::protocol(PROTOCOL_SEMANTIC_ERROR),
178 detail: detail.into(),
179 }
180}
181
182pub type CodecResult<T> = Result<T, ProtocolError>;
183
184#[derive(Debug, Clone)]
186pub struct EncodeError(pub String);
187
188impl std::fmt::Display for EncodeError {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 f.write_str(&self.0)
191 }
192}
193
194impl std::error::Error for EncodeError {}
195
196impl From<AperError> for EncodeError {
197 fn from(e: AperError) -> Self {
198 EncodeError(e.0)
199 }
200}
201
202pub type EncodeResult<T> = Result<T, EncodeError>;
203
204fn enc_err<T>(msg: impl Into<String>) -> EncodeResult<T> {
205 Err(EncodeError(msg.into()))
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum CauseGroup {
214 RadioNetwork,
215 Transport,
216 Nas,
217 Protocol,
218 Misc,
219}
220
221const RADIO_NETWORK_ROOT: &[&str] = &[
222 "unspecified",
223 "txnrelocoverall-expiry",
224 "successful-handover",
225 "release-due-to-ngran-generated-reason",
226 "release-due-to-5gc-generated-reason",
227 "handover-cancelled",
228 "partial-handover",
229 "ho-failure-in-target-5GC-ngran-node-or-target-system",
230 "ho-target-not-allowed",
231 "tngrelocoverall-expiry",
232 "tngrelocprep-expiry",
233 "cell-not-available",
234 "unknown-targetID",
235 "no-radio-resources-available-in-target-cell",
236 "unknown-local-UE-NGAP-ID",
237 "inconsistent-remote-UE-NGAP-ID",
238 "handover-desirable-for-radio-reason",
239 "time-critical-handover",
240 "resource-optimisation-handover",
241 "reduce-load-in-serving-cell",
242 "user-inactivity",
243 "radio-connection-with-ue-lost",
244 "radio-resources-not-available",
245 "invalid-qos-combination",
246 "failure-in-radio-interface-procedure",
247 "interaction-with-other-procedure",
248 "unknown-PDU-session-ID",
249 "unkown-qos-flow-ID",
250 "multiple-PDU-session-ID-instances",
251 "multiple-qos-flow-ID-instances",
252 "encryption-and-or-integrity-protection-algorithms-not-supported",
253 "ng-intra-system-handover-triggered",
254 "ng-inter-system-handover-triggered",
255 "xn-handover-triggered",
256 "not-supported-5QI-value",
257 "ue-context-transfer",
258 "ims-voice-eps-fallback-or-rat-fallback-triggered",
259 "up-integrity-protection-not-possible",
260 "up-confidentiality-protection-not-possible",
261 "slice-not-supported",
262 "ue-in-rrc-inactive-state-not-reachable",
263 "redirection",
264 "resources-not-available-for-the-slice",
265 "ue-max-integrity-protected-data-rate-reason",
266 "release-due-to-cn-detected-mobility",
267];
268const RADIO_NETWORK_EXT: &[&str] = &[
269 "n26-interface-not-available",
270 "release-due-to-pre-emption",
271 "multiple-location-reporting-reference-ID-instances",
272 "rsn-not-available-for-the-up",
273 "npn-access-denied",
274 "cag-only-access-denied",
275 "insufficient-ue-capabilities",
276 "redcap-ue-not-supported",
277 "unknown-MBS-Session-ID",
278 "indicated-MBS-session-area-information-not-served-by-the-gNB",
279 "inconsistent-slice-info-for-the-session",
280 "misaligned-association-for-multicast-unicast",
281 "eredcap-ue-not-supported",
282 "two-rx-xr-ue-not-supported",
283 "aerial-ue-flight-information-reporting-initiation-failure",
284 "unknown-RAN-AIoT-Device-NGAP-ID",
285 "requested-AIoT-service-area-information-not-served-by-the-gNB",
286 "unknown-AIoT-session",
287 "aiot-device-not-reachable",
288 "multiple-AIoT-session-ID-instances",
289];
290const TRANSPORT_ROOT: &[&str] = &["transport-resource-unavailable", "unspecified"];
291const NAS_ROOT: &[&str] = &[
292 "normal-release",
293 "authentication-failure",
294 "deregister",
295 "unspecified",
296];
297const NAS_EXT: &[&str] = &[
298 "uE-not-in-PLMN-serving-area",
299 "mobile-IAB-not-authorized",
300 "iAB-not-authorized",
301 "no-aiot-NAS-response",
302];
303const PROTOCOL_ROOT: &[&str] = &[
304 "transfer-syntax-error",
305 "abstract-syntax-error-reject",
306 "abstract-syntax-error-ignore-and-notify",
307 "message-not-compatible-with-receiver-state",
308 "semantic-error",
309 "abstract-syntax-error-falsely-constructed-message",
310 "unspecified",
311];
312const MISC_ROOT: &[&str] = &[
313 "control-processing-overload",
314 "not-enough-user-plane-processing-resources",
315 "hardware-failure",
316 "om-intervention",
317 "unknown-PLMN-or-SNPN",
318 "unspecified",
319];
320
321pub const RADIO_UNKNOWN_LOCAL_UE_NGAP_ID: u8 = 14;
322pub const RADIO_INCONSISTENT_REMOTE_UE_NGAP_ID: u8 = 15;
323pub const NAS_NORMAL_RELEASE: u8 = 0;
324pub const NAS_UNSPECIFIED: u8 = 3;
325pub const PROTOCOL_TRANSFER_SYNTAX_ERROR: u8 = 0;
326pub const PROTOCOL_ABSTRACT_SYNTAX_ERROR_REJECT: u8 = 1;
327pub const PROTOCOL_MESSAGE_NOT_COMPATIBLE_WITH_RECEIVER_STATE: u8 = 3;
328pub const PROTOCOL_SEMANTIC_ERROR: u8 = 4;
329pub const PROTOCOL_UNSPECIFIED: u8 = 6;
330pub const MISC_CONTROL_PROCESSING_OVERLOAD: u8 = 0;
331pub const MISC_HARDWARE_FAILURE: u8 = 2;
332pub const MISC_OM_INTERVENTION: u8 = 3;
333pub const MISC_UNKNOWN_PLMN: u8 = 4;
334pub const MISC_UNSPECIFIED: u8 = 5;
335
336impl CauseGroup {
337 const ALL: [CauseGroup; 5] = [
338 CauseGroup::RadioNetwork,
339 CauseGroup::Transport,
340 CauseGroup::Nas,
341 CauseGroup::Protocol,
342 CauseGroup::Misc,
343 ];
344
345 pub fn name(self) -> &'static str {
346 match self {
347 CauseGroup::RadioNetwork => "radioNetwork",
348 CauseGroup::Transport => "transport",
349 CauseGroup::Nas => "nas",
350 CauseGroup::Protocol => "protocol",
351 CauseGroup::Misc => "misc",
352 }
353 }
354
355 fn tables(self) -> (&'static [&'static str], &'static [&'static str]) {
356 match self {
357 CauseGroup::RadioNetwork => (RADIO_NETWORK_ROOT, RADIO_NETWORK_EXT),
358 CauseGroup::Transport => (TRANSPORT_ROOT, &[]),
359 CauseGroup::Nas => (NAS_ROOT, NAS_EXT),
360 CauseGroup::Protocol => (PROTOCOL_ROOT, &[]),
361 CauseGroup::Misc => (MISC_ROOT, &[]),
362 }
363 }
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub struct Cause {
370 pub group: CauseGroup,
371 pub extended: bool,
372 pub index: u8,
373}
374
375fn normalize(s: &str) -> String {
376 s.trim().to_ascii_lowercase().replace('_', "-")
377}
378
379impl Cause {
380 pub fn protocol(index: u8) -> Self {
381 Cause {
382 group: CauseGroup::Protocol,
383 extended: false,
384 index,
385 }
386 }
387 pub fn misc(index: u8) -> Self {
388 Cause {
389 group: CauseGroup::Misc,
390 extended: false,
391 index,
392 }
393 }
394 pub fn radio(index: u8) -> Self {
395 Cause {
396 group: CauseGroup::RadioNetwork,
397 extended: false,
398 index,
399 }
400 }
401 pub fn nas(index: u8) -> Self {
402 Cause {
403 group: CauseGroup::Nas,
404 extended: false,
405 index,
406 }
407 }
408
409 pub fn value_name(&self) -> String {
410 let (root, ext) = self.group.tables();
411 let table = if self.extended { ext } else { root };
412 table
413 .get(self.index as usize)
414 .map(|s| s.to_string())
415 .unwrap_or_else(|| format!("{}{}", if self.extended { "ext-" } else { "" }, self.index))
416 }
417
418 pub fn label(&self) -> String {
420 format!("{}/{}", self.group.name(), self.value_name())
421 }
422
423 pub fn to_json(&self) -> Value {
424 json!({"group": self.group.name(), "value": self.value_name()})
425 }
426
427 pub fn from_json(v: &Value) -> EncodeResult<Cause> {
431 let (group, value) = match v {
432 Value::String(s) => match s.split_once('/') {
433 Some((g, val)) => (Some(g.to_string()), val.to_string()),
434 None => (None, s.clone()),
435 },
436 Value::Object(m) => (
437 m.get("group").and_then(Value::as_str).map(str::to_string),
438 m.get("value")
439 .and_then(Value::as_str)
440 .map(str::to_string)
441 .ok_or_else(|| EncodeError("cause.value must be a string".into()))?,
442 ),
443 _ => return enc_err("cause must be \"group/value\" or {\"group\", \"value\"}"),
444 };
445 let wanted = normalize(&value);
446 let groups: Vec<CauseGroup> = match &group {
447 Some(g) => {
448 let g = normalize(g).replace('-', "");
449 let found = CauseGroup::ALL
450 .iter()
451 .copied()
452 .find(|c| c.name().to_ascii_lowercase() == g);
453 match found {
454 Some(c) => vec![c],
455 None => {
456 return enc_err(format!(
457 "cause group {g:?} is not one of radioNetwork, transport, nas, protocol, misc"
458 ))
459 }
460 }
461 }
462 None => CauseGroup::ALL.to_vec(),
463 };
464 let mut hits = Vec::new();
465 for g in groups {
466 let (root, ext) = g.tables();
467 for (i, name) in root.iter().enumerate() {
468 if normalize(name) == wanted {
469 hits.push(Cause {
470 group: g,
471 extended: false,
472 index: i as u8,
473 });
474 }
475 }
476 for (i, name) in ext.iter().enumerate() {
477 if normalize(name) == wanted {
478 hits.push(Cause {
479 group: g,
480 extended: true,
481 index: i as u8,
482 });
483 }
484 }
485 }
486 match hits.len() {
487 1 => Ok(hits[0]),
488 0 => enc_err(format!(
489 "cause value {value:?} is not defined{}; e.g. misc/unknown-PLMN-or-SNPN, \
490 misc/control-processing-overload, nas/normal-release, \
491 radioNetwork/unspecified, protocol/semantic-error",
492 group.map(|g| format!(" in group {g}")).unwrap_or_default()
493 )),
494 _ => enc_err(format!(
495 "cause value {value:?} exists in more than one group; write it as group/value"
496 )),
497 }
498 }
499
500 fn encode(&self, w: &mut Writer) -> EncodeResult<()> {
501 let gi = CauseGroup::ALL
502 .iter()
503 .position(|g| *g == self.group)
504 .unwrap_or(0) as u64;
505 put_choice(w, gi, 6, "Cause")?;
508 let (root, ext) = self.group.tables();
509 let limit = if self.extended { ext.len() } else { root.len() };
510 if self.index as usize >= limit {
511 return enc_err(format!("cause index {} out of range", self.index));
512 }
513 w.enumerated(
515 self.index as u64,
516 self.extended,
517 root.len() as u64,
518 true,
519 "Cause",
520 )?;
521 Ok(())
522 }
523
524 fn decode(r: &mut Reader) -> CodecResult<Cause> {
525 let gi = choice(r, 6, "Cause")? as usize;
526 let group = CauseGroup::ALL[gi];
527 let (root, _) = group.tables();
528 let (extended, index) = r.enumerated(root.len() as u64, true, "Cause value")?;
529 Ok(Cause {
530 group,
531 extended,
532 index: index as u8,
533 })
534 }
535}
536
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
542pub enum PduKind {
543 Initiating,
544 Successful,
545 Unsuccessful,
546}
547
548impl PduKind {
549 pub fn name(self) -> &'static str {
550 match self {
551 PduKind::Initiating => "initiatingMessage",
552 PduKind::Successful => "successfulOutcome",
553 PduKind::Unsuccessful => "unsuccessfulOutcome",
554 }
555 }
556 fn index(self) -> u64 {
557 match self {
558 PduKind::Initiating => 0,
559 PduKind::Successful => 1,
560 PduKind::Unsuccessful => 2,
561 }
562 }
563}
564
565#[derive(Debug, Clone, Copy, PartialEq, Eq)]
566pub enum Criticality {
567 Reject,
568 Ignore,
569 Notify,
570}
571
572impl Criticality {
573 fn from_index(i: u64) -> Criticality {
574 match i {
575 0 => Criticality::Reject,
576 1 => Criticality::Ignore,
577 _ => Criticality::Notify,
578 }
579 }
580 fn index(self) -> u64 {
581 match self {
582 Criticality::Reject => 0,
583 Criticality::Ignore => 1,
584 Criticality::Notify => 2,
585 }
586 }
587 pub fn name(self) -> &'static str {
588 match self {
589 Criticality::Reject => "reject",
590 Criticality::Ignore => "ignore",
591 Criticality::Notify => "notify",
592 }
593 }
594}
595
596#[derive(Debug, Clone, PartialEq, Eq)]
598pub struct Ie {
599 pub id: u16,
600 pub criticality: Criticality,
601 pub value: Vec<u8>,
602}
603
604impl Ie {
605 pub fn new(id: u16, criticality: Criticality, value: Vec<u8>) -> Self {
606 Ie {
607 id,
608 criticality,
609 value,
610 }
611 }
612}
613
614#[derive(Debug, Clone, PartialEq, Eq)]
616pub struct Pdu {
617 pub kind: PduKind,
618 pub procedure_code: u8,
619 pub criticality: Criticality,
620 pub ies: Vec<Ie>,
621}
622
623impl Pdu {
624 pub fn new(kind: PduKind, procedure_code: u8, criticality: Criticality, ies: Vec<Ie>) -> Self {
625 Pdu {
626 kind,
627 procedure_code,
628 criticality,
629 ies,
630 }
631 }
632
633 pub fn ie(&self, id: u16) -> Option<&Ie> {
634 self.ies.iter().find(|ie| ie.id == id)
635 }
636
637 pub fn label(&self) -> String {
639 match procedure_name(self.procedure_code) {
640 Some(n) => format!("{n} {}", self.kind.name()),
641 None => format!("procedure {} {}", self.procedure_code, self.kind.name()),
642 }
643 }
644
645 pub fn uninterpreted_ies(&self, interpreted: &[u16]) -> Vec<Value> {
647 self.ies
648 .iter()
649 .filter(|ie| !interpreted.contains(&ie.id))
650 .map(|ie| {
651 json!({
652 "id": ie.id,
653 "name": ie_name(ie.id),
654 "criticality": ie.criticality.name(),
655 "length": ie.value.len(),
656 })
657 })
658 .collect()
659 }
660
661 pub fn require(&self, id: u16) -> CodecResult<&Ie> {
663 self.ie(id).ok_or_else(|| {
664 abstract_reject(format!(
665 "{} is missing mandatory IE {} ({})",
666 self.label(),
667 id,
668 ie_name(id).unwrap_or("?")
669 ))
670 })
671 }
672}
673
674pub fn decode_pdu(bytes: &[u8]) -> CodecResult<Pdu> {
676 if bytes.is_empty() {
677 return Err(AperError("empty PDU".into()).into());
678 }
679 if bytes.len() > MAX_PDU_LEN {
680 return Err(AperError(format!(
681 "PDU of {} octets exceeds {MAX_PDU_LEN}",
682 bytes.len()
683 ))
684 .into());
685 }
686 let mut r = Reader::new(bytes);
687 if r.extension_bit("NGAP-PDU")? {
688 return Err(AperError("NGAP-PDU choice extension".into()).into());
689 }
690 let kind = match r.constrained(0, 2, "NGAP-PDU")? {
691 0 => PduKind::Initiating,
692 1 => PduKind::Successful,
693 _ => PduKind::Unsuccessful,
694 };
695 let procedure_code = r.constrained(0, 255, "procedureCode")? as u8;
696 let criticality = Criticality::from_index(r.constrained(0, 2, "criticality")?);
697 let content = r.open_type("message value")?;
698 if !r.at_end() {
699 return Err(AperError("octets after the PDU".into()).into());
700 }
701
702 let mut c = Reader::new(content);
703 let extended = c.extension_bit("message")?;
704 let count = c.size(0, 65_535, "ProtocolIE-Container")?;
705 if count > MAX_IES {
706 return Err(AperError(format!(
707 "{count} IEs in one message exceeds the bound of {MAX_IES}"
708 ))
709 .into());
710 }
711 let mut ies = Vec::with_capacity(count);
712 for _ in 0..count {
713 let id = c.constrained(0, 65_535, "ProtocolIE-ID")? as u16;
714 let crit = Criticality::from_index(c.constrained(0, 2, "IE criticality")?);
715 let value = c.open_type("IE value")?.to_vec();
716 if ies.iter().any(|ie: &Ie| ie.id == id) {
717 return Err(ProtocolError {
720 cause: Cause::protocol(5),
721 detail: format!("IE {id} appears more than once"),
722 });
723 }
724 ies.push(Ie::new(id, crit, value));
725 }
726 if extended {
727 c.skip_extension_additions("message extension additions")?;
728 }
729 Ok(Pdu {
730 kind,
731 procedure_code,
732 criticality,
733 ies,
734 })
735}
736
737pub fn encode_pdu(pdu: &Pdu) -> EncodeResult<Vec<u8>> {
739 if pdu.ies.len() > MAX_IES {
740 return enc_err(format!("{} IEs exceeds {MAX_IES}", pdu.ies.len()));
741 }
742 let mut c = Writer::new();
743 c.bit(false);
744 c.size(pdu.ies.len(), 0, 65_535, "ProtocolIE-Container")?;
745 for ie in &pdu.ies {
746 c.constrained(ie.id as u64, 0, 65_535, "ProtocolIE-ID")?;
747 c.constrained(ie.criticality.index(), 0, 2, "IE criticality")?;
748 c.open_type(&ie.value, "IE value")?;
749 }
750 let content = c.finish();
751
752 let mut w = Writer::new();
753 w.bit(false);
754 w.constrained(pdu.kind.index(), 0, 2, "NGAP-PDU")?;
755 w.constrained(pdu.procedure_code as u64, 0, 255, "procedureCode")?;
756 w.constrained(pdu.criticality.index(), 0, 2, "criticality")?;
757 w.open_type(&content, "message value")?;
758 let out = w.finish();
759 if out.len() > MAX_PDU_LEN {
760 return enc_err(format!("PDU of {} octets exceeds {MAX_PDU_LEN}", out.len()));
761 }
762 Ok(out)
763}
764
765pub fn peek(bytes: &[u8]) -> Option<(PduKind, u8)> {
767 let mut r = Reader::new(bytes);
768 if r.extension_bit("NGAP-PDU").ok()? {
769 return None;
770 }
771 let kind = match r.constrained(0, 2, "NGAP-PDU").ok()? {
772 0 => PduKind::Initiating,
773 1 => PduKind::Successful,
774 _ => PduKind::Unsuccessful,
775 };
776 let code = r.constrained(0, 255, "procedureCode").ok()? as u8;
777 Some((kind, code))
778}
779
780pub fn is_ue_associated(bytes: &[u8]) -> bool {
784 match decode_pdu(bytes) {
785 Ok(p) => p.ies.iter().any(|ie| {
786 matches!(
787 ie.id,
788 IE_AMF_UE_NGAP_ID | IE_RAN_UE_NGAP_ID | IE_UE_NGAP_IDS
789 )
790 }),
791 Err(_) => false,
792 }
793}
794
795pub struct Budget(usize);
801
802impl Default for Budget {
803 fn default() -> Self {
804 Budget(MAX_LIST_ITEMS)
805 }
806}
807
808impl Budget {
809 fn take(&mut self, n: usize, what: &str) -> CodecResult<()> {
810 if n > self.0 {
811 return Err(AperError(format!(
812 "{what}: list elements in one PDU exceed the bound of {MAX_LIST_ITEMS}"
813 ))
814 .into());
815 }
816 self.0 -= n;
817 Ok(())
818 }
819}
820
821#[derive(Debug, Clone, PartialEq, Eq)]
827pub struct Plmn {
828 pub mcc: String,
829 pub mnc: String,
830}
831
832impl Plmn {
833 pub fn decode_octets(b: &[u8]) -> CodecResult<Plmn> {
834 if b.len() != 3 {
835 return Err(semantic("PLMN identity is not three octets"));
836 }
837 let d = [
838 b[0] & 0x0F,
839 b[0] >> 4,
840 b[1] & 0x0F,
841 b[1] >> 4,
842 b[2] & 0x0F,
843 b[2] >> 4,
844 ];
845 let digit = |x: u8| -> CodecResult<char> {
846 if x > 9 {
847 Err(semantic("PLMN identity has a non-decimal digit"))
848 } else {
849 Ok((b'0' + x) as char)
850 }
851 };
852 let mcc: String = [digit(d[0])?, digit(d[1])?, digit(d[2])?].iter().collect();
853 let mnc: String = if d[3] == 0xF {
854 [digit(d[4])?, digit(d[5])?].iter().collect()
855 } else {
856 [digit(d[4])?, digit(d[5])?, digit(d[3])?].iter().collect()
857 };
858 Ok(Plmn { mcc, mnc })
859 }
860
861 pub fn octets(&self) -> [u8; 3] {
862 let m: Vec<u8> = self.mcc.bytes().map(|c| c - b'0').collect();
863 let n: Vec<u8> = self.mnc.bytes().map(|c| c - b'0').collect();
864 let mnc3 = if n.len() == 3 { n[2] } else { 0xF };
865 [m[1] << 4 | m[0], mnc3 << 4 | m[2], n[1] << 4 | n[0]]
866 }
867
868 pub fn to_json(&self) -> Value {
869 json!({"mcc": self.mcc, "mnc": self.mnc})
870 }
871
872 pub fn from_json(v: &Value) -> EncodeResult<Plmn> {
875 let (mcc, mnc) = match v {
876 Value::Object(m) => (
877 json_digits(m.get("mcc"), "plmn.mcc")?,
878 json_digits(m.get("mnc"), "plmn.mnc")?,
879 ),
880 Value::String(s) => {
881 let s = s.trim();
882 if let Some((a, b)) = s.split_once(['-', '/']) {
883 (a.to_string(), b.to_string())
884 } else if s.len() == 5 || s.len() == 6 {
885 (s[..3].to_string(), s[3..].to_string())
886 } else {
887 return enc_err(format!(
888 "plmn {s:?} must be \"MCC-MNC\" or {{\"mcc\", \"mnc\"}}"
889 ));
890 }
891 }
892 _ => return enc_err("plmn must be {\"mcc\": \"001\", \"mnc\": \"01\"}"),
893 };
894 if mcc.len() != 3 || !mcc.bytes().all(|c| c.is_ascii_digit()) {
895 return enc_err(format!("mcc {mcc:?} must be three decimal digits"));
896 }
897 if !(2..=3).contains(&mnc.len()) || !mnc.bytes().all(|c| c.is_ascii_digit()) {
898 return enc_err(format!("mnc {mnc:?} must be two or three decimal digits"));
899 }
900 Ok(Plmn { mcc, mnc })
901 }
902
903 fn decode(r: &mut Reader) -> CodecResult<Plmn> {
904 let b = r.fixed_octets(3, "PLMNIdentity")?;
905 Plmn::decode_octets(&b)
906 }
907
908 fn encode(&self, w: &mut Writer) {
909 w.fixed_octets(&self.octets());
910 }
911}
912
913fn json_digits(v: Option<&Value>, what: &str) -> EncodeResult<String> {
914 match v {
915 Some(Value::String(s)) => Ok(s.trim().to_string()),
916 Some(Value::Number(n)) => enc_err(format!(
917 "{what} must be a string so leading zeros survive (got {n})"
918 )),
919 _ => enc_err(format!("{what} is required")),
920 }
921}
922
923pub fn json_u64(v: &Value, field: &str, max: u64) -> EncodeResult<u64> {
924 let n = match v.get(field) {
925 Some(Value::Number(n)) => n
926 .as_u64()
927 .ok_or_else(|| EncodeError(format!("{field} must be a non-negative integer")))?,
928 Some(Value::String(s)) => {
929 let s = s.trim();
930 if let Some(hex) = s.strip_prefix("0x") {
931 u64::from_str_radix(hex, 16)
932 .map_err(|_| EncodeError(format!("{field} {s:?} is not a number")))?
933 } else {
934 s.parse()
935 .map_err(|_| EncodeError(format!("{field} {s:?} is not a number")))?
936 }
937 }
938 None | Some(Value::Null) => return enc_err(format!("{field} is required")),
939 _ => return enc_err(format!("{field} must be a number")),
940 };
941 if n > max {
942 return enc_err(format!("{field} {n} is above its maximum {max}"));
943 }
944 Ok(n)
945}
946
947pub fn json_opt_u64(v: &Value, field: &str, max: u64) -> EncodeResult<Option<u64>> {
948 match v.get(field) {
949 None | Some(Value::Null) => Ok(None),
950 Some(_) => json_u64(v, field, max).map(Some),
951 }
952}
953
954fn opt_extension_container(r: &mut Reader, present: bool, budget: &mut Budget) -> CodecResult<()> {
955 if present {
956 let n = r.size(1, 65_535, "ProtocolExtensionContainer")?;
957 budget.take(n, "ProtocolExtensionContainer")?;
958 for _ in 0..n {
959 r.constrained(0, 65_535, "ProtocolExtensionID")?;
960 r.constrained(0, 2, "extension criticality")?;
961 r.open_type("extension value")?;
962 }
963 }
964 Ok(())
965}
966
967fn finish_sequence(r: &mut Reader, extended: bool) -> CodecResult<()> {
968 if extended {
969 r.skip_extension_additions("extension additions")?;
970 }
971 Ok(())
972}
973
974fn choice(r: &mut Reader, alternatives: u64, what: &str) -> CodecResult<u64> {
980 let i = r.constrained(0, alternatives - 1, what)?;
981 if i == alternatives - 1 {
982 return Err(semantic(format!(
983 "{what} selects its choice-Extensions alternative, which NetGet does not decode"
984 )));
985 }
986 Ok(i)
987}
988
989pub const CHOICE_EXTENSIONS: &str = "choice-Extensions";
991
992fn put_choice(w: &mut Writer, index: u64, alternatives: u64, what: &str) -> EncodeResult<()> {
993 Ok(w.constrained(index, 0, alternatives - 1, what)?)
994}
995
996fn decode_ie<T>(ie: &Ie, f: impl FnOnce(&mut Reader) -> CodecResult<T>) -> CodecResult<T> {
998 let mut r = Reader::new(&ie.value);
999 let v = f(&mut r)?;
1000 if !r.at_end() {
1001 return Err(AperError(format!(
1002 "IE {} ({}) has octets after its value",
1003 ie.id,
1004 ie_name(ie.id).unwrap_or("?")
1005 ))
1006 .into());
1007 }
1008 Ok(v)
1009}
1010
1011fn encode_ie(
1012 id: u16,
1013 crit: Criticality,
1014 f: impl FnOnce(&mut Writer) -> EncodeResult<()>,
1015) -> EncodeResult<Ie> {
1016 let mut w = Writer::new();
1017 f(&mut w)?;
1018 Ok(Ie::new(id, crit, w.finish()))
1019}
1020
1021pub fn decode_amf_ue_ngap_id(ie: &Ie) -> CodecResult<u64> {
1022 decode_ie(ie, |r| {
1023 Ok(r.constrained(0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?)
1024 })
1025}
1026
1027pub fn decode_ran_ue_ngap_id(ie: &Ie) -> CodecResult<u64> {
1028 decode_ie(ie, |r| {
1029 Ok(r.constrained(0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?)
1030 })
1031}
1032
1033pub fn amf_ue_ngap_id_ie(id: u64, crit: Criticality) -> EncodeResult<Ie> {
1034 encode_ie(IE_AMF_UE_NGAP_ID, crit, |w| {
1035 Ok(w.constrained(id, 0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?)
1036 })
1037}
1038
1039pub fn ran_ue_ngap_id_ie(id: u64, crit: Criticality) -> EncodeResult<Ie> {
1040 encode_ie(IE_RAN_UE_NGAP_ID, crit, |w| {
1041 Ok(w.constrained(id, 0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?)
1042 })
1043}
1044
1045pub fn decode_nas_pdu(ie: &Ie) -> CodecResult<Vec<u8>> {
1046 decode_ie(ie, |r| {
1047 Ok(r.octet_string(MAX_NAS_PDU_LEN, "NAS-PDU")?.to_vec())
1048 })
1049}
1050
1051pub fn nas_pdu_ie(nas: &[u8]) -> EncodeResult<Ie> {
1052 if nas.is_empty() || nas.len() > MAX_NAS_PDU_LEN {
1053 return enc_err(format!(
1054 "NAS-PDU of {} octets is outside 1..{MAX_NAS_PDU_LEN}",
1055 nas.len()
1056 ));
1057 }
1058 encode_ie(IE_NAS_PDU, Criticality::Reject, |w| {
1059 Ok(w.octet_string(nas, "NAS-PDU")?)
1060 })
1061}
1062
1063pub fn decode_cause(ie: &Ie) -> CodecResult<Cause> {
1064 decode_ie(ie, Cause::decode)
1065}
1066
1067pub fn cause_ie(cause: &Cause, crit: Criticality) -> EncodeResult<Ie> {
1068 encode_ie(IE_CAUSE, crit, |w| cause.encode(w))
1069}
1070
1071const TIME_TO_WAIT: &[&str] = &["v1s", "v2s", "v5s", "v10s", "v20s", "v60s"];
1072const PAGING_DRX: &[&str] = &["v32", "v64", "v128", "v256"];
1073const RRC_CAUSE_ROOT: &[&str] = &[
1074 "emergency",
1075 "highPriorityAccess",
1076 "mt-Access",
1077 "mo-Signalling",
1078 "mo-Data",
1079 "mo-VoiceCall",
1080 "mo-VideoCall",
1081 "mo-SMS",
1082 "mps-PriorityAccess",
1083 "mcs-PriorityAccess",
1084];
1085const RRC_CAUSE_EXT: &[&str] = &["notAvailable", "mo-ExceptionData"];
1086
1087fn enum_index(table: &[&str], v: &str, what: &str) -> EncodeResult<u64> {
1088 let want = normalize(v);
1089 table
1090 .iter()
1091 .position(|n| normalize(n) == want)
1092 .map(|i| i as u64)
1093 .ok_or_else(|| EncodeError(format!("{what} {v:?} is not one of {}", table.join(", "))))
1094}
1095
1096pub fn time_to_wait_ie(v: &str) -> EncodeResult<Ie> {
1097 let name = if v.starts_with('v') {
1099 v.to_string()
1100 } else {
1101 format!("v{}s", v.trim_end_matches('s'))
1102 };
1103 let i = enum_index(TIME_TO_WAIT, &name, "time_to_wait")?;
1104 encode_ie(IE_TIME_TO_WAIT, Criticality::Ignore, |w| {
1105 Ok(w.enumerated(i, false, 6, true, "TimeToWait")?)
1106 })
1107}
1108
1109pub fn decode_time_to_wait(ie: &Ie) -> CodecResult<String> {
1110 decode_ie(ie, |r| {
1111 let (ext, i) = r.enumerated(6, true, "TimeToWait")?;
1112 Ok(if ext {
1113 format!("ext-{i}")
1114 } else {
1115 TIME_TO_WAIT[i as usize].to_string()
1116 })
1117 })
1118}
1119
1120fn decode_paging_drx(ie: &Ie) -> CodecResult<String> {
1121 decode_ie(ie, |r| {
1122 let (ext, i) = r.enumerated(4, true, "PagingDRX")?;
1123 Ok(if ext {
1124 format!("ext-{i}")
1125 } else {
1126 PAGING_DRX[i as usize].to_string()
1127 })
1128 })
1129}
1130
1131fn decode_rrc_cause(ie: &Ie) -> CodecResult<String> {
1132 decode_ie(ie, |r| {
1133 let (ext, i) = r.enumerated(10, true, "RRCEstablishmentCause")?;
1134 let table = if ext { RRC_CAUSE_EXT } else { RRC_CAUSE_ROOT };
1135 Ok(table
1136 .get(i as usize)
1137 .map(|s| s.to_string())
1138 .unwrap_or_else(|| format!("ext-{i}")))
1139 })
1140}
1141
1142fn rrc_cause_ie(v: &str) -> EncodeResult<Ie> {
1143 let (ext, i) = match enum_index(RRC_CAUSE_ROOT, v, "rrc_establishment_cause") {
1144 Ok(i) => (false, i),
1145 Err(_) => (
1146 true,
1147 enum_index(RRC_CAUSE_EXT, v, "rrc_establishment_cause").map_err(|_| {
1148 EncodeError(format!(
1149 "rrc_establishment_cause {v:?} is not one of {}, {}",
1150 RRC_CAUSE_ROOT.join(", "),
1151 RRC_CAUSE_EXT.join(", ")
1152 ))
1153 })?,
1154 ),
1155 };
1156 encode_ie(IE_RRC_ESTABLISHMENT_CAUSE, Criticality::Ignore, |w| {
1157 Ok(w.enumerated(i, ext, 10, true, "RRCEstablishmentCause")?)
1158 })
1159}
1160
1161fn tac_octets(tac: u64) -> [u8; 3] {
1162 [(tac >> 16) as u8, (tac >> 8) as u8, tac as u8]
1163}
1164
1165fn decode_tac(r: &mut Reader) -> CodecResult<u64> {
1166 let b = r.fixed_octets(3, "TAC")?;
1167 Ok(((b[0] as u64) << 16) | ((b[1] as u64) << 8) | b[2] as u64)
1168}
1169
1170fn decode_snssai(r: &mut Reader, budget: &mut Budget) -> CodecResult<Value> {
1175 let ext = r.extension_bit("S-NSSAI")?;
1176 let has_sd = r.bit("S-NSSAI")?;
1177 let has_ext = r.bit("S-NSSAI")?;
1178 let sst = r.fixed_octets(1, "SST")?[0];
1179 let mut v = json!({"sst": sst});
1180 if has_sd {
1181 let sd = r.fixed_octets(3, "SD")?;
1182 v["sd"] = json!(hex::encode(sd));
1183 }
1184 opt_extension_container(r, has_ext, budget)?;
1185 finish_sequence(r, ext)?;
1186 Ok(v)
1187}
1188
1189fn encode_snssai(w: &mut Writer, v: &Value) -> EncodeResult<()> {
1191 let sst = json_u64(v, "sst", 255)?;
1192 let sd = match v.get("sd") {
1193 None | Some(Value::Null) => None,
1194 Some(Value::String(s)) => {
1195 let s = s.trim().trim_start_matches("0x");
1196 if s.len() != 6 {
1197 return enc_err(format!("sd {s:?} must be six hex digits"));
1198 }
1199 Some(
1200 u32::from_str_radix(s, 16)
1201 .map_err(|_| EncodeError(format!("sd {s:?} is not hex")))?,
1202 )
1203 }
1204 Some(Value::Number(_)) => Some(json_u64(v, "sd", 0xFF_FFFF)? as u32),
1205 Some(_) => return enc_err("sd must be six hex digits"),
1206 };
1207 w.bit(false);
1208 w.bit(sd.is_some());
1209 w.bit(false);
1210 w.fixed_octets(&[sst as u8]);
1211 if let Some(sd) = sd {
1212 w.fixed_octets(&[(sd >> 16) as u8, (sd >> 8) as u8, sd as u8]);
1213 }
1214 Ok(())
1215}
1216
1217fn decode_slice_list(r: &mut Reader, budget: &mut Budget) -> CodecResult<Vec<Value>> {
1218 let n = r.size(1, 1024, "SliceSupportList")?;
1219 budget.take(n, "SliceSupportList")?;
1220 let mut out = Vec::with_capacity(n);
1221 for _ in 0..n {
1222 let ext = r.extension_bit("SliceSupportItem")?;
1223 let has_ext = r.bit("SliceSupportItem")?;
1224 out.push(decode_snssai(r, budget)?);
1225 opt_extension_container(r, has_ext, budget)?;
1226 finish_sequence(r, ext)?;
1227 }
1228 Ok(out)
1229}
1230
1231fn json_list<'a>(v: &'a Value, field: &str) -> EncodeResult<&'a Vec<Value>> {
1232 v.get(field)
1233 .and_then(Value::as_array)
1234 .ok_or_else(|| EncodeError(format!("{field} must be a list")))
1235}
1236
1237fn encode_slice_list(w: &mut Writer, slices: &[Value]) -> EncodeResult<()> {
1238 w.size(slices.len(), 1, 1024, "slices")?;
1239 for s in slices {
1240 w.bit(false);
1241 w.bit(false);
1242 encode_snssai(w, s)?;
1243 }
1244 Ok(())
1245}
1246
1247pub fn decode_global_ran_node_id(ie: &Ie, budget: &mut Budget) -> CodecResult<Value> {
1253 decode_ie(ie, |r| match choice(r, 4, "GlobalRANNodeID")? {
1254 0 => {
1255 let ext = r.extension_bit("GlobalGNB-ID")?;
1256 let has_ext = r.bit("GlobalGNB-ID")?;
1257 let plmn = Plmn::decode(r)?;
1258 choice(r, 2, "GNB-ID")?;
1259 let (id, bits) = r.varying_bits(22, 32, "gNB-ID")?;
1260 opt_extension_container(r, has_ext, budget)?;
1261 finish_sequence(r, ext)?;
1262 Ok(json!({"type": "gnb", "plmn": plmn.to_json(), "gnb_id": id, "gnb_id_bits": bits}))
1263 }
1264 1 => Err(semantic(
1265 "an ng-eNB GlobalRANNodeID; NetGet's AMF serves gNBs",
1266 )),
1267 _ => Err(semantic(
1268 "an N3IWF GlobalRANNodeID; NetGet's AMF serves gNBs",
1269 )),
1270 })
1271}
1272
1273pub fn global_gnb_id_ie(plmn: &Plmn, gnb_id: u64, bits: u32) -> EncodeResult<Ie> {
1274 encode_ie(IE_GLOBAL_RAN_NODE_ID, Criticality::Reject, |w| {
1275 put_choice(w, 0, 4, "GlobalRANNodeID")?;
1276 w.bit(false);
1277 w.bit(false);
1278 plmn.encode(w);
1279 put_choice(w, 0, 2, "GNB-ID")?;
1280 w.varying_bits(gnb_id, bits, 22, 32, "gnb_id")?;
1281 Ok(())
1282 })
1283}
1284
1285pub fn decode_name(ie: &Ie) -> CodecResult<String> {
1286 decode_ie(ie, |r| Ok(r.printable_string(1, 150, "name")?))
1287}
1288
1289pub fn name_ie(id: u16, crit: Criticality, name: &str) -> EncodeResult<Ie> {
1290 encode_ie(id, crit, |w| Ok(w.printable_string(name, 1, 150, "name")?))
1291}
1292
1293pub fn decode_supported_ta_list(ie: &Ie, budget: &mut Budget) -> CodecResult<Vec<Value>> {
1294 decode_ie(ie, |r| {
1295 let n = r.size(1, 256, "SupportedTAList")?;
1296 budget.take(n, "SupportedTAList")?;
1297 let mut out = Vec::with_capacity(n);
1298 for _ in 0..n {
1299 let ext = r.extension_bit("SupportedTAItem")?;
1300 let has_ext = r.bit("SupportedTAItem")?;
1301 let tac = decode_tac(r)?;
1302 let m = r.size(1, 12, "BroadcastPLMNList")?;
1303 budget.take(m, "BroadcastPLMNList")?;
1304 let mut plmns = Vec::with_capacity(m);
1305 for _ in 0..m {
1306 let bext = r.extension_bit("BroadcastPLMNItem")?;
1307 let bhas = r.bit("BroadcastPLMNItem")?;
1308 let plmn = Plmn::decode(r)?;
1309 let slices = decode_slice_list(r, budget)?;
1310 opt_extension_container(r, bhas, budget)?;
1311 finish_sequence(r, bext)?;
1312 plmns.push(json!({"plmn": plmn.to_json(), "slices": slices}));
1313 }
1314 opt_extension_container(r, has_ext, budget)?;
1315 finish_sequence(r, ext)?;
1316 out.push(json!({"tac": tac, "broadcast_plmns": plmns}));
1317 }
1318 Ok(out)
1319 })
1320}
1321
1322pub fn supported_ta_list_ie(list: &[Value]) -> EncodeResult<Ie> {
1324 encode_ie(IE_SUPPORTED_TA_LIST, Criticality::Reject, |w| {
1325 w.size(list.len(), 1, 256, "supported_ta_list")?;
1326 for ta in list {
1327 let tac = json_u64(ta, "tac", 0xFF_FFFF)?;
1328 let plmns = json_list(ta, "broadcast_plmns")?;
1329 w.bit(false);
1330 w.bit(false);
1331 w.fixed_octets(&tac_octets(tac));
1332 w.size(plmns.len(), 1, 12, "broadcast_plmns")?;
1333 for p in plmns {
1334 let plmn = Plmn::from_json(p.get("plmn").unwrap_or(&Value::Null))?;
1335 w.bit(false);
1336 w.bit(false);
1337 plmn.encode(w);
1338 encode_slice_list(w, json_list(p, "slices")?)?;
1339 }
1340 }
1341 Ok(())
1342 })
1343}
1344
1345pub fn paging_drx_ie(v: &str) -> EncodeResult<Ie> {
1346 let i = enum_index(PAGING_DRX, v, "default_paging_drx")?;
1347 encode_ie(IE_DEFAULT_PAGING_DRX, Criticality::Ignore, |w| {
1348 Ok(w.enumerated(i, false, 4, true, "PagingDRX")?)
1349 })
1350}
1351
1352pub fn decode_served_guami_list(ie: &Ie, budget: &mut Budget) -> CodecResult<Vec<Value>> {
1353 decode_ie(ie, |r| {
1354 let n = r.size(1, 256, "ServedGUAMIList")?;
1355 budget.take(n, "ServedGUAMIList")?;
1356 let mut out = Vec::with_capacity(n);
1357 for _ in 0..n {
1358 let ext = r.extension_bit("ServedGUAMIItem")?;
1359 let has_backup = r.bit("ServedGUAMIItem")?;
1360 let has_ext = r.bit("ServedGUAMIItem")?;
1361 let mut g = decode_guami(r, budget)?;
1362 if has_backup {
1363 g["backup_amf_name"] = json!(r.printable_string(1, 150, "backupAMFName")?);
1364 }
1365 opt_extension_container(r, has_ext, budget)?;
1366 finish_sequence(r, ext)?;
1367 out.push(g);
1368 }
1369 Ok(out)
1370 })
1371}
1372
1373fn decode_guami(r: &mut Reader, budget: &mut Budget) -> CodecResult<Value> {
1374 let ext = r.extension_bit("GUAMI")?;
1375 let has_ext = r.bit("GUAMI")?;
1376 let plmn = Plmn::decode(r)?;
1377 let region = r.fixed_bits(8, "AMFRegionID")?;
1378 let set = r.fixed_bits(10, "AMFSetID")?;
1379 let pointer = r.fixed_bits(6, "AMFPointer")?;
1380 opt_extension_container(r, has_ext, budget)?;
1381 finish_sequence(r, ext)?;
1382 Ok(
1383 json!({"plmn": plmn.to_json(), "amf_region_id": region, "amf_set_id": set, "amf_pointer": pointer}),
1384 )
1385}
1386
1387pub fn served_guami_list_ie(list: &[Value]) -> EncodeResult<Ie> {
1389 encode_ie(IE_SERVED_GUAMI_LIST, Criticality::Reject, |w| {
1390 w.size(list.len(), 1, 256, "served_guami")?;
1391 for g in list {
1392 let plmn = Plmn::from_json(g.get("plmn").unwrap_or(&Value::Null))?;
1393 let region = json_u64(g, "amf_region_id", 0xFF)?;
1394 let set = json_u64(g, "amf_set_id", 0x3FF)?;
1395 let pointer = json_u64(g, "amf_pointer", 0x3F)?;
1396 let backup = g.get("backup_amf_name").and_then(Value::as_str);
1397 w.bit(false);
1398 w.bit(backup.is_some());
1399 w.bit(false);
1400 w.bit(false);
1401 w.bit(false);
1402 plmn.encode(w);
1403 w.fixed_bits(region, 8);
1404 w.fixed_bits(set, 10);
1405 w.fixed_bits(pointer, 6);
1406 if let Some(b) = backup {
1407 w.printable_string(b, 1, 150, "backup_amf_name")?;
1408 }
1409 }
1410 Ok(())
1411 })
1412}
1413
1414pub fn relative_amf_capacity_ie(c: u64) -> EncodeResult<Ie> {
1415 encode_ie(IE_RELATIVE_AMF_CAPACITY, Criticality::Ignore, |w| {
1416 Ok(w.constrained(c, 0, 255, "relative_amf_capacity")?)
1417 })
1418}
1419
1420pub fn decode_relative_amf_capacity(ie: &Ie) -> CodecResult<u64> {
1421 decode_ie(ie, |r| Ok(r.constrained(0, 255, "RelativeAMFCapacity")?))
1422}
1423
1424pub fn decode_plmn_support_list(ie: &Ie, budget: &mut Budget) -> CodecResult<Vec<Value>> {
1425 decode_ie(ie, |r| {
1426 let n = r.size(1, 12, "PLMNSupportList")?;
1427 budget.take(n, "PLMNSupportList")?;
1428 let mut out = Vec::with_capacity(n);
1429 for _ in 0..n {
1430 let ext = r.extension_bit("PLMNSupportItem")?;
1431 let has_ext = r.bit("PLMNSupportItem")?;
1432 let plmn = Plmn::decode(r)?;
1433 let slices = decode_slice_list(r, budget)?;
1434 opt_extension_container(r, has_ext, budget)?;
1435 finish_sequence(r, ext)?;
1436 out.push(json!({"plmn": plmn.to_json(), "slices": slices}));
1437 }
1438 Ok(out)
1439 })
1440}
1441
1442pub fn plmn_support_list_ie(list: &[Value]) -> EncodeResult<Ie> {
1444 encode_ie(IE_PLMN_SUPPORT_LIST, Criticality::Reject, |w| {
1445 w.size(list.len(), 1, 12, "plmn_support")?;
1446 for p in list {
1447 let plmn = Plmn::from_json(p.get("plmn").unwrap_or(&Value::Null))?;
1448 w.bit(false);
1449 w.bit(false);
1450 plmn.encode(w);
1451 encode_slice_list(w, json_list(p, "slices")?)?;
1452 }
1453 Ok(())
1454 })
1455}
1456
1457pub fn decode_user_location(ie: &Ie, budget: &mut Budget) -> CodecResult<Value> {
1463 decode_ie(ie, |r| {
1464 match r.constrained(0, 3, "UserLocationInformation")? {
1466 choice @ (0 | 1) => {
1467 let ext = r.extension_bit("UserLocationInformation")?;
1468 let has_ts = r.bit("UserLocationInformation")?;
1469 let has_ext = r.bit("UserLocationInformation")?;
1470 let cext = r.extension_bit("CGI")?;
1472 let chas = r.bit("CGI")?;
1473 let cplmn = Plmn::decode(r)?;
1474 let cell = if choice == 1 {
1475 r.fixed_bits(36, "NRCellIdentity")?
1476 } else {
1477 r.fixed_bits(28, "EUTRACellIdentity")?
1478 };
1479 opt_extension_container(r, chas, budget)?;
1480 finish_sequence(r, cext)?;
1481 let text = r.extension_bit("TAI")?;
1483 let thas = r.bit("TAI")?;
1484 let tplmn = Plmn::decode(r)?;
1485 let tac = decode_tac(r)?;
1486 opt_extension_container(r, thas, budget)?;
1487 finish_sequence(r, text)?;
1488 let mut v = json!({
1489 "type": if choice == 1 { "nr" } else { "eutra" },
1490 "cgi": {"plmn": cplmn.to_json(), "cell_id": cell},
1491 "tai": {"plmn": tplmn.to_json(), "tac": tac},
1492 });
1493 if has_ts {
1494 let ts = r.fixed_octets(4, "TimeStamp")?;
1495 v["time_stamp"] = json!(u32::from_be_bytes([ts[0], ts[1], ts[2], ts[3]]));
1496 }
1497 opt_extension_container(r, has_ext, budget)?;
1498 finish_sequence(r, ext)?;
1499 Ok(v)
1500 }
1501 other => {
1502 while !r.at_end() {
1505 r.bits(8, "UserLocationInformation")?;
1506 }
1507 Ok(json!({"type": if other == 2 { "n3iwf" } else { CHOICE_EXTENSIONS }}))
1508 }
1509 }
1510 })
1511}
1512
1513pub fn user_location_nr_ie(v: &Value, crit: Criticality) -> EncodeResult<Ie> {
1515 let cgi = v
1516 .get("cgi")
1517 .ok_or_else(|| EncodeError("user_location.cgi is required".into()))?;
1518 let tai = v
1519 .get("tai")
1520 .ok_or_else(|| EncodeError("user_location.tai is required".into()))?;
1521 let cplmn = Plmn::from_json(cgi.get("plmn").unwrap_or(&Value::Null))?;
1522 let cell = json_u64(cgi, "cell_id", (1 << 36) - 1)?;
1523 let tplmn = Plmn::from_json(tai.get("plmn").unwrap_or(&Value::Null))?;
1524 let tac = json_u64(tai, "tac", 0xFF_FFFF)?;
1525 encode_ie(IE_USER_LOCATION_INFORMATION, crit, |w| {
1526 put_choice(w, 1, 4, "UserLocationInformation")?;
1527 w.bit(false);
1528 w.bit(false);
1529 w.bit(false);
1530 w.bit(false);
1531 w.bit(false);
1532 cplmn.encode(w);
1533 w.fixed_bits(cell, 36);
1534 w.bit(false);
1535 w.bit(false);
1536 tplmn.encode(w);
1537 w.fixed_octets(&tac_octets(tac));
1538 Ok(())
1539 })
1540}
1541
1542fn decode_five_g_s_tmsi(ie: &Ie, budget: &mut Budget) -> CodecResult<Value> {
1543 decode_ie(ie, |r| {
1544 let ext = r.extension_bit("FiveG-S-TMSI")?;
1545 let has_ext = r.bit("FiveG-S-TMSI")?;
1546 let set = r.fixed_bits(10, "AMFSetID")?;
1547 let pointer = r.fixed_bits(6, "AMFPointer")?;
1548 let t = r.fixed_octets(4, "FiveG-TMSI")?;
1549 opt_extension_container(r, has_ext, budget)?;
1550 finish_sequence(r, ext)?;
1551 Ok(json!({"amf_set_id": set, "amf_pointer": pointer,
1552 "tmsi": u32::from_be_bytes([t[0], t[1], t[2], t[3]])}))
1553 })
1554}
1555
1556pub fn decode_ue_ngap_ids(ie: &Ie, budget: &mut Budget) -> CodecResult<(u64, Option<u64>)> {
1558 decode_ie(ie, |r| {
1559 if choice(r, 3, "UE-NGAP-IDs")? == 0 {
1560 let ext = r.extension_bit("UE-NGAP-ID-pair")?;
1561 let has_ext = r.bit("UE-NGAP-ID-pair")?;
1562 let amf = r.constrained(0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?;
1563 let ran = r.constrained(0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?;
1564 opt_extension_container(r, has_ext, budget)?;
1565 finish_sequence(r, ext)?;
1566 Ok((amf, Some(ran)))
1567 } else {
1568 Ok((
1569 r.constrained(0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?,
1570 None,
1571 ))
1572 }
1573 })
1574}
1575
1576pub fn ue_ngap_ids_ie(amf: u64, ran: Option<u64>) -> EncodeResult<Ie> {
1577 encode_ie(IE_UE_NGAP_IDS, Criticality::Reject, |w| {
1578 match ran {
1579 Some(ran) => {
1580 put_choice(w, 0, 3, "UE-NGAP-IDs")?;
1581 w.bit(false);
1582 w.bit(false);
1583 w.constrained(amf, 0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?;
1584 w.constrained(ran, 0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?;
1585 }
1586 None => {
1587 put_choice(w, 1, 3, "UE-NGAP-IDs")?;
1588 w.constrained(amf, 0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?;
1589 }
1590 }
1591 Ok(())
1592 })
1593}
1594
1595pub type ConnectionItem = (Option<u64>, Option<u64>);
1597
1598fn decode_connection_list(r: &mut Reader, budget: &mut Budget) -> CodecResult<Vec<ConnectionItem>> {
1599 let n = r.size(1, 65_536, "UE-associatedLogicalNG-connectionList")?;
1600 budget.take(n, "UE-associatedLogicalNG-connectionList")?;
1601 let mut out = Vec::with_capacity(n);
1602 for _ in 0..n {
1603 let ext = r.extension_bit("UE-associatedLogicalNG-connectionItem")?;
1604 let has_amf = r.bit("item")?;
1605 let has_ran = r.bit("item")?;
1606 let has_ext = r.bit("item")?;
1607 let amf = if has_amf {
1608 Some(r.constrained(0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?)
1609 } else {
1610 None
1611 };
1612 let ran = if has_ran {
1613 Some(r.constrained(0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?)
1614 } else {
1615 None
1616 };
1617 opt_extension_container(r, has_ext, budget)?;
1618 finish_sequence(r, ext)?;
1619 out.push((amf, ran));
1620 }
1621 Ok(out)
1622}
1623
1624fn encode_connection_list(w: &mut Writer, items: &[ConnectionItem]) -> EncodeResult<()> {
1625 w.size(items.len(), 1, 65_536, "connection list")?;
1626 for (amf, ran) in items {
1627 w.bit(false);
1628 w.bit(amf.is_some());
1629 w.bit(ran.is_some());
1630 w.bit(false);
1631 if let Some(a) = amf {
1632 w.constrained(*a, 0, MAX_AMF_UE_NGAP_ID, "AMF-UE-NGAP-ID")?;
1633 }
1634 if let Some(r) = ran {
1635 w.constrained(*r, 0, MAX_RAN_UE_NGAP_ID, "RAN-UE-NGAP-ID")?;
1636 }
1637 }
1638 Ok(())
1639}
1640
1641pub fn decode_reset_type(ie: &Ie, budget: &mut Budget) -> CodecResult<Option<Vec<ConnectionItem>>> {
1643 decode_ie(ie, |r| {
1644 if choice(r, 3, "ResetType")? == 0 {
1645 r.enumerated(1, true, "ResetAll")?;
1646 Ok(None)
1647 } else {
1648 Ok(Some(decode_connection_list(r, budget)?))
1649 }
1650 })
1651}
1652
1653pub fn reset_type_ie(partial: Option<&[ConnectionItem]>) -> EncodeResult<Ie> {
1654 encode_ie(IE_RESET_TYPE, Criticality::Reject, |w| {
1655 match partial {
1656 None => {
1657 put_choice(w, 0, 3, "ResetType")?;
1658 w.enumerated(0, false, 1, true, "ResetAll")?;
1659 }
1660 Some(items) => {
1661 put_choice(w, 1, 3, "ResetType")?;
1662 encode_connection_list(w, items)?;
1663 }
1664 }
1665 Ok(())
1666 })
1667}
1668
1669pub fn decode_connection_list_ie(ie: &Ie, budget: &mut Budget) -> CodecResult<Vec<ConnectionItem>> {
1670 decode_ie(ie, |r| decode_connection_list(r, budget))
1671}
1672
1673pub fn connection_list_ie(items: &[ConnectionItem]) -> EncodeResult<Ie> {
1674 encode_ie(
1675 IE_UE_ASSOCIATED_LOGICAL_NG_CONNECTION_LIST,
1676 Criticality::Ignore,
1677 |w| encode_connection_list(w, items),
1678 )
1679}
1680
1681pub fn parse_ng_setup_request(pdu: &Pdu) -> CodecResult<Value> {
1687 let mut budget = Budget::default();
1688 let node = decode_global_ran_node_id(pdu.require(IE_GLOBAL_RAN_NODE_ID)?, &mut budget)?;
1689 let tas = decode_supported_ta_list(pdu.require(IE_SUPPORTED_TA_LIST)?, &mut budget)?;
1690 let drx = decode_paging_drx(pdu.require(IE_DEFAULT_PAGING_DRX)?)?;
1691 let name = match pdu.ie(IE_RAN_NODE_NAME) {
1692 Some(ie) => Some(decode_name(ie)?),
1693 None => None,
1694 };
1695 Ok(json!({
1696 "global_ran_node_id": node,
1697 "ran_node_name": name,
1698 "supported_ta_list": tas,
1699 "default_paging_drx": drx,
1700 "uninterpreted_ies": pdu.uninterpreted_ies(&[
1701 IE_GLOBAL_RAN_NODE_ID, IE_SUPPORTED_TA_LIST, IE_DEFAULT_PAGING_DRX, IE_RAN_NODE_NAME
1702 ]),
1703 }))
1704}
1705
1706pub fn parse_ng_setup_response(pdu: &Pdu) -> CodecResult<Value> {
1708 let mut budget = Budget::default();
1709 let name = decode_name(pdu.require(IE_AMF_NAME)?)?;
1710 let guamis = decode_served_guami_list(pdu.require(IE_SERVED_GUAMI_LIST)?, &mut budget)?;
1711 let capacity = decode_relative_amf_capacity(pdu.require(IE_RELATIVE_AMF_CAPACITY)?)?;
1712 let plmns = decode_plmn_support_list(pdu.require(IE_PLMN_SUPPORT_LIST)?, &mut budget)?;
1713 Ok(json!({
1714 "amf_name": name,
1715 "served_guami": guamis,
1716 "relative_amf_capacity": capacity,
1717 "plmn_support": plmns,
1718 "uninterpreted_ies": pdu.uninterpreted_ies(&[
1719 IE_AMF_NAME, IE_SERVED_GUAMI_LIST, IE_RELATIVE_AMF_CAPACITY, IE_PLMN_SUPPORT_LIST
1720 ]),
1721 }))
1722}
1723
1724pub fn parse_cause_message(pdu: &Pdu) -> CodecResult<Value> {
1726 let mut v = Map::new();
1727 if let Some(ie) = pdu.ie(IE_CAUSE) {
1728 v.insert("cause".into(), decode_cause(ie)?.to_json());
1729 }
1730 if let Some(ie) = pdu.ie(IE_TIME_TO_WAIT) {
1731 v.insert("time_to_wait".into(), json!(decode_time_to_wait(ie)?));
1732 }
1733 if let Some(ie) = pdu.ie(IE_AMF_UE_NGAP_ID) {
1734 v.insert("amf_ue_ngap_id".into(), json!(decode_amf_ue_ngap_id(ie)?));
1735 }
1736 if let Some(ie) = pdu.ie(IE_RAN_UE_NGAP_ID) {
1737 v.insert("ran_ue_ngap_id".into(), json!(decode_ran_ue_ngap_id(ie)?));
1738 }
1739 v.insert(
1740 "uninterpreted_ies".into(),
1741 json!(pdu.uninterpreted_ies(&[
1742 IE_CAUSE,
1743 IE_TIME_TO_WAIT,
1744 IE_AMF_UE_NGAP_ID,
1745 IE_RAN_UE_NGAP_ID
1746 ])),
1747 );
1748 Ok(Value::Object(v))
1749}
1750
1751pub struct InitialUe {
1753 pub ran_ue_ngap_id: u64,
1754 pub nas: Vec<u8>,
1755 pub user_location: Value,
1756 pub rrc_establishment_cause: String,
1757 pub five_g_s_tmsi: Option<Value>,
1758 pub uninterpreted: Vec<Value>,
1759}
1760
1761pub fn parse_initial_ue_message(pdu: &Pdu) -> CodecResult<InitialUe> {
1763 let mut budget = Budget::default();
1764 let ran = decode_ran_ue_ngap_id(pdu.require(IE_RAN_UE_NGAP_ID)?)?;
1765 let nas = decode_nas_pdu(pdu.require(IE_NAS_PDU)?)?;
1766 if nas.is_empty() {
1767 return Err(semantic("empty NAS-PDU"));
1768 }
1769 let uli = decode_user_location(pdu.require(IE_USER_LOCATION_INFORMATION)?, &mut budget)?;
1770 let cause = decode_rrc_cause(pdu.require(IE_RRC_ESTABLISHMENT_CAUSE)?)?;
1771 let tmsi = match pdu.ie(IE_FIVE_G_S_TMSI) {
1772 Some(ie) => Some(decode_five_g_s_tmsi(ie, &mut budget)?),
1773 None => None,
1774 };
1775 Ok(InitialUe {
1776 ran_ue_ngap_id: ran,
1777 nas,
1778 user_location: uli,
1779 rrc_establishment_cause: cause,
1780 five_g_s_tmsi: tmsi,
1781 uninterpreted: pdu.uninterpreted_ies(&[
1782 IE_RAN_UE_NGAP_ID,
1783 IE_NAS_PDU,
1784 IE_USER_LOCATION_INFORMATION,
1785 IE_RRC_ESTABLISHMENT_CAUSE,
1786 IE_FIVE_G_S_TMSI,
1787 ]),
1788 })
1789}
1790
1791pub struct NasTransport {
1793 pub amf_ue_ngap_id: u64,
1794 pub ran_ue_ngap_id: u64,
1795 pub nas: Vec<u8>,
1796 pub user_location: Option<Value>,
1797 pub uninterpreted: Vec<Value>,
1798}
1799
1800pub fn parse_nas_transport(pdu: &Pdu) -> CodecResult<NasTransport> {
1802 let mut budget = Budget::default();
1803 let amf = decode_amf_ue_ngap_id(pdu.require(IE_AMF_UE_NGAP_ID)?)?;
1804 let ran = decode_ran_ue_ngap_id(pdu.require(IE_RAN_UE_NGAP_ID)?)?;
1805 let nas = decode_nas_pdu(pdu.require(IE_NAS_PDU)?)?;
1806 if nas.is_empty() {
1807 return Err(semantic("empty NAS-PDU"));
1808 }
1809 let uli = match pdu.ie(IE_USER_LOCATION_INFORMATION) {
1810 Some(ie) => Some(decode_user_location(ie, &mut budget)?),
1811 None if pdu.procedure_code == PROC_UPLINK_NAS_TRANSPORT => {
1812 return Err(abstract_reject(
1813 "UplinkNASTransport is missing mandatory IE 121 (UserLocationInformation)",
1814 ))
1815 }
1816 None => None,
1817 };
1818 Ok(NasTransport {
1819 amf_ue_ngap_id: amf,
1820 ran_ue_ngap_id: ran,
1821 nas,
1822 user_location: uli,
1823 uninterpreted: pdu.uninterpreted_ies(&[
1824 IE_AMF_UE_NGAP_ID,
1825 IE_RAN_UE_NGAP_ID,
1826 IE_NAS_PDU,
1827 IE_USER_LOCATION_INFORMATION,
1828 ]),
1829 })
1830}
1831
1832fn build(kind: PduKind, code: u8, crit: Criticality, ies: Vec<Ie>) -> EncodeResult<Vec<u8>> {
1837 encode_pdu(&Pdu::new(kind, code, crit, ies))
1838}
1839
1840pub fn ng_setup_response(
1842 amf_name: &str,
1843 served_guami: &[Value],
1844 relative_amf_capacity: u64,
1845 plmn_support: &[Value],
1846) -> EncodeResult<Vec<u8>> {
1847 build(
1848 PduKind::Successful,
1849 PROC_NG_SETUP,
1850 Criticality::Reject,
1851 vec![
1852 name_ie(IE_AMF_NAME, Criticality::Reject, amf_name)?,
1853 served_guami_list_ie(served_guami)?,
1854 relative_amf_capacity_ie(relative_amf_capacity)?,
1855 plmn_support_list_ie(plmn_support)?,
1856 ],
1857 )
1858}
1859
1860pub fn ng_setup_failure(cause: &Cause, time_to_wait: Option<&str>) -> EncodeResult<Vec<u8>> {
1861 let mut ies = vec![cause_ie(cause, Criticality::Ignore)?];
1862 if let Some(t) = time_to_wait {
1863 ies.push(time_to_wait_ie(t)?);
1864 }
1865 build(
1866 PduKind::Unsuccessful,
1867 PROC_NG_SETUP,
1868 Criticality::Reject,
1869 ies,
1870 )
1871}
1872
1873pub fn ng_setup_request(
1874 plmn: &Plmn,
1875 gnb_id: u64,
1876 gnb_id_bits: u32,
1877 ran_node_name: Option<&str>,
1878 supported_ta_list: &[Value],
1879 paging_drx: &str,
1880) -> EncodeResult<Vec<u8>> {
1881 let mut ies = vec![global_gnb_id_ie(plmn, gnb_id, gnb_id_bits)?];
1882 if let Some(n) = ran_node_name {
1883 ies.push(name_ie(IE_RAN_NODE_NAME, Criticality::Ignore, n)?);
1884 }
1885 ies.push(supported_ta_list_ie(supported_ta_list)?);
1886 ies.push(paging_drx_ie(paging_drx)?);
1887 build(PduKind::Initiating, PROC_NG_SETUP, Criticality::Reject, ies)
1888}
1889
1890pub fn downlink_nas_transport(amf: u64, ran: u64, nas: &[u8]) -> EncodeResult<Vec<u8>> {
1891 build(
1892 PduKind::Initiating,
1893 PROC_DOWNLINK_NAS_TRANSPORT,
1894 Criticality::Ignore,
1895 vec![
1896 amf_ue_ngap_id_ie(amf, Criticality::Reject)?,
1897 ran_ue_ngap_id_ie(ran, Criticality::Reject)?,
1898 nas_pdu_ie(nas)?,
1899 ],
1900 )
1901}
1902
1903pub fn uplink_nas_transport(
1904 amf: u64,
1905 ran: u64,
1906 nas: &[u8],
1907 user_location: &Value,
1908) -> EncodeResult<Vec<u8>> {
1909 build(
1910 PduKind::Initiating,
1911 PROC_UPLINK_NAS_TRANSPORT,
1912 Criticality::Ignore,
1913 vec![
1914 amf_ue_ngap_id_ie(amf, Criticality::Reject)?,
1915 ran_ue_ngap_id_ie(ran, Criticality::Reject)?,
1916 nas_pdu_ie(nas)?,
1917 user_location_nr_ie(user_location, Criticality::Ignore)?,
1918 ],
1919 )
1920}
1921
1922pub fn initial_ue_message(
1923 ran: u64,
1924 nas: &[u8],
1925 user_location: &Value,
1926 rrc_establishment_cause: &str,
1927) -> EncodeResult<Vec<u8>> {
1928 build(
1929 PduKind::Initiating,
1930 PROC_INITIAL_UE_MESSAGE,
1931 Criticality::Ignore,
1932 vec![
1933 ran_ue_ngap_id_ie(ran, Criticality::Reject)?,
1934 nas_pdu_ie(nas)?,
1935 user_location_nr_ie(user_location, Criticality::Reject)?,
1936 rrc_cause_ie(rrc_establishment_cause)?,
1937 ],
1938 )
1939}
1940
1941pub fn error_indication(
1942 amf: Option<u64>,
1943 ran: Option<u64>,
1944 cause: Option<&Cause>,
1945) -> EncodeResult<Vec<u8>> {
1946 let mut ies = Vec::new();
1947 if let Some(a) = amf {
1948 ies.push(amf_ue_ngap_id_ie(a, Criticality::Ignore)?);
1949 }
1950 if let Some(r) = ran {
1951 ies.push(ran_ue_ngap_id_ie(r, Criticality::Ignore)?);
1952 }
1953 if let Some(c) = cause {
1954 ies.push(cause_ie(c, Criticality::Ignore)?);
1955 }
1956 build(
1957 PduKind::Initiating,
1958 PROC_ERROR_INDICATION,
1959 Criticality::Ignore,
1960 ies,
1961 )
1962}
1963
1964pub fn ng_reset(cause: &Cause, partial: Option<&[ConnectionItem]>) -> EncodeResult<Vec<u8>> {
1965 build(
1966 PduKind::Initiating,
1967 PROC_NG_RESET,
1968 Criticality::Reject,
1969 vec![
1970 cause_ie(cause, Criticality::Ignore)?,
1971 reset_type_ie(partial)?,
1972 ],
1973 )
1974}
1975
1976pub fn ng_reset_acknowledge(partial: Option<&[ConnectionItem]>) -> EncodeResult<Vec<u8>> {
1979 let mut ies = Vec::new();
1980 if let Some(items) = partial {
1981 ies.push(connection_list_ie(items)?);
1982 }
1983 build(PduKind::Successful, PROC_NG_RESET, Criticality::Reject, ies)
1984}
1985
1986pub fn ue_context_release_command(
1987 amf: u64,
1988 ran: Option<u64>,
1989 cause: &Cause,
1990) -> EncodeResult<Vec<u8>> {
1991 build(
1992 PduKind::Initiating,
1993 PROC_UE_CONTEXT_RELEASE,
1994 Criticality::Reject,
1995 vec![
1996 ue_ngap_ids_ie(amf, ran)?,
1997 cause_ie(cause, Criticality::Ignore)?,
1998 ],
1999 )
2000}
2001
2002pub fn ue_context_release_complete(amf: u64, ran: u64) -> EncodeResult<Vec<u8>> {
2003 build(
2004 PduKind::Successful,
2005 PROC_UE_CONTEXT_RELEASE,
2006 Criticality::Reject,
2007 vec![
2008 amf_ue_ngap_id_ie(amf, Criticality::Ignore)?,
2009 ran_ue_ngap_id_ie(ran, Criticality::Ignore)?,
2010 ],
2011 )
2012}
2013
2014pub fn summarize(bytes: &[u8]) -> Option<String> {
2016 let p = decode_pdu(bytes).ok()?;
2017 Some(p.label())
2018}
2019
2020pub use aper::AperError as WireError;