nrf_softdevice/ble/
advertisement_builder.rs

1#[cfg(feature = "defmt")]
2use defmt::Format;
3
4const LEGACY_PAYLOAD_LEN: usize = 31;
5const EXTENDED_PAYLOAD_LEN: usize = 254;
6
7#[derive(Debug, Clone, Copy)]
8#[cfg_attr(feature = "defmt", derive(Format))]
9pub enum Error {
10    Oversize { expected: usize },
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(feature = "defmt", derive(Format))]
15pub struct AdvertisementDataType(u8);
16
17impl AdvertisementDataType {
18    pub const FLAGS: AdvertisementDataType = AdvertisementDataType(0x01);
19    pub const INCOMPLETE_16_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x02);
20    pub const COMPLETE_16_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x03);
21    pub const INCOMPLETE_32_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x04);
22    pub const COMPLETE_32_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x05);
23    pub const INCOMPLETE_128_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x06);
24    pub const COMPLETE_128_SERVICE_LIST: AdvertisementDataType = AdvertisementDataType(0x07);
25    pub const SHORT_NAME: AdvertisementDataType = AdvertisementDataType(0x08);
26    pub const FULL_NAME: AdvertisementDataType = AdvertisementDataType(0x09);
27    pub const TXPOWER_LEVEL: AdvertisementDataType = AdvertisementDataType(0x0a);
28    pub const PERIPHERAL_CONNECTION_INTERVAL_RANGE: AdvertisementDataType = AdvertisementDataType(0x12);
29    pub const SERVICE_SOLICITATION_16: AdvertisementDataType = AdvertisementDataType(0x14);
30    pub const SERVICE_SOLICITATION_128: AdvertisementDataType = AdvertisementDataType(0x15);
31    pub const SERVICE_SOLICITATION_32: AdvertisementDataType = AdvertisementDataType(0x1f);
32    pub const SERVICE_DATA_16: AdvertisementDataType = AdvertisementDataType(0x16);
33    pub const SERVICE_DATA_32: AdvertisementDataType = AdvertisementDataType(0x20);
34    pub const SERVICE_DATA_128: AdvertisementDataType = AdvertisementDataType(0x21);
35    pub const APPEARANCE: AdvertisementDataType = AdvertisementDataType(0x19);
36    pub const PUBLIC_TARGET_ADDRESS: AdvertisementDataType = AdvertisementDataType(0x17);
37    pub const RANDOM_TARGET_ADDRESS: AdvertisementDataType = AdvertisementDataType(0x18);
38    pub const ADVERTISING_INTERVAL: AdvertisementDataType = AdvertisementDataType(0x1a);
39    pub const URI: AdvertisementDataType = AdvertisementDataType(0x24);
40    pub const LE_SUPPORTED_FEATURES: AdvertisementDataType = AdvertisementDataType(0x27);
41    pub const MANUFACTURER_SPECIFIC_DATA: AdvertisementDataType = AdvertisementDataType(0xff);
42
43    pub const fn from_u8(value: u8) -> Self {
44        AdvertisementDataType(value)
45    }
46
47    pub const fn to_u8(self) -> u8 {
48        self.0
49    }
50}
51
52impl From<u8> for AdvertisementDataType {
53    fn from(value: u8) -> Self {
54        AdvertisementDataType(value)
55    }
56}
57
58impl From<AdvertisementDataType> for u8 {
59    fn from(value: AdvertisementDataType) -> Self {
60        value.0
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[cfg_attr(feature = "defmt", derive(Format))]
66#[repr(u8)]
67pub enum Flag {
68    LimitedDiscovery = 0b1,
69    GeneralDiscovery = 0b10,
70    #[allow(non_camel_case_types)]
71    LE_Only = 0b100,
72
73    // i don't understand these but in case people want them
74    Bit3 = 0b1000,
75    Bit4 = 0b10000,
76    // the rest are "reserved for future use"
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(feature = "defmt", derive(Format))]
81pub enum ServiceList {
82    Incomplete,
83    Complete,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87#[cfg_attr(feature = "defmt", derive(Format))]
88pub struct ServiceUuid16(u16);
89
90impl ServiceUuid16 {
91    pub const GENERIC_ACCESS: ServiceUuid16 = ServiceUuid16(0x1800);
92    pub const GENERIC_ATTRIBUTE: ServiceUuid16 = ServiceUuid16(0x1801);
93    pub const IMMEDIATE_ALERT: ServiceUuid16 = ServiceUuid16(0x1802);
94    pub const LINK_LOSS: ServiceUuid16 = ServiceUuid16(0x1803);
95    pub const TX_POWER: ServiceUuid16 = ServiceUuid16(0x1804);
96    pub const CURRENT_TIME: ServiceUuid16 = ServiceUuid16(0x1805);
97    pub const REFERENCE_TIME_UPDATE: ServiceUuid16 = ServiceUuid16(0x1806);
98    pub const NEXT_DST_CHANGE: ServiceUuid16 = ServiceUuid16(0x1807);
99    pub const GLUCOSE: ServiceUuid16 = ServiceUuid16(0x1808);
100    pub const HEALTH_THERMOMETER: ServiceUuid16 = ServiceUuid16(0x1809);
101    pub const DEVICE_INFORMATION: ServiceUuid16 = ServiceUuid16(0x180A);
102    pub const HEART_RATE: ServiceUuid16 = ServiceUuid16(0x180D);
103    pub const PHONE_ALERT_STATUS: ServiceUuid16 = ServiceUuid16(0x180E);
104    pub const BATTERY: ServiceUuid16 = ServiceUuid16(0x180F);
105    pub const BLOOD_PRESSURE: ServiceUuid16 = ServiceUuid16(0x1810);
106    pub const ALERT_NOTIFICATION: ServiceUuid16 = ServiceUuid16(0x1811);
107    pub const HUMAN_INTERFACE_DEVICE: ServiceUuid16 = ServiceUuid16(0x1812);
108    pub const SCAN_PARAMETERS: ServiceUuid16 = ServiceUuid16(0x1813);
109    pub const RUNNNIG_SPEED_AND_CADENCE: ServiceUuid16 = ServiceUuid16(0x1814);
110    pub const AUTOMATION_IO: ServiceUuid16 = ServiceUuid16(0x1815);
111    pub const CYCLING_SPEED_AND_CADENCE: ServiceUuid16 = ServiceUuid16(0x1816);
112    pub const CYCLING_POWER: ServiceUuid16 = ServiceUuid16(0x1818);
113    pub const LOCATION_AND_NAVIGATION: ServiceUuid16 = ServiceUuid16(0x1819);
114    pub const ENVIRONMENTAL_SENSING: ServiceUuid16 = ServiceUuid16(0x181A);
115    pub const BODY_COMPOSITION: ServiceUuid16 = ServiceUuid16(0x181B);
116    pub const USER_DATA: ServiceUuid16 = ServiceUuid16(0x181C);
117    pub const WEIGHT_SCALE: ServiceUuid16 = ServiceUuid16(0x181D);
118    pub const BOND_MANAGEMENT: ServiceUuid16 = ServiceUuid16(0x181E);
119    pub const CONTINOUS_GLUCOSE_MONITORING: ServiceUuid16 = ServiceUuid16(0x181F);
120    pub const INTERNET_PROTOCOL_SUPPORT: ServiceUuid16 = ServiceUuid16(0x1820);
121    pub const INDOOR_POSITIONING: ServiceUuid16 = ServiceUuid16(0x1821);
122    pub const PULSE_OXIMETER: ServiceUuid16 = ServiceUuid16(0x1822);
123    pub const HTTP_PROXY: ServiceUuid16 = ServiceUuid16(0x1823);
124    pub const TRANSPORT_DISCOVERY: ServiceUuid16 = ServiceUuid16(0x1824);
125    pub const OBJECT_TRANSFER: ServiceUuid16 = ServiceUuid16(0x1825);
126    pub const FITNESS_MACHINE: ServiceUuid16 = ServiceUuid16(0x1826);
127    pub const MESH_PROVISIONING: ServiceUuid16 = ServiceUuid16(0x1827);
128    pub const MESH_PROXY: ServiceUuid16 = ServiceUuid16(0x1828);
129    pub const RECONNECTION_CONFIGURATION: ServiceUuid16 = ServiceUuid16(0x1829);
130    pub const INSULIN_DELIVERY: ServiceUuid16 = ServiceUuid16(0x183A);
131    pub const BINARY_SENSOR: ServiceUuid16 = ServiceUuid16(0x183B);
132    pub const EMERGENCY_CONFIGURATION: ServiceUuid16 = ServiceUuid16(0x183C);
133    pub const AUTHORIZATION_CONTROL: ServiceUuid16 = ServiceUuid16(0x183D);
134    pub const PHYSICAL_ACTIVITY_MONITOR: ServiceUuid16 = ServiceUuid16(0x183E);
135    pub const ELAPSED_TIME: ServiceUuid16 = ServiceUuid16(0x183F);
136    pub const GENERIC_HEALTH_SENSOR: ServiceUuid16 = ServiceUuid16(0x1840);
137    pub const AUDIO_INPUT_CONTROL: ServiceUuid16 = ServiceUuid16(0x1843);
138    pub const VOLUME_CONTROL: ServiceUuid16 = ServiceUuid16(0x1844);
139    pub const VOLUME_OFFSET_CONTROL: ServiceUuid16 = ServiceUuid16(0x1845);
140    pub const COORDINATED_SET_IDENTIFICATION: ServiceUuid16 = ServiceUuid16(0x1846);
141    pub const DEVICE_TIME: ServiceUuid16 = ServiceUuid16(0x1847);
142    pub const MEDIA_CONTROL: ServiceUuid16 = ServiceUuid16(0x1848);
143    pub const GENERIC_MEDIA_CONTROL: ServiceUuid16 = ServiceUuid16(0x1849);
144    pub const CONSTANT_TONE_EXTENSION: ServiceUuid16 = ServiceUuid16(0x184A);
145    pub const TELEPHONE_BEARER: ServiceUuid16 = ServiceUuid16(0x184B);
146    pub const GENERIC_TELEPHONE_BEARER: ServiceUuid16 = ServiceUuid16(0x184C);
147    pub const MICROPHONE_CONTROL: ServiceUuid16 = ServiceUuid16(0x184D);
148    pub const AUDIO_STREAM_CONTROL: ServiceUuid16 = ServiceUuid16(0x184E);
149    pub const BROADCAST_AUDIO_SCAN: ServiceUuid16 = ServiceUuid16(0x184F);
150    pub const PUBLISHED_AUDIO_SCAN: ServiceUuid16 = ServiceUuid16(0x1850);
151    pub const BASIC_AUDIO_CAPABILITIES: ServiceUuid16 = ServiceUuid16(0x1851);
152    pub const BROADCAST_AUDIO_ANNOUNCEMENT: ServiceUuid16 = ServiceUuid16(0x1852);
153    pub const COMMON_AUDIO: ServiceUuid16 = ServiceUuid16(0x1853);
154    pub const HEARING_ACCESS: ServiceUuid16 = ServiceUuid16(0x1854);
155    pub const TELEPHONY_AND_MEDIA_AUDIO: ServiceUuid16 = ServiceUuid16(0x1855);
156    pub const PUBLIC_BROADCAST_ANNOUNCEMENT: ServiceUuid16 = ServiceUuid16(0x1856);
157    pub const ELECTRONIC_SHELF_LABEL: ServiceUuid16 = ServiceUuid16(0x1847);
158    pub const GAMING_AUDIO: ServiceUuid16 = ServiceUuid16(0x1858);
159    pub const MESH_PROXY_SOLICITATION: ServiceUuid16 = ServiceUuid16(0x1859);
160
161    pub const fn from_u16(value: u16) -> Self {
162        ServiceUuid16(value)
163    }
164
165    pub const fn to_u16(self) -> u16 {
166        self.0
167    }
168}
169
170impl From<u16> for ServiceUuid16 {
171    fn from(value: u16) -> Self {
172        ServiceUuid16(value)
173    }
174}
175
176impl From<ServiceUuid16> for u16 {
177    fn from(value: ServiceUuid16) -> Self {
178        value.0
179    }
180}
181
182pub struct AdvertisementBuilder<const N: usize> {
183    buf: [u8; N],
184    ptr: usize,
185}
186
187pub struct AdvertisementPayload<const N: usize> {
188    buf: [u8; N],
189    len: usize,
190}
191
192impl<const N: usize> AsRef<[u8]> for AdvertisementPayload<N> {
193    fn as_ref(&self) -> &[u8] {
194        &self.buf[..self.len]
195    }
196}
197
198impl<const N: usize> core::ops::Deref for AdvertisementPayload<N> {
199    type Target = [u8];
200
201    fn deref(&self) -> &Self::Target {
202        &self.buf[..self.len]
203    }
204}
205
206impl<const K: usize> AdvertisementBuilder<K> {
207    pub const fn new() -> Self {
208        Self { buf: [0; K], ptr: 0 }
209    }
210
211    const fn write(mut self, data: &[u8]) -> Self {
212        if self.ptr + data.len() <= K {
213            let mut i = 0;
214            while i < data.len() {
215                self.buf[self.ptr] = data[i];
216                i += 1;
217                self.ptr += 1;
218            }
219        } else {
220            // Overflow, but still track how much data was attempted to be written
221            self.ptr += data.len();
222        }
223
224        self
225    }
226
227    pub const fn capacity() -> usize {
228        K
229    }
230
231    pub const fn len(&self) -> usize {
232        self.ptr
233    }
234
235    /// Write raw bytes to the advertisement data.
236    ///
237    /// *Note: The length is automatically computed and prepended.*
238    pub const fn raw(self, ad: AdvertisementDataType, data: &[u8]) -> Self {
239        self.write(&[data.len() as u8 + 1, ad.to_u8()]).write(data)
240    }
241
242    /// Get the resulting advertisement payload.
243    ///
244    /// Returns `Error::Oversize` if more than `K` bytes were written to the builder.
245    pub const fn try_build(self) -> Result<AdvertisementPayload<K>, Error> {
246        if self.ptr <= K {
247            Ok(AdvertisementPayload {
248                buf: self.buf,
249                len: self.ptr,
250            })
251        } else {
252            Err(Error::Oversize { expected: self.ptr })
253        }
254    }
255
256    /// Get the resulting advertisement payload.
257    ///
258    /// Panics if more than `K` bytes were written to the builder.
259    pub const fn build(self) -> AdvertisementPayload<K> {
260        // Use core::assert! even if defmt is enabled because it is const
261        core::assert!(self.ptr <= K, "advertisement exceeded buffer length");
262
263        AdvertisementPayload {
264            buf: self.buf,
265            len: self.ptr,
266        }
267    }
268
269    /// Add flags to the advertisement data.
270    pub const fn flags(self, flags: &[Flag]) -> Self {
271        let mut i = 0;
272        let mut bits = 0;
273        while i < flags.len() {
274            bits |= flags[i] as u8;
275            i += 1;
276        }
277
278        self.raw(AdvertisementDataType::FLAGS, &[bits])
279    }
280
281    /// Add a list of 16-bit service uuids to the advertisement data.
282    pub const fn services_16(self, complete: ServiceList, services: &[ServiceUuid16]) -> Self {
283        let ad_type = match complete {
284            ServiceList::Incomplete => AdvertisementDataType::INCOMPLETE_16_SERVICE_LIST,
285            ServiceList::Complete => AdvertisementDataType::COMPLETE_16_SERVICE_LIST,
286        };
287
288        let mut res = self.write(&[(services.len() * 2) as u8 + 1, ad_type.to_u8()]);
289        let mut i = 0;
290        while i < services.len() {
291            res = res.write(&(services[i].to_u16()).to_le_bytes());
292            i += 1;
293        }
294        res
295    }
296
297    /// Add a list of 128-bit service uuids to the advertisement data.
298    ///
299    /// Note that each UUID in the list needs to be in little-endian format, i.e. opposite to what you would
300    /// normally write UUIDs.
301    pub const fn services_128(self, complete: ServiceList, services: &[[u8; 16]]) -> Self {
302        let ad_type = match complete {
303            ServiceList::Incomplete => AdvertisementDataType::INCOMPLETE_128_SERVICE_LIST,
304            ServiceList::Complete => AdvertisementDataType::COMPLETE_128_SERVICE_LIST,
305        };
306
307        let mut res = self.write(&[(services.len() * 16) as u8 + 1, ad_type.to_u8()]);
308        let mut i = 0;
309        while i < services.len() {
310            res = res.write(&services[i]);
311            i += 1;
312        }
313        res
314    }
315
316    /// Add a name to the advertisement data.
317    pub const fn short_name(self, name: &str) -> Self {
318        self.raw(AdvertisementDataType::SHORT_NAME, name.as_bytes())
319    }
320
321    /// Add a name to the advertisement data.
322    pub const fn full_name(self, name: &str) -> Self {
323        self.raw(AdvertisementDataType::FULL_NAME, name.as_bytes())
324    }
325
326    /// Adds the provided string as a name, truncating and typing as needed.
327    ///
328    /// *Note: This modifier should be placed last.*
329    pub const fn adapt_name(self, name: &str) -> Self {
330        let p = self.ptr;
331        if p + 2 + name.len() <= K {
332            self.full_name(name)
333        } else {
334            let mut res = self.write(&[(K - p) as u8, AdvertisementDataType::SHORT_NAME.to_u8()]);
335            let mut i: usize = 0;
336            let bytes = name.as_bytes();
337            while res.ptr < K {
338                res.buf[res.ptr] = bytes[i];
339                res.ptr += 1;
340                i += 1;
341            }
342            res
343        }
344    }
345}
346
347pub type LegacyAdvertisementBuilder = AdvertisementBuilder<LEGACY_PAYLOAD_LEN>;
348pub type ExtendedAdvertisementBuilder = AdvertisementBuilder<EXTENDED_PAYLOAD_LEN>;
349
350pub type LegacyAdvertisementPayload = AdvertisementPayload<LEGACY_PAYLOAD_LEN>;
351pub type ExtendedAdvertisementPayload = AdvertisementPayload<EXTENDED_PAYLOAD_LEN>;