blob: 28efd8e0a7457c1f7d936d283d875ca98d1509d3 [file] [log] [blame]
// 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 const 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 }
}
}