Added CRC to patch format.
authorNot Zed <notzed@gmail.com>
Thu, 25 Apr 2019 10:33:43 +0000 (20:03 +0930)
committerNot Zed <notzed@gmail.com>
Thu, 25 Apr 2019 10:33:43 +0000 (20:03 +0930)
Tweaked the main format documentation.
Added a CRC test.
Added a DEZFormatException for error states.

src/notzed.dez.demo/classes/au/notzed/dez/demo/DEZPrinter.java
src/notzed.dez.demo/classes/au/notzed/dez/demo/PrintEncoder.java
src/notzed.dez/classes/au/notzed/dez/ByteDeltaEncoder.java
src/notzed.dez/classes/au/notzed/dez/DEZDecoder.java
src/notzed.dez/classes/au/notzed/dez/DEZEncoder.java
src/notzed.dez/classes/au/notzed/dez/DEZFormat.java
src/notzed.dez/classes/au/notzed/dez/DEZFormatException.java [new file with mode: 0644]
src/notzed.dez/tests/au/notzed/dez/DEZEncoderTest.java
src/notzed.dez/tests/au/notzed/dez/DEZIT.java

index e64833e..4f3d61e 100644 (file)
@@ -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.
-        *
+        * <p>
         */
        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);
        }
 
 }
index 7f398bf..704895d 100644 (file)
@@ -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];
        }
 }
index a3546a1..384a270 100644 (file)
@@ -16,6 +16,8 @@
  */
 package au.notzed.dez;
 
+import java.util.zip.CRC32;
+
 /**
  * The interface for encoding a delta.
  * <p>
@@ -25,9 +27,12 @@ public interface ByteDeltaEncoder {
 
        /**
         * Initialises creating a new patch.
+        * <p>
+        * 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());
        }
 }
index 19549db..0af563a 100644 (file)
@@ -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.
  * <p>
- * @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.
+        * <p>
+        * 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.
+        * <p>
         * 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;
        }
 
index a298843..d65777b 100644 (file)
@@ -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 &ge; 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();
        }
 }
index 56f6df8..66f653d 100644 (file)
@@ -19,58 +19,94 @@ package au.notzed.dez;
 /**
  * Defines constants used in the DEZ1 format.
  * <p>
- * <h3>Header</h3>
+ * <h3>Data Types</h3>
+ * <p>
+ * The patch format is a byte stream consisting of a header and a sequence of
+ * bytes, followed by a crc code.
  * <pre>
- *  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.
  * </pre>
- * <h3>Instruction stream</h3>
+ * 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.
+ * <h3>Patch Format</h3>
  * <pre>
- * 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.
+ * </pre>
+ * <h3>Instruction stream</h3>
+ * Instructions can perform either a single
+ * operation or two operations with limited ranges.
+ * <p>
+ * Single commands perform one operation. They are a 7-bit value with the
+ * 8th bit set. A range test defines the operation.
+ * <pre>
  *
- * 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.
  * </pre>
+ * 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.
  * <p>
- * 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.
- * <p>
+ * 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.
+ * <pre>
+ *
+ * 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+).
+ * </pre>
  * <h3>Addresses</h3>
+ * 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.
  * <p>
- * 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.
+ * <p>
+ * Addresses can be encoded in three ways.
+ * <ol>
+ * <li>An exact reference to an address cache entry.
+ * <li>A reference to an address cache entry with a signed offset.
+ * <li>An absolute address.
+ * </ol>
+ * The first byte determines the address encoding.
  * <pre>
+ * 
  *  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.
  * </pre>
  * <p>
- * 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.
- * <p>
  * 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.
  * <p>
  * 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.
- * <p>
+ * 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.
+ * </p>
  */
 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 (file)
index 0000000..c951dd3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.dez;
+
+import java.io.IOException;
+
+/**
+ *
+ */
+public class DEZFormatException extends IOException {
+
+       public DEZFormatException(String why) {
+               super(why);
+       }
+
+}
index 4617698..14ae02b 100644 (file)
@@ -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);
        }
 
 }
index 9e683cc..712bc5f 100644 (file)
@@ -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);
+               }
+       }
 }