From 5e4913102aee5d8450727754ef3e32f9c201a7e9 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Thu, 25 Apr 2019 20:03:43 +0930 Subject: [PATCH] Added CRC to patch format. Tweaked the main format documentation. Added a CRC test. Added a DEZFormatException for error states. --- .../au/notzed/dez/demo/DEZPrinter.java | 38 ++--- .../au/notzed/dez/demo/PrintEncoder.java | 3 +- .../au/notzed/dez/ByteDeltaEncoder.java | 21 ++- .../classes/au/notzed/dez/DEZDecoder.java | 95 +++++++++---- .../classes/au/notzed/dez/DEZEncoder.java | 39 +++--- .../classes/au/notzed/dez/DEZFormat.java | 132 +++++++++++------- .../au/notzed/dez/DEZFormatException.java | 30 ++++ .../tests/au/notzed/dez/DEZEncoderTest.java | 92 +++++++----- src/notzed.dez/tests/au/notzed/dez/DEZIT.java | 25 ++++ 9 files changed, 324 insertions(+), 151 deletions(-) create mode 100644 src/notzed.dez/classes/au/notzed/dez/DEZFormatException.java diff --git a/src/notzed.dez.demo/classes/au/notzed/dez/demo/DEZPrinter.java b/src/notzed.dez.demo/classes/au/notzed/dez/demo/DEZPrinter.java index e64833e..4f3d61e 100644 --- a/src/notzed.dez.demo/classes/au/notzed/dez/demo/DEZPrinter.java +++ b/src/notzed.dez.demo/classes/au/notzed/dez/demo/DEZPrinter.java @@ -29,11 +29,11 @@ import java.io.PrintStream; public class DEZPrinter implements DEZFormat { private final byte[] patch; - private int pi, si; + private int pi; // Recent address cache. TODO: These can be combined private final int[] matchAddr = new int[64]; private int matchNext = 0; - private int[] recentAddr = new int[32]; + private final int[] recentAddr = new int[32]; private int recentNext = 0; // some statistics. private Stats copy = new Stats(); @@ -44,6 +44,16 @@ public class DEZPrinter implements DEZFormat { this.patch = patch; } + private int decodeInt32() { + int v = (patch[pi++] & 0xff) << 24; + + v |= (patch[pi++] & 0xff) << 16; + v |= (patch[pi++] & 0xff) << 8; + v |= (patch[pi++] & 0xff); + + return v; + } + private int decodeInt() { int v = 0; byte b; @@ -92,7 +102,7 @@ public class DEZPrinter implements DEZFormat { /** * Dumps the patch details to stdout. - * + *

*/ public void print() { DEZPrinter.this.print(System.out); @@ -101,28 +111,21 @@ public class DEZPrinter implements DEZFormat { public void print(PrintStream out) { int ti = 0; int flags; - int smallest = 4; + int smallest; pi = 0; - si = 0; - // 'decode' magic - out.printf("magic: %c%c%c%c\n", patch[0], patch[1], patch[2], patch[3]); + // Read header + out.printf("magic: %c%c%c%c\n", (char) patch[0], (char) patch[1], (char) patch[2], (char) patch[3]); pi += 4; - // decode flags - flags = patch[pi++]; - out.printf("flags: %02x\n", flags & 0xff); - - if ((flags & DEZ_SMALLEST) != 0) - smallest = decodeInt(); + flags = decodeInt(); + out.printf("flags: %08x\n", flags); + smallest = decodeInt(); out.printf("smallest: %d\n", smallest); - // get sizes int sourceSize = decodeInt(); int targetSize = decodeInt(); - int oc = 0; - out.printf("source: %d\ntarget: %d\n", sourceSize, targetSize, patch.length); while (ti < targetSize) { @@ -224,6 +227,8 @@ public class DEZPrinter implements DEZFormat { if (ti != targetSize) throw new ArrayIndexOutOfBoundsException(String.format("Target short write %d != %d", ti, targetSize)); + int crc = decodeInt32(); + out.printf("summary\n"); out.printf(" copy: %s\n", copy); out.printf(" add: %s\n", add); @@ -231,6 +236,7 @@ public class DEZPrinter implements DEZFormat { out.printf("patch: %d\n", pi); out.printf("sorce: %d\n", sourceSize); out.printf("targt: %d\n", targetSize); + out.printf(" crc: %08x\n", crc); } } diff --git a/src/notzed.dez.demo/classes/au/notzed/dez/demo/PrintEncoder.java b/src/notzed.dez.demo/classes/au/notzed/dez/demo/PrintEncoder.java index 7f398bf..704895d 100644 --- a/src/notzed.dez.demo/classes/au/notzed/dez/demo/PrintEncoder.java +++ b/src/notzed.dez.demo/classes/au/notzed/dez/demo/PrintEncoder.java @@ -46,7 +46,8 @@ public class PrintEncoder implements ByteDeltaEncoder { } @Override - public byte[] toPatch() { + public byte[] toPatch(int crc) { + System.out.printf("crc = %08x\n", crc); return new byte[0]; } } diff --git a/src/notzed.dez/classes/au/notzed/dez/ByteDeltaEncoder.java b/src/notzed.dez/classes/au/notzed/dez/ByteDeltaEncoder.java index a3546a1..384a270 100644 --- a/src/notzed.dez/classes/au/notzed/dez/ByteDeltaEncoder.java +++ b/src/notzed.dez/classes/au/notzed/dez/ByteDeltaEncoder.java @@ -16,6 +16,8 @@ */ package au.notzed.dez; +import java.util.zip.CRC32; + /** * The interface for encoding a delta. *

@@ -25,9 +27,12 @@ public interface ByteDeltaEncoder { /** * Initialises creating a new patch. + *

+ * Source is the data which will be common to both the encoder and decoder, + * target is only known by the encoder. * - * @param sourceSize - * @param targetSize + * @param sourceSize Size of source data. + * @param targetSize Size of target data. */ public void init(int sourceSize, int targetSize); @@ -59,9 +64,10 @@ public interface ByteDeltaEncoder { /** * Retrieves the patch. * + * @param targetCRC32 CRC32 of target data. * @return */ - public byte[] toPatch(); + public byte[] toPatch(int targetCRC32); /** * Creates a delta from a matcher and writes it to an encoder. @@ -73,12 +79,12 @@ public interface ByteDeltaEncoder { public static byte[] toDiff(ByteMatcher matcher, ByteDeltaEncoder enc) { byte[] source = matcher.getSource(); byte[] target = matcher.getTarget(); - - enc.init(source.length, target.length); - + CRC32 crc32 = new CRC32(); int targetEnd = 0; int state; + enc.init(source.length, target.length); + while ((state = matcher.nextMatch()) != ByteMatcher.EOF) { int toff = matcher.getTargetOffset(); int slength = matcher.getLength(); @@ -96,6 +102,7 @@ public interface ByteDeltaEncoder { if (targetEnd != target.length) enc.add(target, targetEnd, target.length - targetEnd); - return enc.toPatch(); + crc32.update(target); + return enc.toPatch((int) crc32.getValue()); } } diff --git a/src/notzed.dez/classes/au/notzed/dez/DEZDecoder.java b/src/notzed.dez/classes/au/notzed/dez/DEZDecoder.java index 19549db..0af563a 100644 --- a/src/notzed.dez/classes/au/notzed/dez/DEZDecoder.java +++ b/src/notzed.dez/classes/au/notzed/dez/DEZDecoder.java @@ -18,30 +18,73 @@ package au.notzed.dez; import java.io.PrintStream; import java.util.Arrays; +import java.util.zip.CRC32; /** * Decodes a DEZ patch. *

- * @see au.notzed.dez.DEZEncoder + * Typically one would use {@link DEZFormat#decode} instead of this class. + * + * @see DEZEncoder + * @see DEZFormat */ public class DEZDecoder implements DEZFormat { - final static boolean dump = false; + private final static boolean dump = false; + /** + * All source bytes. + */ private final byte[] source; + /** + * All patch bytes. + */ private final byte[] patch; - private int pi, si; - // recent address cache. TODO: These can be combined - private int[] matchAddr = new int[64]; + /** + * Patch data index. + */ + private int pi; + /** + * Recent exact address cache. + */ + private final int[] matchAddr = new int[64]; + /** + * Next matchAddr index. + */ private int matchNext = 0; - private int[] recentAddr = new int[32]; + /** + * Recent relative address cache. + */ + private final int[] recentAddr = new int[32]; + /** + * next recentAddr index. + */ private int recentNext = 0; + /** + * Create a new decoder. + *

+ * The decoder will transform source using patch to create + * the original target sequence. + * + * @param source Must match the source parameter used when generating the patch. + * @param patch Patch byte stream. + */ public DEZDecoder(byte[] source, byte[] patch) { this.source = source; this.patch = patch; } + private int decodeInt32() { + int v = (patch[pi++] & 0xff) << 24; + + v |= (patch[pi++] & 0xff) << 16; + v |= (patch[pi++] & 0xff) << 8; + v |= (patch[pi++] & 0xff); + + return v; + } + private int decodeInt() { int v = 0; byte b; @@ -89,48 +132,43 @@ public class DEZDecoder implements DEZFormat { if ((op & 0x20) != 0) d = -d; addr = recentAddr[op & 31] + d; - //updateAddr(addr); } } else { // Normal address addr = decodeInt(); - //updateAddr(addr); } updateAddr(addr); - + return addr; } /** + * Perorm decoding. + *

* Recreates the original target data from the source and patch. * - * @return + * @return The decoded data. + * @throws au.notzed.dez.DEZFormatException If the patch is invalid or + * the crc check fails. */ - public byte[] decode() { + public byte[] decode() throws DEZFormatException { int ti = 0; - int smallest = 4; - int flags; - + CRC32 crc32 = new CRC32(); PrintStream out = dump ? System.out : null; - pi = 0; - si = 0; - // 'decode' magic - //out.printf("magic: %c%c%c%c\n", patch[0] & 0xff, patch[1] & 0xff, patch[2] & 0xff, patch[3] & 0xff); + // Read header + if (!Arrays.equals(MAGIC, pi, 4, patch, 0, 4)) + throw new DEZFormatException("Magic missing"); + pi += 4; - // decode flags - flags = patch[pi++]; - if ((flags & DEZ_SMALLEST) != 0) - smallest = decodeInt(); - // get sizes + int flags = decodeInt(); + int smallest = decodeInt(); int sourceSize = decodeInt(); int targetSize = decodeInt(); - - int oc = 0; - byte[] target = new byte[targetSize]; + // Read opcodes until we reach the target size while (ti < targetSize) { int op = patch[pi++] & 0xff; @@ -221,6 +259,11 @@ public class DEZDecoder implements DEZFormat { } } + int crc = decodeInt32(); + crc32.update(target); + if ((int) (crc32.getValue()) != crc) + throw new DEZFormatException("CRC32 Mismatch"); + return target; } diff --git a/src/notzed.dez/classes/au/notzed/dez/DEZEncoder.java b/src/notzed.dez/classes/au/notzed/dez/DEZEncoder.java index a298843..d65777b 100644 --- a/src/notzed.dez/classes/au/notzed/dez/DEZEncoder.java +++ b/src/notzed.dez/classes/au/notzed/dez/DEZEncoder.java @@ -30,25 +30,25 @@ import java.util.Arrays; */ public class DEZEncoder implements ByteDeltaEncoder, DEZFormat { - final static boolean dump = false; + private final static boolean dump = false; private final ByteArrayOutputStream patch = new ByteArrayOutputStream(); - int here; - int header; - int sourceSize, targetSize; + private int here; + private int header; + private int sourceSize, targetSize; // Configurable parameters. final int smallest; // Last operation information - int last = -1; - byte[] lastData; - int lastOff; - int lastLen; - int lastAddr; + private int last = -1; + private byte[] lastData; + private int lastOff; + private int lastLen; + private int lastAddr; // Recent address cache. These can be combined? - private int[] matchAddr = new int[64]; + private final int[] matchAddr = new int[64]; private int matchNext = 0; - private int[] recentAddr = new int[32]; + private final int[] recentAddr = new int[32]; private int recentNext = 0; /** @@ -63,7 +63,7 @@ public class DEZEncoder implements ByteDeltaEncoder, DEZFormat { /** * Creates a new encoder with a smallest copy size. * - * @param smallest Sets the smallest copy allowed. + * @param smallest Sets the smallest copy allowed. It must be ≥ 4. */ public DEZEncoder(int smallest) { this.smallest = smallest; @@ -75,11 +75,9 @@ public class DEZEncoder implements ByteDeltaEncoder, DEZFormat { patch.reset(); patch.write(MAGIC); - if (smallest != 4) - flags |= DEZ_SMALLEST; - patch.write(flags); - if ((flags & DEZ_SMALLEST) != 0) - encodeInt(smallest); + + encodeInt(flags); + encodeInt(smallest); encodeInt(sourceSize); encodeInt(targetSize); header = patch.size(); @@ -307,12 +305,17 @@ public class DEZEncoder implements ByteDeltaEncoder, DEZFormat { here += len; } - public byte[] toPatch() { + public byte[] toPatch(int targetCRC32) { flush(); if (here != targetSize) throw new RuntimeException("Insufficiant data"); // FIXME: better exception + patch.write(targetCRC32 >> 24); + patch.write(targetCRC32 >> 16); + patch.write(targetCRC32 >> 8); + patch.write(targetCRC32); + return patch.toByteArray(); } } diff --git a/src/notzed.dez/classes/au/notzed/dez/DEZFormat.java b/src/notzed.dez/classes/au/notzed/dez/DEZFormat.java index 56f6df8..66f653d 100644 --- a/src/notzed.dez/classes/au/notzed/dez/DEZFormat.java +++ b/src/notzed.dez/classes/au/notzed/dez/DEZFormat.java @@ -19,58 +19,94 @@ package au.notzed.dez; /** * Defines constants used in the DEZ1 format. *

- *

Header

+ *

Data Types

+ *

+ * The patch format is a byte stream consisting of a header and a sequence of + * bytes, followed by a crc code. *

- *  magic: 'D' 'E' 'Z' '1'
- *  flags: one byte
- *         00000001               Includes 'smallest' value setting
- *    [smallest: one integer]     Indicates the smallest value in any copy.  The default is 4.
- *  source size: one integer
- *  target size: one integer
- *  instructions follow directly
- *  ?? no epilogue defined ??
+ *  Ciiiiiiii   integer    A multi-byte integer.  C is a continue bit.  The
+ *                         bits are in big-endian order.
+ *  aaaaaaaaa   address    An encoded address.  See below.
+ *  ddddddddd   data       A data byte.
+ *  XXXX        4-bytes    4 sequential bytes.
  * 
- *

Instruction stream

+ * A trailing (*) indicates zero or more instances. + * A trailing (+) indicates a self-describing encoding of one or more bytes. + * A trailing (?) indicates an implied encoding of one or more bytes. + *

Patch Format

*
- * Dual commands:
+ * HEADER:
+ *  XXXX        magic      4 bytes ASCII, 'DEZ1'.
+ *  Ciiiiiii+   flags      None are defined.
+ *  Ciiiiiii+   smallest   Smallest copy offset.
+ *  Ciiiiiii+   source     Source data size.
+ *  Ciiiiiii+   target     Target data size.
  *
- * 00lllccc dddddddd* aaaaaaaa* - add 1-8 then copy (0-7)+smallest
- * 01cccCCC aaaaaaaa* aaaaaaaa* - copy (0-7)+smallest then copy (0-7)+smallest
+ * DATA:
+ *  IIIIIIII*   commands   Zero or more instruction opcodes and operands
  *
- * Single commands:
+ * CRC:
+ *  XXXX        crc        4-byte CRC of target bytes, network order.
+ * 
+ *

Instruction stream

+ * Instructions can perform either a single + * operation or two operations with limited ranges. + *

+ * Single commands perform one operation. They are a 7-bit value with the + * 8th bit set. A range test defines the operation. + *

  *
- * 1nnnnnnn
+ * 1nnnnnnn:
  *
- *  n is interpreted as a number of ranges, inclusive:
- * 000 ... 099        aaaaaaaa*           - copy (0-99)+smallest
- * 100 ... 123        dddddddd*           - add 1-24
- *         124        Ciiiiiii* aaaaaaaa* - copy i + 100 + smallest
- *         125        Ciiiiiii* dddddddd* - add i+24+1
- *         126        Ciiiiiii* dddddddd  - run length of i+3
- *         127                            - reserved
+ * [000 099] aaaaaaaa+           COPY Copy (n+smallest) from the address.
+ * [100 123] dddddddd?           ADD  Append (n-99) from the patch.
+ * [    124] Ciiiiiii+ aaaaaaaa+ COPY Copy (i+100+smallest) from the address.
+ * [    125] Ciiiiiii+ dddddddd? ADD  Append (i+12+1) from the patch.
+ * [    126] Ciiiiiii+ dddddddd  RUN  Append (i+3) copies of the data byte.
+ * [    127]                          Reserved.
  * 
+ * The split point between immediate-length COPY and ADD can be altered via + * modifying {@link #OP_SINGLE_SPLIT}, but it will create an incompatible + * patch. *

- * Integers are encoded as a compacted big-endian sequence - * with 7 bits per byte. Leading zero septets are discarded. - * The MSB of each byte is a continue bit which indicates - * another 7 bits are to be read. - *

+ * Dual command perform two operations with a single opcode. The lengths + * are encoded as immediate values and have a limited range of 3 bits each. + *

+ *
+ * 00lllccc dddddddd? aaaaaaaa+   ADD+COPY
+ *
+ *            Append the next (lll+1) data bytes (dddddddd?) from the patch.
+ *            Then copy (ccc+smallest) bytes from the address (aaaaaaaa+).
+ *
+ * 01cccCCC aaaaaaaa+ AAAAAAAA+   COPY+COPY
+ *
+ *            Copy (ccc+smallest) bytes from the first address (aaaaaaaa+).
+ *            Then copy (CCC+smallest) bytes from the second address (AAAAAAAA+).
+ * 
*

Addresses

+ * Addresses are a logical pointer which refers to a location within + * a buffer consisting of the concatenated source and target buffers. The + * encoder only referneces addresses that have already been seen so they + * will also be visible to the decoder. *

- * All addresses (aaaaaaaa*) above are encoded using a rolling lookup table. - * The first byte of each address indicates whether a lookup-table specific - * address is used or an absolute address. + * A small address cache is maintained in both the encoder and decoder to + * the last N recently accessed addresses. This can be used to encode + * the address with fewer bytes. + *

+ * Addresses can be encoded in three ways. + *

    + *
  1. An exact reference to an address cache entry. + *
  2. A reference to an address cache entry with a signed offset. + *
  3. An absolute address. + *
+ * The first byte determines the address encoding. *
+ * 
  *  00nnnnnn           - use address 'n' exactly.
- *  01Smmmmm iiiiiiii* - use address 'm' plus or minus 'i'. S={ 0: plus, 1: minus }
- *  1iiiiiii Ciiiiiii* - use address 'i' absolute.  Always at least 2 bytes.
+ *  01Snnnnn Ciiiiiii+ - use address 'n' plus (S=0) or minus (S=1) 'i'
+ *  1iiiiiii Ciiiiiii+ - use address 'i' absolute.  Always at least 2 bytes.
  * 
*

- * Given that 7-bit absolute addresses will rarely occur in typical data all - * absolute addresses use at least 2x bytes. This allows 2+ byte values to be encoded - * as compactly as they would otherwise be and leaves the lower 128 values for other - * encoding options. - *

* The address table is 64 elements long but the relative instruction can only * reference the most recent 32 elements. It is updated each time an address is * encoded or decoded in a simple rolling manner. @@ -84,9 +120,10 @@ package au.notzed.dez; * is encoded implicitly in the opcode via an index. *

* Next the encoder finds the nearest match from the recent table as absolute - * distance. The encoder may encode the address as a relative offset from - * a member of the table if the total is smaller than encoding the address absolutely. - *

+ * distance. The encoder will then encode the address as a relative offset from + * a member of the table if the total encoded length is shorter than encoding + * the address absolutely. + *

*/ public interface DEZFormat { @@ -95,11 +132,6 @@ public interface DEZFormat { */ public static final byte[] MAGIC = {'D', 'E', 'Z', '1'}; - /** - * Header flags indicating the smallest copy size is present in the header. - */ - public static final int DEZ_SMALLEST = 0x01; - /** * COPY select bit in dual opcode. */ @@ -167,20 +199,22 @@ public interface DEZFormat { * * @param source * @param target - * @return + * @return patch, the binary delta. */ public static byte[] encode(byte[] source, byte[] target) { return ByteDeltaEncoder.toDiff(new ByteMatcherHash(6, 4, source, 1, target), new DEZEncoder()); } /** - * Applies a DEZ-1 patch. + * Apply a DEZ-1 patch. * * @param source * @param patch - * @return + * @return target, the reconstructed data. + * @throws au.notzed.dez.DEZFormatException If the patch is not a + * compatible format, or the crc check failed. */ - public static byte[] decode(byte[] source, byte[] patch) { + public static byte[] decode(byte[] source, byte[] patch) throws DEZFormatException { return new au.notzed.dez.DEZDecoder(source, patch).decode(); } } diff --git a/src/notzed.dez/classes/au/notzed/dez/DEZFormatException.java b/src/notzed.dez/classes/au/notzed/dez/DEZFormatException.java new file mode 100644 index 0000000..c951dd3 --- /dev/null +++ b/src/notzed.dez/classes/au/notzed/dez/DEZFormatException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package au.notzed.dez; + +import java.io.IOException; + +/** + * + */ +public class DEZFormatException extends IOException { + + public DEZFormatException(String why) { + super(why); + } + +} diff --git a/src/notzed.dez/tests/au/notzed/dez/DEZEncoderTest.java b/src/notzed.dez/tests/au/notzed/dez/DEZEncoderTest.java index 4617698..14ae02b 100644 --- a/src/notzed.dez/tests/au/notzed/dez/DEZEncoderTest.java +++ b/src/notzed.dez/tests/au/notzed/dez/DEZEncoderTest.java @@ -57,14 +57,16 @@ public class DEZEncoderTest { instance.init(len, len); instance.copy(addr, len); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, (byte) len, (byte) len, - (byte) (128 + len - 4), 0 + (byte) (128 + len - 4), 0, + 0x01, 0x23, 0x45, 0x67 }); } @@ -79,14 +81,16 @@ public class DEZEncoderTest { instance.init(0, len); instance.add(data, off, len); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 0, (byte) len, - (byte) (128 + 100 + len - 1), 1, 2, 3, 4 + (byte) (128 + 100 + len - 1), 1, 2, 3, 4, + 0x01, 0x23, 0x45, 0x67 }); } @@ -100,16 +104,18 @@ public class DEZEncoderTest { instance.init(0, len); instance.run(b, len); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 0, (byte) len, (byte) (126 | 0x80), (byte) (len - 3), - b + b, + 0x01, 0x23, 0x45, 0x67 }); } @@ -123,17 +129,19 @@ public class DEZEncoderTest { instance.init(0, len); instance.run(b, len); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 0, (byte) ((len >> 7) | 0x80), (byte) (len & 127), (byte) (126 | 0x80), (byte) (len - 3), - b + b, + 0x01, 0x23, 0x45, 0x67 }); } @@ -147,18 +155,20 @@ public class DEZEncoderTest { instance.init(0, len); instance.run(b, len); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 0, (byte) ((len >> 7) | 0x80), (byte) (len & 127), (byte) (126 | 0x80), (byte) (((len - 3) >> 7) | 0x80), (byte) ((len - 3) & 0x7f), - b + b, + 0x01, 0x23, 0x45, 0x67 }); } @@ -173,15 +183,17 @@ public class DEZEncoderTest { instance.add(data, 0, 5); instance.add(data, 5, 3); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(patch, new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 0, (byte) len, (byte) (128 + 100 + 5 - 1), 1, 2, 3, 4, 5, - (byte) (128 + 100 + 3 - 1), 6, 7, 8 + (byte) (128 + 100 + 3 - 1), 6, 7, 8, + 0x01, 0x23, 0x45, 0x67 }); } @@ -196,15 +208,17 @@ public class DEZEncoderTest { instance.copy(0, 10); instance.copy(0, 10); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 16, (byte) len, (byte) ((10 - smallest) << 3 | (10 - smallest) | 0x40), - 0, 0 + 0, 0, + 0x01, 0x23, 0x45, 0x67 }, patch); } @@ -219,16 +233,17 @@ public class DEZEncoderTest { instance.copy(0, 10); instance.copy(0, 10); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], - DEZEncoder.DEZ_SMALLEST, + 0, (byte) smallest, 16, (byte) len, (byte) ((10 - smallest) << 3 | (10 - smallest) | 0x40), - 0, 0 + 0, 0, + 0x01, 0x23, 0x45, 0x67 }, patch); } @@ -248,17 +263,19 @@ public class DEZEncoderTest { instance.copy(10, 10); instance.copy(8, 10); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 16, (byte) len, (byte) (((10 - smallest) << 3) | (10 - smallest) | 0x40), 0x40, 10, // - 2 - 0x60, 2 + 0x60, 2, + 0x01, 0x23, 0x45, 0x67 }, patch); } @@ -273,17 +290,19 @@ public class DEZEncoderTest { instance.copy(8, 10); instance.copy(10, 10); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, 16, (byte) len, (byte) (((10 - smallest) << 3) | (10 - smallest) | 0x40), 0x40, 8, // + 2 - 0x40, 2 + 0x40, 2, + 0x01, 0x23, 0x45, 0x67 }, patch); } @@ -296,14 +315,16 @@ public class DEZEncoderTest { byte[] target = new byte[0]; instance.init(source.length, target.length); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); - assertEquals(7, patch.length); - assertArrayEquals(patch, new byte[]{ + assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, + 0, 0, - 0}); + 0x01, 0x23, 0x45, 0x67 + }, patch); } @Test @@ -315,15 +336,16 @@ public class DEZEncoderTest { byte[] target = new byte[0]; instance.init(source.length, target.length); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); - assertEquals(8, patch.length); - assertArrayEquals(patch, new byte[]{ + assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], - DEZEncoder.DEZ_SMALLEST, + 0, 6, 0, - 0}); + 0, + 0x01, 0x23, 0x45, 0x67 + }, patch); } @Test @@ -335,14 +357,16 @@ public class DEZEncoderTest { byte[] target = new byte[0]; instance.init(source.length, target.length); - byte[] patch = instance.toPatch(); + byte[] patch = instance.toPatch(0x01234567); - assertEquals(7, patch.length); - assertArrayEquals(patch, new byte[]{ + assertArrayEquals(new byte[]{ DEZEncoder.MAGIC[0], DEZEncoder.MAGIC[1], DEZEncoder.MAGIC[2], DEZEncoder.MAGIC[3], 0, + 4, + 0, 0, - 0}); + 0x01, 0x23, 0x45, 0x67 + }, patch); } } diff --git a/src/notzed.dez/tests/au/notzed/dez/DEZIT.java b/src/notzed.dez/tests/au/notzed/dez/DEZIT.java index 9e683cc..712bc5f 100644 --- a/src/notzed.dez/tests/au/notzed/dez/DEZIT.java +++ b/src/notzed.dez/tests/au/notzed/dez/DEZIT.java @@ -119,4 +119,29 @@ public class DEZIT { } } + @Test + public void testCorruptCRC() throws Exception { + System.out.println("decode corrupt crc"); + + byte[] source = "the rains in Spain fall mainly on the plains".getBytes(); + byte[] target = "plain breads in Spain aids your dames brains".getBytes(); + + for (int pos = 1; pos <= 4; pos++) { + DEZEncoder de = new DEZEncoder(4); + ByteMatcherHash matcher = new ByteMatcherHash(4, 4, source, 1, target); + byte[] patch = ByteDeltaEncoder.toDiff(matcher, de); + + Exception x = null; + try { + patch[patch.length - pos] ^= 1; + DEZDecoder dd = new DEZDecoder(source, patch); + + dd.decode(); + } catch (Exception xx) { + x = xx; + } + assertNotNull(x); + assertEquals(x.getClass(), DEZFormatException.class); + } + } } -- 2.39.5