Skip to main content

burble/gatt/db/
db.rs

1use std::marker::PhantomData;
2use std::ops::Range;
3use std::{iter, mem, slice};
4
5use structbuf::Unpack;
6use tracing::{debug, warn};
7
8pub use builder::*;
9
10use crate::gap::{Uuid, Uuid16, UuidType, UuidVec};
11
12use super::*;
13
14mod builder;
15
16/// Database data index type. `u16` is enough for 3k 128-bit characteristics.
17type Idx = u16;
18
19/// Read-only attribute database.
20///
21/// Describes the service structure, attribute permissions, and stores read-only
22/// values.
23#[derive(Clone, Debug, Default)]
24pub struct Db {
25    /// Attribute metadata sorted by handle.
26    attr: Box<[Attr]>,
27    /// Concatenated GATT profile attribute values and 128-bit UUIDs, ending
28    /// with a 128-bit hash in little-endian byte order.
29    data: Box<[u8]>,
30}
31
32impl Db {
33    /// Creates a new database builder.
34    #[inline(always)]
35    #[must_use]
36    pub fn build() -> Builder<Self> {
37        Builder::new()
38    }
39
40    /// Returns the database hash ([Vol 3] Part G, Section 7.3).
41    #[inline(always)]
42    #[must_use]
43    pub const fn hash(&self) -> u128 {
44        let (p, i) = (self.data.as_ptr(), self.data.len() - mem::size_of::<u128>());
45        // SAFETY: `self.data` always ends with the hash and the dereferenced
46        // value has an alignment of 1.
47        u128::from_le_bytes(unsafe { *p.add(i).cast() })
48    }
49
50    /// Returns an iterator over all attributes in handle order.
51    #[inline]
52    pub fn iter(&self) -> impl Iterator<Item = (Handle, Uuid, &[u8])> {
53        (self.attr.iter()).map(|at| (at.hdl, self.typ(at), self.value(at)))
54    }
55
56    /// Returns the type and value of the specified handle or [`None`] if the
57    /// handle is invalid. The value will be empty if it's not part of the
58    /// database.
59    #[inline]
60    #[must_use]
61    pub fn get(&self, hdl: Handle) -> Option<(Uuid, &[u8])> {
62        (self.try_get(hdl).ok()).map(|at| (self.typ(at), self.value(at)))
63    }
64
65    /// Returns characteristic information for any handle within the
66    /// characteristic definition. Returns [`None`] if the handle is not part of
67    /// any characteristic.
68    #[inline]
69    #[must_use]
70    pub(super) fn get_characteristic(&self, hdl: Handle) -> Option<CharInfo> {
71        (self.try_get(hdl).ok()).and_then(|at| self.characteristic_for_attr(at))
72    }
73
74    /// Returns an iterator over primary services with optional UUID matching
75    /// ([Vol 3] Part G, Section 4.4).
76    #[inline]
77    pub fn primary_services(
78        &self,
79        start: Handle,
80        uuid: Option<Uuid>,
81    ) -> impl Iterator<Item = DbEntry<ServiceDef>> {
82        let i = self.try_get(start).map_or_else(|i| i, |at| self.index(at));
83        let uuid = uuid.map_or_else(UuidVec::default, UuidVec::new);
84        // SAFETY: 0 <= i <= self.attr.len()
85        GroupIter::new(self, unsafe { self.attr.get_unchecked(i..) }, move |at| {
86            at.is_primary_service() && (uuid.is_empty() || self.value(at) == uuid.as_ref())
87        })
88    }
89
90    /// Returns an iterator over service includes
91    /// ([Vol 3] Part G, Section 4.5.1).
92    pub fn includes(&self, hdls: HandleRange) -> impl Iterator<Item = DbEntry<IncludeDef>> {
93        (self.service_attrs(hdls).iter())
94            .map_while(|at| at.is_include().then(|| DbEntry::new(self, at, at.hdl)))
95    }
96
97    /// Returns an iterator over service characteristics
98    /// ([Vol 3] Part G, Section 4.6.1).
99    pub fn characteristics(
100        &self,
101        hdls: HandleRange,
102    ) -> impl Iterator<Item = DbEntry<CharacteristicDef>> {
103        GroupIter::new(self, self.service_attrs(hdls), Attr::is_char)
104    }
105
106    /// Returns an iterator over characteristic descriptors
107    /// ([Vol 3] Part G, Section 4.7.1).
108    pub fn descriptors(&self, hdls: HandleRange) -> impl Iterator<Item = DbEntry<DescriptorDef>> {
109        let attr = self.subset(hdls).and_then(|s| {
110            use private::Group;
111            // SAFETY: 0 <= s.off < self.attr.len()
112            let decl = unsafe { self.attr.get_unchecked(..s.off).iter() }
113                .rfind(|&at| Attr::is_char(at))?;
114            // Handle range must start after the characteristic value and cannot
115            // cross characteristic boundary.
116            (value_handle(self.value(decl)) < s.first().hdl
117                && !(s.attr.iter()).any(|at| CharacteristicDef::is_next_group(at.typ)))
118            .then_some(s.attr)
119        });
120        (attr.unwrap_or_default().iter()).map(|at| DbEntry::new(self, at, at.hdl))
121    }
122
123    /// Performs read/write access permission check for a single handle.
124    #[inline]
125    pub fn try_access(&self, req: Request, hdl: Handle) -> RspResult<Handle> {
126        self.try_multi_access(req, [hdl]).map(|_| hdl)
127    }
128
129    /// Performs read/write access permission check for multiple handles.
130    #[inline]
131    pub fn try_multi_access<T: AsRef<[Handle]>>(&self, req: Request, hdls: T) -> RspResult<T> {
132        let v = hdls.as_ref();
133        if v.is_empty() {
134            return req.op.err(ErrorCode::InvalidPdu); // Should never happen
135        }
136        // [Vol 3] Part F, Section 3.4.4.7
137        for &hdl in v {
138            let Ok(at) = self.try_get(hdl) else {
139                warn!("Denied {} for invalid {hdl}", req.op);
140                return req.op.hdl_err(ErrorCode::InvalidHandle, hdl);
141            };
142            self.access_check(req, at)?;
143        }
144        Ok(hdls)
145    }
146
147    /// Performs read/write access permission check for a range of handles with
148    /// UUID type matching.
149    #[inline]
150    pub fn try_range_access(
151        &self,
152        req: Request,
153        hdls: HandleRange,
154        uuid: Uuid,
155    ) -> RspResult<Vec<Handle>> {
156        let attr = self.subset(hdls).map_or_else(Default::default, |s| s.attr);
157        let mut it = (attr.iter())
158            .filter_map(|at| (self.typ(at) == uuid).then(|| self.access_check(req, at)))
159            .peekable();
160        // [Vol 3] Part F, Section 3.4.4.1
161        match it.peek() {
162            None => return req.op.hdl_err(ErrorCode::AttributeNotFound, hdls.start()),
163            Some(&r) => {
164                r?;
165            }
166        }
167        Ok(it.map_while(RspResult::ok).collect())
168    }
169
170    /// Logs database contents at `debug` level.
171    ///
172    /// # Panics
173    ///
174    /// Panics if the database structure is invalid.
175    pub fn dump(&self) {
176        use Declaration::*;
177        macro_rules! log {
178            ($at:ident, $fmt:expr$(, $($args:tt)*)?) => {
179                ::tracing::debug!("[{:#06X}] {}", u16::from($at.hdl), format_args!($fmt$(, $($args)*)?))
180            };
181        }
182        let mut vhdl = Handle::MIN;
183        let mut last_char_hdl = Handle::MIN;
184        let mut cont = ' ';
185        debug!("GATT database:");
186        for at in self.attr.iter() {
187            let v = self.value(at);
188            let mut v = v.unpack();
189            if let Some(uuid16) = at.typ {
190                match uuid16.typ() {
191                    UuidType::Declaration(d) => match d {
192                        PrimaryService | SecondaryService => {
193                            last_char_hdl = (self.service_group(at.hdl).unwrap().attr.iter())
194                                .rfind(|at| at.is_char())
195                                .map_or(Handle::MIN, |at| at.hdl);
196                            let sec = ((!at.is_primary_service()).then_some("Secondary"))
197                                .unwrap_or_default();
198                            let uuid = Uuid::try_from(v.as_ref()).unwrap();
199                            if let UuidType::Service(_) = uuid.typ() {
200                                log!(at, "{sec}{uuid} <{uuid:?}>");
201                            } else {
202                                log!(at, "{sec}Service <{uuid:?}>");
203                            }
204                        }
205                        Include => log!(at, "|__ [Include {:#06X}..={:#06X}]", v.u16(), v.u16()),
206                        Characteristic => {
207                            cont = if at.hdl < last_char_hdl { '|' } else { ' ' };
208                            let _prop = Prop::from_bits(v.u8()).unwrap(); // TODO
209                            vhdl = Handle::new(v.u16()).unwrap();
210                            let uuid = Uuid::try_from(v.as_ref()).unwrap();
211                            if let UuidType::Characteristic(_) = uuid.typ() {
212                                log!(at, "|__ {uuid} <{uuid:?}>");
213                            } else {
214                                log!(at, "|__ Characteristic <{uuid:?}>");
215                            }
216                        }
217                        _ => unreachable!(),
218                    },
219                    UuidType::Characteristic(_) => log!(at, "{cont}   |__ [Value <{uuid16:?}>]"),
220                    UuidType::Descriptor(_) => log!(at, "{cont}   |__ {uuid16} <{uuid16:?}>"),
221                    typ => log!(at, "Unexpected {typ}"),
222                }
223            } else {
224                let uuid = self.typ(at);
225                if at.hdl <= vhdl {
226                    log!(at, "{cont}   |__ [Value <{uuid:?}>]");
227                } else {
228                    log!(at, "{cont}   |__ Descriptor <{uuid:?}>");
229                }
230            }
231        }
232    }
233
234    /// Returns a subset of attributes for one service. The service declaration
235    /// is skipped.
236    fn service_attrs(&self, hdls: HandleRange) -> &[Attr] {
237        let attr = self.subset(hdls).and_then(|s| {
238            let attr = if s.first().is_service() {
239                // SAFETY: `s` is not empty
240                unsafe { s.attr.get_unchecked(1..) }
241            } else {
242                s.attr
243            };
244            // Handle range cannot cross service boundary
245            (!attr.iter().any(Attr::is_service)).then_some(attr)
246        });
247        attr.unwrap_or_default()
248    }
249
250    /// Performs read/write access permission check for the specified attribute.
251    fn access_check(&self, req: Request, at: &Attr) -> RspResult<Handle> {
252        use Opcode::*;
253        let (op, hdl) = (req.op, at.hdl);
254        // [Vol 3] Part F, Section 4
255        if let Err(e) = at.perms.test(req.ac) {
256            warn!("Denied {op} to {hdl} due to {e}");
257            return op.hdl_err(e, hdl);
258        }
259        let Some(ch) = self.characteristic_for_attr(at) else {
260            return Ok(hdl); // Permission check passed and no properties to test
261        };
262        if hdl != ch.vhdl {
263            // [Vol 3] Part G, Section 3.3.3.1 and 3.3.3.2
264            if req.ac.typ() == Access::WRITE
265                && matches!(at.typ, Some(Descriptor::CHARACTERISTIC_USER_DESCRIPTION))
266                && !(ch.ext_props).map_or(false, |p| p.contains(ExtProp::WRITABLE_AUX))
267            {
268                warn!("Denied {op} to {hdl} because WRITABLE_AUX bit is not set");
269                return op.hdl_err(ErrorCode::WriteNotPermitted, hdl);
270            }
271            return Ok(hdl); // Descriptor or declaration access
272        }
273        // [Vol 3] Part G, Section 3.3.1.1
274        let bit = match op {
275            ReadReq                                   // [Vol 3] Part G, Section 4.8.1 and 4.8.3
276            | ReadByTypeReq                           // [Vol 3] Part G, Section 4.8.2
277            | ReadBlobReq                             // [Vol 3] Part G, Section 4.8.3
278            | ReadMultipleReq                         // [Vol 3] Part G, Section 4.8.4
279            | ReadMultipleVariableReq => Prop::READ,  // [Vol 3] Part G, Section 4.8.5
280            WriteCmd => Prop::WRITE_CMD,              // [Vol 3] Part G, Section 4.9.1
281            WriteReq                                  // [Vol 3] Part G, Section 4.9.3
282            | PrepareWriteReq => Prop::WRITE,         // [Vol 3] Part G, Section 4.9.4
283            SignedWriteCmd => Prop::SIGNED_WRITE_CMD, // [Vol 3] Part G, Section 4.9.2
284            _ => {
285                warn!("Denied non-read/write {op} for {hdl}");
286                return op.hdl_err(ErrorCode::RequestNotSupported, hdl);
287            }
288        };
289        if !ch.props.contains(bit) {
290            let e = if req.ac.typ() == Access::READ {
291                ErrorCode::ReadNotPermitted
292            } else {
293                ErrorCode::WriteNotPermitted
294            };
295            warn!("Denied {op} for {hdl} due to {e} by properties");
296            return op.hdl_err(e, hdl);
297        }
298        // [Vol 3] Part G, Section 3.3.3.1
299        if matches!(op, PrepareWriteReq)
300            && !(ch.ext_props).map_or(true, |p| p.contains(ExtProp::RELIABLE_WRITE))
301        {
302            // It's not clear how to handle this. WRITE allows Section 4.9.4
303            // procedure, and RELIABLE_WRITE allows Section 4.9.5 procedure, but
304            // they are the exact same procedure from the server's perspective.
305            // We deny it only if the Characteristic Extended Properties
306            // descriptor exists and the RELIABLE_WRITE bit is not set.
307            warn!("Denied {op} for {hdl} because RELIABLE_WRITE bit is not set");
308            return op.hdl_err(ErrorCode::WriteNotPermitted, hdl);
309        }
310        Ok(hdl) // Characteristic value access
311    }
312
313    /// Returns characteristic information for the specified attribute.
314    fn characteristic_for_attr(&self, at: &Attr) -> Option<CharInfo> {
315        use private::Group;
316        let i = self.index(at);
317        // SAFETY: 0 <= i < self.attr.len()
318        let decl = unsafe { self.attr.get_unchecked(..=i).iter() }.rposition(Attr::is_char)?;
319        // SAFETY: 0 <= decl <= i < self.attr.len()
320        let end = unsafe { self.attr.get_unchecked(decl + 1..).iter() }
321            .position(|at| CharacteristicDef::is_next_group(at.typ))
322            .map_or(self.attr.len(), |j| decl + 1 + j);
323        if end <= i {
324            return None; // hdl is not part of a characteristic definition
325        }
326        // SAFETY: 0 <= decl < self.attr.len()
327        let dval = self.value(unsafe { self.attr.get_unchecked(decl) });
328        let vhdl = value_handle(dval);
329        // SAFETY: 0 <= decl < end <= self.attr.len()
330        let val = unsafe { self.attr.get_unchecked(decl + 1..end).iter() }
331            .position(|at| at.hdl == vhdl)
332            .map(|j| decl + 1 + j)
333            .expect("invalid characteristic");
334        // SAFETY: 0 < val < end <= self.attr.len()
335        let desc = unsafe { self.attr.get_unchecked(val + 1..end) };
336        // SAFETY: A valid handle is at indices 1-2
337        let props = Prop::from_bits_retain(unsafe { *dval.get_unchecked(0) });
338        let ext_props = props.contains(Prop::EXT_PROPS).then(|| {
339            (desc.iter().find(|&at| Attr::is_ext_props(at))).map_or(ExtProp::empty(), |at| {
340                ExtProp::from_bits_truncate(self.value(at).unpack().u16())
341            })
342        });
343        // SAFETY: 0 < desc < val < end <= self.attr.len()
344        let at = unsafe { self.attr.get_unchecked(val) };
345        Some(CharInfo {
346            props,
347            ext_props,
348            vhdl: at.hdl,
349            uuid: self.typ(at),
350            _desc: desc,
351        })
352    }
353
354    /// Returns all attributes within the specified handle range or [`None`] if
355    /// the handle range is empty.
356    fn subset(&self, hdls: HandleRange) -> Option<Subset> {
357        let i = self.try_get(hdls.start()).map_or_else(
358            |i| (i < self.attr.len()).then_some(i),
359            |at| Some(self.index(at)),
360        )?;
361        let j = (self.try_get(hdls.end()))
362            .map_or_else(|j| (j > 0).then_some(j), |j| Some(self.index(j) + 1))?;
363        Some(Subset::new(&self.attr, i..j))
364    }
365
366    /// Returns the attribute type.
367    #[inline]
368    fn typ(&self, at: &Attr) -> Uuid {
369        at.typ.map_or_else(
370            // SAFETY: 128-bit UUID is at self.data[at.val.0 - 16..]
371            || unsafe {
372                let i = usize::from(at.val.0) - 16;
373                Uuid::new_unchecked(u128::from_le_bytes(*self.data.as_ptr().add(i).cast()))
374            },
375            Uuid16::as_uuid,
376        )
377    }
378}
379
380/// Operations shared by [`Db`] and [`DbBuilder`].
381trait CommonOps {
382    /// Returns the attribute metadata.
383    fn attr(&self) -> &[Attr];
384
385    /// Returns the attribute value and 128-bit UUID buffer.
386    #[must_use]
387    fn data(&self) -> &[u8];
388
389    /// Returns the attribute for the specified handle or the index where that
390    /// handle can be inserted.
391    #[inline]
392    fn try_get(&self, hdl: Handle) -> std::result::Result<&Attr, usize> {
393        fn search(attr: &[Attr], hdl: Handle) -> std::result::Result<&Attr, usize> {
394            attr.binary_search_by(|at| at.hdl.cmp(&hdl))
395                // SAFETY: 0 <= i < attr.len()
396                .map(|i| unsafe { attr.get_unchecked(i) })
397        }
398        let i = usize::from(hdl) - 1;
399        // The attribute can exist at or, if there are gaps, before index `i`.
400        // Usually, the 1-based handle value should also be the 0-based index.
401        let prior = match self.attr().get(i) {
402            // SAFETY: 0 <= i < attr.len()
403            Some(at) if at.hdl == hdl => return Ok(unsafe { self.attr().get_unchecked(i) }),
404            // SAFETY: 0 <= i < attr.len()
405            Some(_) => unsafe { self.attr().get_unchecked(..i) },
406            None => self.attr(),
407        };
408        search(prior, hdl)
409    }
410
411    /// Returns the index of `at` in `self.attr()`.
412    #[inline(always)]
413    fn index(&self, at: &Attr) -> usize {
414        // TODO: Use `sub_ptr` when stabilized
415        // SAFETY: Caller only has access to attributes in self.attr() and
416        // `self.attr().as_ptr() <= at`
417        unsafe {
418            usize::try_from((at as *const Attr).offset_from(self.attr().as_ptr()))
419                .unwrap_unchecked()
420        }
421    }
422
423    /// Returns the attribute value.
424    #[inline(always)]
425    #[must_use]
426    fn value(&self, at: &Attr) -> &[u8] {
427        // SAFETY: self.data()[val] is always valid
428        unsafe { (self.data()).get_unchecked(usize::from(at.val.0)..usize::from(at.val.1)) }
429    }
430
431    /// Returns all attributes of the service group defined by `hdl` or [`None`]
432    /// if the handle does not refer to a service.
433    fn service_group(&self, hdl: Handle) -> Option<Subset> {
434        let Ok(at) = self.try_get(hdl) else { return None };
435        at.is_service().then(|| {
436            let i = self.index(at);
437            // SAFETY: 0 <= i < self.attr.len()
438            let j = unsafe { self.attr().get_unchecked(i + 1..).iter() }
439                .position(Attr::is_service)
440                .map_or(self.attr().len(), |j| i + 1 + j);
441            Subset::new(self.attr(), i..j)
442        })
443    }
444}
445
446impl CommonOps for Db {
447    #[inline(always)]
448    fn attr(&self) -> &[Attr] {
449        &self.attr
450    }
451
452    #[inline(always)]
453    fn data(&self) -> &[u8] {
454        &self.data
455    }
456}
457
458/// Trait implemented by [`ServiceDef`] and [`CharacteristicDef`] markers.
459pub trait Group: private::Group {}
460
461impl Group for ServiceDef {}
462impl Group for CharacteristicDef {}
463
464/// Database attribute information.
465#[derive(Clone, Copy, Debug)]
466pub struct DbEntry<'a, T> {
467    hdls: HandleRange,
468    typ: Uuid,
469    val: &'a [u8],
470    _marker: PhantomData<T>,
471}
472
473impl<'a, T> DbEntry<'a, T> {
474    /// Combines information about a database entry.
475    #[inline(always)]
476    #[must_use]
477    fn new(db: &'a Db, at: &Attr, end_hdl: Handle) -> Self {
478        Self {
479            hdls: HandleRange::new(at.hdl, end_hdl),
480            typ: db.typ(at),
481            val: db.value(at),
482            _marker: PhantomData,
483        }
484    }
485
486    /// Returns the attribute handle.
487    #[inline(always)]
488    #[must_use]
489    pub const fn handle(&self) -> Handle {
490        self.hdls.start()
491    }
492
493    /// Returns the attribute value.
494    #[inline(always)]
495    #[must_use]
496    pub const fn value(&self) -> &'a [u8] {
497        self.val
498    }
499}
500
501impl<T: Group> DbEntry<'_, T> {
502    /// Returns the group handle range.
503    #[inline(always)]
504    pub const fn handle_range(&self) -> HandleRange {
505        // We could use 0xFFFF for the end handle of the last service in the
506        // database. This avoids an extra round-trip for primary service
507        // discovery, but adds one for characteristic descriptor discovery.
508        // Leaving the handle range open allows new services to be added without
509        // invalidating existing ones.
510        self.hdls
511    }
512
513    /// Returns the service or characteristic UUID.
514    #[inline]
515    #[must_use]
516    pub fn uuid(&self) -> Uuid {
517        // SAFETY: Attribute value contains the UUID at UUID_OFF.
518        Uuid::try_from(unsafe { self.val.get_unchecked(T::UUID_OFF..) })
519            .map_or_else(|_| unreachable!("corrupt database"), |u| u)
520    }
521}
522
523impl DbEntry<'_, CharacteristicDef> {
524    /// Returns the characteristic properties.
525    #[inline]
526    #[must_use]
527    pub fn properties(&self) -> Prop {
528        Prop::from_bits_retain(self.val.unpack().u8())
529    }
530
531    /// Returns the handle of the value attribute.
532    #[inline]
533    #[must_use]
534    pub fn value_handle(&self) -> Handle {
535        value_handle(self.val)
536    }
537}
538
539impl DbEntry<'_, DescriptorDef> {
540    /// Returns the descriptor UUID.
541    #[inline(always)]
542    #[must_use]
543    pub const fn uuid(&self) -> Uuid {
544        self.typ
545    }
546}
547
548impl<'a, T> AsRef<[u8]> for DbEntry<'a, T> {
549    #[inline(always)]
550    fn as_ref(&self) -> &'a [u8] {
551        self.val
552    }
553}
554
555/// Information about a single characteristic.
556#[derive(Clone, Copy, Debug)]
557pub(super) struct CharInfo<'a> {
558    pub props: Prop,
559    pub ext_props: Option<ExtProp>,
560    pub vhdl: Handle,
561    pub uuid: Uuid,
562    _desc: &'a [Attr], // TODO: Remove
563}
564
565/// Attribute entry. `val` contains start and end indices of the attribute value
566/// in the data array. If `typ` is [`None`], then the 128-bit UUID is stored at
567/// `val.0 - 16..val.0` in the data array.
568#[derive(Clone, Copy, Debug)]
569#[must_use]
570struct Attr {
571    hdl: Handle,
572    typ: Option<Uuid16>,
573    val: (Idx, Idx),
574    perms: Perms,
575}
576
577impl Attr {
578    /// Returns whether the attribute is a service declaration.
579    #[inline(always)]
580    const fn is_service(&self) -> bool {
581        matches!(
582            self.typ,
583            Some(Declaration::PRIMARY_SERVICE | Declaration::SECONDARY_SERVICE)
584        )
585    }
586
587    /// Returns whether the attribute is a primary service declaration.
588    #[inline(always)]
589    const fn is_primary_service(&self) -> bool {
590        matches!(self.typ, Some(Declaration::PRIMARY_SERVICE))
591    }
592
593    /// Returns whether the attribute is an include declaration.
594    #[inline(always)]
595    const fn is_include(&self) -> bool {
596        matches!(self.typ, Some(Declaration::INCLUDE))
597    }
598
599    /// Returns whether the attribute is a characteristic declaration.
600    #[inline(always)]
601    const fn is_char(&self) -> bool {
602        matches!(self.typ, Some(Declaration::CHARACTERISTIC))
603    }
604
605    /// Returns whether the attribute is an extended properties descriptor.
606    #[inline(always)]
607    const fn is_ext_props(&self) -> bool {
608        matches!(
609            self.typ,
610            Some(Descriptor::CHARACTERISTIC_EXTENDED_PROPERTIES)
611        )
612    }
613
614    /// Returns the attribute value length.
615    #[inline(always)]
616    const fn len(&self) -> usize {
617        self.val.1 as usize - self.val.0 as usize
618    }
619}
620
621/// A non-empty subset of attributes.
622#[derive(Clone, Copy, Debug)]
623struct Subset<'a> {
624    off: usize,
625    attr: &'a [Attr],
626}
627
628impl<'a> Subset<'a> {
629    /// Creates a new subset of attributes.
630    #[inline(always)]
631    fn new(attr: &[Attr], r: Range<usize>) -> Subset {
632        debug_assert!(!r.is_empty() && r.end <= attr.len());
633        Subset {
634            off: r.start,
635            // SAFETY: r is a valid non-empty range
636            attr: unsafe { attr.get_unchecked(r) },
637        }
638    }
639
640    /// Returns the first attribute.
641    #[inline(always)]
642    fn first(&self) -> &'a Attr {
643        // SAFETY: self.attr is non-empty
644        unsafe { self.attr.get_unchecked(0) }
645    }
646
647    /// Returns the last attribute.
648    #[inline(always)]
649    fn last(&self) -> &'a Attr {
650        // SAFETY: self.attr is non-empty
651        unsafe { self.attr.get_unchecked(self.attr.len() - 1) }
652    }
653}
654
655struct GroupIter<'a, T, F> {
656    db: &'a Db,
657    it: iter::Peekable<slice::Iter<'a, Attr>>,
658    is_start: F,
659    _marker: PhantomData<T>,
660}
661
662impl<'a, T: Group, F: Fn(&Attr) -> bool> GroupIter<'a, T, F> {
663    /// Creates a new attribute group iterator.
664    #[inline(always)]
665    #[must_use]
666    fn new(db: &'a Db, it: &'a [Attr], is_start: F) -> Self {
667        Self {
668            db,
669            it: it.iter().peekable(),
670            is_start,
671            _marker: PhantomData,
672        }
673    }
674}
675
676impl<'a, T: Group, F: Fn(&Attr) -> bool> Iterator for GroupIter<'a, T, F> {
677    type Item = DbEntry<'a, T>;
678
679    fn next(&mut self) -> Option<Self::Item> {
680        let decl = self.it.find(|at| (self.is_start)(at))?;
681        let mut end = decl.hdl;
682        while !self.it.peek().map_or(true, |at| T::is_next_group(at.typ)) {
683            // SAFETY: `peek()` returned another attribute
684            end = unsafe { self.it.next().unwrap_unchecked().hdl };
685        }
686        Some(DbEntry::new(self.db, decl, end))
687    }
688
689    #[inline]
690    fn size_hint(&self) -> (usize, Option<usize>) {
691        self.it.size_hint()
692    }
693}
694
695impl<T: Group, F: Fn(&Attr) -> bool> iter::FusedIterator for GroupIter<'_, T, F> {}
696
697/// Returns the characteristic value attribute handle from the value of the
698/// characteristic declaration.
699#[inline]
700fn value_handle(decl: &[u8]) -> Handle {
701    Handle::new(decl.unpack().split_at(1).1.u16()).unwrap_or(Handle::MAX)
702}
703
704mod private {
705    use super::*;
706
707    /// Sealed implementation of an attribute group.
708    pub trait Group {
709        /// Offset of the UUID in the declaration value.
710        const UUID_OFF: usize = 0;
711
712        /// Returns whether the specified attribute type is not part of the
713        /// current group.
714        #[inline(always)]
715        #[must_use]
716        fn is_next_group(typ: Option<Uuid16>) -> bool {
717            matches!(
718                typ,
719                Some(Declaration::PRIMARY_SERVICE | Declaration::SECONDARY_SERVICE)
720            )
721        }
722    }
723
724    impl Group for ServiceDef {}
725
726    impl Group for CharacteristicDef {
727        const UUID_OFF: usize = 3;
728
729        #[inline(always)]
730        fn is_next_group(typ: Option<Uuid16>) -> bool {
731            // INC isn't needed, but including it improves the generated code
732            matches!(
733                typ,
734                Some(
735                    Declaration::PRIMARY_SERVICE
736                        | Declaration::SECONDARY_SERVICE
737                        | Declaration::INCLUDE
738                        | Declaration::CHARACTERISTIC
739                )
740            )
741        }
742    }
743}