1use core::cell::RefCell;
3use core::fmt;
4use core::marker::PhantomData;
5
6use bt_hci::uuid::declarations::{CHARACTERISTIC, INCLUDE, PRIMARY_SERVICE, SECONDARY_SERVICE};
7use bt_hci::uuid::descriptors::CLIENT_CHARACTERISTIC_CONFIGURATION;
8use embassy_sync::blocking_mutex::raw::RawMutex;
9use embassy_sync::blocking_mutex::Mutex;
10use heapless::Vec;
11
12use crate::att::{AttErrorCode, AttUns};
13use crate::attribute_server::AttributeServer;
14use crate::cursor::ReadCursor;
15use crate::prelude::{AsGatt, FixedGattValue, FromGatt, GattConnection, SecurityLevel};
16use crate::types::gatt_traits::FromGattError;
17pub use crate::types::uuid::Uuid;
18use crate::{gatt, Error, PacketPool, MAX_INVALID_DATA_LEN};
19
20#[derive(Debug, Clone, Copy)]
22#[repr(u8)]
23pub enum CharacteristicProp {
24 Broadcast = 0x01,
26 Read = 0x02,
28 WriteWithoutResponse = 0x04,
30 Write = 0x08,
32 Notify = 0x10,
34 Indicate = 0x20,
36 AuthenticatedWrite = 0x40,
38 Extended = 0x80,
40}
41
42#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45pub enum PermissionLevel {
46 #[default]
47 Allowed,
49 EncryptionRequired,
51 AuthenticationRequired,
53 NotAllowed,
55}
56
57#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
59#[cfg_attr(feature = "defmt", derive(defmt::Format))]
60pub struct AttPermissions {
61 pub read: PermissionLevel,
63 pub write: PermissionLevel,
65}
66
67impl AttPermissions {
68 pub(crate) fn can_read(&self, level: SecurityLevel) -> Result<(), AttErrorCode> {
69 match self.read {
70 PermissionLevel::NotAllowed => Err(AttErrorCode::READ_NOT_PERMITTED),
71 PermissionLevel::EncryptionRequired | PermissionLevel::AuthenticationRequired
72 if level < SecurityLevel::Encrypted =>
73 {
74 Err(AttErrorCode::INSUFFICIENT_ENCRYPTION)
75 }
76 PermissionLevel::AuthenticationRequired if level < SecurityLevel::EncryptedAuthenticated => {
77 Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION)
78 }
79 _ => Ok(()),
80 }
81 }
82
83 pub(crate) fn can_write(&self, level: SecurityLevel) -> Result<(), AttErrorCode> {
84 match self.write {
85 PermissionLevel::NotAllowed => Err(AttErrorCode::WRITE_NOT_PERMITTED),
86 PermissionLevel::EncryptionRequired | PermissionLevel::AuthenticationRequired
87 if level < SecurityLevel::Encrypted =>
88 {
89 Err(AttErrorCode::INSUFFICIENT_ENCRYPTION)
90 }
91 PermissionLevel::AuthenticationRequired if level < SecurityLevel::EncryptedAuthenticated => {
92 Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION)
93 }
94 _ => Ok(()),
95 }
96 }
97}
98
99pub struct Attribute<'a> {
101 pub(crate) uuid: Uuid,
102 pub(crate) data: AttributeData<'a>,
103}
104
105impl<'a> Attribute<'a> {
106 const EMPTY: Option<Attribute<'a>> = None;
107
108 pub(crate) fn read(&self, offset: usize, data: &mut [u8]) -> Result<usize, AttErrorCode> {
109 self.data.read(offset, data)
110 }
111
112 pub(crate) fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> {
113 self.data.write(offset, data)
114 }
115
116 pub(crate) fn permissions(&self) -> AttPermissions {
117 self.data.permissions()
118 }
119}
120
121#[derive(Debug, PartialEq, Eq)]
122pub(crate) enum AttributeData<'d> {
123 Service {
124 uuid: Uuid,
125 last_handle_in_group: u16,
126 },
127 IncludedService {
128 handle: u16,
129 last_handle_in_group: u16,
130 uuid: Option<[u8; 2]>,
131 },
132 ReadOnlyData {
133 permissions: AttPermissions,
134 value: &'d [u8],
135 },
136 Data {
137 permissions: AttPermissions,
138 variable_len: bool,
139 len: u16,
140 value: &'d mut [u8],
141 },
142 SmallData {
143 permissions: AttPermissions,
144 variable_len: bool,
145 capacity: u8,
146 len: u8,
147 value: [u8; 8],
148 },
149 Declaration {
150 props: CharacteristicProps,
151 handle: u16,
152 uuid: Uuid,
153 },
154 Cccd {
155 notifications: bool,
156 indications: bool,
157 write_permission: PermissionLevel,
158 },
159}
160
161impl AttributeData<'_> {
162 pub(crate) fn permissions(&self) -> AttPermissions {
163 match self {
164 AttributeData::Service { .. }
165 | AttributeData::IncludedService { .. }
166 | AttributeData::Declaration { .. } => AttPermissions {
167 read: PermissionLevel::Allowed,
168 write: PermissionLevel::NotAllowed,
169 },
170 AttributeData::ReadOnlyData { permissions, .. }
171 | AttributeData::Data { permissions, .. }
172 | AttributeData::SmallData { permissions, .. } => *permissions,
173 AttributeData::Cccd { write_permission, .. } => AttPermissions {
174 read: PermissionLevel::Allowed,
175 write: *write_permission,
176 },
177 }
178 }
179
180 pub(crate) fn readable(&self) -> bool {
181 self.permissions().read != PermissionLevel::NotAllowed
182 }
183
184 pub(crate) fn writable(&self) -> bool {
185 self.permissions().write != PermissionLevel::NotAllowed
186 }
187
188 fn read(&self, mut offset: usize, mut data: &mut [u8]) -> Result<usize, AttErrorCode> {
189 fn append(src: &[u8], offset: &mut usize, dest: &mut &mut [u8]) -> usize {
190 if *offset >= src.len() {
191 *offset -= src.len();
192 0
193 } else {
194 let d = core::mem::take(dest);
195 let n = d.len().min(src.len() - *offset);
196 d[..n].copy_from_slice(&src[*offset..][..n]);
197 *dest = &mut d[n..];
198 *offset = 0;
199 n
200 }
201 }
202
203 let written = match self {
204 Self::ReadOnlyData { value, .. } => append(value, &mut offset, &mut data),
205 Self::Data { len, value, .. } => {
206 let value = &value[..*len as usize];
207 append(value, &mut offset, &mut data)
208 }
209 Self::SmallData { len, value, .. } => {
210 let value = &value[..*len as usize];
211 append(value, &mut offset, &mut data)
212 }
213 Self::Service { uuid, .. } => {
214 let val = uuid.as_raw();
215 append(val, &mut offset, &mut data)
216 }
217 Self::IncludedService {
218 handle,
219 last_handle_in_group,
220 uuid,
221 } => {
222 let written = append(&handle.to_le_bytes(), &mut offset, &mut data)
223 + append(&last_handle_in_group.to_le_bytes(), &mut offset, &mut data);
224 if let Some(uuid) = uuid {
225 written + append(uuid, &mut offset, &mut data)
226 } else {
227 written
228 }
229 }
230 Self::Cccd {
231 notifications,
232 indications,
233 ..
234 } => {
235 let mut v = 0u16;
236 if *notifications {
237 v |= 0x01;
238 }
239
240 if *indications {
241 v |= 0x02;
242 }
243 append(&v.to_le_bytes(), &mut offset, &mut data)
244 }
245 Self::Declaration { props, handle, uuid } => {
246 let val = uuid.as_raw();
247 append(&[props.0], &mut offset, &mut data)
248 + append(&handle.to_le_bytes(), &mut offset, &mut data)
249 + append(val, &mut offset, &mut data)
250 }
251 };
252
253 if offset > 0 {
254 Err(AttErrorCode::INVALID_OFFSET)
255 } else {
256 Ok(written)
257 }
258 }
259
260 fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> {
261 match self {
262 Self::Data {
263 value,
264 variable_len,
265 len,
266 ..
267 } => {
268 if offset + data.len() <= value.len() {
269 value[offset..offset + data.len()].copy_from_slice(data);
270 if *variable_len {
271 *len = (offset + data.len()) as u16;
272 }
273 Ok(())
274 } else {
275 Err(AttErrorCode::INVALID_OFFSET)
276 }
277 }
278 Self::SmallData {
279 variable_len,
280 capacity,
281 len,
282 value,
283 ..
284 } => {
285 if offset + data.len() <= *capacity as usize {
286 value[offset..offset + data.len()].copy_from_slice(data);
287 if *variable_len {
288 *len = (offset + data.len()) as u8;
289 }
290 Ok(())
291 } else {
292 Err(AttErrorCode::INVALID_OFFSET)
293 }
294 }
295 Self::Cccd {
296 notifications,
297 indications,
298 ..
299 } => {
300 if offset > 0 {
301 return Err(AttErrorCode::INVALID_OFFSET);
302 }
303
304 if data.is_empty() {
305 return Err(AttErrorCode::UNLIKELY_ERROR);
306 }
307
308 *notifications = data[0] & 0x01 != 0;
309 *indications = data[0] & 0x02 != 0;
310 Ok(())
311 }
312 _ => Err(AttErrorCode::WRITE_NOT_PERMITTED),
313 }
314 }
315
316 pub(crate) fn decode_declaration(data: &[u8]) -> Result<Self, Error> {
317 let mut r = ReadCursor::new(data);
318 Ok(Self::Declaration {
319 props: CharacteristicProps(r.read()?),
320 handle: r.read()?,
321 uuid: Uuid::try_from(r.remaining())?,
322 })
323 }
324}
325
326impl fmt::Debug for Attribute<'_> {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("Attribute")
329 .field("uuid", &self.uuid)
330 .field("readable", &self.data.readable())
331 .field("writable", &self.data.writable())
332 .finish()
333 }
334}
335
336#[cfg(feature = "defmt")]
337impl<'a> defmt::Format for Attribute<'a> {
338 fn format(&self, fmt: defmt::Formatter) {
339 defmt::write!(fmt, "{}", defmt::Debug2Format(self))
340 }
341}
342
343impl<'a> Attribute<'a> {
344 pub(crate) fn new(uuid: Uuid, data: AttributeData<'a>) -> Attribute<'a> {
345 Attribute { uuid, data }
346 }
347}
348
349pub struct AttributeTable<'d, M: RawMutex, const MAX: usize> {
351 inner: Mutex<M, RefCell<InnerTable<'d, MAX>>>,
352}
353
354pub(crate) struct InnerTable<'d, const MAX: usize> {
355 attributes: Vec<Attribute<'d>, MAX>,
356}
357
358impl<'d, const MAX: usize> InnerTable<'d, MAX> {
359 fn push(&mut self, attribute: Attribute<'d>) -> u16 {
360 let handle = self.next_handle();
361 self.attributes.push(attribute).unwrap();
362 handle
363 }
364
365 fn next_handle(&self) -> u16 {
366 self.attributes.len() as u16 + 1
367 }
368}
369
370impl<M: RawMutex, const MAX: usize> Default for AttributeTable<'_, M, MAX> {
371 fn default() -> Self {
372 Self::new()
373 }
374}
375
376impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> {
377 pub fn new() -> Self {
379 Self {
380 inner: Mutex::new(RefCell::new(InnerTable { attributes: Vec::new() })),
381 }
382 }
383
384 pub(crate) fn with_inner<F: FnOnce(&mut InnerTable<'d, MAX>) -> R, R>(&self, f: F) -> R {
385 self.inner.lock(|inner| {
386 let mut table = inner.borrow_mut();
387 f(&mut table)
388 })
389 }
390
391 pub(crate) fn iterate<F: FnOnce(AttributeIterator<'_, 'd>) -> R, R>(&self, f: F) -> R {
392 self.with_inner(|table| {
393 let it = AttributeIterator {
394 attributes: table.attributes.as_mut_slice(),
395 pos: 0,
396 };
397 f(it)
398 })
399 }
400
401 pub(crate) fn with_attribute<F: FnOnce(&mut Attribute<'d>) -> R, R>(&self, handle: u16, f: F) -> Option<R> {
402 if handle == 0 {
403 return None;
404 }
405
406 self.with_inner(|table| {
407 let i = usize::from(handle) - 1;
408 table.attributes.get_mut(i).map(f)
409 })
410 }
411
412 pub(crate) fn iterate_from<F: FnOnce(AttributeIterator<'_, 'd>) -> R, R>(&self, start: u16, f: F) -> R {
413 self.with_inner(|table| {
414 let it = AttributeIterator {
415 attributes: &mut table.attributes[..],
416 pos: usize::from(start).saturating_sub(1),
417 };
418 f(it)
419 })
420 }
421
422 fn push(&mut self, attribute: Attribute<'d>) -> u16 {
423 self.with_inner(|table| table.push(attribute))
424 }
425
426 pub fn add_service(&mut self, service: Service) -> ServiceBuilder<'_, 'd, M, MAX> {
428 let handle = self.push(Attribute {
429 uuid: PRIMARY_SERVICE.into(),
430 data: AttributeData::Service {
431 uuid: service.uuid,
432 last_handle_in_group: 0,
433 },
434 });
435 ServiceBuilder { handle, table: self }
436 }
437
438 pub fn add_secondary_service(&mut self, service: Service) -> ServiceBuilder<'_, 'd, M, MAX> {
440 let handle = self.push(Attribute {
441 uuid: SECONDARY_SERVICE.into(),
442 data: AttributeData::Service {
443 uuid: service.uuid,
444 last_handle_in_group: 0,
445 },
446 });
447 ServiceBuilder { handle, table: self }
448 }
449
450 pub fn permissions(&self, attribute: u16) -> Option<AttPermissions> {
454 self.with_attribute(attribute, |att| att.data.permissions())
455 }
456
457 pub fn uuid(&self, attribute: u16) -> Option<Uuid> {
461 self.with_attribute(attribute, |att| att.uuid.clone())
462 }
463
464 pub(crate) fn set_ro(&self, attribute: u16, new_value: &'d [u8]) -> Result<(), Error> {
465 self.with_attribute(attribute, |att| match &mut att.data {
466 AttributeData::ReadOnlyData { value, .. } => {
467 *value = new_value;
468 Ok(())
469 }
470 _ => Err(Error::NotSupported),
471 })
472 .unwrap_or(Err(Error::NotFound))
473 }
474
475 pub fn read(&self, attribute: u16, offset: usize, data: &mut [u8]) -> Result<usize, Error> {
482 self.with_attribute(attribute, |att| att.read(offset, data).map_err(Into::into))
483 .unwrap_or(Err(Error::NotFound))
484 }
485
486 pub fn write(&self, attribute: u16, offset: usize, data: &[u8]) -> Result<(), Error> {
492 self.with_attribute(attribute, |att| att.write(offset, data).map_err(Into::into))
493 .unwrap_or(Err(Error::NotFound))
494 }
495
496 pub(crate) fn set_raw(&self, attribute: u16, input: &[u8]) -> Result<(), Error> {
497 self.with_attribute(attribute, |att| match &mut att.data {
498 AttributeData::Data {
499 value,
500 variable_len,
501 len,
502 ..
503 } => {
504 let expected_len = value.len();
505 let actual_len = input.len();
506
507 if expected_len == actual_len {
508 value.copy_from_slice(input);
509 Ok(())
510 } else if *variable_len && actual_len <= expected_len {
511 value[..input.len()].copy_from_slice(input);
512 *len = input.len() as u16;
513 Ok(())
514 } else {
515 Err(Error::UnexpectedDataLength {
516 expected: expected_len,
517 actual: actual_len,
518 })
519 }
520 }
521 AttributeData::SmallData {
522 variable_len,
523 capacity,
524 len,
525 value,
526 ..
527 } => {
528 let expected_len = usize::from(*capacity);
529 let actual_len = input.len();
530
531 if expected_len == actual_len {
532 value[..expected_len].copy_from_slice(input);
533 Ok(())
534 } else if *variable_len && actual_len <= expected_len {
535 value[..input.len()].copy_from_slice(input);
536 *len = input.len() as u8;
537 Ok(())
538 } else {
539 Err(Error::UnexpectedDataLength {
540 expected: expected_len,
541 actual: actual_len,
542 })
543 }
544 }
545 _ => Err(Error::NotSupported),
546 })
547 .unwrap_or(Err(Error::NotFound))
548 }
549
550 pub fn len(&self) -> usize {
552 self.with_inner(|table| table.attributes.len())
553 }
554
555 pub fn is_empty(&self) -> bool {
557 self.with_inner(|table| table.attributes.is_empty())
558 }
559
560 pub fn set<T: AttributeHandle>(&self, attribute_handle: &T, input: &T::Value) -> Result<(), Error> {
568 let gatt_value = input.as_gatt();
569 self.set_raw(attribute_handle.handle(), gatt_value)
570 }
571
572 pub fn get<T: AttributeHandle<Value = V>, V: FromGatt>(&self, attribute_handle: &T) -> Result<T::Value, Error> {
578 self.with_attribute(attribute_handle.handle(), |att| {
579 let value_slice = match &mut att.data {
580 AttributeData::Data { value, len, .. } => &value[..*len as usize],
581 AttributeData::ReadOnlyData { value, .. } => value,
582 AttributeData::SmallData { len, value, .. } => &value[..usize::from(*len)],
583 _ => return Err(Error::NotSupported),
584 };
585
586 T::Value::from_gatt(value_slice).map_err(|_| {
587 let mut invalid_data = [0u8; MAX_INVALID_DATA_LEN];
588 let len_to_copy = value_slice.len().min(MAX_INVALID_DATA_LEN);
589 invalid_data[..len_to_copy].copy_from_slice(&value_slice[..len_to_copy]);
590
591 Error::CannotConstructGattValue(invalid_data)
592 })
593 })
594 .unwrap_or(Err(Error::NotFound))
595 }
596
597 pub fn find_characteristic_by_value_handle<T: AsGatt>(&self, handle: u16) -> Result<Characteristic<T>, Error> {
601 if handle == 0 {
602 return Err(Error::NotFound);
603 }
604
605 self.iterate_from(handle - 1, |mut it| {
606 if let Some((_, att)) = it.next() {
607 if let AttributeData::Declaration { props, .. } = att.data {
608 if it.next().is_some() {
609 let cccd_handle = it
610 .next()
611 .and_then(|(handle, att)| matches!(att.data, AttributeData::Cccd { .. }).then_some(handle));
612
613 return Ok(Characteristic {
614 handle,
615 cccd_handle,
616 props,
617 phantom: PhantomData,
618 });
619 }
620 }
621 }
622 Err(Error::NotFound)
623 })
624 }
625
626 #[cfg(feature = "security")]
627 pub fn hash(&self) -> u128 {
631 use bt_hci::uuid::*;
632
633 use crate::security_manager::crypto::AesCmac;
634
635 const PRIMARY_SERVICE: Uuid = Uuid::Uuid16(declarations::PRIMARY_SERVICE.to_le_bytes());
636 const SECONDARY_SERVICE: Uuid = Uuid::Uuid16(declarations::SECONDARY_SERVICE.to_le_bytes());
637 const INCLUDED_SERVICE: Uuid = Uuid::Uuid16(declarations::INCLUDE.to_le_bytes());
638 const CHARACTERISTIC: Uuid = Uuid::Uuid16(declarations::CHARACTERISTIC.to_le_bytes());
639 const CHARACTERISTIC_EXTENDED_PROPERTIES: Uuid =
640 Uuid::Uuid16(descriptors::CHARACTERISTIC_EXTENDED_PROPERTIES.to_le_bytes());
641
642 const CHARACTERISTIC_USER_DESCRIPTION: Uuid =
643 Uuid::Uuid16(descriptors::CHARACTERISTIC_USER_DESCRIPTION.to_le_bytes());
644 const CLIENT_CHARACTERISTIC_CONFIGURATION: Uuid =
645 Uuid::Uuid16(descriptors::CLIENT_CHARACTERISTIC_CONFIGURATION.to_le_bytes());
646 const SERVER_CHARACTERISTIC_CONFIGURATION: Uuid =
647 Uuid::Uuid16(descriptors::SERVER_CHARACTERISTIC_CONFIGURATION.to_le_bytes());
648 const CHARACTERISTIC_PRESENTATION_FORMAT: Uuid =
649 Uuid::Uuid16(descriptors::CHARACTERISTIC_PRESENTATION_FORMAT.to_le_bytes());
650 const CHARACTERISTIC_AGGREGATE_FORMAT: Uuid =
651 Uuid::Uuid16(descriptors::CHARACTERISTIC_AGGREGATE_FORMAT.to_le_bytes());
652
653 let mut mac = AesCmac::db_hash();
654
655 self.iterate(|mut it| {
656 while let Some((handle, att)) = it.next() {
657 match att.uuid {
658 PRIMARY_SERVICE
659 | SECONDARY_SERVICE
660 | INCLUDED_SERVICE
661 | CHARACTERISTIC
662 | CHARACTERISTIC_EXTENDED_PROPERTIES => {
663 mac.update(handle.to_le_bytes()).update(att.uuid.as_raw());
664 match &att.data {
665 AttributeData::ReadOnlyData { value, .. } => {
666 mac.update(value);
667 }
668 AttributeData::Data { len, value, .. } => {
669 mac.update(&value[..usize::from(*len)]);
670 }
671 AttributeData::Service { uuid, .. } => {
672 mac.update(uuid.as_raw());
673 }
674 AttributeData::Declaration { props, handle, uuid } => {
675 mac.update([props.0]).update(handle.to_le_bytes()).update(uuid.as_raw());
676 }
677 _ => unreachable!(),
678 }
679 }
680 CHARACTERISTIC_USER_DESCRIPTION
681 | CLIENT_CHARACTERISTIC_CONFIGURATION
682 | SERVER_CHARACTERISTIC_CONFIGURATION
683 | CHARACTERISTIC_PRESENTATION_FORMAT
684 | CHARACTERISTIC_AGGREGATE_FORMAT => {
685 mac.update(handle.to_le_bytes()).update(att.uuid.as_raw());
686 }
687 _ => {}
688 }
689 }
690 });
691
692 mac.finalize()
693 }
694}
695
696pub trait AttributeHandle {
698 type Value: AsGatt;
700
701 fn handle(&self) -> u16;
703}
704
705impl<T: AsGatt> AttributeHandle for Characteristic<T> {
706 type Value = T;
707
708 fn handle(&self) -> u16 {
709 self.handle
710 }
711}
712
713#[derive(Debug, Clone, PartialEq)]
715#[cfg_attr(feature = "defmt", derive(defmt::Format))]
716pub struct InvalidHandle;
717
718impl core::fmt::Display for InvalidHandle {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 core::fmt::Debug::fmt(self, f)
721 }
722}
723
724impl core::error::Error for InvalidHandle {}
725
726impl From<InvalidHandle> for Error {
727 fn from(value: InvalidHandle) -> Self {
728 Error::InvalidValue
729 }
730}
731
732pub struct ServiceBuilder<'r, 'd, M: RawMutex, const MAX: usize> {
734 handle: u16,
735 table: &'r mut AttributeTable<'d, M, MAX>,
736}
737
738impl<'d, M: RawMutex, const MAX: usize> ServiceBuilder<'_, 'd, M, MAX> {
739 fn add_characteristic_internal<T: AsGatt + ?Sized>(
740 &mut self,
741 uuid: Uuid,
742 props: CharacteristicProps,
743 data: AttributeData<'d>,
744 ) -> CharacteristicBuilder<'_, 'd, T, M, MAX> {
745 let (handle, cccd_handle) = self.table.with_inner(|table| {
747 let value_handle = table.next_handle() + 1;
748 table.push(Attribute {
749 uuid: CHARACTERISTIC.into(),
750 data: AttributeData::Declaration {
751 props,
752 handle: value_handle,
753 uuid: uuid.clone(),
754 },
755 });
756
757 let h = table.push(Attribute { uuid, data });
759 debug_assert!(h == value_handle);
760
761 let cccd_handle = if props.has_cccd() {
763 let handle = table.push(Attribute {
764 uuid: CLIENT_CHARACTERISTIC_CONFIGURATION.into(),
765 data: AttributeData::Cccd {
766 notifications: false,
767 indications: false,
768 write_permission: PermissionLevel::Allowed,
769 },
770 });
771
772 Some(handle)
773 } else {
774 None
775 };
776
777 (value_handle, cccd_handle)
778 });
779
780 CharacteristicBuilder {
781 handle: Characteristic {
782 handle,
783 cccd_handle,
784 props,
785 phantom: PhantomData,
786 },
787 table: self.table,
788 }
789 }
790
791 pub fn add_characteristic<T: AsGatt, U: Into<Uuid>, P: Into<CharacteristicProps>>(
793 &mut self,
794 uuid: U,
795 props: P,
796 value: T,
797 store: &'d mut [u8],
798 ) -> CharacteristicBuilder<'_, 'd, T, M, MAX> {
799 let props: CharacteristicProps = props.into();
800 let permissions = props.default_permissions();
801 let bytes = value.as_gatt();
802 store[..bytes.len()].copy_from_slice(bytes);
803 let variable_len = T::MAX_SIZE != T::MIN_SIZE;
804 let len = bytes.len() as u16;
805 self.add_characteristic_internal(
806 uuid.into(),
807 props,
808 AttributeData::Data {
809 permissions,
810 value: store,
811 variable_len,
812 len,
813 },
814 )
815 }
816
817 pub fn add_characteristic_small<T: AsGatt, U: Into<Uuid>, P: Into<CharacteristicProps>>(
819 &mut self,
820 uuid: U,
821 props: P,
822 value: T,
823 ) -> CharacteristicBuilder<'_, 'd, T, M, MAX> {
824 assert!(T::MIN_SIZE <= 8);
825
826 let props: CharacteristicProps = props.into();
827 let permissions = props.default_permissions();
828 let bytes = value.as_gatt();
829 assert!(bytes.len() <= 8);
830 let mut value = [0; 8];
831 value[..bytes.len()].copy_from_slice(bytes);
832 let variable_len = T::MAX_SIZE != T::MIN_SIZE;
833 let capacity = T::MAX_SIZE.min(8) as u8;
834 let len = bytes.len() as u8;
835 self.add_characteristic_internal(
836 uuid.into(),
837 props,
838 AttributeData::SmallData {
839 permissions,
840 variable_len,
841 capacity,
842 len,
843 value,
844 },
845 )
846 }
847
848 pub fn add_characteristic_ro<T: AsGatt + ?Sized, U: Into<Uuid>>(
850 &mut self,
851 uuid: U,
852 value: &'d T,
853 ) -> CharacteristicBuilder<'_, 'd, T, M, MAX> {
854 let props: CharacteristicProps = [CharacteristicProp::Read].into();
855 let permissions = props.default_permissions();
856 self.add_characteristic_internal(
857 uuid.into(),
858 props,
859 AttributeData::ReadOnlyData {
860 permissions,
861 value: value.as_gatt(),
862 },
863 )
864 }
865
866 pub fn add_included_service(&mut self, handle: u16) -> Result<u16, InvalidHandle> {
868 self.table.with_inner(|table| {
869 if handle > 0 && table.attributes.len() >= usize::from(handle) {
870 if let AttributeData::Service {
871 uuid,
872 last_handle_in_group,
873 } = &table.attributes[usize::from(handle) - 1].data
874 {
875 let uuid = match uuid {
877 Uuid::Uuid16(uuid) => Some(*uuid),
878 Uuid::Uuid128(_) => None,
879 };
880
881 Ok(table.push(Attribute {
882 uuid: INCLUDE.into(),
883 data: AttributeData::IncludedService {
884 handle,
885 last_handle_in_group: *last_handle_in_group,
886 uuid,
887 },
888 }))
889 } else {
890 Err(InvalidHandle)
891 }
892 } else {
893 Err(InvalidHandle)
894 }
895 })
896 }
897
898 pub fn build(self) -> u16 {
900 self.handle
901 }
902}
903
904impl<M: RawMutex, const MAX: usize> Drop for ServiceBuilder<'_, '_, M, MAX> {
905 fn drop(&mut self) {
906 self.table.with_inner(|inner| {
907 let last_handle = inner.next_handle() - 1;
908
909 let i = usize::from(self.handle - 1);
910 let AttributeData::Service {
911 last_handle_in_group, ..
912 } = &mut inner.attributes[i].data
913 else {
914 unreachable!()
915 };
916
917 *last_handle_in_group = last_handle;
918 });
919 }
920}
921
922#[cfg_attr(feature = "defmt", derive(defmt::Format))]
924#[derive(Clone, Copy, Debug, PartialEq)]
925pub struct Characteristic<T: AsGatt + ?Sized> {
926 pub cccd_handle: Option<u16>,
928 pub handle: u16,
930 pub props: CharacteristicProps,
932 pub(crate) phantom: PhantomData<T>,
933}
934
935impl<T: AsGatt + ?Sized> Characteristic<T> {
936 pub async fn notify<P: PacketPool>(&self, connection: &GattConnection<'_, '_, P>, value: &T) -> Result<(), Error> {
942 let value = value.as_gatt();
943 let server = connection.server;
944 server.set(self.handle, value)?;
945
946 let cccd_handle = self.cccd_handle.ok_or(Error::NotFound)?;
947 let connection = connection.raw();
948 if !server.should_notify(connection, cccd_handle) {
949 return Ok(());
951 }
952
953 let uns = AttUns::Notify {
954 handle: self.handle,
955 data: value,
956 };
957 let pdu = gatt::assemble(connection, crate::att::AttServer::Unsolicited(uns))?;
958 connection.send(pdu).await;
959 Ok(())
960 }
961
962 pub async fn indicate<P: PacketPool>(
971 &self,
972 connection: &GattConnection<'_, '_, P>,
973 value: &T,
974 ) -> Result<(), Error> {
975 let value = value.as_gatt();
976 let server = connection.server;
977 server.set(self.handle, value)?;
978
979 let cccd_handle = self.cccd_handle.ok_or(Error::NotFound)?;
980 let connection = connection.raw();
981 if !server.should_indicate(connection, cccd_handle) {
982 return Ok(());
984 }
985
986 let uns = AttUns::Indicate {
987 handle: self.handle,
988 data: value,
989 };
990 let pdu = gatt::assemble(connection, crate::att::AttServer::Unsolicited(uns))?;
991 connection.send(pdu).await;
992 Ok(())
993 }
994
995 pub fn set<M: RawMutex, P: PacketPool, const AT: usize, const CT: usize, const CN: usize>(
997 &self,
998 server: &AttributeServer<'_, M, P, AT, CT, CN>,
999 value: &T,
1000 ) -> Result<(), Error> {
1001 let value = value.as_gatt();
1002 server.table().set_raw(self.handle, value)?;
1003 Ok(())
1004 }
1005
1006 pub fn set_ro<'a, M: RawMutex, P: PacketPool, const AT: usize, const CT: usize, const CN: usize>(
1008 &self,
1009 server: &AttributeServer<'a, M, P, AT, CT, CN>,
1010 value: &'a T,
1011 ) -> Result<(), Error> {
1012 let value = value.as_gatt();
1013 server.table().set_ro(self.handle, value)?;
1014 Ok(())
1015 }
1016
1017 pub fn get<M: RawMutex, P: PacketPool, const AT: usize, const CT: usize, const CN: usize>(
1022 &self,
1023 server: &AttributeServer<'_, M, P, AT, CT, CN>,
1024 ) -> Result<T, Error>
1025 where
1026 T: FromGatt,
1027 {
1028 server.table().get(self)
1029 }
1030
1031 pub fn cccd_handle(&self) -> Option<CharacteristicPropertiesHandle> {
1033 self.cccd_handle.map(CharacteristicPropertiesHandle)
1034 }
1035
1036 pub fn to_raw(self) -> Characteristic<[u8]> {
1038 Characteristic {
1039 cccd_handle: self.cccd_handle,
1040 handle: self.handle,
1041 props: self.props,
1042 phantom: PhantomData,
1043 }
1044 }
1045}
1046
1047pub struct CharacteristicPropertiesHandle(u16);
1049
1050impl AttributeHandle for CharacteristicPropertiesHandle {
1051 type Value = CharacteristicProps;
1052
1053 fn handle(&self) -> u16 {
1054 self.0
1055 }
1056}
1057
1058pub struct CharacteristicBuilder<'r, 'd, T: AsGatt + ?Sized, M: RawMutex, const MAX: usize> {
1060 handle: Characteristic<T>,
1061 table: &'r mut AttributeTable<'d, M, MAX>,
1062}
1063
1064impl<'r, 'd, T: AsGatt + ?Sized, M: RawMutex, const MAX: usize> CharacteristicBuilder<'r, 'd, T, M, MAX> {
1065 fn add_descriptor_internal<DT: AsGatt + ?Sized>(&mut self, uuid: Uuid, data: AttributeData<'d>) -> Descriptor<DT> {
1066 let handle = self.table.push(Attribute { uuid, data });
1067
1068 Descriptor {
1069 handle,
1070 phantom: PhantomData,
1071 }
1072 }
1073
1074 pub fn add_descriptor<DT: AsGatt, U: Into<Uuid>>(
1076 &mut self,
1077 uuid: U,
1078 permissions: AttPermissions,
1079 value: DT,
1080 store: &'d mut [u8],
1081 ) -> Descriptor<DT> {
1082 let bytes = value.as_gatt();
1083 store[..bytes.len()].copy_from_slice(bytes);
1084 let variable_len = DT::MAX_SIZE != DT::MIN_SIZE;
1085 let len = bytes.len() as u16;
1086 self.add_descriptor_internal(
1087 uuid.into(),
1088 AttributeData::Data {
1089 permissions,
1090 value: store,
1091 variable_len,
1092 len,
1093 },
1094 )
1095 }
1096
1097 pub fn add_descriptor_small<DT: AsGatt, U: Into<Uuid>>(
1099 &mut self,
1100 uuid: U,
1101 permissions: AttPermissions,
1102 value: DT,
1103 ) -> Descriptor<DT> {
1104 assert!(DT::MIN_SIZE <= 8);
1105
1106 let bytes = value.as_gatt();
1107 assert!(bytes.len() <= 8);
1108 let mut value = [0; 8];
1109 value[..bytes.len()].copy_from_slice(bytes);
1110 let variable_len = T::MAX_SIZE != T::MIN_SIZE;
1111 let capacity = T::MAX_SIZE.min(8) as u8;
1112 let len = bytes.len() as u8;
1113 self.add_descriptor_internal(
1114 uuid.into(),
1115 AttributeData::SmallData {
1116 permissions,
1117 variable_len,
1118 capacity,
1119 len,
1120 value,
1121 },
1122 )
1123 }
1124
1125 pub fn add_descriptor_ro<DT: AsGatt + ?Sized, U: Into<Uuid>>(
1127 &mut self,
1128 uuid: U,
1129 read_permission: PermissionLevel,
1130 data: &'d DT,
1131 ) -> Descriptor<DT> {
1132 let permissions = AttPermissions {
1133 write: PermissionLevel::NotAllowed,
1134 read: read_permission,
1135 };
1136 self.add_descriptor_internal(
1137 uuid.into(),
1138 AttributeData::ReadOnlyData {
1139 permissions,
1140 value: data.as_gatt(),
1141 },
1142 )
1143 }
1144
1145 pub fn read_permission(self, read: PermissionLevel) -> Self {
1147 self.table.with_attribute(self.handle.handle, |att| {
1148 let permissions = match &mut att.data {
1149 AttributeData::Data { permissions, .. }
1150 | AttributeData::SmallData { permissions, .. }
1151 | AttributeData::ReadOnlyData { permissions, .. } => permissions,
1152 _ => unreachable!(),
1153 };
1154
1155 permissions.read = read;
1156 });
1157
1158 self
1159 }
1160
1161 pub fn write_permission(self, write: PermissionLevel) -> Self {
1163 self.table.with_attribute(self.handle.handle, |att| {
1164 let permissions = match &mut att.data {
1165 AttributeData::Data { permissions, .. }
1166 | AttributeData::SmallData { permissions, .. }
1167 | AttributeData::ReadOnlyData { permissions, .. } => permissions,
1168 _ => unreachable!(),
1169 };
1170
1171 permissions.write = write;
1172 });
1173
1174 self
1175 }
1176
1177 pub fn cccd_permission(self, write: PermissionLevel) -> Self {
1181 let Some(handle) = self.handle.cccd_handle else {
1182 panic!("Can't set CCCD permission on characteristics without notify or indicate properties.");
1183 };
1184
1185 self.table.with_attribute(handle, |att| {
1186 let permission = match &mut att.data {
1187 AttributeData::Cccd { write_permission, .. } => write_permission,
1188 _ => unreachable!(),
1189 };
1190
1191 *permission = write;
1192 });
1193
1194 self
1195 }
1196
1197 pub fn to_raw(self) -> CharacteristicBuilder<'r, 'd, [u8], M, MAX> {
1199 CharacteristicBuilder {
1200 handle: self.handle.to_raw(),
1201 table: self.table,
1202 }
1203 }
1204 pub fn build(self) -> Characteristic<T> {
1206 self.handle
1207 }
1208}
1209
1210#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1212#[derive(Clone, Copy, Debug)]
1213pub struct Descriptor<T: AsGatt + ?Sized> {
1214 pub(crate) handle: u16,
1215 phantom: PhantomData<T>,
1216}
1217
1218impl<T: AsGatt> AttributeHandle for Descriptor<T> {
1219 type Value = T;
1220
1221 fn handle(&self) -> u16 {
1222 self.handle
1223 }
1224}
1225
1226impl<T: AsGatt + ?Sized> Descriptor<T> {
1227 pub fn set<M: RawMutex, P: PacketPool, const AT: usize, const CT: usize, const CN: usize>(
1229 &self,
1230 server: &AttributeServer<'_, M, P, AT, CT, CN>,
1231 value: &T,
1232 ) -> Result<(), Error> {
1233 let value = value.as_gatt();
1234 server.table().set_raw(self.handle, value)?;
1235 Ok(())
1236 }
1237
1238 pub fn get<M: RawMutex, P: PacketPool, const AT: usize, const CT: usize, const CN: usize>(
1243 &self,
1244 server: &AttributeServer<'_, M, P, AT, CT, CN>,
1245 ) -> Result<T, Error>
1246 where
1247 T: FromGatt,
1248 {
1249 server.table().get(self)
1250 }
1251}
1252
1253pub struct AttributeIterator<'a, 'd> {
1255 attributes: &'a mut [Attribute<'d>],
1256 pos: usize,
1257}
1258
1259impl<'d> AttributeIterator<'_, 'd> {
1260 pub fn next<'m>(&'m mut self) -> Option<(u16, &'m mut Attribute<'d>)> {
1262 if self.pos < self.attributes.len() {
1263 let att = &mut self.attributes[self.pos];
1264 self.pos += 1;
1265 let handle = self.pos as u16;
1266 Some((handle, att))
1267 } else {
1268 None
1269 }
1270 }
1271}
1272
1273pub struct Service {
1275 pub uuid: Uuid,
1277}
1278
1279impl Service {
1280 pub fn new<U: Into<Uuid>>(uuid: U) -> Self {
1282 Self { uuid: uuid.into() }
1283 }
1284}
1285
1286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1288#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1289pub struct CharacteristicProps(u8);
1290
1291impl<'a> From<&'a [CharacteristicProp]> for CharacteristicProps {
1292 fn from(props: &'a [CharacteristicProp]) -> Self {
1293 let mut val: u8 = 0;
1294 for prop in props {
1295 val |= *prop as u8;
1296 }
1297 CharacteristicProps(val)
1298 }
1299}
1300
1301impl<'a, const N: usize> From<&'a [CharacteristicProp; N]> for CharacteristicProps {
1302 fn from(props: &'a [CharacteristicProp; N]) -> Self {
1303 let mut val: u8 = 0;
1304 for prop in props {
1305 val |= *prop as u8;
1306 }
1307 CharacteristicProps(val)
1308 }
1309}
1310
1311impl<const N: usize> From<[CharacteristicProp; N]> for CharacteristicProps {
1312 fn from(props: [CharacteristicProp; N]) -> Self {
1313 let mut val: u8 = 0;
1314 for prop in props {
1315 val |= prop as u8;
1316 }
1317 CharacteristicProps(val)
1318 }
1319}
1320
1321impl From<u8> for CharacteristicProps {
1322 fn from(value: u8) -> Self {
1323 Self(value)
1324 }
1325}
1326
1327impl CharacteristicProps {
1328 pub fn any(&self, props: &[CharacteristicProp]) -> bool {
1330 for p in props {
1331 if (*p as u8) & self.0 != 0 {
1332 return true;
1333 }
1334 }
1335 false
1336 }
1337
1338 pub(crate) fn default_permissions(&self) -> AttPermissions {
1339 let read = if (self.0 & CharacteristicProp::Read as u8) != 0 {
1340 PermissionLevel::Allowed
1341 } else {
1342 PermissionLevel::NotAllowed
1343 };
1344
1345 let write = if (self.0
1346 & (CharacteristicProp::Write as u8
1347 | CharacteristicProp::WriteWithoutResponse as u8
1348 | CharacteristicProp::AuthenticatedWrite as u8))
1349 != 0
1350 {
1351 PermissionLevel::Allowed
1352 } else {
1353 PermissionLevel::NotAllowed
1354 };
1355
1356 AttPermissions { read, write }
1357 }
1358
1359 pub fn has_cccd(&self) -> bool {
1361 (self.0 & (CharacteristicProp::Indicate as u8 | CharacteristicProp::Notify as u8)) != 0
1362 }
1363
1364 pub fn to_raw(self) -> u8 {
1366 self.0
1367 }
1368}
1369
1370impl FixedGattValue for CharacteristicProps {
1371 const SIZE: usize = 1;
1372}
1373
1374impl FromGatt for CharacteristicProps {
1375 fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
1376 if data.len() != Self::SIZE {
1377 return Err(FromGattError::InvalidLength);
1378 }
1379
1380 Ok(CharacteristicProps(data[0]))
1381 }
1382}
1383
1384impl AsGatt for CharacteristicProps {
1385 const MIN_SIZE: usize = Self::SIZE;
1386 const MAX_SIZE: usize = Self::SIZE;
1387
1388 fn as_gatt(&self) -> &[u8] {
1389 AsGatt::as_gatt(&self.0)
1390 }
1391}
1392
1393pub struct AttributeValue<'d, M: RawMutex> {
1395 value: Mutex<M, &'d mut [u8]>,
1396}
1397
1398impl<M: RawMutex> AttributeValue<'_, M> {}
1399
1400#[derive(Clone, Copy)]
1402pub enum CCCDFlag {
1403 Notify = 0x1,
1405 Indicate = 0x2,
1407}
1408
1409#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1411#[derive(Clone, Copy, Default, Debug, PartialEq)]
1412pub struct CCCD(pub(crate) u16);
1413
1414impl<const T: usize> From<[CCCDFlag; T]> for CCCD {
1415 fn from(props: [CCCDFlag; T]) -> Self {
1416 let mut val: u16 = 0;
1417 for prop in props {
1418 val |= prop as u16;
1419 }
1420 CCCD(val)
1421 }
1422}
1423
1424impl From<u16> for CCCD {
1425 fn from(value: u16) -> Self {
1426 CCCD(value)
1427 }
1428}
1429
1430impl CCCD {
1431 pub fn raw(&self) -> u16 {
1433 self.0
1434 }
1435
1436 pub fn disable(&mut self) {
1438 self.0 = 0;
1439 }
1440
1441 pub fn any(&self, props: &[CCCDFlag]) -> bool {
1443 for p in props {
1444 if (*p as u16) & self.0 != 0 {
1445 return true;
1446 }
1447 }
1448 false
1449 }
1450
1451 pub fn set_notify(&mut self, is_enabled: bool) {
1453 let mask: u16 = CCCDFlag::Notify as u16;
1454 self.0 = if is_enabled { self.0 | mask } else { self.0 & !mask };
1455 }
1456
1457 pub fn should_notify(&self) -> bool {
1459 (self.0 & (CCCDFlag::Notify as u16)) != 0
1460 }
1461
1462 pub fn set_indicate(&mut self, is_enabled: bool) {
1464 let mask: u16 = CCCDFlag::Indicate as u16;
1465 self.0 = if is_enabled { self.0 | mask } else { self.0 & !mask };
1466 }
1467
1468 pub fn should_indicate(&self) -> bool {
1470 (self.0 & (CCCDFlag::Indicate as u16)) != 0
1471 }
1472}
1473
1474#[cfg(test)]
1475mod tests {
1476 extern crate std;
1477
1478 #[cfg(feature = "security")]
1479 #[test]
1480 fn database_hash() {
1481 use bt_hci::uuid::characteristic::{
1482 APPEARANCE, CLIENT_SUPPORTED_FEATURES, DATABASE_HASH, DEVICE_NAME, SERVICE_CHANGED,
1483 };
1484 use bt_hci::uuid::declarations::{CHARACTERISTIC, PRIMARY_SERVICE};
1485 use bt_hci::uuid::descriptors::{
1486 CHARACTERISTIC_PRESENTATION_FORMAT, CHARACTERISTIC_USER_DESCRIPTION, CLIENT_CHARACTERISTIC_CONFIGURATION,
1487 };
1488 use bt_hci::uuid::service::{GAP, GATT};
1489 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
1490
1491 use super::*;
1492
1493 let mut table: AttributeTable<'static, NoopRawMutex, 20> = AttributeTable::new();
1516
1517 table.push(Attribute::new(
1519 PRIMARY_SERVICE.into(),
1520 AttributeData::Service {
1521 uuid: GAP.into(),
1522 last_handle_in_group: 0x05,
1523 },
1524 ));
1525
1526 let expected = 0xd4cdec10804db3f147b4d7d10baa0120;
1527 let actual = table.hash();
1528 assert_eq!(
1529 actual, expected,
1530 "\nexpected: {:#032x}\nactual: {:#032x}",
1531 expected, actual
1532 );
1533
1534 table.push(Attribute::new(
1536 CHARACTERISTIC.into(),
1537 AttributeData::Declaration {
1538 props: [CharacteristicProp::Read].as_slice().into(),
1539 handle: 0x0003,
1540 uuid: DEVICE_NAME.into(),
1541 },
1542 ));
1543
1544 table.push(Attribute::new(
1545 DEVICE_NAME.into(),
1546 AttributeData::ReadOnlyData {
1547 permissions: AttPermissions {
1548 read: PermissionLevel::Allowed,
1549 write: PermissionLevel::NotAllowed,
1550 },
1551 value: b"",
1552 },
1553 ));
1554
1555 table.push(Attribute::new(
1557 CHARACTERISTIC.into(),
1558 AttributeData::Declaration {
1559 props: [CharacteristicProp::Read].as_slice().into(),
1560 handle: 0x0005,
1561 uuid: APPEARANCE.into(),
1562 },
1563 ));
1564
1565 table.push(Attribute::new(
1566 APPEARANCE.into(),
1567 AttributeData::ReadOnlyData {
1568 permissions: AttPermissions {
1569 read: PermissionLevel::Allowed,
1570 write: PermissionLevel::NotAllowed,
1571 },
1572 value: b"",
1573 },
1574 ));
1575
1576 let expected = 0x6c329e3f1d52c03f174980f6b4704875;
1577 let actual = table.hash();
1578 assert_eq!(
1579 actual, expected,
1580 "\nexpected: {:#032x}\n actual: {:#032x}",
1581 expected, actual
1582 );
1583
1584 table.push(Attribute::new(
1586 PRIMARY_SERVICE.into(),
1587 AttributeData::Service {
1588 uuid: GATT.into(),
1589 last_handle_in_group: 0x0d,
1590 },
1591 ));
1592
1593 table.push(Attribute::new(
1595 CHARACTERISTIC.into(),
1596 AttributeData::Declaration {
1597 props: [CharacteristicProp::Indicate].as_slice().into(),
1598 handle: 0x0008,
1599 uuid: SERVICE_CHANGED.into(),
1600 },
1601 ));
1602
1603 table.push(Attribute::new(
1604 SERVICE_CHANGED.into(),
1605 AttributeData::ReadOnlyData {
1606 permissions: AttPermissions {
1607 read: PermissionLevel::Allowed,
1608 write: PermissionLevel::NotAllowed,
1609 },
1610 value: b"",
1611 },
1612 ));
1613
1614 table.push(Attribute::new(
1615 CLIENT_CHARACTERISTIC_CONFIGURATION.into(),
1616 AttributeData::Cccd {
1617 notifications: false,
1618 indications: false,
1619 write_permission: PermissionLevel::Allowed,
1620 },
1621 ));
1622
1623 table.push(Attribute::new(
1625 CHARACTERISTIC.into(),
1626 AttributeData::Declaration {
1627 props: [CharacteristicProp::Read, CharacteristicProp::Write].as_slice().into(),
1628 handle: 0x000b,
1629 uuid: CLIENT_SUPPORTED_FEATURES.into(),
1630 },
1631 ));
1632
1633 table.push(Attribute::new(
1634 CLIENT_SUPPORTED_FEATURES.into(),
1635 AttributeData::ReadOnlyData {
1636 permissions: AttPermissions {
1637 read: PermissionLevel::Allowed,
1638 write: PermissionLevel::NotAllowed,
1639 },
1640 value: b"",
1641 },
1642 ));
1643
1644 table.push(Attribute::new(
1646 CHARACTERISTIC.into(),
1647 AttributeData::Declaration {
1648 props: [CharacteristicProp::Read].as_slice().into(),
1649 handle: 0x000d,
1650 uuid: DATABASE_HASH.into(),
1651 },
1652 ));
1653
1654 table.push(Attribute::new(
1655 DATABASE_HASH.into(),
1656 AttributeData::ReadOnlyData {
1657 permissions: AttPermissions {
1658 read: PermissionLevel::Allowed,
1659 write: PermissionLevel::NotAllowed,
1660 },
1661 value: b"",
1662 },
1663 ));
1664
1665 let expected = 0x16ce756326c5062bf74022f845c2b21f;
1666 let actual = table.hash();
1667 assert_eq!(
1668 actual, expected,
1669 "\nexpected: {:#032x}\n actual: {:#032x}",
1670 expected, actual
1671 );
1672
1673 const CUSTOM_SERVICE: u128 = 0x12345678_12345678_12345678_9abcdef0;
1674 const CUSTOM_CHARACTERISTIC: u128 = 0x12345678_12345678_12345678_9abcdef1;
1675
1676 table.push(Attribute::new(
1678 PRIMARY_SERVICE.into(),
1679 AttributeData::Service {
1680 uuid: CUSTOM_SERVICE.into(),
1681 last_handle_in_group: 0x13,
1682 },
1683 ));
1684
1685 table.push(Attribute::new(
1687 CHARACTERISTIC.into(),
1688 AttributeData::Declaration {
1689 props: [CharacteristicProp::Notify, CharacteristicProp::Read].as_slice().into(),
1690 handle: 0x0010,
1691 uuid: CUSTOM_CHARACTERISTIC.into(),
1692 },
1693 ));
1694
1695 table.push(Attribute::new(
1696 CUSTOM_CHARACTERISTIC.into(),
1697 AttributeData::ReadOnlyData {
1698 permissions: AttPermissions {
1699 read: PermissionLevel::Allowed,
1700 write: PermissionLevel::NotAllowed,
1701 },
1702 value: b"",
1703 },
1704 ));
1705
1706 table.push(Attribute::new(
1707 CLIENT_CHARACTERISTIC_CONFIGURATION.into(),
1708 AttributeData::Cccd {
1709 notifications: false,
1710 indications: false,
1711 write_permission: PermissionLevel::Allowed,
1712 },
1713 ));
1714
1715 table.push(Attribute::new(
1716 CHARACTERISTIC_USER_DESCRIPTION.into(),
1717 AttributeData::ReadOnlyData {
1718 permissions: AttPermissions {
1719 read: PermissionLevel::Allowed,
1720 write: PermissionLevel::NotAllowed,
1721 },
1722 value: b"Custom Characteristic",
1723 },
1724 ));
1725
1726 table.push(Attribute::new(
1727 CHARACTERISTIC_PRESENTATION_FORMAT.into(),
1728 AttributeData::ReadOnlyData {
1729 permissions: AttPermissions {
1730 read: PermissionLevel::Allowed,
1731 write: PermissionLevel::NotAllowed,
1732 },
1733 value: &[4, 0, 0, 0x27, 1, 0, 0],
1734 },
1735 ));
1736
1737 let expected = 0xc7352cced28d6608d4b057d247d8be76;
1738 let actual = table.hash();
1739 assert_eq!(
1740 actual, expected,
1741 "\nexpected: {:#032x}\n actual: {:#032x}",
1742 expected, actual
1743 );
1744 }
1745}