blob: 20aa75f8e514bc169232a25daa99a8581c7342d0 [file] [log] [blame]
//
// Copyright (C) 2021 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 <unistd.h>
#include <android-base/file.h>
#include <android-base/mapped_file.h>
#include <android-base/properties.h>
#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <libsnapshot/cow_writer.h>
#include <libsnapshot/mock_snapshot_writer.h>
#include "update_engine/common/hash_calculator.h"
#include "update_engine/common/mock_dynamic_partition_control.h"
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/vabc_partition_writer.h"
#include "update_engine/payload_generator/delta_diff_generator.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
using android::snapshot::CowOptions;
using testing::_;
using testing::Args;
using testing::ElementsAreArray;
using testing::Invoke;
using testing::Return;
using testing::Sequence;
using utils::GetReadonlyZeroBlock;
namespace {
static constexpr auto& fake_part_name = "fake_part";
static constexpr size_t FAKE_PART_SIZE = 4096 * 50;
class VABCPartitionWriterTest : public ::testing::Test {
public:
void SetUp() override {
ftruncate(source_part_.fd, FAKE_PART_SIZE);
ON_CALL(dynamic_control_, GetVirtualAbCompressionXorFeatureFlag())
.WillByDefault(Return(FeatureFlag(FeatureFlag::Value::NONE)));
}
protected:
CowMergeOperation* AddMergeOp(PartitionUpdate* partition,
std::array<size_t, 2> src_extent,
std::array<size_t, 2> dst_extent,
CowMergeOperation_Type type) {
auto merge_op = partition->add_merge_operations();
auto src = merge_op->mutable_src_extent();
src->set_start_block(src_extent[0]);
src->set_num_blocks(src_extent[1]);
auto dst = merge_op->mutable_dst_extent();
dst->set_start_block(dst_extent[0]);
dst->set_num_blocks(dst_extent[1]);
merge_op->set_type(type);
return merge_op;
}
android::snapshot::CowOptions options_ = {
.block_size = static_cast<uint32_t>(kBlockSize)};
android::snapshot::MockSnapshotWriter cow_writer_{options_};
MockDynamicPartitionControl dynamic_control_;
PartitionUpdate partition_update_;
InstallPlan install_plan_;
TemporaryFile source_part_;
InstallPlan::Partition install_part_{.name = fake_part_name,
.source_path = source_part_.path,
.source_size = FAKE_PART_SIZE};
};
TEST_F(VABCPartitionWriterTest, MergeSequenceWriteTest) {
AddMergeOp(&partition_update_, {5, 1}, {10, 1}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {12, 2}, {13, 2}, CowMergeOperation::COW_XOR);
AddMergeOp(&partition_update_, {15, 1}, {20, 1}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {20, 1}, {25, 1}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {42, 5}, {40, 5}, CowMergeOperation::COW_XOR);
VABCPartitionWriter writer_{
partition_update_, install_part_, &dynamic_control_, kBlockSize};
EXPECT_CALL(dynamic_control_, OpenCowWriter(fake_part_name, _, false))
.WillOnce(Invoke([](const std::string&,
const std::optional<std::string>&,
bool) {
auto cow_writer =
std::make_unique<android::snapshot::MockSnapshotWriter>(
android::snapshot::CowOptions{});
auto expected_merge_sequence = {10, 14, 13, 20, 25, 40, 41, 42, 43, 44};
EXPECT_CALL(*cow_writer, Initialize()).WillOnce(Return(true));
EXPECT_CALL(*cow_writer, EmitSequenceData(_, _))
.With(Args<1, 0>(ElementsAreArray(expected_merge_sequence)))
.WillOnce(Return(true));
ON_CALL(*cow_writer, EmitCopy(_, _)).WillByDefault(Return(true));
ON_CALL(*cow_writer, EmitLabel(_)).WillByDefault(Return(true));
return cow_writer;
}));
EXPECT_CALL(dynamic_control_, GetVirtualAbCompressionXorFeatureFlag())
.WillRepeatedly(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
ASSERT_TRUE(writer_.Init(&install_plan_, true, 0));
}
TEST_F(VABCPartitionWriterTest, MergeSequenceXorSameBlock) {
AddMergeOp(&partition_update_, {19, 4}, {19, 3}, CowMergeOperation::COW_XOR)
->set_src_offset(1);
VABCPartitionWriter writer_{
partition_update_, install_part_, &dynamic_control_, kBlockSize};
EXPECT_CALL(dynamic_control_, OpenCowWriter(fake_part_name, _, false))
.WillOnce(Invoke(
[](const std::string&, const std::optional<std::string>&, bool) {
auto cow_writer =
std::make_unique<android::snapshot::MockSnapshotWriter>(
android::snapshot::CowOptions{});
auto expected_merge_sequence = {19, 20, 21};
EXPECT_CALL(*cow_writer, Initialize()).WillOnce(Return(true));
EXPECT_CALL(*cow_writer, EmitSequenceData(_, _))
.With(Args<1, 0>(ElementsAreArray(expected_merge_sequence)))
.WillOnce(Return(true));
ON_CALL(*cow_writer, EmitCopy(_, _)).WillByDefault(Return(true));
ON_CALL(*cow_writer, EmitLabel(_)).WillByDefault(Return(true));
return cow_writer;
}));
EXPECT_CALL(dynamic_control_, GetVirtualAbCompressionXorFeatureFlag())
.WillRepeatedly(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
ASSERT_TRUE(writer_.Init(&install_plan_, true, 0));
}
TEST_F(VABCPartitionWriterTest, EmitBlockTest) {
AddMergeOp(&partition_update_, {5, 1}, {10, 1}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {10, 1}, {15, 1}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {15, 2}, {20, 2}, CowMergeOperation::COW_COPY);
AddMergeOp(&partition_update_, {20, 1}, {25, 1}, CowMergeOperation::COW_COPY);
VABCPartitionWriter writer_{
partition_update_, install_part_, &dynamic_control_, kBlockSize};
EXPECT_CALL(dynamic_control_, OpenCowWriter(fake_part_name, _, false))
.WillOnce(Invoke(
[](const std::string&, const std::optional<std::string>&, bool) {
auto cow_writer =
std::make_unique<android::snapshot::MockSnapshotWriter>(
android::snapshot::CowOptions{});
Sequence s;
ON_CALL(*cow_writer, EmitCopy(_, _)).WillByDefault(Return(true));
ON_CALL(*cow_writer, EmitLabel(_)).WillByDefault(Return(true));
ON_CALL(*cow_writer, Initialize()).WillByDefault(Return(true));
EXPECT_CALL(*cow_writer, Initialize()).InSequence(s);
EXPECT_CALL(*cow_writer, EmitCopy(10, 5)).InSequence(s);
EXPECT_CALL(*cow_writer, EmitCopy(15, 10)).InSequence(s);
// libsnapshot want blocks in reverser order, so 21 goes before 20
EXPECT_CALL(*cow_writer, EmitCopy(21, 16)).InSequence(s);
EXPECT_CALL(*cow_writer, EmitCopy(20, 15)).InSequence(s);
EXPECT_CALL(*cow_writer, EmitCopy(25, 20)).InSequence(s);
return cow_writer;
}));
ASSERT_TRUE(writer_.Init(&install_plan_, true, 0));
}
std::string GetNoopBSDIFF(size_t data_size) {
auto zeros = GetReadonlyZeroBlock(data_size);
TemporaryFile patch_file;
int error = bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(zeros->data()),
zeros->size(),
reinterpret_cast<const uint8_t*>(zeros->data()),
zeros->size(),
patch_file.path,
nullptr);
if (error) {
LOG(ERROR) << "Failed to generate BSDIFF patch " << error;
return {};
}
std::string patch_data;
if (!utils::ReadFile(patch_file.path, &patch_data)) {
return {};
}
return patch_data;
}
TEST_F(VABCPartitionWriterTest, StreamXORBlockTest) {
AddMergeOp(&partition_update_, {5, 2}, {10, 2}, CowMergeOperation::COW_XOR);
AddMergeOp(&partition_update_, {8, 2}, {13, 2}, CowMergeOperation::COW_XOR);
auto install_op = partition_update_.add_operations();
*install_op->add_src_extents() = ExtentForRange(5, 5);
*install_op->add_dst_extents() = ExtentForRange(10, 5);
install_op->set_type(InstallOperation::SOURCE_BSDIFF);
auto data_hash = install_op->mutable_src_sha256_hash();
auto zeros = GetReadonlyZeroBlock(kBlockSize * 5);
brillo::Blob expected_hash;
truncate64(source_part_.path, kBlockSize * 20);
HashCalculator::RawHashOfBytes(zeros->data(), zeros->size(), &expected_hash);
data_hash->assign(reinterpret_cast<const char*>(expected_hash.data()),
expected_hash.size());
EXPECT_CALL(dynamic_control_, OpenCowWriter(fake_part_name, _, false))
.WillOnce(Invoke([](const std::string&,
const std::optional<std::string>&,
bool) {
auto cow_writer =
std::make_unique<android::snapshot::MockSnapshotWriter>(
android::snapshot::CowOptions{});
ON_CALL(*cow_writer, EmitLabel(_)).WillByDefault(Return(true));
auto expected_merge_sequence = {10, 11, 13, 14};
auto expected_merge_sequence_rev = {11, 10, 14, 13};
const bool is_ascending = android::base::GetBoolProperty(
"ro.virtual_ab.userspace.snapshots.enabled", false);
ON_CALL(*cow_writer, Initialize()).WillByDefault(Return(true));
if (!is_ascending) {
EXPECT_CALL(*cow_writer, EmitSequenceData(_, _))
.With(Args<1, 0>(ElementsAreArray(expected_merge_sequence_rev)))
.WillOnce(Return(true));
} else {
EXPECT_CALL(*cow_writer, EmitSequenceData(_, _))
.With(Args<1, 0>(ElementsAreArray(expected_merge_sequence)))
.WillOnce(Return(true));
}
EXPECT_CALL(*cow_writer, Initialize()).Times(1);
EXPECT_CALL(*cow_writer, EmitCopy(_, _)).Times(0);
EXPECT_CALL(*cow_writer, EmitRawBlocks(_, _, _)).WillOnce(Return(true));
EXPECT_CALL(*cow_writer, EmitXorBlocks(10, _, kBlockSize * 2, 5, 0))
.WillOnce(Return(true));
EXPECT_CALL(*cow_writer, EmitXorBlocks(13, _, kBlockSize * 2, 8, 0))
.WillOnce(Return(true));
return cow_writer;
}));
EXPECT_CALL(dynamic_control_, GetVirtualAbCompressionXorFeatureFlag())
.WillRepeatedly(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
VABCPartitionWriter writer_{
partition_update_, install_part_, &dynamic_control_, kBlockSize};
ASSERT_TRUE(writer_.Init(&install_plan_, true, 0));
const auto patch_data = GetNoopBSDIFF(kBlockSize * 5);
ASSERT_GT(patch_data.size(), 0UL);
ASSERT_TRUE(writer_.PerformDiffOperation(
*install_op, nullptr, patch_data.data(), patch_data.size()));
}
} // namespace
} // namespace chromeos_update_engine