rust/bt-gatt: Add server interface

Add API to define services and then:
 - publish them
 - receive connections from peers
 - answer read requests
 - receive write requests
 - send notifications

Test: added fakes and unit tests
Test: cargo test
Change-Id: I4b4dcd22750e8dc83ffae357a22cde838df5ba04
Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1602
Reviewed-by: Dayeong Lee <dayeonglee@google.com>
Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
diff --git a/rust/bt-gatt/src/client.rs b/rust/bt-gatt/src/client.rs
index 0a573b0..d27a448 100644
--- a/rust/bt-gatt/src/client.rs
+++ b/rust/bt-gatt/src/client.rs
@@ -6,24 +6,6 @@
 
 use bt_common::{PeerId, Uuid};
 
-pub enum ServiceKind {
-    Primary,
-    Secondary,
-}
-
-// 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`].
-    pub id: u64,
-    /// Whether the service is marked as Primary in the GATT server.
-    pub kind: ServiceKind,
-    /// The UUID identifying the type of service that this is.
-    pub uuid: Uuid,
-}
-
 /// GATT Client connected to a particular peer.
 /// Holding a struct that implements this should attempt to maintain a LE
 /// connection to the peer.
@@ -117,6 +99,8 @@
     /// Configuration descriptor for the Characteristic subscribed to.
     /// Updates sent from the peer wlil be delivered to the Stream returned.
     fn subscribe(&self, handle: &Handle) -> T::NotificationStream;
+
+    // TODO: Find included services
 }
 
 /// Convenience class for communicating with characteristics on a remote peer.
diff --git a/rust/bt-gatt/src/lib.rs b/rust/bt-gatt/src/lib.rs
index c441821..f5adefd 100644
--- a/rust/bt-gatt/src/lib.rs
+++ b/rust/bt-gatt/src/lib.rs
@@ -23,6 +23,9 @@
 use futures::{Future, Stream};
 
 /// Implementors implement traits with respect to GattTypes.
+/// Implementation crates provide an object which relates a constellation of
+/// types to each other, used to provide concrete types for library crates to
+/// paramaterize on.
 pub trait GattTypes: Sized {
     // Types related to finding and connecting to peers
     type Central: Central<Self>;
@@ -52,3 +55,19 @@
     /// Returns an error if the value could not be written.
     type WriteFut<'a>: Future<Output = Result<()>> + 'a;
 }
+
+/// Servers and services are defined with respect to ServerTypes.
+/// Implementation crates provide an object which relates this constellation of
+/// types to each other, used to provide concrete types for library crates to
+/// paramaterize on.
+pub trait ServerTypes: Sized {
+    type Server: Server<Self>;
+    type LocalService: server::LocalService<Self>;
+    type LocalServiceFut: Future<Output = Result<Self::LocalService>>;
+    type ServiceEventStream: Stream<Item = Result<server::ServiceEvent<Self>>>;
+    type ServiceWriteType: ToOwned<Owned = Vec<u8>>;
+    type ReadResponder: server::ReadResponder;
+    type WriteResponder: server::WriteResponder;
+
+    type IndicateConfirmationStream: Stream<Item = Result<server::ConfirmationEvent>>;
+}
diff --git a/rust/bt-gatt/src/server.rs b/rust/bt-gatt/src/server.rs
index c5d53d3..8a6f3b3 100644
--- a/rust/bt-gatt/src/server.rs
+++ b/rust/bt-gatt/src/server.rs
@@ -1,5 +1,234 @@
-// Copyright 2023 The Sapphire Authors. All rights reserved.
+// Copyright 2023 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.
 
-pub struct Server;
+use std::collections::HashSet;
+
+use bt_common::{PeerId, Uuid};
+
+use crate::types::*;
+
+/// ServiceId is used to identify a local service when publishing.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+pub struct ServiceId(u64);
+
+impl ServiceId {
+    pub fn new(id: u64) -> Self {
+        Self(id)
+    }
+}
+
+impl From<ServiceId> for u64 {
+    fn from(value: ServiceId) -> Self {
+        value.0
+    }
+}
+
+/// Defines a service to be added to a Server
+#[derive(Debug, Clone)]
+pub struct ServiceDefinition {
+    /// Local identifier used to include services within each other.
+    id: ServiceId,
+    /// UUID identifying the type of service
+    uuid: Uuid,
+    /// Whether the service is marked as Primary in the GATT server.
+    kind: ServiceKind,
+    /// Characteristics in the service. Add with
+    /// [ServiceDefinition::add_characteristic]
+    characteristics: Vec<Characteristic>,
+    included_services: HashSet<ServiceId>,
+    /// Set of handles (characteristic or descriptor) used, to verify uniquness
+    /// of new handles added.
+    handles: HashSet<Handle>,
+}
+
+impl ServiceDefinition {
+    /// Make a new, empty service with a given id.
+    pub fn new(id: ServiceId, uuid: Uuid, kind: ServiceKind) -> Self {
+        Self {
+            id,
+            uuid,
+            kind,
+            characteristics: Default::default(),
+            included_services: Default::default(),
+            handles: HashSet::new(),
+        }
+    }
+
+    /// Add a characteristic to the definition.  If any duplicate handles or an
+    /// invalid configuration of descriptors are detected, an error is
+    /// returned.
+    pub fn add_characteristic(&mut self, characteristic: Characteristic) -> Result<()> {
+        let new_handles = characteristic.handles().collect();
+        if !self.handles.is_disjoint(&new_handles) {
+            return Err(Error::DuplicateHandle(
+                self.handles.intersection(&new_handles).copied().collect(),
+            ));
+        }
+        self.handles.extend(new_handles);
+        // TODO: check for more errors
+        self.characteristics.push(characteristic);
+        Ok(())
+    }
+
+    pub fn id(&self) -> ServiceId {
+        self.id
+    }
+
+    pub fn uuid(&self) -> Uuid {
+        self.uuid
+    }
+
+    pub fn kind(&self) -> ServiceKind {
+        self.kind
+    }
+
+    pub fn characteristics(&self) -> impl Iterator<Item = &Characteristic> {
+        self.characteristics.iter()
+    }
+
+    pub fn included(&self) -> impl Iterator<Item = ServiceId> + '_ {
+        self.included_services.iter().cloned()
+    }
+
+    /// Add a service to the definition.
+    pub fn add_service(&mut self, id: ServiceId) {
+        self.included_services.insert(id);
+    }
+}
+
+/// Services can be included in other services, and are included in the database
+/// when they are published.  All included services should be prepared before
+/// the service including them.  Publishing a service that includes other
+/// services will publish the included services, although the events associated
+/// with the included service will not be returned until the
+/// [LocalService::publish] is called.
+pub trait Server<T: crate::ServerTypes> {
+    /// Prepare to publish a service.
+    /// This service is not immediately visible in the local GATT server.
+    /// It will be published when the LocalService::publish is called.
+    /// If the returned LocalService is dropped, the service will be removed
+    /// from the Server.
+    fn prepare(&self, service: ServiceDefinition) -> T::LocalServiceFut;
+}
+
+pub trait LocalService<T: crate::ServerTypes> {
+    /// Publish the service.
+    /// Returns an EventStream providing Events to be processed by the local
+    /// service implementation.
+    /// Events will only be delivered to one ServiceEventStream at a time.
+    /// Calling publish while a previous ServiceEventStream is still active
+    /// will return a stream with only Err(AlreadyPublished).
+    fn publish(&self) -> T::ServiceEventStream;
+
+    /// Notify a characteristic.
+    /// Leave `peers` empty to notify all peers who have configured
+    /// notifications. Peers that have not configured for notifications will
+    /// not be notified.
+    fn notify(&self, characteristic: &Handle, data: &[u8], peers: &[PeerId]);
+
+    /// Indicate on a characteristic.
+    /// Leave `peers` empty to notify all peers who have configured
+    /// indications. Peers that have not configured for indications will
+    /// be skipped. Returns a stream which has items for each peer that
+    /// confirms the notification, and terminates when all peers have either
+    /// timed out or confirmed.
+    fn indicate(
+        &self,
+        characteristic: &Handle,
+        data: &[u8],
+        peers: &[PeerId],
+    ) -> T::IndicateConfirmationStream;
+}
+
+pub struct ConfirmationEvent {
+    peer_id: PeerId,
+    result: Result<()>,
+}
+
+impl ConfirmationEvent {
+    pub fn create_ack(peer_id: PeerId) -> Self {
+        Self { peer_id, result: Ok(()) }
+    }
+
+    pub fn create_error(peer_id: PeerId, error: Error) -> Self {
+        Self { peer_id, result: Err(error) }
+    }
+    pub fn peer_id(&self) -> PeerId {
+        self.peer_id
+    }
+
+    pub fn error(&self) -> Option<&Error> {
+        self.result.as_ref().err()
+    }
+
+    pub fn is_ok(&self) -> bool {
+        self.result.is_ok()
+    }
+
+    pub fn is_err(&self) -> bool {
+        self.result.is_err()
+    }
+}
+
+/// Responder that can send data that has been read from a characteristic.
+pub trait ReadResponder {
+    /// Respond with the data requested.  `value` may be shorter than requested.
+    fn respond(self, value: &[u8]);
+    /// Respond with an error.
+    fn error(self, error: GattError);
+}
+
+/// Responder that can acknowledge a write to a characteristic.
+pub trait WriteResponder {
+    /// Acknowledge the write. Will only send an acknowledgement if allowed by
+    /// the GATT protocol.
+    fn acknowledge(self);
+    /// Respond with an error.
+    fn error(self, error: GattError);
+}
+
+#[derive(Debug)]
+pub enum NotificationType {
+    Disable,
+    Notify,
+    Indicate,
+}
+
+#[non_exhaustive]
+pub enum ServiceEvent<T: crate::ServerTypes> {
+    /// Peer requests to read from a handle (characteritic or descriptor) at the
+    /// given offset.
+    Read { peer_id: PeerId, handle: Handle, offset: u32, responder: T::ReadResponder },
+    /// Peer has written a value to a handle (characteristic or descriptor) at
+    /// the given offset.
+    Write {
+        peer_id: PeerId,
+        handle: Handle,
+        offset: u32,
+        value: T::ServiceWriteType,
+        responder: T::WriteResponder,
+    },
+    /// Notification that a peer has configured a characteristic for indication
+    /// or notification.
+    ClientConfiguration { peer_id: PeerId, handle: Handle, notification_type: NotificationType },
+    /// Extra information about a peer is provided. This event may not be sent
+    /// by all implementations.
+    #[non_exhaustive]
+    PeerInfo { peer_id: PeerId, mtu: Option<u16>, connected: Option<bool> },
+}
+
+impl<T: crate::ServerTypes> ServiceEvent<T> {
+    pub fn peer_id(&self) -> PeerId {
+        match self {
+            Self::Read { peer_id, .. } => *peer_id,
+            Self::Write { peer_id, .. } => *peer_id,
+            Self::ClientConfiguration { peer_id, .. } => *peer_id,
+            Self::PeerInfo { peer_id, .. } => *peer_id,
+        }
+    }
+
+    pub fn peer_info(peer_id: PeerId, mtu: Option<u16>, connected: Option<bool>) -> Self {
+        Self::PeerInfo { peer_id, mtu, connected }
+    }
+}
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs
index 7a3578f..e155f8f 100644
--- a/rust/bt-gatt/src/test_utils.rs
+++ b/rust/bt-gatt/src/test_utils.rs
@@ -14,7 +14,8 @@
 
 use crate::central::ScanResult;
 use crate::client::CharacteristicNotification;
-use crate::{types::*, GattTypes};
+use crate::server::{self, LocalService, ReadResponder, ServiceDefinition, WriteResponder};
+use crate::{types::*, GattTypes, ServerTypes};
 
 #[derive(Default)]
 struct FakePeerServiceInner {
@@ -257,6 +258,17 @@
     type WriteFut<'a> = Ready<Result<()>>;
 }
 
+impl ServerTypes for FakeTypes {
+    type Server = FakeServer;
+    type LocalService = FakeLocalService;
+    type LocalServiceFut = Ready<Result<FakeLocalService>>;
+    type ServiceEventStream = UnboundedReceiver<Result<server::ServiceEvent<FakeTypes>>>;
+    type ServiceWriteType = Vec<u8>;
+    type ReadResponder = FakeResponder;
+    type WriteResponder = FakeResponder;
+    type IndicateConfirmationStream = UnboundedReceiver<Result<server::ConfirmationEvent>>;
+}
+
 #[derive(Default)]
 pub struct FakeCentralInner {
     clients: HashMap<PeerId, FakeClient>,
@@ -291,3 +303,243 @@
         futures::future::ready(res)
     }
 }
+
+pub enum FakeServerEvent {
+    ReadResponded {
+        service_id: server::ServiceId,
+        handle: Handle,
+        value: Result<Vec<u8>>,
+    },
+    WriteResponded {
+        service_id: server::ServiceId,
+        handle: Handle,
+        value: Result<()>,
+    },
+    Notified {
+        service_id: server::ServiceId,
+        handle: Handle,
+        value: Vec<u8>,
+        peers: Vec<PeerId>,
+    },
+    Indicated {
+        service_id: server::ServiceId,
+        handle: Handle,
+        value: Vec<u8>,
+        peers: Vec<PeerId>,
+        confirmations: UnboundedSender<Result<server::ConfirmationEvent>>,
+    },
+    Unpublished {
+        id: server::ServiceId,
+    },
+    Published {
+        id: server::ServiceId,
+        definition: ServiceDefinition,
+    },
+}
+
+#[derive(Debug)]
+struct FakeServerInner {
+    services: HashMap<server::ServiceId, ServiceDefinition>,
+    service_senders:
+        HashMap<server::ServiceId, UnboundedSender<Result<server::ServiceEvent<FakeTypes>>>>,
+    sender: UnboundedSender<FakeServerEvent>,
+}
+
+#[derive(Debug)]
+pub struct FakeServer {
+    inner: Arc<Mutex<FakeServerInner>>,
+}
+
+impl server::Server<FakeTypes> for FakeServer {
+    fn prepare(
+        &self,
+        service: server::ServiceDefinition,
+    ) -> <FakeTypes as ServerTypes>::LocalServiceFut {
+        let id = service.id();
+        self.inner.lock().services.insert(id, service);
+        futures::future::ready(Ok(FakeLocalService::new(id, self.inner.clone())))
+    }
+}
+
+impl FakeServer {
+    pub fn new() -> (Self, UnboundedReceiver<FakeServerEvent>) {
+        let (sender, receiver) = futures::channel::mpsc::unbounded();
+        (
+            Self {
+                inner: Arc::new(Mutex::new(FakeServerInner {
+                    services: Default::default(),
+                    service_senders: Default::default(),
+                    sender,
+                })),
+            },
+            receiver,
+        )
+    }
+
+    pub fn service(&self, id: server::ServiceId) -> Option<ServiceDefinition> {
+        self.inner.lock().services.get(&id).cloned()
+    }
+
+    pub fn incoming_write(
+        &self,
+        peer_id: PeerId,
+        id: server::ServiceId,
+        handle: Handle,
+        offset: u32,
+        value: Vec<u8>,
+    ) {
+        // TODO: check that the write is allowed
+        let sender = self.inner.lock().sender.clone();
+        self.inner
+            .lock()
+            .service_senders
+            .get(&id)
+            .unwrap()
+            .unbounded_send(Ok(server::ServiceEvent::Write {
+                peer_id,
+                handle,
+                offset,
+                value,
+                responder: FakeResponder { sender, service_id: id, handle },
+            }))
+            .unwrap();
+    }
+
+    pub fn incoming_read(
+        &self,
+        peer_id: PeerId,
+        id: server::ServiceId,
+        handle: Handle,
+        offset: u32,
+    ) {
+        // TODO: check that the read is allowed
+        let sender = self.inner.lock().sender.clone();
+        self.inner
+            .lock()
+            .service_senders
+            .get(&id)
+            .unwrap()
+            .unbounded_send(Ok(server::ServiceEvent::Read {
+                peer_id,
+                handle,
+                offset,
+                responder: FakeResponder { sender, service_id: id, handle },
+            }))
+            .unwrap();
+    }
+}
+
+pub struct FakeLocalService {
+    id: server::ServiceId,
+    inner: Arc<Mutex<FakeServerInner>>,
+}
+
+impl FakeLocalService {
+    fn new(id: server::ServiceId, inner: Arc<Mutex<FakeServerInner>>) -> Self {
+        Self { id, inner }
+    }
+}
+
+impl Drop for FakeLocalService {
+    fn drop(&mut self) {
+        self.inner.lock().services.remove(&self.id);
+    }
+}
+
+impl LocalService<FakeTypes> for FakeLocalService {
+    fn publish(&self) -> <FakeTypes as ServerTypes>::ServiceEventStream {
+        let (sender, receiver) = futures::channel::mpsc::unbounded();
+        let _ = self.inner.lock().service_senders.insert(self.id, sender);
+        let definition = self.inner.lock().services.get(&self.id).unwrap().clone();
+        self.inner
+            .lock()
+            .sender
+            .unbounded_send(FakeServerEvent::Published { id: self.id, definition })
+            .unwrap();
+        receiver
+    }
+
+    fn notify(&self, characteristic: &Handle, data: &[u8], peers: &[PeerId]) {
+        self.inner
+            .lock()
+            .sender
+            .unbounded_send(FakeServerEvent::Notified {
+                service_id: self.id,
+                handle: *characteristic,
+                value: data.into(),
+                peers: peers.into(),
+            })
+            .unwrap();
+    }
+
+    fn indicate(
+        &self,
+        characteristic: &Handle,
+        data: &[u8],
+        peers: &[PeerId],
+    ) -> <FakeTypes as ServerTypes>::IndicateConfirmationStream {
+        let (sender, receiver) = futures::channel::mpsc::unbounded();
+        self.inner
+            .lock()
+            .sender
+            .unbounded_send(FakeServerEvent::Indicated {
+                service_id: self.id,
+                handle: *characteristic,
+                value: data.into(),
+                peers: peers.into(),
+                confirmations: sender,
+            })
+            .unwrap();
+        receiver
+    }
+}
+
+pub struct FakeResponder {
+    sender: UnboundedSender<FakeServerEvent>,
+    service_id: server::ServiceId,
+    handle: Handle,
+}
+
+impl ReadResponder for FakeResponder {
+    fn respond(self, value: &[u8]) {
+        self.sender
+            .unbounded_send(FakeServerEvent::ReadResponded {
+                service_id: self.service_id,
+                handle: self.handle,
+                value: Ok(value.into()),
+            })
+            .unwrap();
+    }
+
+    fn error(self, error: GattError) {
+        self.sender
+            .unbounded_send(FakeServerEvent::ReadResponded {
+                service_id: self.service_id,
+                handle: self.handle,
+                value: Err(Error::Gatt(error)),
+            })
+            .unwrap();
+    }
+}
+
+impl WriteResponder for FakeResponder {
+    fn acknowledge(self) {
+        self.sender
+            .unbounded_send(FakeServerEvent::WriteResponded {
+                service_id: self.service_id,
+                handle: self.handle,
+                value: Ok(()),
+            })
+            .unwrap();
+    }
+
+    fn error(self, error: GattError) {
+        self.sender
+            .unbounded_send(FakeServerEvent::WriteResponded {
+                service_id: self.service_id,
+                handle: self.handle,
+                value: Err(Error::Gatt(error)),
+            })
+            .unwrap();
+    }
+}
diff --git a/rust/bt-gatt/src/tests.rs b/rust/bt-gatt/src/tests.rs
index e753706..896f2cc 100644
--- a/rust/bt-gatt/src/tests.rs
+++ b/rust/bt-gatt/src/tests.rs
@@ -10,9 +10,10 @@
 use bt_common::{PeerId, Uuid};
 
 use crate::central::{AdvertisingDatum, PeerName, ScanResult};
+use crate::server::{ServiceDefinition, ServiceEvent};
 use crate::test_utils::*;
 use crate::types::*;
-use crate::{central::Filter, client::PeerService, Central};
+use crate::{central::Filter, client::PeerService, server, Central};
 
 const TEST_UUID_1: Uuid = Uuid::from_u16(0x1234);
 const TEST_UUID_2: Uuid = Uuid::from_u16(0x2345);
@@ -192,7 +193,7 @@
 }
 
 #[test]
-fn peer_service_subsribe() {
+fn peer_service_subscribe() {
     let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
 
     let mut fake_peer_service = setup_peer_service();
@@ -293,3 +294,113 @@
 
     boxed_generic_usage(boxed);
 }
+
+/// Generate an example service definition with a variety of things in it.
+fn example_service_definition() -> ServiceDefinition {
+    let mut def =
+        ServiceDefinition::new(server::ServiceId::new(1), TEST_UUID_1, ServiceKind::Primary);
+    // Only readable
+    def.add_characteristic(Characteristic {
+        handle: Handle(1),
+        uuid: TEST_UUID_2,
+        properties: CharacteristicProperty::Read.into(),
+        permissions: AttributePermissions {
+            read: Some(SecurityLevels::default()),
+            write: None,
+            update: None,
+        },
+        descriptors: vec![],
+    })
+    .unwrap();
+    // Only writable
+    def.add_characteristic(Characteristic {
+        handle: Handle(2),
+        uuid: TEST_UUID_3,
+        properties: CharacteristicProperty::Write.into(),
+        permissions: AttributePermissions {
+            read: None,
+            write: Some(SecurityLevels::default()),
+            update: None,
+        },
+        descriptors: vec![],
+    })
+    .unwrap();
+    // Readable, writable, and notifiable
+    def.add_characteristic(Characteristic {
+        handle: Handle(3),
+        uuid: TEST_UUID_3,
+        properties: CharacteristicProperty::Read
+            | CharacteristicProperty::Write
+            | CharacteristicProperty::Notify,
+        permissions: AttributePermissions {
+            read: Some(SecurityLevels::default()),
+            write: Some(SecurityLevels::default()),
+            update: Some(SecurityLevels::default()),
+        },
+        descriptors: vec![],
+    })
+    .unwrap();
+    // Only Readable and Indicatable
+    def.add_characteristic(Characteristic {
+        handle: Handle(4),
+        uuid: TEST_UUID_3,
+        properties: CharacteristicProperty::Read | CharacteristicProperty::Indicate,
+        permissions: AttributePermissions {
+            read: Some(SecurityLevels::default()),
+            write: None,
+            update: Some(SecurityLevels::default()),
+        },
+        descriptors: vec![],
+    })
+    .unwrap();
+    def
+}
+
+#[test]
+fn fake_server_usable() {
+    use server::{LocalService, Server, ServiceId};
+    let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
+
+    let (server, mut events) = FakeServer::new();
+    let mut local_service_fut = server.prepare(example_service_definition());
+
+    let Poll::Ready(Ok(local_service)) = local_service_fut.poll_unpin(&mut noop_cx) else {
+        panic!("Expected local service to prepare fine");
+    };
+
+    let mut service_event_stream = local_service.publish();
+
+    // Published shouldn't be emitted until the service is present on the server.
+    let Poll::Ready(Some(FakeServerEvent::Published { id, definition })) =
+        events.poll_next_unpin(&mut noop_cx)
+    else {
+        panic!("Expected to generate an event from publishing");
+    };
+
+    assert_eq!(server::ServiceId::new(1), id);
+    assert_eq!(definition.characteristics().count(), 4);
+
+    server.incoming_read(PeerId(1), ServiceId::new(1), Handle(1), 0);
+
+    let Poll::Ready(Some(Ok(ServiceEvent::Read { peer_id, handle, offset: _, responder }))) =
+        service_event_stream.poll_next_unpin(&mut noop_cx)
+    else {
+        panic!("Expected read on the service stream");
+    };
+
+    assert_eq!(PeerId(1), peer_id);
+    assert_eq!(Handle(1), handle);
+
+    use server::ReadResponder;
+    responder.respond(&[0, 1, 2, 3, 4, 5]);
+
+    let Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle, value })) =
+        events.poll_next_unpin(&mut noop_cx)
+    else {
+        panic!("Expected a read response");
+    };
+
+    assert_eq!(ServiceId::new(1), service_id);
+    assert_eq!(Handle(1), handle);
+    assert_eq!(&[0, 1, 2, 3, 4, 5], value.expect("should be ok").as_slice());
+}
diff --git a/rust/bt-gatt/src/types.rs b/rust/bt-gatt/src/types.rs
index cd052f2..0e7ada4 100644
--- a/rust/bt-gatt/src/types.rs
+++ b/rust/bt-gatt/src/types.rs
@@ -147,6 +147,10 @@
     Other(#[from] Box<dyn std::error::Error + Send + Sync>),
     #[error("GATT error: {0}")]
     Gatt(#[from] GattError),
+    #[error("definition error: duplicate handle(s) {0:?}")]
+    DuplicateHandle(Vec<Handle>),
+    #[error("service for {0:?} already published")]
+    AlreadyPublished(crate::server::ServiceId),
 }
 
 impl From<String> for Error {
@@ -163,14 +167,34 @@
 
 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.
+/// Handles are used as local-to-your-code 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.
+/// and Descriptor within a PeerService for the client.  It is not necessary or
+/// expected for different clients of the same service to have the same handles.
+///
+/// For LocalService publishing, Service Definitions are required to have no
+/// duplicate Handles across their defined Characteristics and Descriptors.
+/// Characteristic and ServiceDefinition will check and return
+/// Error::DuplicateHandle.  Handles in ServiceDefinitions should not match the
+/// attribute handles in the local GATT server.  They are local to your code for
+/// use in correlating with ServiceEvents from peers.  Stacks must translate
+/// the actual GATT handles to the handles provided in the ServiceDefinition
+/// for these events.
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 pub struct Handle(pub u64);
 
+/// Whether a service is marked as Primary or Secondary on the server.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ServiceKind {
+    Primary,
+    Secondary,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum WriteMode {
     None,
     Reliable,
@@ -219,13 +243,13 @@
 pub struct AttributePermissions {
     /// If None, then this cannot be read. Otherwise the SecurityLevels given
     /// are required to read.
-    pub(crate) read: Option<SecurityLevels>,
+    pub read: Option<SecurityLevels>,
     /// If None, then this cannot be written. Otherwise the SecurityLevels given
     /// are required to write.
-    pub(crate) write: Option<SecurityLevels>,
+    pub write: Option<SecurityLevels>,
     /// If None, then this cannot be updated. Otherwise the SecurityLevels given
     /// are required to update.
-    pub(crate) update: Option<SecurityLevels>,
+    pub update: Option<SecurityLevels>,
 }
 
 /// The different types of well-known Descriptors are defined here and should be
@@ -236,8 +260,8 @@
 /// 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.
+/// Vol 3 Part G Sec 3.3.3).  Missing DescriptorTypes are handled internally
+/// and are added automatically, and are omitted from descriptor APIs.
 #[derive(Clone, Debug)]
 pub enum DescriptorType {
     UserDescription(String),
@@ -256,6 +280,18 @@
     },
 }
 
+impl From<&DescriptorType> for Uuid {
+    fn from(value: &DescriptorType) -> Self {
+        // Values from https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/uuids/descriptors.yaml
+        match value {
+            DescriptorType::UserDescription(_) => Uuid::from_u16(0x2901),
+            DescriptorType::ServerConfiguration { .. } => Uuid::from_u16(0x2903),
+            DescriptorType::CharacteristicPresentation { .. } => Uuid::from_u16(0x2904),
+            DescriptorType::Other { uuid } => uuid.clone(),
+        }
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Descriptor {
     pub handle: Handle,
@@ -285,4 +321,8 @@
     pub fn descriptors(&self) -> impl Iterator<Item = &Descriptor> {
         self.descriptors.iter()
     }
+
+    pub fn handles(&self) -> impl Iterator<Item = Handle> + '_ {
+        std::iter::once(self.handle).chain(self.descriptors.iter().map(|d| d.handle))
+    }
 }