trouble_host/
advertise.rs

1//! Advertisement config.
2use bt_hci::param::AdvEventProps;
3pub use bt_hci::param::{AdvChannelMap, AdvFilterPolicy, AdvHandle, AdvSet, PhyKind};
4use embassy_time::Duration;
5
6use crate::cursor::{ReadCursor, WriteCursor};
7use crate::types::uuid::Uuid;
8use crate::{bt_hci_duration, codec, Address};
9
10/// Transmit power levels.
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12#[derive(Eq, PartialEq, Copy, Clone, Debug)]
13#[repr(i8)]
14#[allow(missing_docs)]
15pub enum TxPower {
16    Minus40dBm = -40,
17    Minus20dBm = -20,
18    Minus16dBm = -16,
19    Minus12dBm = -12,
20    Minus8dBm = -8,
21    Minus4dBm = -4,
22    ZerodBm = 0,
23    Plus2dBm = 2,
24    Plus3dBm = 3,
25    Plus4dBm = 4,
26    Plus5dBm = 5,
27    Plus6dBm = 6,
28    Plus7dBm = 7,
29    Plus8dBm = 8,
30    Plus10dBm = 10,
31    Plus12dBm = 12,
32    Plus14dBm = 14,
33    Plus16dBm = 16,
34    Plus18dBm = 18,
35    Plus20dBm = 20,
36}
37
38/// Configuriation for a single advertisement set.
39#[derive(Debug, Clone)]
40#[cfg_attr(feature = "defmt", derive(defmt::Format))]
41pub struct AdvertisementSet<'d> {
42    /// Parameters for the advertisement.
43    pub params: AdvertisementParameters,
44    /// Advertisement data.
45    pub data: Advertisement<'d>,
46}
47
48impl<'d> AdvertisementSet<'d> {
49    /// Create a new advertisement set that can be passed to advertisement functions.
50    pub fn handles<const N: usize>(sets: &[AdvertisementSet<'d>; N]) -> [AdvSet; N] {
51        const NEW_SET: AdvSet = AdvSet {
52            adv_handle: AdvHandle::new(0),
53            duration: bt_hci::param::Duration::from_u16(0),
54            max_ext_adv_events: 0,
55        };
56
57        let mut ret = [NEW_SET; N];
58        for (i, set) in sets.iter().enumerate() {
59            ret[i].adv_handle = AdvHandle::new(i as u8);
60            ret[i].duration = bt_hci_duration(set.params.timeout.unwrap_or(embassy_time::Duration::from_micros(0)));
61            ret[i].max_ext_adv_events = set.params.max_events.unwrap_or(0);
62        }
63        ret
64    }
65}
66
67/// Parameters for an advertisement.
68#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69#[derive(Copy, Clone, Debug)]
70pub struct AdvertisementParameters {
71    /// Phy selection
72    pub primary_phy: PhyKind,
73
74    /// Secondary phy selection
75    pub secondary_phy: PhyKind,
76
77    /// Transmission power
78    pub tx_power: TxPower,
79
80    /// Timeout duration
81    pub timeout: Option<Duration>,
82
83    /// Max advertising events
84    pub max_events: Option<u8>,
85
86    /// Minimum advertising interval
87    pub interval_min: Duration,
88
89    /// Maximum advertising interval
90    pub interval_max: Duration,
91
92    /// Which advertising channels to use
93    pub channel_map: Option<AdvChannelMap>,
94
95    /// Filtering policy
96    pub filter_policy: AdvFilterPolicy,
97
98    /// Fragmentation preference
99    pub fragment: bool,
100}
101
102impl Default for AdvertisementParameters {
103    fn default() -> Self {
104        Self {
105            primary_phy: PhyKind::Le1M,
106            secondary_phy: PhyKind::Le1M,
107            tx_power: TxPower::ZerodBm,
108            timeout: None,
109            max_events: None,
110            interval_min: Duration::from_millis(160),
111            interval_max: Duration::from_millis(160),
112            filter_policy: AdvFilterPolicy::default(),
113            channel_map: None,
114            fragment: false,
115        }
116    }
117}
118
119#[derive(Debug, Clone, Copy)]
120#[cfg_attr(feature = "defmt", derive(defmt::Format))]
121pub(crate) struct RawAdvertisement<'d> {
122    pub(crate) props: AdvEventProps,
123    pub(crate) adv_data: &'d [u8],
124    pub(crate) scan_data: &'d [u8],
125    pub(crate) peer: Option<Address>,
126}
127
128impl Default for RawAdvertisement<'_> {
129    fn default() -> Self {
130        Self {
131            props: AdvEventProps::new()
132                .set_connectable_adv(true)
133                .set_scannable_adv(true)
134                .set_legacy_adv(true),
135            adv_data: &[],
136            scan_data: &[],
137            peer: None,
138        }
139    }
140}
141
142/// Advertisement payload depending on which advertisement kind requested.
143#[derive(Debug, Clone, Copy)]
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145pub enum Advertisement<'d> {
146    /// Connectable and scannable undirected advertisement.
147    ConnectableScannableUndirected {
148        /// Advertisement data.
149        adv_data: &'d [u8],
150        /// Scan data.
151        scan_data: &'d [u8],
152    },
153    /// Connectable and non-scannable directed advertisement.
154    ConnectableNonscannableDirected {
155        /// Address of the peer to direct the advertisement to.
156        peer: Address,
157    },
158    /// Connectable and non-scannable directed advertisement with high duty cycle.
159    ConnectableNonscannableDirectedHighDuty {
160        /// Address of the peer to direct the advertisement to.
161        peer: Address,
162    },
163    /// Nonconnectable and scannable undirected advertisement.
164    NonconnectableScannableUndirected {
165        /// Advertisement data.
166        adv_data: &'d [u8],
167        /// Scan data.
168        scan_data: &'d [u8],
169    },
170    /// Nonconnectable and nonscannable undirected advertisement.
171    NonconnectableNonscannableUndirected {
172        /// Advertisement data.
173        adv_data: &'d [u8],
174    },
175    /// Extended connectable and non-scannable undirected advertisement.
176    ExtConnectableNonscannableUndirected {
177        /// Advertisement data.
178        adv_data: &'d [u8],
179    },
180    /// Extended connectable and non-scannable directed advertisement.
181    ExtConnectableNonscannableDirected {
182        /// Address of the peer to direct the advertisement to.
183        peer: Address,
184        /// Advertisement data.
185        adv_data: &'d [u8],
186    },
187    /// Extended nonconnectable and scannable undirected advertisement.
188    ExtNonconnectableScannableUndirected {
189        /// Scan data.
190        scan_data: &'d [u8],
191    },
192    /// Extended nonconnectable and scannable directed advertisement.
193    ExtNonconnectableScannableDirected {
194        /// Address of the peer to direct the advertisement to.
195        peer: Address,
196        /// Scan data.
197        scan_data: &'d [u8],
198    },
199    /// Extended nonconnectable and nonscannable undirected advertisement.
200    ExtNonconnectableNonscannableUndirected {
201        /// Whether the advertisement is anonymous.
202        anonymous: bool,
203        /// Advertisement data.
204        adv_data: &'d [u8],
205    },
206    /// Extended nonconnectable and nonscannable directed advertisement.
207    ExtNonconnectableNonscannableDirected {
208        /// Whether the advertisement is anonymous.
209        anonymous: bool,
210        /// Address of the peer to direct the advertisement to.
211        peer: Address,
212        /// Advertisement data.
213        adv_data: &'d [u8],
214    },
215}
216
217impl<'d> From<Advertisement<'d>> for RawAdvertisement<'d> {
218    fn from(val: Advertisement<'d>) -> RawAdvertisement<'d> {
219        match val {
220            Advertisement::ConnectableScannableUndirected { adv_data, scan_data } => RawAdvertisement {
221                props: AdvEventProps::new()
222                    .set_connectable_adv(true)
223                    .set_scannable_adv(true)
224                    .set_anonymous_adv(false)
225                    .set_legacy_adv(true),
226                adv_data,
227                scan_data,
228                peer: None,
229            },
230            Advertisement::ConnectableNonscannableDirected { peer } => RawAdvertisement {
231                props: AdvEventProps::new()
232                    .set_connectable_adv(true)
233                    .set_scannable_adv(false)
234                    .set_directed_adv(true)
235                    .set_anonymous_adv(false)
236                    .set_legacy_adv(true),
237                adv_data: &[],
238                scan_data: &[],
239                peer: Some(peer),
240            },
241            Advertisement::ConnectableNonscannableDirectedHighDuty { peer } => RawAdvertisement {
242                props: AdvEventProps::new()
243                    .set_connectable_adv(true)
244                    .set_scannable_adv(false)
245                    .set_high_duty_cycle_directed_connectable_adv(true)
246                    .set_anonymous_adv(false)
247                    .set_legacy_adv(true),
248                adv_data: &[],
249                scan_data: &[],
250                peer: Some(peer),
251            },
252            Advertisement::NonconnectableScannableUndirected { adv_data, scan_data } => RawAdvertisement {
253                props: AdvEventProps::new()
254                    .set_connectable_adv(false)
255                    .set_scannable_adv(true)
256                    .set_anonymous_adv(false)
257                    .set_legacy_adv(true),
258                adv_data,
259                scan_data,
260                peer: None,
261            },
262            Advertisement::NonconnectableNonscannableUndirected { adv_data } => RawAdvertisement {
263                props: AdvEventProps::new()
264                    .set_connectable_adv(false)
265                    .set_scannable_adv(false)
266                    .set_anonymous_adv(false)
267                    .set_legacy_adv(true),
268                adv_data,
269                scan_data: &[],
270                peer: None,
271            },
272            Advertisement::ExtConnectableNonscannableUndirected { adv_data } => RawAdvertisement {
273                props: AdvEventProps::new().set_connectable_adv(true).set_scannable_adv(false),
274                adv_data,
275                scan_data: &[],
276                peer: None,
277            },
278            Advertisement::ExtConnectableNonscannableDirected { adv_data, peer } => RawAdvertisement {
279                props: AdvEventProps::new().set_connectable_adv(true).set_scannable_adv(false),
280                adv_data,
281                scan_data: &[],
282                peer: Some(peer),
283            },
284
285            Advertisement::ExtNonconnectableScannableUndirected { scan_data } => RawAdvertisement {
286                props: AdvEventProps::new().set_connectable_adv(false).set_scannable_adv(true),
287                adv_data: &[],
288                scan_data,
289                peer: None,
290            },
291            Advertisement::ExtNonconnectableScannableDirected { scan_data, peer } => RawAdvertisement {
292                props: AdvEventProps::new()
293                    .set_connectable_adv(false)
294                    .set_scannable_adv(true)
295                    .set_directed_adv(true),
296                adv_data: &[],
297                scan_data,
298                peer: Some(peer),
299            },
300            Advertisement::ExtNonconnectableNonscannableUndirected { adv_data, anonymous } => RawAdvertisement {
301                props: AdvEventProps::new()
302                    .set_connectable_adv(false)
303                    .set_scannable_adv(false)
304                    .set_anonymous_adv(anonymous)
305                    .set_directed_adv(false),
306                adv_data,
307                scan_data: &[],
308                peer: None,
309            },
310            Advertisement::ExtNonconnectableNonscannableDirected {
311                adv_data,
312                peer,
313                anonymous,
314            } => RawAdvertisement {
315                props: AdvEventProps::new()
316                    .set_connectable_adv(false)
317                    .set_scannable_adv(false)
318                    .set_anonymous_adv(anonymous)
319                    .set_directed_adv(true),
320                adv_data,
321                scan_data: &[],
322                peer: Some(peer),
323            },
324        }
325    }
326}
327
328/// Le advertisement.
329pub const AD_FLAG_LE_LIMITED_DISCOVERABLE: u8 = 0b00000001;
330
331/// Discoverable flag.
332pub const LE_GENERAL_DISCOVERABLE: u8 = 0b00000010;
333
334/// BR/EDR not supported.
335pub const BR_EDR_NOT_SUPPORTED: u8 = 0b00000100;
336
337/// Simultaneous LE and BR/EDR to same device capable (controller).
338pub const SIMUL_LE_BR_CONTROLLER: u8 = 0b00001000;
339
340/// Simultaneous LE and BR/EDR to same device capable (Host).
341pub const SIMUL_LE_BR_HOST: u8 = 0b00010000;
342
343/// Error encoding advertisement data.
344#[derive(Debug, Copy, Clone, PartialEq)]
345#[cfg_attr(feature = "defmt", derive(defmt::Format))]
346pub enum AdvertisementDataError {
347    /// Advertisement data too long for buffer.
348    TooLong,
349}
350
351/// Advertisement data structure.
352#[derive(Debug, Copy, Clone)]
353#[cfg_attr(feature = "defmt", derive(defmt::Format))]
354pub enum AdStructure<'a> {
355    /// Device flags and baseband capabilities.
356    ///
357    /// This should be sent if any flags apply to the device. If not (ie. the value sent would be
358    /// 0), this may be omitted.
359    ///
360    /// Must not be used in scan response data.
361    Flags(u8),
362
363    /// List of 16-bit service UUIDs.
364    /// The UUID data matches the ble network's endian order (should be little endian).
365    ServiceUuids16(&'a [[u8; 2]]),
366
367    /// List of 128-bit service UUIDs.
368    /// The UUID data matches the ble network's endian order (should be little endian).
369    ServiceUuids128(&'a [[u8; 16]]),
370
371    /// Service data with 16-bit service UUID.
372    /// The UUID data matches the ble network's endian order (should be little endian).
373    ServiceData16 {
374        /// The 16-bit service UUID.
375        uuid: [u8; 2],
376        /// The associated service data. May be empty.
377        data: &'a [u8],
378    },
379
380    /// Sets the full (unabbreviated) device name.
381    ///
382    /// This will be shown to the user when this device is found.
383    CompleteLocalName(&'a [u8]),
384
385    /// Sets the shortened device name.
386    ShortenedLocalName(&'a [u8]),
387
388    /// Set manufacturer specific data
389    ManufacturerSpecificData {
390        /// Company identifier.
391        company_identifier: u16,
392        /// Payload data.
393        payload: &'a [u8],
394    },
395
396    /// An unknown or unimplemented AD structure stored as raw bytes.
397    Unknown {
398        /// Type byte.
399        ty: u8,
400        /// Raw data transmitted after the type.
401        data: &'a [u8],
402    },
403}
404
405impl AdStructure<'_> {
406    /// Encode a slice of advertisement structures into a buffer.
407    pub fn encode_slice(data: &[AdStructure<'_>], dest: &mut [u8]) -> Result<usize, codec::Error> {
408        let mut w = WriteCursor::new(dest);
409        for item in data.iter() {
410            item.encode(&mut w)?;
411        }
412        Ok(w.len())
413    }
414
415    pub(crate) fn encode(&self, w: &mut WriteCursor<'_>) -> Result<(), codec::Error> {
416        match self {
417            AdStructure::Flags(flags) => {
418                w.append(&[0x02, 0x01, *flags])?;
419            }
420            AdStructure::ServiceUuids16(uuids) => {
421                w.append(&[(uuids.len() * 2 + 1) as u8, 0x02])?;
422                for uuid in uuids.iter() {
423                    w.write_ref(&Uuid::Uuid16(*uuid))?;
424                }
425            }
426            AdStructure::ServiceUuids128(uuids) => {
427                w.append(&[(uuids.len() * 16 + 1) as u8, 0x07])?;
428                for uuid in uuids.iter() {
429                    w.write_ref(&Uuid::Uuid128(*uuid))?;
430                }
431            }
432            AdStructure::ShortenedLocalName(name) => {
433                w.append(&[(name.len() + 1) as u8, 0x08])?;
434                w.append(name)?;
435            }
436            AdStructure::CompleteLocalName(name) => {
437                w.append(&[(name.len() + 1) as u8, 0x09])?;
438                w.append(name)?;
439            }
440            AdStructure::ServiceData16 { uuid, data } => {
441                w.append(&[(data.len() + 3) as u8, 0x16])?;
442                w.write(Uuid::Uuid16(*uuid))?;
443                w.append(data)?;
444            }
445            AdStructure::ManufacturerSpecificData {
446                company_identifier,
447                payload,
448            } => {
449                w.append(&[(payload.len() + 3) as u8, 0xff])?;
450                w.write(*company_identifier)?;
451                w.append(payload)?;
452            }
453            AdStructure::Unknown { ty, data } => {
454                w.append(&[(data.len() + 1) as u8, *ty])?;
455                w.append(data)?;
456            }
457        }
458        Ok(())
459    }
460
461    /// Decode a slice of advertisement structures from a buffer.
462    pub fn decode(data: &[u8]) -> impl Iterator<Item = Result<AdStructure<'_>, codec::Error>> {
463        AdStructureIter {
464            cursor: ReadCursor::new(data),
465        }
466    }
467}
468
469/// Iterator over advertisement structures.
470pub struct AdStructureIter<'d> {
471    cursor: ReadCursor<'d>,
472}
473
474impl<'d> AdStructureIter<'d> {
475    fn read(&mut self) -> Result<AdStructure<'d>, codec::Error> {
476        let len: u8 = self.cursor.read()?;
477        if len < 2 {
478            return Err(codec::Error::InvalidValue);
479        }
480        let code: u8 = self.cursor.read()?;
481        let data = self.cursor.slice(len as usize - 1)?;
482        // These codes correspond to the table in 2.3 Common Data Types table `assigned_numbers/core/ad_types.yaml` from <https://www.bluetooth.com/specifications/assigned-numbers/>
483        // Look there for more information on each.
484        match code {
485            // Flags
486            0x01 => Ok(AdStructure::Flags(data[0])),
487            // Incomplete List of 16-bit Service or Service Class UUIDs
488            // 0x02 =>
489            // Complete List of 16-bit Service or Service Class UUIDs
490            0x03 => match zerocopy::FromBytes::ref_from_bytes(data) {
491                Ok(x) => Ok(AdStructure::ServiceUuids16(x)),
492                Err(e) => {
493                    let _ = zerocopy::SizeError::from(e);
494                    Err(codec::Error::InvalidValue)
495                }
496            },
497            // Incomplete List of 32-bit Service or Service Class UUIDs
498            // 0x04 =>
499            // Complete List of 32-bit Service or Service Class UUIDs
500            // 0x05
501            // Incomplete List of 128-bit Service or Service Class UUIDs
502            // 0x06
503            // Complete List of 128-bit Service or Service Class UUIDs
504            0x07 => match zerocopy::FromBytes::ref_from_bytes(data) {
505                Ok(x) => Ok(AdStructure::ServiceUuids128(x)),
506                Err(e) => {
507                    let _ = zerocopy::SizeError::from(e);
508                    Err(codec::Error::InvalidValue)
509                }
510            },
511            // Shortened Local Name
512            0x08 => Ok(AdStructure::ShortenedLocalName(data)),
513            // Complete Local Name
514            0x09 => Ok(AdStructure::CompleteLocalName(data)),
515            /*
516            0x0A Tx Power Level
517            0x0D Class of Device
518            0x0E Simple Pairing Hash C-192
519            0x0F Simple Pairing Randomizer R-192
520            0x10 Device ID Device: ID Profile (when used in EIR data)
521            0x10 Security Manager TK Value when used in OOB data blocks
522            0x11 Security Manager Out of Band Flags
523            0x12 Peripheral Connection Interval Range
524            0x14 List of 16-bit Service Solicitation UUIDs
525            0x15 List of 128-bit Service Solicitation UUIDs
526            */
527            // Service Data - 16-bit UUID
528            0x16 => {
529                if data.len() < 2 {
530                    return Err(codec::Error::InvalidValue);
531                }
532                let uuid = data[0..2].try_into().unwrap();
533                Ok(AdStructure::ServiceData16 { uuid, data: &data[2..] })
534            }
535            /*
536            0x17 Public Target Address
537            0x18 Random Target Address
538            0x19 Appearance
539            0x1A Advertising Interval
540            0x1B LE Bluetooth Device Address
541            0x1C LE Role
542            0x1D Simple Pairing Hash C-256
543            0x1E Simple Pairing Randomizer R-256
544            0x1F List of 32-bit Service Solicitation UUIDs
545            0x20 Service Data - 32-bit UUID
546            0x21 Service Data - 128-bit UUID
547            0x22 LE Secure Connections Confirmation Value
548            0x23 LE Secure Connections Random Value
549            0x24 URI
550            0x25 Indoor Positioning
551            0x26 Transport Discovery Data
552            0x27 LE Supported Features
553            0x28 Channel Map Update Indication
554            0x29 PB-ADV
555            0x2A Mesh Message
556            0x2B Mesh Beacon
557            0x2C BIGInfo
558            0x2D Broadcast_Code
559            0x2E Resolvable Set Identifier
560            0x2F Advertising Interval - long
561            0x30 Broadcast_Name
562            0x31 Encrypted Advertising Data
563            0x32 Periodic Advertising Response Timing
564            0x34 Electronic Shelf Label
565            0x3D 3D Information Data
566            */
567            // Manufacturer Specific Data
568            0xff if data.len() >= 2 => Ok(AdStructure::ManufacturerSpecificData {
569                company_identifier: u16::from_le_bytes([data[0], data[1]]),
570                payload: &data[2..],
571            }),
572            ty => Ok(AdStructure::Unknown { ty, data }),
573        }
574    }
575}
576
577impl<'d> Iterator for AdStructureIter<'d> {
578    type Item = Result<AdStructure<'d>, codec::Error>;
579    fn next(&mut self) -> Option<Self::Item> {
580        if self.cursor.available() == 0 {
581            return None;
582        }
583        Some(self.read())
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use super::*;
590
591    #[test]
592    fn adv_name_truncate() {
593        let mut adv_data = [0; 31];
594        assert!(AdStructure::encode_slice(
595            &[
596                AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
597                AdStructure::ServiceUuids16(&[[0x0f, 0x18]]),
598                AdStructure::CompleteLocalName(b"12345678901234567890123"),
599            ],
600            &mut adv_data[..],
601        )
602        .is_err());
603    }
604}