burble/gatt/db/
builder.rs

1use std::ops::{Deref, DerefMut};
2
3use smallvec::SmallVec;
4use structbuf::{Pack, Packer, StructBuf};
5
6use burble_const::UuidPacker;
7
8use super::*;
9
10/// Database service definition marker type.
11#[derive(Debug)]
12pub struct ServiceDef;
13
14/// Database characteristic definition marker type.
15#[derive(Debug)]
16pub struct CharacteristicDef;
17
18/// Database include definition marker type.
19#[derive(Debug)]
20pub struct IncludeDef;
21
22/// Database descriptor definition marker type.
23#[derive(Debug)]
24pub struct DescriptorDef;
25
26/// Database builder used to define services, characteristics, and descriptors.
27#[derive(Debug)]
28#[repr(transparent)]
29pub struct Builder<T>(DbBuilder, PhantomData<T>);
30
31impl<T> Builder<T> {
32    /// Creates a generic attribute with an externally stored value.
33    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    /// Creates a new database builder.
62    #[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    /// Returns the final read-only database and I/O map.
76    #[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; // Only one instance is allowed
85            }
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    /// Defines a primary service ([Vol 3] Part G, Section 3.1).
97    ///
98    /// 16-bit UUID services should precede 128-bit ones.
99    #[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    /// Defines a secondary service ([Vol 3] Part G, Section 3.1).
111    ///
112    /// 16-bit UUID services should precede 128-bit ones.
113    #[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    /// Morphs all UUIDs of the next service to allow debugging otherwise
125    /// protected services on the client.
126    #[cfg(debug_assertions)]
127    #[inline(always)]
128    pub fn morph_next(&mut self) {
129        self.0.flag.insert(Bld::MORPH);
130    }
131
132    /// Declares a primary or secondary service and any included services
133    /// ([Vol 3] Part G, Section 3.2).
134    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    /// Calls `f` to define service characteristics.
156    #[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    /// Calculates the database hash ([Vol 3] Part G, Section 7.3.1).
168    #[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    /// Defines a single-value characteristic ([Vol 3] Part G, Section 3.3).
196    ///
197    /// Mandatory service characteristics must precede optional ones and 16-bit
198    /// UUID characteristics should precede 128-bit ones.
199    #[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    /// Defines a read-only characteristic with a database-stored value
223    /// ([Vol 3] Part G, Section 3.3).
224    #[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    // TODO: Method for multi-value characteristics
241
242    /// Adds characteristic and characteristic value declarations.
243    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    /// Returns a new descriptor builder.
253    fn builder(&mut self, flag: Bld) -> &mut Builder<CharacteristicDef> {
254        self.flag = flag;
255        // SAFETY: 0 is a valid length and there is nothing to drop
256        unsafe { self.fmt.set_len(0) };
257        self.0.builder()
258    }
259}
260
261impl Builder<CharacteristicDef> {
262    /// Declares a non-GATT profile characteristic descriptor
263    /// ([Vol 3] Part G, Section 3.3.3).
264    #[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    /// Declares a read-only non-GATT profile characteristic descriptor with the
278    /// value stored in the database ([Vol 3] Part G, Section 3.3.3).
279    #[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    /// Declares a Characteristic Extended Properties descriptor
291    /// ([Vol 3] Part G, Section 3.3.3.1).
292    ///
293    /// This descriptor will be added automatically if the characteristic
294    /// properties contain `EXT_PROPS` flag.
295    ///
296    /// # Panics
297    ///
298    /// Panics if the characteristic cannot have or already has this descriptor.
299    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    /// Declares a Client Characteristic Configuration descriptor
311    /// ([Vol 3] Part G, Section 3.3.3.3).
312    ///
313    /// # Panics
314    ///
315    /// Panics if the characteristic already has this descriptor.
316    #[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    /// Declares a Characteristic Presentation Format descriptor
331    /// ([Vol 3] Part G, Section 3.3.3.5).
332    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    /// Declares a Characteristic Aggregate Format descriptor
347    /// ([Vol 3] Part G, Section 3.3.3.6).
348    ///
349    /// This descriptor will be added automatically when more than one
350    /// Presentation Format descriptor is present.
351    ///
352    /// # Panics
353    ///
354    /// Panics if the characteristic already has this descriptor.
355    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    /// Finalizes characteristic definition by adding required descriptors.
369    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            // TODO: Copy write permission from value?
375            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; // Preserve any allocated capacity
381        }
382    }
383}
384
385bitflags::bitflags! {
386    /// Builder flags.
387    #[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/// Shared [`Db`] builder state.
399#[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    /// Creates a read-only GATT profile declaration with value set by `val`.
410    #[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        // Maximum length of the Characteristic declaration value, which is the
424        // longest value stored in the database ([Vol 3] Part G, Section 3.3.1).
425        let mut b = StructBuf::new(1 + 2 + 16);
426        val(&mut b.append());
427        append_attr(self, typ.into(), &b)
428    }
429
430    /// Returns the next unused handle.
431    #[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    /// Appends a new attribute entry. If `typ == None`, then the last 16 data
439    /// bytes must contain the 128-bit UUID.
440    #[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    /// Appends a read-only value for the last attribute entry.
457    #[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    /// Appends `v` to the database and returns the resulting index range.
463    #[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    /// Returns a new builder.
474    #[inline(always)]
475    fn builder<T>(&mut self) -> &mut Builder<T> {
476        // SAFETY: Builder is a `repr(transparent)` onetype
477        unsafe { &mut *(self as *mut Self).cast() }
478    }
479
480    /// Returns the possibly morphed UUID.
481    #[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        // SAFETY: `u` is a non-zero 16-bit UUID
488        (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    /// Example database hash ([Vol 3] Part G, Appendix B).
529    #[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        // Remove the battery service
556        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            // Hack to include a service that hasn't been defined yet
662            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}