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}