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::{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 = 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#[cfg_attr(feature = "defmt", derive(defmt::Format))]
73#[derive(Copy, Clone, Debug)]
74pub struct AdvertisementParameters {
75    pub primary_phy: PhyKind,
77
78    pub secondary_phy: PhyKind,
80
81    pub tx_power: TxPower,
83
84    pub timeout: Option<Duration>,
86
87    pub max_events: Option<u8>,
89
90    pub interval_min: Duration,
92
93    pub interval_max: Duration,
95
96    pub channel_map: Option<AdvChannelMap>,
98
99    pub filter_policy: AdvFilterPolicy,
101
102    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#[derive(Debug, Clone, Copy)]
148#[cfg_attr(feature = "defmt", derive(defmt::Format))]
149pub enum Advertisement<'d> {
150    ConnectableScannableUndirected {
152        adv_data: &'d [u8],
154        scan_data: &'d [u8],
156    },
157    ConnectableNonscannableDirected {
159        peer: Address,
161    },
162    ConnectableNonscannableDirectedHighDuty {
164        peer: Address,
166    },
167    NonconnectableScannableUndirected {
169        adv_data: &'d [u8],
171        scan_data: &'d [u8],
173    },
174    NonconnectableNonscannableUndirected {
176        adv_data: &'d [u8],
178    },
179    ExtConnectableNonscannableUndirected {
181        adv_data: &'d [u8],
183    },
184    ExtConnectableNonscannableDirected {
186        peer: Address,
188        adv_data: &'d [u8],
190    },
191    ExtNonconnectableScannableUndirected {
193        scan_data: &'d [u8],
195    },
196    ExtNonconnectableScannableDirected {
198        peer: Address,
200        scan_data: &'d [u8],
202    },
203    ExtNonconnectableNonscannableUndirected {
205        anonymous: bool,
207        adv_data: &'d [u8],
209    },
210    ExtNonconnectableNonscannableDirected {
212        anonymous: bool,
214        peer: Address,
216        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
332pub const AD_FLAG_LE_LIMITED_DISCOVERABLE: u8 = 0b00000001;
334
335pub const LE_GENERAL_DISCOVERABLE: u8 = 0b00000010;
337
338pub const BR_EDR_NOT_SUPPORTED: u8 = 0b00000100;
340
341pub const SIMUL_LE_BR_CONTROLLER: u8 = 0b00001000;
343
344pub const SIMUL_LE_BR_HOST: u8 = 0b00010000;
346
347#[derive(Debug, Copy, Clone, PartialEq)]
349#[cfg_attr(feature = "defmt", derive(defmt::Format))]
350pub enum AdvertisementDataError {
351    TooLong,
353}
354
355#[derive(Debug, Copy, Clone)]
357#[cfg_attr(feature = "defmt", derive(defmt::Format))]
358pub enum AdStructure<'a> {
359    Flags(u8),
366
367    ServiceUuids16(&'a [[u8; 2]]),
370
371    ServiceUuids128(&'a [[u8; 16]]),
374
375    ServiceData16 {
378        uuid: [u8; 2],
380        data: &'a [u8],
382    },
383
384    CompleteLocalName(&'a [u8]),
388
389    ShortenedLocalName(&'a [u8]),
391
392    ManufacturerSpecificData {
394        company_identifier: u16,
396        payload: &'a [u8],
398    },
399
400    Unknown {
402        ty: u8,
404        data: &'a [u8],
406    },
407}
408
409impl AdStructure<'_> {
410    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    pub fn decode(data: &[u8]) -> impl Iterator<Item = Result<AdStructure<'_>, codec::Error>> {
467        AdStructureIter {
468            cursor: ReadCursor::new(data),
469        }
470    }
471}
472
473pub 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        match code {
489            0x01 => Ok(AdStructure::Flags(data[0])),
491            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            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            0x08 => Ok(AdStructure::ShortenedLocalName(data)),
517            0x09 => Ok(AdStructure::CompleteLocalName(data)),
519            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            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}