Merge "Add tests for imgdiff."
am: 344c8eb453

Change-Id: Iad8cb5e7ef3e3a09c305913a1367af8585d59529
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index fa0fe8a..61e1106 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -17,7 +17,6 @@
 # libapplypatch (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
     applypatch.cpp \
     bspatch.cpp \
@@ -26,11 +25,11 @@
     utils.cpp
 LOCAL_MODULE := libapplypatch
 LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += \
+LOCAL_STATIC_LIBRARIES := \
     libotafault \
     libbase \
     libcrypto \
@@ -42,36 +41,45 @@
 # libimgpatch (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp \
+    utils.cpp
 LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto libbz libz
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbz \
+    libz
 LOCAL_CFLAGS := -Werror
 include $(BUILD_STATIC_LIBRARY)
 
 # libimgpatch (host static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp \
+    utils.cpp
 LOCAL_MODULE := libimgpatch
 LOCAL_MODULE_HOST_OS := linux
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto libbz libz
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbz \
+    libz
 LOCAL_CFLAGS := -Werror
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 # libapplypatch_modes (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
     applypatch_modes.cpp
 LOCAL_MODULE := libapplypatch_modes
@@ -87,7 +95,6 @@
 # applypatch (target executable)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := applypatch_main.cpp
 LOCAL_MODULE := applypatch
 LOCAL_C_INCLUDES := bootable/recovery
@@ -106,18 +113,59 @@
 LOCAL_CFLAGS := -Werror
 include $(BUILD_EXECUTABLE)
 
+libimgdiff_src_files := \
+    imgdiff.cpp \
+    utils.cpp
+
+# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
+libimgdiff_cflags := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
+libimgdiff_static_libraries := \
+    libbsdiff \
+    libz
+
+# libimgdiff (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_STATIC_LIBRARY)
+
+# libimgdiff (host static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_HOST_STATIC_LIBRARY)
+
 # imgdiff (host static executable)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := imgdiff.cpp utils.cpp
+LOCAL_SRC_FILES := imgdiff_main.cpp
 LOCAL_MODULE := imgdiff
-LOCAL_STATIC_LIBRARIES += \
-    libbsdiff \
-    libbz \
-    libdivsufsort64 \
-    libdivsufsort \
-    libz
 LOCAL_CFLAGS := -Werror
-LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    $(libimgdiff_static_libraries) \
+    libbz \
+    libdivsufsort \
+    libdivsufsort64
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 528daf1..18a15a1 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -121,19 +121,19 @@
  * information that is stored on the system partition.
  */
 
+#include "applypatch/imgdiff.h"
+
 #include <errno.h>
-#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
-#include <unistd.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <bsdiff.h>
+#include <zlib.h>
 
-#include "zlib.h"
-#include "imgdiff.h"
 #include "utils.h"
 
 typedef struct {
@@ -374,8 +374,7 @@
  * return value when done with all the chunks.  Returns NULL on
  * failure.
  */
-unsigned char* ReadImage(const char* filename,
-                         int* num_chunks, ImageChunk** chunks) {
+unsigned char* ReadImage(const char* filename, int* num_chunks, ImageChunk** chunks) {
   struct stat st;
   if (stat(filename, &st) != 0) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
@@ -403,7 +402,7 @@
   *chunks = NULL;
 
   while (pos < sz) {
-    unsigned char* p = img+pos;
+    unsigned char* p = img + pos;
 
     if (sz - pos >= 4 &&
         p[0] == 0x1f && p[1] == 0x8b &&
@@ -413,8 +412,7 @@
       size_t chunk_offset = pos;
 
       *num_chunks += 3;
-      *chunks = static_cast<ImageChunk*>(realloc(*chunks,
-          *num_chunks * sizeof(ImageChunk)));
+      *chunks = static_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk)));
       ImageChunk* curr = *chunks + (*num_chunks-3);
 
       // create a normal chunk for the header.
@@ -502,8 +500,7 @@
       // the decompression.
       size_t footer_size = Read4(p-4);
       if (footer_size != curr[-2].len) {
-        printf("Error: footer size %zu != decompressed size %zu\n",
-            footer_size, curr[-2].len);
+        printf("Error: footer size %zu != decompressed size %zu\n", footer_size, curr[-2].len);
         free(img);
         return NULL;
       }
@@ -637,7 +634,11 @@
     }
   }
 
+#if defined(__ANDROID__)
+  char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
+#else
   char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
+#endif
   int fd = mkstemp(ptemp);
 
   if (fd == -1) {
@@ -793,10 +794,8 @@
   *num_chunks = out;
 }
 
-ImageChunk* FindChunkByName(const char* name,
-                            ImageChunk* chunks, int num_chunks) {
-  int i;
-  for (i = 0; i < num_chunks; ++i) {
+ImageChunk* FindChunkByName(const char* name, ImageChunk* chunks, int num_chunks) {
+  for (int i = 0; i < num_chunks; ++i) {
     if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename &&
         strcmp(name, chunks[i].filename) == 0) {
       return chunks+i;
@@ -812,11 +811,11 @@
     }
 }
 
-int main(int argc, char** argv) {
-  int zip_mode = 0;
+int imgdiff(int argc, const char** argv) {
+  bool zip_mode = false;
 
   if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
-    zip_mode = 1;
+    zip_mode = true;
     --argc;
     ++argv;
   }
@@ -880,12 +879,10 @@
     // Verify that the source and target images have the same chunk
     // structure (ie, the same sequence of deflate and normal chunks).
 
-    if (!zip_mode) {
-        // Merge the gzip header and footer in with any adjacent
-        // normal chunks.
-        MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
-        MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
-    }
+    // Merge the gzip header and footer in with any adjacent
+    // normal chunks.
+    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+    MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
 
     if (num_src_chunks != num_tgt_chunks) {
       printf("source and target don't have same number of chunks!\n");
@@ -897,8 +894,7 @@
     }
     for (i = 0; i < num_src_chunks; ++i) {
       if (src_chunks[i].type != tgt_chunks[i].type) {
-        printf("source and target don't have same chunk "
-                "structure! (chunk %d)\n", i);
+        printf("source and target don't have same chunk structure! (chunk %d)\n", i);
         printf("source chunks:\n");
         DumpChunks(src_chunks, num_src_chunks);
         printf("target chunks:\n");
@@ -983,8 +979,7 @@
     if (zip_mode) {
       ImageChunk* src;
       if (tgt_chunks[i].type == CHUNK_DEFLATE &&
-          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks,
-                                 num_src_chunks))) {
+          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks))) {
         patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i);
       } else {
         patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
@@ -1000,8 +995,7 @@
 
       patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
     }
-    printf("patch %3d is %zu bytes (of %zu)\n",
-           i, patch_size[i], tgt_chunks[i].source_len);
+    printf("patch %3d is %zu bytes (of %zu)\n", i, patch_size[i], tgt_chunks[i].source_len);
   }
 
   // Figure out how big the imgdiff file header is going to be, so
diff --git a/applypatch/imgdiff_main.cpp b/applypatch/imgdiff_main.cpp
new file mode 100644
index 0000000..7d5bdf9
--- /dev/null
+++ b/applypatch/imgdiff_main.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "applypatch/imgdiff.h"
+
+int main(int argc, char** argv) {
+  return imgdiff(argc, const_cast<const char**>(argv));
+}
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 1c4409e..ae6071f 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -14,32 +14,34 @@
  * limitations under the License.
  */
 
-// See imgdiff.c in this directory for a description of the patch file
+// See imgdiff.cpp in this directory for a description of the patch file
 // format.
 
+#include <applypatch/imgpatch.h>
+
+#include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include <sys/cdefs.h>
 #include <sys/stat.h>
-#include <errno.h>
 #include <unistd.h>
-#include <string.h>
 
 #include <string>
 #include <vector>
 
-#include "zlib.h"
-#include "openssl/sha.h"
-#include "applypatch/applypatch.h"
-#include "imgdiff.h"
+#include <applypatch/applypatch.h>
+#include <applypatch/imgdiff.h>
+#include <openssl/sha.h>
+#include <zlib.h>
+
 #include "utils.h"
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token) {
-    Value patch(VAL_BLOB, std::string(
-            reinterpret_cast<const char*>(patch_data), patch_size));
+  Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
 
-    return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
+  return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
 }
 
 /*
@@ -48,208 +50,201 @@
  * file, and update the SHA context with the output data as well.
  * Return 0 on success.
  */
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
-                    const Value* patch,
-                    SinkFn sink, void* token, SHA_CTX* ctx,
-                    const Value* bonus_data) {
-    if (patch->data.size() < 12) {
-        printf("patch too short to contain header\n");
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
+                    SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) {
+  if (patch->data.size() < 12) {
+    printf("patch too short to contain header\n");
+    return -1;
+  }
+
+  // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
+  // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
+  // CHUNK_GZIP.)
+  size_t pos = 12;
+  const char* header = &patch->data[0];
+  if (memcmp(header, "IMGDIFF2", 8) != 0) {
+    printf("corrupt patch file header (magic number)\n");
+    return -1;
+  }
+
+  int num_chunks = Read4(header + 8);
+
+  for (int i = 0; i < num_chunks; ++i) {
+    // each chunk's header record starts with 4 bytes.
+    if (pos + 4 > patch->data.size()) {
+      printf("failed to read chunk %d record\n", i);
+      return -1;
+    }
+    int type = Read4(&patch->data[pos]);
+    pos += 4;
+
+    if (type == CHUNK_NORMAL) {
+      const char* normal_header = &patch->data[pos];
+      pos += 24;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d normal header data\n", i);
         return -1;
-    }
+      }
 
-    // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
-    // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
-    // CHUNK_GZIP.)
-    size_t pos = 12;
-    const char* header = &patch->data[0];
-    if (memcmp(header, "IMGDIFF2", 8) != 0) {
-        printf("corrupt patch file header (magic number)\n");
+      size_t src_start = Read8(normal_header);
+      size_t src_len = Read8(normal_header + 8);
+      size_t patch_offset = Read8(normal_header + 16);
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
         return -1;
-    }
+      }
+      ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx);
+    } else if (type == CHUNK_RAW) {
+      const char* raw_header = &patch->data[pos];
+      pos += 4;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d raw header data\n", i);
+        return -1;
+      }
 
-    int num_chunks = Read4(header+8);
+      ssize_t data_len = Read4(raw_header);
 
-    for (int i = 0; i < num_chunks; ++i) {
-        // each chunk's header record starts with 4 bytes.
-        if (pos + 4 > patch->data.size()) {
-            printf("failed to read chunk %d record\n", i);
-            return -1;
+      if (pos + data_len > patch->data.size()) {
+        printf("failed to read chunk %d raw data\n", i);
+        return -1;
+      }
+      if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
+      if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) !=
+          data_len) {
+        printf("failed to write chunk %d raw data\n", i);
+        return -1;
+      }
+      pos += data_len;
+    } else if (type == CHUNK_DEFLATE) {
+      // deflate chunks have an additional 60 bytes in their chunk header.
+      const char* deflate_header = &patch->data[pos];
+      pos += 60;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d deflate header data\n", i);
+        return -1;
+      }
+
+      size_t src_start = Read8(deflate_header);
+      size_t src_len = Read8(deflate_header + 8);
+      size_t patch_offset = Read8(deflate_header + 16);
+      size_t expanded_len = Read8(deflate_header + 24);
+      size_t target_len = Read8(deflate_header + 32);
+      int level = Read4(deflate_header + 40);
+      int method = Read4(deflate_header + 44);
+      int windowBits = Read4(deflate_header + 48);
+      int memLevel = Read4(deflate_header + 52);
+      int strategy = Read4(deflate_header + 56);
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
+        return -1;
+      }
+
+      // Decompress the source data; the chunk header tells us exactly
+      // how big we expect it to be when decompressed.
+
+      // Note: expanded_len will include the bonus data size if
+      // the patch was constructed with bonus data.  The
+      // deflation will come up 'bonus_size' bytes short; these
+      // must be appended from the bonus_data value.
+      size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
+
+      std::vector<unsigned char> expanded_source(expanded_len);
+
+      // inflate() doesn't like strm.next_out being a nullptr even with
+      // avail_out being zero (Z_STREAM_ERROR).
+      if (expanded_len != 0) {
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = src_len;
+        strm.next_in = const_cast<unsigned char*>(old_data + src_start);
+        strm.avail_out = expanded_len;
+        strm.next_out = expanded_source.data();
+
+        int ret = inflateInit2(&strm, -15);
+        if (ret != Z_OK) {
+          printf("failed to init source inflation: %d\n", ret);
+          return -1;
         }
-        int type = Read4(&patch->data[pos]);
-        pos += 4;
 
-        if (type == CHUNK_NORMAL) {
-            const char* normal_header = &patch->data[pos];
-            pos += 24;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d normal header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(normal_header);
-            size_t src_len = Read8(normal_header+8);
-            size_t patch_offset = Read8(normal_header+16);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-            ApplyBSDiffPatch(old_data + src_start, src_len,
-                             patch, patch_offset, sink, token, ctx);
-        } else if (type == CHUNK_RAW) {
-            const char* raw_header = &patch->data[pos];
-            pos += 4;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d raw header data\n", i);
-                return -1;
-            }
-
-            ssize_t data_len = Read4(raw_header);
-
-            if (pos + data_len > patch->data.size()) {
-                printf("failed to read chunk %d raw data\n", i);
-                return -1;
-            }
-            if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
-            if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]),
-                     data_len, token) != data_len) {
-                printf("failed to write chunk %d raw data\n", i);
-                return -1;
-            }
-            pos += data_len;
-        } else if (type == CHUNK_DEFLATE) {
-            // deflate chunks have an additional 60 bytes in their chunk header.
-            const char* deflate_header = &patch->data[pos];
-            pos += 60;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d deflate header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(deflate_header);
-            size_t src_len = Read8(deflate_header+8);
-            size_t patch_offset = Read8(deflate_header+16);
-            size_t expanded_len = Read8(deflate_header+24);
-            size_t target_len = Read8(deflate_header+32);
-            int level = Read4(deflate_header+40);
-            int method = Read4(deflate_header+44);
-            int windowBits = Read4(deflate_header+48);
-            int memLevel = Read4(deflate_header+52);
-            int strategy = Read4(deflate_header+56);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-
-            // Decompress the source data; the chunk header tells us exactly
-            // how big we expect it to be when decompressed.
-
-            // Note: expanded_len will include the bonus data size if
-            // the patch was constructed with bonus data.  The
-            // deflation will come up 'bonus_size' bytes short; these
-            // must be appended from the bonus_data value.
-            size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
-
-            std::vector<unsigned char> expanded_source(expanded_len);
-
-            // inflate() doesn't like strm.next_out being a nullptr even with
-            // avail_out being zero (Z_STREAM_ERROR).
-            if (expanded_len != 0) {
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = src_len;
-                strm.next_in = (unsigned char*)(old_data + src_start);
-                strm.avail_out = expanded_len;
-                strm.next_out = expanded_source.data();
-
-                int ret;
-                ret = inflateInit2(&strm, -15);
-                if (ret != Z_OK) {
-                    printf("failed to init source inflation: %d\n", ret);
-                    return -1;
-                }
-
-                // Because we've provided enough room to accommodate the output
-                // data, we expect one call to inflate() to suffice.
-                ret = inflate(&strm, Z_SYNC_FLUSH);
-                if (ret != Z_STREAM_END) {
-                    printf("source inflation returned %d\n", ret);
-                    return -1;
-                }
-                // We should have filled the output buffer exactly, except
-                // for the bonus_size.
-                if (strm.avail_out != bonus_size) {
-                    printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
-                    return -1;
-                }
-                inflateEnd(&strm);
-
-                if (bonus_size) {
-                    memcpy(expanded_source.data() + (expanded_len - bonus_size),
-                           &bonus_data->data[0], bonus_size);
-                }
-            }
-
-            // Next, apply the bsdiff patch (in memory) to the uncompressed
-            // data.
-            std::vector<unsigned char> uncompressed_target_data;
-            if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len,
-                                    patch, patch_offset,
-                                    &uncompressed_target_data) != 0) {
-                return -1;
-            }
-            if (uncompressed_target_data.size() != target_len) {
-                printf("expected target len to be %zu, but it's %zu\n",
-                       target_len, uncompressed_target_data.size());
-                return -1;
-            }
-
-            // Now compress the target data and append it to the output.
-
-            // we're done with the expanded_source data buffer, so we'll
-            // reuse that memory to receive the output of deflate.
-            if (expanded_source.size() < 32768U) {
-                expanded_source.resize(32768U);
-            }
-
-            {
-                std::vector<unsigned char>& temp_data = expanded_source;
-
-                // now the deflate stream
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = uncompressed_target_data.size();
-                strm.next_in = uncompressed_target_data.data();
-                int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
-                if (ret != Z_OK) {
-                    printf("failed to init uncompressed data deflation: %d\n", ret);
-                    return -1;
-                }
-                do {
-                    strm.avail_out = temp_data.size();
-                    strm.next_out = temp_data.data();
-                    ret = deflate(&strm, Z_FINISH);
-                    ssize_t have = temp_data.size() - strm.avail_out;
-
-                    if (sink(temp_data.data(), have, token) != have) {
-                        printf("failed to write %zd compressed bytes to output\n",
-                               have);
-                        return -1;
-                    }
-                    if (ctx) SHA1_Update(ctx, temp_data.data(), have);
-                } while (ret != Z_STREAM_END);
-                deflateEnd(&strm);
-            }
-        } else {
-            printf("patch chunk %d is unknown type %d\n", i, type);
-            return -1;
+        // Because we've provided enough room to accommodate the output
+        // data, we expect one call to inflate() to suffice.
+        ret = inflate(&strm, Z_SYNC_FLUSH);
+        if (ret != Z_STREAM_END) {
+          printf("source inflation returned %d\n", ret);
+          return -1;
         }
-    }
+        // We should have filled the output buffer exactly, except
+        // for the bonus_size.
+        if (strm.avail_out != bonus_size) {
+          printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size);
+          return -1;
+        }
+        inflateEnd(&strm);
 
-    return 0;
+        if (bonus_size) {
+          memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0],
+                 bonus_size);
+        }
+      }
+
+      // Next, apply the bsdiff patch (in memory) to the uncompressed data.
+      std::vector<unsigned char> uncompressed_target_data;
+      if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset,
+                              &uncompressed_target_data) != 0) {
+        return -1;
+      }
+      if (uncompressed_target_data.size() != target_len) {
+        printf("expected target len to be %zu, but it's %zu\n", target_len,
+               uncompressed_target_data.size());
+        return -1;
+      }
+
+      // Now compress the target data and append it to the output.
+
+      // we're done with the expanded_source data buffer, so we'll
+      // reuse that memory to receive the output of deflate.
+      if (expanded_source.size() < 32768U) {
+        expanded_source.resize(32768U);
+      }
+
+      {
+        std::vector<unsigned char>& temp_data = expanded_source;
+
+        // now the deflate stream
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = uncompressed_target_data.size();
+        strm.next_in = uncompressed_target_data.data();
+        int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
+        if (ret != Z_OK) {
+          printf("failed to init uncompressed data deflation: %d\n", ret);
+          return -1;
+        }
+        do {
+          strm.avail_out = temp_data.size();
+          strm.next_out = temp_data.data();
+          ret = deflate(&strm, Z_FINISH);
+          ssize_t have = temp_data.size() - strm.avail_out;
+
+          if (sink(temp_data.data(), have, token) != have) {
+            printf("failed to write %zd compressed bytes to output\n", have);
+            return -1;
+          }
+          if (ctx) SHA1_Update(ctx, temp_data.data(), have);
+        } while (ret != Z_STREAM_END);
+        deflateEnd(&strm);
+      }
+    } else {
+      printf("patch chunk %d is unknown type %d\n", i, type);
+      return -1;
+    }
+  }
+
+  return 0;
 }
diff --git a/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h
similarity index 69%
rename from applypatch/imgdiff.h
rename to applypatch/include/applypatch/imgdiff.h
index f2069b4..22cbd4f 100644
--- a/applypatch/imgdiff.h
+++ b/applypatch/include/applypatch/imgdiff.h
@@ -14,17 +14,26 @@
  * limitations under the License.
  */
 
+#ifndef _APPLYPATCH_IMGDIFF_H
+#define _APPLYPATCH_IMGDIFF_H
+
+#include <stddef.h>
+
 // Image patch chunk types
-#define CHUNK_NORMAL   0
-#define CHUNK_GZIP     1   // version 1 only
-#define CHUNK_DEFLATE  2   // version 2 only
-#define CHUNK_RAW      3   // version 2 only
+#define CHUNK_NORMAL 0
+#define CHUNK_GZIP 1     // version 1 only
+#define CHUNK_DEFLATE 2  // version 2 only
+#define CHUNK_RAW 3      // version 2 only
 
 // The gzip header size is actually variable, but we currently don't
 // support gzipped data with any of the optional fields, so for now it
 // will always be ten bytes.  See RFC 1952 for the definition of the
 // gzip format.
-#define GZIP_HEADER_LEN   10
+static constexpr size_t GZIP_HEADER_LEN = 10;
 
 // The gzip footer size really is fixed.
-#define GZIP_FOOTER_LEN   8
+static constexpr size_t GZIP_FOOTER_LEN = 8;
+
+int imgdiff(int argc, const char** argv);
+
+#endif  // _APPLYPATCH_IMGDIFF_H
diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h
index 64d9aa9..6549f79 100644
--- a/applypatch/include/applypatch/imgpatch.h
+++ b/applypatch/include/applypatch/imgpatch.h
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef _IMGPATCH_H
-#define _IMGPATCH_H
+#ifndef _APPLYPATCH_IMGPATCH_H
+#define _APPLYPATCH_IMGPATCH_H
 
-typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
+#include <sys/types.h>
+
+using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*);
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token);
 
-#endif  //_IMGPATCH_H
+#endif  // _APPLYPATCH_IMGPATCH_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 17e2b7f..0aca8c6 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -65,6 +65,7 @@
     component/applypatch_test.cpp \
     component/bootloader_message_test.cpp \
     component/edify_test.cpp \
+    component/imgdiff_test.cpp \
     component/uncrypt_test.cpp \
     component/updater_test.cpp \
     component/verifier_test.cpp
@@ -83,6 +84,9 @@
     libapplypatch_modes \
     libapplypatch \
     libedify \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
     libotafault \
     libupdater \
     libbootloader_message \
@@ -90,6 +94,8 @@
     libminui \
     libotautil \
     libmounts \
+    libdivsufsort \
+    libdivsufsort64 \
     libfs_mgr \
     liblog \
     libselinux \
@@ -130,3 +136,26 @@
 LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
 
 include $(BUILD_NATIVE_TEST)
+
+# Host tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_host_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_SRC_FILES := \
+    component/imgdiff_test.cpp
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
+    libziparchive \
+    libbase \
+    libcrypto \
+    libbz \
+    libdivsufsort64 \
+    libdivsufsort \
+    libz
+LOCAL_SHARED_LIBRARIES := \
+    liblog
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
new file mode 100644
index 0000000..3711859
--- /dev/null
+++ b/tests/component/imgdiff_test.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <applypatch/imgdiff.h>
+#include <applypatch/imgpatch.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+#include "applypatch/utils.h"
+
+static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
+  std::string* s = static_cast<std::string*>(token);
+  s->append(reinterpret_cast<const char*>(data), len);
+  return len;
+}
+
+// Sanity check for the given imgdiff patch header.
+static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
+                                size_t* num_deflate) {
+  const size_t size = patch.size();
+  const char* data = patch.data();
+
+  ASSERT_GE(size, 12U);
+  ASSERT_EQ("IMGDIFF2", std::string(data, 8));
+
+  const int num_chunks = Read4(data + 8);
+  ASSERT_GE(num_chunks, 0);
+
+  size_t normal = 0;
+  size_t raw = 0;
+  size_t deflate = 0;
+
+  size_t pos = 12;
+  for (int i = 0; i < num_chunks; ++i) {
+    ASSERT_LE(pos + 4, size);
+    int type = Read4(data + pos);
+    pos += 4;
+    if (type == CHUNK_NORMAL) {
+      pos += 24;
+      ASSERT_LE(pos, size);
+      normal++;
+    } else if (type == CHUNK_RAW) {
+      ASSERT_LE(pos + 4, size);
+      ssize_t data_len = Read4(data + pos);
+      ASSERT_GT(data_len, 0);
+      pos += 4 + data_len;
+      ASSERT_LE(pos, size);
+      raw++;
+    } else if (type == CHUNK_DEFLATE) {
+      pos += 60;
+      ASSERT_LE(pos, size);
+      deflate++;
+    } else {
+      FAIL() << "Invalid patch type: " << type;
+    }
+  }
+
+  if (num_normal != nullptr) *num_normal = normal;
+  if (num_raw != nullptr) *num_raw = raw;
+  if (num_deflate != nullptr) *num_deflate = deflate;
+}
+
+TEST(ImgdiffTest, invalid_args) {
+  // Insufficient inputs.
+  ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" }));
+  ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" }));
+
+  // Failed to read bonus file.
+  ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" }));
+
+  // Failed to read input files.
+  ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" }));
+  ASSERT_EQ(
+      1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" }));
+}
+
+TEST(ImgdiffTest, image_mode_smoke) {
+  // Random bytes.
+  const std::string src("abcdefg");
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  const std::string tgt("abcdefgxyz");
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_store) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_simple) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_different_num_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
+  const std::vector<char> src_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'h',    '\x1f', '\x8b', '\x08',
+    '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02',
+    '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b',
+    '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d',
+    '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00'
+  };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(1, imgdiff(args.size(), args.data()));
+}
+
+TEST(ImgdiffTest, image_mode_merge_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: gzipped "xyz" + "abcdefgh".
+  const std::vector<char> tgt_data = {
+    '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8',
+    '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00',
+    '\x00', '\x00', 'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) +
+  // CHUNK_RAW (footer), they both should contain the same chunk types after merging.
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_spurious_magic) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_single_entry_long) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + 200 bytes.
+  std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  tgt_data.resize(tgt_data.size() + 200);
+
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(1U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(0U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}