SimCore
Shared transport-layer plumbing for the ADR-029 Train/IL/CTC RBC simulators
Loading...
Searching...
No Matches
rbc_wire.py
Go to the documentation of this file.
1"""RBC envelope codec (ADR-029) - Python mirror of the WIRE LAYOUT in
2safeAPIRBC2oo2/src/application/rbc_wire.c/rbc_wire_types.h (struct shape
3and byte order only). Keep the struct format string and field order in
4exact sync with that C file; there is no shared schema between the two
5languages, so this is a by-hand port, not a generated binding.
6
7Deliberately generic - this module knows the envelope's SHAPE (which
8byte offsets exist and what type each is), not which `kind`/`nid_message`
9values MEAN, or which of them get which content fields. Naming/owning
10specific kind numbers (MSG_P0, MSG_M136, MSG_ROUTE_ADD, MSG_CTC_CONNECTED,
11...) and deciding nid_message/l_message/which optional fields apply to
12which message is each sim's own application-layer concern (its own
13message_catalog.json, resolved by rbc_messages.py's MessageCatalog before
14it ever calls encode() here) - SimCore is transport plumbing, not a
15registry of what every adaptation built on it sends or receives. This
16mirrors the C side's own split: rbc_wire_types.h/rbc_wire.c DOES branch on
17kind (RBC_MSG_M146/RBC_MSG_M136 specifically) because it is
18safeAPIRBC2oo2's own single application-layer file, not a shared-across-
19adaptations transport layer the way this module is - the two are not
20equivalent, on purpose.
21
22Wire layout (ENVELOPE_SIZE = 96 bytes, little-endian) - REQ-RBC-001
23(revised, safeAPIFreamwork/docs/requirements/SRS.md), rbc_wire_types.h's
24own doc for the full rationale:
25 kind(1) + train_id(1) + nid_message(1) + reserved(1) +
26 l_message(2) + reserved(2) + t_train(4) +
27 cycle(4) + d_lrbg(4) + msg_type(4) + route_len(4) + ma_seq(4) +
28 ma_length(4) + t_train_ack(4) +
29 nid_lrbg(4) + q_dirlrbg(4) + q_dlrbg(4) + l_doubtover(4) +
30 l_doubtunder(4) + q_length(4) + v_train(4) + q_dirtrain(4) +
31 m_mode(4) + m_level(4) +
32 start_signal(4) + end_signal(4) + route_type(4) + route_status(4)
33The last 4 fields (offsets 80-95) were appended in the IL<->RBC
34route-identity pass (ENVELOPE_SIZE grew 80 -> 96) - same additive-growth
35precedent the M136 block and t_train_ack already set: never a repurposed
36existing field, always appended after everything that came before.
37Every field is ALWAYS packed/unpacked at these same fixed, non-
38overlapping offsets regardless of kind (t_train_ack, the ten M136-only
39fields, and the four route-identity fields included) - see rbc_wire.c's
40own header for why NOT to branch on kind to decide WHERE something goes
41(would alias different kinds' own extension fields onto the same wire
42bytes for different meanings). l_message says how much of this fixed
4396-byte slot is actually MEANINGFUL for a given envelope (36 for most
44kinds, 40 for RBC_MSG_M146, 80 for RBC_MSG_M136, 96 - the whole slot -
45for RBC_MSG_ROUTE_ADD/_ROUTE_RELEASE/_TRAIN_POSITION_IN_ROUTE) - the
46caller (rbc_messages.py) decides that per message from its own catalog
47data, this module has no opinion.
48"""
49
50import struct
51
52ENVELOPE_SIZE = 96
53_STRUCT = struct.Struct("<4B2H22I")
54
55
56def encode(kind, train_id, nid_message=0, l_message=ENVELOPE_SIZE, t_train=0,
57 cycle=0, d_lrbg=0, msg_type=0, route_len=0, ma_seq=0, ma_length=0, t_train_ack=0,
58 nid_lrbg=0, q_dirlrbg=0, q_dlrbg=0, l_doubtover=0, l_doubtunder=0,
59 q_length=0, v_train=0, q_dirtrain=0, m_mode=0, m_level=0,
60 start_signal=0, end_signal=0, route_type=0, route_status=0):
61 """Packs the full fixed 96-byte envelope shape - every parameter is
62 exactly one wire field, positional meaning only (see this module's
63 own doc), no kind-based branching here. @l_message is NOT computed
64 from @kind by this function - the caller (rbc_messages.py, which has
65 the catalog data needed to know) must pass the right value."""
66 return _STRUCT.pack(
67 kind, train_id, nid_message, 0,
68 l_message, 0,
69 t_train, cycle, d_lrbg, msg_type, route_len, ma_seq, ma_length, t_train_ack,
70 nid_lrbg, q_dirlrbg, q_dlrbg, l_doubtover, l_doubtunder, q_length, v_train, q_dirtrain, m_mode, m_level,
71 start_signal, end_signal, route_type, route_status,
72 )
73
74
75def encode_opaque(kind, payload, frame_size=ENVELOPE_SIZE):
76 """Builds a fixed @frame_size frame carrying an OPAQUE payload rather
77 than the typed struct encode() packs: byte 0 = @kind, byte 1 = payload
78 length, bytes 2.. = @payload (ASCII str or bytes), the rest zero.
79
80 For the C side's domain-blind control kinds that ride the envelope
81 slot without being real Subset-026 messages (rbc_wire_types.h:
82 RBC_MSG_RELAY_KEEPALIVE / RBC_MSG_SET_LOG_LEVEL / RBC_MSG_SIM_STATUS)
83 - the caller owns the kind number and the payload meaning, this
84 module stays kind-blind (same split as encode()'s own doc). A payload
85 longer than @frame_size-2 is truncated.
86
87 @param frame_size: defaults to ENVELOPE_SIZE (every sim's own channel
88 historically this size) - a caller whose OWN channel uses a different
89 fixed slot size (e.g. TrainRBCSim's Train relay channel, widened to
90 RBC_TRAIN_ENVELOPE_WIRE_SIZE for RBC_MSG_ERTMS_ENVELOPE) must pass
91 that size here too, or the receiving side's own exact-datagram-size
92 check rejects this frame - see that project's own train_sim.py for
93 the concrete call site."""
94 if isinstance(payload, str):
95 payload = payload.encode("ascii", "replace")
96 payload = payload[: frame_size - 2]
97 frame = bytearray(frame_size)
98 frame[0] = kind & 0xFF
99 frame[1] = len(payload)
100 frame[2 : 2 + len(payload)] = payload
101 return bytes(frame)
102
103
104def decode(data):
105 """Returns a dict of every wire field, or None if @data doesn't hold a
106 structurally-valid ENVELOPE_SIZE-byte flat frame - mirrors
107 rbc_wire_decode()'s own REQ-RBC-002 structural-validity contract.
108 Does NOT judge whether `kind`/`l_message` are a real/known/consistent
109 combination - that is application-layer knowledge this transport-layer
110 codec deliberately does not have; a caller wanting that asks its OWN
111 sim's MessageCatalog (rbc_messages.py's decode_to_message(), which
112 already returns None for an out-of-catalog kind rather than raising).
113
114 @data may be LONGER than ENVELOPE_SIZE - a caller whose own channel is
115 wider than the flat scheme's own 96-byte slot (e.g. TrainRBCSim's Train
116 relay channel, widened to RBC_TRAIN_ENVELOPE_WIRE_SIZE for
117 RBC_MSG_ERTMS_ENVELOPE, but still carrying this project's own
118 pre-existing flat P0/M136/M146 traffic on the very same train_id/wire)
119 zero-pads every send up to its own fixed frame width, flat sends
120 included - see that project's own train_sim.py/_pad(). Only the first
121 ENVELOPE_SIZE bytes are ever meaningful for this shape; anything past
122 that MUST be all-zero padding, never non-zero content silently
123 ignored - a non-zero tail is exactly the kind of "wrong shape on this
124 wire" a caller wants to know about, so it is treated the same as a
125 too-short buffer: not decodable."""
126 if len(data) < ENVELOPE_SIZE:
127 return None
128 if any(data[ENVELOPE_SIZE:]):
129 return None
130 (kind, train_id, nid_message, _reserved0,
131 l_message, _reserved1,
132 t_train, cycle, d_lrbg, msg_type, route_len, ma_seq, ma_length, t_train_ack,
133 nid_lrbg, q_dirlrbg, q_dlrbg, l_doubtover, l_doubtunder,
134 q_length, v_train, q_dirtrain, m_mode, m_level,
135 start_signal, end_signal, route_type, route_status) = _STRUCT.unpack_from(data)
136 return {
137 "kind": kind,
138 "train_id": train_id,
139 "nid_message": nid_message,
140 "l_message": l_message,
141 "t_train": t_train,
142 "cycle": cycle,
143 "d_lrbg": d_lrbg,
144 "msg_type": msg_type,
145 "route_len": route_len,
146 "ma_seq": ma_seq,
147 "ma_length": ma_length,
148 "t_train_ack": t_train_ack,
149 "nid_lrbg": nid_lrbg,
150 "q_dirlrbg": q_dirlrbg,
151 "q_dlrbg": q_dlrbg,
152 "l_doubtover": l_doubtover,
153 "l_doubtunder": l_doubtunder,
154 "q_length": q_length,
155 "v_train": v_train,
156 "q_dirtrain": q_dirtrain,
157 "m_mode": m_mode,
158 "m_level": m_level,
159 "start_signal": start_signal,
160 "end_signal": end_signal,
161 "route_type": route_type,
162 "route_status": route_status,
163 }
encode(kind, train_id, nid_message=0, l_message=ENVELOPE_SIZE, t_train=0, cycle=0, d_lrbg=0, msg_type=0, route_len=0, ma_seq=0, ma_length=0, t_train_ack=0, nid_lrbg=0, q_dirlrbg=0, q_dlrbg=0, l_doubtover=0, l_doubtunder=0, q_length=0, v_train=0, q_dirtrain=0, m_mode=0, m_level=0, start_signal=0, end_signal=0, route_type=0, route_status=0)
Definition rbc_wire.py:60
encode_opaque(kind, payload, frame_size=ENVELOPE_SIZE)
Definition rbc_wire.py:75