RBC_Test_Sim_Core
0.1.0 build 51fd23e
Shared transport-layer plumbing for the ADR-029 Train/IL/CTC RBC simulators
Toggle main menu visibility
Loading...
Searching...
No Matches
sai.py
Go to the documentation of this file.
1
"""SAI - Python mirror of the Safe Application Intermediate sub-layer (Subset-098 v4.0.0 SS5.4),
2
EC-counter variant. Byte-identical to ``Platform_Protocol_SafeComm`` (``src/sai/sai.c``), the same way
3
``simcore.saf`` mirrors ``src/saf/saf.c``.
4
5
Sits functionally *above* a connected :class:`simcore.saf.SafSession`: it wraps every user PDU in a SAI
6
header (message type, 16-bit sequence number, EC counter; the Triple-Time-Stamp fields are zeroed - only
7
EC is implemented, same scope limit as the C module) before handing it to ``SafSession.send_pdu``, and
8
unwraps/checks it on ``recv_pdu``. This supplies the delay/re-ordering/repetition defence the Safety
9
Layer's own DT PDU doesn't carry (Subset-037-2 Table 12 has no sequence field at all).
10
11
Implements SS5.4.7 (sequence window) and SS5.4.9 (EC counter freshness: the EC-start exchange, the
12
ratio/Ex bookkeeping, and the Delta-based 0..3 state check). Does NOT implement Triple Time Stamping
13
(SS5.4.8), the long-term drift correction of SS5.4.9.5, or the separate delay-detection heartbeat of
14
SS5.4.9.6 - same scope as ``sai.c``; see Platform_Protocol_SafeComm/docs/safety/SUBSET_MAPPING.md.
15
"""
16
from
simcore
import
saf
as
_saf
17
18
MSG_EC_START = 0x81
19
MSG_EC_DATA = 0x86
20
21
HEADER_LEN = 19
# msgtype(1) + seq(2) + TTS(12, zeroed) + EC(4)
22
START_EXTRA_LEN = 6
# Version(4) + EC period(2)
23
24
25
class
SaiError
(Exception):
26
"""A SAI failure; ``status`` names the matching C ``SAFECOMM_STATUS_*`` value."""
27
28
def
__init__
(self, status, detail=""):
29
super().
__init__
(f
"{status}{': ' + detail if detail else ''}"
)
30
self.
status
= status
31
32
33
def
_ec_state_from_delta
(delta):
34
"""SS5.4.9.4.7: the current EC state depends only on the latest Delta."""
35
if
delta <= 0:
36
return
0
37
if
delta == 1:
38
return
1
39
if
delta == 2:
40
return
2
41
return
3
# alarm
42
43
44
class
SaiSession
:
45
"""One SAI connection, layered over an already-connected :class:`simcore.saf.SafSession`.
46
47
``ec_period_ms`` is this side's own EC (execution cycle) period. ``seq_window`` is N of SS5.4.7 (up
48
to ``seq_window - 1`` missing messages tolerated; default 1, no gaps allowed). ``ec_alarm_state`` is
49
the Delta at/above which freshness fails (default 3, matching the spec's own worked example).
50
"""
51
52
def
__init__
(self, saf_session, ec_period_ms, *, seq_window=1, ec_alarm_state=3):
53
if
ec_period_ms <= 0:
54
raise
ValueError(
"ec_period_ms must be positive"
)
55
self.
saf
= saf_session
56
self.
ec_period_ms
= ec_period_ms
57
self.
seq_window
= seq_window
or
1
58
self.
ec_alarm_state
= ec_alarm_state
or
3
59
60
self.
seq_tx
= 0
61
self.
seq_rx
= 0
62
self.
seq_rx_valid
=
False
63
self.
ec_tx
= 0
64
self.
peer_ec_period_ms
=
None
65
self.
ratio
=
None
66
self.
expected_ec
=
None
67
self.
last_rx_ec
= 0
68
self.
ec_initialized
=
False
69
self.
ec_state
= 0
70
71
def
_header
(self, msg_type, seq, ec):
72
return
bytes([msg_type]) + seq.to_bytes(2,
"big"
) + bytes(12) + ec.to_bytes(4,
"big"
)
73
74
def
start
(self):
75
"""Run the EC initialisation exchange (SS5.4.9.3): send our ExecutionCycleStart, wait for the
76
peer's, and compute the EC ratio. ``self.saf`` must already be in DATA (its own handshake done)."""
77
out = (self.
_header
(MSG_EC_START, self.
seq_tx
, self.
ec_tx
)
78
+ (1).to_bytes(4,
"big"
) + min(self.
ec_period_ms
, 0xFFFF).to_bytes(2,
"big"
))
79
self.
saf
.send_pdu(out)
80
self.
seq_tx
= (self.
seq_tx
+ 1) & 0xFFFF
81
82
try
:
83
frame = self.
saf
.recv_pdu()
84
except
_saf.SafError
as
exc:
85
raise
SaiError
(exc.status, str(exc))
from
exc
86
if
len(frame) != HEADER_LEN + START_EXTRA_LEN
or
frame[0] != MSG_EC_START:
87
raise
SaiError
(
"AUTH_FAILED"
,
"EC start shape"
)
88
89
self.
last_rx_ec
= int.from_bytes(frame[15:19],
"big"
)
90
self.
peer_ec_period_ms
= int.from_bytes(frame[23:25],
"big"
)
91
if
self.
peer_ec_period_ms
== 0:
92
raise
SaiError
(
"INVALID_PARAM"
,
"peer EC period"
)
93
self.
seq_rx
= int.from_bytes(frame[1:3],
"big"
)
94
self.
seq_rx_valid
=
True
95
96
# SS5.4.9.4.3: R = receiver (our) EC period / sender (peer) EC period.
97
# SS5.4.9.4.4: update Ex once immediately, using the peer's initial EC counter.
98
self.
ratio
= self.
ec_period_ms
/ self.
peer_ec_period_ms
99
self.
expected_ec
= self.
last_rx_ec
+ self.
ratio
100
self.
ec_initialized
=
True
101
self.
ec_state
= 0
102
103
def
send
(self, pdu):
104
frame = self.
_header
(MSG_EC_DATA, self.
seq_tx
, self.
ec_tx
) + bytes(pdu)
105
self.
saf
.send_pdu(frame)
106
self.
seq_tx
= (self.
seq_tx
+ 1) & 0xFFFF
107
108
def
recv
(self):
109
"""Receive, unwrap and check one SAI-protected PDU. Raises :class:`SaiError`."""
110
try
:
111
frame = self.
saf
.recv_pdu()
112
except
_saf.SafError
as
exc:
113
raise
SaiError
(exc.status, str(exc))
from
exc
114
if
len(frame) < HEADER_LEN
or
frame[0] != MSG_EC_DATA:
115
raise
SaiError
(
"STATE"
,
"unexpected SAI message type"
)
116
117
# SS5.4.7.2: sequence number check with up to (seq_window - 1) tolerated gaps.
118
rx_seq = int.from_bytes(frame[1:3],
"big"
)
119
if
self.
seq_rx_valid
:
120
diff = (rx_seq - self.
seq_rx
) & 0xFFFF
121
if
diff >= self.
seq_window
:
122
raise
SaiError
(
"SEQ_ERROR"
, f
"got {rx_seq}, expected {self.seq_rx}..+{self.seq_window - 1}"
)
123
self.
seq_rx
= (rx_seq + 1) & 0xFFFF
124
self.
seq_rx_valid
=
True
125
126
# SS5.4.9.4: freshness.
127
self.
last_rx_ec
= int.from_bytes(frame[15:19],
"big"
)
128
if
self.
ec_initialized
:
129
delta = int(self.
expected_ec
- self.
last_rx_ec
)
130
self.
ec_state
=
_ec_state_from_delta
(delta)
131
if
self.
ec_state
>= self.
ec_alarm_state
:
132
raise
SaiError
(
"TIMEOUT"
,
"EC freshness alarm"
)
133
134
return
frame[HEADER_LEN:]
135
136
def
tick
(self):
137
"""Advance our own EC by one: call this once per ``ec_period_ms`` elapsed on the caller's clock.
138
Updates the expected-EC estimate and the freshness state (SS5.4.9.4.4-7), reporting the state
139
computed BEFORE advancing Ex for the next cycle (so the first tick after a fresh receive is 0)."""
140
if
not
self.
ec_initialized
:
141
return
142
delta = int(self.
expected_ec
- self.
last_rx_ec
)
143
self.
ec_state
=
_ec_state_from_delta
(delta)
144
self.
expected_ec
+= self.
ratio
145
self.
ec_tx
+= 1
simcore.sai.SaiError
Definition
sai.py:25
simcore.sai.SaiError.status
status
Definition
sai.py:30
simcore.sai.SaiError.__init__
__init__(self, status, detail="")
Definition
sai.py:28
simcore.sai.SaiSession
Definition
sai.py:44
simcore.sai.SaiSession.expected_ec
int expected_ec
Definition
sai.py:66
simcore.sai.SaiSession.ec_initialized
bool ec_initialized
Definition
sai.py:68
simcore.sai.SaiSession.ec_tx
int ec_tx
Definition
sai.py:63
simcore.sai.SaiSession.seq_tx
int seq_tx
Definition
sai.py:60
simcore.sai.SaiSession.tick
tick(self)
Definition
sai.py:136
simcore.sai.SaiSession.ec_alarm_state
int ec_alarm_state
Definition
sai.py:58
simcore.sai.SaiSession.start
start(self)
Definition
sai.py:74
simcore.sai.SaiSession.ec_period_ms
ec_period_ms
Definition
sai.py:56
simcore.sai.SaiSession.send
send(self, pdu)
Definition
sai.py:103
simcore.sai.SaiSession.seq_rx_valid
bool seq_rx_valid
Definition
sai.py:62
simcore.sai.SaiSession.ratio
int ratio
Definition
sai.py:65
simcore.sai.SaiSession.recv
recv(self)
Definition
sai.py:108
simcore.sai.SaiSession.saf
saf
Definition
sai.py:55
simcore.sai.SaiSession.peer_ec_period_ms
int peer_ec_period_ms
Definition
sai.py:64
simcore.sai.SaiSession.ec_state
int ec_state
Definition
sai.py:69
simcore.sai.SaiSession.last_rx_ec
int last_rx_ec
Definition
sai.py:67
simcore.sai.SaiSession.seq_rx
int seq_rx
Definition
sai.py:61
simcore.sai.SaiSession._header
_header(self, msg_type, seq, ec)
Definition
sai.py:71
simcore.sai.SaiSession.__init__
__init__(self, saf_session, ec_period_ms, *, seq_window=1, ec_alarm_state=3)
Definition
sai.py:52
simcore.sai.SaiSession.seq_window
int seq_window
Definition
sai.py:57
simcore.sai._ec_state_from_delta
_ec_state_from_delta(delta)
Definition
sai.py:33
src
simcore
sai.py
Generated by
1.18.0