-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreference.py
More file actions
158 lines (133 loc) · 5.36 KB
/
Copy pathreference.py
File metadata and controls
158 lines (133 loc) · 5.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# SPDX-License-Identifier: MIT
"""Reference pure-Python implementation of COBS and COBS/R.
This is the canonical implementation used to generate the conformance vectors.
The algorithms are those described in:
Stuart Cheshire and Mary Baker, "Consistent Overhead Byte Stuffing",
IEEE/ACM Transactions on Networking, Vol. 7, No. 2, April 1999.
COBS/R (Reduced) is a variant devised by Craig McQueen; this reference follows
his MIT-licensed pure-Python implementation.
"""
class DecodeError(Exception):
"""Raised when input is not a valid COBS/COBS-R encoding."""
def cobs_encode(in_bytes):
"""Encode ``in_bytes`` (bytes) with basic COBS. Returns bytes."""
final_zero = True
out_bytes = bytearray()
idx = 0
search_start_idx = 0
for in_char in in_bytes:
if in_char == 0:
final_zero = True
out_bytes.append(idx - search_start_idx + 1)
out_bytes += in_bytes[search_start_idx:idx]
search_start_idx = idx + 1
else:
if idx - search_start_idx == 0xFD:
final_zero = False
out_bytes.append(0xFF)
out_bytes += in_bytes[search_start_idx:idx + 1]
search_start_idx = idx + 1
idx += 1
if idx != search_start_idx or final_zero:
out_bytes.append(idx - search_start_idx + 1)
out_bytes += in_bytes[search_start_idx:idx]
return bytes(out_bytes)
def cobs_decode(in_bytes):
"""Decode basic-COBS ``in_bytes``. Raises DecodeError on invalid input."""
out_bytes = bytearray()
idx = 0
if len(in_bytes) > 0:
while True:
length = in_bytes[idx]
if length == 0:
raise DecodeError("zero byte found in input")
idx += 1
end = idx + length - 1
copy = in_bytes[idx:end]
if 0 in copy:
raise DecodeError("zero byte found in input")
out_bytes += copy
idx = end
if idx > len(in_bytes):
raise DecodeError("not enough input bytes for length code")
if idx < len(in_bytes):
if length < 0xFF:
out_bytes.append(0)
else:
break
return bytes(out_bytes)
def cobsr_encode(in_bytes):
"""Encode ``in_bytes`` with COBS/R (Reduced). Returns bytes."""
out_bytes = bytearray()
idx = 0
search_start_idx = 0
for in_char in in_bytes:
if idx - search_start_idx == 0xFE:
out_bytes.append(0xFF)
out_bytes += in_bytes[search_start_idx:idx]
search_start_idx = idx
if in_char == 0:
out_bytes.append(idx - search_start_idx + 1)
out_bytes += in_bytes[search_start_idx:idx]
search_start_idx = idx + 1
idx += 1
try:
final_byte_value = in_bytes[-1]
except IndexError:
final_byte_value = 0
length_value = idx - search_start_idx + 1
if final_byte_value < length_value:
out_bytes.append(length_value)
out_bytes += in_bytes[search_start_idx:idx]
else:
out_bytes.append(final_byte_value)
out_bytes += in_bytes[search_start_idx:idx - 1]
return bytes(out_bytes)
def cobsr_decode(in_bytes):
"""Decode COBS/R ``in_bytes``. Raises DecodeError on invalid input."""
out_bytes = bytearray()
idx = 0
if len(in_bytes) > 0:
while True:
length = in_bytes[idx]
if length == 0:
raise DecodeError("zero byte found in input")
idx += 1
end = idx + length - 1
copy = in_bytes[idx:end]
if 0 in copy:
raise DecodeError("zero byte found in input")
out_bytes += copy
idx = end
if idx > len(in_bytes):
out_bytes.append(length)
break
elif idx < len(in_bytes):
if length < 0xFF:
out_bytes.append(0)
else:
break
return bytes(out_bytes)
def _xor(data, sentinel):
"""XOR every byte of ``data`` with ``sentinel`` (a no-op when sentinel==0)."""
if sentinel == 0:
return bytes(data)
return bytes(b ^ sentinel for b in data)
# Configurable-sentinel variants. A single, arbitrary "sentinel" byte replaces
# 0x00 as the value the encoding avoids (so it can delimit frames). The scheme is
# XOR-over-the-finished-encoding: encode/decode normally, then XOR the whole byte
# stream with the sentinel. ``sentinel == 0`` is byte-for-byte identical to the
# plain codec. Every Firechip implementation uses this exact method, which is why
# the sentinel encoding stays byte-identical across languages.
def cobs_encode_with_sentinel(in_bytes, sentinel):
"""Encode ``in_bytes`` with basic COBS using an arbitrary ``sentinel`` byte."""
return _xor(cobs_encode(in_bytes), sentinel)
def cobs_decode_with_sentinel(in_bytes, sentinel):
"""Decode basic-COBS ``in_bytes`` encoded with an arbitrary ``sentinel``."""
return cobs_decode(_xor(in_bytes, sentinel))
def cobsr_encode_with_sentinel(in_bytes, sentinel):
"""Encode ``in_bytes`` with COBS/R using an arbitrary ``sentinel`` byte."""
return _xor(cobsr_encode(in_bytes), sentinel)
def cobsr_decode_with_sentinel(in_bytes, sentinel):
"""Decode COBS/R ``in_bytes`` encoded with an arbitrary ``sentinel``."""
return cobsr_decode(_xor(in_bytes, sentinel))