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