Expand description
A codec for S1AP, the S1 Application Protocol of 3GPP TS 36.413, in aligned PER.
use oxirush_s1ap::{build_s1ap, extract_s1ap_ies, s1ap::*};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Decode a UE CONTEXT RELEASE REQUEST
let bytes = hex::decode("0012401500000300000002002a000800020007000240020280")?;
let pdu = S1AP_PDU::decode(&bytes)?;
assert_eq!((pdu.procedure_code(), pdu.is_initiating()), (18, true));
println!("{}", pdu.procedure_name());
// Read its IEs by name. An `opt` field is `None` when its IE is not there,
// and what follows `=>` is what the field keeps of the decoded value
let request: UEContextReleaseRequest = pdu.decode_value()?;
extract_s1ap_ies!(request, UEContextReleaseRequest,
opt mme_id: u32 = MME_UE_S1AP_ID(id),
opt enb_id: u32 = eNB_UE_S1AP_ID(id),
opt cause: Cause = Cause(cause) => cause,
);
println!("MME={mme_id:?} eNB={enb_id:?} cause={cause:?}");
// Build it with another cause: the direction, the procedure, the criticality
// of the PDU and the message, then each IE with its criticality
let rebuilt = build_s1ap!(InitiatingMessage, UEContextReleaseRequest,
IGNORE, UEContextReleaseRequest,
REJECT MME_UE_S1AP_ID(42u32),
REJECT eNB_UE_S1AP_ID(7u32),
IGNORE Cause(Cause::radioNetwork(CauseRadioNetwork::radio_connection_with_ue_lost)),
)?;
// Encode it
let encoded = hex::encode(rebuilt.encode()?);
assert_eq!(encoded, "0012401500000300000002002a0008000200070002400202a0");
Ok(())
}UEContextReleaseRequest
MME=Some(42) eNB=Some(7) cause=Some(radioNetwork(user_inactivity))The types of s1ap are generated from the ASN.1 modules of the specification and
checked in: rasn-compiler resolves the modules, and the generator of the crate
writes each type with its codec on oxirush-aper.
§Tasks
The README has an example of each, with what it prints.
| Task | Items |
|---|---|
| Answer an eNB, from S1 SETUP to the INITIAL CONTEXT SETUP | build_s1ap!, build_s1ap_ie!, extract_s1ap_ies!, helpers, S1AP_PDU::ie |
| Decode a PDU | S1AP_PDU::decode, S1AP_PDU::decode_borrowed, S1AP_PDU::kind, S1AP_PDU::decode_value |
| Build a PDU | build_s1ap!, S1AP_PDU::of_message |
| Read the IEs of a message | extract_s1ap_ies!, ie, with_s1ap_ie_mut!, build_s1ap_ie! |
| A list of single containers, and a handover container, typed | S1AP_PDU::items, s1ap::decode_open_type, s1ap::encode_open_type |
| Check a message, and answer one that is faulty | S1AP_PDU::check, S1AP_PDU::check_structure, Deviation, S1AP_PDU::criticality_diagnostics_for_response |
| A PDU as JSON | S1AP_PDU::to_tree, S1AP_PDU::to_view, S1AP_PDU::with_view, inspect |
| Write a message from its tree | S1AP_PDU::from_tree |
Every function and macro that can fail returns an Error: its ErrorKind is
what a caller matches on, and its Display says what is wrong. error.ie() names
the IE of an error of the macros.
use oxirush_s1ap::{ErrorKind, s1ap::*};
// A UE CONTEXT RELEASE REQUEST is not decoded as an S1 SETUP REQUEST
let bytes = hex::decode("0012401500000300000002002a000800020007000240020280").unwrap();
let pdu = S1AP_PDU::decode(&bytes).unwrap();
let wrong = pdu.decode_value::<S1SetupRequest>().unwrap_err();
assert_eq!(wrong.kind(), ErrorKind::WrongMessage);
println!("{wrong}");
// Octets that end early
let short = S1AP_PDU::decode(&bytes[..10]).unwrap_err();
assert_eq!(short.kind(), ErrorKind::Decode);
println!("{short}");the message of this PDU is UEContextReleaseRequest, not S1SetupRequest: kind() says which message a PDU holds
these octets do not decode as S1AP-PDU: the octets end before the value does, in its field `value`The codec builds, encodes and decodes a message whatever its IEs are. check() of
a PDU reports how it deviates from what the ASN.1 assigns its message, each as a
Deviation: a criticality of the PDU or of an IE that is not the assigned one, an
IE that the message cannot have, has twice or has out of order, a mandatory IE that
is missing, an IE whose value does not decode or is of a later version of its type,
an encoding in another form than the canonical one, and a procedure that the
specification does not have. criticality_diagnostics() of the PDU gathers them
into the Criticality Diagnostics IE of the answer.
§Values that borrow, and values that are kept
A value that is decoded borrows the octets that it holds from what it was decoded
from: the NAS-PDU of a message is a slice of the PDU, and nothing is copied. A type
that holds octets has a lifetime for that, as InitialUEMessage<'a> and
NASPDU<'a>. A type that holds none, as Cause, TAI, EUTRANCGI or GUMMEI,
has no lifetime. The extension container of a value owns its octets, as a peer
seldom sends one, so that it gives the value no lifetime.
| To | Write |
|---|---|
| Read a message where it was received | let pdu = S1AP_PDU::decode(&octets)?; then pdu.decode_value()?: the message borrows from pdu, which is a variable of its own |
| Keep a value after the PDU is gone | value.into_owned(), which gives the same type with the lifetime 'static, as NASPDU<'static>: this is the type to write in a struct |
| Build a value | From data that it owns: NASPDU::from(vec) is a NASPDU<'static> |
| Read a PDU in the buffer that it was received in | S1AP_PDU::decode_borrowed, which copies nothing: the PDU borrows from the buffer |
S1AP_PDU is the PDU that owns its octets, S1APPDU<'static>,
and has no lifetime to write.
use oxirush_s1ap::{Error, build_s1ap, extract_s1ap_ies, s1ap::*};
/// What a node keeps of a UE after its first message.
struct Ue {
enb_id: u32,
// The context owns these octets: `'static`, and no lifetime on `Ue`
nas: NASPDU<'static>,
}
fn admit(pdu: &S1AP_PDU) -> Result<Ue, Error> {
// `message` borrows from `pdu`, and `nas` from `message`
let message: InitialUEMessage = pdu.decode_value()?;
extract_s1ap_ies!(message, InitialUEMessage,
req enb_id: u32 = eNB_UE_S1AP_ID(id),
req nas: NASPDU = NAS_PDU(nas) => nas,
);
// Kept beyond the PDU: the octets are copied here, once
Ok(Ue { enb_id, nas: nas.into_owned() })
}
let pdu = build_s1ap!(InitiatingMessage, initialUEMessage, IGNORE, InitialUEMessage,
REJECT eNB_UE_S1AP_ID(7u32),
REJECT NAS_PDU(vec![0x07, 0x41, 0x71]),
)?;
let ue = admit(&pdu)?;
drop(pdu);
assert_eq!((ue.enb_id, &ue.nas.0[..]), (7, &[0x07, 0x41, 0x71][..]));A message may be held across an .await. The future of this handler owns the
buffer, the PDU borrows from the buffer, the message from the PDU and the NAS-PDU
from the message. The future is Send, as every value of the bindings is.
use oxirush_s1ap::{Error, build_s1ap, extract_s1ap_ies, s1ap::*};
async fn deliver(nas: &[u8]) {
assert_eq!(nas, [0x07, 0x5e, 0x03]);
}
async fn handle(received: Vec<u8>) -> Result<(), Error> {
let pdu = S1AP_PDU::decode_borrowed(&received)?;
let message: UplinkNASTransport = pdu.decode_value()?;
extract_s1ap_ies!(message, UplinkNASTransport,
req nas: NASPDU = NAS_PDU(nas) => nas,
);
// `nas` is a slice of `received`, here and after the await
deliver(&nas.0).await;
assert!(nas.0.is_borrowed());
Ok(())
}
let transport = build_s1ap!(InitiatingMessage, uplinkNASTransport, IGNORE, UplinkNASTransport,
REJECT MME_UE_S1AP_ID(1u32),
REJECT eNB_UE_S1AP_ID(7u32),
REJECT NAS_PDU(vec![0x07, 0x5e, 0x03]),
)?;
let handler = handle(transport.encode()?);
// A runtime may move the handler to another thread
fn is_send(_: &impl Send) {}
is_send(&handler);§The names of the fields
A field has the words of the identifier that ASN.1 gives its member, in lower case
with _ between them, as a reader of the specification says them: pLMNidentity is
plmn_identity, tAC is tac, iE-Extensions is ie_extensions, eNB-ID is
enb_id, mME-Code is mme_code, gTP-TEID is gtp_teid.
- A hyphen ends a word, and so does a lower-case letter or a digit before a
capital:
procedureCodeisprocedure_code. - The first letter of an identifier belongs to the capitals after it: the identifier starts in lower case whatever its first word is.
- Capitals in a row are an abbreviation, and the last of them starts a word when a
lower-case letter follows. When the capitals without it are no abbreviation of
the list and the capitals with it are one, they end the abbreviation:
nRCellIdentityisnr_cell_identity, andpLMNIdentityisplmn_identityaspLMNidentityis. Ansafter them is their plural:protocolIEsisprotocol_ies. - Capitals that are several abbreviations of the list are these:
eNBX2TransportLayerAddressesisenb_x2_transport_layer_addresses. - An abbreviation that the specifications write with a lower-case letter is one
word wherever it is:
a2X,AIoT,IoT,QoS,QoE,ProSe,eNB,gNB,IPsec,IPv4,IPv6,NRPPa,LPPa,n3IWF,eDRX,eCGI.gNB-IDisgnb_id. fiveGandfiveGSarefive_gandfive_gs, and5GSis one word.
The list of the abbreviations is ABBREVIATIONS of generator/src/names.rs in
oxirush-aper, with the identifiers that the specifications write otherwise than
their others, as parentTImeSource. build/fields.tsv of the repository has each
field of each type with the name that rasn-compiler gives it, which the fields had
before this rule and have in 0.2.0 for every field of a type that 0.2.0 has but one,
the excludedCellsToAddModList of InterSystemMeasurementItem. The JSON forms keep
the identifiers of ASN.1.
A SEQUENCE is a struct that a literal builds, and its new takes the members in
their order. An extensible CHOICE or ENUMERATED is #[non_exhaustive]: a later
version of the specification adds alternatives and values to it, which a match
leaves room for with a _ arm. A member that a later version adds to a SEQUENCE
changes its struct and its new, and comes with a breaking release of the crate.
§The types of the values
| ASN.1 | Rust |
|---|---|
INTEGER with a constraint | The native number that holds its range: MMEUES1APID(pub u32) for (0..4294967295) |
INTEGER whose constraint has an extension marker | u64: ERABID(pub u64) for (0..15, ...) |
OCTET STRING (SIZE (n)) | [u8; n]: TAC(pub [u8; 2]) |
Another OCTET STRING, and an open type | Octets: NASPDU<'a>(pub Octets<'a>) |
BIT STRING of 64 bits at most, without an extension marker | Bits, a number with a length, which is Copy. A type of one size is made of the number: CellIdentity::new(0x1234507)? |
Another BIT STRING | BitString, which borrows its octets when it is longer than 64 bits |
PrintableString, VisibleString, UTF8String | Cow<'a, str>: MMEname<'a> |
ENUMERATED, CHOICE, SEQUENCE, SEQUENCE OF | An enum, an enum, a struct, and a Vec in a struct |
A bit string is a Bits when its type says that 64 bits hold it. A string that
may be longer, and one whose size has an extension marker, which a later release
may send longer, is a BitString:
Bits | BitString |
|---|---|
ENBID::macroENB_ID (20 bits), ENBID::homeENB_ID (28) | TransportLayerAddress (1 to 160, …) |
CellIdentity (28), NRCellIdentity (36), CSGId (27) | SecurityKey (256) |
MaskedIMEISV (64), GNBID (22 to 32) | EncryptionAlgorithms, IntegrityProtectionAlgorithms, NRencryptionAlgorithms and NRintegrityProtectionAlgorithms (16, …) |
UEIdentityIndexValue (10), MessageIdentifier, SerialNumber (16), InterfacesToTrace (8) | EnGNBID (22 to 32, …), ReceiveStatusofULPDCPSDUs (4096) |
The bindings hold an INTEGER in 64 bits. A number that an extension marker lets
a later release send beyond that does not decode, with an error of the kind
Decode that says the number does not fit in 64 bits. The PDU still decodes:
its IEs are octets until one is read, and the JSON forms of the inspect feature
show an IE that does not decode as the octets received, and write them back.
§The OxiRush family
- oxirush-utils: the 3GPP identities, and the JSON tree with its paths that the others share.
- oxirush-aper: aligned PER, the runtime of the NGAP and S1AP codecs.
- oxirush-nas: the NAS messages of 5GS and EPS.
- oxirush-security: NAS security, AKA and the derivation of keys.
- oxirush-ngap: NGAP, between a gNB and an AMF.
- oxirush-s1ap: S1AP, between an eNB and an MME.
- oxirush-gtp-u: GTP-U tunnels, with a test UPF.
- oxirush-sctp: SCTP sockets, which carry NGAP and S1AP.
Re-exports§
pub use s1ap::S1apPduKind;pub use oxirush_aper as aper;
Modules§
- helpers
- Convenience helpers for building common S1AP types.
- identity
- The identities of 3GPP networks as values, of the
oxirush-utilsthat this crate is built with: aPLMNidentityconverts to and from itsPlmn, which has the MCC and the MNC. The identities of 3GPP networks as values: what every family of messages holds and prints the same way. - ie
- The IEs of the protocol, one marker type each, by the names that the macros take:
ies::<T>()andie::<T>()of a PDU and of a message take them, aspdu.ie::<ie::Cause>(). Each implementsaper::protocol::Ie, which gives its identifier and the type of its value. The IEs of the protocol, one marker type each, by the names that the macros take: its own, after the identifier that ASN.1 gives it, and the name of its type for the one IE of that type. - inspect
inspect - Decoded PDUs as JSON, in two forms: a view, which is read and edited by paths and encoded again, and a tree, the short form in which a message is shown and written.
- macros
- Ergonomic macros for building, extracting, and mutating S1AP protocol IEs.
- s1ap
- The types of the ASN.1 modules of TS 36.413, as the generator writes them:
S1AP_PDUand its three directions, each message, each IE and each type of a value, which decode and encode themselves in aligned PER. - tree
inspect - The trees of
oxirush-utils, of the version that this crate is built with: a PDU and each value of the bindings as JSON whose members keep the order of the specification,tree::Value, andtree!, which writes one in the code asserde_json::json!does. A crate that reads or writes them takes them from here. One tree for the messages of every family: the tree of a value as JSON, the forms of its plain values, and paths into it.
Macros§
- build_
s1ap - Build a complete
S1AP_PDUfrom a direction, procedure, message, and IEs. - build_
s1ap_ ie - Build one S1AP protocol IE entry. Every message takes the same
ProtocolIEField: the name before the comma says where the IE goes, and is the type of a message. The IE has the criticality that the object set of that message assigns it, unless the call writes its own. - extract_
s1ap_ ies - Extract typed S1AP protocol IEs into
req,optandlaxfields. - tree
inspect - The trees of
oxirush-utils, of the version that this crate is built with: a PDU and each value of the bindings as JSON whose members keep the order of the specification,tree::Value, andtree!, which writes one in the code asserde_json::json!does. A crate that reads or writes them takes them from here. Atree::Valuewritten as JSON, asserde_json::json!writes one, whose objects keep their members in the order in which they are written. A value in it is a literal or any expression that converts into a tree, which it takes as a call takes its argument: a variable that holds a tree moves into it, and is given by reference to stay where it is (&name); a name is a string literal or an expression, as a variable, that converts into aString. - with_
s1ap_ ie_ mut - Locate, decode, mutate, and re-encode one S1AP protocol IE.
Structs§
- BitString
- A BIT STRING of any length: its octets, the last one padded with zero bits, and how many bits it has.
- Bits
- A BIT STRING of 64 bits at most: a number with a length.
- Error
- The error of every function of the crate that can fail, and of the bindings that are built on it.
- Faults
- The faults of one reading, in the order of the octets. It is a list of
Noted, which it derefs to: the first four, held in place without an allocation, andcountsays how many the reader met. It is empty for octets that are written by the clauses. - Octets
- The octets of an OCTET STRING or of an open type: borrowed from the octets that they were decoded from, or owned.
- S1ap
- S1AP, as the types that its deviations are told with: a
Deviationhas the criticality of the protocol, and its answer theTypeOfError, theCauseProtocol, theCriticalityDiagnostics-IE-Itemand theCriticalityDiagnosticsof the protocol. The traits ofoxirush_aper::protocolname the protocol by it.
Enums§
- Error
Kind - What went wrong: the part of an
Errorthat a caller matches on. - Fault
- An encoding that X.691 gives one form for, in another: the value is whole, and a
sender by the clause writes it otherwise. A reader takes it, as the encoder of a
peer may write it, and notes it: see
Reader::faults.
Constants§
- VERSION
- Version of oxirush-s1ap.
Traits§
- AsnType
- A type of an ASN.1 module. The example of
Decodeimplements it. - Decode
- A type whose values are read in aligned PER.
'ais how long the octets live that a value borrows from. - Encode
- A type whose values are written in aligned PER. The example of
Decodeimplements it. - Message
- A message of a protocol: the type of what the PDU of one procedure holds in one
direction.
'ais how long the octets live that the message borrows from.