pw_bluetooth: Create pw_bluetooth API
Create the pw_bluetooth module.
Create the Bluetooth Low Energy C++ API in the pw_bluetooth module.
This API will be used to wrap various BLE implementations.
Bug: 636
Change-Id: I329c8e0f307d30a26581a43cb46cf2d3dbd35da0
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/84180
Pigweed-Auto-Submit: Ben Lawson <benlawson@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Ben Lawson <benlawson@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 061cc54..b41d6f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,6 +26,7 @@
add_subdirectory(pw_assert_zephyr EXCLUDE_FROM_ALL)
add_subdirectory(pw_base64 EXCLUDE_FROM_ALL)
add_subdirectory(pw_blob_store EXCLUDE_FROM_ALL)
+add_subdirectory(pw_bluetooth EXCLUDE_FROM_ALL)
add_subdirectory(pw_build EXCLUDE_FROM_ALL)
add_subdirectory(pw_build_info EXCLUDE_FROM_ALL)
add_subdirectory(pw_bytes EXCLUDE_FROM_ALL)
diff --git a/PIGWEED_MODULES b/PIGWEED_MODULES
index dfb7f27..7bf9e03 100644
--- a/PIGWEED_MODULES
+++ b/PIGWEED_MODULES
@@ -11,6 +11,7 @@
pw_base64
pw_bloat
pw_blob_store
+pw_bluetooth
pw_bluetooth_hci
pw_boot
pw_boot_cortex_m
diff --git a/pw_bluetooth/BUILD.bazel b/pw_bluetooth/BUILD.bazel
new file mode 100644
index 0000000..e1623dc
--- /dev/null
+++ b/pw_bluetooth/BUILD.bazel
@@ -0,0 +1,74 @@
+# Copyright 2022 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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+ "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+ name = "pw_bluetooth",
+ hdrs = [
+ "public/pw_bluetooth/constants.h",
+ "public/pw_bluetooth/gatt/client.h",
+ "public/pw_bluetooth/gatt/constants.h",
+ "public/pw_bluetooth/gatt/error.h",
+ "public/pw_bluetooth/gatt/server.h",
+ "public/pw_bluetooth/gatt/types.h",
+ "public/pw_bluetooth/hci.h",
+ "public/pw_bluetooth/host.h",
+ "public/pw_bluetooth/low_energy/advertising_data.h",
+ "public/pw_bluetooth/low_energy/bond_data.h",
+ "public/pw_bluetooth/low_energy/central.h",
+ "public/pw_bluetooth/low_energy/connection.h",
+ "public/pw_bluetooth/low_energy/peripheral.h",
+ "public/pw_bluetooth/low_energy/security_mode.h",
+ "public/pw_bluetooth/pairing_delegate.h",
+ "public/pw_bluetooth/peer.h",
+ "public/pw_bluetooth/result.h",
+ "public/pw_bluetooth/types.h",
+ ],
+ includes = ["public"],
+ deps = [
+ "//pw_chrono:system_clock",
+ "//pw_containers",
+ "//pw_function",
+ "//pw_status",
+ ],
+)
+
+pw_cc_test(
+ name = "api_test",
+ srcs = [
+ "api_test.cc",
+ ],
+ deps = [
+ ":pw_bluetooth",
+ ],
+)
+
+pw_cc_test(
+ name = "result_test",
+ srcs = [
+ "result_test.cc",
+ ],
+ deps = [
+ "pw_bluetooth",
+ ],
+)
diff --git a/pw_bluetooth/BUILD.gn b/pw_bluetooth/BUILD.gn
new file mode 100644
index 0000000..c6ceb1c
--- /dev/null
+++ b/pw_bluetooth/BUILD.gn
@@ -0,0 +1,77 @@
+# Copyright 2022 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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_chrono/backend.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("public_include_path") {
+ include_dirs = [ "public" ]
+ visibility = [ ":*" ]
+}
+
+pw_doc_group("docs") {
+ sources = [ "docs.rst" ]
+}
+
+pw_source_set("pw_bluetooth") {
+ public_configs = [ ":public_include_path" ]
+ public = [
+ "public/pw_bluetooth/constants.h",
+ "public/pw_bluetooth/gatt/client.h",
+ "public/pw_bluetooth/gatt/constants.h",
+ "public/pw_bluetooth/gatt/error.h",
+ "public/pw_bluetooth/gatt/server.h",
+ "public/pw_bluetooth/gatt/types.h",
+ "public/pw_bluetooth/hci.h",
+ "public/pw_bluetooth/host.h",
+ "public/pw_bluetooth/low_energy/advertising_data.h",
+ "public/pw_bluetooth/low_energy/bond_data.h",
+ "public/pw_bluetooth/low_energy/central.h",
+ "public/pw_bluetooth/low_energy/connection.h",
+ "public/pw_bluetooth/low_energy/peripheral.h",
+ "public/pw_bluetooth/low_energy/security_mode.h",
+ "public/pw_bluetooth/pairing_delegate.h",
+ "public/pw_bluetooth/peer.h",
+ "public/pw_bluetooth/result.h",
+ "public/pw_bluetooth/types.h",
+ ]
+ public_deps = [
+ "$dir_pw_chrono:system_clock",
+ dir_pw_containers,
+ dir_pw_function,
+ dir_pw_status,
+ ]
+}
+
+pw_test_group("tests") {
+ enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+ tests = [
+ ":api_test",
+ ":result_test",
+ ]
+}
+
+pw_test("api_test") {
+ sources = [ "api_test.cc" ]
+ deps = [ ":pw_bluetooth" ]
+}
+
+pw_test("result_test") {
+ sources = [ "result_test.cc" ]
+ deps = [ ":pw_bluetooth" ]
+}
diff --git a/pw_bluetooth/CMakeLists.txt b/pw_bluetooth/CMakeLists.txt
new file mode 100644
index 0000000..5d25a6b
--- /dev/null
+++ b/pw_bluetooth/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright 2022 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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+
+pw_add_module_library(pw_bluetooth
+ HEADERS
+ public/pw_bluetooth/gatt/client.h
+ public/pw_bluetooth/gatt/constants.h
+ public/pw_bluetooth/gatt/error.h
+ public/pw_bluetooth/gatt/server.h
+ public/pw_bluetooth/gatt/types.h
+ public/pw_bluetooth/low_energy/advertising_data.h
+ public/pw_bluetooth/low_energy/bond_data.h
+ public/pw_bluetooth/low_energy/central.h
+ public/pw_bluetooth/low_energy/connection.h
+ public/pw_bluetooth/low_energy/peripheral.h
+ public/pw_bluetooth/low_energy/security_mode.h
+ public/pw_bluetooth/constants.h
+ public/pw_bluetooth/hci.h
+ public/pw_bluetooth/host.h
+ public/pw_bluetooth/pairing_delegate.h
+ public/pw_bluetooth/peer.h
+ public/pw_bluetooth/result.h
+ public/pw_bluetooth/types.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_containers
+ pw_function
+ pw_status
+ pw_chrono.system_clock
+)
+
+pw_add_test(pw_bluetooth.api_test
+ SOURCES
+ api_test.cc
+ DEPS
+ pw_bluetooth
+ GROUPS
+ pw_bluetooth
+)
+
+pw_add_test(pw_bluetooth.result_test
+ SOURCES
+ result_test.cc
+ DEPS
+ pw_bluetooth
+ GROUPS
+ pw_bluetooth
+)
\ No newline at end of file
diff --git a/pw_bluetooth/README.md b/pw_bluetooth/README.md
new file mode 100644
index 0000000..f1526b2
--- /dev/null
+++ b/pw_bluetooth/README.md
@@ -0,0 +1 @@
+This directory contains Bluetooth Low Energy APIs.
diff --git a/pw_bluetooth/api_test.cc b/pw_bluetooth/api_test.cc
new file mode 100644
index 0000000..4fdd04b
--- /dev/null
+++ b/pw_bluetooth/api_test.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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 "gtest/gtest.h"
+#include "pw_bluetooth/host.h"
+
+namespace pw::bluetooth {
+namespace {
+
+// This empty test ensures that the included API compiles.
+TEST(ApiTest, ApiCompiles) {}
+
+} // namespace
+} // namespace pw::bluetooth
\ No newline at end of file
diff --git a/pw_bluetooth/docs.rst b/pw_bluetooth/docs.rst
new file mode 100644
index 0000000..b7cf1a5
--- /dev/null
+++ b/pw_bluetooth/docs.rst
@@ -0,0 +1,18 @@
+.. _module-pw_bluetooth:
+
+================
+pw_bluetooth
+================
+The ``pw_bluetooth`` module contains APIs for the host layer of Bluetooth Low
+Energy. The APIs are a collection of virtual interfaces that must implemented by
+a BLE host stack.
+
+.. note::
+ This module is still under construction, the API is not yet stable.
+
+Callbacks
+===========
+This module contains callback-heavy APIs. Callbacks must not call into the
+``pw_bluetooth`` APIs unless otherwise noted. This includes calls made by
+destroying objects returned by the API. Additionally, callbacks must not block.
+
diff --git a/pw_bluetooth/public/pw_bluetooth/constants.h b/pw_bluetooth/public/pw_bluetooth/constants.h
new file mode 100644
index 0000000..549dff4
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/constants.h
@@ -0,0 +1,22 @@
+// Copyright 2022 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
+
+namespace pw::bluetooth {
+
+// The maximum length of a device name. This value was selected based on the HCI
+// and GAP specifications (v5.2, Vol 4, Part E, 7.3.11 and Vol 3, Part C, 12.1).
+constexpr uint8_t kMaxDeviceNameLength = 248;
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/gatt/client.h b/pw_bluetooth/public/pw_bluetooth/gatt/client.h
new file mode 100644
index 0000000..4ebf649
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/gatt/client.h
@@ -0,0 +1,285 @@
+// Copyright 2022 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 <memory>
+#include <span>
+
+#include "pw_bluetooth/gatt/constants.h"
+#include "pw_bluetooth/gatt/error.h"
+#include "pw_bluetooth/gatt/types.h"
+#include "pw_bluetooth/result.h"
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+#include "pw_function/function.h"
+#include "pw_result/result.h"
+
+namespace pw::bluetooth::gatt {
+
+// Represents a GATT service on a remote GATT server.
+// Clients should call `SetErrorCallback` before using in order to handle fatal
+// errors.
+class RemoteService {
+ public:
+ enum class RemoteServiceError {
+ // The service has been modified or removed.
+ kServiceRemoved = 0,
+
+ // The peer serving this service has disconnected.
+ kPeerDisconnected = 1,
+ };
+
+ // Wrapper around a possible truncated value received from the server.
+ struct ReadValue {
+ // Characteristic or descriptor handle.
+ Handle handle;
+
+ // The value of the characteristic or descriptor.
+ Vector<std::byte> value;
+
+ // True if `value` might be truncated (the buffer was completely filled by
+ // the server and the read was a short read). `ReadCharacteristic` or
+ // `ReadDescriptor` should be used to read the complete value.
+ bool maybe_truncated;
+ };
+
+ // A result returned by `ReadByType`.
+ struct ReadByTypeResult {
+ // Characteristic or descriptor handle.
+ Handle handle;
+
+ // The value of the characteristic or descriptor, if it was read
+ // successfully, or an error explaining why the value could not be read.
+ Result<Error, ReadValue> result;
+ };
+
+ // Represents the supported options to read a long characteristic or
+ // descriptor value from a server. Long values are those that may not fit in a
+ // single message (longer than 22 bytes).
+ struct LongReadOptions {
+ // The byte to start the read at. Must be less than the length of the
+ // value.
+ uint16_t offset = 0;
+
+ // The maximum number of bytes to read.
+ uint16_t max_bytes = kMaxValueLength;
+ };
+
+ // Represents the supported write modes for writing characteristics &
+ // descriptors to the server.
+ enum class WriteMode : uint8_t {
+ // Wait for a response from the server before returning but do not verify
+ // the echo response. Supported for both characteristics and descriptors.
+ kDefault = 0,
+
+ // Every value blob is verified against an echo response from the server.
+ // The procedure is aborted if a value blob has not been reliably delivered
+ // to the peer. Only supported for characteristics.
+ kReliable = 1,
+
+ // Delivery will not be confirmed before returning. Writing without a
+ // response is only supported for short characteristics with the
+ // `WRITE_WITHOUT_RESPONSE` property. The value must fit into a single
+ // message. It is guaranteed that at least 20 bytes will fit into a single
+ // message. If the value does not fit, a `kFailure` error will be produced.
+ // The value will be written at offset 0. Only supported for
+ // characteristics.
+ kWithoutResponse = 2,
+ };
+
+ // Represents the supported options to write a characteristic/descriptor value
+ // to a server.
+ struct WriteOptions {
+ // The mode of the write operation. For descriptors, only
+ // `WriteMode::kDefault` is supported
+ WriteMode mode = WriteMode::kDefault;
+
+ // Request a write starting at the byte indicated.
+ // Must be 0 if `mode` is `WriteMode.kWithoutResponse`.
+ uint16_t offset = 0;
+ };
+
+ using ReadByTypeCallback = Function<void(Result<Vector<ReadByTypeResult>>)>;
+ using ReadCallback = Function<void(Result<ReadValue>)>;
+ using NotificationCallback = Function<void(ReadValue)>;
+
+ // Set a callback that will be called when there is an error with this
+ // RemoteService, after which this RemoteService will be invalid.
+ void SetErrorCallback(Function<void(RemoteServiceError)> error_callback);
+
+ // Calls `characteristic_callback` with the characteristics and descriptors in
+ // this service.
+ void DiscoverCharacteristics(
+ Function<void(Characteristic)>&& characteristic_callback);
+
+ // Reads characteristics and descriptors with the specified type. This method
+ // is useful for reading values before discovery has completed, thereby
+ // reducing latency.
+ // `uuid` - The UUID of the characteristics/descriptors to read.
+ // `result_callback` - Results are returned via this callback. Results may be
+ // empty if no matching values are read. If reading a value results in a
+ // permission error, the handle and error will be included.
+ //
+ // This may fail with the following errors:
+ // kInvalidParameters: if `uuid` refers to an internally reserved descriptor
+ // type (e.g. the Client Characteristic Configuration descriptor).
+ // kTooManyResults: More results were read than can fit
+ // in a Vector. Consider reading characteristics/descriptors individually
+ // after performing discovery.
+ // kFailure: The server returned an error not specific to a single result.
+ void ReadByType(Uuid uuid, ReadByTypeCallback&& result_callback);
+
+ // Reads the value of a characteristic.
+ // `handle` - The handle of the characteristic to be read.
+ // `options` - If null, a short read will be performed, which may be truncated
+ // to what fits in a single message (at least 22 bytes). If long read
+ // options are present, performs a long read with the indicated options.
+ // `result_callback` - called with the result of the read and the value of the
+ // characteristic if successful.
+ //
+ // This may fail with the following errors:
+ // kInvalidHandle - if `handle` is invalid
+ // kInvalidParameters - if `options is invalid
+ // kReadNotPermitted or kInsufficient* if the server rejects the request.
+ // kFailure if the server returns an error not covered by the above errors.
+ void ReadCharacteristic(Handle handle,
+ std::optional<LongReadOptions> options,
+ ReadCallback&& result_callback);
+
+ // Writes `value` to the characteristic with `handle` using the provided
+ // `options`. It is not recommended to send additional writes while a write
+ // is already in progress (the server may receive simultaneous writes in any
+ // order).
+ //
+ // Parameters:
+ // `handle` - Handle of the characteristic to be written to
+ // `value` - The value to be written.
+ // `options` - Options that apply to the write.
+ //
+ // This may fail with the following errors:
+ // kInvalidHandle - if `handle` is invalid
+ // kInvalidParameters - if `options is invalid
+ // kWriteNotPermitted or kInsufficient* if the server rejects the request.
+ // kFailure if the server returns an error not covered by the above errors.
+ void WriteCharacteristic(Handle handle,
+ std::span<const std::byte> value,
+ WriteOptions options,
+ Function<void(Result<Error>)>&& result_callback);
+
+ // Reads the value of the characteristic descriptor with `handle` and
+ // returns it in the reply.
+ // `handle` - The descriptor handle to read.
+ // `options` - Options that apply to the read.
+ // `result_callback` - Returns a result containing the value of the descriptor
+ // on success.
+ //
+ // This may fail with the following errors:
+ // `kInvalidHandle` - if `handle` is invalid.
+ // `kInvalidParameters` - if `options` is invalid.
+ // `kReadNotPermitted` or `INSUFFICIENT_*` - if the server rejects the read
+ // request. `kFailure` - if the server returns an error.
+ void ReadDescriptor(Handle handle,
+ std::optional<LongReadOptions> options,
+ ReadCallback&& result_callback);
+
+ void WriteDescriptor(Handle handle,
+ std::span<const std::byte> value,
+ WriteOptions options,
+ Function<void(Result<Error>)>&& result_callback);
+
+ // Subscribe to notifications & indications from the characteristic with
+ // the given `handle`.
+ //
+ // Either notifications or indications will be enabled depending on
+ // characteristic properties. Indications will be preferred if they are
+ // supported. This operation fails if the characteristic does not have the
+ // "notify" or "indicate" property.
+ //
+ // A write request will be issued to configure the characteristic for
+ // notifications/indications if it contains a Client Characteristic
+ // Configuration descriptor. This method fails if an error occurs while
+ // writing to the descriptor.
+ //
+ // On success, `notification_callback` will be called when
+ // the peer sends a notification or indication. Indications are
+ // automatically confirmed.
+ //
+ // Subscriptions can be canceled with `StopNotifications`.
+ //
+ // Parameters:
+ // `handle` - the handle of the characteristic to subscribe to.
+ // `notification_callback` - will be called with the values of
+ // notifications/indications when received.
+ // `result_callback` - called with the result of enabling
+ // notifications/indications.
+ //
+ // This may fail with the following errors:
+ // `kFailure` - the characteristic does not support notifications or
+ // indications.
+ // `kInvalidHandle` - `handle` is invalid.
+ // `kWriteNotPermitted`or `kInsufficient*` - descriptor write error.
+ void RegisterNotificationCallback(
+ Handle handle,
+ NotificationCallback&& notification_callback,
+ Function<void(Result<Error>)> result_callback);
+
+ // Stops notifications for the characteristic with the given `handle`.
+ void StopNotifications(Handle handle);
+};
+
+// Represents a GATT client that interacts with services on a GATT server.
+class Client {
+ public:
+ // Represents a remote GATT service.
+ struct RemoteServiceInfo {
+ // Uniquely identifies this GATT service.
+ Handle handle;
+
+ // Indicates whether this is a primary or secondary service.
+ bool primary;
+
+ // The UUID that identifies the type of this service.
+ // There may be multiple services with the same UUID.
+ Uuid type;
+ };
+
+ virtual ~Client() = default;
+
+ // Enumerates existing services found on the peer that this Client represents,
+ // and provides a stream of updates thereafter. Results can be filtered by
+ // specifying a list of UUIDs in `uuids`. To further interact with services,
+ // clients must obtain a RemoteService protocol by calling ConnectToService().
+ // `uuid_allowlist` - The allowlist of UUIDs to filter services with.
+ // `updated_callback` - Will be called with services that are
+ // updated/modified.
+ // `removed_callback` - Called with the handles of servies
+ // that have been removed. Note that handles may be reused.
+ virtual void WatchServices(
+ Vector<Uuid> uuid_allowlist,
+ Function<void(RemoteServiceInfo)>&& updated_callback,
+ Function<void(Handle)>&& removed_callback) = 0;
+
+ // Stops service watching if started by `WatchServices`.
+ virtual void StopWatchingServices();
+
+ // Connects to a RemoteService. Only 1 connection per service is allowed.
+ // `handle` - the handle of the service to connect to.
+ //
+ // This may fail with the following errors:
+ // kInvalidParameters - `handle` does not correspond to a known service.
+ virtual Result<Error, std::unique_ptr<RemoteService>> ConnectToService(
+ Handle handle) = 0;
+};
+
+} // namespace pw::bluetooth::gatt
diff --git a/pw_bluetooth/public/pw_bluetooth/gatt/constants.h b/pw_bluetooth/public/pw_bluetooth/gatt/constants.h
new file mode 100644
index 0000000..7e25501
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/gatt/constants.h
@@ -0,0 +1,23 @@
+// Copyright 2022 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 <cstdint>
+
+namespace pw::bluetooth::gatt {
+
+// The maximum length of an attribute value.
+constexpr uint16_t kMaxValueLength = 512;
+
+} // namespace pw::bluetooth::gatt
diff --git a/pw_bluetooth/public/pw_bluetooth/gatt/error.h b/pw_bluetooth/public/pw_bluetooth/gatt/error.h
new file mode 100644
index 0000000..3bc9222
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/gatt/error.h
@@ -0,0 +1,136 @@
+// Copyright 2022 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 <cstdint>
+
+namespace pw::bluetooth::gatt {
+
+/// The values correspond with those in Bluetooth 5.2 Vol. 3 Part G Table 3.4,
+/// and Supplement to the Bluetooth Core Specification v9 Part B Table 1.1,
+/// but this is for ease of reference only. Clients should *not* rely on these
+/// values remaining constant. Omitted values from the spec are handled
+/// internally and will not be returned to clients.
+enum class Error : uint16_t {
+ // ATT Errors
+
+ /// The attribute indicated by the handle is invalid. It may have been
+ /// removed.
+ ///
+ /// This may be returned by a LocalService method.
+ kInvalidHandle = 0x1,
+
+ /// This attribute is not readable.
+ kReadNotPermitted = 0x2,
+
+ /// This attribute is not writable.
+ kWriteNotPermitted = 0x3,
+
+ /// Indicates that the response received from the server was invalid.
+ kInvalidPdu = 0x4,
+
+ /// This attribute requires authentication, but the client is not
+ /// authenticated.
+ kInsufficientAuthentication = 0x5,
+
+ /// Indicates that the offset used in a read or write request exceeds the
+ /// bounds of the value.
+ kInvalidOffset = 0x7,
+
+ /// This attribute requires authorization, but the client is not authorized.
+ kInsufficientAuthorization = 0x8,
+
+ /// This attribute requires a connection encrypted by a larger encryption key.
+ kInsufficientEncryptionKeySize = 0xC,
+
+ /// Indicates that the value given in a write request would exceed the maximum
+ /// length allowed for the destionation characteristic or descriptor.
+ kInvalidAttributeValueLength = 0xD,
+
+ /// A general error occurred that can not be classified as one of the more
+ /// specific errors.
+ kUnlikelyError = 0xE,
+
+ /// This attribute requires encryption, but the connection is not encrypted.
+ kInsufficientEncryption = 0xF,
+
+ /// The server had insufficient resources to complete the task.
+ kInsufficientResources = 0x11,
+
+ /// The value was not allowed.
+ kValueNotAllowed = 0x13,
+
+ // Application Errors
+
+ /// Application Errors. The uses of these are specified at the application
+ /// level.
+ kApplicationError80 = 0x80,
+ kApplicationError81 = 0x81,
+ kApplicationError82 = 0x82,
+ kApplicationError83 = 0x83,
+ kApplicationError84 = 0x84,
+ kApplicationError85 = 0x85,
+ kApplicationError86 = 0x86,
+ kApplicationError87 = 0x87,
+ kApplicationError88 = 0x88,
+ kApplicationError89 = 0x89,
+ kApplicationError8A = 0x8A,
+ kApplicationError8B = 0x8B,
+ kApplicationError8C = 0x8C,
+ kApplicationError8D = 0x8D,
+ kApplicationError8E = 0x8E,
+ kApplicationError8F = 0x8F,
+ kApplicationError90 = 0x90,
+ kApplicationError91 = 0x91,
+ kApplicationError92 = 0x92,
+ kApplicationError93 = 0x93,
+ kApplicationError94 = 0x94,
+ kApplicationError95 = 0x95,
+ kApplicationError96 = 0x96,
+ kApplicationError97 = 0x97,
+ kApplicationError98 = 0x98,
+ kApplicationError99 = 0x99,
+ kApplicationError9A = 0x9A,
+ kApplicationError9B = 0x9B,
+ kApplicationError9C = 0x9C,
+ kApplicationError9D = 0x9D,
+ kApplicationError9E = 0x9E,
+ kApplicationError9F = 0x9F,
+
+ // Common Profile and Service Error Codes
+
+ /// Write request was rejected at the profile or service level.
+ kWriteRequestRejected = 0xFC,
+
+ /// The Client Characteristic Configuration Descriptor was improperly
+ /// configured.
+ kCccDescriptorImproperlyConfigured = 0xFD,
+
+ /// Profile or service procedure already in progress.
+ kProcedureAlreadyInProgress = 0xFE,
+
+ /// A value was out of range at the profile or service level.
+ kOutOfRange = 0xFF,
+
+ // Errors not specified by Bluetooth.
+
+ // One or more of the call parameters are invalid. See the parameter
+ // documentation.
+ kInvalidParameters = 0x101,
+
+ // Generic failure.
+ kFailure = 0x102,
+};
+
+} // namespace pw::bluetooth::gatt
diff --git a/pw_bluetooth/public/pw_bluetooth/gatt/server.h b/pw_bluetooth/public/pw_bluetooth/gatt/server.h
new file mode 100644
index 0000000..b74c572
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/gatt/server.h
@@ -0,0 +1,223 @@
+// Copyright 2022 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 <cstdint>
+#include <memory>
+#include <span>
+
+#include "pw_bluetooth/gatt/error.h"
+#include "pw_bluetooth/gatt/types.h"
+#include "pw_bluetooth/result.h"
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+#include "pw_function/function.h"
+#include "pw_status/status.h"
+
+namespace pw::bluetooth::gatt {
+
+// Parameters for registering a local GATT service.
+struct LocalServiceInfo {
+ // Indicates whether this is a primary or secondary service.
+ bool primary;
+
+ // The UUID that identifies the type of this service.
+ // There may be multiple services with the same UUID.
+ Uuid type;
+
+ // The characteristics of this service.
+ Vector<Characteristic> characteristics;
+
+ // Handles of other services that are included by this service.
+ Vector<Handle> includes;
+};
+
+// Interface for serving a local GATT service. This is implemented by the API
+// client.
+class LocalServiceDelegate {
+ public:
+ virtual ~LocalServiceDelegate() = default;
+
+ // Called when there is a fatal error related to this service that forces the
+ // service to close. LocalServiceDelegate methods will no longer be called.
+ // This invalidates the associated LocalService. It is OK to destroy both
+ // LocalServiceDelegate and the associated LocalService from within this
+ // method.
+ virtual void OnError(Error error) = 0;
+
+ // This notifies the current configuration of a particular
+ // characteristic/descriptor for a particular peer. It will be called when the
+ // peer GATT client changes the configuration.
+ //
+ // The Bluetooth stack maintains the state of each peer's configuration across
+ // reconnections. As such, this method will also be called when a peer
+ // connects for each characteristic with the initial, persisted state of the
+ // newly-connected peer's configuration. However, clients should not rely on
+ // this state being persisted indefinitely by the Bluetooth stack.
+ //
+ // Parameters:
+ // `peer_id` - The PeerId of the GATT client associated with this particular
+ // CCC.
+ // `handle` - The handle of the characteristic associated with the `notify`
+ // and `indicate` parameters.
+ // `notify` - True if the client has enabled notifications, false otherwise.
+ // `indicate` - True if the client has enabled indications, false otherwise.
+ virtual void CharacteristicConfiguration(PeerId peer_id,
+ Handle handle,
+ bool notify,
+ bool indicate) = 0;
+
+ // Called when a peer requests to read the value of a characteristic or
+ // descriptor. It is guaranteed that the peer satisfies the permssions
+ // associated with this attribute.
+ //
+ // Parameters:
+ // `peer_id` - The PeerId of the GATT client making the read request.
+ // `handle` - The handle of the requested descriptor/characteristic.
+ // `offset` - The offset at which to start reading the requested value.
+ // `result_callback` - Called with the value of the characteristic on success,
+ // or an Error on failure. The value will be truncated to fit in the MTU
+ // if necessary. It is OK to call `result_callback` in `ReadValue`.
+ virtual void ReadValue(
+ PeerId peer_id,
+ Handle handle,
+ uint32_t offset,
+ Function<void(Result<Error, std::span<const std::byte>>)>
+ result_callback) = 0;
+
+ // Called when a peer issues a request to write the value of a characteristic
+ // or descriptor. It is guaranteed that the peer satisfies the permissions
+ // associated with this attribute.
+ //
+ // Parameters:
+ // `peer_id` - The PeerId of the GATT client making the write request.
+ // `handle` - The handle of the requested descriptor/characteristic.
+ // `offset` - The offset at which to start writing the requested value. If the
+ // offset is 0, any existing value should be overwritten by the new value.
+ // Otherwise, the existing value between offset:(offset + len(value))
+ // should be changed to `value`.
+ // `value` - The new value for the descriptor/characteristic.
+ // `status_callback` - Called with the result of the write.
+ virtual void WriteValue(PeerId peer_id,
+ Handle handle,
+ uint32_t offset,
+ std::span<const std::byte> value,
+ Function<void(Result<Error>)> status_callback) = 0;
+
+ // Called when the MTU of a peer is updated. Also called for peers that are
+ // already connected when the server is published. This method is safe to
+ // ignore if you do not care about the MTU. It is intended for use cases where
+ // throughput needs to be optimized.
+ virtual void MtuUpdate(PeerId peer_id, uint16_t mtu) = 0;
+};
+
+// LocalService is valid for the lifetime of a published GATT service. It is
+// used to control the service and send notifications/indications.
+class LocalService {
+ public:
+ // The parameters used to signal a characteristic value change from a
+ // LocalService to a peer.
+ struct ValueChangedParameters {
+ // The PeerIds of the peers to signal. The LocalService should respect the
+ // Characteristic Configuration associated with a peer+handle when deciding
+ // whether to signal it. If empty, all peers are signalled.
+ Vector<PeerId> peer_ids;
+ // The handle of the characteristic value being signaled.
+ Handle handle;
+ // The new value for the descriptor/characteristic.
+ std::span<const std::byte> value;
+ };
+
+ virtual ~LocalService() = default;
+
+ // Returns the unique handle assigned to this service.
+ virtual Handle GetHandle() = 0;
+
+ // Sends a notification to peers. Notifications should be used instead of
+ // indications when the service does *not* require peer confirmation of the
+ // update.
+ //
+ // Notifications should not be sent to peers which have not enabled
+ // notifications on a particular characteristic - if they are sent, they will
+ // not be propagated. The Bluetooth stack will track this configuration for
+ // the lifetime of the service.
+ //
+ // Parameters:
+ // `parameters` - The parameters associated with the changed characteristic.
+ // `completion_callback` - Called when the notification has been sent.
+ // Additional values should not be notified until this callback is called.
+ virtual void NotifyValue(const ValueChangedParameters& parameters,
+ Closure completion_callback) = 0;
+
+ // Sends an indication to peers. Indications should be used instead of
+ // notifications when the service *does* require peer confirmation of the
+ // update.
+ //
+ // Indications should not be sent to peers which have not enabled indications
+ // on a particular characteristic - if they are sent, they will not be
+ // propagated. The Bluetooth stack will track this configuration for the
+ // lifetime of the service.
+ //
+ // If any of the peers in `update.peer_ids` fails to confirm the indication
+ // within the ATT transaction timeout (30 seconds per Bluetooth 5.2 Vol. 4
+ // Part G 3.3.3), the link between the peer and the local adapter will be
+ // closed.
+ //
+ // Parameters:
+ // `parameters` - The parameters associated with the changed characteristic.
+ // `confirmation` - When all the peers listed in `parameters.peer_ids` have
+ // confirmed the indication, `confirmation` is called. If the
+ // implementation wishes to receive indication confirmations on a per-peer
+ // basis, they should send this event with a single PeerId in
+ // `parameters.peer_ids`. Additional values should not be indicated until
+ // this callback is called.
+ virtual void IndicateValue(const ValueChangedParameters& parameters,
+ Function<void(Result<Error>)> confirmation) = 0;
+};
+
+// Interface for a GATT server that serves many GATT services.
+class Server {
+ public:
+ enum class PublishServiceError {
+ kInternalError = 0,
+
+ /// Invalid service UUID provided.
+ kInvalidUuid = 1,
+
+ // Invalid service characteristics provided.
+ kInvalidCharacteristics = 2,
+
+ // Invalid service includes provided.
+ kInvalidIncludes = 3,
+ };
+
+ virtual ~Server() = default;
+
+ // Publishes the service defined by `info` and implemented by `delegate` so
+ // that it is available to all remote peers.
+ //
+ // The caller must assign distinct handles to the characteristics and
+ // descriptors listed in `info`. These identifiers will be used in requests
+ // sent to `delegate`. On success, a `LocalService` is returned. When the
+ // `LocalService` is destroyed or an error occurs
+ // (LocalServiceDelegate.OnError), the service will be unpublished.
+ virtual void PublishService(
+ LocalServiceInfo info,
+ LocalServiceDelegate* delegate,
+ Function<
+ void(Result<PublishServiceError, std::unique_ptr<LocalService>>)>&&
+ result_callback) = 0;
+};
+
+} // namespace pw::bluetooth::gatt
diff --git a/pw_bluetooth/public/pw_bluetooth/gatt/types.h b/pw_bluetooth/public/pw_bluetooth/gatt/types.h
new file mode 100644
index 0000000..3a2b2ea
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/gatt/types.h
@@ -0,0 +1,119 @@
+// Copyright 2022 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 <cstdint>
+#include <optional>
+
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+
+namespace pw::bluetooth::gatt {
+
+// A Handle uniquely identifies a service, characteristic, or descriptor.
+enum class Handle : uint64_t {};
+
+// Possible values for the characteristic properties bitfield. These specify
+// the GATT procedures that are allowed for a particular characteristic.
+enum class CharacteristicPropertyBits : uint16_t {
+ kBroadcast = 0x1,
+ kRead = 0x2,
+ kWriteWithoutResponse = 0x4,
+ kWrite = 0x8,
+ kNotify = 0x10,
+ kIndicate = 0x20,
+ kAuthenticatedSignedWrites = 0x40,
+ kReliableWrite = 0x100,
+ kWritableAuxiliaries = 0x200
+};
+
+// Represents encryption, authentication, and authorization permissions that can
+// be assigned to a specific access permission.
+struct SecurityRequirements {
+ // If true, the physical link must be encrypted to access this attribute.
+ bool encryption_required;
+
+ // If true, the physical link must be authenticated to access this
+ // attribute.
+ bool authentication_required;
+
+ // If true, the client needs to be authorized before accessing this
+ // attribute.
+ bool authorization_required;
+};
+
+/// Specifies the access permissions for a specific attribute value.
+struct AttributePermissions {
+ // Specifies whether or not an attribute has the read permission. If null,
+ // then the attribute value cannot be read. Otherwise, it can be read only if
+ // the permissions specified in the SecurityRequirements table are satisfied.
+ std::optional<SecurityRequirements> read;
+
+ // Specifies whether or not an attribute has the write permission. If null,
+ // then the attribute value cannot be written. Otherwise, it can be written
+ // only if the permissions specified in the SecurityRequirements table are
+ // satisfied.
+ std::optional<SecurityRequirements> write;
+
+ // Specifies the security requirements for a client to subscribe to
+ // notifications or indications on a characteristic. A characteristic's
+ // support for notifications or indiciations is specified using the NOTIFY and
+ // INDICATE characteristic properties. If a local characteristic has one of
+ // these properties then this field can not be null. Otherwise, this field
+ // must be left as null.
+ //
+ // This field is ignored for Descriptors.
+ std::optional<SecurityRequirements> update;
+};
+
+// Represents a local or remote GATT characteristic descriptor.
+struct Descriptor {
+ // Uniquely identifies this descriptor within a service.
+ // For local descriptors, the specified handle must be unique
+ // across all characteristic and descriptor handles in this service.
+ Handle handle;
+
+ // The UUID that identifies the type of this descriptor.
+ Uuid type;
+
+ // The attribute permissions of this descriptor. For remote descriptors, this
+ // value will be null until the permissions are discovered via read and write
+ // requests.
+ std::optional<AttributePermissions> permissions;
+};
+
+// Represents a local or remote GATT characteristic.
+struct Characteristic {
+ // Uniquely identifies this characteristic within a service.
+ // For local characteristics, the specified handle must be unique across
+ // all characteristic and descriptor handles in this service.
+ Handle handle;
+
+ // The UUID that identifies the type of this characteristic.
+ Uuid type;
+
+ // The characteristic properties bitfield. See `CharacteristicPropertyBits`
+ // above for possible values.
+ CharacteristicPropertyBits properties;
+
+ // The attribute permissions of this characteristic. For remote
+ // characteristics, this value will be null until the permissions are
+ // discovered via read and write requests.
+ std::optional<AttributePermissions> permissions;
+
+ // The descriptors of this characteristic.
+ Vector<Descriptor> descriptors;
+};
+
+} // namespace pw::bluetooth::gatt
\ No newline at end of file
diff --git a/pw_bluetooth/public/pw_bluetooth/hci.h b/pw_bluetooth/public/pw_bluetooth/hci.h
new file mode 100644
index 0000000..b71a51d
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/hci.h
@@ -0,0 +1,45 @@
+// Copyright 2022 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 <span>
+
+#include "pw_function/function.h"
+
+namespace pw::bluetooth {
+
+// The Hci (Host Controller Interface) class is a shim for transferring packets
+// between the Host and the Controller.
+class Hci {
+ public:
+ using DataCallback = Function<void(std::span<const std::byte>)>;
+
+ virtual ~Hci() = default;
+
+ // Sends an HCI `command` packet to the controller.
+ virtual void SendCommand(std::span<const std::byte> command) = 0;
+
+ // Sets a callback that will be called with HCI event packets received from
+ // the controller.
+ virtual void SetEventCallback(DataCallback callback) = 0;
+
+ // Sends an ACL data packet to the controller.
+ virtual void SendAclData(std::span<const std::byte> data) = 0;
+
+ // Sets a callback that will be called with ACL data packets received from the
+ // controller.
+ virtual void SetReceiveAclCallback(DataCallback callback) = 0;
+};
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/host.h b/pw_bluetooth/public/pw_bluetooth/host.h
new file mode 100644
index 0000000..cd9091c
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/host.h
@@ -0,0 +1,194 @@
+// Copyright 2022 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 <optional>
+#include <string_view>
+
+#include "pw_bluetooth/gatt/client.h"
+#include "pw_bluetooth/gatt/server.h"
+#include "pw_bluetooth/hci.h"
+#include "pw_bluetooth/low_energy/bond_data.h"
+#include "pw_bluetooth/low_energy/central.h"
+#include "pw_bluetooth/low_energy/peripheral.h"
+#include "pw_bluetooth/low_energy/security_mode.h"
+#include "pw_bluetooth/pairing_delegate.h"
+#include "pw_bluetooth/peer.h"
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+#include "pw_function/function.h"
+#include "pw_status/status.h"
+
+namespace pw::bluetooth {
+
+// Host is the entrypoint API for interacting with a Bluetooth host stack. Host
+// is an abstract class that is implemented by a host stack implementation.
+class Host {
+ public:
+ // Represents the persistent configuration of a single Host instance. This is
+ // used for identity representation in advertisements & bonding secrets
+ // recall.
+ struct PersistentData {
+ // The local Identity Resolving Key used by a Host to generate Resolvable
+ // Private Addresses when privacy is enabled. May be absent for hosts that
+ // do not use LE privacy, or that only use Non-Resolvable Private Addresses.
+ //
+ // NOTE: This key is distributed to LE peers during pairing procedures. The
+ // client must take care to assign an IRK that consistent with the local
+ // Host identity.
+ std::optional<Key> identity_resolving_key;
+
+ // All bonds that use a public identity address must contain the same local
+ // address.
+ Vector<low_energy::BondData> bonds;
+ };
+
+ // The security level required for this pairing. This corresponds to the
+ // security levels defined in the Security Manager Protocol in Core spec v5.3,
+ // Vol 3, Part H, Section 2.3.1
+ enum class PairingSecurityLevel : uint8_t {
+ // Encrypted without person-in-the-middle protection (unauthenticated)
+ kEncrypted,
+ // Encrypted with person-in-the-middle protection (authenticated), although
+ // this level of security does not fully protect against passive
+ // eavesdroppers
+ kAuthenticated,
+ // Encrypted with person-in-the-middle protection (authenticated).
+ // This level of security fully protects against eavesdroppers.
+ kLeSecureConnections,
+ };
+
+ // Whether or not the device should form a bluetooth bond during the pairing
+ // prodecure. As described in Core Spec v5.2, Vol 3, Part C, Sec 4.3
+ enum class BondableMode : uint8_t {
+ // The device will form a bond during pairing with peers
+ kBondable,
+ // The device will not form a bond during pairing with peers
+ kNonBondable,
+ };
+
+ // Parameters that give a caller more fine-grained control over the pairing
+ // process.
+ struct PairingOptions {
+ // Determines the Security Manager security level to pair with.
+ PairingSecurityLevel security_level = PairingSecurityLevel::kAuthenticated;
+
+ // Indicated whether the device should form a bond or not during pairing. If
+ // not present, interpreted as bondable mode.
+ BondableMode bondable_mode = BondableMode::kBondable;
+ };
+
+ // `Close` should complete before `Host` is destroyed.
+ virtual ~Host() = default;
+
+ // Initializes the host stack. Vendor specific controller initialization (e.g.
+ // loading firmware) must be done before initializing `Host`.
+ //
+ // Parameters:
+ // `hci` - Pointer to a concrete `Hci` that the host stack should use to
+ // communicate with the controller.
+ // `data` - Data to persist from a previous instance of `Host`.
+ // `on_initialization_complete` - Called when initialization is complete.
+ // Other methods should not be called until initialization completes.
+ virtual void Initialize(
+ Hci* hci,
+ PersistentData data,
+ Function<void(Status)>&& on_initialization_complete) = 0;
+
+ // Safely shuts down the host, ending all active Bluetooth procedures:
+ // - All objects/pointers associated with this host are destroyed/invalidated
+ // and all connections disconnected.
+ // - All scanning and advertising procedures are stopped.
+ //
+ // The Host may send events or call callbacks as procedures get terminated.
+ // `callback` will be called once all procedures have terminated.
+ virtual void Close(Closure callback) = 0;
+
+ // Returns a pointer to the Central API, which is used to scan and connect to
+ // peers.
+ virtual low_energy::Central* Central() = 0;
+
+ // Returns a pointer to the Peripheral API, which is used to advertise and
+ // accept connections from peers.
+ virtual low_energy::Peripheral* Peripheral() = 0;
+
+ // Returns a pointer to the GATT Server API, which is used to publish GATT
+ // services.
+ virtual gatt::Server* GattServer() = 0;
+
+ // Deletes a peer from the Bluetooth host. If the peer is connected, it will
+ // be disconnected. `peer_id` will no longer refer to any peer.
+ //
+ // Returns `OK` after no peer exists that's identified by `peer_id` (even
+ // if it didn't exist), `ABORTED` if the peer could not be disconnected or
+ // deleted and still exists.
+ virtual Status ForgetPeer(PeerId peer_id) = 0;
+
+ // Enable or disable the LE privacy feature. When enabled, the host will use a
+ // private device address in all LE procedures. When disabled, the public
+ // identity address will be used instead (which is the default).
+ virtual void EnablePrivacy(bool enabled) = 0;
+
+ // Set the GAP LE Security Mode of the host. Only encrypted,
+ // connection-based security modes are supported, i.e. Mode 1 and Secure
+ // Connections Only mode. If the security mode is set to Secure Connections
+ // Only, any existing encrypted connections which do not meet the security
+ // requirements of Secure Connections Only mode will be disconnected.
+ virtual void SetSecurityMode(low_energy::SecurityMode security_mode) = 0;
+
+ // Assigns the pairing delegate that will respond to authentication challenges
+ // using the given I/O capabilities. Calling this method cancels any on-going
+ // pairing procedure started using a previous delegate. Pairing requests will
+ // be rejected if no PairingDelegate has been assigned.
+ virtual void SetPairingDelegate(InputCapability input,
+ OutputCapability output,
+ PairingDelegate* pairing_delegate) = 0;
+
+ // NOTE: This is intended to satisfy test scenarios that require pairing
+ // procedures to be initiated without relying on service access. In normal
+ // operation, Bluetooth security is enforced during service access.
+ //
+ // Initiates pairing to the peer with the supplied `peer_id` and `options`.
+ // Returns an error if no connected peer with `peer_id` is found or the
+ // pairing procedure fails.
+ //
+ // If `options` specifies a higher security level than the current pairing,
+ // this method attempts to raise the security level. Otherwise this method has
+ // no effect and returns success.
+ //
+ // Returns the following errors via `callback`:
+ // `NOT_FOUND` - The peer `peer_id` was not found.
+ // `ABORTED` - The pairing procedure failed.
+ virtual void Pair(PeerId peer_id,
+ PairingOptions options,
+ Function<void(Status)>&& callback) = 0;
+
+ // Configures a callback to be called when new bond data for a peer has been
+ // created. This data should be persisted and used to initialize Host in the
+ // future. New bond data may be received for an already bonded peer, in which
+ // case the new data should overwrite the old data.
+ virtual void SetBondDataCallback(
+ Function<void(low_energy::BondData)>&& callback) = 0;
+
+ // Looks up the `PeerId` corresponding to `address`. If `address` does not
+ // correspond to a known peer, a new `PeerId` will be generated for the
+ // address. If a `PeerId` cannot be generated, std::nullopt will be returned.
+ virtual std::optional<PeerId> PeerIdFromAddress(Address address) = 0;
+
+ // Looks up the Address corresponding to `peer_id`. Returns null if `peer_id`
+ // does not correspond to a known peer.
+ virtual std::optional<Address> DeviceAddressFromPeerId(PeerId peer_id) = 0;
+};
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/advertising_data.h b/pw_bluetooth/public/pw_bluetooth/low_energy/advertising_data.h
new file mode 100644
index 0000000..f04a1b1
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/advertising_data.h
@@ -0,0 +1,66 @@
+// Copyright 2022 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 <cstdint>
+#include <span>
+
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+
+namespace pw::bluetooth::low_energy {
+
+// A service data field in an advertising data payload.
+struct ServiceData {
+ Uuid uuid;
+ std::span<const std::byte> data;
+};
+
+// A manufacturer data field in an advertising data payload.
+struct ManufacturerData {
+ uint16_t company_id = 0;
+ std::span<const std::byte> data;
+};
+
+// Represents advertising and scan response data that are transmitted by a LE
+// peripheral or broadcaster.
+struct AdvertisingData {
+ // Long or short name of the device.
+ std::string_view name;
+
+ // The appearance of the local device.
+ Appearance appearance = Appearance::kUnknown;
+
+ Vector<Uuid> service_uuids;
+
+ Vector<ServiceData> service_data;
+
+ Vector<ManufacturerData> manufacturer_data;
+
+ // String representing a URI to be advertised, as defined in IETF STD 66:
+ // https://tools.ietf.org/html/std66. Each entry should be a UTF-8 string
+ // including the scheme. For more information, see:
+ // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml for allowed
+ // schemes;
+ // https://www.bluetooth.com/specifications/assigned-numbers/uri-scheme-name-string-mapping
+ // for code-points used by the system to compress the scheme to save space in
+ // the payload.
+ Vector<std::string_view> uris;
+
+ // Indicates whether the current TX power level should be included in the
+ // advertising data.
+ bool include_tx_power_level = false;
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/bond_data.h b/pw_bluetooth/public/pw_bluetooth/low_energy/bond_data.h
new file mode 100644
index 0000000..5029aa9
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/bond_data.h
@@ -0,0 +1,78 @@
+// Copyright 2022 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 <array>
+#include <cstdint>
+#include <optional>
+
+#include "pw_bluetooth/low_energy/connection.h"
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+
+namespace pw::bluetooth::low_energy {
+
+// A 128-bit secret key.
+using Key = std::array<uint8_t, 16>;
+
+/// Represents a LE Long-Term peer key used for link encyrption. The `ediv` and
+/// `rand` fields are zero if distributed using LE Secure Connections pairing.
+struct LongTermKey {
+ Key key;
+ uint16_t ediv;
+ uint16_t rand;
+};
+
+struct BondData {
+ // The identifier that uniquely identifies this peer.
+ PeerId peer_id;
+
+ // The local Bluetooth identity address that this bond is associated with.
+ Address local_address;
+
+ std::optional<DeviceName> name;
+
+ // The identity address of the peer.
+ Address peer_address;
+
+ // The peer's preferred connection parameters, if known.
+ std::optional<RequestedConnectionParameters> connection_parameters;
+
+ // Identity Resolving RemoteKey used to generate and resolve random addresses.
+ Key identity_resolving_remote_key;
+
+ // Connection Signature Resolving RemoteKey used for data signing without
+ // encryption.
+ Key connection_signature_resolving_remote_key;
+
+ // LE long-term key used to encrypt a connection when the peer is in the LE
+ // Peripheral role.
+ //
+ // In legacy pairing (`peer_long_term_key.security.secure_connections` is
+ // false), this key corresponds to the key distributed by the peer. In Secure
+ // Connections pairing there is only one LTK and `peer_long_term_key` is the
+ // same as `local_long_term_key`.
+ LongTermKey peer_long_term_key;
+
+ // LE long-term key used to encrypt a connection when the peer is in the LE
+ // Central role.
+ //
+ // In legacy pairing (`local_long_term_key.security.secure_connections` is
+ // false), this key corresponds to the key distributed by the local device.
+ // In Secure Connections pairing there is only one LTK and
+ // `local_long_term_key` is the same as `peer_long_term_key`.
+ LongTermKey local_long_term_key;
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/central.h b/pw_bluetooth/public/pw_bluetooth/low_energy/central.h
new file mode 100644
index 0000000..2ce553e
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/central.h
@@ -0,0 +1,244 @@
+// Copyright 2022 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 <memory>
+#include <optional>
+
+#include "pw_bluetooth/low_energy/advertising_data.h"
+#include "pw_bluetooth/low_energy/connection.h"
+#include "pw_bluetooth/result.h"
+#include "pw_bluetooth/types.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_containers/vector.h"
+#include "pw_function/function.h"
+
+namespace pw::bluetooth::low_energy {
+
+// Represents the LE central role. Used to scan and connect to peripherals.
+class Central {
+ public:
+ // Represents an ongoing LE scan.
+ class Scan {
+ public:
+ enum class ScanError : uint8_t { kCanceled = 0 };
+
+ virtual ~Scan() = 0;
+
+ // Set a callback that will be called if the scan is stopped due to an error
+ // in the BLE stack.
+ virtual void SetErrorCallback(Function<void(ScanError)>&& callback) = 0;
+ };
+
+ // Filter parameters for use during a scan. A discovered peer only matches the
+ // filter if it satisfies all of the present filter parameters.
+ struct ScanFilter {
+ // Filter based on advertised service UUID.
+ std::optional<Uuid> service_uuid;
+
+ // Filter based on service data containing the given UUID.
+ std::optional<Uuid> service_data_uuid;
+
+ // Filter based on a manufacturer identifier present in the manufacturer
+ // data. If this filter parameter is set, then the advertising payload must
+ // contain manufacturer specific data with the provided company identifier
+ // to satisfy this filter. Manufacturer identifiers can be found at
+ // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
+ std::optional<uint16_t> manufacturer_id;
+
+ // Filter based on whether or not a device is connectable. For example, a
+ // client that is only interested in peripherals that it can connect to can
+ // set this to true. Similarly a client can scan only for broadcasters by
+ // setting this to false.
+ std::optional<bool> connectable;
+
+ // Filter results based on a portion of the advertised device name.
+ // Substring matches are allowed.
+ // The name length must be at most pw::bluetooth::kMaxDeviceNameLength.
+ std::optional<std::string_view> name;
+
+ // Filter results based on the path loss of the radio wave. A device that
+ // matches this filter must satisfy the following:
+ // 1. Radio transmission power level and received signal strength must be
+ // available for the path loss calculation.
+ // 2. The calculated path loss value must be less than, or equal to,
+ // `max_path_loss`.
+ //
+ // NOTE: This field is calculated using the RSSI and TX Power information
+ // obtained from advertising and scan response data during a scan procedure.
+ // It should NOT be confused with information for an active connection
+ // obtained using the "Path Loss Reporting" feature.
+ std::optional<uint8_t> max_path_loss;
+ };
+
+ // Parameters used during a scan.
+ struct ScanOptions {
+ // List of filters for use during a scan. A peripheral that satisfies any of
+ // these filters will be reported. At least 1 filter must be specified.
+ // While not recommended, clients that require that all peripherals be
+ // reported can specify an empty filter.
+ Vector<ScanFilter> filters;
+
+ // The time interval between scans.
+ // Time = N * 0.625ms
+ // Range: 0x0004 (2.5ms) - 10.24ms (0x4000)
+ // Default: 10ms
+ uint16_t interval = 0x0010;
+
+ // The duration of the scan. The window must be less than or equal to the
+ // interval.
+ // Time = N * 0.625ms
+ // Range: 0x0004 (2.5ms) - 10.24ms (0x4000)
+ // Default: 10ms
+ uint16_t window = 0x0010;
+ };
+
+ // Information obtained from advertising and scan response data broadcast by a
+ // peer.
+ struct ScanData {
+ // The radio transmit power level.
+ // NOTE: This field should NOT be confused with the "connection TX Power
+ // Level" of a peer that is currently connected to the system obtained via
+ // the "Transmit Power reporting" feature.
+ std::optional<uint8_t> tx_power;
+
+ // The appearance of the device.
+ std::optional<Appearance> appearance;
+
+ Vector<Uuid> service_uuids;
+
+ Vector<ServiceData> service_data;
+
+ Vector<ManufacturerData> manufacturer_data;
+
+ // String representing a URI to be advertised, as defined in IETF STD
+ // 66: https://tools.ietf.org/html/std66. Each entry should be a UTF-8
+ // string including the scheme. For more information, see
+ // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml for
+ // allowed schemes; NOTE: Bluetooth advertising compresses schemas over the
+ // air to save space. See
+ // https://www.bluetooth.com/specifications/assigned-numbers/uri-scheme-name-string-mapping.
+ Vector<std::string_view> uris;
+
+ // The time when this scan data was received.
+ chrono::SystemClock::time_point timestamp;
+ };
+
+ struct ScanResult {
+ // ScanResult is non-copyable becuase strings are only valid in the
+ // result callback.
+ ScanResult(const ScanResult&) = delete;
+ ScanResult& operator=(const ScanResult&) = delete;
+
+ // Uniquely identifies this peer on the current system.
+ PeerId peer_id;
+
+ // Whether or not this peer is connectable. Non-connectable peers are
+ // typically in the LE broadcaster role.
+ bool connectable;
+
+ // The last observed signal strength of this peer. This field is only
+ // present for a peer that is broadcasting. The RSSI can be stale if the
+ // peer has not been advertising.
+ //
+ // NOTE: This field should NOT be confused with the "connection RSSI" of a
+ // peer that is currently connected to the system.
+ std::optional<uint8_t> rssi;
+
+ // Information from advertising and scan response data broadcast by this
+ // peer. This contains the advertising data last received from the peer.
+ ScanData scan_data;
+
+ // The name of this peer. The name is often obtained during a scan procedure
+ // and can get updated during the name discovery procedure following a
+ // connection.
+ //
+ // This field is present if the name is known.
+ std::optional<std::string_view> name;
+
+ // Timestamp of when the information in this `ScanResult` was last updated.
+ chrono::SystemClock::time_point last_updated;
+ };
+
+ // Possible errors returned by `Connect`.
+ enum class ConnectError : uint8_t {
+ // The peer ID is unknown.
+ kUnknownPeer,
+
+ // The `ConnectionOptions` were invalid.
+ kInvalidOptions,
+
+ // A connection to the peer already exists.
+ kAlreadyExists,
+
+ // A connection could not be established.
+ kCouldNotBeEstablished,
+ };
+
+ enum class StartScanError : uint8_t {
+ // A scan is already in progress. Only 1 scan may be active at a time.
+ kScanInProgress,
+ // Some of the scan options are invalid.
+ kInvalidParameters,
+ // An internal error occurred and a scan could not be started.
+ kInternal,
+ };
+
+ virtual ~Central() = default;
+
+ // Connect to the peer with the given identifier.
+ //
+ // The requested `Connection` represents the client's interest in the LE
+ // connection to the peer. Destroying the `Connection` will disconnect from
+ // the peer. Only 1 connection per peer may exist at a time.
+ //
+ // The `Connection` will be closed by the system if the connection to the peer
+ // is lost or an error occurs, as indicated by `Connection.OnError`.
+ //
+ // Parameters:
+ // `id` - Identifier of the peer to initiate a connection to.
+ // `options` - Options used to configure the connection.
+ // `callback` - Called when a connection is successfully established, or an
+ // error occurs.
+ //
+ // Possible errors are documented in `ConnectError`.
+ virtual void Connect(
+ PeerId peer_id,
+ ConnectionOptions options,
+ Function<void(Result<ConnectError, std::unique_ptr<Connection>>)>&&
+ callback) = 0;
+
+ // Scans for nearby LE peripherals and broadcasters. The lifetime of the scan
+ // session is tied to the returned `Scan` object. Once a scan is started,
+ // `scan_result_callback` will be called with scan results. Only 1 scan may be
+ // active at a time. It is OK to destroy the `Scan` object in
+ // `scan_result_callback` to stop scanning (no more results will be returned).
+ //
+ // Parameters:
+ // `options` - Options used to configure the scan session.
+ // `scan_result_callback` - If scanning starts successfully,called for LE
+ // peers that satisfy the filters indicated in `options`. The initial
+ // calls may report recently discovered peers. Subsequent calls will
+ // be made only when peers have been scanned or updated since the last
+ // call.
+ // `scan_started_callback` - Called with a `Scan` object if the
+ // scan successfully starts, or a `ScanError` otherwise.
+ virtual void Scan(
+ ScanOptions options,
+ Function<void(ScanResult)>&& scan_result_callback,
+ Function<void(Result<StartScanError, std::unique_ptr<Scan>>)>&&
+ scan_started_callback) = 0;
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/connection.h b/pw_bluetooth/public/pw_bluetooth/low_energy/connection.h
new file mode 100644
index 0000000..8895c65
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/connection.h
@@ -0,0 +1,160 @@
+// Copyright 2022 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 "pw_bluetooth/gatt/client.h"
+#include "pw_bluetooth/types.h"
+
+namespace pw::bluetooth::low_energy {
+
+// Actual connection parameters returned by the controller.
+struct ConnectionParameters {
+ // The connection interval indicates the frequency of link layer connection
+ // events over which data channel PDUs can be transmitted. See Core Spec v5.3,
+ // Vol 6, Part B, Section 4.5.1 for more information on the link layer
+ // connection events.
+ // Range: 0x0006 to 0x0C80
+ // Time: N * 1.25 ms
+ // Time Range: 7.5 ms to 4 s.
+ uint16_t interval;
+
+ // The maximum allowed peripheral connection latency in number of connection
+ // events. See Core Spec v5.3, Vol 6, Part B, Section 4.5.2.
+ // Range: 0x0000 to 0x01F3
+ uint16_t latency;
+
+ // This defines the maximum time between two received data packet PDUs
+ // before the connection is considered lost. See Core Spec v5.3, Vol 6, Part
+ // B, Section 4.5.2.
+ // Range: 0x000A to 0x0C80
+ // Time: N * 10 ms
+ // Time Range: 100 ms to 32 s
+ uint16_t supervision_timeout;
+};
+
+// Connection parameters that either the local device or a peer device are
+// requesting.
+struct RequestedConnectionParameters {
+ // Minimum value for the connection interval. This shall be less than or equal
+ // to `max_interval`. The connection interval indicates the frequency of link
+ // layer connection events over which data channel PDUs can be transmitted.
+ // See Core Spec v5.3, Vol 6, Part B, Section 4.5.1 for more information on
+ // the link layer connection events.
+ // Range: 0x0006 to 0x0C80
+ // Time: N * 1.25 ms
+ // Time Range: 7.5 ms to 4 s.
+ uint16_t min_interval;
+
+ // Maximum value for the connection interval. This shall be greater than or
+ // equal to `min_interval`. The connection interval indicates the frequency
+ // of link layer connection events over which data channel PDUs can be
+ // transmitted. See Core Spec v5.3, Vol 6, Part B, Section 4.5.1 for more
+ // information on the link layer connection events.
+ // Range: 0x0006 to 0x0C80
+ // Time: N * 1.25 ms
+ // Time Range: 7.5 ms to 4 s.
+ uint16_t max_interval;
+
+ // Maximum peripheral latency for the connection in number of connection
+ // events. See Core Spec v5.3, Vol 6, Part B, Section 4.5.2.
+ // Range: 0x0000 to 0x01F3
+ uint16_t max_latency;
+
+ // This defines the maximum time between two received data packet PDUs
+ // before the connection is considered lost. See Core Spec v5.3, Vol 6, Part
+ // B, Section 4.5.2.
+ // Range: 0x000A to 0x0C80
+ // Time: N * 10 ms
+ // Time Range: 100 ms to 32 s
+ uint16_t supervision_timeout;
+};
+
+// Represents parameters that are set on a per-connection basis.
+struct ConnectionOptions {
+ // When true, the connection operates in bondable mode. This means pairing
+ // will form a bond, or persist across disconnections, if the peer is also
+ // in bondable mode. When false, the connection operates in non-bondable
+ // mode, which means the local device only allows pairing that does not form
+ // a bond.
+ bool bondable_mode = true;
+
+ // When present, service discovery performed following the connection is
+ // restricted to primary services that match this field. Otherwise, by
+ // default all available services are discovered.
+ std::optional<Uuid> service_filter;
+
+ // When present, specifies the initial connection parameters. Otherwise, the
+ // connection parameters will be selected by the implementation.
+ std::optional<RequestedConnectionParameters> parameters;
+};
+
+/// Class that represents a connection to a peer. This can be used to interact
+/// with GATT services and establish LE L2CAP channels.
+///
+/// This lifetime of this object is tied to that of the LE connection it
+/// represents. Destroying the object results in a disconnection.
+class Connection {
+ public:
+ // Possible errors when updating the connection parameters.
+ enum class ConnectionParameterUpdateError : uint8_t {
+ kFailure,
+ kInvalidParameters,
+ kRejected,
+ };
+
+ // Possible reasons a connection was disconnected.
+ enum class DisconnectReason : uint8_t {
+ kFailure,
+ kRemoteUserTerminatedConnection,
+ // This usually indicates that the link supervision timeout expired.
+ kConnectionTimeout,
+ };
+
+ // If a disconnection has not occurred, destroying this object will result in
+ // disconnection.
+ virtual ~Connection() = default;
+
+ // Sets a callback that will be called when the peer disconnects or there is a
+ // connection error that causes a disconnection. This should be configured by
+ // the client immediately after establishing the connection. `callback` will
+ // not be called for disconnections iniated by the client (e.g. by destroying
+ // `Connection`). It is OK to destroy this object from within `callback`.
+ virtual void SetDisconnectCallback(
+ Function<void(DisconnectReason)>&& callback) = 0;
+
+ // Returns a GATT client to the connected peer that is valid for the lifetime
+ // of this connection. The client is valid for the lifetime of this
+ // connection.
+ virtual gatt::Client* GattClient() = 0;
+
+ // Returns the current ATT Maximum Transmission Unit. By subtracting ATT
+ // headers from the MTU, the maximum payload size of messages can be
+ // calculated.
+ virtual uint16_t AttMtu() = 0;
+
+ // Sets a callback that will be called with the new ATT MTU whenever it is
+ // updated.
+ virtual void SetAttMtuChangeCallback(Function<void(uint16_t)> callback) = 0;
+
+ // Returns the current connection parameters.
+ virtual ConnectionParameters Parameters() = 0;
+
+ // Requests an update to the connection parameters. `callback` will be called
+ // with the result of the request.
+ virtual void RequestConnectionParameterUpdate(
+ RequestedConnectionParameters parameters,
+ Function<void(Result<ConnectionParameterUpdateError>)>&& callback) = 0;
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/peripheral.h b/pw_bluetooth/public/pw_bluetooth/low_energy/peripheral.h
new file mode 100644
index 0000000..8a77492
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/peripheral.h
@@ -0,0 +1,145 @@
+// Copyright 2022 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 <cstdint>
+#include <memory>
+
+#include "pw_bluetooth/low_energy/advertising_data.h"
+#include "pw_bluetooth/low_energy/connection.h"
+#include "pw_bluetooth/result.h"
+#include "pw_bluetooth/types.h"
+#include "pw_function/function.h"
+#include "pw_status/status.h"
+
+namespace pw::bluetooth::low_energy {
+
+// `AdvertisedPeripheral` instances are valid for the duration of advertising.
+class AdvertisedPeripheral {
+ public:
+ // Destroying an instance will stop advertising.
+ virtual ~AdvertisedPeripheral() = default;
+
+ // Set a callback that will be called when an error occurs and advertising
+ // has been stopped (invalidating this object). It is OK to destroy this
+ // object from within `callback`.
+ virtual void SetErrorCallback(Closure callback) = 0;
+
+ // For connectable advertisements, this callback will be called when an LE
+ // central connects to the advertisement. It is recommended to set this
+ // callback immediately after advertising starts to avoid dropping
+ // connections.
+ //
+ // The returned Connection can be used to interact with the peer. It also
+ // represents a peripheral's ownership over the connection: the client can
+ // drop the object to request a disconnection. Similarly, the Connection
+ // error handler is called by the system to indicate that the connection to
+ // the peer has been lost. While connections are exclusive among peripherals,
+ // they may be shared with centrals.
+ //
+ // If advertising is not stopped, this callback may be called with multiple
+ // connections over the lifetime of an advertisement. It is OK to destroy
+ // this object from within `callback` in order to stop advertising.
+ virtual void SetConnectionCallback(
+ Function<void(std::unique_ptr<Connection>)> callback) = 0;
+};
+
+// Represents the LE Peripheral role, which advertises and is connected to.
+class Peripheral {
+ public:
+ // The range of the time interval between advertisements. Shorter intervals
+ // result in faster discovery at the cost of higher power consumption. The
+ // exact interval used is determined by the Bluetooth controller.
+ // Time = N * 0.625ms.
+ // Time range: 0x0020 (20ms) - 0x4000 (10.24s)
+ struct AdvertisingIntervalRange {
+ uint16_t min = 0x0800; // 1.28s
+ uint16_t max = 0x0800; // 1.28s
+ };
+
+ // Represents the parameters for configuring advertisements.
+ struct AdvertisingParameters {
+ // The fields that will be encoded in the data section of advertising
+ // packets.
+ AdvertisingData data;
+
+ // The fields that are to be sent in a scan response packet. Clients may use
+ // this to send additional data that does not fit inside an advertising
+ // packet on platforms that do not support the advertising data length
+ // extensions.
+ //
+ // If present advertisements will be configured to be scannable.
+ std::optional<AdvertisingData> scan_response;
+
+ // See `AdvertisingIntervalRange` documentation.
+ AdvertisingIntervalRange interval_range;
+
+ // If present, the controller will broadcast connectable advertisements
+ // which allow peers to initiate connections to the Peripheral. The fields
+ // of `ConnectionOptions` will configure any connections set up from
+ // advertising.
+ std::optional<ConnectionOptions> connection_options;
+ };
+
+ // Errors returned by `Advertise`.
+ enum class AdvertiseError {
+ // The operation or parameters requested are not supported on the current
+ // hardware.
+ kNotSupported = 1,
+
+ // The provided advertising data exceeds the maximum allowed length when
+ // encoded.
+ kAdvertisingDataTooLong = 2,
+
+ // The provided scan response data exceeds the maximum allowed length when
+ // encoded.
+ kScanResponseDataTooLong = 3,
+
+ // The requested parameters are invalid.
+ kInvalidParameters = 4,
+
+ // This may be called if the maximum number of simultaneous advertisements
+ // has already been reached.
+ kNotEnoughAdvertisingSlots = 5,
+
+ // Advertising could not be initiated due to a hardware or system error.
+ kFailed = 6,
+ };
+
+ using AdvertiseCallback = Function<void(
+ Result<AdvertiseError, std::unique_ptr<AdvertisedPeripheral>>)>;
+
+ virtual ~Peripheral() = default;
+
+ // Start advertising continuously as a LE peripheral. If advertising cannot
+ // be initiated then `result_callback` will be called with an error. Once
+ // started, advertising can be stopped by destroying the returned
+ // `AdvertisedPeripheral`.
+ //
+ // If the system supports multiple advertising, this may be called as many
+ // times as there are advertising slots. To reconfigure an advertisement,
+ // first close the original advertisement and then initiate a new
+ // advertisement.
+ //
+ // Parameters:
+ // `parameters` - Parameters used while configuring the advertising
+ // instance.
+ // `result_callback` - Called once advertising has started or failed. On
+ // success, called with an `AdvertisedPeripheral` that models the lifetime
+ // of the advertisement. Destroying it will stop advertising.
+ virtual void Advertise(AdvertisingParameters parameters,
+ AdvertiseCallback&& result_callback) = 0;
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/low_energy/security_mode.h b/pw_bluetooth/public/pw_bluetooth/low_energy/security_mode.h
new file mode 100644
index 0000000..9fb2250
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/low_energy/security_mode.h
@@ -0,0 +1,41 @@
+// Copyright 2022 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 <cstdint>
+
+namespace pw::bluetooth::low_energy {
+
+// The LE Security Mode of a BLE device determines the possible security
+// properties of the device. The security mode does not make specific guarantees
+// about the current security properties of a device's connections; it sets
+// restrictions on the allowable security properties. See Core Spec v5.2 Vol. 3,
+// Part C 10.2 for more details.
+enum class SecurityMode : uint8_t {
+ // In LE Security Mode 1, communication is secured by encryption, and
+ // BLE-based services may specify varying requirements for authentication, key
+ // size, or Secure Connections protection on the encryption keys.
+ kMode1 = 0,
+
+ // In Secure Connections Only mode, all secure communication must use 128 bit,
+ // authenticated, and LE Secure Connections-generated encryption keys. If
+ // these encryption key properties cannot be satisfied by a device due to
+ // system constraints, any connection involving such a device will not be able
+ // to secure the link at all. This mode does not prevent unencrypted
+ // communication; it merely enforces stricter policies on all encrypted
+ // communication.
+ kSecureConnectionsOnly = 1
+};
+
+} // namespace pw::bluetooth::low_energy
diff --git a/pw_bluetooth/public/pw_bluetooth/pairing_delegate.h b/pw_bluetooth/public/pw_bluetooth/pairing_delegate.h
new file mode 100644
index 0000000..c4c5c53
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/pairing_delegate.h
@@ -0,0 +1,119 @@
+// Copyright 2022 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 "pw_bluetooth/peer.h"
+#include "pw_function/function.h"
+
+namespace pw::bluetooth {
+
+// Input Capabilities for pairing exchanges.
+// See Core Spec v5.3 Volume 3, Part C, Section 5.2.2.4, Table 5.3.
+enum class InputCapability : uint8_t { kNone, kConfirmation, kKeyboard };
+
+// Output Capabilities for pairing exchanges.
+// See Core Spec v5.3 Volume 3, Part C, Section 5.2.2.4, Table 5.4.
+enum class OutputCapability : uint8_t { kNone, kDisplay };
+
+// Pairing event handler implemented by the API client.
+class PairingDelegate {
+ public:
+ // Different types required by the Security Manager for pairing methods.
+ // Bluetooth SIG has different requirements for different device capabilities.
+ enum class PairingMethod : uint8_t {
+ // The user is asked to accept or reject pairing.
+ kConsent,
+
+ // The user is shown a 6-digit numerical passkey which they must enter on
+ // the
+ // peer device.
+ kPasskeyDisplay,
+
+ // The user is shown a 6-digit numerical passkey which will also shown on
+ // the
+ // peer device. The user must compare the passkeys and accept the pairing if
+ // the passkeys match.
+ kPasskeyComparison,
+
+ // The user is asked to enter a 6-digit passkey.
+ kPasskeyEntry
+ };
+
+ enum class PairingKeypress : uint8_t {
+ // The user has entered a single digit.
+ kDigitEntered,
+
+ // The user has erased a single digit.
+ kDigitErased,
+
+ // The user has cleared the entire passkey.
+ kPasskeyCleared,
+
+ // The user has finished entering the passkey.
+ kPasskeyEntered
+ };
+
+ // Callback for responding to pairing requests.
+ using ResponseCallback =
+ pw::Function<void(bool accept, uint32_t entered_passkey)>;
+
+ // Callback for signaling local keypresses to a peer.
+ using KeypressCallback =
+ pw::Function<void(PeerId peer_id, PairingKeypress keypress)>;
+
+ virtual ~PairingDelegate() = default;
+
+ // Called to initiate a pairing request. The delegate must respond with "true"
+ // or "false" in the callback to either accept or reject the pairing request.
+ // If the pairing method requires a passkey this is returned as well. It is OK
+ // to call `callback` synchronously in this method.
+ //
+ // Any response from this method will be ignored if the `OnPairingComplete`
+ // event has already been sent for `peer`.
+ //
+ // The `displayed_passkey` parameter should be displayed to the user if
+ // `method` equals `PairingMethod::kPasskeyDisplay` or
+ // `PairingMethod.kPasskeyComparison`. Otherwise, this parameter has no
+ // meaning and should be ignored.
+ //
+ // The `entered_passkey` parameter of `callback` only has meaning if `method`
+ // equals `PairingMethod.kPasskeyEntry`. It will be ignored otherwise.
+ virtual void OnPairingRequest(Peer peer,
+ PairingMethod method,
+ uint32_t displayed_passkey,
+ ResponseCallback&& callback) = 0;
+
+ // Called if the pairing procedure for the device with the given ID is
+ // completed. This can be due to successful completion or an error (e.g. due
+ // to cancellation by the peer, a timeout, or disconnection) which is
+ // indicated by `success`.
+ virtual void OnPairingComplete(PeerId peer_id, bool success) = 0;
+
+ // Called to notify keypresses from the peer device during pairing using
+ // `PairingMethod::kPasskeyDisplay`.
+ //
+ // This event is used to provide key press events to the delegate for a
+ // responsive user experience as the user types the passkey on the peer
+ // device. This event will be called once for each keypress.
+ virtual void OnRemoteKeypress(PeerId peer_id, PairingKeypress keypress) = 0;
+
+ // Sets a callback that the client may call on local passkey keypresses during
+ // a `PairingMethod::kPasskeyEntry` pairing request. Signaled keypresses may
+ // be used in the UI of the peer. This should be set immediately by the
+ // Bluetooth stack when the delegate is configured by the client unless
+ // sending local keypresses is not supported.
+ virtual void SetLocalKeypressCallback(KeypressCallback&& callback) = 0;
+};
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/peer.h b/pw_bluetooth/public/pw_bluetooth/peer.h
new file mode 100644
index 0000000..39990dc
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/peer.h
@@ -0,0 +1,41 @@
+// Copyright 2022 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 <optional>
+
+#include "pw_bluetooth/types.h"
+#include "pw_containers/vector.h"
+
+namespace pw::bluetooth {
+
+// Information about a remote Bluetooth device.
+struct Peer {
+ // Uniquely identifies this peer on the current system.
+ PeerId peer_id;
+
+ // Bluetooth device address that identifies this peer.
+ // NOTE: Clients should use the `peer_id` field to keep track of peers
+ // instead of their address.
+ Address address;
+
+ // The name of the peer, if known.
+ std::optional<DeviceName> name;
+
+ // The LE appearance property. Present if the appearance information was
+ // obtained over advertising and/or GATT.
+ std::optional<Appearance> appearance;
+};
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/result.h b/pw_bluetooth/public/pw_bluetooth/result.h
new file mode 100644
index 0000000..b543e92
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/result.h
@@ -0,0 +1,101 @@
+// Copyright 2022 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 <optional>
+#include <utility>
+
+#include "pw_assert/assert.h"
+
+namespace pw::bluetooth {
+
+// A Result represents the result of an operation which can fail. If it
+// represents an error, it contains an error value. If it represents success, it
+// contains zero or one success value.
+template <typename E, typename... Ts>
+class Result;
+
+// Result specialization for returning OK or an error (E).
+template <typename E>
+class [[nodiscard]] Result<E> {
+ public:
+ constexpr Result() = default;
+ constexpr Result(E error) : error_(error) {}
+
+ constexpr Result(const Result&) = default;
+ constexpr Result& operator=(const Result&) = default;
+
+ constexpr Result(Result&&) = default;
+ constexpr Result& operator=(Result&&) = default;
+
+ [[nodiscard]] constexpr E error() const {
+ PW_ASSERT(error_.has_value());
+ return error_.value();
+ }
+ [[nodiscard]] constexpr bool ok() const { return !error_.has_value(); }
+
+ private:
+ std::optional<E> error_;
+};
+
+// Result specialization for returning some data (T) or an error (E).
+template <typename E, typename T>
+class [[nodiscard]] Result<E, T> {
+ public:
+ constexpr Result(T&& value) : value_(std::move(value)) {}
+ constexpr Result(const T& value) : value_(value) {}
+
+ template <typename... Args>
+ constexpr Result(std::in_place_t, Args&&... args)
+ : value_(std::forward<Args>(args)...) {}
+
+ constexpr Result(E error) : error_(error), ok_(false) {}
+
+ constexpr Result(const Result&) = default;
+ constexpr Result& operator=(const Result&) = default;
+
+ constexpr Result(Result&&) = default;
+ constexpr Result& operator=(Result&&) = default;
+
+ [[nodiscard]] constexpr E error() const {
+ PW_ASSERT(!ok_);
+ return error_;
+ }
+ [[nodiscard]] constexpr bool ok() const { return ok_; }
+
+ constexpr T& value() & {
+ PW_ASSERT(ok_);
+ return value_;
+ }
+
+ constexpr const T& value() const& {
+ PW_ASSERT(ok_);
+ return value_;
+ }
+
+ constexpr T&& value() && {
+ PW_ASSERT(ok_);
+ return std::move(value_);
+ }
+
+ private:
+ union {
+ T value_;
+ E error_;
+ };
+
+ bool ok_ = true;
+};
+
+} // namespace pw::bluetooth
diff --git a/pw_bluetooth/public/pw_bluetooth/types.h b/pw_bluetooth/public/pw_bluetooth/types.h
new file mode 100644
index 0000000..4918f85
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/types.h
@@ -0,0 +1,103 @@
+// Copyright 2022 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 <array>
+#include <cstdint>
+#include <string_view>
+
+namespace pw::bluetooth {
+
+// 64-bit unique value used by the system to identify peer devices.
+using PeerId = uint64_t;
+
+// The device address bytes in little-endian order.
+using Address = std::array<uint8_t, 6>;
+
+using DeviceName = std::string_view;
+
+using Uuid = std::array<uint8_t, 16>;
+
+// A 128-bit secret key.
+using Key = std::array<uint8_t, 16>;
+
+/// Refers to the role of a Bluetooth device in a physical channel piconet.
+enum class ConnectionRole : uint8_t {
+ // The connection initiating device.
+ kCentral,
+ // The advertising device.
+ kPeripheral
+};
+
+/// Possible values for the LE Appearance property which describes the external
+/// appearance of a peer at a high level.
+/// (See the Bluetooth assigned-numbers document:
+/// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml)
+enum class Appearance : uint16_t {
+ kUnknown = 0,
+ kPhone = 64,
+ kComputer = 128,
+ kWatch = 192,
+ kWatchSports = 193,
+ kClock = 256,
+ kDisplay = 320,
+ kRemoteControl = 384,
+ kEyeGlasses = 448,
+ kTag = 512,
+ kKeyring = 576,
+ kMediaPlayer = 640,
+ kBarcodeScanner = 704,
+ kThermometer = 768,
+ kThermometerEar = 769,
+ kHeartRateSensor = 832,
+ kHeartRateSensorBelt = 833,
+ kBloodPressure = 896,
+ kBloodPressureArm = 897,
+ kBloodPressureWrist = 898,
+ kHid = 960,
+ kHidKeyboard = 961,
+ kHidMouse = 962,
+ kHidJoystick = 963,
+ kHidGamepad = 964,
+ kHidDigitizerTablet = 965,
+ kHidCardReader = 966,
+ kHidDigitalPen = 967,
+ kHidBarcodeScanner = 968,
+ kGlucoseMeter = 1024,
+ kRunningWalkingSensor = 1088,
+ kRunningWalkingSensorInShoe = 1089,
+ kRunningWalkingSensorOnShoe = 1090,
+ kRunningWalkingSensorOnHip = 1091,
+ kCycling = 1152,
+ kCyclingComputer = 1153,
+ kCyclingSpeedSensor = 1154,
+ kCyclingCadenceSensor = 1155,
+ kCyclingPowerSensor = 1156,
+ kCyclingSpeedAndCadenceSensor = 1157,
+ kPulseOximeter = 3136,
+ kPulseOximeterFingertip = 3137,
+ kPulseOximeterWrist = 3138,
+ kWeightScale = 3200,
+ kPersonalMobility = 3264,
+ kPersonalMobilityWheelchair = 3265,
+ kPersonalMobilityScooter = 3266,
+ kGlucoseMonitor = 3328,
+ kSportsActivity = 5184,
+ kSportsActivityLocationDisplay = 5185,
+ kSportsActivityLocationAndNavDisplay = 5186,
+ kSportsActivityLocationPod = 5187,
+ kSportsActivityLocationAndNavPod = 5188,
+};
+
+} // namespace pw::bluetooth
\ No newline at end of file
diff --git a/pw_bluetooth/result_test.cc b/pw_bluetooth/result_test.cc
new file mode 100644
index 0000000..fb65921
--- /dev/null
+++ b/pw_bluetooth/result_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2022 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_bluetooth/result.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::bluetooth {
+namespace {
+
+enum class TestError { kFailure0, kFailure1 };
+
+TEST(ResultTest, NoValue) {
+ EXPECT_TRUE(Result<TestError>().ok());
+
+ Result<TestError> error_result(TestError::kFailure0);
+ ASSERT_FALSE(error_result.ok());
+ EXPECT_EQ(error_result.error(), TestError::kFailure0);
+}
+
+TEST(ResultTest, Value) {
+ Result<TestError, int> ok_result(42);
+ ASSERT_TRUE(ok_result.ok());
+ EXPECT_EQ(ok_result.value(), 42);
+
+ Result<TestError, int> error_result(TestError::kFailure1);
+ ASSERT_FALSE(error_result.ok());
+ EXPECT_EQ(error_result.error(), TestError::kFailure1);
+}
+
+} // namespace
+} // namespace pw::bluetooth
diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni
index 21d9203..8a80c64 100644
--- a/pw_build/generated_pigweed_modules_lists.gni
+++ b/pw_build/generated_pigweed_modules_lists.gni
@@ -40,6 +40,7 @@
dir_pw_base64 = get_path_info("../pw_base64", "abspath")
dir_pw_bloat = get_path_info("../pw_bloat", "abspath")
dir_pw_blob_store = get_path_info("../pw_blob_store", "abspath")
+ dir_pw_bluetooth = get_path_info("../pw_bluetooth", "abspath")
dir_pw_bluetooth_hci = get_path_info("../pw_bluetooth_hci", "abspath")
dir_pw_boot = get_path_info("../pw_boot", "abspath")
dir_pw_boot_cortex_m = get_path_info("../pw_boot_cortex_m", "abspath")
@@ -175,6 +176,7 @@
dir_pw_base64,
dir_pw_bloat,
dir_pw_blob_store,
+ dir_pw_bluetooth,
dir_pw_bluetooth_hci,
dir_pw_boot,
dir_pw_boot_cortex_m,
@@ -291,6 +293,7 @@
"$dir_pw_assert:tests",
"$dir_pw_base64:tests",
"$dir_pw_blob_store:tests",
+ "$dir_pw_bluetooth:tests",
"$dir_pw_bluetooth_hci:tests",
"$dir_pw_bytes:tests",
"$dir_pw_checksum:tests",
@@ -365,6 +368,7 @@
"$dir_pw_base64:docs",
"$dir_pw_bloat:docs",
"$dir_pw_blob_store:docs",
+ "$dir_pw_bluetooth:docs",
"$dir_pw_bluetooth_hci:docs",
"$dir_pw_boot:docs",
"$dir_pw_boot_cortex_m:docs",