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:
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:
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:
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
| Encoding | wirespec name | Use case |
|---|---|---|
| UPER | uper | 3GPP, V2X, LTE |
| BER | ber | LDAP, SNMP |
| DER | der | X.509, CMS |
| APER | aper | S1AP, NGAP |
| OER | oer | V2X IEEE 1609.2 |
| COER | coer | Canonical OER |
Length Specification
Two forms:
// 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:
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:
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:
-- messages.asn1
Messages DEFINITIONS AUTOMATIC TAGS ::= BEGIN
SimpleMessage ::= SEQUENCE {
id INTEGER (0..255),
active BOOLEAN
}
ENDmodule example
@endian big
packet MessageWrapper {
version: u8,
payload_length: u16,
payload: bytes[length: payload_length],
}2. Compile both:
# 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:
#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:
// 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 frameBuild
Include both asn1c and wirespec generated files in your build:
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 \
-lmThe -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