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