// // 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 #include #include #include #include #include #include #include #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 src_extent, std::array 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(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&, bool) { auto cow_writer = std::make_unique( 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&, bool) { auto cow_writer = std::make_unique( 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&, bool) { auto cow_writer = std::make_unique( 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(zeros->data()), zeros->size(), reinterpret_cast(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(expected_hash.data()), expected_hash.size()); EXPECT_CALL(dynamic_control_, OpenCowWriter(fake_part_name, _, false)) .WillOnce(Invoke([](const std::string&, const std::optional&, bool) { auto cow_writer = std::make_unique( 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