1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Builder for Advertising Data
16 
17 use netsim_proto::model::{
18     chip::ble_beacon::AdvertiseData as AdvertiseDataProto,
19     chip_create::BleBeaconCreate as BleBeaconCreateProto,
20 };
21 use std::convert::TryInto;
22 
23 use super::advertise_settings::TxPowerLevel;
24 
25 // Core Specification (v5.3 Vol 6 Part B §2.3.1.3 and §2.3.1.4)
26 const MAX_ADV_NONCONN_DATA_LEN: usize = 31;
27 
28 // Assigned Numbers Document (§2.3)
29 const AD_TYPE_COMPLETE_NAME: u8 = 0x09;
30 const AD_TYPE_TX_POWER: u8 = 0x0A;
31 const AD_TYPE_MANUFACTURER_DATA: u8 = 0xFF;
32 
33 #[derive(Debug)]
34 pub struct AdvertiseData {
35     /// Whether or not to include the device name in the packet.
36     pub include_device_name: bool,
37     /// Whether or not to include the transmit power in the packet.
38     pub include_tx_power_level: bool,
39     /// Manufacturer-specific data.
40     pub manufacturer_data: Option<Vec<u8>>,
41     bytes: Vec<u8>,
42 }
43 
44 impl AdvertiseData {
45     /// Returns a new advertise data builder with no fields.
builder(device_name: String, tx_power_level: TxPowerLevel) -> AdvertiseDataBuilder46     pub fn builder(device_name: String, tx_power_level: TxPowerLevel) -> AdvertiseDataBuilder {
47         AdvertiseDataBuilder::new(device_name, tx_power_level)
48     }
49 
50     /// Returns a new advertise data with fields from a protobuf.
from_proto( device_name: String, tx_power_level: TxPowerLevel, proto: &AdvertiseDataProto, ) -> Result<Self, String>51     pub fn from_proto(
52         device_name: String,
53         tx_power_level: TxPowerLevel,
54         proto: &AdvertiseDataProto,
55     ) -> Result<Self, String> {
56         let mut builder = AdvertiseDataBuilder::new(device_name, tx_power_level);
57 
58         if proto.include_device_name {
59             builder.include_device_name();
60         }
61 
62         if proto.include_tx_power_level {
63             builder.include_tx_power_level();
64         }
65 
66         if !proto.manufacturer_data.is_empty() {
67             builder.manufacturer_data(proto.manufacturer_data.clone());
68         }
69 
70         builder.build()
71     }
72 
73     /// Gets the raw bytes to be sent in the advertise data field of a BLE advertise packet.
to_bytes(&self) -> Vec<u8>74     pub fn to_bytes(&self) -> Vec<u8> {
75         self.bytes.clone()
76     }
77 }
78 
79 impl From<&AdvertiseData> for AdvertiseDataProto {
from(value: &AdvertiseData) -> Self80     fn from(value: &AdvertiseData) -> Self {
81         AdvertiseDataProto {
82             include_device_name: value.include_device_name,
83             include_tx_power_level: value.include_tx_power_level,
84             manufacturer_data: value.manufacturer_data.clone().unwrap_or_default(),
85             ..Default::default()
86         }
87     }
88 }
89 
90 #[derive(Default)]
91 /// Builder for the advertise data field of a Bluetooth packet.
92 pub struct AdvertiseDataBuilder {
93     device_name: String,
94     tx_power_level: TxPowerLevel,
95     include_device_name: bool,
96     include_tx_power_level: bool,
97     manufacturer_data: Option<Vec<u8>>,
98 }
99 
100 impl AdvertiseDataBuilder {
101     /// Returns a new advertise data builder with empty fields.
new(device_name: String, tx_power_level: TxPowerLevel) -> Self102     pub fn new(device_name: String, tx_power_level: TxPowerLevel) -> Self {
103         AdvertiseDataBuilder { device_name, tx_power_level, ..Self::default() }
104     }
105 
106     /// Build the advertise data.
107     ///
108     /// Returns a vector of bytes holding the serialized advertise data based on the fields added to the builder, or `Err(String)` if the data would be malformed.
build(&self) -> Result<AdvertiseData, String>109     pub fn build(&self) -> Result<AdvertiseData, String> {
110         Ok(AdvertiseData {
111             include_device_name: self.include_device_name,
112             include_tx_power_level: self.include_tx_power_level,
113             manufacturer_data: self.manufacturer_data.clone(),
114             bytes: self.serialize()?,
115         })
116     }
117 
118     /// Add a complete device name field to the advertise data.
include_device_name(&mut self) -> &mut Self119     pub fn include_device_name(&mut self) -> &mut Self {
120         self.include_device_name = true;
121         self
122     }
123 
124     /// Add a transmit power field to the advertise data.
include_tx_power_level(&mut self) -> &mut Self125     pub fn include_tx_power_level(&mut self) -> &mut Self {
126         self.include_tx_power_level = true;
127         self
128     }
129 
130     /// Add a manufacturer data field to the advertise data.
manufacturer_data(&mut self, manufacturer_data: Vec<u8>) -> &mut Self131     pub fn manufacturer_data(&mut self, manufacturer_data: Vec<u8>) -> &mut Self {
132         self.manufacturer_data = Some(manufacturer_data);
133         self
134     }
135 
serialize(&self) -> Result<Vec<u8>, String>136     fn serialize(&self) -> Result<Vec<u8>, String> {
137         let mut bytes = Vec::new();
138 
139         if self.include_device_name {
140             let device_name = self.device_name.as_bytes();
141 
142             if device_name.len() > MAX_ADV_NONCONN_DATA_LEN - 2 {
143                 return Err(format!(
144                     "complete name must be less than {} chars",
145                     MAX_ADV_NONCONN_DATA_LEN - 2
146                 ));
147             }
148 
149             bytes.extend(vec![
150                 (1 + device_name.len())
151                     .try_into()
152                     .map_err(|_| "complete name must be less than 255 chars")?,
153                 AD_TYPE_COMPLETE_NAME,
154             ]);
155             bytes.extend_from_slice(device_name);
156         }
157 
158         if self.include_tx_power_level {
159             bytes.extend(vec![2, AD_TYPE_TX_POWER, self.tx_power_level.dbm as u8]);
160         }
161 
162         if let Some(manufacturer_data) = &self.manufacturer_data {
163             if manufacturer_data.len() < 2 {
164                 // Supplement to the Core Specification (v10 Part A §1.4.2)
165                 return Err("manufacturer data must be at least 2 bytes".to_string());
166             }
167 
168             if manufacturer_data.len() > MAX_ADV_NONCONN_DATA_LEN - 2 {
169                 return Err(format!(
170                     "manufacturer data must be less than {} bytes",
171                     MAX_ADV_NONCONN_DATA_LEN - 2
172                 ));
173             }
174 
175             bytes.extend(vec![
176                 (1 + manufacturer_data.len())
177                     .try_into()
178                     .map_err(|_| "manufacturer data must be less than 255 bytes")?,
179                 AD_TYPE_MANUFACTURER_DATA,
180             ]);
181             bytes.extend_from_slice(manufacturer_data);
182         }
183 
184         if bytes.len() > MAX_ADV_NONCONN_DATA_LEN {
185             return Err(format!(
186                 "exceeded maximum advertising packet length of {} bytes",
187                 MAX_ADV_NONCONN_DATA_LEN
188             ));
189         }
190 
191         Ok(bytes)
192     }
193 }
194 
195 #[cfg(test)]
196 mod tests {
197     use super::*;
198     use netsim_proto::model::chip::ble_beacon::AdvertiseSettings as AdvertiseSettingsProto;
199     use protobuf::MessageField;
200 
201     const HEADER_LEN: usize = 2;
202 
203     #[test]
test_from_proto_succeeds()204     fn test_from_proto_succeeds() {
205         let device_name = String::from("test-device-name");
206         let tx_power = TxPowerLevel::new(1);
207         let exp_name_len = HEADER_LEN + device_name.len();
208         let exp_tx_power_len = HEADER_LEN + 1;
209 
210         let ad = AdvertiseData::from_proto(
211             device_name.clone(),
212             tx_power,
213             &AdvertiseDataProto {
214                 include_device_name: true,
215                 include_tx_power_level: true,
216                 ..Default::default()
217             },
218         );
219 
220         assert!(ad.is_ok());
221         let bytes = ad.unwrap().bytes;
222 
223         assert_eq!(exp_name_len + exp_tx_power_len, bytes.len());
224         assert_eq!(
225             [
226                 vec![(exp_name_len - 1) as u8, AD_TYPE_COMPLETE_NAME],
227                 device_name.into_bytes(),
228                 vec![(exp_tx_power_len - 1) as u8, AD_TYPE_TX_POWER, tx_power.dbm as u8]
229             ]
230             .concat(),
231             bytes
232         );
233     }
234 
235     #[test]
test_from_proto_fails()236     fn test_from_proto_fails() {
237         let device_name = "a".repeat(MAX_ADV_NONCONN_DATA_LEN - HEADER_LEN + 1);
238         let data = AdvertiseData::from_proto(
239             device_name,
240             TxPowerLevel::new(0),
241             &AdvertiseDataProto { include_device_name: true, ..Default::default() },
242         );
243 
244         assert!(data.is_err());
245     }
246 
247     #[test]
test_from_proto_sets_proto_field()248     fn test_from_proto_sets_proto_field() {
249         let device_name = String::from("test-device-name");
250         let tx_power = TxPowerLevel::new(1);
251         let ad_proto = AdvertiseDataProto {
252             include_device_name: true,
253             include_tx_power_level: true,
254             ..Default::default()
255         };
256 
257         let ad = AdvertiseData::from_proto(device_name.clone(), tx_power, &ad_proto);
258 
259         assert!(ad.is_ok());
260         assert_eq!(ad_proto, (&ad.unwrap()).into());
261     }
262 
263     #[test]
test_set_device_name_succeeds()264     fn test_set_device_name_succeeds() {
265         let device_name = String::from("test-device-name");
266         let ad = AdvertiseData::builder(device_name.clone(), TxPowerLevel::default())
267             .include_device_name()
268             .build();
269         let exp_len = HEADER_LEN + device_name.len();
270 
271         assert!(ad.is_ok());
272         let bytes = ad.unwrap().bytes;
273 
274         assert_eq!(exp_len, bytes.len());
275         assert_eq!(
276             [vec![(exp_len - 1) as u8, AD_TYPE_COMPLETE_NAME], device_name.into_bytes()].concat(),
277             bytes
278         );
279     }
280 
281     #[test]
test_set_device_name_fails()282     fn test_set_device_name_fails() {
283         let device_name = "a".repeat(MAX_ADV_NONCONN_DATA_LEN - HEADER_LEN + 1);
284         let data = AdvertiseData::builder(device_name, TxPowerLevel::default())
285             .include_device_name()
286             .build();
287 
288         assert!(data.is_err());
289     }
290 
291     #[test]
test_set_tx_power_level()292     fn test_set_tx_power_level() {
293         let tx_power = TxPowerLevel::new(-6);
294         let ad =
295             AdvertiseData::builder(String::default(), tx_power).include_tx_power_level().build();
296         let exp_len = HEADER_LEN + 1;
297 
298         assert!(ad.is_ok());
299         let bytes = ad.unwrap().bytes;
300 
301         assert_eq!(exp_len, bytes.len());
302         assert_eq!(vec![(exp_len - 1) as u8, AD_TYPE_TX_POWER, tx_power.dbm as u8], bytes);
303     }
304 
305     #[test]
test_set_manufacturer_data_succeeds()306     fn test_set_manufacturer_data_succeeds() {
307         let manufacturer_data = String::from("test-manufacturer-data");
308         let ad = AdvertiseData::builder(String::default(), TxPowerLevel::default())
309             .manufacturer_data(manufacturer_data.clone().into_bytes())
310             .build();
311         let exp_len = HEADER_LEN + manufacturer_data.len();
312 
313         assert!(ad.is_ok());
314         let bytes = ad.unwrap().bytes;
315 
316         assert_eq!(exp_len, bytes.len());
317         assert_eq!(
318             [vec![(exp_len - 1) as u8, AD_TYPE_MANUFACTURER_DATA], manufacturer_data.into_bytes()]
319                 .concat(),
320             bytes
321         );
322     }
323 
324     #[test]
test_set_manufacturer_data_fails()325     fn test_set_manufacturer_data_fails() {
326         let manufacturer_data = "a".repeat(MAX_ADV_NONCONN_DATA_LEN - HEADER_LEN + 1);
327         let data = AdvertiseData::builder(String::default(), TxPowerLevel::default())
328             .manufacturer_data(manufacturer_data.into_bytes())
329             .build();
330 
331         assert!(data.is_err());
332     }
333 
334     #[test]
test_set_name_and_power_succeeds()335     fn test_set_name_and_power_succeeds() {
336         let exp_data = [
337             0x0F, 0x09, b'g', b'D', b'e', b'v', b'i', b'c', b'e', b'-', b'b', b'e', b'a', b'c',
338             b'o', b'n', 0x02, 0x0A, 0x0,
339         ];
340         let data = AdvertiseData::builder(String::from("gDevice-beacon"), TxPowerLevel::new(0))
341             .include_device_name()
342             .include_tx_power_level()
343             .build();
344 
345         assert!(data.is_ok());
346         assert_eq!(exp_data, data.unwrap().bytes.as_slice());
347     }
348 }
349