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