|  | // Copyright 2020 The Fuchsia 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 "src/storage/extractor/c/extractor.h" | 
|  |  | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <unistd.h> | 
|  | #include <zircon/errors.h> | 
|  |  | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <cstdio> | 
|  | #include <tuple> | 
|  |  | 
|  | #include <fbl/unique_fd.h> | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include "src/storage/extractor/cpp/extractor.h" | 
|  |  | 
|  | namespace extractor { | 
|  | namespace { | 
|  |  | 
|  | std::tuple<fbl::unique_fd, fbl::unique_fd> setup_fds() { | 
|  | char kInFile[20] = "/tmp/payload.XXXXXX"; | 
|  | char kOutFile[20] = "/tmp/payload.XXXXXX"; | 
|  |  | 
|  | fbl::unique_fd out_file(mkstemp(kOutFile)); | 
|  | fbl::unique_fd in_file(mkstemp(kInFile)); | 
|  | EXPECT_TRUE(out_file); | 
|  | EXPECT_TRUE(in_file); | 
|  | const uint16_t kBlockSize = 8192; | 
|  | const uint16_t kBlockCount = 10; | 
|  | char buf[kBlockSize]; | 
|  | for (int i = 0; i < kBlockCount; i++) { | 
|  | memset(buf, i, sizeof(buf)); | 
|  | EXPECT_EQ(write(in_file.get(), buf, sizeof(buf)), static_cast<ssize_t>(sizeof(buf))); | 
|  | } | 
|  | return std::make_tuple(std::move(in_file), std::move(out_file)); | 
|  | } | 
|  |  | 
|  | TEST(Extractor, CreateInvalidAlignment) { | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = 0; | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | auto extractor = Extractor::Create(std::move(in), options, std::move(out)); | 
|  | ASSERT_TRUE(extractor.is_error()); | 
|  | ASSERT_EQ(extractor.error_value(), ZX_ERR_INVALID_ARGS); | 
|  | } | 
|  |  | 
|  | TEST(Extractor, CreateFailureWithInvalidFd) { | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = 1; | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | auto extractor = Extractor::Create(std::move(in), options, std::move(out)); | 
|  | ASSERT_TRUE(extractor.is_error()); | 
|  | ASSERT_EQ(extractor.error_value(), ZX_ERR_IO); | 
|  | } | 
|  |  | 
|  | TEST(Extractor, Create) { | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = 1; | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | std::tie(in, out) = setup_fds(); | 
|  | auto extractor_or = Extractor::Create(std::move(in), options, out.duplicate()); | 
|  | ASSERT_TRUE(extractor_or.is_ok()); | 
|  |  | 
|  | // If we don't issue a write, then file size should be 0. | 
|  | struct stat stats; | 
|  | ASSERT_EQ(fstat(out.get(), &stats), 0); | 
|  | ASSERT_EQ(stats.st_size, 0); | 
|  | } | 
|  |  | 
|  | TEST(Extractor, AddInvalidExtent) { | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = 1; | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | std::tie(in, out) = setup_fds(); | 
|  | auto extractor_or = Extractor::Create(std::move(in), options, std::move(out)); | 
|  | ASSERT_TRUE(extractor_or.is_ok()); | 
|  | auto extractor = std::move(extractor_or.value()); | 
|  | ExtentProperties properties = {.extent_kind = ExtentKind::Data, | 
|  | .data_kind = DataKind::Unmodified}; | 
|  | ASSERT_EQ(extractor->Add(10, 0, properties).error_value(), ZX_ERR_OUT_OF_RANGE); | 
|  | } | 
|  |  | 
|  | void verify_block(int fd, size_t size, off_t offset, uint8_t content) { | 
|  | uint8_t buffer[size]; | 
|  | ASSERT_EQ(pread(fd, buffer, size, offset), static_cast<ssize_t>(size)); | 
|  | for (size_t i = 0; i < size; i++) { | 
|  | ASSERT_EQ(buffer[i], content); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(Extractor, AddUnaligned) { | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = 1024; | 
|  |  | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | std::tie(in, out) = setup_fds(); | 
|  | auto extractor_or = Extractor::Create(std::move(in), options, std::move(out)); | 
|  | ASSERT_TRUE(extractor_or.is_ok()); | 
|  | auto extractor = std::move(extractor_or.value()); | 
|  | ExtentProperties properties = {.extent_kind = ExtentKind::Data, | 
|  | .data_kind = DataKind::Unmodified}; | 
|  | // Alignment is 1024 bytes and test is adding extent at offset 10. | 
|  | ASSERT_EQ(extractor->Add(10, 1, properties).error_value(), ZX_ERR_INVALID_ARGS); | 
|  | } | 
|  |  | 
|  | TEST(Extractor, Write) { | 
|  | constexpr uint16_t kBlockSize = 8192; | 
|  | ExtractorOptions options; | 
|  | options.add_checksum = false; | 
|  | options.force_dump_pii = false; | 
|  | options.alignment = kBlockSize; | 
|  |  | 
|  | fbl::unique_fd in; | 
|  | fbl::unique_fd out; | 
|  | std::tie(in, out) = setup_fds(); | 
|  | auto extractor_or = Extractor::Create(in.duplicate(), options, out.duplicate()); | 
|  | ASSERT_TRUE(extractor_or.is_ok()); | 
|  | auto extractor = std::move(extractor_or.value()); | 
|  | ExtentProperties properties = {.extent_kind = ExtentKind::Data, | 
|  | .data_kind = DataKind::Unmodified}; | 
|  | // Add a block starting at offset 5*8192 | 
|  | ASSERT_TRUE(extractor->Add(5 * kBlockSize, kBlockSize, properties).is_ok()); | 
|  | // Add two blocks starting at offset 8192 | 
|  | ASSERT_TRUE(extractor->Add(1 * kBlockSize, 2 * kBlockSize, properties).is_ok()); | 
|  |  | 
|  | ASSERT_TRUE(extractor->Write().is_ok()); | 
|  |  | 
|  | EXPECT_EQ(lseek(out.get(), SEEK_SET, 0), 0); | 
|  | struct stat stats; | 
|  | EXPECT_EQ(fstat(out.get(), &stats), 0); | 
|  |  | 
|  | // We should have 5 block in image file. Header, extent cluster, and 3 data blocks. | 
|  | ASSERT_EQ(stats.st_size, 5 * kBlockSize); | 
|  |  | 
|  | // We know the content of data blocks written. Verify that those blocks are | 
|  | // in the image file. | 
|  | // input file's block 'n' contens all 'n'. | 
|  | // Skip first two block of the image file as they contain image header. Block | 
|  | // 2, 3 4 should have out data. | 
|  | verify_block(out.get(), kBlockSize, 2 * kBlockSize, 1); | 
|  | verify_block(out.get(), kBlockSize, 3 * kBlockSize, 2); | 
|  | verify_block(out.get(), kBlockSize, 4 * kBlockSize, 5); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace extractor |