1use bt_hci::param::{AddrKind, AdvEventProps, BdAddr};
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 pub address: Option<BdAddr>,
49}
50
51impl<'d> AdvertisementSet<'d> {
52 pub fn handles<const N: usize>(sets: &[AdvertisementSet<'d>; N]) -> [AdvSet; N] {
54 const NEW_SET: AdvSet = AdvSet {
55 adv_handle: AdvHandle::new(0),
56 duration: bt_hci::param::Duration::from_u16(0),
57 max_ext_adv_events: 0,
58 };
59
60 let mut ret = [NEW_SET; N];
61 for (i, set) in sets.iter().enumerate() {
62 ret[i].adv_handle = AdvHandle::new(i as u8);
63 ret[i].duration = bt_hci_duration(set.params.timeout.unwrap_or(embassy_time::Duration::from_micros(0)));
64 ret[i].max_ext_adv_events = set.params.max_events.unwrap_or(0);
65 }
66 ret
67 }
68}
69
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
72#[derive(Copy, Clone, Debug)]
73pub struct AdvertisementParameters {
74 pub primary_phy: PhyKind,
76
77 pub secondary_phy: PhyKind,
79
80 pub tx_power: TxPower,
82
83 pub timeout: Option<Duration>,
85
86 pub max_events: Option<u8>,
88
89 pub interval_min: Duration,
91
92 pub interval_max: Duration,
94
95 pub channel_map: Option<AdvChannelMap>,
97
98 pub filter_policy: AdvFilterPolicy,
100
101 pub fragment: bool,
103
104 pub own_addr_kind: Option<AddrKind>,
107}
108
109impl Default for AdvertisementParameters {
110 fn default() -> Self {
111 Self {
112 primary_phy: PhyKind::Le1M,
113 secondary_phy: PhyKind::Le1M,
114 tx_power: TxPower::ZerodBm,
115 timeout: None,
116 max_events: None,
117 interval_min: Duration::from_millis(160),
118 interval_max: Duration::from_millis(160),
119 filter_policy: AdvFilterPolicy::default(),
120 channel_map: None,
121 fragment: false,
122 own_addr_kind: None,
123 }
124 }
125}
126
127#[derive(Debug, Clone, Copy)]
128#[cfg_attr(feature = "defmt", derive(defmt::Format))]
129pub(crate) struct RawAdvertisement<'d> {
130 pub(crate) props: AdvEventProps,
131 pub(crate) adv_data: &'d [u8],
132 pub(crate) scan_data: &'d [u8],
133 pub(crate) peer: Option<Address>,
134}
135
136impl Default for RawAdvertisement<'_> {
137 fn default() -> Self {
138 Self {
139 props: AdvEventProps::new()
140 .set_connectable_adv(true)
141 .set_scannable_adv(true)
142 .set_legacy_adv(true),
143 adv_data: &[],
144 scan_data: &[],
145 peer: None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy)]
152#[cfg_attr(feature = "defmt", derive(defmt::Format))]
153pub enum Advertisement<'d> {
154 ConnectableScannableUndirected {
156 adv_data: &'d [u8],
158 scan_data: &'d [u8],
160 },
161 ConnectableNonscannableDirected {
163 peer: Address,
165 },
166 ConnectableNonscannableDirectedHighDuty {
168 peer: Address,
170 },
171 NonconnectableScannableUndirected {
173 adv_data: &'d [u8],
175 scan_data: &'d [u8],
177 },
178 NonconnectableNonscannableUndirected {
180 adv_data: &'d [u8],
182 },
183 ExtConnectableNonscannableUndirected {
185 adv_data: &'d [u8],
187 },
188 ExtConnectableNonscannableDirected {
190 peer: Address,
192 adv_data: &'d [u8],
194 },
195 ExtNonconnectableScannableUndirected {
197 scan_data: &'d [u8],
199 },
200 ExtNonconnectableScannableDirected {
202 peer: Address,
204 scan_data: &'d [u8],
206 },
207 ExtNonconnectableNonscannableUndirected {
209 anonymous: bool,
211 adv_data: &'d [u8],
213 },
214 ExtNonconnectableNonscannableDirected {
216 anonymous: bool,
218 peer: Address,
220 adv_data: &'d [u8],
222 },
223}
224
225impl<'d> Advertisement<'d> {
226 pub(crate) fn is_valid(&self) -> bool {
227 match self {
228 Advertisement::ConnectableScannableUndirected { adv_data, .. }
229 | Advertisement::ExtConnectableNonscannableUndirected { adv_data }
230 | Advertisement::ExtConnectableNonscannableDirected { adv_data, .. } => {
231 adv_data.len() >= 3 && adv_data[0] == 2 && adv_data[1] == 1 && (adv_data[2] & BR_EDR_NOT_SUPPORTED) != 0
233 }
234 _ => true,
235 }
236 }
237}
238impl<'d> From<Advertisement<'d>> for RawAdvertisement<'d> {
239 fn from(val: Advertisement<'d>) -> RawAdvertisement<'d> {
240 match val {
241 Advertisement::ConnectableScannableUndirected { adv_data, scan_data } => RawAdvertisement {
242 props: AdvEventProps::new()
243 .set_connectable_adv(true)
244 .set_scannable_adv(true)
245 .set_anonymous_adv(false)
246 .set_legacy_adv(true),
247 adv_data,
248 scan_data,
249 peer: None,
250 },
251 Advertisement::ConnectableNonscannableDirected { peer } => RawAdvertisement {
252 props: AdvEventProps::new()
253 .set_connectable_adv(true)
254 .set_scannable_adv(false)
255 .set_directed_adv(true)
256 .set_anonymous_adv(false)
257 .set_legacy_adv(true),
258 adv_data: &[],
259 scan_data: &[],
260 peer: Some(peer),
261 },
262 Advertisement::ConnectableNonscannableDirectedHighDuty { peer } => RawAdvertisement {
263 props: AdvEventProps::new()
264 .set_connectable_adv(true)
265 .set_scannable_adv(false)
266 .set_high_duty_cycle_directed_connectable_adv(true)
267 .set_anonymous_adv(false)
268 .set_legacy_adv(true),
269 adv_data: &[],
270 scan_data: &[],
271 peer: Some(peer),
272 },
273 Advertisement::NonconnectableScannableUndirected { adv_data, scan_data } => RawAdvertisement {
274 props: AdvEventProps::new()
275 .set_connectable_adv(false)
276 .set_scannable_adv(true)
277 .set_anonymous_adv(false)
278 .set_legacy_adv(true),
279 adv_data,
280 scan_data,
281 peer: None,
282 },
283 Advertisement::NonconnectableNonscannableUndirected { adv_data } => RawAdvertisement {
284 props: AdvEventProps::new()
285 .set_connectable_adv(false)
286 .set_scannable_adv(false)
287 .set_anonymous_adv(false)
288 .set_legacy_adv(true),
289 adv_data,
290 scan_data: &[],
291 peer: None,
292 },
293 Advertisement::ExtConnectableNonscannableUndirected { adv_data } => RawAdvertisement {
294 props: AdvEventProps::new().set_connectable_adv(true).set_scannable_adv(false),
295 adv_data,
296 scan_data: &[],
297 peer: None,
298 },
299 Advertisement::ExtConnectableNonscannableDirected { adv_data, peer } => RawAdvertisement {
300 props: AdvEventProps::new().set_connectable_adv(true).set_scannable_adv(false),
301 adv_data,
302 scan_data: &[],
303 peer: Some(peer),
304 },
305
306 Advertisement::ExtNonconnectableScannableUndirected { scan_data } => RawAdvertisement {
307 props: AdvEventProps::new().set_connectable_adv(false).set_scannable_adv(true),
308 adv_data: &[],
309 scan_data,
310 peer: None,
311 },
312 Advertisement::ExtNonconnectableScannableDirected { scan_data, peer } => RawAdvertisement {
313 props: AdvEventProps::new()
314 .set_connectable_adv(false)
315 .set_scannable_adv(true)
316 .set_directed_adv(true),
317 adv_data: &[],
318 scan_data,
319 peer: Some(peer),
320 },
321 Advertisement::ExtNonconnectableNonscannableUndirected { adv_data, anonymous } => RawAdvertisement {
322 props: AdvEventProps::new()
323 .set_connectable_adv(false)
324 .set_scannable_adv(false)
325 .set_anonymous_adv(anonymous)
326 .set_directed_adv(false),
327 adv_data,
328 scan_data: &[],
329 peer: None,
330 },
331 Advertisement::ExtNonconnectableNonscannableDirected {
332 adv_data,
333 peer,
334 anonymous,
335 } => RawAdvertisement {
336 props: AdvEventProps::new()
337 .set_connectable_adv(false)
338 .set_scannable_adv(false)
339 .set_anonymous_adv(anonymous)
340 .set_directed_adv(true),
341 adv_data,
342 scan_data: &[],
343 peer: Some(peer),
344 },
345 }
346 }
347}
348
349pub const AD_FLAG_LE_LIMITED_DISCOVERABLE: u8 = 0b00000001;
351
352pub const LE_GENERAL_DISCOVERABLE: u8 = 0b00000010;
354
355pub const BR_EDR_NOT_SUPPORTED: u8 = 0b00000100;
357
358pub const SIMUL_LE_BR_CONTROLLER: u8 = 0b00001000;
360
361pub const SIMUL_LE_BR_HOST: u8 = 0b00010000;
363
364#[derive(Debug, Copy, Clone, PartialEq)]
366#[cfg_attr(feature = "defmt", derive(defmt::Format))]
367pub enum AdvertisementDataError {
368 TooLong,
370}
371
372#[derive(Debug, Copy, Clone)]
374#[cfg_attr(feature = "defmt", derive(defmt::Format))]
375pub enum AdStructure<'a> {
376 Flags(u8),
383
384 IncompleteServiceUuids16(&'a [[u8; 2]]),
387
388 CompleteServiceUuids16(&'a [[u8; 2]]),
391
392 IncompleteServiceUuids32(&'a [[u8; 4]]),
395
396 CompleteServiceUuids32(&'a [[u8; 4]]),
399
400 IncompleteServiceUuids128(&'a [[u8; 16]]),
403
404 CompleteServiceUuids128(&'a [[u8; 16]]),
407
408 TxPowerLevel(i8),
410
411 ServiceData16 {
414 uuid: [u8; 2],
416 data: &'a [u8],
418 },
419
420 CompleteLocalName(&'a [u8]),
424
425 ShortenedLocalName(&'a [u8]),
427
428 ManufacturerSpecificData {
430 company_identifier: u16,
432 payload: &'a [u8],
434 },
435
436 Unknown {
438 ty: u8,
440 data: &'a [u8],
442 },
443}
444
445impl AdStructure<'_> {
446 pub fn encode_slice(data: &[AdStructure<'_>], dest: &mut [u8]) -> Result<usize, codec::Error> {
448 let mut w = WriteCursor::new(dest);
449 for item in data.iter() {
450 item.encode(&mut w)?;
451 }
452 Ok(w.len())
453 }
454
455 pub(crate) fn encode(&self, w: &mut WriteCursor<'_>) -> Result<(), codec::Error> {
456 match self {
457 AdStructure::Flags(flags) => {
458 w.append(&[0x02, 0x01, *flags])?;
459 }
460 AdStructure::IncompleteServiceUuids16(uuids) => {
461 w.append(&[(uuids.len() * 2 + 1) as u8, 0x02])?;
462 for uuid in uuids.iter() {
463 w.write_ref(&Uuid::Uuid16(*uuid))?;
464 }
465 }
466 AdStructure::CompleteServiceUuids16(uuids) => {
467 w.append(&[(uuids.len() * 2 + 1) as u8, 0x03])?;
468 for uuid in uuids.iter() {
469 w.write_ref(&Uuid::Uuid16(*uuid))?;
470 }
471 }
472 AdStructure::IncompleteServiceUuids32(uuids) => {
473 w.append(&[(uuids.len() * 4 + 1) as u8, 0x04])?;
474 for uuid in uuids.iter() {
475 w.write_ref(&Uuid::Uuid32(*uuid))?;
476 }
477 }
478 AdStructure::CompleteServiceUuids32(uuids) => {
479 w.append(&[(uuids.len() * 4 + 1) as u8, 0x05])?;
480 for uuid in uuids.iter() {
481 w.write_ref(&Uuid::Uuid32(*uuid))?;
482 }
483 }
484 AdStructure::IncompleteServiceUuids128(uuids) => {
485 w.append(&[(uuids.len() * 16 + 1) as u8, 0x06])?;
486 for uuid in uuids.iter() {
487 w.write_ref(&Uuid::Uuid128(*uuid))?;
488 }
489 }
490 AdStructure::CompleteServiceUuids128(uuids) => {
491 w.append(&[(uuids.len() * 16 + 1) as u8, 0x07])?;
492 for uuid in uuids.iter() {
493 w.write_ref(&Uuid::Uuid128(*uuid))?;
494 }
495 }
496 AdStructure::ShortenedLocalName(name) => {
497 w.append(&[(name.len() + 1) as u8, 0x08])?;
498 w.append(name)?;
499 }
500 AdStructure::CompleteLocalName(name) => {
501 w.append(&[(name.len() + 1) as u8, 0x09])?;
502 w.append(name)?;
503 }
504 AdStructure::TxPowerLevel(power) => {
505 w.append(&[0x02, 0x0a, *power as u8])?;
506 }
507 AdStructure::ServiceData16 { uuid, data } => {
508 w.append(&[(data.len() + 3) as u8, 0x16])?;
509 w.write(Uuid::Uuid16(*uuid))?;
510 w.append(data)?;
511 }
512 AdStructure::ManufacturerSpecificData {
513 company_identifier,
514 payload,
515 } => {
516 w.append(&[(payload.len() + 3) as u8, 0xff])?;
517 w.write(*company_identifier)?;
518 w.append(payload)?;
519 }
520 AdStructure::Unknown { ty, data } => {
521 w.append(&[(data.len() + 1) as u8, *ty])?;
522 w.append(data)?;
523 }
524 }
525 Ok(())
526 }
527
528 pub fn decode(data: &[u8]) -> impl Iterator<Item = Result<AdStructure<'_>, codec::Error>> {
530 AdStructureIter {
531 cursor: ReadCursor::new(data),
532 }
533 }
534}
535
536pub struct AdStructureIter<'d> {
538 cursor: ReadCursor<'d>,
539}
540
541impl<'d> AdStructureIter<'d> {
542 fn read(&mut self) -> Result<AdStructure<'d>, codec::Error> {
543 let len: u8 = self.cursor.read()?;
544 if len < 2 {
545 return Err(codec::Error::InvalidValue);
546 }
547 let code: u8 = self.cursor.read()?;
548 let data = self.cursor.slice(len as usize - 1)?;
549 match code {
552 0x01 => Ok(AdStructure::Flags(data[0])),
554 0x02 => match zerocopy::FromBytes::ref_from_bytes(data) {
556 Ok(x) => Ok(AdStructure::IncompleteServiceUuids16(x)),
557 Err(e) => {
558 let _ = zerocopy::SizeError::from(e);
559 Err(codec::Error::InvalidValue)
560 }
561 },
562 0x03 => match zerocopy::FromBytes::ref_from_bytes(data) {
564 Ok(x) => Ok(AdStructure::CompleteServiceUuids16(x)),
565 Err(e) => {
566 let _ = zerocopy::SizeError::from(e);
567 Err(codec::Error::InvalidValue)
568 }
569 },
570 0x04 => match zerocopy::FromBytes::ref_from_bytes(data) {
572 Ok(x) => Ok(AdStructure::IncompleteServiceUuids32(x)),
573 Err(e) => {
574 let _ = zerocopy::SizeError::from(e);
575 Err(codec::Error::InvalidValue)
576 }
577 },
578 0x05 => match zerocopy::FromBytes::ref_from_bytes(data) {
580 Ok(x) => Ok(AdStructure::CompleteServiceUuids32(x)),
581 Err(e) => {
582 let _ = zerocopy::SizeError::from(e);
583 Err(codec::Error::InvalidValue)
584 }
585 },
586 0x06 => match zerocopy::FromBytes::ref_from_bytes(data) {
588 Ok(x) => Ok(AdStructure::IncompleteServiceUuids128(x)),
589 Err(e) => {
590 let _ = zerocopy::SizeError::from(e);
591 Err(codec::Error::InvalidValue)
592 }
593 },
594 0x07 => match zerocopy::FromBytes::ref_from_bytes(data) {
596 Ok(x) => Ok(AdStructure::CompleteServiceUuids128(x)),
597 Err(e) => {
598 let _ = zerocopy::SizeError::from(e);
599 Err(codec::Error::InvalidValue)
600 }
601 },
602 0x08 => Ok(AdStructure::ShortenedLocalName(data)),
604 0x09 => Ok(AdStructure::CompleteLocalName(data)),
606 0x0a => Ok(AdStructure::TxPowerLevel(data[0] as i8)),
608 0x16 => {
621 if data.len() < 2 {
622 return Err(codec::Error::InvalidValue);
623 }
624 let uuid = data[0..2].try_into().unwrap();
625 Ok(AdStructure::ServiceData16 { uuid, data: &data[2..] })
626 }
627 0xff if data.len() >= 2 => Ok(AdStructure::ManufacturerSpecificData {
661 company_identifier: u16::from_le_bytes([data[0], data[1]]),
662 payload: &data[2..],
663 }),
664 ty => Ok(AdStructure::Unknown { ty, data }),
665 }
666 }
667}
668
669impl<'d> Iterator for AdStructureIter<'d> {
670 type Item = Result<AdStructure<'d>, codec::Error>;
671 fn next(&mut self) -> Option<Self::Item> {
672 if self.cursor.available() == 0 {
673 return None;
674 }
675 Some(self.read())
676 }
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682
683 #[test]
684 fn adv_name_truncate() {
685 let mut adv_data = [0; 31];
686 assert!(AdStructure::encode_slice(
687 &[
688 AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
689 AdStructure::IncompleteServiceUuids16(&[[0x0f, 0x18]]),
690 AdStructure::CompleteLocalName(b"12345678901234567890123"),
691 ],
692 &mut adv_data[..],
693 )
694 .is_err());
695 }
696}