1 //! FFI interfaces for the GATT module. Some structs are exported so that
2 //! core::init can instantiate and pass them into the main loop.
3 
4 use std::iter::Peekable;
5 
6 use anyhow::{bail, Result};
7 use bt_common::init_flags::always_use_private_gatt_for_debugging_is_enabled;
8 use cxx::UniquePtr;
9 pub use inner::*;
10 use log::{error, info, trace, warn};
11 use tokio::task::spawn_local;
12 
13 use crate::{
14     do_in_rust_thread,
15     packets::{AttAttributeDataChild, AttBuilder, AttErrorCode, Serializable, SerializeError},
16 };
17 
18 use super::{
19     arbiter::with_arbiter,
20     callbacks::{GattWriteRequestType, GattWriteType, TransactionDecision},
21     channel::AttTransport,
22     ids::{AdvertiserId, AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
23     server::{
24         gatt_database::{
25             AttPermissions, GattCharacteristicWithHandle, GattDescriptorWithHandle,
26             GattServiceWithHandle,
27         },
28         IndicationError,
29     },
30     GattCallbacks,
31 };
32 
33 #[cxx::bridge]
34 #[allow(clippy::needless_lifetimes)]
35 #[allow(clippy::too_many_arguments)]
36 #[allow(missing_docs)]
37 #[allow(unsafe_op_in_unsafe_fn)]
38 mod inner {
39     impl UniquePtr<GattServerCallbacks> {}
40 
41     #[namespace = "bluetooth"]
42     extern "C++" {
43         include!("bluetooth/uuid.h");
44         /// A C++ UUID.
45         type Uuid = crate::core::uuid::Uuid;
46     }
47 
48     /// The GATT entity backing the value of a user-controlled
49     /// attribute
50     #[derive(Debug)]
51     #[namespace = "bluetooth::gatt"]
52     enum AttributeBackingType {
53         /// A GATT characteristic
54         #[cxx_name = "CHARACTERISTIC"]
55         Characteristic = 0u32,
56         /// A GATT descriptor
57         #[cxx_name = "DESCRIPTOR"]
58         Descriptor = 1u32,
59     }
60 
61     #[namespace = "bluetooth::gatt"]
62     unsafe extern "C++" {
63         include!("src/gatt/ffi/gatt_shim.h");
64         type AttributeBackingType;
65 
66         /// This contains the callbacks from Rust into C++ JNI needed for GATT
67         type GattServerCallbacks;
68 
69         /// This callback is invoked when reading - the client
70         /// must reply using SendResponse
71         #[cxx_name = "OnServerRead"]
on_server_read( self: &GattServerCallbacks, conn_id: u16, trans_id: u32, attr_handle: u16, attr_type: AttributeBackingType, offset: u32, is_long: bool, )72         fn on_server_read(
73             self: &GattServerCallbacks,
74             conn_id: u16,
75             trans_id: u32,
76             attr_handle: u16,
77             attr_type: AttributeBackingType,
78             offset: u32,
79             is_long: bool,
80         );
81 
82         /// This callback is invoked when writing - the client
83         /// must reply using SendResponse
84         #[cxx_name = "OnServerWrite"]
on_server_write( self: &GattServerCallbacks, conn_id: u16, trans_id: u32, attr_handle: u16, attr_type: AttributeBackingType, offset: u32, need_response: bool, is_prepare: bool, value: &[u8], )85         fn on_server_write(
86             self: &GattServerCallbacks,
87             conn_id: u16,
88             trans_id: u32,
89             attr_handle: u16,
90             attr_type: AttributeBackingType,
91             offset: u32,
92             need_response: bool,
93             is_prepare: bool,
94             value: &[u8],
95         );
96 
97         /// This callback is invoked when executing / cancelling a write
98         #[cxx_name = "OnExecute"]
on_execute(self: &GattServerCallbacks, conn_id: u16, trans_id: u32, execute: bool)99         fn on_execute(self: &GattServerCallbacks, conn_id: u16, trans_id: u32, execute: bool);
100 
101         /// This callback is invoked when an indication has been sent and the
102         /// peer device has confirmed it, or if some error occurred.
103         #[cxx_name = "OnIndicationSentConfirmation"]
on_indication_sent_confirmation(self: &GattServerCallbacks, conn_id: u16, status: i32)104         fn on_indication_sent_confirmation(self: &GattServerCallbacks, conn_id: u16, status: i32);
105     }
106 
107     /// What action the arbiter should take in response to an incoming packet
108     #[namespace = "bluetooth::shim::arbiter"]
109     enum InterceptAction {
110         /// Forward the packet to the legacy stack
111         #[cxx_name = "FORWARD"]
112         Forward = 0u32,
113         /// Discard the packet (typically because it has been intercepted)
114         #[cxx_name = "DROP"]
115         Drop = 1u32,
116     }
117 
118     /// The type of GATT record supplied over FFI
119     #[derive(Debug)]
120     #[namespace = "bluetooth::gatt"]
121     enum GattRecordType {
122         PrimaryService,
123         SecondaryService,
124         IncludedService,
125         Characteristic,
126         Descriptor,
127     }
128 
129     /// An entry in a service definition received from JNI. See GattRecordType
130     /// for possible types.
131     #[namespace = "bluetooth::gatt"]
132     struct GattRecord {
133         uuid: Uuid,
134         record_type: GattRecordType,
135         attribute_handle: u16,
136 
137         properties: u8,
138         extended_properties: u16,
139 
140         permissions: u16,
141     }
142 
143     #[namespace = "bluetooth::shim::arbiter"]
144     unsafe extern "C++" {
145         include!("stack/arbiter/acl_arbiter.h");
146         type InterceptAction;
147 
148         /// Register callbacks from C++ into Rust within the Arbiter
StoreCallbacksFromRust( on_le_connect: fn(tcb_idx: u8, advertiser: u8), on_le_disconnect: fn(tcb_idx: u8), intercept_packet: fn(tcb_idx: u8, packet: Vec<u8>) -> InterceptAction, on_outgoing_mtu_req: fn(tcb_idx: u8), on_incoming_mtu_resp: fn(tcb_idx: u8, mtu: usize), on_incoming_mtu_req: fn(tcb_idx: u8, mtu: usize), )149         fn StoreCallbacksFromRust(
150             on_le_connect: fn(tcb_idx: u8, advertiser: u8),
151             on_le_disconnect: fn(tcb_idx: u8),
152             intercept_packet: fn(tcb_idx: u8, packet: Vec<u8>) -> InterceptAction,
153             on_outgoing_mtu_req: fn(tcb_idx: u8),
154             on_incoming_mtu_resp: fn(tcb_idx: u8, mtu: usize),
155             on_incoming_mtu_req: fn(tcb_idx: u8, mtu: usize),
156         );
157 
158         /// Send an outgoing packet on the specified tcb_idx
SendPacketToPeer(tcb_idx: u8, packet: Vec<u8>)159         fn SendPacketToPeer(tcb_idx: u8, packet: Vec<u8>);
160     }
161 
162     #[namespace = "bluetooth::gatt"]
163     extern "Rust" {
164         // service management
open_server(server_id: u8)165         fn open_server(server_id: u8);
close_server(server_id: u8)166         fn close_server(server_id: u8);
add_service(server_id: u8, service_records: Vec<GattRecord>)167         fn add_service(server_id: u8, service_records: Vec<GattRecord>);
remove_service(server_id: u8, service_handle: u16)168         fn remove_service(server_id: u8, service_handle: u16);
169 
170         // att operations
send_response(server_id: u8, conn_id: u16, trans_id: u32, status: u8, value: &[u8])171         fn send_response(server_id: u8, conn_id: u16, trans_id: u32, status: u8, value: &[u8]);
send_indication(_server_id: u8, handle: u16, conn_id: u16, value: &[u8])172         fn send_indication(_server_id: u8, handle: u16, conn_id: u16, value: &[u8]);
173 
174         // connection
is_connection_isolated(conn_id: u16) -> bool175         fn is_connection_isolated(conn_id: u16) -> bool;
176 
177         // arbitration
associate_server_with_advertiser(server_id: u8, advertiser_id: u8)178         fn associate_server_with_advertiser(server_id: u8, advertiser_id: u8);
clear_advertiser(advertiser_id: u8)179         fn clear_advertiser(advertiser_id: u8);
180     }
181 }
182 
183 /// Implementation of GattCallbacks wrapping the corresponding C++ methods
184 pub struct GattCallbacksImpl(pub UniquePtr<GattServerCallbacks>);
185 
186 impl GattCallbacks for GattCallbacksImpl {
on_server_read( &self, conn_id: ConnectionId, trans_id: TransactionId, handle: AttHandle, attr_type: AttributeBackingType, offset: u32, )187     fn on_server_read(
188         &self,
189         conn_id: ConnectionId,
190         trans_id: TransactionId,
191         handle: AttHandle,
192         attr_type: AttributeBackingType,
193         offset: u32,
194     ) {
195         trace!("on_server_read ({conn_id:?}, {trans_id:?}, {handle:?}, {attr_type:?}, {offset:?}");
196         self.0.as_ref().unwrap().on_server_read(
197             conn_id.0,
198             trans_id.0,
199             handle.0,
200             attr_type,
201             offset,
202             offset != 0,
203         );
204     }
205 
on_server_write( &self, conn_id: ConnectionId, trans_id: TransactionId, handle: AttHandle, attr_type: AttributeBackingType, write_type: GattWriteType, value: &[u8], )206     fn on_server_write(
207         &self,
208         conn_id: ConnectionId,
209         trans_id: TransactionId,
210         handle: AttHandle,
211         attr_type: AttributeBackingType,
212         write_type: GattWriteType,
213         value: &[u8],
214     ) {
215         trace!(
216             "on_server_write ({conn_id:?}, {trans_id:?}, {handle:?}, {attr_type:?}, {write_type:?}"
217         );
218         self.0.as_ref().unwrap().on_server_write(
219             conn_id.0,
220             trans_id.0,
221             handle.0,
222             attr_type,
223             match write_type {
224                 GattWriteType::Request(GattWriteRequestType::Prepare { offset }) => offset,
225                 _ => 0,
226             },
227             matches!(write_type, GattWriteType::Request { .. }),
228             matches!(write_type, GattWriteType::Request(GattWriteRequestType::Prepare { .. })),
229             value,
230         );
231     }
232 
on_indication_sent_confirmation( &self, conn_id: ConnectionId, result: Result<(), IndicationError>, )233     fn on_indication_sent_confirmation(
234         &self,
235         conn_id: ConnectionId,
236         result: Result<(), IndicationError>,
237     ) {
238         trace!("on_indication_sent_confirmation ({conn_id:?}, {result:?}");
239         self.0.as_ref().unwrap().on_indication_sent_confirmation(
240             conn_id.0,
241             match result {
242                 Ok(()) => 0, // GATT_SUCCESS
243                 _ => 133,    // GATT_ERROR
244             },
245         )
246     }
247 
on_execute( &self, conn_id: ConnectionId, trans_id: TransactionId, decision: TransactionDecision, )248     fn on_execute(
249         &self,
250         conn_id: ConnectionId,
251         trans_id: TransactionId,
252         decision: TransactionDecision,
253     ) {
254         trace!("on_execute ({conn_id:?}, {trans_id:?}, {decision:?}");
255         self.0.as_ref().unwrap().on_execute(
256             conn_id.0,
257             trans_id.0,
258             match decision {
259                 TransactionDecision::Execute => true,
260                 TransactionDecision::Cancel => false,
261             },
262         )
263     }
264 }
265 
266 /// Implementation of AttTransport wrapping the corresponding C++ method
267 pub struct AttTransportImpl();
268 
269 impl AttTransport for AttTransportImpl {
send_packet( &self, tcb_idx: TransportIndex, packet: AttBuilder, ) -> Result<(), SerializeError>270     fn send_packet(
271         &self,
272         tcb_idx: TransportIndex,
273         packet: AttBuilder,
274     ) -> Result<(), SerializeError> {
275         SendPacketToPeer(tcb_idx.0, packet.to_vec()?);
276         Ok(())
277     }
278 }
279 
open_server(server_id: u8)280 fn open_server(server_id: u8) {
281     let server_id = ServerId(server_id);
282 
283     do_in_rust_thread(move |modules| {
284         if always_use_private_gatt_for_debugging_is_enabled() {
285             modules
286                 .gatt_module
287                 .get_isolation_manager()
288                 .associate_server_with_advertiser(server_id, AdvertiserId(0))
289         }
290         if let Err(err) = modules.gatt_module.open_gatt_server(server_id) {
291             error!("{err:?}")
292         }
293     })
294 }
295 
close_server(server_id: u8)296 fn close_server(server_id: u8) {
297     let server_id = ServerId(server_id);
298 
299     do_in_rust_thread(move |modules| {
300         if let Err(err) = modules.gatt_module.close_gatt_server(server_id) {
301             error!("{err:?}")
302         }
303     })
304 }
305 
consume_descriptors<'a>( records: &mut Peekable<impl Iterator<Item = &'a GattRecord>>, ) -> Vec<GattDescriptorWithHandle>306 fn consume_descriptors<'a>(
307     records: &mut Peekable<impl Iterator<Item = &'a GattRecord>>,
308 ) -> Vec<GattDescriptorWithHandle> {
309     let mut out = vec![];
310     while let Some(GattRecord { uuid, attribute_handle, permissions, .. }) =
311         records.next_if(|record| record.record_type == GattRecordType::Descriptor)
312     {
313         let mut att_permissions = AttPermissions::empty();
314         att_permissions.set(AttPermissions::READABLE, permissions & 0x01 != 0);
315         att_permissions.set(AttPermissions::WRITABLE_WITH_RESPONSE, permissions & 0x10 != 0);
316 
317         out.push(GattDescriptorWithHandle {
318             handle: AttHandle(*attribute_handle),
319             type_: *uuid,
320             permissions: att_permissions,
321         })
322     }
323     out
324 }
325 
records_to_service(service_records: &[GattRecord]) -> Result<GattServiceWithHandle>326 fn records_to_service(service_records: &[GattRecord]) -> Result<GattServiceWithHandle> {
327     let mut characteristics = vec![];
328     let mut service_handle_uuid = None;
329 
330     let mut service_records = service_records.iter().peekable();
331 
332     while let Some(record) = service_records.next() {
333         match record.record_type {
334             GattRecordType::PrimaryService => {
335                 if service_handle_uuid.is_some() {
336                     bail!("got service registration but with duplicate primary service! {service_records:?}".to_string());
337                 }
338                 service_handle_uuid = Some((record.attribute_handle, record.uuid));
339             }
340             GattRecordType::Characteristic => {
341                 characteristics.push(GattCharacteristicWithHandle {
342                     handle: AttHandle(record.attribute_handle),
343                     type_: record.uuid,
344                     permissions: AttPermissions::from_bits_truncate(record.properties),
345                     descriptors: consume_descriptors(&mut service_records),
346                 });
347             }
348             GattRecordType::Descriptor => {
349                 bail!("Got unexpected descriptor outside of characteristic declaration")
350             }
351             _ => {
352                 warn!("ignoring unsupported database entry of type {:?}", record.record_type)
353             }
354         }
355     }
356 
357     let Some((handle, uuid)) = service_handle_uuid else {
358         bail!(
359             "got service registration but with no primary service! {characteristics:?}".to_string()
360         )
361     };
362 
363     Ok(GattServiceWithHandle { handle: AttHandle(handle), type_: uuid, characteristics })
364 }
365 
add_service(server_id: u8, service_records: Vec<GattRecord>)366 fn add_service(server_id: u8, service_records: Vec<GattRecord>) {
367     // marshal into the form expected by GattModule
368     let server_id = ServerId(server_id);
369 
370     match records_to_service(&service_records) {
371         Ok(service) => {
372             let handle = service.handle;
373             do_in_rust_thread(move |modules| {
374                 let ok = modules.gatt_module.register_gatt_service(
375                     server_id,
376                     service.clone(),
377                     modules.gatt_incoming_callbacks.get_datastore(server_id),
378                 );
379                 match ok {
380                     Ok(_) => info!(
381                         "successfully registered service for server {server_id:?} with handle {handle:?} (service={service:?})"
382                     ),
383                     Err(err) => error!(
384                         "failed to register GATT service for server {server_id:?} with error: {err},  (service={service:?})"
385                     ),
386                 }
387             });
388         }
389         Err(err) => {
390             error!("failed to register service for server {server_id:?}, err: {err:?}")
391         }
392     }
393 }
394 
remove_service(server_id: u8, service_handle: u16)395 fn remove_service(server_id: u8, service_handle: u16) {
396     let server_id = ServerId(server_id);
397     let service_handle = AttHandle(service_handle);
398     do_in_rust_thread(move |modules| {
399         let ok = modules.gatt_module.unregister_gatt_service(server_id, service_handle);
400         match ok {
401             Ok(_) => info!(
402                 "successfully removed service {service_handle:?} for server {server_id:?}"
403             ),
404             Err(err) => error!(
405                 "failed to remove GATT service {service_handle:?} for server {server_id:?} with error: {err}"
406             ),
407         }
408     })
409 }
410 
is_connection_isolated(conn_id: u16) -> bool411 fn is_connection_isolated(conn_id: u16) -> bool {
412     with_arbiter(|arbiter| arbiter.is_connection_isolated(ConnectionId(conn_id).get_tcb_idx()))
413 }
414 
send_response(_server_id: u8, conn_id: u16, trans_id: u32, status: u8, value: &[u8])415 fn send_response(_server_id: u8, conn_id: u16, trans_id: u32, status: u8, value: &[u8]) {
416     // TODO(aryarahul): fixup error codes to allow app-specific values (i.e. don't
417     // make it an enum in PDL)
418     let value = if status == 0 {
419         Ok(value.to_vec())
420     } else {
421         Err(AttErrorCode::try_from(status).unwrap_or(AttErrorCode::UNLIKELY_ERROR))
422     };
423 
424     trace!("send_response {conn_id:?}, {trans_id:?}, {:?}", value.as_ref().err());
425 
426     do_in_rust_thread(move |modules| {
427         match modules.gatt_incoming_callbacks.send_response(
428             ConnectionId(conn_id),
429             TransactionId(trans_id),
430             value,
431         ) {
432             Ok(()) => { /* no-op */ }
433             Err(err) => warn!("{err:?}"),
434         }
435     })
436 }
437 
send_indication(_server_id: u8, handle: u16, conn_id: u16, value: &[u8])438 fn send_indication(_server_id: u8, handle: u16, conn_id: u16, value: &[u8]) {
439     let handle = AttHandle(handle);
440     let conn_id = ConnectionId(conn_id);
441     let value = AttAttributeDataChild::RawData(value.into());
442 
443     trace!("send_indication {handle:?}, {conn_id:?}");
444 
445     do_in_rust_thread(move |modules| {
446         let Some(bearer) = modules.gatt_module.get_bearer(conn_id.get_tcb_idx()) else {
447             error!("connection {conn_id:?} does not exist");
448             return;
449         };
450         let pending_indication = bearer.send_indication(handle, value);
451         let gatt_outgoing_callbacks = modules.gatt_outgoing_callbacks.clone();
452         spawn_local(async move {
453             gatt_outgoing_callbacks
454                 .on_indication_sent_confirmation(conn_id, pending_indication.await);
455         });
456     })
457 }
458 
associate_server_with_advertiser(server_id: u8, advertiser_id: u8)459 fn associate_server_with_advertiser(server_id: u8, advertiser_id: u8) {
460     let server_id = ServerId(server_id);
461     let advertiser_id = AdvertiserId(advertiser_id);
462     do_in_rust_thread(move |modules| {
463         modules
464             .gatt_module
465             .get_isolation_manager()
466             .associate_server_with_advertiser(server_id, advertiser_id);
467     })
468 }
469 
clear_advertiser(advertiser_id: u8)470 fn clear_advertiser(advertiser_id: u8) {
471     let advertiser_id = AdvertiserId(advertiser_id);
472 
473     do_in_rust_thread(move |modules| {
474         modules.gatt_module.get_isolation_manager().clear_advertiser(advertiser_id);
475     })
476 }
477 
478 #[cfg(test)]
479 mod test {
480     use super::*;
481 
482     const SERVICE_HANDLE: AttHandle = AttHandle(1);
483     const SERVICE_UUID: Uuid = Uuid::new(0x1234);
484 
485     const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(2);
486     const CHARACTERISTIC_UUID: Uuid = Uuid::new(0x5678);
487 
488     const DESCRIPTOR_UUID: Uuid = Uuid::new(0x4321);
489     const ANOTHER_DESCRIPTOR_UUID: Uuid = Uuid::new(0x5432);
490 
491     const ANOTHER_CHARACTERISTIC_HANDLE: AttHandle = AttHandle(10);
492     const ANOTHER_CHARACTERISTIC_UUID: Uuid = Uuid::new(0x9ABC);
493 
make_service_record(uuid: Uuid, handle: AttHandle) -> GattRecord494     fn make_service_record(uuid: Uuid, handle: AttHandle) -> GattRecord {
495         GattRecord {
496             uuid,
497             record_type: GattRecordType::PrimaryService,
498             attribute_handle: handle.0,
499             properties: 0,
500             extended_properties: 0,
501             permissions: 0,
502         }
503     }
504 
make_characteristic_record(uuid: Uuid, handle: AttHandle, properties: u8) -> GattRecord505     fn make_characteristic_record(uuid: Uuid, handle: AttHandle, properties: u8) -> GattRecord {
506         GattRecord {
507             uuid,
508             record_type: GattRecordType::Characteristic,
509             attribute_handle: handle.0,
510             properties,
511             extended_properties: 0,
512             permissions: 0,
513         }
514     }
515 
make_descriptor_record(uuid: Uuid, handle: AttHandle, permissions: u16) -> GattRecord516     fn make_descriptor_record(uuid: Uuid, handle: AttHandle, permissions: u16) -> GattRecord {
517         GattRecord {
518             uuid,
519             record_type: GattRecordType::Descriptor,
520             attribute_handle: handle.0,
521             properties: 0,
522             extended_properties: 0,
523             permissions,
524         }
525     }
526 
527     #[test]
test_empty_records()528     fn test_empty_records() {
529         let res = records_to_service(&[]);
530         assert!(res.is_err());
531     }
532 
533     #[test]
test_primary_service()534     fn test_primary_service() {
535         let service =
536             records_to_service(&[make_service_record(SERVICE_UUID, SERVICE_HANDLE)]).unwrap();
537 
538         assert_eq!(service.handle, SERVICE_HANDLE);
539         assert_eq!(service.type_, SERVICE_UUID);
540         assert_eq!(service.characteristics.len(), 0);
541     }
542 
543     #[test]
test_dupe_primary_service()544     fn test_dupe_primary_service() {
545         let res = records_to_service(&[
546             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
547             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
548         ]);
549 
550         assert!(res.is_err());
551     }
552 
553     #[test]
test_service_with_single_characteristic()554     fn test_service_with_single_characteristic() {
555         let service = records_to_service(&[
556             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
557             make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0),
558         ])
559         .unwrap();
560 
561         assert_eq!(service.handle, SERVICE_HANDLE);
562         assert_eq!(service.type_, SERVICE_UUID);
563 
564         assert_eq!(service.characteristics.len(), 1);
565         assert_eq!(service.characteristics[0].handle, CHARACTERISTIC_HANDLE);
566         assert_eq!(service.characteristics[0].type_, CHARACTERISTIC_UUID);
567     }
568 
569     #[test]
test_multiple_characteristics()570     fn test_multiple_characteristics() {
571         let service = records_to_service(&[
572             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
573             make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0),
574             make_characteristic_record(
575                 ANOTHER_CHARACTERISTIC_UUID,
576                 ANOTHER_CHARACTERISTIC_HANDLE,
577                 0,
578             ),
579         ])
580         .unwrap();
581 
582         assert_eq!(service.characteristics.len(), 2);
583         assert_eq!(service.characteristics[0].handle, CHARACTERISTIC_HANDLE);
584         assert_eq!(service.characteristics[0].type_, CHARACTERISTIC_UUID);
585         assert_eq!(service.characteristics[1].handle, ANOTHER_CHARACTERISTIC_HANDLE);
586         assert_eq!(service.characteristics[1].type_, ANOTHER_CHARACTERISTIC_UUID);
587     }
588 
589     #[test]
test_characteristic_readable_property()590     fn test_characteristic_readable_property() {
591         let service = records_to_service(&[
592             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
593             make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x02),
594         ])
595         .unwrap();
596 
597         assert_eq!(service.characteristics[0].permissions, AttPermissions::READABLE);
598     }
599 
600     #[test]
test_characteristic_writable_property()601     fn test_characteristic_writable_property() {
602         let service = records_to_service(&[
603             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
604             make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x08),
605         ])
606         .unwrap();
607 
608         assert_eq!(service.characteristics[0].permissions, AttPermissions::WRITABLE_WITH_RESPONSE);
609     }
610 
611     #[test]
test_characteristic_readable_and_writable_property()612     fn test_characteristic_readable_and_writable_property() {
613         let service = records_to_service(&[
614             make_service_record(SERVICE_UUID, SERVICE_HANDLE),
615             make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x02 | 0x08),
616         ])
617         .unwrap();
618 
619         assert_eq!(
620             service.characteristics[0].permissions,
621             AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE
622         );
623     }
624 
625     #[test]
test_multiple_descriptors()626     fn test_multiple_descriptors() {
627         let service = records_to_service(&[
628             make_service_record(SERVICE_UUID, AttHandle(1)),
629             make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
630             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
631             make_descriptor_record(ANOTHER_DESCRIPTOR_UUID, AttHandle(4), 0),
632         ])
633         .unwrap();
634 
635         assert_eq!(service.characteristics[0].descriptors.len(), 2);
636         assert_eq!(service.characteristics[0].descriptors[0].handle, AttHandle(3));
637         assert_eq!(service.characteristics[0].descriptors[0].type_, DESCRIPTOR_UUID);
638         assert_eq!(service.characteristics[0].descriptors[1].handle, AttHandle(4));
639         assert_eq!(service.characteristics[0].descriptors[1].type_, ANOTHER_DESCRIPTOR_UUID);
640     }
641 
642     #[test]
test_descriptor_permissions()643     fn test_descriptor_permissions() {
644         let service = records_to_service(&[
645             make_service_record(SERVICE_UUID, AttHandle(1)),
646             make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
647             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0x01),
648             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(4), 0x10),
649             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(5), 0x11),
650         ])
651         .unwrap();
652 
653         assert_eq!(service.characteristics[0].descriptors[0].permissions, AttPermissions::READABLE);
654         assert_eq!(
655             service.characteristics[0].descriptors[1].permissions,
656             AttPermissions::WRITABLE_WITH_RESPONSE
657         );
658         assert_eq!(
659             service.characteristics[0].descriptors[2].permissions,
660             AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE
661         );
662     }
663 
664     #[test]
test_descriptors_multiple_characteristics()665     fn test_descriptors_multiple_characteristics() {
666         let service = records_to_service(&[
667             make_service_record(SERVICE_UUID, AttHandle(1)),
668             make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
669             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
670             make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(4), 0),
671             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(5), 0),
672         ])
673         .unwrap();
674 
675         assert_eq!(service.characteristics[0].descriptors.len(), 1);
676         assert_eq!(service.characteristics[0].descriptors[0].handle, AttHandle(3));
677         assert_eq!(service.characteristics[1].descriptors.len(), 1);
678         assert_eq!(service.characteristics[1].descriptors[0].handle, AttHandle(5));
679     }
680 
681     #[test]
test_unexpected_descriptor()682     fn test_unexpected_descriptor() {
683         let res = records_to_service(&[
684             make_service_record(SERVICE_UUID, AttHandle(1)),
685             make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
686         ]);
687 
688         assert!(res.is_err());
689     }
690 }
691