[inspect] Add snapshot wrapper.

This change adds a new Snapshot object that handles reading a copy of a
Inspect VMO from a read-only VMO. By default, it verifies the included
header and generation count to load a consistent view of the VMO buffer.

TEST=runtests -t inspect-test
CF-216: #progress

Change-Id: I06107999232eac6032836ed181bddd632db1b509
diff --git a/system/ulib/inspect/include/lib/inspect/snapshot.h b/system/ulib/inspect/include/lib/inspect/snapshot.h
new file mode 100644
index 0000000..f1b5923
--- /dev/null
+++ b/system/ulib/inspect/include/lib/inspect/snapshot.h
@@ -0,0 +1,93 @@
+// Copyright 2019 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.
+
+#pragma once
+
+#include <fbl/array.h>
+#include <fbl/function.h>
+#include <lib/inspect/block.h>
+#include <lib/zx/vmo.h>
+#include <unistd.h>
+#include <zircon/types.h>
+
+namespace inspect {
+
+// |Snapshot| parses an incoming VMO buffer and produces a snapshot of
+// the VMO contents. |Snapshot::Options| determines the behavior of
+// snapshotting if a concurrent write potentially occurred.
+//
+// Example:
+// fbl::unique_ptr<Snapshot> snapshot;
+// zx_status_t status = Snapshot::Create(std::move(vmo),
+//   {.read_attempts = 1024, .skip_consistency_check = false},
+//   &snapshot);
+//
+// Test Example:
+// zx_status_t status = Snapshot::Create(std::move(vmo),
+//   {.read_attempts = 1024, .skip_consistency_check = false},
+//   fbl::make_unique<TestCallback>(),
+//   &snapshot);
+class Snapshot final {
+public:
+    struct Options {
+        // The number of attempts to read a consistent snapshot.
+        // Reading fails if the number of attempts exceeds this number.
+        int read_attempts;
+
+        // If true, skip checking the buffer for consistency.
+        bool skip_consistency_check;
+    };
+
+    // Type for observing reads on the VMO.
+    using ReadObserver = fbl::Function<void(uint8_t* buffer, size_t buffer_size)>;
+
+    // By default, ensure consistency of the incoming Inspect VMO and retry up to
+    // 1024 times.
+    static constexpr Options kDefaultOptions = {.read_attempts = 1024,
+                                                .skip_consistency_check = false};
+
+    // Create a new snapshot of the given VMO and default options.
+    static zx_status_t Create(zx::vmo vmo, Snapshot* out_snapshot);
+
+    // Create a new snapshot of the given VMO and given options.
+    static zx_status_t Create(zx::vmo vmo, Options options, Snapshot* out_snapshot);
+
+    // Create a new snapshot of the given VMO, given options, and the given read observer
+    // for observing snapshot operations.
+    static zx_status_t Create(zx::vmo vmo, Options options, ReadObserver read_observer,
+                              Snapshot* out_snapshot);
+
+    Snapshot() = default;
+    ~Snapshot() = default;
+    Snapshot(Snapshot&&) = default;
+    Snapshot& operator=(Snapshot&&) = default;
+
+    operator bool() const { return buffer_.size() > 0; }
+
+    // Returns the start of the snapshot data, if valid.
+    const uint8_t* data() const { return buffer_.begin(); }
+
+    // Returns the size of the snapshot, if valid.
+    size_t size() const { return buffer_.size(); }
+
+    // Get a pointer to a block in the buffer by index.
+    // Returns nullptr if the index is out of bounds.
+    internal::Block* GetBlock(internal::BlockIndex index) const;
+
+private:
+    // Read from the VMO into a buffer.
+    static zx_status_t Read(zx::vmo& vmo, size_t size, uint8_t* buffer);
+
+    // Parse the header from a buffer and obtain the generation count.
+    static zx_status_t ParseHeader(uint8_t* buffer, uint64_t* out_generation_count);
+
+    // Take a new snapshot of the VMO with default options.
+    // If reading fails, the boolean value of the constructed |Snapshot| will be false.
+    explicit Snapshot(fbl::Array<uint8_t> buffer);
+
+    // The buffer storing the snapshot.
+    fbl::Array<uint8_t> buffer_;
+};
+
+} // namespace inspect
diff --git a/system/ulib/inspect/rules.mk b/system/ulib/inspect/rules.mk
index dd92fb8..102ec0e 100644
--- a/system/ulib/inspect/rules.mk
+++ b/system/ulib/inspect/rules.mk
@@ -13,6 +13,7 @@
 MODULE_SRCS += \
     $(LOCAL_DIR)/heap.cpp \
     $(LOCAL_DIR)/scanner.cpp \
+    $(LOCAL_DIR)/snapshot.cpp \
 
 MODULE_HEADER_DEPS := \
     system/ulib/zircon-internal \
@@ -40,6 +41,7 @@
     $(TEST_DIR)/main.cpp \
     $(TEST_DIR)/heap_tests.cpp \
     $(TEST_DIR)/scanner_tests.cpp \
+    $(TEST_DIR)/snapshot_tests.cpp \
 
 MODULE_STATIC_LIBS := \
     system/ulib/inspect \
diff --git a/system/ulib/inspect/snapshot.cpp b/system/ulib/inspect/snapshot.cpp
new file mode 100644
index 0000000..bb9cb7a
--- /dev/null
+++ b/system/ulib/inspect/snapshot.cpp
@@ -0,0 +1,114 @@
+// Copyright 2019 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/inspect/block.h>
+#include <lib/inspect/snapshot.h>
+
+namespace inspect {
+
+using internal::Block;
+
+Snapshot::Snapshot(fbl::Array<uint8_t> buffer) : buffer_(std::move(buffer)) {}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Snapshot* out_snapshot) {
+    return Snapshot::Create(std::move(vmo), kDefaultOptions, out_snapshot);
+}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Options options, Snapshot* out_snapshot) {
+    return Snapshot::Create(std::move(vmo), std::move(options), nullptr, out_snapshot);
+}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Options options, ReadObserver read_observer,
+                             Snapshot* out_snapshot) {
+    size_t tries_left = options.read_attempts;
+
+    zx_status_t status;
+    fbl::Array<uint8_t> buffer;
+
+    while (tries_left-- > 0) {
+        size_t size;
+        status = vmo.get_size(&size);
+        if (status != ZX_OK) {
+            return status;
+        }
+        if (size < sizeof(internal::Block)) {
+            return ZX_ERR_OUT_OF_RANGE;
+        }
+        if (buffer.size() != size) {
+            buffer.reset(new uint8_t[size], size);
+        }
+
+        status = Snapshot::Read(vmo, sizeof(internal::Block), buffer.begin());
+        if (status != ZX_OK) {
+            return status;
+        }
+        if (read_observer) {
+            read_observer(buffer.begin(), sizeof(internal::Block));
+        }
+
+        uint64_t generation;
+        status = Snapshot::ParseHeader(buffer.begin(), &generation);
+        if (status != ZX_OK) {
+            return status;
+        }
+
+        if (!options.skip_consistency_check && generation % 2 != 0) {
+            continue;
+        }
+
+        status = Snapshot::Read(vmo, size, buffer.begin());
+        if (status != ZX_OK) {
+            return status;
+        }
+        if (read_observer) {
+            read_observer(buffer.begin(), sizeof(size));
+        }
+
+        uint64_t new_generation;
+        status = Snapshot::ParseHeader(buffer.begin(), &new_generation);
+        if (status != ZX_OK) {
+          return status;
+        }
+        if (!options.skip_consistency_check && generation != new_generation) {
+            continue;
+        }
+
+        size_t new_size;
+        if (vmo.get_size(&new_size) != ZX_OK) {
+            return ZX_ERR_INTERNAL;
+        }
+        if (new_size != size) {
+            continue;
+        }
+
+        *out_snapshot = Snapshot(std::move(buffer));
+
+        return ZX_OK;
+    }
+
+    return ZX_ERR_INTERNAL;
+}
+
+zx_status_t Snapshot::Read(zx::vmo& vmo, size_t size, uint8_t* buffer) {
+    memset(buffer, 0, size);
+    return vmo.read(buffer, 0, size);
+}
+
+zx_status_t Snapshot::ParseHeader(uint8_t* buffer, uint64_t* out_generation_count) {
+    Block* block = reinterpret_cast<Block*>(buffer);
+    if (memcmp(&block->header_data[4], kMagicNumber, 4) != 0) {
+        return ZX_ERR_INTERNAL;
+    }
+    *out_generation_count = block->payload.u64;
+    return ZX_OK;
+}
+
+internal::Block* Snapshot::GetBlock(internal::BlockIndex index) const {
+    if (index >= IndexForOffset(buffer_.size())) {
+        return nullptr;
+    }
+    return reinterpret_cast<internal::Block*>(buffer_.begin() + index * kMinOrderSize);
+}
+
+} // namespace inspect
diff --git a/system/ulib/inspect/test/snapshot_tests.cpp b/system/ulib/inspect/test/snapshot_tests.cpp
new file mode 100644
index 0000000..c88b142
--- /dev/null
+++ b/system/ulib/inspect/test/snapshot_tests.cpp
@@ -0,0 +1,194 @@
+// 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/block.h>
+#include <lib/inspect/snapshot.h>
+#include <unittest/unittest.h>
+
+namespace {
+
+using inspect::BlockType;
+using inspect::Snapshot;
+using inspect::internal::Block;
+using inspect::internal::HeaderBlockFields;
+
+bool ValidRead() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    memset(vmo.start(), 'a', 4096);
+    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], inspect::kMagicNumber, 4);
+    header->payload.u64 = 0;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+    EXPECT_EQ(ZX_OK, status);
+    EXPECT_EQ(4096, snapshot.size());
+
+    // Make sure that the data was actually fully copied to the snapshot.
+    uint8_t buf[snapshot.size() - sizeof(Block)];
+    memset(buf, 'a', snapshot.size() - sizeof(Block));
+    EXPECT_EQ(0, memcmp(snapshot.data() + sizeof(Block), buf, snapshot.size() - sizeof(Block)));
+
+    END_TEST;
+}
+
+bool InvalidWritePending() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    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], inspect::kMagicNumber, 4);
+    header->payload.u64 = 1;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+    EXPECT_EQ(ZX_ERR_INTERNAL, status);
+    EXPECT_FALSE(snapshot);
+
+    END_TEST;
+}
+
+bool ValidPendingSkipCheck() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    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], inspect::kMagicNumber, 4);
+    header->payload.u64 = 1;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(
+        std::move(dup), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
+    EXPECT_EQ(ZX_OK, status);
+    EXPECT_EQ(4096, snapshot.size());
+
+    END_TEST;
+}
+
+bool InvalidGenerationChange() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    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], inspect::kMagicNumber, 4);
+    header->payload.u64 = 0;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(
+        std::move(dup), Snapshot::kDefaultOptions,
+        [header] (uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
+
+    EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+    END_TEST;
+}
+
+bool ValidGenerationChangeSkipCheck() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    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], inspect::kMagicNumber, 4);
+    header->payload.u64 = 0;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(
+        std::move(dup), {.read_attempts = 100, .skip_consistency_check = true},
+        [header] (uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
+
+    EXPECT_EQ(ZX_OK, status);
+    EXPECT_EQ(4096, snapshot.size());
+
+    END_TEST;
+}
+
+bool InvalidBadMagicNumber() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    Block* header = reinterpret_cast<Block*>(vmo.start());
+    header->header = HeaderBlockFields::Order::Make(0) |
+                     HeaderBlockFields::Type::Make(BlockType::kHeader) |
+                     HeaderBlockFields::Version::Make(0);
+    header->payload.u64 = 0;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+    EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+    END_TEST;
+}
+
+bool InvalidBadMagicNumberSkipCheck() {
+    BEGIN_TEST;
+
+    fzl::OwnedVmoMapper vmo;
+    ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+    Block* header = reinterpret_cast<Block*>(vmo.start());
+    header->header = HeaderBlockFields::Order::Make(0) |
+                     HeaderBlockFields::Type::Make(BlockType::kHeader) |
+                     HeaderBlockFields::Version::Make(0);
+    header->payload.u64 = 0;
+
+    zx::vmo dup;
+    ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+    Snapshot snapshot;
+    zx_status_t status = Snapshot::Create(
+        std::move(dup), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
+
+    EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(SnapshotTests)
+RUN_TEST(ValidRead)
+RUN_TEST(InvalidWritePending)
+RUN_TEST(ValidPendingSkipCheck)
+RUN_TEST(InvalidGenerationChange)
+RUN_TEST(ValidGenerationChangeSkipCheck)
+RUN_TEST(InvalidBadMagicNumber)
+RUN_TEST(InvalidBadMagicNumberSkipCheck)
+END_TEST_CASE(SnapshotTests)