blob: 552ef9dbc5de87daf960fe1b2adb184712bcd9c3 [file] [log] [blame]
// Copyright 2018 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 <lib/fzl/owned-vmo-mapper.h>
#include <lib/inspect/cpp/vmo/block.h>
#include <lib/inspect/cpp/vmo/limits.h>
#include <lib/inspect/cpp/vmo/snapshot.h>
#include <zxtest/zxtest.h>
namespace {
using inspect::BackingBuffer;
using inspect::Snapshot;
using inspect::internal::Block;
using inspect::internal::BlockType;
using inspect::internal::FreeBlockFields;
using inspect::internal::GetBlock;
using inspect::internal::HeaderBlockFields;
using inspect::internal::kMagicNumber;
using inspect::internal::kMaxVmoSize;
using inspect::internal::kMinOrderSize;
using inspect::internal::kMinVmoSize;
using inspect::internal::kVmoHeaderBlockSize;
using inspect::internal::kVmoHeaderOrder;
using inspect::internal::SetHeaderVmoSize;
TEST(Snapshot, ValidRead) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
memset(vmo.start(), 'a', 4096);
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
SetHeaderVmoSize(header, vmo.size());
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
ASSERT_OK(status);
ASSERT_EQ(4096u, snapshot.size());
// Make sure that the data was actually fully copied to the snapshot.
std::vector<uint8_t> buf;
buf.resize(4096u - kVmoHeaderBlockSize);
memset(buf.data(), 'a', buf.size());
EXPECT_EQ(0, memcmp(snapshot.data() + kVmoHeaderBlockSize, buf.data(), buf.size()));
}
TEST(Snapshot, ReadFailsWithBadVersion) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(1ul << 15);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
}
TEST(Snapshot, InvalidBufferSize) {
for (size_t i = 0; i < kMinOrderSize; i++) {
Snapshot snapshot;
std::vector<uint8_t> buffer;
buffer.resize(i);
EXPECT_EQ(ZX_ERR_INVALID_ARGS, Snapshot::Create(BackingBuffer(std::move(buffer)), &snapshot));
}
}
TEST(Snapshot, GetBlock) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
memset(vmo.start(), 'a', 4096);
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
SetHeaderVmoSize(header, vmo.size());
{
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_OK(status);
// Can get block 0.
EXPECT_NE(nullptr, GetBlock(&snapshot, 0));
// Cannot get block past the end of the snapshot.
EXPECT_EQ(nullptr, GetBlock(&snapshot, 4096 / kMinOrderSize));
}
Block* tester =
reinterpret_cast<Block*>(reinterpret_cast<uint8_t*>(vmo.start()) + 4096 - kMinOrderSize * 2);
size_t tester_index = 4096 / kMinOrderSize - 2;
{
tester->header =
FreeBlockFields::Order::Make(1) | FreeBlockFields::Type::Make(BlockType::kFree);
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_OK(status);
// Can get tester, since it's adjacent to the end of the snapshot.
EXPECT_NE(nullptr, GetBlock(&snapshot, tester_index));
}
{
// Set the order to be too large for the buffer
tester->header =
FreeBlockFields::Order::Make(2) | FreeBlockFields::Type::Make(BlockType::kFree);
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_OK(status);
// Cannot get block, since its order is too large for the remaining space.
EXPECT_EQ(nullptr, GetBlock(&snapshot, tester_index));
}
}
TEST(Snapshot, InvalidWritePending) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 1;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
EXPECT_FALSE(!!snapshot);
}
TEST(Snapshot, ValidPendingSkipCheck) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 1;
SetHeaderVmoSize(header, vmo.size());
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
EXPECT_OK(status);
EXPECT_EQ(4096u, snapshot.size());
}
TEST(Snapshot, InvalidGenerationChange) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), Snapshot::kDefaultOptions,
[header](const uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
}
TEST(Snapshot, InvalidGenerationChangeFinalStep) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
int times_called = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), Snapshot::Options{.read_attempts = 1, .skip_consistency_check = false},
[&](const uint8_t* buffer, size_t buffer_size) {
// Only change the generation count after the second read obtaining the whole buffer.
if (++times_called == 2) {
header->payload.u64 += 2;
}
},
&snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
}
TEST(Snapshot, ValidGenerationChangeSkipCheck) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
SetHeaderVmoSize(header, vmo.size());
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), {.read_attempts = 100, .skip_consistency_check = true},
[header](const uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
EXPECT_OK(status);
EXPECT_EQ(4096u, snapshot.size());
}
TEST(Snapshot, InvalidBadMagicNumber) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
header->payload.u64 = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
}
TEST(Snapshot, InvalidBadMagicNumberSkipCheck) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
header->payload.u64 = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
EXPECT_EQ(ZX_ERR_INTERNAL, status);
}
TEST(Snapshot, FrozenVmo) {
fzl::OwnedVmoMapper vmo;
ASSERT_OK(vmo.CreateAndMap(4096, "test"));
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = inspect::internal::kVmoFrozen;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(
vmo.vmo(), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
EXPECT_EQ(ZX_OK, status);
const auto* header_block = reinterpret_cast<Block const*>(snapshot.data());
EXPECT_EQ(inspect::internal::kVmoFrozen, header_block->payload.u64);
}
TEST(Snapshot, VmoWithUnusedSpace) {
fzl::OwnedVmoMapper vmo;
size_t size = 4 * kMinVmoSize;
ASSERT_OK(vmo.CreateAndMap(size, "test"));
memset(vmo.start(), 'a', size);
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
SetHeaderVmoSize(header, kMinVmoSize);
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
ASSERT_OK(status);
ASSERT_EQ(kMinVmoSize, snapshot.size());
// Make sure that the data was actually fully copied to the snapshot.
std::vector<uint8_t> buf;
buf.resize(kMinVmoSize - kVmoHeaderBlockSize);
memset(buf.data(), 'a', buf.size());
EXPECT_EQ(0, memcmp(snapshot.data() + kVmoHeaderBlockSize, buf.data(), buf.size()));
}
TEST(Snapshot, TruncatedSnapshotOfVeryLargeVmo) {
fzl::OwnedVmoMapper vmo;
size_t size = 2 * kMaxVmoSize;
ASSERT_OK(vmo.CreateAndMap(size, "test"));
memset(vmo.start(), 'a', size);
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(kVmoHeaderOrder) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
SetHeaderVmoSize(header, vmo.size());
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
ASSERT_OK(status);
ASSERT_EQ(kMaxVmoSize, snapshot.size());
// Make sure that the data was actually fully copied to the snapshot.
std::vector<uint8_t> buf;
buf.resize(kMaxVmoSize - kVmoHeaderBlockSize);
memset(buf.data(), 'a', buf.size());
EXPECT_EQ(0, memcmp(snapshot.data() + kVmoHeaderBlockSize, buf.data(), buf.size()));
}
TEST(Snapshot, HeaderWithoutSizeInfo) {
fzl::OwnedVmoMapper vmo;
size_t size = 2 * kMinVmoSize;
ASSERT_OK(vmo.CreateAndMap(size, "test"));
memset(vmo.start(), 'a', size);
Block* header = reinterpret_cast<Block*>(vmo.start());
header->header = HeaderBlockFields::Order::Make(0) |
HeaderBlockFields::Type::Make(BlockType::kHeader) |
HeaderBlockFields::Version::Make(0);
memcpy(&header->header_data[4], kMagicNumber, 4);
header->payload.u64 = 0;
Snapshot snapshot;
zx_status_t status = Snapshot::Create(vmo.vmo(), &snapshot);
ASSERT_OK(status);
ASSERT_EQ(size, snapshot.size());
// Make sure that the data was actually fully copied to the snapshot.
std::vector<uint8_t> buf;
buf.resize(size - kVmoHeaderBlockSize);
memset(buf.data(), 'a', buf.size());
EXPECT_EQ(0, memcmp(snapshot.data() + kVmoHeaderBlockSize, buf.data(), buf.size()));
}
} // namespace