| // 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. |
| |
| 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 } |
| } |
| } |