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