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