///|
pub(all) enum PatchKind {
PatchAdd
PatchRemove
PatchReplace
} derive(Eq, Debug)
///|
pub struct PatchHunk {
kind_value : PatchKind
address_value : UInt64
before_value : Bytes
after_value : Bytes
} derive(Eq, Debug)
///|
pub struct ImagePatch {
hunk_values : Array[PatchHunk]
old_entry_value : UInt64?
new_entry_value : UInt64?
added_bytes_value : Int
removed_bytes_value : Int
changed_bytes_value : Int
} derive(Eq, Debug)
///|
fn patch_error(message : String, address : UInt64) -> FirmwareError {
FirmwareError::new(
IntegrityViolation,
message + " at address " + address.to_string(radix=16),
SourcePosition::line(0),
)
}
///|
fn difference_patch_kind(kind : ByteDifferenceKind) -> PatchKind {
match kind {
ByteAdded => PatchAdd
ByteRemoved => PatchRemove
ByteChanged => PatchReplace
}
}
///|
fn optional_bytes(values : Array[Byte]) -> Bytes {
Bytes::makei(values.length(), index => values[index])
}
///|
fn make_patch_hunk(
kind : PatchKind,
address : UInt64,
before : Array[Byte],
after : Array[Byte],
) -> PatchHunk {
{
kind_value: kind,
address_value: address,
before_value: optional_bytes(before),
after_value: optional_bytes(after),
}
}
///|
fn append_difference_bytes(
difference : ByteDifference,
before : Array[Byte],
after : Array[Byte],
) -> Unit {
match difference.left() {
Some(value) => before.push(value)
None => ()
}
match difference.right() {
Some(value) => after.push(value)
None => ()
}
}
///|
/// Build ordered contiguous patch hunks from exact sparse-byte differences.
pub fn create_image_patch(
source : FirmwareImage,
target : FirmwareImage,
max_differences? : Int = 10000,
) -> Result[ImagePatch, FirmwareError] {
let comparison = match compare_images(source, target, max_differences~) {
Ok(value) => value
Err(error) => return Err(error)
}
let differences = comparison.differences()
let hunks : Array[PatchHunk] = []
if differences.length() > 0 {
let mut current_kind = difference_patch_kind(differences[0].kind())
let mut current_address = differences[0].address()
let mut previous_address = current_address
let mut before : Array[Byte] = []
let mut after : Array[Byte] = []
append_difference_bytes(differences[0], before, after)
for index = 1; index < differences.length(); index = index + 1 {
let difference = differences[index]
let kind = difference_patch_kind(difference.kind())
if kind != current_kind || difference.address() != previous_address + 1UL {
hunks.push(
make_patch_hunk(current_kind, current_address, before, after),
)
current_kind = kind
current_address = difference.address()
before = []
after = []
}
append_difference_bytes(difference, before, after)
previous_address = difference.address()
}
hunks.push(make_patch_hunk(current_kind, current_address, before, after))
}
Ok({
hunk_values: hunks,
old_entry_value: source.entry_point(),
new_entry_value: target.entry_point(),
added_bytes_value: comparison.added_count(),
removed_bytes_value: comparison.removed_count(),
changed_bytes_value: comparison.changed_count(),
})
}
///|
pub fn PatchHunk::kind(self : PatchHunk) -> PatchKind {
self.kind_value
}
///|
pub fn PatchHunk::address(self : PatchHunk) -> UInt64 {
self.address_value
}
///|
pub fn PatchHunk::before(self : PatchHunk) -> Bytes {
Bytes::makei(self.before_value.length(), index => self.before_value[index])
}
///|
pub fn PatchHunk::after(self : PatchHunk) -> Bytes {
Bytes::makei(self.after_value.length(), index => self.after_value[index])
}
///|
pub fn PatchHunk::length(self : PatchHunk) -> Int {
match self.kind_value {
PatchAdd => self.after_value.length()
PatchRemove | PatchReplace => self.before_value.length()
}
}
///|
pub fn ImagePatch::hunks(self : ImagePatch) -> Array[PatchHunk] {
self.hunk_values.copy()
}
///|
pub fn ImagePatch::old_entry(self : ImagePatch) -> UInt64? {
self.old_entry_value
}
///|
pub fn ImagePatch::new_entry(self : ImagePatch) -> UInt64? {
self.new_entry_value
}
///|
pub fn ImagePatch::added_bytes(self : ImagePatch) -> Int {
self.added_bytes_value
}
///|
pub fn ImagePatch::removed_bytes(self : ImagePatch) -> Int {
self.removed_bytes_value
}
///|
pub fn ImagePatch::changed_bytes(self : ImagePatch) -> Int {
self.changed_bytes_value
}
///|
fn verify_patch_bytes(
image : FirmwareImage,
hunk : PatchHunk,
) -> Result[Unit, FirmwareError] {
match hunk.kind() {
PatchAdd =>
for offset = 0; offset < hunk.after_value.length(); offset = offset + 1 {
let address = hunk.address() + offset.to_uint64()
if image.byte_at(address) is Some(_) {
return Err(
patch_error("patch expected an absent source byte", address),
)
}
}
PatchRemove | PatchReplace =>
for offset = 0; offset < hunk.before_value.length(); offset = offset + 1 {
let address = hunk.address() + offset.to_uint64()
if image.byte_at(address) != Some(hunk.before_value[offset]) {
return Err(
patch_error("patch source byte does not match evidence", address),
)
}
}
}
Ok(())
}
///|
/// Apply a patch only when all recorded source bytes and entry metadata match.
pub fn ImagePatch::apply(
self : ImagePatch,
source : FirmwareImage,
) -> Result[FirmwareImage, FirmwareError] {
if source.entry_point() != self.old_entry_value {
return Err(
patch_error("patch source entry point does not match evidence", 0UL),
)
}
let mut current = source
for hunk in self.hunk_values {
match verify_patch_bytes(current, hunk) {
Ok(_) => ()
Err(error) => return Err(error)
}
match hunk.kind() {
PatchAdd | PatchReplace =>
match current.write(hunk.address(), hunk.after()) {
Ok(value) => current = value
Err(error) => return Err(error)
}
PatchRemove => {
let end = hunk.address() + hunk.before_value.length().to_uint64()
match current.erase(hunk.address(), end) {
Ok(value) => current = value
Err(error) => return Err(error)
}
}
}
}
FirmwareImage::from_chunks(
image_as_chunks(current),
entry_point=self.new_entry_value,
)
}