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