[bt-pacs] Initial PACS crate

Add bt_gatt::client::FromCharacteristic as utility to be used
in a later change, as a convenient way to define decoding and updating
structures defined directly from characteristic values.

Add Characteristic types and deocding.

Bug: b/308483293
Test: cargo test
Change-Id: If56d06670eb58895d30c63b23194228282ac59cf
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1321
Reviewed-by: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/bt-common/src/generic_audio.rs b/rust/bt-common/src/generic_audio.rs
index c2f72f0..f6e33dd 100644
--- a/rust/bt-common/src/generic_audio.rs
+++ b/rust/bt-common/src/generic_audio.rs
@@ -5,3 +5,148 @@
 pub mod codec_capabilities;
 
 pub mod metadata_ltv;
+
+use crate::{codable_as_bitmask, decodable_enum};
+
+// Source:
+// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/profiles_and_services/generic_audio/context_type.yaml
+decodable_enum! {
+    pub enum ContextType<u16, crate::packet_encoding::Error, OutOfRange> {
+        Unspecified = 0x0001,
+        Conversational = 0x0002,
+        Media = 0x0004,
+        Game = 0x0008,
+        Instructional = 0x0010,
+        VoiceAssistants = 0x0020,
+        Live = 0x0040,
+        SoundEffects = 0x0080,
+        Notifications = 0x0100,
+        Ringtone = 0x0200,
+        Alerts = 0x0400,
+        EmergencyAlarm = 0x0800,
+    }
+}
+
+codable_as_bitmask!(ContextType, u16);
+
+// Source:
+// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/profiles_and_services/generic_audio/audio_location_definitions.yaml
+// Regexp magic for quick variants:
+// %s/ - value: \(\S\+\)\n   audio_location: \(.*\)\n/\2 = \1\r,/g
+// with subsequent removal of Spaces
+decodable_enum! {
+    pub enum AudioLocation<u32, crate::packet_encoding::Error, OutOfRange> {
+        FrontLeft = 0x00000001,
+        FrontRight = 0x00000002,
+        FrontCenter = 0x00000004,
+        LowFrequencyEffects1 = 0x00000008,
+        BackLeft = 0x00000010,
+        BackRight = 0x00000020,
+        FrontLeftOfCenter = 0x00000040,
+        FrontRightOfCenter = 0x00000080,
+        BackCenter = 0x00000100,
+        LowFrequencyEffects2 = 0x00000200,
+        SideLeft = 0x00000400,
+        SideRight = 0x00000800,
+        TopFrontLeft = 0x00001000,
+        TopFrontRight = 0x00002000,
+        TopFrontCenter = 0x00004000,
+        TopCenter = 0x00008000,
+        TopBackLeft = 0x00010000,
+        TopBackRight = 0x00020000,
+        TopSideLeft = 0x00040000,
+        TopSideRight = 0x00080000,
+        TopBackCenter = 0x00100000,
+        BottomFrontCenter = 0x00200000,
+        BottomFrontLeft = 0x00400000,
+        BottomFrontRight = 0x00800000,
+        FrontLeftWide = 0x01000000,
+        FrontRightWide = 0x02000000,
+        LeftSurround = 0x04000000,
+        RightSurround = 0x08000000,
+    }
+}
+
+codable_as_bitmask!(AudioLocation, u32);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn locations_decodable() {
+        let five_point_one = 0b111111;
+
+        let locations: std::collections::HashSet<AudioLocation> =
+            AudioLocation::from_bits(five_point_one).collect();
+
+        assert_eq!(6, locations.len());
+
+        let expected_locations = [
+            AudioLocation::FrontLeft,
+            AudioLocation::FrontRight,
+            AudioLocation::FrontCenter,
+            AudioLocation::LowFrequencyEffects1,
+            AudioLocation::BackLeft,
+            AudioLocation::BackRight,
+        ]
+        .into_iter()
+        .collect();
+
+        assert_eq!(locations, expected_locations);
+
+        assert_eq!(AudioLocation::try_from(0x4), Ok(AudioLocation::FrontCenter));
+
+        // Directly decoding a location that is not a single bit is an error.
+        assert!(AudioLocation::try_from(0b1010101).is_err());
+    }
+
+    #[test]
+    fn locations_encodable() {
+        let locations_missing_sub = [
+            AudioLocation::FrontLeft,
+            AudioLocation::FrontRight,
+            AudioLocation::FrontCenter,
+            AudioLocation::BackLeft,
+            AudioLocation::BackRight,
+        ];
+
+        let value = AudioLocation::to_bits(locations_missing_sub.iter());
+
+        assert_eq!(0b110111, value);
+    }
+
+    #[test]
+    fn context_type_decodable() {
+        let contexts: Vec<ContextType> = ContextType::from_bits(0b10).collect();
+        assert_eq!(contexts.len(), 1);
+        assert_eq!(contexts[0], ContextType::Conversational);
+
+        let live_and_instructional = 0b1010000;
+        let contexts: std::collections::HashSet<ContextType> =
+            ContextType::from_bits(live_and_instructional).collect();
+
+        assert_eq!(contexts.len(), 2);
+        assert_eq!(contexts, [ContextType::Live, ContextType::Instructional].into_iter().collect());
+
+        let alerts_and_conversational = 0x0402;
+
+        let contexts: std::collections::HashSet<ContextType> =
+            ContextType::from_bits(alerts_and_conversational).collect();
+
+        assert_eq!(contexts.len(), 2);
+        assert_eq!(
+            contexts,
+            [ContextType::Alerts, ContextType::Conversational].into_iter().collect()
+        );
+    }
+
+    #[test]
+    fn context_type_encodable() {
+        let contexts = [ContextType::Notifications, ContextType::SoundEffects, ContextType::Game];
+
+        let value = ContextType::to_bits(contexts.iter());
+
+        assert_eq!(0x188, value);
+    }
+}
diff --git a/rust/bt-common/src/packet_encoding.rs b/rust/bt-common/src/packet_encoding.rs
index 978430b..10058b8 100644
--- a/rust/bt-common/src/packet_encoding.rs
+++ b/rust/bt-common/src/packet_encoding.rs
@@ -26,6 +26,90 @@
     fn encode(&self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error>;
 }
 
+/// Generates an enum value where each variant can be converted into a constant in the given
+/// raw_type.
+///
+/// For example:
+/// decodable_enum! {
+///     pub(crate) enum Color<u8, MyError, Variant> {
+///        Red = 1,
+///        Blue = 2,
+///        Green = 3,
+///     }
+/// }
+///
+/// Color::try_from(2) -> Color::Red
+/// u8::from(&Color::Red) -> 1.
+#[macro_export]
+macro_rules! decodable_enum {
+    ($(#[$meta:meta])* $visibility:vis enum $name:ident<
+        $raw_type:ty,
+        $error_type:ty,
+        $error_path:ident
+    > {
+        $($(#[$variant_meta:meta])* $variant:ident = $val:expr),*,
+    }) => {
+        $(#[$meta])*
+        #[derive(
+            ::core::clone::Clone,
+            ::core::marker::Copy,
+            ::core::fmt::Debug,
+            ::core::cmp::Eq,
+            ::core::hash::Hash,
+            ::core::cmp::PartialEq)]
+        $visibility enum $name {
+            $($(#[$variant_meta])* $variant = $val),*
+        }
+
+        impl $name {
+            pub const VALUES : &'static [$raw_type] = &[$($val),*,];
+            pub const VARIANTS : &'static [$name] = &[$($name::$variant),*,];
+            pub fn name(&self) -> &'static ::core::primitive::str {
+                match self {
+                    $($name::$variant => ::core::stringify!($variant)),*
+                }
+            }
+        }
+
+        impl ::core::convert::From<&$name> for $raw_type {
+            fn from(v: &$name) -> $raw_type {
+                match v {
+                    $($name::$variant => $val),*,
+                }
+            }
+        }
+
+        impl ::core::convert::TryFrom<$raw_type> for $name {
+            type Error = $error_type;
+
+            fn try_from(value: $raw_type) -> ::core::result::Result<Self, $error_type> {
+                match value {
+                    $($val => ::core::result::Result::Ok($name::$variant)),*,
+                    _ => ::core::result::Result::Err(<$error_type>::$error_path),
+                }
+            }
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! codable_as_bitmask {
+    ($type:ty, $raw_type:ty) => {
+        impl $type {
+            pub fn from_bits(v: $raw_type) -> impl Iterator<Item = $type> {
+                (0..<$raw_type>::BITS)
+                    .map(|bit| 1 << bit)
+                    .filter(move |val| (v & val) != 0)
+                    .filter_map(|val| val.try_into().ok())
+            }
+
+            pub fn to_bits<'a>(it: impl Iterator<Item = &'a $type>) -> $raw_type {
+                it.fold(0, |acc, item| acc | Into::<$raw_type>::into(item))
+            }
+        }
+    };
+}
+
 #[derive(Error, Debug, PartialEq)]
 pub enum Error {
     #[error("Parameter is not valid: {0}")]
diff --git a/rust/bt-gatt/src/client/mod.rs b/rust/bt-gatt/src/client/mod.rs
index 8415f69..e34c8ae 100644
--- a/rust/bt-gatt/src/client/mod.rs
+++ b/rust/bt-gatt/src/client/mod.rs
@@ -12,11 +12,12 @@
     Secondary,
 }
 
-// A short definition of a service that is either being published or has been discovered on a
-// peer.
+// A short definition of a service that is either being published or has been
+// discovered on a peer.
 pub struct PeerServiceDefinition {
     /// Service Handle
-    /// When publishing services, unique among all services published with [`Server::publish`].
+    /// When publishing services, unique among all services published with
+    /// [`Server::publish`].
     pub id: u64,
     /// Whether the service is marked as Primary in the GATT server.
     pub kind: ServiceKind,
@@ -25,7 +26,8 @@
 }
 
 /// GATT Client connected to a particular peer.
-/// Holding a struct that implements this should attempt to maintain a LE connection to the peer.
+/// Holding a struct that implements this should attempt to maintain a LE
+/// connection to the peer.
 pub trait Client {
     type PeerServiceHandleT: PeerServiceHandle;
     type ServiceResultFut: Future<Output = Result<Vec<Self::PeerServiceHandleT>>> + 'static;
@@ -34,8 +36,8 @@
     fn peer_id(&self) -> PeerId;
 
     /// Find services by UUID on the peer.
-    /// This may cause as much as a full discovery of all services on the peer if the stack deems it
-    /// appropriate.
+    /// This may cause as much as a full discovery of all services on the peer
+    /// if the stack deems it appropriate.
     /// Service information should be up to date at the time returned.
     fn find_service(&self, uuid: Uuid) -> Self::ServiceResultFut;
 }
@@ -49,7 +51,25 @@
     fn connect(&self) -> Self::ConnectFut;
 }
 
-#[derive(Debug)]
+/// Implement when a type can be deserialized from a characteristic value.
+pub trait FromCharacteristic: Sized {
+    const UUID: Uuid;
+
+    /// Create this type from a Characteristic and an initial value.
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> ::core::result::Result<Self, bt_common::packet_encoding::Error>;
+
+    /// Attempt to update the type when supplied with the `new_value`, which may
+    /// or may not be the complete value.
+    fn update(
+        &mut self,
+        new_value: &[u8],
+    ) -> ::core::result::Result<&mut Self, bt_common::packet_encoding::Error>;
+}
+
+#[derive(Debug, Clone)]
 pub struct CharacteristicNotification {
     pub handle: Handle,
     pub value: Vec<u8>,
@@ -65,15 +85,17 @@
     type WriteFut<'a>: Future<Output = Result<()>> + 'a;
 
     /// Discover characteristics on this service.
-    /// If `uuid` is provided, only the characteristics matching `uuid` will be returned.
-    /// This operation may use either the Discovery All Characteristics of a Service or
-    /// Discovery Characteristic by UUID procedures, regardless of `uuid`.
+    /// If `uuid` is provided, only the characteristics matching `uuid` will be
+    /// returned. This operation may use either the Discovery All
+    /// Characteristics of a Service or Discovery Characteristic by UUID
+    /// procedures, regardless of `uuid`.
     fn discover_characteristics(&self, uuid: Option<Uuid>) -> Self::CharacteristicsFut;
 
-    /// Read a characteristic into a buffer, given the handle within the service.
-    /// On success, returns the size read and whether the value may have been truncated.
-    /// By default this will try to use a long read if the `buf` is larger than a normal read will
-    /// allow (22 bytes) or if the offset is non-zero.
+    /// Read a characteristic into a buffer, given the handle within the
+    /// service. On success, returns the size read and whether the value may
+    /// have been truncated. By default this will try to use a long read if
+    /// the `buf` is larger than a normal read will allow (22 bytes) or if
+    /// the offset is non-zero.
     fn read_characteristic<'a>(
         &self,
         handle: &Handle,
@@ -104,12 +126,12 @@
     ) -> Self::WriteFut<'a>;
 
     /// Subscribe to updates on a Characteristic.
-    /// Either notifications or indications will be enabled depending on the properties available,
-    /// with indications preferred if they are supported.
-    /// Fails if the Characteristic doesn't support indications or notifications.
-    /// Errors are delivered through an Err item in the stream.
-    /// This will often write to the Client Characteristic Configuration descriptor for the
-    /// Characteristic subscribed to.
+    /// Either notifications or indications will be enabled depending on the
+    /// properties available, with indications preferred if they are
+    /// supported. Fails if the Characteristic doesn't support indications
+    /// or notifications. Errors are delivered through an Err item in the
+    /// stream. This will often write to the Client Characteristic
+    /// Configuration descriptor for the Characteristic subscribed to.
     /// Updates sent from the peer wlil be delivered to the Stream returned.
     fn subscribe(&self, handle: &Handle) -> Self::NotificationStream;
 }
@@ -127,14 +149,7 @@
         uuid: Uuid,
     ) -> Result<Vec<ServiceCharacteristic<'a, PeerServiceT>>> {
         let chrs = service.discover_characteristics(Some(uuid)).await?;
-        Ok(chrs
-            .into_iter()
-            .map(|characteristic| Self {
-                service,
-                characteristic,
-                uuid,
-            })
-            .collect())
+        Ok(chrs.into_iter().map(|characteristic| Self { service, characteristic, uuid }).collect())
     }
 }
 
@@ -154,9 +169,6 @@
 
 impl<'a, PeerServiceT: PeerService> ServiceCharacteristic<'a, PeerServiceT> {
     pub async fn read(&self, buf: &mut [u8]) -> Result<usize> {
-        self.service
-            .read_characteristic(self.handle(), 0, buf)
-            .await
-            .map(|(bytes, _)| bytes)
+        self.service.read_characteristic(self.handle(), 0, buf).await.map(|(bytes, _)| bytes)
     }
 }
diff --git a/rust/bt-gatt/src/types/mod.rs b/rust/bt-gatt/src/types/mod.rs
index e6eda85..d07c186 100644
--- a/rust/bt-gatt/src/types/mod.rs
+++ b/rust/bt-gatt/src/types/mod.rs
@@ -5,8 +5,9 @@
 use bt_common::{PeerId, Uuid};
 use thiserror::Error;
 
-/// Errors that can be returned from GATT procedures. These errors are sent from the peer.
-/// These are defined to match the Bluetooth Core Spec (v5.4, Vol 3, Part F, Sec 3.4.1.1)
+/// Errors that can be returned from GATT procedures. These errors are sent from
+/// the peer. These are defined to match the Bluetooth Core Spec (v5.4, Vol 3,
+/// Part F, Sec 3.4.1.1)
 #[derive(Debug, Copy, Clone)]
 pub enum GattError {
     InvalidHandle = 1,
@@ -150,10 +151,10 @@
 pub type Result<T> = core::result::Result<T, Error>;
 
 /// Handles are used as opaque identifiers for Characteristics and Descriptors.
-/// Their value should be treated as opaque by clients of PeerService, and are explicitly not
-/// guaranteed to be equal to a peer's attribute handles.
-/// Stack implementations should provide unique handles for each Characteristic and Descriptor
-/// within a PeerService.
+/// Their value should be treated as opaque by clients of PeerService, and are
+/// explicitly not guaranteed to be equal to a peer's attribute handles.
+/// Stack implementations should provide unique handles for each Characteristic
+/// and Descriptor within a PeerService.
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 pub struct Handle(pub u64);
 
@@ -163,8 +164,11 @@
     WithoutResponse,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq)]
-#[repr(u16)]
+/// Characteristic Properties, including Extended Properties
+/// Determines how the Characteristic Value may be used.
+///
+/// Defined in Core Spec 5.3, Vol 3 Part G Sec 3.3.1.1 and 3.3.3.1
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum CharacteristicProperty {
     Broadcast = 0x01,
     Read = 0x02,
@@ -177,7 +181,7 @@
     WritableAuxiliaries = 0x200,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Default, Debug, Clone)]
 pub struct CharacteristicProperties(pub Vec<CharacteristicProperty>);
 
 #[derive(Debug, Clone, Copy, Default)]
@@ -200,25 +204,27 @@
 
 #[derive(Debug, Clone, Copy, Default)]
 pub struct AttributePermissions {
-    /// If None, then this cannot be read. Otherwise the SecurityLevels given are required to read.
+    /// If None, then this cannot be read. Otherwise the SecurityLevels given
+    /// are required to read.
     pub(crate) read: Option<SecurityLevels>,
-    /// If None, then this cannot be written. Otherwise the SecurityLevels given are required to
-    /// write.
+    /// If None, then this cannot be written. Otherwise the SecurityLevels given
+    /// are required to write.
     pub(crate) write: Option<SecurityLevels>,
-    /// If None, then this cannot be updated. Otherwise the SecurityLevels given are required to
-    /// update.
+    /// If None, then this cannot be updated. Otherwise the SecurityLevels given
+    /// are required to update.
     pub(crate) update: Option<SecurityLevels>,
 }
 
-/// The different types of well-known Descriptors are defined here and should be automatically read
-/// during service characteristic discovery by Stack Implementations and included.
-/// Other Descriptor attribute values can be read using [`PeerService::read_descriptor`].
+/// The different types of well-known Descriptors are defined here and should be
+/// automatically read during service characteristic discovery by Stack
+/// Implementations and included. Other Descriptor attribute values can be read
+/// using [`PeerService::read_descriptor`].
 ///
-/// Characteristic Extended Properties are included in the CharacteristicProperties of the
-/// Characteristic.
-/// This is a subset of the descriptors defined in the Core Specification (v5.4, Vol 3 Part G Sec
-/// 3.3.3).  Missing DescriptorTypes are handled internally and will be omitted from descriptor
-/// APIs.
+/// Characteristic Extended Properties are included in the
+/// CharacteristicProperties of the Characteristic.
+/// This is a subset of the descriptors defined in the Core Specification (v5.4,
+/// Vol 3 Part G Sec 3.3.3).  Missing DescriptorTypes are handled internally and
+/// will be omitted from descriptor APIs.
 #[derive(Clone, Debug)]
 pub enum DescriptorType {
     UserDescription(String),
@@ -240,14 +246,14 @@
 #[derive(Clone, Debug)]
 pub struct Descriptor {
     pub handle: Handle,
-    /// Permissions required needed to interact with this descriptor.  May not be accurate on
-    /// clients until the descriptor is interacted with.
+    /// Permissions required needed to interact with this descriptor.  May not
+    /// be accurate on clients until the descriptor is interacted with.
     pub permissions: AttributePermissions,
     pub r#type: DescriptorType,
 }
 
-/// A Characteristic on a Service. Each Characteristic has a declaration, value, and zero or more
-/// decriptors.
+/// A Characteristic on a Service. Each Characteristic has a declaration, value,
+/// and zero or more decriptors.
 #[derive(Clone, Debug)]
 pub struct Characteristic {
     pub handle: Handle,
diff --git a/rust/bt-pacs/.gitignore b/rust/bt-pacs/.gitignore
new file mode 100644
index 0000000..438e2b1
--- /dev/null
+++ b/rust/bt-pacs/.gitignore
@@ -0,0 +1,17 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# Vim swap files.
+*.swp
diff --git a/rust/bt-pacs/Cargo.toml b/rust/bt-pacs/Cargo.toml
new file mode 100644
index 0000000..9a471b2
--- /dev/null
+++ b/rust/bt-pacs/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "bt-pacs"
+description = "Client and server library for the Published Audio Capabilities Service"
+version = "0.0.1"
+edition = "2021"
+license = "BSD-2-Clause"
+
+[dependencies]
+
+### In-tree dependencies
+bt-gatt = { path = "../bt-gatt" }
+bt-common = { path = "../bt-common" }
+
+[dev-dependencies]
+pretty_assertions = "1"
diff --git a/rust/bt-pacs/LICENSE b/rust/bt-pacs/LICENSE
new file mode 100644
index 0000000..e5de3c8
--- /dev/null
+++ b/rust/bt-pacs/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2023 Google LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/rust/bt-pacs/PATENTS b/rust/bt-pacs/PATENTS
new file mode 100644
index 0000000..3a21f11
--- /dev/null
+++ b/rust/bt-pacs/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of this crate
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute
+or order or agree to the institution of patent litigation or any other
+patent enforcement activity against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that this
+implementation constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation
+shall terminate as of the date such litigation is filed.
diff --git a/rust/bt-pacs/src/lib.rs b/rust/bt-pacs/src/lib.rs
new file mode 100644
index 0000000..b371697
--- /dev/null
+++ b/rust/bt-pacs/src/lib.rs
@@ -0,0 +1,581 @@
+// Copyright 2023 Google LLC
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use bt_common::core::ltv::LtValue;
+use bt_common::generic_audio::codec_capabilities::CodecCapability;
+use bt_common::generic_audio::{AudioLocation, ContextType};
+use bt_common::packet_encoding::Decodable;
+use bt_common::Uuid;
+use bt_gatt::{client::FromCharacteristic, Characteristic};
+
+/// Codec_ID communicated by the Published Audio Capabilities Service
+/// From Section 3.1 of the Spec.
+/// Used in [`PacRecord`]
+#[derive(Debug, Clone, PartialEq)]
+pub enum CodecId {
+    /// From the Assigned Numbers. Format will not be
+    /// `CodingFormat::VendorSpecific`
+    Assigned(bt_common::core::CodingFormat),
+    VendorSpecific {
+        company_id: bt_common::CompanyId,
+        vendor_specific_codec_id: u16,
+    },
+}
+
+impl bt_common::packet_encoding::Decodable for CodecId {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < 5 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let format = buf[0].into();
+        if format != bt_common::core::CodingFormat::VendorSpecific {
+            // Maybe don't ignore the company and vendor id, and check if they are wrong.
+            return Ok((Self::Assigned(format), 5));
+        }
+        let company_id = u16::from_le_bytes([buf[1], buf[2]]).into();
+        let vendor_specific_codec_id = u16::from_le_bytes([buf[3], buf[4]]);
+        Ok((Self::VendorSpecific { company_id, vendor_specific_codec_id }, 5))
+    }
+}
+
+/// A Published Audio Capability (PAC) record.
+/// Published Audio Capabilities represent the capabilities of a given peer to
+/// transmit or receive Audio capabilities, exposed in PAC records, represent
+/// the server audio capabilities independent of available resources at any
+/// given time. Audio capabilities do not distinguish between unicast
+/// Audio Streams or broadcast Audio Streams.
+#[derive(Debug, Clone, PartialEq)]
+pub struct PacRecord {
+    pub codec_id: CodecId,
+    pub codec_specific_capabilities: Vec<CodecCapability>,
+    // TODO: Actually parse the metadata once Metadata
+    pub metadata: (),
+}
+
+impl bt_common::packet_encoding::Decodable for PacRecord {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        let mut idx = 0;
+        let (codec_id, consumed) = CodecId::decode(&buf[idx..])?;
+        idx += consumed;
+        let codec_specific_capabilites_length = buf[idx] as usize;
+        idx += 1;
+        let (results, consumed) =
+            CodecCapability::decode_all(&buf[idx..idx + codec_specific_capabilites_length]);
+        if consumed != codec_specific_capabilites_length {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let codec_specific_capabilities = results.into_iter().filter_map(Result::ok).collect();
+        idx += consumed;
+
+        let metadata_length = buf[idx];
+        idx += 1;
+        // TODO: Actually parse the Metadata when the type is included in bt_common
+        /*
+        let (results, consumed) = Metadatum::decode_all(&buf[idx..idx + metadata_length]);
+        if consumed != metadata_length {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let metadata = results.into_iter().filter(Result::ok).collect();
+        idx += consumed;
+        */
+        idx += metadata_length as usize;
+
+        Ok((Self { codec_id, codec_specific_capabilities, metadata: () }, idx))
+    }
+}
+
+/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
+/// a service. More than one Sink PAC can exist on a given PACS service.  If
+/// multiple are exposed, they are returned separately and can be notified by
+/// the server separately.
+#[derive(Debug, PartialEq, Clone)]
+pub struct SinkPac {
+    pub handle: bt_gatt::types::Handle,
+    pub capabilities: Vec<PacRecord>,
+}
+
+fn pac_records_from_bytes(
+    value: &[u8],
+) -> Result<Vec<PacRecord>, bt_common::packet_encoding::Error> {
+    if value.len() < 1 {
+        return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+    }
+    let num_of_pac_records = value[0] as usize;
+    let mut next_idx = 1;
+    let mut capabilities = Vec::with_capacity(num_of_pac_records);
+    for _ in 0..num_of_pac_records {
+        let (cap, consumed) = PacRecord::decode(&value[next_idx..])?;
+        capabilities.push(cap);
+        next_idx += consumed;
+    }
+    Ok(capabilities)
+}
+
+impl FromCharacteristic for SinkPac {
+    const UUID: Uuid = Uuid::from_u16(0x2BC9);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        let capabilities = pac_records_from_bytes(value)?;
+        Ok(Self { handle, capabilities })
+    }
+
+    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
+        self.capabilities = pac_records_from_bytes(new_value)?;
+        Ok(self)
+    }
+}
+
+/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
+/// a service. More than one Sink PAC can exist on a given PACS service.  If
+/// multiple are exposed, they are returned separately and can be notified by
+/// the server separately.
+#[derive(Debug, PartialEq, Clone)]
+pub struct SourcePac {
+    pub handle: bt_gatt::types::Handle,
+    pub capabilities: Vec<PacRecord>,
+}
+
+impl FromCharacteristic for SourcePac {
+    const UUID: Uuid = Uuid::from_u16(0x2BCB);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        let capabilities = pac_records_from_bytes(value)?;
+        Ok(Self { handle, capabilities })
+    }
+
+    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
+        self.capabilities = pac_records_from_bytes(new_value)?;
+        Ok(self)
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct AudioLocations {
+    pub locations: std::collections::HashSet<AudioLocation>,
+}
+
+impl bt_common::packet_encoding::Decodable for AudioLocations {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() != 4 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let locations =
+            AudioLocation::from_bits(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
+                .collect();
+        Ok((AudioLocations { locations }, 4))
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct SourceAudioLocations {
+    pub handle: bt_gatt::types::Handle,
+    pub locations: AudioLocations,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct SinkAudioLocations {
+    pub handle: bt_gatt::types::Handle,
+    pub locations: AudioLocations,
+}
+
+impl FromCharacteristic for SourceAudioLocations {
+    const UUID: Uuid = Uuid::from_u16(0x2BCC);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        let (locations, _) = AudioLocations::decode(value)?;
+        Ok(Self { handle, locations })
+    }
+
+    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
+        self.locations = AudioLocations::decode(new_value)?.0;
+        Ok(self)
+    }
+}
+
+impl FromCharacteristic for SinkAudioLocations {
+    const UUID: Uuid = Uuid::from_u16(0x2BCA);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        let (locations, _) = AudioLocations::decode(value)?;
+        Ok(Self { handle, locations })
+    }
+
+    fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
+        self.locations = AudioLocations::decode(new_value)?.0;
+        Ok(self)
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum AvailableContexts {
+    NotAvailable,
+    Available(std::collections::HashSet<ContextType>),
+}
+
+impl bt_common::packet_encoding::Decodable for AvailableContexts {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+        if buf.len() < 2 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let encoded = u16::from_le_bytes([buf[0], buf[1]]);
+        if encoded == 0 {
+            Ok((Self::NotAvailable, 2))
+        } else {
+            Ok((Self::Available(ContextType::from_bits(encoded).collect()), 2))
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct AvailableAudioContexts {
+    pub handle: bt_gatt::types::Handle,
+    pub sink: AvailableContexts,
+    pub source: AvailableContexts,
+}
+
+impl FromCharacteristic for AvailableAudioContexts {
+    const UUID: Uuid = Uuid::from_u16(0x28CD);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        if value.len() < 4 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let sink = AvailableContexts::decode(&value[0..2])?.0;
+        let source = AvailableContexts::decode(&value[2..4])?.0;
+        Ok(Self { handle, sink, source })
+    }
+
+    fn update(
+        &mut self,
+        new_value: &[u8],
+    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
+        if new_value.len() != 4 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let sink = AvailableContexts::decode(&new_value[0..2])?.0;
+        let source = AvailableContexts::decode(&new_value[2..4])?.0;
+        self.sink = sink;
+        self.source = source;
+        Ok(self)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct SupportedAudioContexts {
+    pub handle: bt_gatt::types::Handle,
+    pub sink: std::collections::HashSet<ContextType>,
+    pub source: std::collections::HashSet<ContextType>,
+}
+
+impl FromCharacteristic for SupportedAudioContexts {
+    const UUID: Uuid = Uuid::from_u16(0x28CE);
+
+    fn from_chr(
+        characteristic: Characteristic,
+        value: &[u8],
+    ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
+        let handle = characteristic.handle;
+        if value.len() < 4 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        let sink = ContextType::from_bits(u16::from_le_bytes([value[0], value[1]])).collect();
+        let source = ContextType::from_bits(u16::from_le_bytes([value[2], value[3]])).collect();
+        Ok(Self { handle, sink, source })
+    }
+
+    fn update(
+        &mut self,
+        new_value: &[u8],
+    ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
+        if new_value.len() < 4 {
+            return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+        }
+        self.sink =
+            ContextType::from_bits(u16::from_le_bytes([new_value[0], new_value[1]])).collect();
+        self.source =
+            ContextType::from_bits(u16::from_le_bytes([new_value[2], new_value[3]])).collect();
+        Ok(self)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use bt_common::{
+        generic_audio::codec_capabilities::{CodecCapabilityType, SamplingFrequency},
+        Uuid,
+    };
+    use bt_gatt::{
+        types::{AttributePermissions, Handle},
+        Characteristic,
+    };
+
+    use pretty_assertions::assert_eq;
+
+    const SINGLE_PAC_SIMPLE: [u8; 12] = [
+        0x01, // Num of records; 1
+        0x06, // CodecID: codec LC3,
+        0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
+        0x04, // Len of Codec capabilities
+        0x03, 0x01, // lt: supported_sampling_frequencies
+        0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
+        0x00, // Len of metadata
+    ];
+
+    const MULTIPLE_PAC_COMPLEX: [u8; 35] = [
+        0x02, // Num of records; 2
+        0x05, // CodecID: codec MSBC,
+        0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
+        0x0A, // Len of Codec capabilities (10)
+        0x03, 0x01, // lt: supported_sampling_frequencies
+        0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
+        0x05, 0x04, // lt: Octets per codec frame
+        0x11, 0x00, // Minimum: 9
+        0x00, 0x10, // Maximum: 4096
+        0x0A, // Len of metadata: 10
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+        0x0A, // Metadata (not parsed at this time)
+        0xFF, // CodecId: Vendor specific
+        0xE0, 0x00, // Google
+        0x01, 0x10, // ID 4097
+        0x00, // Len of codec capabilities (none)
+        0x00, // Len of metadata (none)
+    ];
+
+    #[track_caller]
+    fn assert_has_frequencies(
+        cap: &bt_common::generic_audio::codec_capabilities::CodecCapability,
+        freqs: &[SamplingFrequency],
+    ) {
+        let CodecCapability::SupportedSamplingFrequencies(set) = cap else {
+            unreachable!();
+        };
+
+        for freq in freqs {
+            assert!(set.contains(freq));
+        }
+    }
+
+    #[test]
+    fn simple_sink_pac() {
+        let pac = SinkPac::from_chr(
+            Characteristic {
+                handle: Handle(1),
+                uuid: Uuid::from_u16(0x2BC9),
+                properties: bt_gatt::types::CharacteristicProperties(vec![
+                    bt_gatt::types::CharacteristicProperty::Read,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            &SINGLE_PAC_SIMPLE,
+        )
+        .expect("should decode correctly");
+
+        assert_eq!(pac.capabilities.len(), 1);
+        let cap = &pac.capabilities[0];
+        assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
+        let freq_cap = cap
+            .codec_specific_capabilities
+            .iter()
+            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
+            .unwrap();
+        assert_has_frequencies(
+            freq_cap,
+            &[
+                SamplingFrequency::F44100Hz,
+                SamplingFrequency::F48000Hz,
+                SamplingFrequency::F96000Hz,
+            ],
+        );
+    }
+
+    #[test]
+    fn simple_source_pac() {
+        let pac = SinkPac::from_chr(
+            Characteristic {
+                handle: Handle(1),
+                uuid: Uuid::from_u16(0x2BC9),
+                properties: bt_gatt::types::CharacteristicProperties(vec![
+                    bt_gatt::types::CharacteristicProperty::Read,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            &SINGLE_PAC_SIMPLE,
+        )
+        .expect("should decode correctly");
+
+        assert_eq!(pac.capabilities.len(), 1);
+        let cap = &pac.capabilities[0];
+        assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
+        let freq_cap = cap
+            .codec_specific_capabilities
+            .iter()
+            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
+            .unwrap();
+        assert_has_frequencies(
+            freq_cap,
+            &[
+                SamplingFrequency::F44100Hz,
+                SamplingFrequency::F48000Hz,
+                SamplingFrequency::F96000Hz,
+            ],
+        );
+    }
+
+    #[test]
+    fn complex_sink_pac() {
+        let pac = SinkPac::from_chr(
+            Characteristic {
+                handle: Handle(1),
+                uuid: Uuid::from_u16(0x2BC9),
+                properties: bt_gatt::types::CharacteristicProperties(vec![
+                    bt_gatt::types::CharacteristicProperty::Read,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            &MULTIPLE_PAC_COMPLEX,
+        )
+        .expect("should decode correctly");
+        assert_eq!(pac.capabilities.len(), 2);
+        let first = &pac.capabilities[0];
+        assert_eq!(first.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Msbc));
+        let freq_cap = first
+            .codec_specific_capabilities
+            .iter()
+            .find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
+            .unwrap();
+        assert_has_frequencies(
+            freq_cap,
+            &[
+                SamplingFrequency::F44100Hz,
+                SamplingFrequency::F48000Hz,
+                SamplingFrequency::F96000Hz,
+            ],
+        );
+
+        let second = &pac.capabilities[1];
+        assert_eq!(
+            second.codec_id,
+            CodecId::VendorSpecific {
+                company_id: 0x00E0.into(),
+                vendor_specific_codec_id: 0x1001_u16,
+            }
+        );
+        assert_eq!(second.codec_specific_capabilities.len(), 0);
+    }
+
+    #[test]
+    fn available_contexts_no_sink() {
+        let available = AvailableAudioContexts::from_chr(
+            Characteristic {
+                handle: Handle(1),
+                uuid: Uuid::from_u16(0x28CD),
+                properties: bt_gatt::types::CharacteristicProperties(vec![
+                    bt_gatt::types::CharacteristicProperty::Read,
+                ]),
+                permissions: AttributePermissions::default(),
+                descriptors: vec![],
+            },
+            &[0x00, 0x00, 0x02, 0x04],
+        )
+        .expect("should decode correctly");
+        assert_eq!(available.handle, Handle(1));
+        assert_eq!(available.sink, AvailableContexts::NotAvailable);
+        let AvailableContexts::Available(a) = available.source else {
+            panic!("Source should be available");
+        };
+        assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
+    }
+
+    #[test]
+    fn available_contexts_wrong_size() {
+        let chr = Characteristic {
+            handle: Handle(1),
+            uuid: Uuid::from_u16(0x28CD),
+            properties: bt_gatt::types::CharacteristicProperties(vec![
+                bt_gatt::types::CharacteristicProperty::Read,
+            ]),
+            permissions: AttributePermissions::default(),
+            descriptors: vec![],
+        };
+        let _ = AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02])
+            .expect_err("should not decode with too short");
+
+        let available =
+            AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02, 0x04, 0xCA, 0xFE])
+                .expect("should attempt to decode with too long");
+
+        assert_eq!(available.sink, AvailableContexts::NotAvailable);
+        let AvailableContexts::Available(a) = available.source else {
+            panic!("Source should be available");
+        };
+        assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
+    }
+
+    #[test]
+    fn supported_contexts() {
+        let chr = Characteristic {
+            handle: Handle(1),
+            uuid: Uuid::from_u16(0x28CE),
+            properties: bt_gatt::types::CharacteristicProperties(vec![
+                bt_gatt::types::CharacteristicProperty::Read,
+            ]),
+            permissions: AttributePermissions::default(),
+            descriptors: vec![],
+        };
+
+        let supported =
+            SupportedAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x00, 0x00]).unwrap();
+        assert_eq!(supported.sink.len(), 0);
+        assert_eq!(supported.source.len(), 0);
+
+        let supported =
+            SupportedAudioContexts::from_chr(chr.clone(), &[0x08, 0x06, 0x06, 0x03]).unwrap();
+        assert_eq!(supported.sink.len(), 3);
+        assert_eq!(supported.source.len(), 4);
+        assert_eq!(
+            supported.source,
+            [
+                ContextType::Media,
+                ContextType::Conversational,
+                ContextType::Ringtone,
+                ContextType::Notifications
+            ]
+            .into_iter()
+            .collect()
+        );
+    }
+}