ISO 15118-2 · ISO 15118-20 · ISO 15118-3

Vehicle-to-grid, from the ECU to the back end.

iso15118 is the ISO 15118 protocol suite in pure Rust: a schema-informed EXI codec generated from the official schemas, both message sets, the SLAC link-setup and SECC discovery state machines, and sans-I/O session engines for the vehicle and the charging station alike. No sockets, no clock, no async runtime — those are yours.

cargo add iso15118
121 × 2
messages verified
298
EXI grammars checked
10
fuzz targets
0
lines of unsafe
129
feature combinations

Why another implementation

ISO 15118 is what your car and a DC fast charger speak while deciding whether to move 350 kW between them. The implementations that exist are C, C++ or Python, and none of them runs unchanged on both a charge-point back end and a bare-metal microcontroller. This one is built on four decisions that make that possible.

01

Sans-I/O

Nothing opens a socket or reads a clock. Bytes and a timestamp in; bytes, events and deadlines out. The pattern behind quinn-proto and rustls — and the reason a whole DC charging session runs as a unit test in microseconds, with time as a variable.

02

no_std + alloc

The protocol core builds for thumbv7em-none-eabihf. Feature flags are additive and every one gates real code, so a DC-only embedded vehicle controller compiles the DC message set and nothing else — grammar tables included.

03

A wire format someone else agrees with

Round-tripping your own encoder through your own decoder proves they agree with each other and nothing more. Every grammar and every message here is diffed against the EXI reference implementation, as documents and as signature fragments.

04

A decoder you can point at the internet

The EXI parser is the first thing to touch unauthenticated bytes from a cable a stranger plugged in. Every length is bounded by its schema facet before anything is allocated, nesting is capped, and the layers above hold the same line.

Protocol, not policy

The engines own framing, decoding, the handshake, session-id checking, message ordering and the spec timers. They own nothing about charging: whether to authorize a vehicle, which schedule to offer, how much current to deliver. Those arrive as events and are answered with a message you build.

  • One engine shape everywhere. handle_input, poll_event, respond, take_transmit, poll_timeout — the same five calls for the charger, the vehicle, discovery and SLAC.
  • The ordering rules are data. One graph per protocol generation, shared by both roles, so the two sides cannot disagree about what is legal next.
  • Including the rules about failing. A FAILED_* response ends the session and leaves only SessionStopReq; the drivers enter that state on their own.
  • And about stopping. Pause is not terminate, and renegotiating a schedule is neither.

How sessions and ordering work →

let mut secc = Secc::new(SeccConfig {
    protocols: Protocols::ISO,
    session_id: SessionId::new(*b"\x11\x22\x33\x44\x55\x66\x77\x88"),
    ..SeccConfig::default()
});

let mut buf = [0u8; 4096];
loop {
    let n = read(&mut buf);
    secc.handle_input(now(), &buf[..n])?;
    while let Some(event) = secc.poll_event() {
        match event {
            Event::ProtocolAgreed(p) => println!("speaking {p}"),
            Event::Request(req) => secc.respond(now(), answer(&req))?,
            Event::Refused { .. } => break,
            Event::Closed(why) => return Ok(println!("session over: {why}")),
            _ => {}
        }
    }
    write(&secc.take_transmit());
}

What is implemented

Everything from the powerline link coming up to the charge loop turning.

LayerState
EXI — schema-informed, bit-packedPrimitives, value string table, grammars, header, documents and fragments
V2GTP framingAll payload types, hostile-length handling, bounded stream reassembly
SDP discoveryCodec, the vehicle-side retry engine, TLS-downgrade and off-link rejection
SLAC — ISO 15118-3Frame codec, timers, and the matching state machine for both roles
ISO 15118-2 message setAll 34 body messages, ds:Signature included
ISO 15118-20 message setsCommonMessages, AC, DC, WPT, ACDP
Session layerClock, spec timers and loop budgets, ordering graphs for both generations, the ISO 15118-2 charging-profile rule
EVCC / SECC driversHandshake, sequencing, session-id checks, half-duplex enforcement, pause and resume
Plug & Charge signaturesXMLDSig over EXI fragments, build and verify, algorithm restrictions enforced
V2G PKIDER/X.509 reader, RFC 5280 path validation, the Annex F certificate profiles, and the encrypted contract-key envelope

Read the documentation →

And what is not

Named rather than implied. A library that is vague about its edges is one you find the edges of in the field.

Certificate revocation

Chains validate — signatures, validity windows, path length, key usage, the Annex F profiles — but nothing here asks whether a certificate was withdrawn. No OCSP, no CRL. The standard makes OCSP a recommendation because a charging vehicle cannot be assumed to reach a responder, and the answer belongs in the back end your station already talks to.

Transport bindings

No TCP, no TLS, no raw sockets, by design. The examples show the twenty lines of glue for each, so you bring rustls on a server and embedded-tls on an ECU without this crate choosing.

DIN SPEC 70121

Its message set is not here: the schemas are behind a paywall, and hand-transcribing them would produce a codec with no reference to check against. The handshake, the framing and the timers below it are generation-agnostic, so a DIN codec of your own can ride them.

The full list of what is not here →