pw_bluetooth_proxy: Add some emboss helper functions

Bug: 326499611
Test: CQ
Change-Id: Ifc1af4f0e139fe3f5ff7f55fcf5c3b51d78ef64a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/205741
Pigweed-Auto-Submit: David Rees <drees@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Reviewed-by: Ben Lawson <benlawson@google.com>
diff --git a/pw_bluetooth_proxy/BUILD.bazel b/pw_bluetooth_proxy/BUILD.bazel
index fa5a4b2..63b28eb 100644
--- a/pw_bluetooth_proxy/BUILD.bazel
+++ b/pw_bluetooth_proxy/BUILD.bazel
@@ -21,6 +21,8 @@
 filegroup(
     name = "files_not_in_bazel_yet",
     srcs = [
+        "emboss_util.h",
+        "emboss_util_test.cc",
         "hci_proxy.cc",
         "passthrough_test.cc",
         "public/pw_bluetooth_proxy/common.h",
diff --git a/pw_bluetooth_proxy/BUILD.gn b/pw_bluetooth_proxy/BUILD.gn
index 5cf8abf..30db653 100644
--- a/pw_bluetooth_proxy/BUILD.gn
+++ b/pw_bluetooth_proxy/BUILD.gn
@@ -34,7 +34,10 @@
 }
 
 pw_test_group("tests") {
-  tests = [ ":proxy_passthrough_test" ]
+  tests = [
+    ":pw_bluetooth_proxy_test",
+    ":emboss_util_test",
+  ]
 }
 
 pw_source_set("pw_bluetooth_proxy") {
@@ -53,10 +56,29 @@
   sources = [ "hci_proxy.cc" ]
 }
 
-pw_test("proxy_passthrough_test") {
+pw_test("emboss_util_test") {
   enable_if =
       dir_pw_third_party_emboss != "" && pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "passthrough_test.cc" ]
+  sources = [
+    "emboss_util.h",
+    "emboss_util_test.cc",
+  ]
+  include_dirs = [ "." ]
+  deps = [
+    ":pw_bluetooth_proxy",
+    "//pw_bluetooth:emboss_hci_test",
+    "//pw_span",
+  ]
+}
+
+pw_test("pw_bluetooth_proxy_test") {
+  enable_if =
+      dir_pw_third_party_emboss != "" && pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+  sources = [
+    "emboss_util.h",
+    "passthrough_test.cc",
+  ]
+  include_dirs = [ "." ]
   deps = [
     ":pw_bluetooth_proxy",
     "$dir_pw_third_party/fuchsia:stdcompat",
diff --git a/pw_bluetooth_proxy/emboss_util.h b/pw_bluetooth_proxy/emboss_util.h
new file mode 100644
index 0000000..ebc3207
--- /dev/null
+++ b/pw_bluetooth_proxy/emboss_util.h
@@ -0,0 +1,39 @@
+// Copyright 2024 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.
+#pragma once
+
+#include <type_traits>
+
+#include "pw_span/span.h"
+
+/// Creates a pw::span on the HCI portion of an H4 buffer.
+template <typename C>
+constexpr inline auto H4HciSubspan(C&& container) {
+  return pw::span(container.data() + 1, container.size() - 1);
+}
+
+// Create an Emboss View or Writer from a pw::span value or reference. The
+// Emboss type is determined by the template's first parameter.
+// Unlike the Emboss `Make*View` creation methods, this function accepts a
+// reference so it can be used with rvalues. This is ok to do with pw::span
+// since it doesn't own its underlying data. Another key difference is the
+// caller explicitly chooses if they want a View or a Writer using the first
+// template argument.
+template <
+    typename EmbossT,
+    typename ContainerT,
+    typename = std::enable_if<std::is_base_of_v<pw::span<uint8_t>, ContainerT>>>
+constexpr inline EmbossT MakeEmboss(ContainerT&& buffer) {
+  return EmbossT(buffer.data(), buffer.size());
+}
diff --git a/pw_bluetooth_proxy/emboss_util_test.cc b/pw_bluetooth_proxy/emboss_util_test.cc
new file mode 100644
index 0000000..a57b4f1
--- /dev/null
+++ b/pw_bluetooth_proxy/emboss_util_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2024 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 "emboss_util.h"
+
+#include "pw_bluetooth/hci_test.emb.h"
+#include "pw_unit_test/framework.h"  // IWYU pragma: keep
+
+namespace pw::bluetooth::proxy {
+namespace {
+
+TEST(EmbossUtilTest, CreateH4Subspan) {
+  std::array<uint8_t, 4> buffer = {0x00, 0x01, 0x02, 0x03};
+  auto span = H4HciSubspan(buffer);
+  EXPECT_EQ(span.front(), buffer[1]);
+  EXPECT_EQ(span.back(), buffer[3]);
+  EXPECT_EQ(span.size(), buffer.size() - 1);
+}
+
+TEST(EmbossUtilTest, MakeViewFromSpan) {
+  std::array<uint8_t, 4> buffer = {0x00, 0x01, 0x02, 0x03};
+  auto span = pw::span(buffer);
+  auto view = MakeEmboss<emboss::TestCommandPacketView>(span);
+  EXPECT_TRUE(view.IsComplete());
+  EXPECT_EQ(view.payload().Read(), 0x03);
+}
+
+TEST(EmbossUtilTest, MakeWriterFromSpan) {
+  std::array<uint8_t, 4> buffer = {0x00, 0x01, 0x02, 0x03};
+  auto span = pw::span(buffer);
+  auto view = MakeEmboss<emboss::TestCommandPacketWriter>(span);
+  EXPECT_TRUE(view.IsComplete());
+  EXPECT_EQ(view.payload().Read(), 0x03);
+}
+
+TEST(EmbossUtilTest, MakeViewFromSubspan) {
+  std::array<uint8_t, 5> buffer = {0x00, 0x01, 0x02, 0x03, 0x04};
+  auto view = MakeEmboss<emboss::TestCommandPacketView>(H4HciSubspan(buffer));
+  EXPECT_TRUE(view.IsComplete());
+  EXPECT_EQ(view.payload().Read(), 0x04);
+}
+
+TEST(EmbossUtilTest, MakeWriterFromSubspan) {
+  std::array<uint8_t, 5> buffer = {0x00, 0x01, 0x02, 0x03, 0x04};
+  auto view = MakeEmboss<emboss::TestCommandPacketWriter>(H4HciSubspan(buffer));
+  EXPECT_TRUE(view.IsComplete());
+  EXPECT_EQ(view.payload().Read(), 0x04);
+}
+
+}  // namespace
+}  // namespace pw::bluetooth::proxy
diff --git a/pw_bluetooth_proxy/passthrough_test.cc b/pw_bluetooth_proxy/passthrough_test.cc
index 2ce8abd..80e1466 100644
--- a/pw_bluetooth_proxy/passthrough_test.cc
+++ b/pw_bluetooth_proxy/passthrough_test.cc
@@ -15,6 +15,7 @@
 #include <cstdint>
 #include <numeric>
 
+#include "emboss_util.h"
 #include "lib/stdcompat/utility.h"
 #include "pw_bluetooth/hci_commands.emb.h"
 #include "pw_bluetooth/hci_common.emb.h"
@@ -30,12 +31,6 @@
 
 // Util functions
 
-// Create pw::span on the HCI portion of an H4 buffer.
-template <typename C>
-constexpr const pw::span<uint8_t> HciSubspanOfH4Buffer(C&& container) {
-  return pw::span(container.data() + 1, container.size() - 1);
-}
-
 // Simple struct for returning a H4 array and an emboss view on its HCI packet.
 template <typename EmbossT>
 struct EmbossViewWithH4Buffer {
@@ -52,8 +47,7 @@
   EmbossViewWithH4Buffer<EmbossT> view_arr;
   std::iota(view_arr.arr.begin(), view_arr.arr.end(), 100);
   view_arr.arr[0] = cpp23::to_underlying(emboss::H4PacketType::COMMAND);
-  const auto hci_span = HciSubspanOfH4Buffer(view_arr.arr);
-  view_arr.view = EmbossT(&hci_span);
+  view_arr.view = MakeEmboss<EmbossT>(H4HciSubspan(view_arr.arr));
   EXPECT_TRUE(view_arr.view.IsComplete());
   view_arr.view.header().opcode_full().Write(opcode);
   return view_arr;
@@ -77,8 +71,7 @@
   EmbossViewWithH4Buffer<EmbossT> view_arr;
   std::iota(view_arr.arr.begin(), view_arr.arr.end(), 100);
   view_arr.arr[0] = cpp23::to_underlying(emboss::H4PacketType::EVENT);
-  const auto hci_span = HciSubspanOfH4Buffer(view_arr.arr);
-  view_arr.view = EmbossT(&hci_span);
+  view_arr.view = MakeEmboss<EmbossT>(H4HciSubspan(view_arr.arr));
   view_arr.view.header().event_code().Write(event_code);
   view_arr.view.status().Write(status_code);
   EXPECT_TRUE(view_arr.view.IsComplete());