Enable BCJ filters for X86 and ARM ELF binary.

BCJ filter convert relative addresses into absolute addresses to
increase redundancy, but it could make things worse if applied on the
wrong data, so we only enabled it on ELF with supported arch.

Some testing with an ARM system image shows that using the filter could
reduce the compressed size by up to 20% when compressing a single ELF
file using XZ, or increase by up to 1%, on average this will save about
6%.

The saving on final payload size are less significant because bsdiff
will win most of the time in delta payload, and for full payload we
are not splitting the operation by file, so the filter won't apply
most of the time.

Bug: 27878181
Test: generated delta and full payload
Test: update_engine_unittests

Change-Id: I9b6ce1b0b8b0e37cf35a9a6dfb02ebbb8be65109
diff --git a/payload_generator/xz_android.cc b/payload_generator/xz_android.cc
index b2b74b1..41c55f7 100644
--- a/payload_generator/xz_android.cc
+++ b/payload_generator/xz_android.cc
@@ -16,12 +16,14 @@
 
 #include "update_engine/payload_generator/xz.h"
 
-#include <7zCrc.h>
-#include <Xz.h>
-#include <XzEnc.h>
+#include <elf.h>
+#include <endian.h>
 
 #include <algorithm>
 
+#include <7zCrc.h>
+#include <Xz.h>
+#include <XzEnc.h>
 #include <base/logging.h>
 
 namespace {
@@ -67,6 +69,37 @@
   brillo::Blob* data_;
 };
 
+// Returns the filter id to be used to compress |data|.
+// Only BCJ filter for x86 and ARM ELF file are supported, returns 0 otherwise.
+int GetFilterID(const brillo::Blob& data) {
+  if (data.size() < sizeof(Elf32_Ehdr) ||
+      memcmp(data.data(), ELFMAG, SELFMAG) != 0)
+    return 0;
+
+  const Elf32_Ehdr* header = reinterpret_cast<const Elf32_Ehdr*>(data.data());
+
+  // Only little-endian is supported.
+  if (header->e_ident[EI_DATA] != ELFDATA2LSB)
+    return 0;
+
+  switch (le16toh(header->e_machine)) {
+    case EM_386:
+    case EM_X86_64:
+      return XZ_ID_X86;
+    case EM_ARM:
+      // Both ARM and ARM Thumb instructions could be found in the same ARM ELF
+      // file. We choose to use the ARM Thumb filter here because testing shows
+      // that it usually works better than the ARM filter.
+      return XZ_ID_ARMT;
+#ifdef EM_AARCH64
+    case EM_AARCH64:
+      // Neither the ARM nor the ARM Thumb filter works well with AArch64.
+      return 0;
+#endif
+  }
+  return 0;
+}
+
 }  // namespace
 
 namespace chromeos_update_engine {
@@ -107,6 +140,8 @@
   Lzma2EncProps_Normalize(&lzma2Props);
   props.lzma2Props = lzma2Props;
 
+  props.filterProps.id = GetFilterID(in);
+
   BlobWriterStream out_writer(out);
   BlobReaderStream in_reader(in);
   SRes res = Xz_Encode(&out_writer, &in_reader, &props, nullptr /* progress */);