-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbit_stream.py
More file actions
344 lines (260 loc) · 10.2 KB
/
Copy pathbit_stream.py
File metadata and controls
344 lines (260 loc) · 10.2 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
import logging
from enum import Enum
import struct
import sys
class OpenMode(Enum):
WRITE = 0
READ = 1
class BitStream(object):
"""
Bit-level write and read operations to a file
"""
__file_object = None
__byte_buffer = 0
__idx = 0
__logger = None
__open_mode = None
# Entire file buffer. Only used in OpenMode.READ mode
__file_buffer = None
__current_byte_position = 0
__padding_with_zeros = True
def __init__(self, file_path: str, open_mode: OpenMode, padding_with_zeros=True):
"""
Constructor of BitStream. Note: logging level should be set using the appropriate cmd flag (Python3 logging
--log=INFO/DEBUG/etc)
:param file_path: string representing the file path to be opened
:param open_mode: specify wether to read or write to the file
"""
self.__logger = logging.getLogger(__name__)
# self.__logger.setLevel(logging.DEBUG)
self.__open_mode = open_mode
rwb = None
self.__current_byte_position = 0
self.__padding_with_zeros = padding_with_zeros
if open_mode == OpenMode.WRITE:
rwb = 'wb'
elif open_mode == OpenMode.READ:
rwb = 'rb'
else:
self.__logger.critical('Unexpected Open Mode specified!')
self.__file_object = open(file_path, rwb)
if open_mode == OpenMode.READ:
self.__init_read_buffer()
self.__logger.debug('BitStream initialized with file {} and mode {}'.format(file_path, open_mode))
def set_padding_mode(self, with_zeros):
self.__padding_with_zeros = with_zeros
def __init_read_buffer(self):
"""
Called if OpenMode == READ. It will read the entire content of the file to memory.
:return:
"""
self.__file_buffer = self.__file_object.read()
self.__file_object.close()
# self.__read_byte()
def flush(self):
"""
Writes remaining bits to file. Right-padding if necessary.
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. Flush not performed.')
if self.__idx != 0:
self.__logger.debug('Flushing {} bits of data to file'.format(self.__idx))
self.__write_byte()
else:
self.__logger.debug('Byte buffer was empty. Flushing operation not performed')
def __write_byte(self):
"""
Writes byte buffer to file
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. __write_byte not performed.')
return
if self.__idx == 8:
self.__file_object.write(self.__byte_buffer.to_bytes(1, byteorder='big'))
self.__byte_buffer = 0
# This should only run if self.flush() calls self.__write_byte()
elif self.__idx < 8:
bitwise_length = 8 - self.__idx
self.__byte_buffer = self.__byte_buffer << bitwise_length
if not self.__padding_with_zeros:
mask = 0xFF >> self.__idx
self.__byte_buffer = self.__byte_buffer | mask
self.__logger.debug('Padded with {} ones'.format(bitwise_length))
else:
self.__logger.debug('Padded with {} zeroes'.format(bitwise_length))
# print('IDX:', self.__idx)
# print('BUF:', bin(self.__byte_buffer))
# self.__byte_buffer = self.__byte_buffer | (~self.__byte_buffer)
# print('BUF:', bin(self.__byte_buffer))
self.__file_object.write(self.__byte_buffer.to_bytes(1, byteorder='big'))
else:
self.__logger.debug(
'__write_byte was called in an unexpected state. Current byte buffer size: {}'.format(self.__idx))
self.__idx = 0
def write_bit(self, bit: str):
"""
Writes one bit to file. Note: the actual content is only written when the buffer has been filled or flush was called.
:param bit:
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. Write_bit not performed.')
return
self.__idx = self.__idx + 1
if bit == '0':
self.__byte_buffer = self.__byte_buffer << 1
elif bit == '1':
self.__byte_buffer = self.__byte_buffer << 1
self.__byte_buffer = self.__byte_buffer | 1
else:
self.__logger.error('Unexpected bit value: {}. Ignoring.'.format(bit))
if self.__idx >= 8:
self.__write_byte()
def write_n_bits(self, bits: [int]):
"""
Writes n bits to the file
:param bits: list of integers representing the bit sequence
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. write_n_bits not performed.')
return
for b in bits:
self.write_bit(b)
def __read_byte(self):
""""
Reads the next byte from buffer
"""
if self.__current_byte_position >= len(self.__file_buffer):
self.__logger.info('Reached end of file. Cannot read further.')
self.__byte_buffer = None
return False
self.__byte_buffer = self.__file_buffer[self.__current_byte_position]
self.__current_byte_position = self.__current_byte_position + 1
return True
def read_bit(self) -> int:
"""
Reads one bit from file
:return:
"""
if self.__open_mode != OpenMode.READ:
self.__logger.critical('OpenMode is not READ. read_bit not performed.')
return -1
if self.__idx >= 8:
# Read next byte from file and update buffer
# Reset pointer to first bit
self.__idx = 0
if not self.__read_byte():
return -1
if self.__byte_buffer == None:
return -1
mask = int(2 ** (7 - self.__idx))
bit = self.__byte_buffer & mask
self.__idx = self.__idx + 1
return 1 if bit != 0 else 0
def read_n_bits(self, num_of_bits: int) -> str:
"""
Reads n bits from the sequence
:param num_of_bits:
:return: bit sequence represented as a string
"""
if self.__current_byte_position >= len(self.__file_buffer) and self.__byte_buffer is None:
return -1
# lst = []
bit_sequence_str = ''
for i in range(0, num_of_bits):
bit = self.read_bit()
if bit == -1:
return bit_sequence_str
bit_sequence_str = bit_sequence_str + str(bit)
return bit_sequence_str
def close(self):
"""
Closes file handler and flushes buffer if necessary
:return:
"""
if self.__open_mode == OpenMode.WRITE:
self.flush()
elif self.__open_mode == OpenMode.READ:
self.__file_buffer == None
self.__byte_buffer == None
self.__file_object.close()
def __del__(self):
if self.__open_mode == OpenMode.WRITE:
self.flush()
def read_int(self, n_bytes):
"""
:return:
"""
if self.__open_mode != OpenMode.READ:
self.__logger.critical('OpenMode is not READ. readint not performed.')
return
sequence = []
for x in range(0, n_bytes):
if not self.__read_byte():
return None
sequence.append(self.__byte_buffer)
# Advance to next byte since everything from the current one was consumed
self.__idx += 8
return int.from_bytes(sequence, byteorder='big')
def read_signed_int(self, n_bytes):
"""
:return:
"""
if self.__open_mode != OpenMode.READ:
self.__logger.critical('OpenMode is not READ. readint not performed.')
return
if not self.__read_byte():
return None
# Advance to next byte since everything from the current one was consumed
self.__idx += 8
print('Content:', self.__byte_buffer)
t = struct.unpack('b', self.__byte_buffer.to_bytes(1, sys.byteorder))[0]
print('t: ', type(t))
return t
def write_int(self, number, n_bytes):
"""
:type number: integer value that will be written using 4 bytes and system endianness
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. write_int not performed.')
return
# self.__file_object.write(struct.pack('B', number))
self.__file_object.write(number.to_bytes(n_bytes, byteorder='big'))
def write_signed_int(self, number):
"""
:type number: integer value that will be written using 4 bytes and system endianness
:return:
"""
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. write_int not performed.')
return
self.__file_object.write(struct.pack('b', number))
def write_bytes(self, bs):
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. write_int not performed.')
return
self.__file_object.write(bs)
def write_str(self, string):
if self.__open_mode != OpenMode.WRITE:
self.__logger.critical('OpenMode is not WRITE. write_int not performed.')
return
self.__file_object.write(string.encode('utf-8'))
def read_bytes(self, num_of_bytes):
if self.__open_mode != OpenMode.READ:
self.__logger.critical('OpenMode is not READ. readint not performed.')
return
sequence = []
for x in range(0, num_of_bytes):
if not self.__read_byte():
return None
sequence.append(self.__byte_buffer)
self.__idx += 8
return sequence
def has_reached_eof(self):
if self.__current_byte_position == len(self.__file_buffer) and self.__idx >= 8:
return True
return False