dfindexeddb 20240519__py3-none-any.whl → 20241105__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.
@@ -0,0 +1,600 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2024 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Parsers for Gecko encoded JavaScript values."""
16
+ from __future__ import annotations
17
+
18
+ import dataclasses
19
+ import datetime
20
+ import io
21
+ import struct
22
+ from typing import Any, List, Tuple, Union
23
+
24
+ import snappy
25
+
26
+ from dfindexeddb import errors
27
+ from dfindexeddb import utils
28
+ from dfindexeddb.indexeddb import types
29
+ from dfindexeddb.indexeddb.firefox import definitions
30
+
31
+
32
+ @dataclasses.dataclass
33
+ class IDBKey(utils.FromDecoderMixin):
34
+ """An IndexedDB Key.
35
+
36
+ Attributes:
37
+ offset: the offset of the key.
38
+ type: the type of the key.
39
+ value: the value of the key.
40
+ """
41
+ offset: int
42
+ type: definitions.IndexedDBKeyType
43
+ value: Union[bytes, datetime.datetime, float, list, str, None]
44
+
45
+ @classmethod
46
+ def FromDecoder(
47
+ cls, decoder: utils.StreamDecoder, base_offset: int = 0) -> IDBKey:
48
+ """Decodes an IndexedDB key from the current position of the decoder.
49
+
50
+ Args:
51
+ decoder: the decoder.
52
+ base_offset: the base offset.
53
+
54
+ Returns:
55
+ The IndexedDB Key.
56
+ """
57
+ offset, key_type, data = KeyDecoder(decoder.stream).DecodeKey()
58
+ return cls(offset=offset + base_offset, type=key_type, value=data)
59
+
60
+
61
+ class KeyDecoder(utils.StreamDecoder):
62
+ """A helper class to decode the Gecko-encoded key value."""
63
+
64
+ def DecodeKey(self) -> Tuple[int, definitions.IndexedDBKeyType, Any]:
65
+ """Decodes the key.
66
+
67
+ Returns:
68
+ a tuple comprising the offset where the key was decoded, the key type and
69
+ the key value.
70
+ """
71
+ offset, key_type_bytes = self.PeekBytes(1)
72
+ value = self._DecodeJSValInternal(key_type_bytes[0])
73
+ if key_type_bytes[0] >= definitions.IndexedDBKeyType.ARRAY:
74
+ key_type = definitions.IndexedDBKeyType.ARRAY
75
+ else:
76
+ key_type = definitions.IndexedDBKeyType(key_type_bytes[0])
77
+ return offset, key_type, value
78
+
79
+ def _DecodeJSValInternal(
80
+ self,
81
+ key_type: int,
82
+ type_offset: int = 0,
83
+ recursion_depth: int = 0
84
+ ) -> Any:
85
+ """Decodes a Javascript Value.
86
+
87
+ Args:
88
+ key_type: the key type byte value.
89
+ type_offset: the type_offset.
90
+ recursion_depth: the current recursion depth.
91
+
92
+ Returns:
93
+ the value.
94
+
95
+ Raises:
96
+ ParserError: when reaching maximum recursion depth or when an known key
97
+ type has been parsed.
98
+ """
99
+ if recursion_depth == definitions.MAX_RECURSION_DEPTH:
100
+ raise errors.ParserError('Reached Maximum Recursion Depth')
101
+
102
+ if key_type - type_offset >= definitions.IndexedDBKeyType.ARRAY:
103
+ type_offset += definitions.IndexedDBKeyType.ARRAY
104
+ if (type_offset ==
105
+ definitions.IndexedDBKeyType.ARRAY * definitions.MAX_ARRAY_COLLAPSE):
106
+ _ = self.ReadBytes(1)
107
+ type_offset = 0
108
+ value = []
109
+ while (
110
+ self.NumRemainingBytes() and (key_type - type_offset) != (
111
+ definitions.IndexedDBKeyType.TERMINATOR)):
112
+ value.append(
113
+ self._DecodeJSValInternal(
114
+ key_type, type_offset, recursion_depth + 1))
115
+ type_offset = 0
116
+ if self.NumRemainingBytes():
117
+ _, key_bytes = self.PeekBytes(1)
118
+ key_type = key_bytes[0]
119
+
120
+ if self.NumRemainingBytes():
121
+ _ = self.ReadBytes(1) # consume terminator byte
122
+
123
+ elif key_type - type_offset == definitions.IndexedDBKeyType.STRING:
124
+ _, value = self._DecodeAsStringy()
125
+ value = value.decode('utf-8') # TODO: test other text encoding types
126
+ elif key_type - type_offset == definitions.IndexedDBKeyType.DATE:
127
+ _, value = self._DecodeDate()
128
+ elif key_type - type_offset == definitions.IndexedDBKeyType.FLOAT:
129
+ _, value = self._DecodeFloat()
130
+ elif key_type - type_offset == definitions.IndexedDBKeyType.BINARY:
131
+ _, value = self._DecodeAsStringy()
132
+ else:
133
+ raise errors.ParserError(
134
+ f'Unknown key type parsed: {key_type - type_offset}')
135
+ return value
136
+
137
+ def _ReadUntilNull(self) -> bytearray:
138
+ """Read bytes until a null (terminator) byte is encountered."""
139
+ result = bytearray()
140
+ num_remaining_bytes = self.NumRemainingBytes()
141
+ while num_remaining_bytes:
142
+ _, c = self.ReadBytes(1)
143
+ if c == b'\x00':
144
+ break
145
+ result += c
146
+ num_remaining_bytes -= 1
147
+ return result
148
+
149
+ def _DecodeAsStringy(self, element_size: int = 1) -> Tuple[int, bytes]:
150
+ """Decodes a string buffer.
151
+
152
+ Args:
153
+ element_size: parse string as a multi-byte string.
154
+
155
+ Returns:
156
+ A tuple of the offset and the decoded string.
157
+
158
+ Raises:
159
+ ParserError: if an invalid type is read or an unsupported
160
+ encoded byte is encountered
161
+ """
162
+ offset, type_int = self.DecodeUint8()
163
+ if type_int % definitions.IndexedDBKeyType.ARRAY.value not in (
164
+ definitions.IndexedDBKeyType.STRING.value,
165
+ definitions.IndexedDBKeyType.BINARY.value):
166
+ raise errors.ParserError(
167
+ 'Invalid type', hex(type_int), hex(type_int % 0x50))
168
+
169
+ i = 0
170
+ result = self._ReadUntilNull()
171
+ while i < len(result):
172
+ if not result[i] & 0x80:
173
+ result[i] -= definitions.ONE_BYTE_ADJUST
174
+ elif element_size == 2 or not result[i] & 0x40:
175
+ c = struct.unpack_from('>H', result, i)[0]
176
+ d = c - 0x8000 - definitions.TWO_BYTE_ADJUST
177
+ struct.pack_into('>H', result, i, d)
178
+ else:
179
+ raise errors.ParserError('Unsupported byte') # TODO: add support.
180
+ i += 1
181
+ return offset + 1, result
182
+
183
+ def _DecodeFloat(self) -> Tuple[int, float]:
184
+ """Decodes a float.
185
+
186
+ Returns:
187
+ A tuple of the offset and a (double-precision) float value.
188
+
189
+ Raises:
190
+ ParserError if an invalid type is read
191
+ """
192
+ _, type_byte = self.DecodeUint8()
193
+ if type_byte % definitions.IndexedDBKeyType.ARRAY not in (
194
+ definitions.IndexedDBKeyType.FLOAT, definitions.IndexedDBKeyType.DATE):
195
+ raise errors.ParserError(
196
+ 'Invalid type', hex(type_byte), hex(type_byte % 0x50))
197
+
198
+ float_offset, number_bytes = self.ReadBytes(
199
+ min(8, self.NumRemainingBytes()))
200
+
201
+ # pad to 64-bits
202
+ if len(number_bytes) != 8:
203
+ number_bytes += b'\x00'*(8 - len(number_bytes))
204
+
205
+ int_value = struct.unpack('>q', number_bytes)[0]
206
+
207
+ if int_value & 0x8000000000000000:
208
+ int_value = int_value & 0x7FFFFFFFFFFFFFFF
209
+ else:
210
+ int_value = -int_value
211
+ return float_offset, struct.unpack('>d', struct.pack('>q', int_value))[0]
212
+
213
+ def _DecodeBinary(self) -> Tuple[int, bytes]:
214
+ """Decodes a binary value.
215
+
216
+ Returns:
217
+ A tuple of the offset and the bytearray.
218
+
219
+ Raises:
220
+ ParserError: when an invalid type byte is encountered.
221
+ """
222
+ _, type_byte = self.ReadBytes(1)
223
+ if (type_byte[0] % definitions.IndexedDBKeyType.ARRAY !=
224
+ definitions.IndexedDBKeyType.BINARY):
225
+ raise errors.ParserError(
226
+ f'Invalid type {type_byte[0] % definitions.IndexedDBKeyType.ARRAY}')
227
+ values = bytearray()
228
+ offset = self.stream.tell()
229
+ while self.NumRemainingBytes():
230
+ _, value = self.ReadBytes(1)
231
+ if value == definitions.IndexedDBKeyType.TERMINATOR:
232
+ break
233
+ values.append(value[0])
234
+ return offset, values
235
+
236
+ def _DecodeDate(self) -> Tuple[int, datetime.datetime]:
237
+ """Decodes a date.
238
+
239
+ Returns:
240
+ A tuple of the offset and the decoded datetime.
241
+ """
242
+ offset, value = self._DecodeFloat()
243
+ return offset, datetime.datetime.fromtimestamp(
244
+ value/1000, tz=datetime.timezone.utc)
245
+
246
+
247
+ class JSStructuredCloneDecoder(utils.FromDecoderMixin):
248
+ """Decodes a serialized JavaScript value.
249
+
250
+ Attributes:
251
+ decoder: for decoding the buffer.
252
+ all_objects: all the decoded objects.
253
+ """
254
+
255
+ def __init__(self, decoder: utils.StreamDecoder):
256
+ """Initializes the reader.
257
+
258
+ Args:
259
+ buffer: the serialized bytes.
260
+ """
261
+ self.decoder = decoder
262
+ self.all_objects: List[Any] = []
263
+ self._object_stack: List[Any] = []
264
+
265
+ def _PeekTagAndData(self) -> tuple[int, int]:
266
+ """Peeks Tag and Data values."""
267
+ _, pair_bytes = self.decoder.PeekBytes(8)
268
+ pair = int.from_bytes(pair_bytes, byteorder='little', signed=False)
269
+ tag = pair >> 32
270
+ data = pair & 0x00000000FFFFFFFF
271
+ return tag, data
272
+
273
+ def _DecodeTagAndData(self) -> tuple[int, int]:
274
+ """Decodes Tag and Data values."""
275
+ _, pair = self.decoder.DecodeUint64()
276
+ tag = pair >> 32
277
+ data = pair & 0x00000000FFFFFFFF
278
+ return tag, data
279
+
280
+ def _DecodeString(self, data: int) -> str:
281
+ """Decodes a String.
282
+
283
+ Args:
284
+ data: the data value.
285
+
286
+ Returns:
287
+ the decoded string.
288
+
289
+ Raises:
290
+ ParserError: if the serialized string length is too large.
291
+ """
292
+ number_chars = data & 0x7FFFFFFF
293
+ latin1 = data & 0x80000000
294
+ if number_chars > definitions.MAX_LENGTH:
295
+ raise errors.ParserError('Bad serialized string length')
296
+
297
+ if latin1:
298
+ _, chars = self.decoder.ReadBytes(number_chars)
299
+ chars = chars.decode('latin-1')
300
+ else:
301
+ _, chars = self.decoder.ReadBytes(number_chars*2)
302
+ chars = chars.decode('utf-16-le')
303
+
304
+ return chars
305
+
306
+ def _DecodeBigInt(self, data: int) -> int:
307
+ """Decodes a BigInteger.
308
+
309
+ Args:
310
+ data: the data value
311
+
312
+ Returns:
313
+ the decoded integer.
314
+ """
315
+ length = data & 0x7FFFFFFF
316
+ is_negative = data & 0x80000000
317
+ if length == 0:
318
+ return 0
319
+ contents = []
320
+ for _ in range(length):
321
+ _, element = self.decoder.ReadBytes(8)
322
+ contents.extend(element)
323
+ return int.from_bytes(
324
+ contents, byteorder='little', signed=bool(is_negative))
325
+
326
+ def _DecodeTypedArray(
327
+ self, array_type: int, number_elements: int, is_version1: bool = False
328
+ ) -> Any:
329
+ """Decodes a typed array.
330
+
331
+ Args:
332
+ array_type: the array type.
333
+ number_elements: the number of elements.
334
+ is_version1: True if the typed array is of version 1 type.
335
+
336
+ Returns:
337
+ The typed array.
338
+ """
339
+ self.all_objects.append(None)
340
+ dummy_index = len(self.all_objects) - 1
341
+
342
+ is_auto_length = number_elements == -1
343
+ if is_auto_length:
344
+ number_elements = 0
345
+
346
+ if is_version1:
347
+ value = self._DecodeV1ArrayBuffer(array_type, number_elements)
348
+ byte_offset = 0 # pylint: disable=unused-variable
349
+ else:
350
+ value = self._StartRead()
351
+ byte_offset = self.decoder.DecodeUint64() # pylint: disable=unused-variable
352
+
353
+ self.all_objects[dummy_index] = value
354
+ return value
355
+
356
+ def _DecodeArrayBuffer(
357
+ self, buffer_type: int, data: int
358
+ ) -> bytes:
359
+ """Decodes an ArrayBuffer.
360
+
361
+ Args:
362
+ buffer_type: the data type.
363
+ data: the data value.
364
+
365
+ Returns:
366
+ the array buffer.
367
+ """
368
+ if buffer_type == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT:
369
+ _, number_bytes = self.decoder.DecodeUint64()
370
+ elif (buffer_type ==
371
+ definitions.StructuredDataType.RESIZABLE_ARRAY_BUFFER_OBJECT):
372
+ _, number_bytes = self.decoder.DecodeUint64()
373
+ _, max_bytes = self.decoder.DecodeUint64() # pylint: disable=unused-variable
374
+ else:
375
+ number_bytes = data
376
+
377
+ _, buffer = self.decoder.ReadBytes(number_bytes)
378
+ return buffer
379
+
380
+ def _DecodeV1ArrayBuffer(self, array_type: int, number_elements: int):
381
+ """Decodes a V1 ArrayBuffer.
382
+
383
+ Not currently implemented.
384
+
385
+ Raises:
386
+ NotImplementedError: because it's not yet supported.
387
+ """
388
+ raise NotImplementedError('V1 Array Buffer not supported')
389
+
390
+ def DecodeValue(self) -> Any:
391
+ """Decodes a Javascript value."""
392
+ if not self._DecodeHeader():
393
+ return None
394
+
395
+ tag, _ = self._PeekTagAndData()
396
+ if tag == definitions.StructuredDataType.TRANSFER_MAP_HEADER:
397
+ raise errors.ParserError('Transfer Map is not supported.')
398
+
399
+ value = self._StartRead()
400
+
401
+ while self._object_stack:
402
+ offset = self.decoder.stream.tell() % 8
403
+ if offset:
404
+ self.decoder.stream.seek(8 - offset, io.SEEK_CUR)
405
+ tag, _ = self._PeekTagAndData()
406
+ obj = self._object_stack[-1]
407
+ if tag == definitions.StructuredDataType.END_OF_KEYS:
408
+ self._object_stack.pop()
409
+ _, _ = self._DecodeTagAndData()
410
+ continue
411
+
412
+ expect_key_value_pairs = isinstance(obj, (dict, set))
413
+ key = self._StartRead()
414
+ if isinstance(key, types.Null) and expect_key_value_pairs:
415
+ self._object_stack.pop()
416
+ continue
417
+
418
+ if isinstance(obj, types.JSSet):
419
+ obj.values.add(key)
420
+ elif isinstance(obj, types.JSArray):
421
+ field = self._StartRead()
422
+ obj.values[key] = field
423
+ else:
424
+ field = self._StartRead()
425
+ obj[key] = field
426
+
427
+ return value
428
+
429
+ def _DecodeHeader(self) -> bool:
430
+ """Decodes the header.
431
+
432
+ Returns:
433
+ True if the correct header tag is read.
434
+
435
+ Raises:
436
+ ParserError: when a header tag is not found.
437
+ """
438
+ tag, _ = self._DecodeTagAndData()
439
+ if tag != definitions.StructuredDataType.HEADER:
440
+ raise errors.ParserError('Header tag not found.')
441
+ return True
442
+
443
+ def _DecodeRegexp(self, flags: int):
444
+ """Decodes a regular expression.
445
+
446
+ Args:
447
+ flags: the regular expression flags.
448
+
449
+ Raises:
450
+ ParserError: when the string tag is not found.
451
+ """
452
+ tag, string_data = self._DecodeTagAndData()
453
+ if tag != definitions.StructuredDataType.STRING:
454
+ raise errors.ParserError('String tag not found')
455
+
456
+ pattern = self._DecodeString(string_data)
457
+ return types.RegExp(pattern=pattern, flags=str(flags))
458
+
459
+ def _StartRead(self) -> Any:
460
+ """Reads the start of a serialized value.
461
+
462
+ Raises:
463
+ ParserError: when an unsupported StructuredDataType,
464
+ StructuredCloneTag or unknown tag is read.
465
+ """
466
+ tag, data = self._DecodeTagAndData()
467
+ if tag == definitions.StructuredDataType.NULL:
468
+ value = types.Null()
469
+ elif tag == definitions.StructuredDataType.UNDEFINED:
470
+ value = types.Undefined()
471
+ elif tag == definitions.StructuredDataType.INT32:
472
+ value = data
473
+ elif tag == definitions.StructuredDataType.BOOLEAN:
474
+ value = bool(data)
475
+ elif tag == definitions.StructuredDataType.BOOLEAN_OBJECT:
476
+ value = bool(data)
477
+ self.all_objects.append(value)
478
+ elif tag == definitions.StructuredDataType.STRING:
479
+ value = self._DecodeString(data)
480
+ elif tag == definitions.StructuredDataType.STRING_OBJECT:
481
+ value = self._DecodeString(data)
482
+ self.all_objects.append(value)
483
+ elif tag == definitions.StructuredDataType.NUMBER_OBJECT:
484
+ _, value = self.decoder.DecodeDouble()
485
+ self.all_objects.append(value)
486
+ elif tag == definitions.StructuredDataType.BIGINT:
487
+ value = self._DecodeBigInt(data)
488
+ elif tag == definitions.StructuredDataType.BIGINT_OBJECT:
489
+ value = self._DecodeBigInt(data)
490
+ self.all_objects.append(value)
491
+ elif tag == definitions.StructuredDataType.DATE_OBJECT:
492
+ _, timestamp = self.decoder.DecodeDouble()
493
+ value = datetime.datetime.fromtimestamp(
494
+ timestamp/1000, tz=datetime.timezone.utc)
495
+ self.all_objects.append(value)
496
+ elif tag == definitions.StructuredDataType.REGEXP_OBJECT:
497
+ value = self._DecodeRegexp(data)
498
+ self.all_objects.append(value)
499
+ elif tag == definitions.StructuredDataType.ARRAY_OBJECT:
500
+ value = types.JSArray()
501
+ for _ in range(data):
502
+ value.values.append(None)
503
+ self._object_stack.append(value)
504
+ self.all_objects.append(value)
505
+ elif tag == definitions.StructuredDataType.OBJECT_OBJECT:
506
+ value = {}
507
+ self._object_stack.append(value)
508
+ self.all_objects.append(value)
509
+ elif tag == definitions.StructuredDataType.BACK_REFERENCE_OBJECT:
510
+ value = self.all_objects[data]
511
+ elif tag == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT_V2:
512
+ value = self._DecodeArrayBuffer(tag, data)
513
+ self.all_objects.append(value)
514
+ elif tag == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT:
515
+ value = self._DecodeArrayBuffer(tag, data)
516
+ self.all_objects.append(value)
517
+ elif tag == definitions.StructuredDataType.RESIZABLE_ARRAY_BUFFER_OBJECT:
518
+ value = self._DecodeArrayBuffer(tag, data)
519
+ self.all_objects.append(value)
520
+ elif tag == definitions.StructuredDataType.TYPED_ARRAY_OBJECT_V2:
521
+ _, array_type = self.decoder.DecodeUint64()
522
+ self._DecodeTypedArray(array_type, data)
523
+ elif tag == definitions.StructuredDataType.TYPED_ARRAY_OBJECT:
524
+ _, number_elements = self.decoder.DecodeUint64()
525
+ value = self._DecodeTypedArray(data, number_elements)
526
+ elif tag == definitions.StructuredDataType.MAP_OBJECT:
527
+ value = {}
528
+ self._object_stack.append(value)
529
+ self.all_objects.append(value)
530
+ elif tag == definitions.StructuredDataType.SET_OBJECT:
531
+ value = types.JSSet()
532
+ self._object_stack.append(value)
533
+ self.all_objects.append(value)
534
+ elif tag <= definitions.StructuredDataType.FLOAT_MAX:
535
+ value = struct.unpack('<d', struct.pack('<II', data, tag))[0]
536
+ elif (definitions.StructuredDataType.TYPED_ARRAY_V1_INT8 <= tag
537
+ <= definitions.StructuredDataType.TYPED_ARRAY_V1_UINT8_CLAMPED):
538
+ value = self._DecodeTypedArray(tag, data)
539
+ elif tag in iter(definitions.StructuredDataType):
540
+ raise errors.ParserError(
541
+ 'Unsupported StructuredDataType',
542
+ definitions.StructuredDataType(tag))
543
+ elif tag in iter(definitions.StructuredCloneTags):
544
+ raise errors.ParserError(
545
+ 'Unsupported StructuredCloneTag',
546
+ definitions.StructuredCloneTags(tag))
547
+ else:
548
+ raise errors.ParserError('Unknown tag', hex(tag))
549
+
550
+ # align the stream to an 8 byte boundary
551
+ offset = self.decoder.stream.tell() % 8
552
+ if offset:
553
+ _, slack_bytes = self.decoder.ReadBytes(8 - offset) # pylint: disable=unused-variable
554
+
555
+ return value
556
+
557
+ @classmethod
558
+ def FromDecoder(
559
+ cls, decoder: utils.StreamDecoder, base_offset: int = 0) -> Any:
560
+ """Decodes the parsed JavaScript object from the StreamDecoder.
561
+
562
+ Args:
563
+ decoder: the StreamDecoder.
564
+ base_offset: the base offset.
565
+
566
+ Returns:
567
+ The class instance.
568
+ """
569
+ return cls(decoder).DecodeValue()
570
+
571
+ @classmethod
572
+ def FromBytes(cls, raw_data: bytes, base_offset: int = 0) -> Any:
573
+ """Returns the parsed JavaScript object from the raw bytes.
574
+
575
+ Args:
576
+ raw_data: the raw data to deserialize/parse.
577
+
578
+ Returns:
579
+ A python representation of the parsed JavaScript object.
580
+ """
581
+ if raw_data.startswith(definitions.FRAME_HEADER):
582
+ uncompressed_data = bytearray()
583
+ pos = len(definitions.FRAME_HEADER)
584
+ while pos < len(raw_data):
585
+ is_uncompressed = raw_data[pos]
586
+ block_size = int.from_bytes(
587
+ raw_data[pos + 1:pos + 4], byteorder='little', signed=False)
588
+ masked_checksum = int.from_bytes( # pylint: disable=unused-variable
589
+ raw_data[pos + 4: pos + 9], byteorder='little', signed=False)
590
+ if is_uncompressed:
591
+ uncompressed_data += raw_data[pos + 8: pos + 8 + block_size - 4]
592
+ else:
593
+ uncompressed_data += snappy.decompress(
594
+ raw_data[pos + 8: pos + 8 + block_size - 4])
595
+ pos += block_size + 4
596
+ else:
597
+ uncompressed_data = snappy.decompress(raw_data)
598
+ stream = io.BytesIO(uncompressed_data)
599
+
600
+ return cls.FromStream(stream=stream, base_offset=base_offset)