Skip to content

Commit fe83ca5

Browse files
committed
init
0 parents  commit fe83ca5

File tree

5 files changed

+682
-0
lines changed

5 files changed

+682
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__/
2+
*.py[cod]

bits_coder/__init__.py

Whitespace-only changes.

bits_coder/coder.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from fields import NBITS_AUTO, Uint
2+
3+
4+
class BitsCoder:
5+
"""
6+
Tool to encode/decode binary data.
7+
8+
BitsCoder specifies format of data stored in binary format
9+
using fields.Fields. When the format is specified you can either
10+
decode from binary or encode data to binary format.
11+
12+
Parameters
13+
----------
14+
fields: list
15+
List of fields which inherit from `fields.Field`
16+
byteorder: unicode
17+
Byte order, choices are: 'big' or 'little'
18+
19+
"""
20+
21+
def __init__(self, fields, byteorder='big'):
22+
assert byteorder in ['big', 'little'], \
23+
"byteorder must be either 'little' or 'big'"
24+
self.byteorder = byteorder
25+
self._fields = fields
26+
self._add_field_for_remaining_bits()
27+
self._map = {}
28+
self._create_fields_map()
29+
30+
def _add_field_for_remaining_bits(self):
31+
bits_count = sum(
32+
field.nbits
33+
for field in self._fields
34+
if field.nbits != NBITS_AUTO
35+
)
36+
remaining_bits = bits_count % 8
37+
if remaining_bits:
38+
self._fields.append(Uint(nbits=remaining_bits, value=0))
39+
40+
def _create_fields_map(self):
41+
underscore_counter = 1
42+
for field in self._fields:
43+
if not field.name:
44+
field.name = '___{}'.format(underscore_counter)
45+
underscore_counter += 1
46+
self._map[field.name] = field.value
47+
48+
def validate_fields_for_encoding(self):
49+
if not all(field.enc_value is not None for field in self._fields):
50+
raise ValueError('All fields need to have a value for encoding')
51+
52+
@property
53+
def map(self):
54+
"""Filed name to field value map."""
55+
self._create_fields_map()
56+
return self._map
57+
58+
@property
59+
def list(self):
60+
"""List of field's values."""
61+
if not hasattr(self, '_list'):
62+
self._list = [field.value for field in self._fields]
63+
return self._list
64+
65+
def encode(self):
66+
"""Encode all fields to bytearray."""
67+
self.validate_fields_for_encoding()
68+
byte_array = bytearray()
69+
last_byte = 0
70+
free_bits_left = 8
71+
72+
for field in self._fields:
73+
shift = free_bits_left - field.nbits
74+
if shift >= 0:
75+
last_byte |= (field.enc_value << shift)
76+
free_bits_left = shift
77+
if shift == 0:
78+
byte_array.append(last_byte)
79+
last_byte = 0
80+
else:
81+
shift = abs(shift)
82+
last_byte |= (field.enc_value >> shift)
83+
byte_array.append(last_byte)
84+
full_bytes, remaining_bits = divmod(shift, 8)
85+
if full_bytes:
86+
remaining_full_bytes_value = (
87+
(field.enc_value >> remaining_bits) &
88+
(2 ** (8 * full_bytes) - 1)
89+
)
90+
byte_array.extend(
91+
remaining_full_bytes_value.to_bytes(
92+
full_bytes, byteorder='big')
93+
)
94+
if remaining_bits:
95+
free_bits_left = 8 - remaining_bits
96+
last_byte = (
97+
(field.enc_value & (2 ** remaining_bits - 1)) <<
98+
(8 - remaining_bits)
99+
)
100+
else:
101+
free_bits_left = 8
102+
last_byte = 0
103+
if 0 < free_bits_left < 8:
104+
byte_array.append(last_byte)
105+
if self.byteorder == 'little':
106+
byte_array = byte_array[::-1]
107+
108+
return byte_array
109+
110+
def decode(self, pld):
111+
"""Decode values from bytearray
112+
113+
Parameters
114+
----------
115+
pld: bytearray, str(hex)
116+
Payload to be decoded.
117+
118+
"""
119+
if isinstance(pld, str):
120+
pld = bytearray.fromhex(pld)
121+
byte_number = 0
122+
bits_on_byte_left = 8
123+
if self.byteorder == 'little':
124+
pld = pld[::-1]
125+
for field in self._fields:
126+
bytes_to_decode = bytearray([pld[byte_number]])
127+
if field.nbits > bits_on_byte_left:
128+
add_bytes, last_bits_used = divmod(
129+
field.nbits - bits_on_byte_left, 8)
130+
end_byte_n = byte_number + add_bytes + (last_bits_used != 0)
131+
bytes_to_decode = pld[byte_number:end_byte_n + 1]
132+
bits_on_byte_left = 8 - last_bits_used
133+
byte_number += add_bytes + 1
134+
else:
135+
bits_on_byte_left -= field.nbits
136+
if not bits_on_byte_left:
137+
byte_number += 1
138+
value = (
139+
(
140+
int.from_bytes(bytes_to_decode, byteorder='big') >>
141+
(bits_on_byte_left % 8)
142+
) &
143+
(2 ** field.nbits - 1)
144+
)
145+
field.decode(value)

0 commit comments

Comments
 (0)