1use std::ops::{Deref, DerefMut};
2
3use smallvec::SmallVec;
4use structbuf::{Pack, Packer, StructBuf};
5
6use burble_const::UuidPacker;
7
8use super::*;
9
10#[derive(Debug)]
12pub struct ServiceDef;
13
14#[derive(Debug)]
16pub struct CharacteristicDef;
17
18#[derive(Debug)]
20pub struct IncludeDef;
21
22#[derive(Debug)]
24pub struct DescriptorDef;
25
26#[derive(Debug)]
28#[repr(transparent)]
29pub struct Builder<T>(DbBuilder, PhantomData<T>);
30
31impl<T> Builder<T> {
32 fn attr(&mut self, typ: Uuid, perms: Perms) -> Handle {
34 let typ = self.0.morph(typ);
35 let typ16 = typ.as_uuid16();
36 if typ16.is_none() {
37 self.append_data(u128::from(typ).to_le_bytes());
38 }
39 let hdl = self.next_handle();
40 self.append_attr(hdl, typ16, perms)
41 }
42}
43
44impl<T> Deref for Builder<T> {
45 type Target = DbBuilder;
46
47 #[inline(always)]
48 fn deref(&self) -> &Self::Target {
49 &self.0
50 }
51}
52
53impl<T> DerefMut for Builder<T> {
54 #[inline(always)]
55 fn deref_mut(&mut self) -> &mut Self::Target {
56 &mut self.0
57 }
58}
59
60impl Builder<Db> {
61 #[inline]
63 #[must_use]
64 pub(super) fn new() -> Self {
65 Self(
66 DbBuilder {
67 attr: Vec::with_capacity(128),
68 data: Vec::with_capacity(512),
69 ..DbBuilder::default()
70 },
71 PhantomData,
72 )
73 }
74
75 #[inline]
77 #[must_use]
78 pub(in crate::gatt) fn freeze(mut self) -> (Db, IoMap) {
79 let hash = self.calc_hash().to_le_bytes();
80 let hash = self.append_data(hash);
81 for at in &mut self.0.attr {
82 if matches!(at.typ, Some(Characteristic::DATABASE_HASH)) {
83 at.val = hash;
84 break; }
86 }
87 (
88 Db {
89 attr: self.0.attr.into_boxed_slice(),
90 data: self.0.data.into_boxed_slice(),
91 },
92 IoMap(self.0.io),
93 )
94 }
95
96 #[inline]
100 pub fn primary_service<T>(
101 &mut self,
102 uuid: impl Into<Uuid>,
103 include: impl AsRef<[Handle]>,
104 chars: impl FnOnce(&mut Builder<ServiceDef>) -> T,
105 ) -> (Handle, T) {
106 let hdl = self.service(Declaration::PrimaryService, uuid.into(), include.as_ref());
107 self.service_chars(hdl, chars)
108 }
109
110 #[inline]
114 pub fn secondary_service<T>(
115 &mut self,
116 uuid: impl Into<Uuid>,
117 include: impl AsRef<[Handle]>,
118 chars: impl FnOnce(&mut Builder<ServiceDef>) -> T,
119 ) -> (Handle, T) {
120 let hdl = self.service(Declaration::SecondaryService, uuid.into(), include.as_ref());
121 self.service_chars(hdl, chars)
122 }
123
124 #[cfg(debug_assertions)]
127 #[inline(always)]
128 pub fn morph_next(&mut self) {
129 self.0.flag.insert(Bld::MORPH);
130 }
131
132 fn service(&mut self, typ: Declaration, uuid: Uuid, include: &[Handle]) -> Handle {
135 let uuid = self.morph(uuid);
136 if let Some((UuidType::Service(s), uuid16)) = uuid.as_uuid16().map(|u| (u.typ(), u)) {
137 assert!(
138 !s.singleton() || !self.attr.iter().any(|at| at.typ == Some(uuid16)),
139 "only one instance of the {s} service is allowed"
140 );
141 }
142 let hdl = self.decl(typ, |v| v.uuid(uuid));
143 for &inc in include {
144 let s = self.service_group(inc).expect("invalid service handle");
145 let uuid = (s.first().len() == 2).then(|| self.value(s.first()).unpack().u16());
146 let end = s.last().hdl;
147 self.decl(Declaration::Include, |v| {
148 v.u16(inc).u16(end);
149 uuid.map(|u| v.u16(u));
150 });
151 }
152 hdl
153 }
154
155 #[inline(always)]
157 fn service_chars<T>(
158 &mut self,
159 hdl: Handle,
160 f: impl FnOnce(&mut Builder<ServiceDef>) -> T,
161 ) -> (Handle, T) {
162 let v = f(self.builder());
163 self.0.flag.remove(Bld::MORPH);
164 (hdl, v)
165 }
166
167 #[must_use]
169 fn calc_hash(&self) -> u128 {
170 use Descriptor::*;
171 let mut m = burble_crypto::AesCmac::db_hash();
172 for at in &self.attr {
173 let Some(typ) = at.typ else { continue };
174 let val = match typ.typ() {
175 UuidType::Declaration(_)
176 | UuidType::Descriptor(CharacteristicExtendedProperties) => self.value(at),
177 UuidType::Descriptor(
178 CharacteristicUserDescription
179 | ClientCharacteristicConfiguration
180 | ServerCharacteristicConfiguration
181 | CharacteristicPresentationFormat
182 | CharacteristicAggregateFormat,
183 ) => &[],
184 _ => continue,
185 };
186 m.update(u16::from(at.hdl).to_le_bytes())
187 .update(u16::from(typ).to_le_bytes())
188 .update(val);
189 }
190 m.finalize()
191 }
192}
193
194impl Builder<ServiceDef> {
195 #[inline]
200 pub fn characteristic<T>(
201 &mut self,
202 uuid: impl Into<Uuid>,
203 props: Prop,
204 perms: impl Into<Perms>,
205 io: impl Into<Io>,
206 descs: impl FnOnce(&mut Builder<CharacteristicDef>) -> T,
207 ) -> (Handle, T) {
208 let hdl = self.decl_value(uuid.into(), props, perms.into());
209 self.io.insert(hdl, io.into());
210 let mut flag = Bld::empty();
211 flag.set(
212 Bld::NEED_CCCD,
213 props.intersects(Prop::NOTIFY.union(Prop::INDICATE)),
214 );
215 flag.set(Bld::NEED_EXT_PROPS, props.contains(Prop::EXT_PROPS));
216 let b = self.builder(flag);
217 let v = descs(b);
218 b.finalize();
219 (hdl, v)
220 }
221
222 #[inline]
225 pub fn ro_characteristic<T>(
226 &mut self,
227 uuid: impl Into<Uuid>,
228 perms: impl Into<Perms>,
229 val: impl AsRef<[u8]>,
230 descs: impl FnOnce(&mut Builder<CharacteristicDef>) -> T,
231 ) -> T {
232 self.decl_value(uuid.into(), Prop::READ, perms.into());
233 self.append_val(val);
234 let b = self.builder(Bld::empty());
235 let v = descs(b);
236 b.finalize();
237 v
238 }
239
240 fn decl_value(&mut self, uuid: Uuid, props: Prop, perms: Perms) -> Handle {
244 let uuid = self.0.morph(uuid);
245 let val_hdl = self.next_handle().next().expect("maximum handle reached");
246 self.decl(Declaration::Characteristic, |v| {
247 v.u8(props.bits()).u16(val_hdl).uuid(uuid);
248 });
249 self.append_attr(val_hdl, uuid.as_uuid16(), perms)
250 }
251
252 fn builder(&mut self, flag: Bld) -> &mut Builder<CharacteristicDef> {
254 self.flag = flag;
255 unsafe { self.fmt.set_len(0) };
257 self.0.builder()
258 }
259}
260
261impl Builder<CharacteristicDef> {
262 #[inline]
265 pub fn descriptor(
266 &mut self,
267 uuid: impl Into<Uuid>,
268 perms: impl Into<Perms>,
269 io: impl Into<Io>,
270 ) -> Handle {
271 let perms = perms.into();
272 let hdl = self.attr(uuid.into(), perms);
273 self.io.insert(hdl, io.into());
274 hdl
275 }
276
277 #[inline]
280 pub fn ro_descriptor(
281 &mut self,
282 uuid: impl Into<Uuid>,
283 perms: impl Into<Perms>,
284 val: impl AsRef<[u8]>,
285 ) {
286 self.attr(uuid.into(), perms.into());
287 self.append_val(val);
288 }
289
290 pub fn ext_props(&mut self, props: ExtProp) {
300 assert!(
301 self.flag.contains(Bld::NEED_EXT_PROPS),
302 "EXT_PROPS not set or descriptor already exists"
303 );
304 self.flag.remove(Bld::NEED_EXT_PROPS);
305 self.decl(Descriptor::CharacteristicExtendedProperties, |v| {
306 v.u16(props.bits());
307 });
308 }
309
310 #[inline]
317 pub fn cccd(&mut self, perms: impl Into<Perms>) -> Handle {
318 assert!(
319 !self.flag.contains(Bld::HAVE_CCCD),
320 "descriptor already exists"
321 );
322 self.flag.remove(Bld::NEED_CCCD);
323 self.flag.insert(Bld::HAVE_CCCD);
324 self.attr(
325 Descriptor::ClientCharacteristicConfiguration.uuid(),
326 perms.into(),
327 )
328 }
329
330 pub fn presentation_fmt(
333 &mut self,
334 fmt: Format,
335 exp: i8,
336 unit: Unit,
337 desc: Description,
338 ) -> Handle {
339 let hdl = self.decl(Descriptor::CharacteristicPresentationFormat, |v| {
340 v.u8(fmt).i8(exp).u16(unit).u8(desc.ns()).u16(desc);
341 });
342 self.fmt.push(hdl);
343 hdl
344 }
345
346 pub fn aggregate_fmt(&mut self, hdls: impl AsRef<[Handle]>) {
356 assert!(
357 !self.flag.contains(Bld::HAVE_AGGREGATE_FMT),
358 "descriptor already exists"
359 );
360 self.flag.insert(Bld::HAVE_AGGREGATE_FMT);
361 self.decl(Descriptor::CharacteristicAggregateFormat, |v| {
362 for &hdl in hdls.as_ref() {
363 v.u16(hdl);
364 }
365 });
366 }
367
368 fn finalize(&mut self) {
370 if self.flag.contains(Bld::NEED_EXT_PROPS) {
371 self.ext_props(ExtProp::empty());
372 }
373 if self.flag.contains(Bld::NEED_CCCD) {
374 self.cccd(Access::READ | Access::WRITE.authn().encrypt());
376 }
377 if !self.flag.contains(Bld::HAVE_AGGREGATE_FMT) && self.fmt.len() > 1 {
378 let fmt = mem::take(&mut self.fmt);
379 self.aggregate_fmt(&fmt);
380 self.fmt = fmt; }
382 }
383}
384
385bitflags::bitflags! {
386 #[derive(Clone, Copy, Debug, Default)]
388 #[repr(transparent)]
389 pub struct Bld: u8 {
390 const MORPH = 1 << 0;
391 const NEED_EXT_PROPS = 1 << 1;
392 const NEED_CCCD = 1 << 2;
393 const HAVE_CCCD = 1 << 3;
394 const HAVE_AGGREGATE_FMT = 1 << 4;
395 }
396}
397
398#[derive(Debug, Default)]
400pub struct DbBuilder {
401 attr: Vec<Attr>,
402 data: Vec<u8>,
403 io: BTreeMap<Handle, Io>,
404 flag: Bld,
405 fmt: SmallVec<[Handle; 4]>,
406}
407
408impl DbBuilder {
409 #[inline]
411 fn decl(&mut self, typ: impl Into<Uuid16>, val: impl FnOnce(&mut Packer)) -> Handle {
412 fn append_attr(this: &mut DbBuilder, typ: Uuid16, val: &[u8]) -> Handle {
413 let hdl = this.next_handle();
414 let val = this.append_data(val);
415 this.attr.push(Attr {
416 hdl,
417 typ: Some(typ),
418 val,
419 perms: Perms::new(Access::READ),
420 });
421 hdl
422 }
423 let mut b = StructBuf::new(1 + 2 + 16);
426 val(&mut b.append());
427 append_attr(self, typ.into(), &b)
428 }
429
430 #[inline]
432 fn next_handle(&self) -> Handle {
433 self.attr.last().map_or(Handle::MIN, |at| {
434 at.hdl.next().expect("maximum handle reached")
435 })
436 }
437
438 #[inline]
441 fn append_attr(&mut self, hdl: Handle, typ: Option<Uuid16>, perms: Perms) -> Handle {
442 #[allow(clippy::cast_possible_truncation)]
443 let i = match typ {
444 None => self.data.len() as Idx,
445 Some(_) => 0,
446 };
447 self.attr.push(Attr {
448 hdl,
449 typ,
450 val: (i, i),
451 perms,
452 });
453 hdl
454 }
455
456 #[inline]
458 fn append_val(&mut self, v: impl AsRef<[u8]>) {
459 self.attr.last_mut().expect("empty database").val = self.append_data(v);
460 }
461
462 #[inline]
464 fn append_data(&mut self, v: impl AsRef<[u8]>) -> (Idx, Idx) {
465 #[allow(clippy::cast_possible_truncation)]
466 let start = self.data.len() as Idx;
467 self.data.extend_from_slice(v.as_ref());
468 let end = Idx::try_from(self.data.len())
469 .expect("database data overflow (see Idx type in gatt/db.rs)");
470 (start, end)
471 }
472
473 #[inline(always)]
475 fn builder<T>(&mut self) -> &mut Builder<T> {
476 unsafe { &mut *(self as *mut Self).cast() }
478 }
479
480 #[inline(always)]
482 fn morph(&self, u: impl Into<Uuid>) -> Uuid {
483 let u = u.into();
484 if !cfg!(debug_assertions) || !self.flag.contains(Bld::MORPH) {
485 return u;
486 }
487 (u.as_u16()).map_or(u, |u| unsafe { Uuid::new_unchecked(u128::from(u) << 96) })
489 }
490}
491
492impl CommonOps for DbBuilder {
493 #[inline(always)]
494 fn attr(&self) -> &[Attr] {
495 &self.attr
496 }
497
498 #[inline(always)]
499 fn data(&self) -> &[u8] {
500 &self.data
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use std::assert_eq;
507
508 use super::*;
509
510 #[test]
511 fn service_group() {
512 fn eq(b: &DbBuilder, h: Handle, r: Range<usize>) {
513 let s = b.service_group(h).unwrap();
514 assert_eq!(s.off..s.off + s.attr.len(), r);
515 }
516
517 let mut db = Db::build();
518 let (h1, _) = db.primary_service(Service::GenericAccess, [], |_| {});
519 let (h2, _) = db.primary_service(Service::GenericAttribute, [h1], |_| {});
520 eq(&db, h1, 0..1);
521 eq(&db, h2, 1..3);
522
523 let (h3, _) = db.primary_service(Service::Battery, [], |_| {});
524 eq(&db, h2, 1..3);
525 eq(&db, h3, 3..4);
526 }
527
528 #[test]
530 fn hash() {
531 assert_eq!(
532 appendix_b().hash(),
533 0xF1_CA_2D_48_EC_F5_8B_AC_8A_88_30_BB_B9_FB_A9_90
534 );
535 }
536
537 #[test]
538 fn primary_services() {
539 use Service::*;
540 let mut s = appendix_b();
541
542 let mut it = s.primary_services(Handle::MIN, None);
543 group_eq(it.next(), 0x0001, 0x0005, GenericAccess);
544 group_eq(it.next(), 0x0006, 0x000D, GenericAttribute);
545 group_eq(it.next(), 0x000E, 0x0013, Glucose);
546 assert!(it.next().is_none());
547 assert!(it.next().is_none());
548 drop(it);
549
550 let mut it = s.primary_services(Handle::MIN, Some(GenericAttribute.uuid()));
551 group_eq(it.next(), 0x0006, 0x000D, GenericAttribute);
552 assert!(it.next().is_none());
553 drop(it);
554
555 let mut v = s.attr.to_vec();
557 v.truncate(s.attr.len() - 3);
558 s.attr = v.into_boxed_slice();
559
560 let mut it = s.primary_services(Handle::new(0x0002).unwrap(), None);
561 group_eq(it.next(), 0x0006, 0x000D, GenericAttribute);
562 group_eq(it.next(), 0x000E, 0x0013, Glucose);
563 assert!(it.next().is_none());
564 }
565
566 #[test]
567 fn characteristics() {
568 use Characteristic::*;
569
570 let s = appendix_b();
571 let mut pri = s.primary_services(Handle::MIN, None);
572
573 let mut it = s.characteristics(pri.next().unwrap().handle_range());
574 group_eq(it.next(), 0x0002, 0x0003, DeviceName);
575 group_eq(it.next(), 0x0004, 0x0005, Appearance);
576 assert!(it.next().is_none());
577
578 let mut it = s.characteristics(pri.next().unwrap().handle_range());
579 group_eq(it.next(), 0x0007, 0x0009, ServiceChanged);
580 group_eq(it.next(), 0x000A, 0x000B, ClientSupportedFeatures);
581 group_eq(it.next(), 0x000C, 0x000D, DatabaseHash);
582 assert!(it.next().is_none());
583
584 let mut it = s.characteristics(pri.next().unwrap().handle_range());
585 group_eq(it.next(), 0x0010, 0x0013, GlucoseMeasurement);
586 assert!(it.next().is_none());
587 }
588
589 #[test]
590 fn descriptors() {
591 let s = appendix_b();
592
593 let mut pri = s.primary_services(Handle::new(0x0006).unwrap(), None);
594 let mut chars = s.characteristics(pri.next().unwrap().handle_range());
595
596 let hdl = chars.next().unwrap().value_handle().next().unwrap();
597 let mut it = s.descriptors(HandleRange::new(hdl, hdl));
598 let v = it.next().unwrap();
599 assert_eq!(v.handle(), hdl);
600 assert_eq!(
601 v.uuid(),
602 Descriptor::ClientCharacteristicConfiguration.uuid()
603 );
604 assert!(it.next().is_none());
605
606 let mut it = s.descriptors(HandleRange::new(Handle::new(0x0008).unwrap(), hdl));
607 assert!(it.next().is_none());
608 }
609
610 fn group_eq<T: Group>(v: Option<DbEntry<T>>, decl: u16, end: u16, uuid: impl Into<Uuid16>) {
611 let v = v.unwrap();
612 assert_eq!(
613 v.handle_range(),
614 HandleRange::new(Handle::new(decl).unwrap(), Handle::new(end).unwrap())
615 );
616 assert_eq!(v.uuid(), uuid.into().as_uuid());
617 }
618
619 fn appendix_b() -> Db {
620 let mut db = Db::build();
621 db.primary_service(Service::GenericAccess, [], |db| {
622 db.characteristic(
623 Characteristic::DeviceName,
624 Prop::READ | Prop::WRITE,
625 Access::READ_WRITE,
626 Io::NONE,
627 |_| {},
628 );
629 db.characteristic(
630 Characteristic::Appearance,
631 Prop::READ,
632 Access::READ,
633 Io::NONE,
634 |_| {},
635 )
636 });
637 db.primary_service(Service::GenericAttribute, [], |db| {
638 db.characteristic(
639 Characteristic::ServiceChanged,
640 Prop::INDICATE,
641 Access::NONE,
642 Io::NONE,
643 |db| db.cccd(Access::READ_WRITE),
644 );
645 db.characteristic(
646 Characteristic::ClientSupportedFeatures,
647 Prop::READ | Prop::WRITE,
648 Access::READ_WRITE,
649 Io::NONE,
650 |_| {},
651 );
652 db.characteristic(
653 Characteristic::DatabaseHash,
654 Prop::READ,
655 Access::READ,
656 Io::NONE,
657 |_| {},
658 );
659 });
660 db.primary_service(Service::Glucose, [], |db| {
661 db.decl(Declaration::Include, |v| {
663 v.u16(0x0014_u16).u16(0x0016_u16).u16(0x180F_u16);
664 });
665 db.characteristic(
666 Characteristic::GlucoseMeasurement,
667 Prop::READ | Prop::INDICATE | Prop::EXT_PROPS,
668 Access::READ,
669 Io::NONE,
670 |db| db.cccd(Access::READ_WRITE),
671 );
672 });
673 db.secondary_service(Service::Battery, [], |db| {
674 db.characteristic(
675 Characteristic::BatteryLevel,
676 Prop::READ,
677 Access::READ,
678 Io::NONE,
679 |_| {},
680 );
681 });
682 let (db, _) = db.freeze();
683 db
684 }
685}