OCPP-RS
Rust protocol library for the Open Charge Point Protocol (OCPP).
Supports OCPP 1.6 (JSON) and OCPP 2.1 (additive / 2.0.1-compatible schemas).
Documentation · Changelog · Guides
Features
- Full OCPP 1.6 and 2.1 message payloads + OCPP-J parse/serialize
- CallResult typing via
PendingCalls/ action-name correlation - Typed RPC framework error codes
- Optional payload length/bounds checks (
schema_validate) and device-model catalogs #![no_std]+alloc— zerostdin library code (global allocator required on baremetal)
Install
[]
= "0.4"
# Optional:
# features = ["schema_validate", "device_model_catalog", "datetime_serialize_rfc3339"]
| Feature | Purpose |
|---|---|
schema_validate |
Enforce string/array/numeric bounds on CALL (and 2.1 SEND) payloads after parse |
device_model_catalog |
Standard 2.1 component/variable name tables |
datetime_serialize_rfc3339 |
Emit RFC3339 millis instead of %.3fZ |
MSRV: 1.85 (edition 2024).
Upgrading from 0.2.x: guides/migration-0.4.md.
no_std / firmware
[]
= { = "0.4", = false }
# plus your allocator / #[global_allocator]
|
OCPP 1.6 CallResult
CALLRESULT has no action on the wire. Blind parse yields CallResultRaw. Correlate with PendingCalls:
use ;
use TypedMessage;
use PendingCalls;
use TypedCallResult;
let mut pending = new;
let _wire = pending.send_call?;
let typed = pending.deserialize_typed?;
assert!;
- Sticky sessions or Redis
messageId → action name+resolve_with_action_name - Datetime: RFC3339 parse; serialize defaults to
%.3fZ— guides/datetime-features.md - Details:
v16::pending, migration guide
OCPP 2.1
Same correlation model under v21::pending. Framing types 2–6 (CALL, CALLRESULT, CALLERROR, CALLRESULTERROR, SEND).
use ;
use HeartbeatRequest;
use TypedMessage;
use PendingCalls;
use TypedCallResult;
let mut pending = new;
let call = new;
assert_eq!;
let _wire = pending.send_call?;
let typed = pending.deserialize_typed?;
assert!;
2.0.1 subprotocol gating: guides/ocpp-2.0.1.md.
Load-balanced resolve
use resolve_with_action_name;
use ;
let CallResult = deserialize_to_message? else ;
let typed = resolve_with_action_name?;
Example crate
&&
Covers 1.6 and 2.1: PendingCalls correlation, CALL / CALLRESULT / CALLERROR, and 2.0.1 subprotocol gating.
Example (1.6 CALL)
use ;
use Action;
let incoming_text = r#"[2, "19223201", "BootNotification", {"chargePointVendor":"VendorX","chargePointModel":"SingleSocketCharger"}]"#;
let incoming_message = deserialize_to_message?;
if let Call = incoming_message
Out of scope
This is a protocol wire crate, not a full stack:
- No SOAP / HTTP binding for 1.6
- No WebSocket, TLS, or Security Profile handshake
- No Charge Point / CSMS state machines or certification test runners
Contributing
Add or update tests with behavior changes.
Fuzzing
Requires nightly + cargo-fuzz:
| Target | What it feeds the parser |
|---|---|
v16_deserialize / v21_deserialize |
Raw bytes as UTF-8 |
v16_roundtrip / v21_roundtrip |
Raw bytes; if parse OK, serialize → reparse |
v16_structured / v21_structured |
Valid-looking OCPP-J frames (real actions, RPC codes, bounded JSON) |
v16_corrupt / v21_corrupt |
Seed / structured frames + truncations, bit flips, bad UTF-8, wrong arity, … |
Or run a single target manually:
# One target, 5 minutes, 8 workers
Discovered corpus inputs under fuzz/corpus/ are gitignored (seed *.json only).
Regenerate action seeds from exhaust tests: python3 tools/gen_fuzz_corpus.py.
License: MIT.