pw_snapshot: Add helper to read snapshot UUIDs
Adds a helper that reads the UUID of an in-memory snapshot.
Change-Id: Ie8ec8c5b5431fcab6b7bac817644fcc38b997f40
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/61781
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_snapshot/BUILD.bazel b/pw_snapshot/BUILD.bazel
index 19fde86..38816e9 100644
--- a/pw_snapshot/BUILD.bazel
+++ b/pw_snapshot/BUILD.bazel
@@ -15,6 +15,7 @@
load(
"//pw_build:pigweed.bzl",
"pw_cc_library",
+ "pw_cc_test",
)
package(default_visibility = ["//visibility:public"])
@@ -23,12 +24,18 @@
pw_cc_library(
name = "uuid",
+ srcs = [
+ "uuid.cc",
+ ],
hdrs = [
"public/pw_snapshot/uuid.h",
],
includes = ["public"],
deps = [
+ ":metadata_proto",
"//pw_bytes",
+ "//pw_protobuf",
+ "//pw_status",
],
)
@@ -56,3 +63,18 @@
"cpp_compile_test.cc",
],
)
+
+pw_cc_test(
+ name = "uuid_test",
+ srcs = [
+ "uuid_test.cc",
+ ],
+ deps = [
+ ":metadata_proto",
+ ":uuid",
+ "//pw_bytes",
+ "//pw_protobuf",
+ "//pw_result",
+ "//pw_status",
+ ],
+)
diff --git a/pw_snapshot/BUILD.gn b/pw_snapshot/BUILD.gn
index 3b1d920..4a6c8c1 100644
--- a/pw_snapshot/BUILD.gn
+++ b/pw_snapshot/BUILD.gn
@@ -27,7 +27,16 @@
pw_source_set("uuid") {
public_configs = [ ":public_include_path" ]
public = [ "public/pw_snapshot/uuid.h" ]
- public_deps = [ dir_pw_bytes ]
+ public_deps = [
+ dir_pw_bytes,
+ dir_pw_result,
+ dir_pw_status,
+ ]
+ deps = [
+ ":metadata_proto.pwpb",
+ dir_pw_protobuf,
+ ]
+ sources = [ "uuid.cc" ]
}
group("pw_snapshot") {
@@ -71,7 +80,10 @@
}
pw_test_group("tests") {
- tests = [ ":cpp_compile_test" ]
+ tests = [
+ ":cpp_compile_test",
+ ":uuid_test",
+ ]
}
# An empty test to ensure the proto libraries compile correctly.
@@ -82,3 +94,15 @@
dir_pw_protobuf,
]
}
+
+pw_test("uuid_test") {
+ sources = [ "uuid_test.cc" ]
+ deps = [
+ ":metadata_proto.pwpb",
+ ":uuid",
+ dir_pw_bytes,
+ dir_pw_protobuf,
+ dir_pw_result,
+ dir_pw_status,
+ ]
+}
diff --git a/pw_snapshot/module_usage.rst b/pw_snapshot/module_usage.rst
index e50f399..be29b4d 100644
--- a/pw_snapshot/module_usage.rst
+++ b/pw_snapshot/module_usage.rst
@@ -176,3 +176,34 @@
matcher: processor.ElfMatcher = lambda snapshot: _snapshot_elf_matcher(
fw_bundle_dir, snapshot)
return processor.process_snapshots(snapshot, DETOKENIZER, matcher)
+
+-------------
+C++ Utilities
+-------------
+
+UUID utilities
+==============
+Snapshot UUIDs are used to uniquely identify snapshots. Pigweed strongly
+recommends using randomly generated data as a snapshot UUID. The
+more entropy and random bits, the lower the probability that two devices will
+produce the same UUID for a snapshot. 16 bytes should be sufficient for most
+projects, so this module provides ``UuidSpan`` and ``ConstUuidSpan`` types that
+can be helpful for referring to UUID-sized byte spans.
+
+Reading a snapshot's UUID
+-------------------------
+An in-memory snapshot's UUID may be read using ``ReadUuidFromSnapshot()``.
+
+.. code-block:: cpp
+
+ void NotifyNewSnapshot(ConstByteSpan snapshot) {
+ std::array<std::byte, pw::snapshot::kUuidSizeBytes> uuid;
+ pw::Result<pw::ConstByteSpan> result =
+ pw::snapshot::ReadUuidFromSnapshot(snapshot, uuid);
+ if (!result.ok()) {
+ PW_LOG_ERROR("Failed to read UUID from new snapshot, error code %d",
+ static_cast<int>(result.status().code()));
+ return;
+ }
+ LogNewSnapshotUuid(result.value());
+ }
diff --git a/pw_snapshot/public/pw_snapshot/uuid.h b/pw_snapshot/public/pw_snapshot/uuid.h
index 7c6ac9a..9946a36 100644
--- a/pw_snapshot/public/pw_snapshot/uuid.h
+++ b/pw_snapshot/public/pw_snapshot/uuid.h
@@ -17,6 +17,7 @@
#include <span>
#include "pw_bytes/span.h"
+#include "pw_result/result.h"
namespace pw::snapshot {
@@ -28,4 +29,15 @@
using UuidSpan = std::span<std::byte, kUuidSizeBytes>;
using ConstUuidSpan = std::span<const std::byte, kUuidSizeBytes>;
+// Reads the snapshot UUID from an in memory snapshot, if present, and returns
+// the subspan of `output` that contains the read snapshot.
+//
+// Returns:
+// OK - UUID found, status with size indicates number of bytes written to
+// `output`.
+// RESOURCE_EXHUASTED - UUID found, but `output` was too small to fit it.
+// NOT_FOUND - No snapshot UUID found in the provided snapshot.
+Result<ConstByteSpan> ReadUuidFromSnapshot(ConstByteSpan snapshot,
+ UuidSpan output);
+
} // namespace pw::snapshot
diff --git a/pw_snapshot/uuid.cc b/pw_snapshot/uuid.cc
new file mode 100644
index 0000000..420d5e3
--- /dev/null
+++ b/pw_snapshot/uuid.cc
@@ -0,0 +1,67 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+// https://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 "pw_snapshot/uuid.h"
+
+#include <cstddef>
+#include <span>
+
+#include "pw_bytes/span.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_result/result.h"
+#include "pw_snapshot_metadata_proto/snapshot_metadata.pwpb.h"
+#include "pw_status/try.h"
+
+namespace pw::snapshot {
+
+using protobuf::Decoder;
+
+Result<ConstByteSpan> ReadUuidFromSnapshot(ConstByteSpan snapshot,
+ UuidSpan output) {
+ Decoder decoder(snapshot);
+ ConstByteSpan metadata;
+ while (decoder.Next().ok()) {
+ if (decoder.FieldNumber() ==
+ static_cast<uint32_t>(
+ pw::snapshot::SnapshotBasicInfo::Fields::METADATA)) {
+ PW_TRY(decoder.ReadBytes(&metadata));
+ break;
+ }
+ }
+ if (metadata.empty()) {
+ return Status::NotFound();
+ }
+
+ // Start to read from the metadata.
+ decoder.Reset(metadata);
+ ConstByteSpan snapshot_uuid;
+ while (decoder.Next().ok()) {
+ if (decoder.FieldNumber() ==
+ static_cast<uint32_t>(pw::snapshot::Metadata::Fields::SNAPSHOT_UUID)) {
+ PW_TRY(decoder.ReadBytes(&snapshot_uuid));
+ break;
+ }
+ }
+ if (snapshot_uuid.empty()) {
+ return Status::NotFound();
+ }
+ if (snapshot_uuid.size_bytes() > output.size_bytes()) {
+ return Status::ResourceExhausted();
+ }
+
+ memcpy(output.data(), snapshot_uuid.data(), snapshot_uuid.size_bytes());
+ return ConstByteSpan(output.first(snapshot_uuid.size_bytes()));
+}
+
+} // namespace pw::snapshot
diff --git a/pw_snapshot/uuid_test.cc b/pw_snapshot/uuid_test.cc
new file mode 100644
index 0000000..795071e
--- /dev/null
+++ b/pw_snapshot/uuid_test.cc
@@ -0,0 +1,102 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+// https://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 "pw_snapshot/uuid.h"
+
+#include <array>
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/span.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_result/result.h"
+#include "pw_snapshot_metadata_proto/snapshot_metadata.pwpb.h"
+#include "pw_status/status.h"
+
+namespace pw::snapshot {
+namespace {
+
+ConstByteSpan EncodeSnapshotWithUuid(ConstByteSpan uuid, ByteSpan dest) {
+ SnapshotBasicInfo::MemoryEncoder snapshot_encoder(dest);
+ {
+ Metadata::StreamEncoder metadata_encoder =
+ snapshot_encoder.GetMetadataEncoder();
+ EXPECT_EQ(OkStatus(), metadata_encoder.WriteSnapshotUuid(uuid));
+ }
+ EXPECT_EQ(OkStatus(), snapshot_encoder.status());
+
+ return snapshot_encoder;
+}
+
+TEST(ReadUuid, ReadUuid) {
+ const std::array<uint8_t, 8> kExpectedUuid = {
+ 0x1F, 0x8F, 0xBF, 0xC4, 0x86, 0x0E, 0xED, 0xD4};
+ std::array<std::byte, 16> snapshot_buffer;
+ ConstByteSpan snapshot = EncodeSnapshotWithUuid(
+ std::as_bytes(std::span(kExpectedUuid)), snapshot_buffer);
+
+ std::array<std::byte, kUuidSizeBytes> uuid_dest;
+ Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
+ EXPECT_EQ(OkStatus(), result.status());
+ EXPECT_EQ(kExpectedUuid.size(), result->size());
+ EXPECT_EQ(0, memcmp(result->data(), kExpectedUuid.data(), result->size()));
+}
+
+TEST(ReadUuid, NoUuid) {
+ std::array<std::byte, 16> snapshot_buffer;
+
+ // Write some snapshot metadata, but no UUID.
+ SnapshotBasicInfo::MemoryEncoder snapshot_encoder(snapshot_buffer);
+ {
+ Metadata::StreamEncoder metadata_encoder =
+ snapshot_encoder.GetMetadataEncoder();
+ EXPECT_EQ(OkStatus(), metadata_encoder.WriteFatal(true));
+ }
+ EXPECT_EQ(OkStatus(), snapshot_encoder.status());
+
+ ConstByteSpan snapshot(snapshot_encoder);
+ std::array<std::byte, kUuidSizeBytes> uuid_dest;
+ Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
+ EXPECT_EQ(Status::NotFound(), result.status());
+}
+
+TEST(ReadUuid, UndersizedBuffer) {
+ const std::array<uint8_t, 17> kExpectedUuid = {0xF4,
+ 0x1B,
+ 0xE1,
+ 0x2D,
+ 0x10,
+ 0x9B,
+ 0xB2,
+ 0x1A,
+ 0x88,
+ 0xE0,
+ 0xC4,
+ 0x77,
+ 0xCA,
+ 0x18,
+ 0x83,
+ 0xB5,
+ 0xBB};
+ std::array<std::byte, 32> snapshot_buffer;
+ ConstByteSpan snapshot = EncodeSnapshotWithUuid(
+ std::as_bytes(std::span(kExpectedUuid)), snapshot_buffer);
+
+ std::array<std::byte, kUuidSizeBytes> uuid_dest;
+ Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
+ EXPECT_EQ(Status::ResourceExhausted(), result.status());
+}
+
+} // namespace
+} // namespace pw::snapshot