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