Skip to main content

Crate oxirush_s1ap

Crate oxirush_s1ap 

Source
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.

TaskItems
Answer an eNB, from S1 SETUP to the INITIAL CONTEXT SETUPbuild_s1ap!, build_s1ap_ie!, extract_s1ap_ies!, helpers, S1AP_PDU::ie
Decode a PDUS1AP_PDU::decode, S1AP_PDU::decode_borrowed, S1AP_PDU::kind, S1AP_PDU::decode_value
Build a PDUbuild_s1ap!, S1AP_PDU::of_message
Read the IEs of a messageextract_s1ap_ies!, ie, with_s1ap_ie_mut!, build_s1ap_ie!
A list of single containers, and a handover container, typedS1AP_PDU::items, s1ap::decode_open_type, s1ap::encode_open_type
Check a message, and answer one that is faultyS1AP_PDU::check, S1AP_PDU::check_structure, Deviation, S1AP_PDU::criticality_diagnostics_for_response
A PDU as JSONS1AP_PDU::to_tree, S1AP_PDU::to_view, S1AP_PDU::with_view, inspect
Write a message from its treeS1AP_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.

ToWrite
Read a message where it was receivedlet 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 gonevalue.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 valueFrom data that it owns: NASPDU::from(vec) is a NASPDU<'static>
Read a PDU in the buffer that it was received inS1AP_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.

  1. A hyphen ends a word, and so does a lower-case letter or a digit before a capital: procedureCode is procedure_code.
  2. The first letter of an identifier belongs to the capitals after it: the identifier starts in lower case whatever its first word is.
  3. 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: nRCellIdentity is nr_cell_identity, and pLMNIdentity is plmn_identity as pLMNidentity is. An s after them is their plural: protocolIEs is protocol_ies.
  4. Capitals that are several abbreviations of the list are these: eNBX2TransportLayerAddresses is enb_x2_transport_layer_addresses.
  5. 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-ID is gnb_id.
  6. fiveG and fiveGS are five_g and five_gs, and 5GS is 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.1Rust
INTEGER with a constraintThe native number that holds its range: MMEUES1APID(pub u32) for (0..4294967295)
INTEGER whose constraint has an extension markeru64: ERABID(pub u64) for (0..15, ...)
OCTET STRING (SIZE (n))[u8; n]: TAC(pub [u8; 2])
Another OCTET STRING, and an open typeOctets: NASPDU<'a>(pub Octets<'a>)
BIT STRING of 64 bits at most, without an extension markerBits, a number with a length, which is Copy. A type of one size is made of the number: CellIdentity::new(0x1234507)?
Another BIT STRINGBitString, which borrows its octets when it is longer than 64 bits
PrintableString, VisibleString, UTF8StringCow<'a, str>: MMEname<'a>
ENUMERATED, CHOICE, SEQUENCE, SEQUENCE OFAn 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:

BitsBitString
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

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-utils that this crate is built with: a PLMNidentity converts to and from its Plmn, 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>() and ie::<T>() of a PDU and of a message take them, as pdu.ie::<ie::Cause>(). Each implements aper::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.
inspectinspect
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_PDU and its three directions, each message, each IE and each type of a value, which decode and encode themselves in aligned PER.
treeinspect
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, and tree!, which writes one in the code as serde_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_PDU from 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, opt and lax fields.
treeinspect
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, and tree!, which writes one in the code as serde_json::json! does. A crate that reads or writes them takes them from here. A tree::Value written as JSON, as serde_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 a String.
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, and count says 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 Deviation has the criticality of the protocol, and its answer the TypeOfError, the CauseProtocol, the CriticalityDiagnostics-IE-Item and the CriticalityDiagnostics of the protocol. The traits of oxirush_aper::protocol name the protocol by it.

Enums§

ErrorKind
What went wrong: the part of an Error that 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 Decode implements it.
Decode
A type whose values are read in aligned PER. 'a is how long the octets live that a value borrows from.
Encode
A type whose values are written in aligned PER. The example of Decode implements it.
Message
A message of a protocol: the type of what the PDU of one procedure holds in one direction. 'a is how long the octets live that the message borrows from.

Type Aliases§

Deviation