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}