1 //! The GATT service as defined in Core Spec 5.3 Vol 3G Section 7
2 
3 use pdl_runtime::Packet;
4 use std::{cell::RefCell, collections::HashMap, ops::RangeInclusive, rc::Rc};
5 
6 use anyhow::Result;
7 use async_trait::async_trait;
8 use log::{error, warn};
9 use tokio::task::spawn_local;
10 
11 use crate::{
12     core::{
13         shared_box::{WeakBox, WeakBoxRef},
14         uuid::Uuid,
15     },
16     gatt::{
17         callbacks::GattDatastore,
18         ffi::AttributeBackingType,
19         ids::{AttHandle, TransportIndex},
20         server::{
21             att_server_bearer::AttServerBearer,
22             gatt_database::{
23                 AttDatabaseImpl, AttPermissions, GattCharacteristicWithHandle, GattDatabase,
24                 GattDatabaseCallbacks, GattDescriptorWithHandle, GattServiceWithHandle,
25             },
26         },
27     },
28     packets::att::{self, AttErrorCode},
29 };
30 
31 #[derive(Default)]
32 struct GattService {
33     clients: RefCell<HashMap<TransportIndex, ClientState>>,
34 }
35 
36 #[derive(Clone)]
37 struct ClientState {
38     bearer: WeakBox<AttServerBearer<AttDatabaseImpl>>,
39     registered_for_service_change: bool,
40 }
41 
42 // Must lie in the range specified by GATT_GATT_START_HANDLE from legacy stack
43 const GATT_SERVICE_HANDLE: AttHandle = AttHandle(1);
44 const SERVICE_CHANGE_HANDLE: AttHandle = AttHandle(3);
45 const SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE: AttHandle = AttHandle(4);
46 
47 /// The UUID used for the GATT service (Assigned Numbers 3.4.1 Services by Name)
48 pub const GATT_SERVICE_UUID: Uuid = Uuid::new(0x1801);
49 /// The UUID used for the Service Changed characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
50 pub const SERVICE_CHANGE_UUID: Uuid = Uuid::new(0x2A05);
51 /// The UUID used for the Client Characteristic Configuration descriptor (Assigned Numbers 3.7 Descriptors)
52 pub const CLIENT_CHARACTERISTIC_CONFIGURATION_UUID: Uuid = Uuid::new(0x2902);
53 
54 #[async_trait(?Send)]
55 impl GattDatastore for GattService {
read( &self, tcb_idx: TransportIndex, handle: AttHandle, _: AttributeBackingType, ) -> Result<Vec<u8>, AttErrorCode>56     async fn read(
57         &self,
58         tcb_idx: TransportIndex,
59         handle: AttHandle,
60         _: AttributeBackingType,
61     ) -> Result<Vec<u8>, AttErrorCode> {
62         if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
63             att::GattClientCharacteristicConfiguration {
64                 notification: 0,
65                 indication: self
66                     .clients
67                     .borrow()
68                     .get(&tcb_idx)
69                     .map(|state| state.registered_for_service_change)
70                     .unwrap_or(false)
71                     .into(),
72             }
73             .encode_to_vec()
74             .map_err(|_| AttErrorCode::UnlikelyError)
75         } else {
76             unreachable!()
77         }
78     }
79 
write( &self, tcb_idx: TransportIndex, handle: AttHandle, _: AttributeBackingType, data: &[u8], ) -> Result<(), AttErrorCode>80     async fn write(
81         &self,
82         tcb_idx: TransportIndex,
83         handle: AttHandle,
84         _: AttributeBackingType,
85         data: &[u8],
86     ) -> Result<(), AttErrorCode> {
87         if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
88             let ccc =
89                 att::GattClientCharacteristicConfiguration::decode_full(data).map_err(|err| {
90                     warn!("failed to parse CCC descriptor, got: {err:?}");
91                     AttErrorCode::ApplicationError
92                 })?;
93             let mut clients = self.clients.borrow_mut();
94             let state = clients.get_mut(&tcb_idx);
95             let Some(state) = state else {
96                 error!("Received write request from disconnected client...");
97                 return Err(AttErrorCode::UnlikelyError);
98             };
99             state.registered_for_service_change = ccc.indication != 0;
100             Ok(())
101         } else {
102             unreachable!()
103         }
104     }
105 }
106 
107 impl GattDatabaseCallbacks for GattService {
on_le_connect( &self, tcb_idx: TransportIndex, bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>, )108     fn on_le_connect(
109         &self,
110         tcb_idx: TransportIndex,
111         bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>,
112     ) {
113         // TODO(aryarahul): registered_for_service_change may not be false for bonded devices
114         self.clients.borrow_mut().insert(
115             tcb_idx,
116             ClientState { bearer: bearer.downgrade(), registered_for_service_change: false },
117         );
118     }
119 
on_le_disconnect(&self, tcb_idx: TransportIndex)120     fn on_le_disconnect(&self, tcb_idx: TransportIndex) {
121         self.clients.borrow_mut().remove(&tcb_idx);
122     }
123 
on_service_change(&self, range: RangeInclusive<AttHandle>)124     fn on_service_change(&self, range: RangeInclusive<AttHandle>) {
125         for (conn_id, client) in self.clients.borrow().clone() {
126             if client.registered_for_service_change {
127                 client.bearer.with(|bearer| match bearer {
128                     Some(bearer) => {
129                         spawn_local(
130                             bearer.send_indication(
131                                 SERVICE_CHANGE_HANDLE,
132                                 att::GattServiceChanged {
133                                     start_handle: (*range.start()).into(),
134                                     end_handle: (*range.end()).into(),
135                                 }
136                                 .encode_to_vec()
137                                 .unwrap(),
138                             ),
139                         );
140                     }
141                     None => {
142                         error!("Registered client's bearer has been destructed ({conn_id:?})")
143                     }
144                 });
145             }
146         }
147     }
148 }
149 
150 /// Register the GATT service in the provided GATT database.
register_gatt_service(database: &mut GattDatabase) -> Result<()>151 pub fn register_gatt_service(database: &mut GattDatabase) -> Result<()> {
152     let this = Rc::new(GattService::default());
153     database.add_service_with_handles(
154         // GATT Service
155         GattServiceWithHandle {
156             handle: GATT_SERVICE_HANDLE,
157             type_: GATT_SERVICE_UUID,
158             // Service Changed Characteristic
159             characteristics: vec![GattCharacteristicWithHandle {
160                 handle: SERVICE_CHANGE_HANDLE,
161                 type_: SERVICE_CHANGE_UUID,
162                 permissions: AttPermissions::INDICATE,
163                 descriptors: vec![GattDescriptorWithHandle {
164                     handle: SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
165                     type_: CLIENT_CHARACTERISTIC_CONFIGURATION_UUID,
166                     permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
167                 }],
168             }],
169         },
170         this.clone(),
171     )?;
172     database.register_listener(this);
173     Ok(())
174 }
175 #[cfg(test)]
176 mod test {
177     use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
178 
179     use super::*;
180 
181     use crate::{
182         core::shared_box::SharedBox,
183         gatt::{
184             mocks::mock_datastore::MockDatastore,
185             server::{
186                 att_database::AttDatabase,
187                 gatt_database::{
188                     GattDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
189                 },
190             },
191         },
192         packets::att,
193         utils::task::{block_on_locally, try_await},
194     };
195 
196     const TCB_IDX: TransportIndex = TransportIndex(1);
197     const ANOTHER_TCB_IDX: TransportIndex = TransportIndex(2);
198     const SERVICE_TYPE: Uuid = Uuid::new(0x1234);
199     const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x5678);
200 
init_gatt_db() -> SharedBox<GattDatabase>201     fn init_gatt_db() -> SharedBox<GattDatabase> {
202         let mut gatt_database = GattDatabase::new();
203         register_gatt_service(&mut gatt_database).unwrap();
204         SharedBox::new(gatt_database)
205     }
206 
add_connection( gatt_database: &SharedBox<GattDatabase>, tcb_idx: TransportIndex, ) -> (AttDatabaseImpl, SharedBox<AttServerBearer<AttDatabaseImpl>>, UnboundedReceiver<att::Att>)207     fn add_connection(
208         gatt_database: &SharedBox<GattDatabase>,
209         tcb_idx: TransportIndex,
210     ) -> (AttDatabaseImpl, SharedBox<AttServerBearer<AttDatabaseImpl>>, UnboundedReceiver<att::Att>)
211     {
212         let att_database = gatt_database.get_att_database(tcb_idx);
213         let (tx, rx) = unbounded_channel();
214         let bearer = SharedBox::new(AttServerBearer::new(att_database.clone(), move |packet| {
215             tx.send(packet).unwrap();
216             Ok(())
217         }));
218         gatt_database.on_bearer_ready(tcb_idx, bearer.as_ref());
219         (att_database, bearer, rx)
220     }
221 
222     #[test]
test_gatt_service_discovery()223     fn test_gatt_service_discovery() {
224         // arrange
225         let gatt_db = init_gatt_db();
226         let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
227 
228         // act: discover all services
229         let attrs = att_db.list_attributes();
230 
231         // assert: 1 service + 1 char decl + 1 char value + 1 char descriptor = 4 attrs
232         assert_eq!(attrs.len(), 4);
233         // assert: value handles are correct
234         assert_eq!(attrs[0].handle, GATT_SERVICE_HANDLE);
235         assert_eq!(attrs[2].handle, SERVICE_CHANGE_HANDLE);
236         // assert: types are correct
237         assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
238         assert_eq!(attrs[1].type_, CHARACTERISTIC_UUID);
239         assert_eq!(attrs[2].type_, SERVICE_CHANGE_UUID);
240         assert_eq!(attrs[3].type_, CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
241         // assert: permissions of value attrs are correct
242         assert_eq!(attrs[2].permissions, AttPermissions::INDICATE);
243         assert_eq!(
244             attrs[3].permissions,
245             AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE
246         );
247     }
248 
249     #[test]
test_default_indication_subscription()250     fn test_default_indication_subscription() {
251         // arrange
252         let gatt_db = init_gatt_db();
253         let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
254 
255         // act: try to read the CCC descriptor
256         let resp =
257             block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
258 
259         assert_eq!(
260             Ok(resp),
261             att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
262                 .encode_to_vec()
263         );
264     }
265 
register_for_indication( att_db: &impl AttDatabase, handle: AttHandle, ) -> Result<(), AttErrorCode>266     async fn register_for_indication(
267         att_db: &impl AttDatabase,
268         handle: AttHandle,
269     ) -> Result<(), AttErrorCode> {
270         att_db
271             .write_attribute(
272                 handle,
273                 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
274                     .encode_to_vec()
275                     .unwrap(),
276             )
277             .await
278     }
279 
280     #[test]
test_subscribe_to_indication()281     fn test_subscribe_to_indication() {
282         // arrange
283         let gatt_db = init_gatt_db();
284         let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
285 
286         // act: register for service change indication
287         block_on_locally(register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE))
288             .unwrap();
289         // read our registration status
290         let resp =
291             block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
292 
293         // assert: we are registered for indications
294         assert_eq!(
295             Ok(resp),
296             att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
297                 .encode_to_vec()
298         );
299     }
300 
301     #[test]
test_unsubscribe_to_indication()302     fn test_unsubscribe_to_indication() {
303         // arrange
304         let gatt_db = init_gatt_db();
305         let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
306 
307         // act: register for service change indication
308         block_on_locally(
309             att_db.write_attribute(
310                 SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
311                 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
312                     .encode_to_vec()
313                     .unwrap(),
314             ),
315         )
316         .unwrap();
317         // act: next, unregister from this indication
318         block_on_locally(
319             att_db.write_attribute(
320                 SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
321                 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
322                     .encode_to_vec()
323                     .unwrap(),
324             ),
325         )
326         .unwrap();
327         // read our registration status
328         let resp =
329             block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
330 
331         // assert: we are not registered for indications
332         assert_eq!(
333             Ok(resp),
334             att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
335                 .encode_to_vec()
336         );
337     }
338 
339     #[test]
test_single_registered_service_change_indication()340     fn test_single_registered_service_change_indication() {
341         block_on_locally(async {
342             // arrange
343             let gatt_db = init_gatt_db();
344             let (att_db, _bearer, mut rx) = add_connection(&gatt_db, TCB_IDX);
345             let (gatt_datastore, _) = MockDatastore::new();
346             let gatt_datastore = Rc::new(gatt_datastore);
347             register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
348 
349             // act: register some new service
350             gatt_db
351                 .add_service_with_handles(
352                     GattServiceWithHandle {
353                         handle: AttHandle(15),
354                         type_: SERVICE_TYPE,
355                         characteristics: vec![GattCharacteristicWithHandle {
356                             handle: AttHandle(17),
357                             type_: CHARACTERISTIC_TYPE,
358                             permissions: AttPermissions::empty(),
359                             descriptors: vec![],
360                         }],
361                     },
362                     gatt_datastore,
363                 )
364                 .unwrap();
365 
366             // assert: we received the service change indication
367             let resp = rx.recv().await.unwrap();
368             let Ok(resp): Result<att::AttHandleValueIndication, _> = resp.try_into() else {
369                 unreachable!();
370             };
371             let Ok(resp) = att::GattServiceChanged::decode_full(resp.value.as_slice()) else {
372                 unreachable!();
373             };
374             assert_eq!(resp.start_handle.handle, 15);
375             assert_eq!(resp.end_handle.handle, 17);
376         });
377     }
378 
379     #[test]
test_multiple_registered_service_change_indication()380     fn test_multiple_registered_service_change_indication() {
381         block_on_locally(async {
382             // arrange: two connections, both registered
383             let gatt_db = init_gatt_db();
384             let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
385             let (att_db_2, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
386 
387             register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
388             register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
389 
390             let (gatt_datastore, _) = MockDatastore::new();
391             let gatt_datastore = Rc::new(gatt_datastore);
392 
393             // act: register some new service
394             gatt_db
395                 .add_service_with_handles(
396                     GattServiceWithHandle {
397                         handle: AttHandle(15),
398                         type_: SERVICE_TYPE,
399                         characteristics: vec![GattCharacteristicWithHandle {
400                             handle: AttHandle(17),
401                             type_: CHARACTERISTIC_TYPE,
402                             permissions: AttPermissions::empty(),
403                             descriptors: vec![],
404                         }],
405                     },
406                     gatt_datastore,
407                 )
408                 .unwrap();
409 
410             // assert: both connections received the service change indication
411             let resp1 = rx1.recv().await.unwrap();
412             let resp2 = rx2.recv().await.unwrap();
413             assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
414             assert!(matches!(resp2.try_into(), Ok(att::AttHandleValueIndication { .. })));
415         });
416     }
417 
418     #[test]
test_one_unregistered_service_change_indication()419     fn test_one_unregistered_service_change_indication() {
420         block_on_locally(async {
421             // arrange: two connections, only the first is registered
422             let gatt_db = init_gatt_db();
423             let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
424             let (_, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
425 
426             register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
427 
428             let (gatt_datastore, _) = MockDatastore::new();
429             let gatt_datastore = Rc::new(gatt_datastore);
430 
431             // act: register some new service
432             gatt_db
433                 .add_service_with_handles(
434                     GattServiceWithHandle {
435                         handle: AttHandle(15),
436                         type_: SERVICE_TYPE,
437                         characteristics: vec![GattCharacteristicWithHandle {
438                             handle: AttHandle(17),
439                             type_: CHARACTERISTIC_TYPE,
440                             permissions: AttPermissions::empty(),
441                             descriptors: vec![],
442                         }],
443                     },
444                     gatt_datastore,
445                 )
446                 .unwrap();
447 
448             // assert: the first connection received the service change indication
449             let resp1 = rx1.recv().await.unwrap();
450             assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
451             // assert: the second connection received nothing
452             assert!(try_await(async move { rx2.recv().await }).await.is_err());
453         });
454     }
455 
456     #[test]
test_one_disconnected_service_change_indication()457     fn test_one_disconnected_service_change_indication() {
458         block_on_locally(async {
459             // arrange: two connections, both register, but the second one disconnects
460             let gatt_db = init_gatt_db();
461             let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
462             let (att_db_2, bearer_2, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
463 
464             register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
465             register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
466 
467             drop(bearer_2);
468             gatt_db.on_bearer_dropped(ANOTHER_TCB_IDX);
469 
470             let (gatt_datastore, _) = MockDatastore::new();
471             let gatt_datastore = Rc::new(gatt_datastore);
472 
473             // act: register some new service
474             gatt_db
475                 .add_service_with_handles(
476                     GattServiceWithHandle {
477                         handle: AttHandle(15),
478                         type_: SERVICE_TYPE,
479                         characteristics: vec![GattCharacteristicWithHandle {
480                             handle: AttHandle(17),
481                             type_: CHARACTERISTIC_TYPE,
482                             permissions: AttPermissions::empty(),
483                             descriptors: vec![],
484                         }],
485                     },
486                     gatt_datastore,
487                 )
488                 .unwrap();
489 
490             // assert: the first connection received the service change indication
491             let resp1 = rx1.recv().await.unwrap();
492             assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
493             // assert: the second connection is closed
494             assert!(rx2.recv().await.is_none());
495         });
496     }
497 }
498