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