Commit ebf960b2 authored by Etienne Pierre-Doray's avatar Etienne Pierre-Doray Committed by Commit Bot

[Zucchini] Introduce patch utilities.

This CL adds Zucchini utilities related to patch manipulation, along
with tests. Details:
- PatchHeader struct
- ElementPatchHeader struct
- Encode/Decode varint functions

Bug: 729154
Change-Id: I081752542cb8b6ffe35c7dde079df70c19ca71e5
Reviewed-on: https://chromium-review.googlesource.com/572306
Commit-Queue: Samuel Huang <huangs@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#487478}
parent 3c123639
......@@ -21,6 +21,7 @@ static_library("zucchini_lib") {
"io_utils.h",
"main_utils.cc",
"main_utils.h",
"patch_utils.h",
"suffix_array.h",
"typed_value.h",
]
......@@ -60,6 +61,7 @@ test("zucchini_unittests") {
"buffer_view_unittest.cc",
"crc32_unittest.cc",
"io_utils_unittest.cc",
"patch_utils_unittest.cc",
"suffix_array_unittest.cc",
"typed_value_unittest.cc",
]
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_INSTALLER_ZUCCHINI_PATCH_UTILS_H_
#define CHROME_INSTALLER_ZUCCHINI_PATCH_UTILS_H_
#include <cstdint>
#include <type_traits>
#include "base/logging.h"
#include "base/optional.h"
#include "chrome/installer/zucchini/image_utils.h"
namespace zucchini {
// Constants that appear inside a patch.
enum class PatchType : uint32_t {
kRawPatch = 0,
kSinglePatch = 1,
kEnsemblePatch = 2,
kUnrecognisedPatch,
};
// A Zucchini 'ensemble' patch is the concatenation of a patch header with a
// list of patch 'elements', each containing data for patching individual
// elements.
// Supported by MSVC, g++, and clang++. Ensures no gaps in packing.
#pragma pack(push, 1)
// Header for a Zucchini patch, found at the begining of an ensemble patch.
struct PatchHeader {
// Magic signature at the beginning of a Zucchini patch file.
static constexpr uint32_t kMagic = 'Z' | ('u' << 8) | ('c' << 16);
uint32_t magic = 0;
uint32_t old_size = 0;
uint32_t old_crc = 0;
uint32_t new_size = 0;
uint32_t new_crc = 0;
};
// Sanity check.
static_assert(sizeof(PatchHeader) == 20, "PatchHeader is 20 bytes");
// Header for a patch element, found at the begining of every patch element.
struct PatchElementHeader {
uint32_t old_offset;
uint32_t new_offset;
uint64_t old_length;
uint64_t new_length;
uint32_t exe_type;
};
// Sanity check.
static_assert(sizeof(PatchElementHeader) == 28,
"PatchElementHeader is 28 bytes");
#pragma pack(pop)
// Descibes a raw FIX operation.
struct RawDeltaUnit {
offset_t copy_offset; // Offset in copy regions starting from last delta.
int8_t diff; // Bytewise difference.
};
// A Zucchini patch contains data streams encoded using varint format to reduce
// uncompressed size. This is a variable-length encoding for integer quantities
// that strips away leading (most-significant) null bytes.
// Writes |value| as a varint in |dst| and returns an iterator pointing beyond
// the written region. |dst| is assumed to hold enough space. Typically, this
// will write to a vector using back insertion, e.g.:
// EncodeVarUInt(value, std::back_inserter(vector));
template <class T, class It>
It EncodeVarUInt(T value, It dst) {
static_assert(std::is_unsigned<T>::value, "Value type must be unsigned");
while (value >= 128) {
*dst++ = static_cast<uint8_t>(value) | 128;
value >>= 7;
}
*dst++ = static_cast<uint8_t>(value);
return dst;
}
// Same as EncodeVarUInt(), but for signed values.
template <class T, class It>
It EncodeVarInt(T value, It dst) {
static_assert(std::is_signed<T>::value, "Value type must be signed");
using unsigned_value_type = typename std::make_unsigned<T>::type;
if (value < 0)
return EncodeVarUInt((unsigned_value_type(~value) << 1) | 1, dst);
else
return EncodeVarUInt(unsigned_value_type(value) << 1, dst);
}
// Tries to read a varint unsigned integer from [|first|, |last|). If
// succesfull, writes result into |value| and returns an iterator pointing
// beyond the formatted varint. Otherwise returns nullopt.
template <class T, class It>
base::Optional<It> DecodeVarUInt(It first, It last, T* value) {
static_assert(std::is_unsigned<T>::value, "Value type must be unsigned");
uint8_t sh = 0;
T val = 0;
while (first != last) {
val |= T(*first & 0x7F) << sh;
if (*(first++) < 0x80) {
*value = val;
return first;
}
sh += 7;
if (sh >= sizeof(T) * 8) { // Overflow!
LOG(ERROR) << "Overflow encountered.";
return base::nullopt;
}
}
LOG(ERROR) << "Exhausted data while reading.";
return base::nullopt;
}
// Same as DecodeVarUInt(), but for signed values.
template <class T, class It>
base::Optional<It> DecodeVarInt(It first, It last, T* value) {
static_assert(std::is_signed<T>::value, "Value type must be signed");
typename std::make_unsigned<T>::type tmp = 0;
auto res = DecodeVarUInt(first, last, &tmp);
if (!res)
return res;
if (tmp & 1)
*value = ~static_cast<T>(tmp >> 1);
else
*value = static_cast<T>(tmp >> 1);
return res;
}
} // namespace zucchini
#endif // CHROME_INSTALLER_ZUCCHINI_PATCH_UTILS_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/installer/zucchini/patch_utils.h"
#include <cstdint>
#include <iterator>
#include <vector>
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace zucchini {
template <class T>
void TestEncodeDecodeVarUInt(const std::vector<T>& data) {
std::vector<uint8_t> buffer;
std::vector<T> values;
for (T basis : data) {
// For variety, test the neighborhood values for each case in |data|. Some
// test cases may result in overflow when computing |value|, but we don't
// care about that.
for (int delta = -4; delta <= 4; ++delta) {
T value = delta + basis;
EncodeVarUInt<T>(value, std::back_inserter(buffer));
values.push_back(value);
value = delta - basis;
EncodeVarUInt<T>(value, std::back_inserter(buffer));
values.push_back(value);
}
}
auto it = buffer.begin();
for (T expected : values) {
T value = T(-1);
auto res = DecodeVarUInt(it, buffer.end(), &value);
EXPECT_TRUE(res.has_value());
EXPECT_EQ(expected, value);
it = res.value();
}
EXPECT_EQ(it, buffer.end());
T dummy = T(-1);
auto res = DecodeVarUInt(it, buffer.end(), &dummy);
EXPECT_EQ(base::nullopt, res);
EXPECT_EQ(T(-1), dummy);
}
template <class T>
void TestEncodeDecodeVarInt(const std::vector<T>& data) {
std::vector<uint8_t> buffer;
std::vector<T> values;
for (T basis : data) {
// For variety, test the neighborhood values for each case in |data|. Some
// test cases may result in overflow when computing |value|, but we don't
// care about that.
for (int delta = -4; delta <= 4; ++delta) {
T value = delta + basis;
EncodeVarInt(value, std::back_inserter(buffer));
values.push_back(value);
value = delta - basis;
EncodeVarInt(value, std::back_inserter(buffer));
values.push_back(value);
}
}
auto it = buffer.begin();
for (T expected : values) {
T value = T(-1);
auto res = DecodeVarInt(it, buffer.end(), &value);
EXPECT_TRUE(res.has_value());
EXPECT_EQ(expected, value);
it = res.value();
}
T dummy = T(-1);
auto res = DecodeVarInt(it, buffer.end(), &dummy);
EXPECT_EQ(base::nullopt, res);
EXPECT_EQ(T(-1), dummy);
}
TEST(PatchUtilsTest, EncodeDecodeVarUInt32) {
TestEncodeDecodeVarUInt<uint32_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
1 << 22, 1 << 27, 1 << 28, 0x7FFFFFFF,
UINT32_MAX});
}
TEST(PatchUtilsTest, EncodeDecodeVarInt32) {
TestEncodeDecodeVarInt<int32_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
1 << 22, 1 << 27, 1 << 28, -1, INT32_MIN,
INT32_MAX});
}
TEST(PatchUtilsTest, EncodeDecodeVarUInt64) {
TestEncodeDecodeVarUInt<uint64_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
1 << 22, 1ULL << 55, 1ULL << 56,
0x7FFFFFFFFFFFFFFF, UINT64_MAX});
}
TEST(PatchUtilsTest, EncodeDecodeVarInt64) {
TestEncodeDecodeVarInt<int64_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
1 << 22, 1LL << 55, 1LL << 56, -1, INT64_MIN,
INT64_MAX});
}
TEST(PatchUtilsTest, DecodeVarUInt32Malformed) {
// Dummy variable to ensure that on failure, the output variable is not
// written to.
uint32_t dummy = uint32_t(-1);
auto TestDecodeVarInt = [&dummy](const std::vector<uint8_t>& buffer) {
dummy = uint32_t(-1);
return DecodeVarUInt(buffer.begin(), buffer.end(), &dummy);
};
// Exhausted.
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>{}));
EXPECT_EQ(uint32_t(-1), dummy);
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>(4, 128)));
EXPECT_EQ(uint32_t(-1), dummy);
// Overflow.
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>(6, 128)));
EXPECT_EQ(uint32_t(-1), dummy);
EXPECT_EQ(base::nullopt, TestDecodeVarInt({128, 128, 128, 128, 128, 42}));
EXPECT_EQ(uint32_t(-1), dummy);
// Following are pathological cases that are not handled for simplicity,
// hence decoding is expected to be successful.
EXPECT_NE(base::nullopt, TestDecodeVarInt({128, 128, 128, 128, 16}));
EXPECT_EQ(uint32_t(0), dummy);
EXPECT_NE(base::nullopt, TestDecodeVarInt({128, 128, 128, 128, 32}));
EXPECT_EQ(uint32_t(0), dummy);
EXPECT_NE(base::nullopt, TestDecodeVarInt({128, 128, 128, 128, 64}));
EXPECT_EQ(uint32_t(0), dummy);
}
TEST(PatchUtilsTest, DecodeVarUInt64Malformed) {
uint64_t dummy = uint64_t(-1);
auto TestDecodeVarInt = [&dummy](const std::vector<uint8_t>& buffer) {
return DecodeVarUInt(buffer.begin(), buffer.end(), &dummy);
};
// Exhausted.
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>{}));
EXPECT_EQ(uint64_t(-1), dummy);
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>(9, 128)));
EXPECT_EQ(uint64_t(-1), dummy);
// Overflow.
EXPECT_EQ(base::nullopt, TestDecodeVarInt(std::vector<uint8_t>(10, 128)));
EXPECT_EQ(uint64_t(-1), dummy);
EXPECT_EQ(base::nullopt, TestDecodeVarInt({128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 42}));
EXPECT_EQ(uint64_t(-1), dummy);
}
} // namespace zucchini
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment