///|
fn append_bytes(output : Array[Byte], bytes : Bytes) -> Unit {
for byte in bytes {
output.push(byte)
}
}
///|
fn append_run(output : Array[Byte], byte : Byte, count : Int) -> Unit {
for i = 0; i < count; i = i + 1 {
output.push(byte)
}
}
///|
fn checked_target_growth(
window : ParsedWindow,
current : Int,
amount : Int,
) -> Unit raise VcdiffError {
if amount < 0 || current > window.target_size - amount {
raise LengthMismatch(
offset=window.instruction_offset,
expected=window.target_size,
actual=current + amount,
)
}
}
///|
fn resolve_instruction_size(
instruction : CodeInstruction,
instruction_cursor : ByteCursor,
) -> Int raise VcdiffError {
match instruction.kind {
Noop => 0
_ =>
if instruction.size == 0 {
read_varint(instruction_cursor)
} else {
instruction.size
}
}
}
///|
fn copy_from_dictionary(
window : ParsedWindow,
dictionary : Bytes,
target : Array[Byte],
address : Int,
size : Int,
) -> Unit raise VcdiffError {
checked_target_growth(window, target.length(), size)
let source_size = dictionary.length()
if address < source_size {
if size > source_size - address {
raise InvalidAddress(
offset=window.address_offset,
address~,
limit=source_size - size,
)
}
for index = 0; index < size; index = index + 1 {
target.push(dictionary[address + index])
}
} else {
let target_address = address - source_size
if target_address < 0 || target_address >= target.length() {
raise InvalidAddress(
offset=window.address_offset,
address~,
limit=source_size + target.length() - 1,
)
}
for index = 0; index < size; index = index + 1 {
let read_index = target_address + index
if read_index >= target.length() {
raise InvalidAddress(
offset=window.address_offset,
address=address + index,
limit=source_size + target.length() - 1,
)
}
target.push(target[read_index])
}
}
}
///|
fn execute_instruction(
window : ParsedWindow,
instruction : CodeInstruction,
instruction_cursor : ByteCursor,
data_cursor : ByteCursor,
address_cursor : ByteCursor,
address_cache : AddressCache,
dictionary : Bytes,
target : Array[Byte],
) -> Bool raise VcdiffError {
match instruction.kind {
Noop => false
Copy => {
let size = resolve_instruction_size(instruction, instruction_cursor)
let here = dictionary.length() + target.length()
let address = address_cache.decode(address_cursor, here, instruction.mode)
copy_from_dictionary(window, dictionary, target, address, size)
true
}
Add => {
let size = resolve_instruction_size(instruction, instruction_cursor)
checked_target_growth(window, target.length(), size)
append_bytes(target, data_cursor.read_exact(size))
true
}
Run => {
let size = resolve_instruction_size(instruction, instruction_cursor)
checked_target_growth(window, target.length(), size)
let byte = data_cursor.read_byte()
append_run(target, byte, size)
true
}
}
}
///|
fn decode_window(
window : ParsedWindow,
dictionary : Bytes,
limits : DecodeLimits,
table : Array[CodeTableEntry],
) -> Bytes raise VcdiffError {
if window.target_size > limits.max_window_size {
raise ResourceLimit(
offset=window.offset,
resource="window_size",
limit=limits.max_window_size,
)
}
let instruction_cursor = ByteCursor::with_base(
window.instructions,
window.instruction_offset,
)
let data_cursor = ByteCursor::with_base(window.data, window.data_offset)
let address_cursor = ByteCursor::with_base(
window.addresses,
window.address_offset,
)
let address_cache = AddressCache::new()
let target : Array[Byte] = []
let mut instruction_count = 0
while instruction_cursor.remaining() > 0 {
let code_offset = instruction_cursor.offset()
let code = instruction_cursor.read_byte().to_int()
let entry = table[code]
if execute_instruction(
window,
entry.first,
instruction_cursor,
data_cursor,
address_cursor,
address_cache,
dictionary,
target,
) {
instruction_count += 1
}
if execute_instruction(
window,
entry.second,
instruction_cursor,
data_cursor,
address_cursor,
address_cache,
dictionary,
target,
) {
instruction_count += 1
}
if instruction_count > limits.max_instructions_per_window {
raise ResourceLimit(
offset=code_offset,
resource="instructions_per_window",
limit=limits.max_instructions_per_window,
)
}
}
if target.length() != window.target_size {
raise LengthMismatch(
offset=window.instruction_offset + window.instructions.length(),
expected=window.target_size,
actual=target.length(),
)
}
if data_cursor.remaining() != 0 {
raise LengthMismatch(
offset=data_cursor.offset(),
expected=window.data.length() - data_cursor.remaining(),
actual=window.data.length(),
)
}
if address_cursor.remaining() != 0 {
raise LengthMismatch(
offset=address_cursor.offset(),
expected=window.addresses.length() - address_cursor.remaining(),
actual=window.addresses.length(),
)
}
Bytes::from_array(target)
}
///|
fn checked_dictionary_slice(
bytes : Bytes,
position : Int,
size : Int,
offset : Int,
) -> Bytes raise VcdiffError {
if size > bytes.length() || position > bytes.length() - size {
raise InvalidWindow(
offset~,
reason="source segment is outside the referenced file",
)
}
bytes[position:position + size].to_owned()
}
///|
fn select_dictionary(
source : Bytes,
decoded_target : Array[Byte],
window : ParsedWindow,
) -> Bytes raise VcdiffError {
if window.indicator == 0 {
return b""
}
if window.indicator == VCD_SOURCE {
if source.length() == 0 && window.source_size > 0 {
raise MissingSource(offset=window.offset)
}
return checked_dictionary_slice(
source,
window.source_position,
window.source_size,
window.offset,
)
}
let previous_target = Bytes::from_array(decoded_target)
checked_dictionary_slice(
previous_target,
window.source_position,
window.source_size,
window.offset,
)
}
///|
/// Decodes an RFC 3284 delta using explicit resource limits.
///
/// ADD, RUN, and COPY instructions use the RFC default code table. COPY
/// addresses may use SELF, HERE, NEAR, or SAME modes.
pub fn decode(
source : Bytes,
delta : Bytes,
limits : DecodeLimits,
) -> Bytes raise VcdiffError {
let parsed = parse_delta(delta)
if parsed.windows.length() > limits.max_windows {
raise ResourceLimit(
offset=parsed.header.length,
resource="windows",
limit=limits.max_windows,
)
}
let output : Array[Byte] = []
let table = default_code_table()
for window in parsed.windows {
let dictionary = select_dictionary(source, output, window)
let bytes = decode_window(window, dictionary, limits, table)
if output.length() > limits.max_output_size - bytes.length() {
raise ResourceLimit(
offset=window.offset,
resource="output_size",
limit=limits.max_output_size,
)
}
append_bytes(output, bytes)
}
Bytes::from_array(output)
}