pycyphal2 2.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycyphal2/__init__.py +89 -0
- pycyphal2/_api.py +604 -0
- pycyphal2/_hash.py +204 -0
- pycyphal2/_header.py +349 -0
- pycyphal2/_node.py +1472 -0
- pycyphal2/_publisher.py +427 -0
- pycyphal2/_subscriber.py +430 -0
- pycyphal2/_transport.py +92 -0
- pycyphal2/can/__init__.py +43 -0
- pycyphal2/can/_interface.py +131 -0
- pycyphal2/can/_reassembly.py +158 -0
- pycyphal2/can/_transport.py +525 -0
- pycyphal2/can/_wire.py +376 -0
- pycyphal2/can/pythoncan.py +261 -0
- pycyphal2/can/socketcan.py +225 -0
- pycyphal2/py.typed +0 -0
- pycyphal2/udp.py +1000 -0
- pycyphal2-2.0.0.dev0.dist-info/METADATA +58 -0
- pycyphal2-2.0.0.dev0.dist-info/RECORD +22 -0
- pycyphal2-2.0.0.dev0.dist-info/WHEEL +5 -0
- pycyphal2-2.0.0.dev0.dist-info/licenses/LICENSE +20 -0
- pycyphal2-2.0.0.dev0.dist-info/top_level.txt +1 -0
pycyphal2/_hash.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Hash and CRC utilities"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# =====================================================================================================================
|
|
6
|
+
# CRC-32C (Castagnoli)
|
|
7
|
+
# =====================================================================================================================
|
|
8
|
+
|
|
9
|
+
CRC32C_INITIAL = 0xFFFFFFFF
|
|
10
|
+
CRC32C_OUTPUT_XOR = 0xFFFFFFFF
|
|
11
|
+
CRC32C_RESIDUE = 0x48674BC7
|
|
12
|
+
# fmt: off
|
|
13
|
+
_CRC32C_TABLE = [
|
|
14
|
+
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
|
15
|
+
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
|
16
|
+
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
|
17
|
+
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
|
18
|
+
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
|
19
|
+
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
|
20
|
+
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
|
21
|
+
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
|
22
|
+
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
|
23
|
+
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
|
24
|
+
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
|
25
|
+
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
|
26
|
+
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
|
27
|
+
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
|
28
|
+
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
|
29
|
+
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
|
30
|
+
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
|
31
|
+
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
|
32
|
+
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
|
33
|
+
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
|
34
|
+
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
|
35
|
+
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
|
36
|
+
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
|
37
|
+
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
|
38
|
+
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
|
39
|
+
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
|
40
|
+
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
|
41
|
+
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
|
42
|
+
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
|
43
|
+
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
|
44
|
+
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
|
45
|
+
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
|
46
|
+
]
|
|
47
|
+
# fmt: on
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def crc32c_add(crc: int, data: bytes | memoryview) -> int:
|
|
51
|
+
"""CRC-32C (Castagnoli) one update step without the output XOR."""
|
|
52
|
+
for b in data:
|
|
53
|
+
crc = (crc >> 8) ^ _CRC32C_TABLE[b ^ (crc & 0xFF)]
|
|
54
|
+
return crc
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def crc32c_full(data: bytes | memoryview) -> int:
|
|
58
|
+
"""CRC-32C (Castagnoli) with the output XOR."""
|
|
59
|
+
return crc32c_add(CRC32C_INITIAL, data) ^ CRC32C_OUTPUT_XOR
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# =====================================================================================================================
|
|
63
|
+
# CRC-16/CCITT-FALSE
|
|
64
|
+
# =====================================================================================================================
|
|
65
|
+
|
|
66
|
+
CRC16CCITT_FALSE_INITIAL = 0xFFFF
|
|
67
|
+
CRC16CCITT_FALSE_RESIDUE = 0x0000
|
|
68
|
+
# fmt: off
|
|
69
|
+
_CRC16CCITT_FALSE_TABLE = [
|
|
70
|
+
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C,
|
|
71
|
+
0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318,
|
|
72
|
+
0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4,
|
|
73
|
+
0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630,
|
|
74
|
+
0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4,
|
|
75
|
+
0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969,
|
|
76
|
+
0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
|
|
77
|
+
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
|
78
|
+
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
|
|
79
|
+
0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9,
|
|
80
|
+
0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046,
|
|
81
|
+
0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2,
|
|
82
|
+
0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2,
|
|
83
|
+
0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,
|
|
84
|
+
0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E,
|
|
85
|
+
0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
|
86
|
+
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1,
|
|
87
|
+
0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
|
|
88
|
+
0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9,
|
|
89
|
+
0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
|
|
90
|
+
]
|
|
91
|
+
# fmt: on
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def crc16ccitt_false_add(crc: int, data: bytes | memoryview) -> int:
|
|
95
|
+
"""CRC-16/CCITT-FALSE one update step without output post-processing."""
|
|
96
|
+
for b in data:
|
|
97
|
+
crc = ((crc << 8) & 0xFFFF) ^ _CRC16CCITT_FALSE_TABLE[((crc >> 8) ^ b) & 0xFF]
|
|
98
|
+
return crc
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def crc16ccitt_false_full(data: bytes | memoryview) -> int:
|
|
102
|
+
"""CRC-16/CCITT-FALSE with the standard initial value."""
|
|
103
|
+
return crc16ccitt_false_add(CRC16CCITT_FALSE_INITIAL, data)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# =====================================================================================================================
|
|
107
|
+
# rapidhash V3
|
|
108
|
+
# =====================================================================================================================
|
|
109
|
+
|
|
110
|
+
_RAPID_MASK = 0xFFFFFFFFFFFFFFFF
|
|
111
|
+
_RAPID_SECRET = (
|
|
112
|
+
0x2D358DCCAA6C78A5,
|
|
113
|
+
0x8BB84B93962EACC9,
|
|
114
|
+
0x4B33A62ED433D4A3,
|
|
115
|
+
0x4D5A2DA51DE1AA47,
|
|
116
|
+
0xA0761D6478BD642F,
|
|
117
|
+
0xE7037ED1A0B428DB,
|
|
118
|
+
0x90ED1765281C388C,
|
|
119
|
+
0xAAAAAAAAAAAAAAAA,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _rapid_mum(a: int, b: int) -> tuple[int, int]:
|
|
124
|
+
r = a * b
|
|
125
|
+
return r & _RAPID_MASK, (r >> 64) & _RAPID_MASK
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _rapid_mix(a: int, b: int) -> int:
|
|
129
|
+
lo, hi = _rapid_mum(a, b)
|
|
130
|
+
return lo ^ hi
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _r64(d: bytes, o: int) -> int:
|
|
134
|
+
return int.from_bytes(d[o : o + 8], "little")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _r32(d: bytes, o: int) -> int:
|
|
138
|
+
return int.from_bytes(d[o : o + 4], "little")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def rapidhash(data: bytes | str) -> int:
|
|
142
|
+
"""
|
|
143
|
+
A compliant implementation of rapidhash that matches rapidhash.h that can accept strings directly.
|
|
144
|
+
The eponymous package published PyPI is NOT compatible with rapidhash.h, it must not be used!
|
|
145
|
+
"""
|
|
146
|
+
data = data if isinstance(data, bytes) else data.encode("utf8")
|
|
147
|
+
assert isinstance(data, bytes)
|
|
148
|
+
s = _RAPID_SECRET
|
|
149
|
+
n = len(data)
|
|
150
|
+
seed = _rapid_mix(s[2], s[1])
|
|
151
|
+
a = b = 0
|
|
152
|
+
i = n
|
|
153
|
+
p = 0
|
|
154
|
+
if n <= 16:
|
|
155
|
+
if n >= 4:
|
|
156
|
+
seed = (seed ^ n) & _RAPID_MASK
|
|
157
|
+
if n >= 8:
|
|
158
|
+
a = _r64(data, 0)
|
|
159
|
+
b = _r64(data, n - 8)
|
|
160
|
+
else:
|
|
161
|
+
a = _r32(data, 0)
|
|
162
|
+
b = _r32(data, n - 4)
|
|
163
|
+
elif n > 0:
|
|
164
|
+
a = (data[0] << 45) | data[n - 1]
|
|
165
|
+
b = data[n >> 1]
|
|
166
|
+
else:
|
|
167
|
+
if n > 112:
|
|
168
|
+
see1 = see2 = see3 = see4 = see5 = see6 = seed
|
|
169
|
+
while True:
|
|
170
|
+
seed = _rapid_mix(_r64(data, p) ^ s[0], _r64(data, p + 8) ^ seed)
|
|
171
|
+
see1 = _rapid_mix(_r64(data, p + 16) ^ s[1], _r64(data, p + 24) ^ see1)
|
|
172
|
+
see2 = _rapid_mix(_r64(data, p + 32) ^ s[2], _r64(data, p + 40) ^ see2)
|
|
173
|
+
see3 = _rapid_mix(_r64(data, p + 48) ^ s[3], _r64(data, p + 56) ^ see3)
|
|
174
|
+
see4 = _rapid_mix(_r64(data, p + 64) ^ s[4], _r64(data, p + 72) ^ see4)
|
|
175
|
+
see5 = _rapid_mix(_r64(data, p + 80) ^ s[5], _r64(data, p + 88) ^ see5)
|
|
176
|
+
see6 = _rapid_mix(_r64(data, p + 96) ^ s[6], _r64(data, p + 104) ^ see6)
|
|
177
|
+
p += 112
|
|
178
|
+
i -= 112
|
|
179
|
+
if i <= 112:
|
|
180
|
+
break
|
|
181
|
+
seed ^= see1
|
|
182
|
+
see2 ^= see3
|
|
183
|
+
see4 ^= see5
|
|
184
|
+
seed ^= see6
|
|
185
|
+
see2 ^= see4
|
|
186
|
+
seed ^= see2
|
|
187
|
+
if i > 16:
|
|
188
|
+
seed = _rapid_mix(_r64(data, p) ^ s[2], _r64(data, p + 8) ^ seed)
|
|
189
|
+
if i > 32:
|
|
190
|
+
seed = _rapid_mix(_r64(data, p + 16) ^ s[2], _r64(data, p + 24) ^ seed)
|
|
191
|
+
if i > 48:
|
|
192
|
+
seed = _rapid_mix(_r64(data, p + 32) ^ s[1], _r64(data, p + 40) ^ seed)
|
|
193
|
+
if i > 64:
|
|
194
|
+
seed = _rapid_mix(_r64(data, p + 48) ^ s[1], _r64(data, p + 56) ^ seed)
|
|
195
|
+
if i > 80:
|
|
196
|
+
seed = _rapid_mix(_r64(data, p + 64) ^ s[2], _r64(data, p + 72) ^ seed)
|
|
197
|
+
if i > 96:
|
|
198
|
+
seed = _rapid_mix(_r64(data, p + 80) ^ s[1], _r64(data, p + 88) ^ seed)
|
|
199
|
+
a = _r64(data, p + i - 16) ^ i
|
|
200
|
+
b = _r64(data, p + i - 8)
|
|
201
|
+
a ^= s[1]
|
|
202
|
+
b ^= seed
|
|
203
|
+
a, b = _rapid_mum(a, b)
|
|
204
|
+
return _rapid_mix(a ^ s[7], b ^ s[1] ^ i)
|
pycyphal2/_header.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import struct
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
U64_MASK = 0xFFFFFFFFFFFFFFFF
|
|
7
|
+
|
|
8
|
+
HEADER_SIZE = 24
|
|
9
|
+
SEQNO48_MASK = (1 << 48) - 1
|
|
10
|
+
LAGE_MIN = -1
|
|
11
|
+
LAGE_MAX = 35
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# =====================================================================================================================
|
|
15
|
+
# MSG headers
|
|
16
|
+
# =====================================================================================================================
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class MsgBeHeader:
|
|
21
|
+
TYPE = 0
|
|
22
|
+
|
|
23
|
+
topic_log_age: int
|
|
24
|
+
topic_evictions: int
|
|
25
|
+
topic_hash: int
|
|
26
|
+
tag: int
|
|
27
|
+
|
|
28
|
+
def serialize(self) -> bytes:
|
|
29
|
+
return _serialize_msg(self.TYPE, self.topic_log_age, self.topic_evictions, self.topic_hash, self.tag)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def deserialize(buf: bytes | memoryview) -> MsgBeHeader | None:
|
|
33
|
+
r = _deserialize_msg(buf)
|
|
34
|
+
return MsgBeHeader(*r) if r is not None else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class MsgRelHeader:
|
|
39
|
+
TYPE = 1
|
|
40
|
+
|
|
41
|
+
topic_log_age: int
|
|
42
|
+
topic_evictions: int
|
|
43
|
+
topic_hash: int
|
|
44
|
+
tag: int
|
|
45
|
+
|
|
46
|
+
def serialize(self) -> bytes:
|
|
47
|
+
return _serialize_msg(self.TYPE, self.topic_log_age, self.topic_evictions, self.topic_hash, self.tag)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def deserialize(buf: bytes | memoryview) -> MsgRelHeader | None:
|
|
51
|
+
r = _deserialize_msg(buf)
|
|
52
|
+
return MsgRelHeader(*r) if r is not None else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _serialize_msg(ty: int, lage: int, evictions: int, topic_hash: int, tag: int) -> bytes:
|
|
56
|
+
buf = bytearray(HEADER_SIZE)
|
|
57
|
+
buf[0] = ty
|
|
58
|
+
buf[3] = lage & 0xFF
|
|
59
|
+
struct.pack_into("<I", buf, 4, evictions & 0xFFFFFFFF)
|
|
60
|
+
struct.pack_into("<Q", buf, 8, topic_hash & U64_MASK)
|
|
61
|
+
struct.pack_into("<Q", buf, 16, tag & U64_MASK)
|
|
62
|
+
return bytes(buf)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _deserialize_msg(buf: bytes | memoryview) -> tuple[int, int, int, int] | None:
|
|
66
|
+
if len(buf) < HEADER_SIZE:
|
|
67
|
+
return None
|
|
68
|
+
if buf[2] != 0: # incompatibility
|
|
69
|
+
return None
|
|
70
|
+
lage = struct.unpack_from("<b", buf, 3)[0]
|
|
71
|
+
if not (LAGE_MIN <= lage <= LAGE_MAX):
|
|
72
|
+
return None
|
|
73
|
+
evictions = struct.unpack_from("<I", buf, 4)[0]
|
|
74
|
+
topic_hash = struct.unpack_from("<Q", buf, 8)[0]
|
|
75
|
+
tag = struct.unpack_from("<Q", buf, 16)[0]
|
|
76
|
+
return (lage, evictions, topic_hash, tag)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# =====================================================================================================================
|
|
80
|
+
# MSG ACK/NACK headers
|
|
81
|
+
# =====================================================================================================================
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class MsgAckHeader:
|
|
86
|
+
TYPE = 2
|
|
87
|
+
|
|
88
|
+
topic_hash: int
|
|
89
|
+
tag: int
|
|
90
|
+
|
|
91
|
+
def serialize(self) -> bytes:
|
|
92
|
+
return _serialize_msg_ack(self.TYPE, self.topic_hash, self.tag)
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def deserialize(buf: bytes | memoryview) -> MsgAckHeader | None:
|
|
96
|
+
r = _deserialize_msg_ack(buf)
|
|
97
|
+
return MsgAckHeader(*r) if r is not None else None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class MsgNackHeader:
|
|
102
|
+
TYPE = 3
|
|
103
|
+
|
|
104
|
+
topic_hash: int
|
|
105
|
+
tag: int
|
|
106
|
+
|
|
107
|
+
def serialize(self) -> bytes:
|
|
108
|
+
return _serialize_msg_ack(self.TYPE, self.topic_hash, self.tag)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def deserialize(buf: bytes | memoryview) -> MsgNackHeader | None:
|
|
112
|
+
r = _deserialize_msg_ack(buf)
|
|
113
|
+
return MsgNackHeader(*r) if r is not None else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _serialize_msg_ack(ty: int, topic_hash: int, tag: int) -> bytes:
|
|
117
|
+
buf = bytearray(HEADER_SIZE)
|
|
118
|
+
buf[0] = ty
|
|
119
|
+
struct.pack_into("<Q", buf, 8, topic_hash & U64_MASK)
|
|
120
|
+
struct.pack_into("<Q", buf, 16, tag & U64_MASK)
|
|
121
|
+
return bytes(buf)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _deserialize_msg_ack(buf: bytes | memoryview) -> tuple[int, int] | None:
|
|
125
|
+
if len(buf) < HEADER_SIZE:
|
|
126
|
+
return None
|
|
127
|
+
if struct.unpack_from("<I", buf, 4)[0] != 0: # incompatibility
|
|
128
|
+
return None
|
|
129
|
+
topic_hash = struct.unpack_from("<Q", buf, 8)[0]
|
|
130
|
+
tag = struct.unpack_from("<Q", buf, 16)[0]
|
|
131
|
+
return (topic_hash, tag)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# =====================================================================================================================
|
|
135
|
+
# RSP headers (responses)
|
|
136
|
+
# =====================================================================================================================
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class RspBeHeader:
|
|
141
|
+
TYPE = 4
|
|
142
|
+
|
|
143
|
+
tag: int # u8
|
|
144
|
+
seqno: int # u48
|
|
145
|
+
topic_hash: int
|
|
146
|
+
message_tag: int
|
|
147
|
+
|
|
148
|
+
def serialize(self) -> bytes:
|
|
149
|
+
return _serialize_rsp(self.TYPE, self.tag, self.seqno, self.topic_hash, self.message_tag)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def deserialize(buf: bytes | memoryview) -> RspBeHeader | None:
|
|
153
|
+
r = _deserialize_rsp(buf)
|
|
154
|
+
return RspBeHeader(*r) if r is not None else None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class RspRelHeader:
|
|
159
|
+
TYPE = 5
|
|
160
|
+
|
|
161
|
+
tag: int
|
|
162
|
+
seqno: int
|
|
163
|
+
topic_hash: int
|
|
164
|
+
message_tag: int
|
|
165
|
+
|
|
166
|
+
def serialize(self) -> bytes:
|
|
167
|
+
return _serialize_rsp(self.TYPE, self.tag, self.seqno, self.topic_hash, self.message_tag)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def deserialize(buf: bytes | memoryview) -> RspRelHeader | None:
|
|
171
|
+
r = _deserialize_rsp(buf)
|
|
172
|
+
return RspRelHeader(*r) if r is not None else None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# =====================================================================================================================
|
|
176
|
+
# RSP ACK/NACK headers
|
|
177
|
+
# =====================================================================================================================
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True)
|
|
181
|
+
class RspAckHeader:
|
|
182
|
+
TYPE = 6
|
|
183
|
+
|
|
184
|
+
tag: int
|
|
185
|
+
seqno: int
|
|
186
|
+
topic_hash: int
|
|
187
|
+
message_tag: int
|
|
188
|
+
|
|
189
|
+
def serialize(self) -> bytes:
|
|
190
|
+
return _serialize_rsp(self.TYPE, self.tag, self.seqno, self.topic_hash, self.message_tag)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def deserialize(buf: bytes | memoryview) -> RspAckHeader | None:
|
|
194
|
+
r = _deserialize_rsp(buf)
|
|
195
|
+
return RspAckHeader(*r) if r is not None else None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclass(frozen=True)
|
|
199
|
+
class RspNackHeader:
|
|
200
|
+
TYPE = 7
|
|
201
|
+
|
|
202
|
+
tag: int
|
|
203
|
+
seqno: int
|
|
204
|
+
topic_hash: int
|
|
205
|
+
message_tag: int
|
|
206
|
+
|
|
207
|
+
def serialize(self) -> bytes:
|
|
208
|
+
return _serialize_rsp(self.TYPE, self.tag, self.seqno, self.topic_hash, self.message_tag)
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def deserialize(buf: bytes | memoryview) -> RspNackHeader | None:
|
|
212
|
+
r = _deserialize_rsp(buf)
|
|
213
|
+
return RspNackHeader(*r) if r is not None else None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _serialize_rsp(ty: int, tag: int, seqno: int, topic_hash: int, message_tag: int) -> bytes:
|
|
217
|
+
buf = bytearray(HEADER_SIZE)
|
|
218
|
+
buf[0] = ty
|
|
219
|
+
buf[1] = tag & 0xFF
|
|
220
|
+
seqno48 = seqno & SEQNO48_MASK
|
|
221
|
+
for i in range(6):
|
|
222
|
+
buf[2 + i] = (seqno48 >> (i * 8)) & 0xFF
|
|
223
|
+
struct.pack_into("<Q", buf, 8, topic_hash & U64_MASK)
|
|
224
|
+
struct.pack_into("<Q", buf, 16, message_tag & U64_MASK)
|
|
225
|
+
return bytes(buf)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _deserialize_rsp(buf: bytes | memoryview) -> tuple[int, int, int, int] | None:
|
|
229
|
+
if len(buf) < HEADER_SIZE:
|
|
230
|
+
return None
|
|
231
|
+
tag = buf[1]
|
|
232
|
+
seqno = 0
|
|
233
|
+
for i in range(6):
|
|
234
|
+
seqno |= buf[2 + i] << (i * 8)
|
|
235
|
+
topic_hash = struct.unpack_from("<Q", buf, 8)[0]
|
|
236
|
+
message_tag = struct.unpack_from("<Q", buf, 16)[0]
|
|
237
|
+
return (tag, seqno, topic_hash, message_tag)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# =====================================================================================================================
|
|
241
|
+
# GOSSIP header
|
|
242
|
+
# =====================================================================================================================
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass(frozen=True)
|
|
246
|
+
class GossipHeader:
|
|
247
|
+
TYPE = 8
|
|
248
|
+
|
|
249
|
+
topic_log_age: int
|
|
250
|
+
topic_hash: int
|
|
251
|
+
topic_evictions: int
|
|
252
|
+
name_len: int
|
|
253
|
+
|
|
254
|
+
def serialize(self) -> bytes:
|
|
255
|
+
buf = bytearray(HEADER_SIZE)
|
|
256
|
+
buf[0] = self.TYPE
|
|
257
|
+
buf[3] = self.topic_log_age & 0xFF
|
|
258
|
+
struct.pack_into("<Q", buf, 8, self.topic_hash & U64_MASK)
|
|
259
|
+
struct.pack_into("<I", buf, 16, self.topic_evictions & 0xFFFFFFFF)
|
|
260
|
+
buf[23] = self.name_len & 0xFF
|
|
261
|
+
return bytes(buf)
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def deserialize(buf: bytes | memoryview) -> GossipHeader | None:
|
|
265
|
+
if len(buf) < HEADER_SIZE:
|
|
266
|
+
return None
|
|
267
|
+
if struct.unpack_from("<I", buf, 4)[0] != 0:
|
|
268
|
+
return None
|
|
269
|
+
lage = struct.unpack_from("<b", buf, 3)[0]
|
|
270
|
+
if not (LAGE_MIN <= lage <= LAGE_MAX):
|
|
271
|
+
return None
|
|
272
|
+
topic_hash = struct.unpack_from("<Q", buf, 8)[0]
|
|
273
|
+
evictions = struct.unpack_from("<I", buf, 16)[0]
|
|
274
|
+
name_len = buf[23]
|
|
275
|
+
return GossipHeader(lage, topic_hash, evictions, name_len)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# =====================================================================================================================
|
|
279
|
+
# SCOUT header
|
|
280
|
+
# =====================================================================================================================
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass(frozen=True)
|
|
284
|
+
class ScoutHeader:
|
|
285
|
+
TYPE = 9
|
|
286
|
+
|
|
287
|
+
pattern_len: int
|
|
288
|
+
|
|
289
|
+
def serialize(self) -> bytes:
|
|
290
|
+
buf = bytearray(HEADER_SIZE)
|
|
291
|
+
buf[0] = self.TYPE
|
|
292
|
+
buf[23] = self.pattern_len & 0xFF
|
|
293
|
+
return bytes(buf)
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def deserialize(buf: bytes | memoryview) -> ScoutHeader | None:
|
|
297
|
+
if len(buf) < HEADER_SIZE:
|
|
298
|
+
return None
|
|
299
|
+
if struct.unpack_from("<I", buf, 4)[0] != 0:
|
|
300
|
+
return None
|
|
301
|
+
if struct.unpack_from("<Q", buf, 8)[0] != 0:
|
|
302
|
+
return None
|
|
303
|
+
return ScoutHeader(buf[23])
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# =====================================================================================================================
|
|
307
|
+
# Dispatcher
|
|
308
|
+
# =====================================================================================================================
|
|
309
|
+
|
|
310
|
+
HeaderType = (
|
|
311
|
+
MsgBeHeader
|
|
312
|
+
| MsgRelHeader
|
|
313
|
+
| MsgAckHeader
|
|
314
|
+
| MsgNackHeader
|
|
315
|
+
| RspBeHeader
|
|
316
|
+
| RspRelHeader
|
|
317
|
+
| RspAckHeader
|
|
318
|
+
| RspNackHeader
|
|
319
|
+
| GossipHeader
|
|
320
|
+
| ScoutHeader
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def deserialize_header(buf: bytes | memoryview) -> HeaderType | None:
|
|
325
|
+
"""Deserialize a 24-byte session-layer header. Returns None on validation failure."""
|
|
326
|
+
if len(buf) < 1:
|
|
327
|
+
return None
|
|
328
|
+
ty = buf[0]
|
|
329
|
+
if ty == 0:
|
|
330
|
+
return MsgBeHeader.deserialize(buf)
|
|
331
|
+
if ty == 1:
|
|
332
|
+
return MsgRelHeader.deserialize(buf)
|
|
333
|
+
if ty == 2:
|
|
334
|
+
return MsgAckHeader.deserialize(buf)
|
|
335
|
+
if ty == 3:
|
|
336
|
+
return MsgNackHeader.deserialize(buf)
|
|
337
|
+
if ty == 4:
|
|
338
|
+
return RspBeHeader.deserialize(buf)
|
|
339
|
+
if ty == 5:
|
|
340
|
+
return RspRelHeader.deserialize(buf)
|
|
341
|
+
if ty == 6:
|
|
342
|
+
return RspAckHeader.deserialize(buf)
|
|
343
|
+
if ty == 7:
|
|
344
|
+
return RspNackHeader.deserialize(buf)
|
|
345
|
+
if ty == 8:
|
|
346
|
+
return GossipHeader.deserialize(buf)
|
|
347
|
+
if ty == 9:
|
|
348
|
+
return ScoutHeader.deserialize(buf)
|
|
349
|
+
return None
|