vcdiff-decoder 0.1.0__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.
- LICENSE +204 -0
- README.md +260 -0
- vcdiff_decoder/__init__.py +43 -0
- vcdiff_decoder/addresscache.py +111 -0
- vcdiff_decoder/adler32.py +43 -0
- vcdiff_decoder/codetable.py +106 -0
- vcdiff_decoder/decoder.py +435 -0
- vcdiff_decoder/exceptions.py +56 -0
- vcdiff_decoder/types.py +112 -0
- vcdiff_decoder/varint.py +52 -0
- vcdiff_decoder-0.1.0.dist-info/LICENSE +204 -0
- vcdiff_decoder-0.1.0.dist-info/METADATA +285 -0
- vcdiff_decoder-0.1.0.dist-info/RECORD +14 -0
- vcdiff_decoder-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""VCDIFF default code table implementation - RFC 3284 Section 5"""
|
|
2
|
+
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
from .types import Instruction, InstructionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CodeTable:
|
|
9
|
+
"""VCDIFF instruction code table"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize empty code table"""
|
|
13
|
+
# Initialize all entries to NO_OP
|
|
14
|
+
self.entries: List[Tuple[Instruction, Instruction]] = [
|
|
15
|
+
(
|
|
16
|
+
Instruction(InstructionType.NO_OP, 0, 0),
|
|
17
|
+
Instruction(InstructionType.NO_OP, 0, 0)
|
|
18
|
+
)
|
|
19
|
+
for _ in range(256)
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
def get(self, code: int, slot: int) -> Instruction:
|
|
23
|
+
"""Get the instruction at the given code and slot
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
code: Instruction code (0-255)
|
|
27
|
+
slot: Slot number (0 or 1)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The instruction at the specified code and slot
|
|
31
|
+
"""
|
|
32
|
+
return self.entries[code][slot]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_default_code_table() -> CodeTable:
|
|
36
|
+
"""Create the default code table specified in RFC 3284
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The default code table
|
|
40
|
+
"""
|
|
41
|
+
ct = CodeTable()
|
|
42
|
+
|
|
43
|
+
# Entry 0: RUN with size 0
|
|
44
|
+
ct.entries[0] = (
|
|
45
|
+
Instruction(InstructionType.RUN, 0, 0),
|
|
46
|
+
Instruction(InstructionType.NO_OP, 0, 0)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Entries 1-18: ADD with sizes 0-17
|
|
50
|
+
for i in range(18):
|
|
51
|
+
ct.entries[i + 1] = (
|
|
52
|
+
Instruction(InstructionType.ADD, i, 0),
|
|
53
|
+
Instruction(InstructionType.NO_OP, 0, 0)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
index = 19
|
|
57
|
+
|
|
58
|
+
# Entries 19-162: COPY instructions with different modes and sizes
|
|
59
|
+
for mode in range(9):
|
|
60
|
+
# COPY with size 0 (size will be read from stream)
|
|
61
|
+
ct.entries[index] = (
|
|
62
|
+
Instruction(InstructionType.COPY, 0, mode),
|
|
63
|
+
Instruction(InstructionType.NO_OP, 0, 0)
|
|
64
|
+
)
|
|
65
|
+
index += 1
|
|
66
|
+
|
|
67
|
+
# COPY with sizes 4-18
|
|
68
|
+
for size in range(4, 19):
|
|
69
|
+
ct.entries[index] = (
|
|
70
|
+
Instruction(InstructionType.COPY, size, mode),
|
|
71
|
+
Instruction(InstructionType.NO_OP, 0, 0)
|
|
72
|
+
)
|
|
73
|
+
index += 1
|
|
74
|
+
|
|
75
|
+
# Entries 163-234: Combined ADD+COPY instructions
|
|
76
|
+
for mode in range(6):
|
|
77
|
+
for add_size in range(1, 5):
|
|
78
|
+
for copy_size in range(4, 7):
|
|
79
|
+
ct.entries[index] = (
|
|
80
|
+
Instruction(InstructionType.ADD, add_size, 0),
|
|
81
|
+
Instruction(InstructionType.COPY, copy_size, mode)
|
|
82
|
+
)
|
|
83
|
+
index += 1
|
|
84
|
+
|
|
85
|
+
# Entries 235-246: More combined ADD+COPY instructions
|
|
86
|
+
for mode in range(6, 9):
|
|
87
|
+
for add_size in range(1, 5):
|
|
88
|
+
ct.entries[index] = (
|
|
89
|
+
Instruction(InstructionType.ADD, add_size, 0),
|
|
90
|
+
Instruction(InstructionType.COPY, 4, mode)
|
|
91
|
+
)
|
|
92
|
+
index += 1
|
|
93
|
+
|
|
94
|
+
# Entries 247-255: COPY+ADD combinations
|
|
95
|
+
for mode in range(9):
|
|
96
|
+
ct.entries[index] = (
|
|
97
|
+
Instruction(InstructionType.COPY, 4, mode),
|
|
98
|
+
Instruction(InstructionType.ADD, 1, 0)
|
|
99
|
+
)
|
|
100
|
+
index += 1
|
|
101
|
+
|
|
102
|
+
return ct
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Default code table instance
|
|
106
|
+
DEFAULT_CODE_TABLE = build_default_code_table()
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""VCDIFF decoder implementation - RFC 3284 compliance"""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import BinaryIO, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from .types import (
|
|
7
|
+
Header, Window, ParsedDelta, RuntimeInstruction, InstructionType,
|
|
8
|
+
VCDIFF_MAGIC, VCDIFF_VERSION, MINIMUM_FILE_SIZE,
|
|
9
|
+
VCD_DECOMPRESS, VCD_CODETABLE, VCD_APPHEADER,
|
|
10
|
+
VCD_SOURCE, VCD_TARGET, VCD_ADLER32,
|
|
11
|
+
NEAR_CACHE_SIZE, SAME_CACHE_SIZE
|
|
12
|
+
)
|
|
13
|
+
from .exceptions import (
|
|
14
|
+
VCDIFFError, InvalidMagicError, InvalidVersionError, InvalidFormatError,
|
|
15
|
+
CorruptedDataError, InvalidChecksumError,
|
|
16
|
+
err_unexpected_eof, err_data_overrun, err_invalid_value, err_out_of_bounds
|
|
17
|
+
)
|
|
18
|
+
from .varint import read_varint
|
|
19
|
+
from .adler32 import compute_checksum
|
|
20
|
+
from .addresscache import AddressCache
|
|
21
|
+
from .codetable import DEFAULT_CODE_TABLE
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Decoder:
|
|
25
|
+
"""VCDIFF decoder interface"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, source: Union[bytes, bytearray]):
|
|
28
|
+
"""Initialize decoder with source data
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
source: Source data for applying deltas
|
|
32
|
+
"""
|
|
33
|
+
self.source = bytes(source)
|
|
34
|
+
|
|
35
|
+
def decode(self, delta: Union[bytes, bytearray]) -> bytes:
|
|
36
|
+
"""Decode a VCDIFF delta and return target data
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
delta: VCDIFF delta data
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The decoded target data
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
VCDIFFError: If the delta is malformed or cannot be decoded
|
|
46
|
+
"""
|
|
47
|
+
# Parse the delta to get structured information
|
|
48
|
+
parsed = parse_delta(delta)
|
|
49
|
+
|
|
50
|
+
# Process all windows and accumulate target data
|
|
51
|
+
target = bytearray()
|
|
52
|
+
|
|
53
|
+
for window in parsed.windows:
|
|
54
|
+
# Decode this window's target data
|
|
55
|
+
window_target = self._decode_window(window, self.source)
|
|
56
|
+
target.extend(window_target)
|
|
57
|
+
|
|
58
|
+
return bytes(target)
|
|
59
|
+
|
|
60
|
+
def _decode_window(self, window: Window, source: bytes) -> bytes:
|
|
61
|
+
"""Decode a single window using the source data and window instructions
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
window: Window to decode
|
|
65
|
+
source: Source data
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Decoded target data for this window
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
VCDIFFError: If the window cannot be decoded
|
|
72
|
+
"""
|
|
73
|
+
# Initialize address cache
|
|
74
|
+
address_cache = AddressCache(NEAR_CACHE_SIZE, SAME_CACHE_SIZE)
|
|
75
|
+
address_cache.reset(window.address_section)
|
|
76
|
+
|
|
77
|
+
# Create target buffer
|
|
78
|
+
target = bytearray()
|
|
79
|
+
|
|
80
|
+
# Get source segment for this window
|
|
81
|
+
source_segment = b''
|
|
82
|
+
source_length = 0
|
|
83
|
+
if window.win_indicator & VCD_SOURCE:
|
|
84
|
+
# Use source data
|
|
85
|
+
start = window.source_segment_position
|
|
86
|
+
end = start + window.source_segment_size
|
|
87
|
+
if end > len(source):
|
|
88
|
+
raise InvalidFormatError("source segment extends beyond source data")
|
|
89
|
+
source_segment = source[start:end]
|
|
90
|
+
source_length = len(source_segment)
|
|
91
|
+
|
|
92
|
+
# Parse and execute the actual instructions
|
|
93
|
+
instructions = self._parse_instructions(
|
|
94
|
+
window.instruction_section,
|
|
95
|
+
window.data_section,
|
|
96
|
+
address_cache
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Execute each instruction
|
|
100
|
+
for instruction in instructions:
|
|
101
|
+
if instruction.type == InstructionType.NO_OP:
|
|
102
|
+
# Skip
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
elif instruction.type == InstructionType.ADD:
|
|
106
|
+
# Add data from the instruction's data
|
|
107
|
+
if len(instruction.data) != instruction.size:
|
|
108
|
+
raise InvalidFormatError("ADD instruction data size mismatch")
|
|
109
|
+
target.extend(instruction.data)
|
|
110
|
+
|
|
111
|
+
elif instruction.type == InstructionType.COPY:
|
|
112
|
+
# Decode the address using the address cache
|
|
113
|
+
here = len(target) + source_length
|
|
114
|
+
addr = address_cache.decode_address(here, instruction.mode)
|
|
115
|
+
|
|
116
|
+
# Determine if copying from source or target
|
|
117
|
+
if addr < source_length:
|
|
118
|
+
# Copy from source segment
|
|
119
|
+
end = addr + instruction.size
|
|
120
|
+
if end > source_length:
|
|
121
|
+
raise err_out_of_bounds("COPY", addr, instruction.size, source_length)
|
|
122
|
+
target.extend(source_segment[addr:end])
|
|
123
|
+
else:
|
|
124
|
+
# Copy from target data (self-referential copy)
|
|
125
|
+
target_addr = addr - source_length
|
|
126
|
+
if target_addr >= len(target):
|
|
127
|
+
raise VCDIFFError(
|
|
128
|
+
f"COPY instruction address {addr} references target position "
|
|
129
|
+
f"{target_addr} but target only has {len(target)} bytes"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Handle overlapping copies byte by byte
|
|
133
|
+
for i in range(instruction.size):
|
|
134
|
+
if target_addr + i >= len(target):
|
|
135
|
+
raise VCDIFFError(
|
|
136
|
+
f"COPY instruction would read beyond target bounds: "
|
|
137
|
+
f"position {target_addr + i}, target size {len(target)}"
|
|
138
|
+
)
|
|
139
|
+
target.append(target[target_addr + i])
|
|
140
|
+
|
|
141
|
+
elif instruction.type == InstructionType.RUN:
|
|
142
|
+
# Repeat a single byte
|
|
143
|
+
if len(instruction.data) != 1:
|
|
144
|
+
raise InvalidFormatError("RUN instruction must have exactly 1 data byte")
|
|
145
|
+
run_byte = instruction.data[0]
|
|
146
|
+
target.extend([run_byte] * instruction.size)
|
|
147
|
+
|
|
148
|
+
else:
|
|
149
|
+
raise InvalidFormatError(f"unknown instruction type: {instruction.type}")
|
|
150
|
+
|
|
151
|
+
# Validate Adler32 checksum if present
|
|
152
|
+
if window.has_checksum:
|
|
153
|
+
computed = compute_checksum(1, target) # Adler32 starts with initial value 1
|
|
154
|
+
if computed != window.checksum:
|
|
155
|
+
raise InvalidChecksumError(
|
|
156
|
+
f"checksum validation failed: expected 0x{window.checksum:08x}, "
|
|
157
|
+
f"got 0x{computed:08x}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return bytes(target)
|
|
161
|
+
|
|
162
|
+
def _parse_instructions(
|
|
163
|
+
self,
|
|
164
|
+
instruction_data: bytes,
|
|
165
|
+
data_section: bytes,
|
|
166
|
+
address_cache: AddressCache
|
|
167
|
+
) -> List[RuntimeInstruction]:
|
|
168
|
+
"""Parse the instruction data from a window using the code table
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
instruction_data: Raw instruction bytes
|
|
172
|
+
data_section: Data section for ADD and RUN instructions
|
|
173
|
+
address_cache: Address cache for this window
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of runtime instructions
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
VCDIFFError: If instructions cannot be parsed
|
|
180
|
+
"""
|
|
181
|
+
stream = io.BytesIO(instruction_data)
|
|
182
|
+
instructions = []
|
|
183
|
+
data_index = 0
|
|
184
|
+
instruction_offset = 0
|
|
185
|
+
|
|
186
|
+
while True:
|
|
187
|
+
code_data = stream.read(1)
|
|
188
|
+
if not code_data:
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
code = code_data[0]
|
|
192
|
+
|
|
193
|
+
# Each code can have up to 2 instructions
|
|
194
|
+
for slot in range(2):
|
|
195
|
+
instruction = DEFAULT_CODE_TABLE.get(code, slot)
|
|
196
|
+
if instruction.type == InstructionType.NO_OP:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
size = instruction.size
|
|
200
|
+
if size == 0 and instruction.type != InstructionType.NO_OP:
|
|
201
|
+
size = read_varint(stream)
|
|
202
|
+
|
|
203
|
+
runtime_inst = RuntimeInstruction(
|
|
204
|
+
type=instruction.type,
|
|
205
|
+
size=size,
|
|
206
|
+
mode=instruction.mode
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Handle instruction-specific data
|
|
210
|
+
if instruction.type == InstructionType.ADD:
|
|
211
|
+
if data_index + size > len(data_section):
|
|
212
|
+
raise err_data_overrun("ADD", instruction_offset, size, len(data_section) - data_index)
|
|
213
|
+
runtime_inst.data = data_section[data_index:data_index + size]
|
|
214
|
+
data_index += size
|
|
215
|
+
|
|
216
|
+
elif instruction.type == InstructionType.RUN:
|
|
217
|
+
if data_index >= len(data_section):
|
|
218
|
+
raise VCDIFFError(
|
|
219
|
+
f"RUN instruction at offset {instruction_offset} requires 1 byte "
|
|
220
|
+
"but no data available in data section"
|
|
221
|
+
)
|
|
222
|
+
runtime_inst.data = bytes([data_section[data_index]])
|
|
223
|
+
data_index += 1
|
|
224
|
+
|
|
225
|
+
elif instruction.type == InstructionType.COPY:
|
|
226
|
+
# Address will be decoded when needed during execution
|
|
227
|
+
runtime_inst.mode = instruction.mode
|
|
228
|
+
|
|
229
|
+
instructions.append(runtime_inst)
|
|
230
|
+
|
|
231
|
+
instruction_offset += 1
|
|
232
|
+
|
|
233
|
+
return instructions
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def decode(source: Union[bytes, bytearray], delta: Union[bytes, bytearray]) -> bytes:
|
|
237
|
+
"""Decode a VCDIFF delta against source data
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
source: Source data
|
|
241
|
+
delta: VCDIFF delta data
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
The decoded target data
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
VCDIFFError: If the delta cannot be decoded
|
|
248
|
+
"""
|
|
249
|
+
decoder = Decoder(source)
|
|
250
|
+
return decoder.decode(delta)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def parse_delta(delta: Union[bytes, bytearray]) -> ParsedDelta:
|
|
254
|
+
"""Parse a VCDIFF delta and return a structured representation
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
delta: VCDIFF delta data
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Parsed delta structure
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
VCDIFFError: If the delta cannot be parsed
|
|
264
|
+
"""
|
|
265
|
+
delta_bytes = bytes(delta)
|
|
266
|
+
if len(delta_bytes) < MINIMUM_FILE_SIZE:
|
|
267
|
+
raise InvalidFormatError("delta too small to be valid VCDIFF")
|
|
268
|
+
|
|
269
|
+
parsed = ParsedDelta(
|
|
270
|
+
header=Header(b'', 0, 0),
|
|
271
|
+
windows=[],
|
|
272
|
+
instructions=[]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
stream = io.BytesIO(delta_bytes)
|
|
276
|
+
|
|
277
|
+
_parse_header(stream, parsed)
|
|
278
|
+
|
|
279
|
+
while stream.tell() < len(delta_bytes):
|
|
280
|
+
window = _parse_window(stream)
|
|
281
|
+
parsed.windows.append(window)
|
|
282
|
+
|
|
283
|
+
# Create address cache for this window
|
|
284
|
+
address_cache = AddressCache(NEAR_CACHE_SIZE, SAME_CACHE_SIZE)
|
|
285
|
+
address_cache.reset(window.address_section)
|
|
286
|
+
|
|
287
|
+
# Parse instructions using the instruction section and data section
|
|
288
|
+
decoder_instance = Decoder(b'') # Temporary decoder for parsing
|
|
289
|
+
instructions = decoder_instance._parse_instructions(
|
|
290
|
+
window.instruction_section,
|
|
291
|
+
window.data_section,
|
|
292
|
+
address_cache
|
|
293
|
+
)
|
|
294
|
+
parsed.instructions.extend(instructions)
|
|
295
|
+
|
|
296
|
+
return parsed
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _parse_header(stream: BinaryIO, parsed: ParsedDelta) -> None:
|
|
300
|
+
"""Parse the VCDIFF header section
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
stream: Input stream
|
|
304
|
+
parsed: Parsed delta to update
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
VCDIFFError: If the header is invalid
|
|
308
|
+
"""
|
|
309
|
+
# Read 3 magic bytes as defined in RFC 3284
|
|
310
|
+
magic = stream.read(3)
|
|
311
|
+
if len(magic) < 3:
|
|
312
|
+
raise err_unexpected_eof("VCDIFF magic bytes", 3 - len(magic))
|
|
313
|
+
|
|
314
|
+
# Compare magic bytes - RFC 3284 Section 4.1
|
|
315
|
+
if magic != VCDIFF_MAGIC:
|
|
316
|
+
raise InvalidMagicError(
|
|
317
|
+
f"invalid VCDIFF magic bytes at offset 0: expected "
|
|
318
|
+
f"{VCDIFF_MAGIC.hex()} but got {magic.hex()}"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
version_data = stream.read(1)
|
|
322
|
+
if not version_data:
|
|
323
|
+
raise err_unexpected_eof("version byte", 1)
|
|
324
|
+
|
|
325
|
+
version = version_data[0]
|
|
326
|
+
if version != VCDIFF_VERSION:
|
|
327
|
+
raise InvalidVersionError(
|
|
328
|
+
f"invalid version at offset 3: value {version}, "
|
|
329
|
+
f"only version {VCDIFF_VERSION} is supported"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
indicator_data = stream.read(1)
|
|
333
|
+
if not indicator_data:
|
|
334
|
+
raise err_unexpected_eof("header indicator", 1)
|
|
335
|
+
|
|
336
|
+
indicator = indicator_data[0]
|
|
337
|
+
|
|
338
|
+
# Check for reserved bits in header indicator
|
|
339
|
+
valid_header_bits = VCD_DECOMPRESS | VCD_CODETABLE | VCD_APPHEADER
|
|
340
|
+
if indicator & ~valid_header_bits != 0:
|
|
341
|
+
raise err_invalid_value("header indicator", 4, indicator, "reserved bits must be zero")
|
|
342
|
+
|
|
343
|
+
parsed.header = Header(magic, version, indicator)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _parse_window(stream: BinaryIO) -> Window:
|
|
347
|
+
"""Parse a single VCDIFF window
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
stream: Input stream
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Parsed window
|
|
354
|
+
|
|
355
|
+
Raises:
|
|
356
|
+
VCDIFFError: If the window is invalid
|
|
357
|
+
"""
|
|
358
|
+
indicator_data = stream.read(1)
|
|
359
|
+
if not indicator_data:
|
|
360
|
+
raise err_unexpected_eof("window indicator", 1)
|
|
361
|
+
|
|
362
|
+
indicator = indicator_data[0]
|
|
363
|
+
|
|
364
|
+
# Check for reserved bits in window indicator
|
|
365
|
+
valid_bits = VCD_SOURCE | VCD_TARGET | VCD_ADLER32
|
|
366
|
+
if indicator & ~valid_bits != 0:
|
|
367
|
+
raise err_invalid_value("window indicator", stream.tell() - 1, indicator, "reserved bits must be zero")
|
|
368
|
+
|
|
369
|
+
window = Window(win_indicator=indicator)
|
|
370
|
+
|
|
371
|
+
if indicator & VCD_SOURCE:
|
|
372
|
+
window.source_segment_size = read_varint(stream)
|
|
373
|
+
window.source_segment_position = read_varint(stream)
|
|
374
|
+
|
|
375
|
+
# Read the length of the delta encoding
|
|
376
|
+
window.delta_encoding_length = read_varint(stream)
|
|
377
|
+
|
|
378
|
+
# Read the delta encoding section - RFC 3284 Section 4.3
|
|
379
|
+
delta_data = stream.read(window.delta_encoding_length)
|
|
380
|
+
if len(delta_data) != window.delta_encoding_length:
|
|
381
|
+
raise err_unexpected_eof("delta encoding", window.delta_encoding_length - len(delta_data))
|
|
382
|
+
|
|
383
|
+
# Parse the delta encoding according to RFC 3284 Section 4.3
|
|
384
|
+
delta_stream = io.BytesIO(delta_data)
|
|
385
|
+
|
|
386
|
+
# 1. Length of the target window
|
|
387
|
+
window.target_window_length = read_varint(delta_stream)
|
|
388
|
+
|
|
389
|
+
# 2. Delta_Indicator byte
|
|
390
|
+
delta_indicator_data = delta_stream.read(1)
|
|
391
|
+
if not delta_indicator_data:
|
|
392
|
+
raise err_unexpected_eof("delta indicator", 1)
|
|
393
|
+
window.delta_indicator = delta_indicator_data[0]
|
|
394
|
+
|
|
395
|
+
# 3. Length of data for ADDs and RUNs
|
|
396
|
+
window.data_section_length = read_varint(delta_stream)
|
|
397
|
+
|
|
398
|
+
# 4. Length of instructions section
|
|
399
|
+
window.instruction_section_length = read_varint(delta_stream)
|
|
400
|
+
|
|
401
|
+
# 5. Length of addresses for COPYs
|
|
402
|
+
window.address_section_length = read_varint(delta_stream)
|
|
403
|
+
|
|
404
|
+
# Handle VCD_ADLER32 extension - checksum comes AFTER section lengths but BEFORE data sections
|
|
405
|
+
if indicator & VCD_ADLER32:
|
|
406
|
+
window.has_checksum = True
|
|
407
|
+
# Read the 4-byte checksum from the delta encoding data
|
|
408
|
+
checksum_bytes = delta_stream.read(4)
|
|
409
|
+
if len(checksum_bytes) != 4:
|
|
410
|
+
raise err_unexpected_eof("Adler32 checksum", 4 - len(checksum_bytes))
|
|
411
|
+
# Convert to uint32 (big-endian)
|
|
412
|
+
window.checksum = (
|
|
413
|
+
(checksum_bytes[0] << 24) |
|
|
414
|
+
(checksum_bytes[1] << 16) |
|
|
415
|
+
(checksum_bytes[2] << 8) |
|
|
416
|
+
checksum_bytes[3]
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# 6. Data section for ADDs and RUNs
|
|
420
|
+
window.data_section = delta_stream.read(window.data_section_length)
|
|
421
|
+
if len(window.data_section) != window.data_section_length:
|
|
422
|
+
raise err_unexpected_eof("data section", window.data_section_length - len(window.data_section))
|
|
423
|
+
|
|
424
|
+
# 7. Instructions and sizes section
|
|
425
|
+
window.instruction_section = delta_stream.read(window.instruction_section_length)
|
|
426
|
+
if len(window.instruction_section) != window.instruction_section_length:
|
|
427
|
+
raise err_unexpected_eof("instruction section", window.instruction_section_length - len(window.instruction_section))
|
|
428
|
+
|
|
429
|
+
# 8. Addresses section for COPYs
|
|
430
|
+
if window.address_section_length > 0:
|
|
431
|
+
window.address_section = delta_stream.read(window.address_section_length)
|
|
432
|
+
if len(window.address_section) != window.address_section_length:
|
|
433
|
+
raise err_unexpected_eof("address section", window.address_section_length - len(window.address_section))
|
|
434
|
+
|
|
435
|
+
return window
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""VCDIFF exception classes"""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class VCDIFFError(Exception):
|
|
5
|
+
"""Base exception for VCDIFF operations"""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidMagicError(VCDIFFError):
|
|
10
|
+
"""Invalid VCDIFF magic bytes"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InvalidVersionError(VCDIFFError):
|
|
15
|
+
"""Unsupported VCDIFF version"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InvalidFormatError(VCDIFFError):
|
|
20
|
+
"""Invalid VCDIFF format"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CorruptedDataError(VCDIFFError):
|
|
25
|
+
"""Corrupted VCDIFF data"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InvalidChecksumError(VCDIFFError):
|
|
30
|
+
"""Invalid checksum"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def err_unexpected_eof(context: str, bytes_needed: int) -> VCDIFFError:
|
|
35
|
+
"""Create unexpected EOF error with context"""
|
|
36
|
+
return VCDIFFError(f"unexpected EOF while reading {context}: need {bytes_needed} bytes")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def err_data_overrun(instruction: str, offset: int, needed: int, available: int) -> VCDIFFError:
|
|
40
|
+
"""Create data overrun error"""
|
|
41
|
+
return VCDIFFError(
|
|
42
|
+
f"{instruction} instruction at offset {offset} requires {needed} bytes "
|
|
43
|
+
f"but only {available} available in data section"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def err_invalid_value(field: str, offset: int, value, reason: str) -> VCDIFFError:
|
|
48
|
+
"""Create invalid value error"""
|
|
49
|
+
return VCDIFFError(f"invalid {field} at offset {offset}: value {value}, {reason}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def err_out_of_bounds(instruction: str, address: int, size: int, max_bound: int) -> VCDIFFError:
|
|
53
|
+
"""Create out of bounds error"""
|
|
54
|
+
return VCDIFFError(
|
|
55
|
+
f"{instruction} instruction address {address} + size {size} exceeds bounds (max {max_bound})"
|
|
56
|
+
)
|