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