rust/bt-pacs: Initial server implementation

Published Audio Service server implementation that can be initialized
with all the mandatory characteristics with mandatory properties. Read
and write functionalities are implemented.

Change-Id: I4d10298dd505f0dd543802a6b414f819229134e5
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/2000
Reviewed-by: Marie Janssen <jamuraa@google.com>
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 4ce19aa..650f02c 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -31,3 +31,4 @@
 pretty_assertions = "1.2.1"
 thiserror = "2.0.12"
 uuid = { version = "1.1.2", features = ["serde", "v4"] }
+pin-project = "1.0.11"
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs
index d2e2ce5..f7d94e5 100644
--- a/rust/bt-common/src/core.rs
+++ b/rust/bt-common/src/core.rs
@@ -177,6 +177,32 @@
     }
 }
 
+impl Encodable for CodecId {
+    type Error = PacketError;
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        Self::BYTE_SIZE
+    }
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < Self::BYTE_SIZE {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        match self {
+            CodecId::Assigned(format) => {
+                buf[0] = (*format).into();
+                buf[1..5].fill(0);
+            }
+            CodecId::VendorSpecific { company_id, vendor_specific_codec_id } => {
+                buf[0] = 0xFF;
+                [buf[1], buf[2]] = u16::from(*company_id).to_le_bytes();
+                [buf[3], buf[4]] = vendor_specific_codec_id.to_le_bytes();
+            }
+        }
+        Ok(())
+    }
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Phy {
     /// LE 1M PHY
@@ -226,4 +252,21 @@
             }
         );
     }
+
+    #[test]
+    fn encode_codec_id() {
+        let assigned = [0x01, 0x00, 0x00, 0x00, 0x00];
+        let (codec_id, _) = CodecId::decode(&assigned[..]).expect("should succeed");
+        assert_eq!(codec_id, CodecId::Assigned(CodingFormat::ALawLog));
+
+        let vendor_specific = [0xFF, 0x36, 0xFD, 0x11, 0x22];
+        let (codec_id, _) = CodecId::decode(&vendor_specific[..]).expect("should succeed");
+        assert_eq!(
+            codec_id,
+            CodecId::VendorSpecific {
+                company_id: (0xFD36 as u16).into(),
+                vendor_specific_codec_id: 0x2211
+            }
+        );
+    }
 }
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs
index c72b395..873689f 100644
--- a/rust/bt-gatt/src/test_utils.rs
+++ b/rust/bt-gatt/src/test_utils.rs
@@ -403,7 +403,7 @@
     sender: UnboundedSender<FakeServerEvent>,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct FakeServer {
     inner: Arc<Mutex<FakeServerInner>>,
 }
diff --git a/rust/bt-gatt/src/types.rs b/rust/bt-gatt/src/types.rs
index 5644c31..aefbb78 100644
--- a/rust/bt-gatt/src/types.rs
+++ b/rust/bt-gatt/src/types.rs
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::collections::HashSet;
+
 use bt_common::{PeerId, Uuid};
 use thiserror::Error;
 
@@ -260,6 +262,18 @@
     }
 }
 
+impl CharacteristicProperties {
+    pub fn is_disjoint(&self, other: &Self) -> bool {
+        let this_properties: HashSet<_> = HashSet::from_iter(self.0.iter());
+        let other_properties: HashSet<_> = HashSet::from_iter(other.0.iter());
+        this_properties.is_disjoint(&other_properties)
+    }
+
+    pub fn contains(&self, property: CharacteristicProperty) -> bool {
+        self.0.contains(&property)
+    }
+}
+
 #[derive(Debug, Clone, Copy, Default)]
 pub struct SecurityLevels {
     /// Encryption is required / provided
@@ -276,6 +290,10 @@
             && (!self.authentication || provided.authentication)
             && (!self.authorization || provided.encryption)
     }
+
+    pub const fn encryption_required() -> Self {
+        Self { encryption: true, authentication: false, authorization: false }
+    }
 }
 
 #[derive(Debug, Clone, Copy, Default)]
@@ -291,6 +309,17 @@
     pub update: Option<SecurityLevels>,
 }
 
+impl AttributePermissions {
+    pub fn with_levels(properties: &CharacteristicProperties, levels: &SecurityLevels) -> Self {
+        let notify_properties = CharacteristicProperty::Notify | CharacteristicProperty::Indicate;
+        Self {
+            read: properties.contains(CharacteristicProperty::Read).then_some(levels.clone()),
+            write: properties.contains(CharacteristicProperty::Write).then_some(levels.clone()),
+            update: (!properties.is_disjoint(&notify_properties)).then_some(levels.clone()),
+        }
+    }
+}
+
 /// 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
diff --git a/rust/bt-pacs/Cargo.toml b/rust/bt-pacs/Cargo.toml
index 52d4737..dd975d8 100644
--- a/rust/bt-pacs/Cargo.toml
+++ b/rust/bt-pacs/Cargo.toml
@@ -12,6 +12,9 @@
 
 ### Others
 futures.workspace = true
+pin-project.workspace = true
+thiserror.workspace = true
 
 [dev-dependencies]
+bt-gatt = { workspace = true, features = ["test-utils"] }
 pretty_assertions.workspace = true
diff --git a/rust/bt-pacs/src/lib.rs b/rust/bt-pacs/src/lib.rs
index e926716..b77a577 100644
--- a/rust/bt-pacs/src/lib.rs
+++ b/rust/bt-pacs/src/lib.rs
@@ -7,11 +7,14 @@
 use bt_common::generic_audio::codec_capabilities::CodecCapability;
 use bt_common::generic_audio::metadata_ltv::Metadata;
 use bt_common::generic_audio::{AudioLocation, ContextType};
-use bt_common::packet_encoding::Decodable;
+use bt_common::packet_encoding::{Decodable, Encodable};
 use bt_common::Uuid;
 use bt_gatt::{client::FromCharacteristic, Characteristic};
 
+use std::collections::HashSet;
+
 pub mod debug;
+pub mod server;
 
 /// UUID from Assigned Numbers section 3.4.
 pub const PACS_UUID: Uuid = Uuid::from_u16(0x1850);
@@ -26,11 +29,10 @@
 pub struct PacRecord {
     pub codec_id: CodecId,
     pub codec_specific_capabilities: Vec<CodecCapability>,
-    // TODO: Actually parse the metadata once Metadata
     pub metadata: Vec<Metadata>,
 }
 
-impl bt_common::packet_encoding::Decodable for PacRecord {
+impl Decodable for PacRecord {
     type Error = bt_common::packet_encoding::Error;
 
     fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
@@ -66,14 +68,48 @@
     }
 }
 
-/// 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>,
+impl Encodable for PacRecord {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        5usize
+            + self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len())
+            + self.metadata.iter().fold(0, |a, x| a + x.encoded_len())
+            + 2
+    }
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < self.encoded_len() {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        self.codec_id.encode(&mut buf[0..]).unwrap();
+        let codec_capabilities_len =
+            self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len());
+        buf[5] = codec_capabilities_len as u8;
+        LtValue::encode_all(self.codec_specific_capabilities.clone().into_iter(), &mut buf[6..])?;
+        let metadata_len = self.metadata.iter().fold(0, |a, x| a + x.encoded_len());
+        buf[6 + codec_capabilities_len] = metadata_len as u8;
+        if metadata_len != 0 {
+            LtValue::encode_all(
+                self.metadata.clone().into_iter(),
+                &mut buf[6 + codec_capabilities_len + 1..],
+            )?;
+        }
+        Ok(())
+    }
+}
+
+fn pac_records_into_char_value(records: &Vec<PacRecord>) -> Vec<u8> {
+    let mut val = Vec::new();
+    val.push(records.len() as u8);
+    let mut idx = 1;
+    for record in records {
+        let record_len = record.encoded_len();
+        val.resize(val.len() + record_len, 0);
+        record.encode(&mut val[idx..]).unwrap();
+        idx += record_len;
+    }
+    val
 }
 
 fn pac_records_from_bytes(
@@ -93,6 +129,16 @@
     Ok(capabilities)
 }
 
+/// 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>,
+}
+
 impl FromCharacteristic for SinkPac {
     /// UUID from Assigned Numbers section 3.8.
     const UUID: Uuid = Uuid::from_u16(0x2BC9);
@@ -141,12 +187,12 @@
     }
 }
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Clone, Default)]
 pub struct AudioLocations {
-    pub locations: std::collections::HashSet<AudioLocation>,
+    pub locations: HashSet<AudioLocation>,
 }
 
-impl bt_common::packet_encoding::Decodable for AudioLocations {
+impl Decodable for AudioLocations {
     type Error = bt_common::packet_encoding::Error;
 
     fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
@@ -160,16 +206,36 @@
     }
 }
 
+impl Encodable for AudioLocations {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        4
+    }
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < 4 {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        [buf[0], buf[1], buf[2], buf[3]] =
+            AudioLocation::to_bits(self.locations.iter()).to_le_bytes();
+        Ok(())
+    }
+}
+
 #[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 SourceAudioLocations {
+    fn into_char_value(&self) -> Vec<u8> {
+        let mut val = Vec::with_capacity(self.locations.encoded_len());
+        val.resize(self.locations.encoded_len(), 0);
+        self.locations.encode(&mut val[..]).unwrap();
+        val
+    }
 }
 
 impl FromCharacteristic for SourceAudioLocations {
@@ -191,6 +257,21 @@
     }
 }
 
+#[derive(Debug, PartialEq, Clone)]
+pub struct SinkAudioLocations {
+    pub handle: bt_gatt::types::Handle,
+    pub locations: AudioLocations,
+}
+
+impl SinkAudioLocations {
+    fn into_char_value(&self) -> Vec<u8> {
+        let mut val = Vec::with_capacity(self.locations.encoded_len());
+        val.resize(self.locations.encoded_len(), 0);
+        self.locations.encode(&mut val[..]).unwrap();
+        val
+    }
+}
+
 impl FromCharacteristic for SinkAudioLocations {
     const UUID: Uuid = Uuid::from_u16(0x2BCA);
 
@@ -212,10 +293,10 @@
 #[derive(Debug, PartialEq, Clone)]
 pub enum AvailableContexts {
     NotAvailable,
-    Available(std::collections::HashSet<ContextType>),
+    Available(HashSet<ContextType>),
 }
 
-impl bt_common::packet_encoding::Decodable for AvailableContexts {
+impl Decodable for AvailableContexts {
     type Error = bt_common::packet_encoding::Error;
 
     fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
@@ -231,6 +312,36 @@
     }
 }
 
+impl Encodable for AvailableContexts {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        2
+    }
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < 2 {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        match self {
+            AvailableContexts::NotAvailable => [buf[0], buf[1]] = [0x00, 0x00],
+            AvailableContexts::Available(set) => {
+                [buf[0], buf[1]] = ContextType::to_bits(set.iter()).to_le_bytes()
+            }
+        }
+        Ok(())
+    }
+}
+
+impl From<&HashSet<ContextType>> for AvailableContexts {
+    fn from(value: &HashSet<ContextType>) -> Self {
+        if value.is_empty() {
+            return AvailableContexts::NotAvailable;
+        }
+        AvailableContexts::Available(value.clone())
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct AvailableAudioContexts {
     pub handle: bt_gatt::types::Handle,
@@ -238,6 +349,16 @@
     pub source: AvailableContexts,
 }
 
+impl AvailableAudioContexts {
+    fn into_char_value(&self) -> Vec<u8> {
+        let mut val = Vec::with_capacity(self.sink.encoded_len() + self.source.encoded_len());
+        val.resize(self.sink.encoded_len() + self.source.encoded_len(), 0);
+        self.sink.encode(&mut val[0..]).unwrap();
+        self.source.encode(&mut val[2..]).unwrap();
+        val
+    }
+}
+
 impl FromCharacteristic for AvailableAudioContexts {
     /// UUID from Assigned Numbers section 3.8.
     const UUID: Uuid = Uuid::from_u16(0x2BCD);
@@ -273,8 +394,34 @@
 #[derive(Debug, Clone)]
 pub struct SupportedAudioContexts {
     pub handle: bt_gatt::types::Handle,
-    pub sink: std::collections::HashSet<ContextType>,
-    pub source: std::collections::HashSet<ContextType>,
+    pub sink: HashSet<ContextType>,
+    pub source: HashSet<ContextType>,
+}
+
+impl SupportedAudioContexts {
+    fn into_char_value(&self) -> Vec<u8> {
+        let mut val = Vec::with_capacity(self.encoded_len());
+        val.resize(self.encoded_len(), 0);
+        self.encode(&mut val[0..]).unwrap();
+        val
+    }
+}
+
+impl Encodable for SupportedAudioContexts {
+    type Error = bt_common::packet_encoding::Error;
+
+    fn encoded_len(&self) -> core::primitive::usize {
+        4
+    }
+
+    fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
+        if buf.len() < 4 {
+            return Err(Self::Error::BufferTooSmall);
+        }
+        [buf[0], buf[1]] = ContextType::to_bits(self.sink.iter()).to_le_bytes();
+        [buf[2], buf[3]] = ContextType::to_bits(self.source.iter()).to_le_bytes();
+        Ok(())
+    }
 }
 
 impl FromCharacteristic for SupportedAudioContexts {
diff --git a/rust/bt-pacs/src/server.rs b/rust/bt-pacs/src/server.rs
new file mode 100644
index 0000000..5531187
--- /dev/null
+++ b/rust/bt-pacs/src/server.rs
@@ -0,0 +1,640 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements the Published Audio Capabilities Service server role.
+//!
+//! Use the `ServerBuilder` to define a new `Server` instance with the specified
+//! characteristics. The server isn't published to GATT until
+//! `Server::publish` method is called.
+//! Once the `Server` is published, poll on it to receive events from the
+//! `Server`, which are created as it processes incoming client requests.
+//!
+//! For example:
+//!
+//! // Set up a GATT Server which implements `bt_gatt::ServerTypes::Server`.
+//! let gatt_server = ...;
+//! // Define supported and available audio contexts for this PACS.
+//! let supported = AudioContexts::new(...);
+//! let available = AudioContexts::new(...);
+//! let pacs_server = ServerBuilder::new(supported,
+//! available).with_sources(...).with_sinks(...).build()?;
+//!
+//! // Publish the server.
+//! pacs_server.publish(gatt_server).expect("publishes fine");
+//! // Process events from the PACS server.
+//! while let Some(event) = pacs_server.next().await {
+//!     // Do something with `event`
+//! }
+
+use bt_common::generic_audio::ContextType;
+use bt_gatt::server::LocalService;
+use bt_gatt::server::{ReadResponder, ServiceDefinition, WriteResponder};
+use bt_gatt::types::{GattError, Handle};
+use bt_gatt::Server as _;
+use futures::task::{Poll, Waker};
+use futures::{Future, Stream};
+use pin_project::pin_project;
+use std::collections::HashMap;
+use thiserror::Error;
+
+use crate::{
+    AudioLocations, AvailableAudioContexts, PacRecord, SinkAudioLocations, SourceAudioLocations,
+    SupportedAudioContexts,
+};
+
+mod types;
+use crate::server::types::*;
+
+#[pin_project(project = LocalServiceProj)]
+enum LocalServiceState<T: bt_gatt::ServerTypes> {
+    NotPublished {
+        waker: Option<Waker>,
+    },
+    Preparing {
+        #[pin]
+        fut: T::LocalServiceFut,
+    },
+    Published {
+        service: T::LocalService,
+        #[pin]
+        events: T::ServiceEventStream,
+    },
+    Terminated,
+}
+
+impl<T: bt_gatt::ServerTypes> Default for LocalServiceState<T> {
+    fn default() -> Self {
+        Self::NotPublished { waker: None }
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("Service is already published")]
+    AlreadyPublished,
+    #[error("Issue publishing service: {0}")]
+    PublishError(#[from] bt_gatt::types::Error),
+    #[error("Service should support at least one of Sink or Source PAC characteristics")]
+    MissingPac,
+    #[error("Available audio contexts are not supported: {0:?}")]
+    UnsupportedAudioContexts(Vec<ContextType>),
+}
+
+impl<T: bt_gatt::ServerTypes> Stream for LocalServiceState<T> {
+    type Item = Result<bt_gatt::server::ServiceEvent<T>, Error>;
+
+    fn poll_next(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> Poll<Option<Self::Item>> {
+        // SAFETY:
+        //  - Wakers are Unpin
+        //  - We re-pin the structurally pinned futures in Preparing and Published
+        //    (service is untouched)
+        //  - Terminated is empty
+        loop {
+            match self.as_mut().project() {
+                LocalServiceProj::Terminated => return Poll::Ready(None),
+                LocalServiceProj::NotPublished { .. } => {
+                    self.as_mut()
+                        .set(LocalServiceState::NotPublished { waker: Some(cx.waker().clone()) });
+                    return Poll::Pending;
+                }
+                LocalServiceProj::Preparing { fut } => {
+                    let service_result = futures::ready!(fut.poll(cx));
+                    let Ok(service) = service_result else {
+                        return Poll::Ready(Some(Err(Error::PublishError(
+                            service_result.err().unwrap(),
+                        ))));
+                    };
+                    let events = service.publish();
+                    self.as_mut().set(LocalServiceState::Published { service, events });
+                    continue;
+                }
+                LocalServiceProj::Published { service: _, events } => {
+                    let item = futures::ready!(events.poll_next(cx));
+                    let Some(gatt_result) = item else {
+                        self.as_mut().set(LocalServiceState::Terminated);
+                        return Poll::Ready(None);
+                    };
+                    let Ok(event) = gatt_result else {
+                        self.as_mut().set(LocalServiceState::Terminated);
+                        return Poll::Ready(Some(Err(Error::PublishError(
+                            gatt_result.err().unwrap(),
+                        ))));
+                    };
+                    return Poll::Ready(Some(Ok(event)));
+                }
+            }
+        }
+    }
+}
+
+impl<T: bt_gatt::ServerTypes> LocalServiceState<T> {
+    fn is_published(&self) -> bool {
+        if let LocalServiceState::NotPublished { .. } = self { false } else { true }
+    }
+}
+
+#[derive(Default)]
+pub struct ServerBuilder {
+    source_pacs: Vec<Vec<PacRecord>>,
+    source_audio_locations: Option<AudioLocations>,
+    sink_pacs: Vec<Vec<PacRecord>>,
+    sink_audio_locations: Option<AudioLocations>,
+}
+
+impl ServerBuilder {
+    pub fn new() -> ServerBuilder {
+        ServerBuilder::default()
+    }
+
+    /// Adds a source PAC characteristic to the builder.
+    /// Each call adds a new characteristic.
+    /// `capabilities` represents the records for a single PAC characteristic.
+    /// If `capabilities` is empty, it will be ignored.
+    pub fn add_source(mut self, capabilities: Vec<PacRecord>) -> Self {
+        if !capabilities.is_empty() {
+            self.source_pacs.push(capabilities);
+        }
+        self
+    }
+
+    /// Sets the audio locations for the source.
+    /// This corresponds to a single Source Audio Locations characteristic.
+    pub fn set_source_locations(mut self, audio_locations: AudioLocations) -> Self {
+        self.source_audio_locations = Some(audio_locations);
+        self
+    }
+
+    /// Adds a sink PAC characteristic to the builder.
+    /// Each call adds a new characteristic.
+    /// `capabilities` represents the records for a single PAC characteristic.
+    /// If `capabilities` is empty, it will be ignored.
+    pub fn add_sink(mut self, capabilities: Vec<PacRecord>) -> Self {
+        if !capabilities.is_empty() {
+            self.sink_pacs.push(capabilities);
+        }
+        self
+    }
+
+    /// Sets the audio locations for the sink.
+    /// This corresponds to a single Sink Audio Locations characteristic.
+    pub fn set_sink_locations(mut self, audio_locations: AudioLocations) -> Self {
+        self.sink_audio_locations = Some(audio_locations);
+        self
+    }
+
+    fn verify_characteristics(
+        &self,
+        supported: &AudioContexts,
+        available: &AudioContexts,
+    ) -> Result<(), Error> {
+        // If the corresponding bit in the supported audio contexts is
+        // not set to 0b1, we shall not set a bit to 0b1 in the
+        // available audio contexts. See PACS v1.0.1 section 3.5.1.
+        let diff: Vec<ContextType> = available.sink.difference(&supported.sink).cloned().collect();
+        if diff.len() != 0 {
+            return Err(Error::UnsupportedAudioContexts(diff));
+        }
+        let diff: Vec<ContextType> =
+            available.source.difference(&supported.source).cloned().collect();
+        if diff.len() != 0 {
+            return Err(Error::UnsupportedAudioContexts(diff));
+        }
+
+        // PACS server must have at least one Sink or Source PACS record.
+        if self.source_pacs.len() == 0 && self.sink_pacs.len() == 0 {
+            return Err(Error::MissingPac);
+        }
+        Ok(())
+    }
+
+    /// Builds a server after verifying all the defined characteristics
+    /// for this server (see PACS v1.0.1 section 3 for details).
+    pub fn build<T>(
+        mut self,
+        mut supported: AudioContexts,
+        available: AudioContexts,
+    ) -> Result<Server<T>, Error>
+    where
+        T: bt_gatt::ServerTypes,
+    {
+        let _ = self.verify_characteristics(&supported, &available)?;
+
+        let mut service_def = ServiceDefinition::new(
+            bt_gatt::server::ServiceId::new(1),
+            crate::PACS_UUID,
+            bt_gatt::types::ServiceKind::Primary,
+        );
+
+        let supported = SupportedAudioContexts {
+            handle: SUPPORTED_AUDIO_CONTEXTS_HANDLE,
+            sink: supported.sink.drain().collect(),
+            source: supported.source.drain().collect(),
+        };
+        let _ = service_def.add_characteristic((&supported).into());
+
+        let available = AvailableAudioContexts {
+            handle: AVAILABLE_AUDIO_CONTEXTS_HANDLE,
+            sink: (&available.sink).into(),
+            source: (&available.source).into(),
+        };
+        let _ = service_def.add_characteristic((&available).into());
+
+        let mut next_handle_iter = (HANDLE_OFFSET..).map(|x| Handle(x));
+        let mut audio_capabilities = HashMap::new();
+
+        // Sink audio locations characteristic may exist iff it's defined
+        // and there are valid sink PAC characteristics.
+        let sink_audio_locations = match self.sink_audio_locations.take() {
+            Some(locations) if self.sink_pacs.len() > 0 => {
+                let sink =
+                    SinkAudioLocations { handle: next_handle_iter.next().unwrap(), locations };
+                let _ = service_def.add_characteristic((&sink).into());
+                Some(sink)
+            }
+            _ => None,
+        };
+        for capabilities in self.sink_pacs.drain(..) {
+            let handle = next_handle_iter.next().unwrap();
+            let pac = PublishedAudioCapability::new_sink(handle, capabilities);
+            let _ = service_def.add_characteristic((&pac).into());
+            audio_capabilities.insert(handle, pac);
+        }
+
+        // Source audio locations characteristic may exist iff it's defined
+        // and there are valid source PAC characteristics.
+        let source_audio_locations = match self.source_audio_locations.take() {
+            Some(locations) if self.source_pacs.len() > 0 => {
+                let source =
+                    SourceAudioLocations { handle: next_handle_iter.next().unwrap(), locations };
+                let _ = service_def.add_characteristic((&source).into());
+                Some(source)
+            }
+            _ => None,
+        };
+        for capabilities in self.source_pacs.drain(..) {
+            let handle = next_handle_iter.next().unwrap();
+            let pac = PublishedAudioCapability::new_source(handle, capabilities);
+            let _ = service_def.add_characteristic((&pac).into());
+            audio_capabilities.insert(handle, pac);
+        }
+
+        let server = Server {
+            service_def,
+            local_service: Default::default(),
+            published_audio_capabilities: audio_capabilities,
+            source_audio_locations,
+            sink_audio_locations,
+            available_audio_contexts: available,
+            supported_audio_contexts: supported,
+        };
+        Ok(server)
+    }
+}
+
+#[pin_project]
+pub struct Server<T: bt_gatt::ServerTypes> {
+    service_def: ServiceDefinition,
+    #[pin]
+    local_service: LocalServiceState<T>,
+    published_audio_capabilities: HashMap<Handle, PublishedAudioCapability>,
+    source_audio_locations: Option<SourceAudioLocations>,
+    sink_audio_locations: Option<SinkAudioLocations>,
+    available_audio_contexts: AvailableAudioContexts,
+    supported_audio_contexts: SupportedAudioContexts,
+}
+
+impl<T: bt_gatt::ServerTypes> Server<T> {
+    pub fn publish(&mut self, server: T::Server) -> Result<(), Error> {
+        if self.local_service.is_published() {
+            return Err(Error::AlreadyPublished);
+        }
+
+        let LocalServiceState::NotPublished { waker } = std::mem::replace(
+            &mut self.local_service,
+            LocalServiceState::Preparing { fut: server.prepare(self.service_def.clone()) },
+        ) else {
+            unreachable!();
+        };
+        waker.map(Waker::wake);
+        Ok(())
+    }
+
+    fn is_source_locations_handle(&self, handle: Handle) -> bool {
+        self.source_audio_locations.as_ref().map_or(false, |locations| locations.handle == handle)
+    }
+
+    fn is_sink_locations_handle(&self, handle: Handle) -> bool {
+        self.sink_audio_locations.as_ref().map_or(false, |locations| locations.handle == handle)
+    }
+}
+
+impl<T: bt_gatt::ServerTypes> Stream for Server<T> {
+    type Item = Result<(), Error>;
+
+    fn poll_next(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Option<Self::Item>> {
+        loop {
+            let mut this = self.as_mut().project();
+            let gatt_event = match futures::ready!(this.local_service.as_mut().poll_next(cx)) {
+                None => return Poll::Ready(None),
+                Some(Err(e)) => return Poll::Ready(Some(Err(e))),
+                Some(Ok(event)) => event,
+            };
+            use bt_gatt::server::ServiceEvent::*;
+            match gatt_event {
+                Read { handle, offset, responder, .. } => {
+                    let offset = offset as usize;
+                    let value = match handle {
+                        x if x == AVAILABLE_AUDIO_CONTEXTS_HANDLE => {
+                            self.available_audio_contexts.into_char_value()
+                        }
+                        x if x == SUPPORTED_AUDIO_CONTEXTS_HANDLE => {
+                            self.supported_audio_contexts.into_char_value()
+                        }
+                        x if self.is_source_locations_handle(x) => {
+                            self.source_audio_locations.as_ref().unwrap().into_char_value()
+                        }
+                        x if self.is_sink_locations_handle(x) => {
+                            self.sink_audio_locations.as_ref().unwrap().into_char_value()
+                        }
+                        pac_handle => {
+                            let Some(ref pac) = self.published_audio_capabilities.get(&pac_handle)
+                            else {
+                                responder.error(GattError::InvalidHandle);
+                                continue;
+                            };
+                            pac.encode()
+                        }
+                    };
+                    responder.respond(&value[offset..]);
+                    continue;
+                }
+                // TODO(b/309015071): support optional writes.
+                Write { responder, .. } => {
+                    responder.error(GattError::WriteNotPermitted);
+                    continue;
+                }
+                // TODO(b/309015071): implement notify since it's mandatory.
+                ClientConfiguration { .. } => {
+                    unimplemented!();
+                }
+                _ => continue,
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use bt_common::core::{CodecId, CodingFormat};
+    use bt_common::generic_audio::codec_capabilities::*;
+    use bt_common::generic_audio::AudioLocation;
+    use bt_common::PeerId;
+    use bt_gatt::server;
+    use bt_gatt::test_utils::{FakeServer, FakeServerEvent, FakeTypes};
+    use bt_gatt::types::ServiceKind;
+    use futures::{FutureExt, StreamExt};
+
+    use std::collections::HashSet;
+
+    use crate::AvailableContexts;
+
+    // Builder for a server with:
+    // - 1 sink and 1 source PAC characteristics
+    // - sink audio locations
+    fn default_server_builder() -> ServerBuilder {
+        let builder = ServerBuilder::new()
+            .add_sink(vec![PacRecord {
+                codec_id: CodecId::Assigned(CodingFormat::ALawLog),
+                codec_specific_capabilities: vec![CodecCapability::SupportedFrameDurations(
+                    FrameDurationSupport::BothNoPreference,
+                )],
+                metadata: vec![],
+            }])
+            .set_sink_locations(AudioLocations {
+                locations: HashSet::from([AudioLocation::FrontLeft, AudioLocation::FrontRight]),
+            })
+            .add_source(vec![
+                PacRecord {
+                    codec_id: CodecId::Assigned(CodingFormat::ALawLog),
+                    codec_specific_capabilities: vec![CodecCapability::SupportedFrameDurations(
+                        FrameDurationSupport::BothNoPreference,
+                    )],
+                    metadata: vec![],
+                },
+                PacRecord {
+                    codec_id: CodecId::Assigned(CodingFormat::MuLawLog),
+                    codec_specific_capabilities: vec![CodecCapability::SupportedFrameDurations(
+                        FrameDurationSupport::BothNoPreference,
+                    )],
+                    metadata: vec![],
+                },
+            ])
+            .add_source(vec![]);
+        builder
+    }
+
+    #[test]
+    fn build_server() {
+        let server = default_server_builder()
+            .build::<FakeTypes>(
+                AudioContexts::new(
+                    HashSet::from([ContextType::Conversational, ContextType::Media]),
+                    HashSet::from([ContextType::Media]),
+                ),
+                AudioContexts::new(HashSet::from([ContextType::Media]), HashSet::new()),
+            )
+            .expect("should succeed");
+        assert_eq!(server.published_audio_capabilities.len(), 2);
+
+        assert_eq!(server.supported_audio_contexts.handle.0, 1);
+        assert_eq!(
+            server.supported_audio_contexts.sink,
+            HashSet::from([ContextType::Conversational, ContextType::Media])
+        );
+        assert_eq!(server.supported_audio_contexts.source, HashSet::from([ContextType::Media]));
+
+        assert_eq!(server.available_audio_contexts.handle.0, 2);
+        assert_eq!(
+            server.available_audio_contexts.sink,
+            AvailableContexts::Available(HashSet::from([ContextType::Media]))
+        );
+        assert_eq!(server.available_audio_contexts.source, AvailableContexts::NotAvailable);
+
+        // Should have 1 sink PAC characteristic with audio locations.
+        let location_char = server.sink_audio_locations.as_ref().expect("should exist");
+        assert_eq!(location_char.handle.0, 3);
+        assert_eq!(
+            location_char.locations.locations,
+            HashSet::from([AudioLocation::FrontLeft, AudioLocation::FrontRight])
+        );
+
+        let mut sink_iter =
+            server.published_audio_capabilities.iter().filter(|(_handle, pac)| pac.is_sink());
+        let sink_char = sink_iter.next().expect("should exist");
+        assert_eq!(sink_char.0, &Handle(4));
+        assert_eq!(sink_char.1.pac_records().len(), 1);
+        assert!(sink_iter.next().is_none());
+
+        // Should have 1 source PAC characteristic w/o audio locations.
+        assert!(server.source_audio_locations.is_none());
+        let mut source_iter =
+            server.published_audio_capabilities.iter().filter(|(_handle, pac)| pac.is_source());
+        let source_char = source_iter.next().expect("should exist");
+        assert_eq!(source_char.0, &Handle(5));
+        assert_eq!(source_char.1.pac_records().len(), 2);
+        assert_eq!(source_iter.next(), None);
+    }
+
+    #[test]
+    fn build_server_error() {
+        // No sink or source PACs.
+        assert!(
+            ServerBuilder::new()
+                .build::<FakeTypes>(
+                    AudioContexts::new(
+                        HashSet::from([ContextType::Conversational, ContextType::Media]),
+                        HashSet::from([ContextType::Media]),
+                    ),
+                    AudioContexts::new(HashSet::from([ContextType::Media]), HashSet::new()),
+                )
+                .is_err()
+        );
+
+        // Sink audio context in available not in supported.
+        assert!(
+            default_server_builder()
+                .build::<FakeTypes>(
+                    AudioContexts::new(
+                        HashSet::from([ContextType::Conversational, ContextType::Media]),
+                        HashSet::from([ContextType::Media]),
+                    ),
+                    AudioContexts::new(HashSet::from([ContextType::Alerts]), HashSet::new()),
+                )
+                .is_err()
+        );
+
+        // Sink audio context in available not in supported.
+        assert!(
+            default_server_builder()
+                .build::<FakeTypes>(
+                    AudioContexts::new(
+                        HashSet::from([ContextType::Conversational, ContextType::Media]),
+                        HashSet::from([ContextType::Media]),
+                    ),
+                    AudioContexts::new(
+                        HashSet::from([]),
+                        HashSet::from([ContextType::EmergencyAlarm])
+                    ),
+                )
+                .is_err()
+        );
+    }
+
+    #[test]
+    fn publish_server() {
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+
+        let mut server = default_server_builder()
+            .build::<FakeTypes>(
+                AudioContexts::new(
+                    HashSet::from([ContextType::Media]),
+                    HashSet::from([ContextType::Media]),
+                ),
+                AudioContexts::new(HashSet::new(), HashSet::new()),
+            )
+            .unwrap();
+
+        // Server should be pending still since GATT server not establihsed.
+        let Poll::Pending = server.next().poll_unpin(&mut noop_cx) else {
+            panic!("Should be pending");
+        };
+
+        let (fake_gatt_server, mut event_receiver) = FakeServer::new();
+
+        // Event stream should be pending still since service not published.
+        let mut event_stream = event_receiver.next();
+        let Poll::Pending = event_stream.poll_unpin(&mut noop_cx) else {
+            panic!("Should be pending");
+        };
+
+        let _ = server.publish(fake_gatt_server).expect("should succeed");
+
+        // Server should poll on local server state.
+        let Poll::Pending = server.next().poll_unpin(&mut noop_cx) else {
+            panic!("Should be pending");
+        };
+
+        // Should receive event that GATT service was published.
+        let Poll::Ready(Some(FakeServerEvent::Published { id, definition })) =
+            event_stream.poll_unpin(&mut noop_cx)
+        else {
+            panic!("Should be published");
+        };
+        assert_eq!(id, server::ServiceId::new(1));
+        assert_eq!(definition.characteristics().collect::<Vec<_>>().len(), 5);
+        assert_eq!(definition.kind(), ServiceKind::Primary);
+        assert_eq!(definition.uuid(), crate::PACS_UUID);
+
+        // Server can only be published once.
+        let (fake_gatt_server, _) = FakeServer::new();
+        assert!(server.publish(fake_gatt_server).is_err());
+    }
+
+    #[test]
+    fn read_from_server() {
+        let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+
+        let mut server = default_server_builder()
+            .build::<FakeTypes>(
+                AudioContexts::new(
+                    HashSet::from([ContextType::Media]),
+                    HashSet::from([ContextType::Media]),
+                ),
+                AudioContexts::new(HashSet::from([ContextType::Media]), HashSet::new()),
+            )
+            .unwrap();
+
+        let (fake_gatt_server, mut event_receiver) = FakeServer::new();
+        let _ = server.publish(fake_gatt_server.clone()).expect("should succeed");
+
+        // Server should poll on local server state.
+        let Poll::Pending = server.next().poll_unpin(&mut noop_cx) else {
+            panic!("Should be pending");
+        };
+
+        // Should receive event that GATT service was published.
+        let mut event_stream = event_receiver.next();
+        let Poll::Ready(Some(FakeServerEvent::Published { id, .. })) =
+            event_stream.poll_unpin(&mut noop_cx)
+        else {
+            panic!("Should be published");
+        };
+
+        // Fake an incoming read from a remote peer.
+        let available_char_handle = server.available_audio_contexts.handle;
+        fake_gatt_server.incoming_read(PeerId(0x01), id, available_char_handle, 0);
+
+        // Server should still be pending.
+        let Poll::Pending = server.next().poll_unpin(&mut noop_cx) else {
+            panic!("Should be pending");
+        };
+
+        // We should received read response.
+        let Poll::Ready(Some(FakeServerEvent::ReadResponded { handle, value, .. })) =
+            event_stream.poll_unpin(&mut noop_cx)
+        else {
+            panic!("Should be published");
+        };
+        assert_eq!(handle, available_char_handle);
+        assert_eq!(value.expect("should be ok"), vec![0x04, 0x00, 0x00, 0x00]);
+    }
+}
diff --git a/rust/bt-pacs/src/server/types.rs b/rust/bt-pacs/src/server/types.rs
new file mode 100644
index 0000000..db45e52
--- /dev/null
+++ b/rust/bt-pacs/src/server/types.rs
@@ -0,0 +1,200 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Define types and trait implementations specific to the PACS server.
+
+use bt_gatt::client::FromCharacteristic;
+use bt_gatt::types::{
+    AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle, SecurityLevels,
+};
+use bt_gatt::Characteristic;
+
+use std::collections::HashSet;
+
+use crate::*;
+
+pub(crate) const SUPPORTED_AUDIO_CONTEXTS_HANDLE: Handle = Handle(1);
+pub(crate) const AVAILABLE_AUDIO_CONTEXTS_HANDLE: Handle = Handle(2);
+pub(crate) const HANDLE_OFFSET: u64 = 3;
+
+impl From<&SupportedAudioContexts> for Characteristic {
+    fn from(_value: &SupportedAudioContexts) -> Self {
+        // TODO(b/309015071): implement optional properties.
+        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+
+        Characteristic {
+            handle: SUPPORTED_AUDIO_CONTEXTS_HANDLE,
+            uuid: <SupportedAudioContexts as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+impl From<&AvailableAudioContexts> for Characteristic {
+    fn from(_value: &AvailableAudioContexts) -> Self {
+        let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
+
+        Characteristic {
+            handle: AVAILABLE_AUDIO_CONTEXTS_HANDLE,
+            uuid: <AvailableAudioContexts as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+impl From<&SourcePac> for Characteristic {
+    fn from(value: &SourcePac) -> Self {
+        // TODO(b/309015071): implement optional properties.
+        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+
+        Characteristic {
+            handle: value.handle,
+            uuid: <SourcePac as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+impl From<&SinkPac> for Characteristic {
+    fn from(value: &SinkPac) -> Self {
+        // TODO(b/309015071): implement optional properties.
+        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+
+        Characteristic {
+            handle: value.handle,
+            uuid: <SinkPac as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+impl From<&SourceAudioLocations> for Characteristic {
+    fn from(value: &SourceAudioLocations) -> Self {
+        // TODO(b/309015071): implement optional properties.
+        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+
+        Characteristic {
+            handle: value.handle,
+            uuid: <SourceAudioLocations as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+impl From<&SinkAudioLocations> for Characteristic {
+    fn from(value: &SinkAudioLocations) -> Self {
+        // TODO(b/309015071): implement optional properties.
+        let properties: CharacteristicProperties = CharacteristicProperty::Read.into();
+
+        Characteristic {
+            handle: value.handle,
+            uuid: <SinkAudioLocations as FromCharacteristic>::UUID,
+            properties: properties.clone(),
+            permissions: AttributePermissions::with_levels(
+                &properties,
+                &SecurityLevels::encryption_required(),
+            ),
+            descriptors: Vec::new(),
+        }
+    }
+}
+
+#[derive(Default)]
+pub struct AudioContexts {
+    pub(crate) sink: HashSet<ContextType>,
+    pub(crate) source: HashSet<ContextType>,
+}
+
+impl AudioContexts {
+    #[cfg(test)]
+    pub fn new(sink: HashSet<ContextType>, source: HashSet<ContextType>) -> Self {
+        AudioContexts { sink, source }
+    }
+}
+
+/// A single PAC characteristic consists of 1 or more PAC records.
+pub type PacRecords = Vec<PacRecord>;
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum PublishedAudioCapability {
+    Sink(SinkPac),
+    Source(SourcePac),
+}
+
+impl PublishedAudioCapability {
+    pub fn new_sink(handle: Handle, records: PacRecords) -> Self {
+        Self::Sink(SinkPac { handle: handle, capabilities: records })
+    }
+
+    pub fn new_source(handle: Handle, records: PacRecords) -> Self {
+        Self::Source(SourcePac { handle: handle, capabilities: records })
+    }
+
+    #[cfg(test)]
+    pub fn is_sink(&self) -> bool {
+        match self {
+            PublishedAudioCapability::Sink(_) => true,
+            PublishedAudioCapability::Source(_) => false,
+        }
+    }
+
+    #[cfg(test)]
+    pub fn is_source(&self) -> bool {
+        match self {
+            PublishedAudioCapability::Sink(_) => false,
+            PublishedAudioCapability::Source(_) => true,
+        }
+    }
+
+    #[cfg(test)]
+    pub fn pac_records(&self) -> &Vec<PacRecord> {
+        match self {
+            PublishedAudioCapability::Sink(pac) => &pac.capabilities,
+            PublishedAudioCapability::Source(pac) => &pac.capabilities,
+        }
+    }
+
+    /// Encode into PAC characteristic format as defined in PACS v1.0.1
+    /// Table 3.2/3.4.
+    pub(crate) fn encode(&self) -> Vec<u8> {
+        match self {
+            PublishedAudioCapability::Sink(pac) => pac_records_into_char_value(&pac.capabilities),
+            PublishedAudioCapability::Source(pac) => pac_records_into_char_value(&pac.capabilities),
+        }
+    }
+}
+
+impl From<&PublishedAudioCapability> for Characteristic {
+    fn from(value: &PublishedAudioCapability) -> Self {
+        match value {
+            PublishedAudioCapability::Sink(pac) => pac.into(),
+            PublishedAudioCapability::Source(pac) => pac.into(),
+        }
+    }
+}