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