Skip to content

ASN.1 Integration

wirespec can embed ASN.1-encoded fields in binary protocol descriptions. This is useful for protocols that combine a binary wire header with ASN.1 payloads, such as SUPL, V2X ITS, and 3GPP LPP.

Basic Usage

Declare external ASN.1 types with extern asn1, then use them as field types:

wire
extern asn1 "etsi_its_cdd.asn1" use crate::etsi_its_cdd { CAM }

packet ItsCamPacket {
    version: u8,
    length: u16,
    cam: asn1(CAM, encoding: uper, length: length),
}

The asn1() field type tells wirespec that this field is ASN.1-encoded. On the wire, it is bytes[length: length] — the ASN.1 encoding is an upper-layer concern.

Generated Code

Rust Backend

The Rust backend generates rasn::uper::decode/encode calls:

rust
pub struct ItsCamPacket {
    pub version: u8,
    pub length: u16,
    pub cam: CAM,  // decoded ASN.1 type, not &[u8]
}

The cam field is the fully decoded ASN.1 type. Parse decodes automatically, serialize re-encodes and recomputes the length field.

C Backend

The C backend treats ASN.1 fields as raw bytes:

c
typedef struct {
    uint8_t version;
    uint16_t length;
    const uint8_t *cam;  // raw bytes
} its_cam_packet_t;

C users decode the bytes manually with asn1c or another C ASN.1 library.

Supported Encodings

Encodingwirespec nameUse case
UPERuper3GPP, V2X, LTE
BERberLDAP, SNMP
DERderX.509, CMS
APERaperS1AP, NGAP
OERoerV2X IEEE 1609.2
COERcoerCanonical OER

Length Specification

Two forms:

wire
// Length-prefixed: reads `length` bytes
payload: asn1(Type, encoding: uper, length: length_field)

// Remaining: consumes all remaining bytes
payload: asn1(Type, encoding: uper, remaining)

Auto-compilation (asn1 feature)

With the asn1 Cargo feature, wirespec automatically compiles .asn1 files via rasn-compiler:

bash
cargo run --features asn1 -- compile its.wspec -t rust -o build/

This outputs both the rasn-generated types and the wirespec codec. The use clause in extern asn1 is auto-resolved.

Without the feature, users must provide the use clause manually:

wire
extern asn1 "schema.asn1" use crate::my_types { MyType }

Using asn1c with the C Backend

The C backend outputs ASN.1 fields as wirespec_bytes_t — a pointer and length into the parsed buffer. To decode the actual ASN.1 content, pair wirespec with asn1c, a widely used open-source ASN.1 compiler for C.

Workflow

1. Define your ASN.1 schema and wirespec packet:

asn1
-- messages.asn1
Messages DEFINITIONS AUTOMATIC TAGS ::= BEGIN
  SimpleMessage ::= SEQUENCE {
    id INTEGER (0..255),
    active BOOLEAN
  }
END
wire
module example

@endian big

packet MessageWrapper {
    version: u8,
    payload_length: u16,
    payload: bytes[length: payload_length],
}

2. Compile both:

bash
# Compile ASN.1 with UPER support
asn1c -fcompound-names -gen-PER -pdu=SimpleMessage messages.asn1

# Compile wirespec packet to C
wirespec compile wrapper.wspec -t c -o build/

3. Parse wirespec, then decode ASN.1:

c
#include "SimpleMessage.h"       // asn1c generated
#include "example.h"             // wirespec generated
#include "wirespec_runtime.h"

// Parse the binary frame with wirespec
example_message_wrapper_t wrapper;
size_t consumed;
wirespec_result_t rc = example_message_wrapper_parse(
    buf, buf_len, &wrapper, &consumed);
if (rc != WIRESPEC_OK) { /* handle error */ }

// Decode the ASN.1 payload with asn1c
SimpleMessage_t *msg = NULL;
asn_dec_rval_t dec = uper_decode(
    0, &asn_DEF_SimpleMessage,
    (void **)&msg,
    wrapper.payload.ptr,
    wrapper.payload.len,
    0, 0);
if (dec.code != RC_OK) { /* handle error */ }

printf("id=%ld active=%d\n", msg->id, msg->active);
ASN_STRUCT_FREE(asn_DEF_SimpleMessage, msg);

4. Encode ASN.1, then serialize wirespec:

c
// Encode ASN.1 with asn1c
SimpleMessage_t msg = { .id = 42, .active = 1 };
uint8_t asn1_buf[128];
asn_enc_rval_t enc = uper_encode_to_buffer(
    &asn_DEF_SimpleMessage, 0, &msg,
    asn1_buf, sizeof(asn1_buf));
size_t asn1_len = (enc.encoded + 7) / 8;  // bits to bytes

// Wrap in wirespec packet
example_message_wrapper_t wrapper = {
    .version = 1,
    .payload_length = (uint16_t)asn1_len,
    .payload = { .ptr = asn1_buf, .len = asn1_len },
};

uint8_t wire_buf[256];
size_t written;
wirespec_result_t rc = example_message_wrapper_serialize(
    &wrapper, wire_buf, sizeof(wire_buf), &written);
// wire_buf now contains the complete binary frame

Build

Include both asn1c and wirespec generated files in your build:

bash
gcc -Wall -Wextra -std=c11 \
    -I asn1c_output/ \
    -I wirespec_output/ \
    -I path/to/wirespec/runtime/ \
    -o my_app \
    my_app.c \
    wirespec_output/example.c \
    asn1c_output/*.c \
    -lm

The -lm flag is needed because asn1c uses math functions internally.

Key Points

  • wirespec handles the binary framing (header fields, length prefixes, endianness)
  • asn1c handles the ASN.1 encoding/decoding (UPER, BER, DER, etc.)
  • The two libraries communicate through raw byte buffers — no tight coupling
  • This pattern works for any ASN.1 library, not just asn1c