1use 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#[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#[derive(Debug, Clone)]
40#[cfg_attr(feature = "defmt", derive(defmt::Format))]
41pub struct AdvertisementSet<'d> {
42    pub params: AdvertisementParameters,
44    pub data: Advertisement<'d>,
46}
47
48impl<'d> AdvertisementSet<'d> {
49    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#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69#[derive(Copy, Clone, Debug)]
70pub struct AdvertisementParameters {
71    pub primary_phy: PhyKind,
73
74    pub secondary_phy: PhyKind,
76
77    pub tx_power: TxPower,
79
80    pub timeout: Option<Duration>,
82
83    pub max_events: Option<u8>,
85
86    pub interval_min: Duration,
88
89    pub interval_max: Duration,
91
92    pub channel_map: Option<AdvChannelMap>,
94
95    pub filter_policy: AdvFilterPolicy,
97
98    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#[derive(Debug, Clone, Copy)]
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145pub enum Advertisement<'d> {
146    ConnectableScannableUndirected {
148        adv_data: &'d [u8],
150        scan_data: &'d [u8],
152    },
153    ConnectableNonscannableDirected {
155        peer: Address,
157    },
158    ConnectableNonscannableDirectedHighDuty {
160        peer: Address,
162    },
163    NonconnectableScannableUndirected {
165        adv_data: &'d [u8],
167        scan_data: &'d [u8],
169    },
170    NonconnectableNonscannableUndirected {
172        adv_data: &'d [u8],
174    },
175    ExtConnectableNonscannableUndirected {
177        adv_data: &'d [u8],
179    },
180    ExtConnectableNonscannableDirected {
182        peer: Address,
184        adv_data: &'d [u8],
186    },
187    ExtNonconnectableScannableUndirected {
189        scan_data: &'d [u8],
191    },
192    ExtNonconnectableScannableDirected {
194        peer: Address,
196        scan_data: &'d [u8],
198    },
199    ExtNonconnectableNonscannableUndirected {
201        anonymous: bool,
203        adv_data: &'d [u8],
205    },
206    ExtNonconnectableNonscannableDirected {
208        anonymous: bool,
210        peer: Address,
212        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
328pub const AD_FLAG_LE_LIMITED_DISCOVERABLE: u8 = 0b00000001;
330
331pub const LE_GENERAL_DISCOVERABLE: u8 = 0b00000010;
333
334pub const BR_EDR_NOT_SUPPORTED: u8 = 0b00000100;
336
337pub const SIMUL_LE_BR_CONTROLLER: u8 = 0b00001000;
339
340pub const SIMUL_LE_BR_HOST: u8 = 0b00010000;
342
343#[derive(Debug, Copy, Clone, PartialEq)]
345#[cfg_attr(feature = "defmt", derive(defmt::Format))]
346pub enum AdvertisementDataError {
347    TooLong,
349}
350
351#[derive(Debug, Copy, Clone)]
353#[cfg_attr(feature = "defmt", derive(defmt::Format))]
354pub enum AdStructure<'a> {
355    Flags(u8),
362
363    ServiceUuids16(&'a [[u8; 2]]),
366
367    ServiceUuids128(&'a [[u8; 16]]),
370
371    ServiceData16 {
374        uuid: [u8; 2],
376        data: &'a [u8],
378    },
379
380    CompleteLocalName(&'a [u8]),
384
385    ShortenedLocalName(&'a [u8]),
387
388    ManufacturerSpecificData {
390        company_identifier: u16,
392        payload: &'a [u8],
394    },
395
396    Unknown {
398        ty: u8,
400        data: &'a [u8],
402    },
403}
404
405impl AdStructure<'_> {
406    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    pub fn decode(data: &[u8]) -> impl Iterator<Item = Result<AdStructure<'_>, codec::Error>> {
463        AdStructureIter {
464            cursor: ReadCursor::new(data),
465        }
466    }
467}
468
469pub 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        match code {
485            0x01 => Ok(AdStructure::Flags(data[0])),
487            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            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            0x08 => Ok(AdStructure::ShortenedLocalName(data)),
513            0x09 => Ok(AdStructure::CompleteLocalName(data)),
515            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            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}