ocpp/protocol/csms
A pure, sans-IO CSMS-side machine for one station connection.
Wraps the generic RPC endpoint (ocpp/protocol/endpoint) and adds the
CSMS half of the provisioning rules. It is deliberately much thinner
than the station machine: the CSMS answers boot notifications rather
than sending them, so there is no registration lifecycle of its own to
drive — it only tracks what the application’s replies have told the
peer, and (optionally) enforces the peer’s obligations. It initiates no
calls of its own either, so application refs pass through to the
endpoint untranslated, and it adds no deadlines of its own —
next_deadline is the wrapped endpoint’s.
Peer registration
The peer starts PeerUnregistered. Inbound CALLs
recognised by the config’s is_boot_request are remembered by id; when
the application answers one, read_boot_reply reads the decision out of
the response: Accepted makes the peer
PeerRegistered, Pending/Rejected makes it
PeerUnregistered — the tracked state always follows the latest
answered boot request, so a re-boot answered Rejected unregisters a
registered peer. A reply that is not a readable boot reply (None) and
a CALLERROR answer both leave the tracked state unchanged.
Enforcement
A station SHALL NOT send messages other than boot notifications before
it has been accepted, and the 2.x provisioning rules (B02.FR.09) let the
CSMS answer offending CALLs with a CALLERROR SecurityError. With
RejectUnregistered this machine enforces
exactly that: a non-boot inbound CALL while the peer is unregistered is
answered on the wire with a CALLERROR SecurityError and surfaced as an
UnregisteredCallRejected output instead of being delivered
to the application. Boot requests always deliver, under either policy.
Inbound SENDs (2.1) always deliver too — they are unconfirmed, so no
error reply is possible, and whether to act on one from an unregistered
peer is application policy. With
DeliverAll everything is delivered and
registration is merely tracked.
Time and termination
Time follows the endpoint’s convention: it enters exclusively as the
now arguments to init and update (absolute
monotonic milliseconds); the machine schedules nothing at rest, so
init’s clock is accepted purely for uniformity across the layer.
SocketClosed is the endpoint’s terminal transition: the in-flight and
queued outbound calls fail with Disconnected; afterwards a new
CallRequested fails immediately and every other input is a silent
no-op.
Types
Version-specific bindings for the CSMS machine, on top of the wrapped
endpoint’s codec Config.
pub type CsmsConfig(req, res) {
CsmsConfig(
endpoint: endpoint.Config(req, res),
is_boot_request: fn(req) -> Bool,
read_boot_reply: fn(res) -> option.Option(protocol.BootStatus),
enforcement: RegistrationEnforcement,
)
}
Constructors
-
CsmsConfig( endpoint: endpoint.Config(req, res), is_boot_request: fn(req) -> Bool, read_boot_reply: fn(res) -> option.Option(protocol.BootStatus), enforcement: RegistrationEnforcement, )Arguments
- endpoint
-
Codecs and call timeout for the wrapped RPC endpoint.
- is_boot_request
-
Is this inbound request this version’s boot notification (1.6/2.x BootNotification)?
- read_boot_reply
-
Read the registration decision out of the application’s reply to a boot request.
Nonemeans the response is not a readable boot reply; the tracked registration state is then left unchanged. - enforcement
-
What to do with a non-boot inbound CALL from an unregistered station: reject it on the wire or deliver it anyway (see
RegistrationEnforcement).
Inputs to the machine — the same vocabulary as the endpoint’s Msg.
ref values belong to the application and pass through unchanged.
pub type Msg(req, res) {
WireIn(text: String)
CallRequested(ref: Int, request: req)
SendRequested(request: req)
ReplyProvided(
id: String,
reply: Result(res, #(rpc.ErrorCode, String)),
)
Tick
SocketClosed
}
Constructors
-
WireIn(text: String)A text frame arrived from the peer station.
-
CallRequested(ref: Int, request: req)The application wants to make a CALL to the station. Subject to the endpoint’s one-in-flight rule (queued FIFO behind our own calls).
-
SendRequested(request: req)The application wants to emit a SEND (2.1): unconfirmed, exempt from the one-in-flight rule, never queued.
-
ReplyProvided( id: String, reply: Result(res, #(rpc.ErrorCode, String)), )The application answers the inbound CALL with this id:
Okbecomes a CALLRESULT,Errora CALLERROR. Replies to boot requests also update the tracked peer registration (see the module doc). -
TickTime passed; sent by the adapter when the
next_deadlinetimer fires. -
SocketClosedThe transport closed. Fails every pending and queued call with
Disconnectedand makes the machine terminal.
Outputs emitted by the machine: the endpoint’s outputs passed through
unchanged, plus the RejectUnregistered rejection.
pub type Output(req, res) {
WireOut(text: String)
CallSucceeded(ref: Int, response: res)
CallFailed(ref: Int, failure: endpoint.CallFailure)
CallReceived(id: String, request: req)
SendReceived(request: req)
ReplyRejected(id: String, code: String, description: String)
ViolationDetected(violation: endpoint.Violation)
UnregisteredCallRejected(id: String, request: req)
}
Constructors
-
WireOut(text: String)Write this text frame to the socket.
-
CallSucceeded(ref: Int, response: res)The application’s CALL identified by
refsucceeded. -
CallFailed(ref: Int, failure: endpoint.CallFailure)The application’s CALL identified by
reffailed. -
CallReceived(id: String, request: req)The station initiated a CALL. Answer it with
ReplyProvided(id, ...). -
SendReceived(request: req)The station emitted a SEND. No reply is possible.
-
ReplyRejected(id: String, code: String, description: String)The station rejected a CALLRESULT we previously sent (2.1).
-
ViolationDetected(violation: endpoint.Violation)A protocol irregularity, reported by the wrapped endpoint.
-
UnregisteredCallRejected(id: String, request: req)RejectUnregistered: the unregistered peer sent a non-boot CALL, violating the provisioning rules. A CALLERRORSecurityErrorwas already emitted alongside this output, and the request was NOT delivered as aCallReceived— do not answer it.
What this connection knows about the peer station’s registration: the status the application granted in its reply to the latest answered boot request.
pub type PeerRegistration {
PeerUnregistered
PeerRegistered
}
Constructors
-
PeerUnregisteredNo boot request on this connection has been answered yet, or the latest answered one was
Pending/Rejected. -
PeerRegisteredThe application answered the latest boot request
Accepted.
What to do with a non-boot inbound CALL from a station whose boot notification has not been accepted (see the module doc’s Enforcement section). Boot requests and inbound SENDs always deliver, under either policy.
pub type RegistrationEnforcement {
RejectUnregistered
DeliverAll
}
Constructors
-
RejectUnregisteredAnswer the offending CALL with a CALLERROR
SecurityError(B02.FR.09) and surface it asUnregisteredCallRejectedinstead of delivering it. -
DeliverAllDeliver everything; registration is only tracked.
Values
pub fn init(
config: CsmsConfig(req, res),
now now: Int,
) -> #(Csms(req, res), effect.Effect(Output(req, res)))
Create the CSMS side of a fresh station connection at time now. Emits
no effects — the CSMS speaks only when spoken to (or when the application
makes a call) — and schedules nothing, so now is currently unused. It
is accepted anyway so that every machine in this layer shares the
init(config, now) shape (the station genuinely needs the clock at init)
and adapters can treat them uniformly.
pub fn next_deadline(csms: Csms(req, res)) -> option.Option(Int)
The earliest instant at which this machine needs a Tick to make
progress: the wrapped endpoint’s call timeout, if any — the CSMS layer
adds no deadlines of its own.
pub fn peer_registration(
csms: Csms(req, res),
) -> PeerRegistration
What this connection currently knows about the peer station’s registration.
pub fn update(
csms: Csms(req, res),
msg: Msg(req, res),
now: Int,
) -> #(Csms(req, res), effect.Effect(Output(req, res)))
Advance the machine with one input at time now (absolute milliseconds,
monotonic). The wrapped endpoint applies its call-timeout rule; the CSMS
layer has no deadlines of its own.