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 // http://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 //! Common externally-acessible V0 constructs for both of the 16 //! serialization+deserialization flows. 17 18 use crate::common::InvalidStackDataStructure; 19 use crate::serialize::AdvertisementBuilderKind; 20 use crate::utils::FfiEnum; 21 use np_adv::{ 22 legacy::data_elements::actions::ActionsDataElement, 23 legacy::{data_elements as np_adv_de, Ciphertext, PacketFlavorEnum, Plaintext}, 24 }; 25 use strum::IntoEnumIterator; 26 27 /// Discriminant for `V0DataElement`. 28 #[repr(u8)] 29 pub enum V0DataElementKind { 30 /// A transmission Power (Tx Power) data-element. 31 /// The associated payload may be obtained via 32 /// `V0DataElement#into_tx_power`. 33 TxPower = 1, 34 /// The Actions data-element. 35 /// The associated payload may be obtained via 36 /// `V0DataElement#into_actions`. 37 Actions = 2, 38 } 39 40 /// Representation of a V0 data element. 41 #[repr(C)] 42 #[allow(missing_docs)] 43 #[derive(Clone)] 44 pub enum V0DataElement { 45 TxPower(TxPower), 46 Actions(V0Actions), 47 } 48 49 impl TryFrom<V0DataElement> for np_adv_dynamic::legacy::ToBoxedSerializeDataElement { 50 type Error = InvalidStackDataStructure; try_from(de: V0DataElement) -> Result<Self, InvalidStackDataStructure>51 fn try_from(de: V0DataElement) -> Result<Self, InvalidStackDataStructure> { 52 match de { 53 V0DataElement::TxPower(x) => x.try_into(), 54 V0DataElement::Actions(x) => x.try_into(), 55 } 56 } 57 } 58 59 impl<F: np_adv::legacy::PacketFlavor> From<np_adv::legacy::deserialize::DeserializedDataElement<F>> 60 for V0DataElement 61 { from(de: np_adv::legacy::deserialize::DeserializedDataElement<F>) -> Self62 fn from(de: np_adv::legacy::deserialize::DeserializedDataElement<F>) -> Self { 63 use np_adv::legacy::deserialize::DeserializedDataElement; 64 match de { 65 DeserializedDataElement::Actions(x) => V0DataElement::Actions(x.into()), 66 DeserializedDataElement::TxPower(x) => V0DataElement::TxPower(x.into()), 67 } 68 } 69 } 70 71 impl FfiEnum for V0DataElement { 72 type Kind = V0DataElementKind; kind(&self) -> Self::Kind73 fn kind(&self) -> Self::Kind { 74 match self { 75 V0DataElement::Actions(_) => V0DataElementKind::Actions, 76 V0DataElement::TxPower(_) => V0DataElementKind::TxPower, 77 } 78 } 79 } 80 81 impl V0DataElement { 82 declare_enum_cast! {into_tx_power, TxPower, TxPower} 83 declare_enum_cast! {into_actions, Actions, V0Actions} 84 } 85 86 /// Discriminant for `BuildTxPowerResult`. 87 #[repr(u8)] 88 #[derive(Clone, Copy)] 89 pub enum BuildTxPowerResultKind { 90 /// The transmission power was outside the 91 /// allowed -100dBm to 20dBm range. 92 OutOfRange = 0, 93 /// The transmission power was in range, 94 /// and so a `TxPower` struct was constructed. 95 Success = 1, 96 } 97 98 /// Result type for attempting to construct a 99 /// Tx Power from a signed byte. 100 #[repr(C)] 101 #[allow(missing_docs)] 102 pub enum BuildTxPowerResult { 103 OutOfRange, 104 Success(TxPower), 105 } 106 107 impl FfiEnum for BuildTxPowerResult { 108 type Kind = BuildTxPowerResultKind; kind(&self) -> Self::Kind109 fn kind(&self) -> Self::Kind { 110 match self { 111 Self::OutOfRange => BuildTxPowerResultKind::OutOfRange, 112 Self::Success(_) => BuildTxPowerResultKind::Success, 113 } 114 } 115 } 116 117 impl BuildTxPowerResult { 118 declare_enum_cast! {into_success, Success, TxPower} 119 } 120 121 /// Representation of a transmission power, 122 /// as used for the Tx Power DE in V0 and V1. 123 #[derive(Clone)] 124 #[repr(C)] 125 pub struct TxPower { 126 tx_power: i8, 127 } 128 129 impl TxPower { 130 /// Attempts to construct a new TxPower from the given signed-byte value. build_from_signed_byte(tx_power: i8) -> BuildTxPowerResult131 pub fn build_from_signed_byte(tx_power: i8) -> BuildTxPowerResult { 132 match np_adv::shared_data::TxPower::try_from(tx_power) { 133 Ok(_) => BuildTxPowerResult::Success(Self { tx_power }), 134 Err(_) => BuildTxPowerResult::OutOfRange, 135 } 136 } 137 /// Yields this Tx Power value as an i8. as_i8(&self) -> i8138 pub fn as_i8(&self) -> i8 { 139 self.tx_power 140 } 141 } 142 143 impl From<np_adv_de::tx_power::TxPowerDataElement> for TxPower { from(de: np_adv_de::tx_power::TxPowerDataElement) -> Self144 fn from(de: np_adv_de::tx_power::TxPowerDataElement) -> Self { 145 Self { tx_power: de.tx_power_value() } 146 } 147 } 148 149 impl TryFrom<TxPower> for np_adv_dynamic::legacy::ToBoxedSerializeDataElement { 150 type Error = InvalidStackDataStructure; try_from(value: TxPower) -> Result<Self, InvalidStackDataStructure>151 fn try_from(value: TxPower) -> Result<Self, InvalidStackDataStructure> { 152 np_adv::shared_data::TxPower::try_from(value.as_i8()) 153 .map_err(|_| InvalidStackDataStructure) 154 .map(|x| x.into()) 155 } 156 } 157 158 /// Representation of the Actions DE in V0. 159 #[derive(Clone, Copy)] 160 #[repr(C)] 161 pub enum V0Actions { 162 /// A set of action bits which were present in a plaintext identity advertisement 163 Plaintext(V0ActionBits), 164 /// A set of action bits which were present in a encrypted identity advertisement 165 Encrypted(V0ActionBits), 166 } 167 168 impl TryFrom<V0Actions> for np_adv_dynamic::legacy::ToBoxedSerializeDataElement { 169 type Error = InvalidStackDataStructure; try_from(value: V0Actions) -> Result<Self, InvalidStackDataStructure>170 fn try_from(value: V0Actions) -> Result<Self, InvalidStackDataStructure> { 171 let boxed_action_bits = np_adv_dynamic::legacy::BoxedActionBits::try_from(value)?; 172 Ok(boxed_action_bits.into()) 173 } 174 } 175 176 impl<F: np_adv::legacy::PacketFlavor> From<ActionsDataElement<F>> for V0Actions { from(value: ActionsDataElement<F>) -> Self177 fn from(value: ActionsDataElement<F>) -> Self { 178 match F::ENUM_VARIANT { 179 PacketFlavorEnum::Plaintext => { 180 Self::Plaintext(V0ActionBits { bitfield: value.action.as_u32() }) 181 } 182 PacketFlavorEnum::Ciphertext => { 183 Self::Encrypted(V0ActionBits { bitfield: value.action.as_u32() }) 184 } 185 } 186 } 187 } 188 189 #[repr(C)] 190 #[derive(Clone, Copy)] 191 /// The bitfield data of a V0Actions data element 192 pub struct V0ActionBits { 193 bitfield: u32, 194 } 195 196 impl From<u32> for V0ActionBits { from(bitfield: u32) -> Self197 fn from(bitfield: u32) -> Self { 198 // No need to validate here. Validation of this struct is done at all places where it is 199 // taken as a parameter. See [`InvalidStackDataStructure`]. Since this struct is 200 // `#[repr(C)]` if rules were enforced here, they could be broken by a foreign language 201 // user. 202 Self { bitfield } 203 } 204 } 205 206 #[derive(Clone, Copy, strum_macros::EnumIter)] 207 #[allow(missing_docs)] 208 #[repr(u8)] 209 /// The possible boolean action types which can be present in an Actions data element 210 pub enum ActionType { 211 CrossDevSdk = 1, 212 CallTransfer = 4, 213 ActiveUnlock = 8, 214 NearbyShare = 9, 215 InstantTethering = 10, 216 PhoneHub = 11, 217 } 218 219 #[derive(Clone, Copy, Debug)] 220 /// The given int is out of range for conversion. 221 pub struct TryFromIntError; 222 223 impl From<core::num::TryFromIntError> for TryFromIntError { from(_: core::num::TryFromIntError) -> Self224 fn from(_: core::num::TryFromIntError) -> Self { 225 Self 226 } 227 } 228 229 impl TryFrom<u8> for ActionType { 230 type Error = TryFromIntError; try_from(n: u8) -> Result<Self, Self::Error>231 fn try_from(n: u8) -> Result<Self, Self::Error> { 232 // cast is safe since it's a repr(u8) unit enum 233 Self::iter().find(|t| *t as u8 == n).ok_or(TryFromIntError) 234 } 235 } 236 237 impl ActionType { as_boxed_action_element( &self, value: bool, ) -> np_adv_dynamic::legacy::ToBoxedActionElement238 pub(crate) fn as_boxed_action_element( 239 &self, 240 value: bool, 241 ) -> np_adv_dynamic::legacy::ToBoxedActionElement { 242 use np_adv_dynamic::legacy::ToBoxedActionElement; 243 match self { 244 Self::CrossDevSdk => ToBoxedActionElement::CrossDevSdk(value), 245 Self::CallTransfer => ToBoxedActionElement::CallTransfer(value), 246 Self::ActiveUnlock => ToBoxedActionElement::ActiveUnlock(value), 247 Self::NearbyShare => ToBoxedActionElement::NearbyShare(value), 248 Self::InstantTethering => ToBoxedActionElement::InstantTethering(value), 249 Self::PhoneHub => ToBoxedActionElement::PhoneHub(value), 250 } 251 } 252 } 253 254 impl From<ActionType> for np_adv::legacy::data_elements::actions::ActionType { from(value: ActionType) -> Self255 fn from(value: ActionType) -> Self { 256 match value { 257 ActionType::CrossDevSdk => { 258 np_adv::legacy::data_elements::actions::ActionType::CrossDevSdk 259 } 260 ActionType::CallTransfer => { 261 np_adv::legacy::data_elements::actions::ActionType::CallTransfer 262 } 263 ActionType::ActiveUnlock => { 264 np_adv::legacy::data_elements::actions::ActionType::ActiveUnlock 265 } 266 ActionType::NearbyShare => { 267 np_adv::legacy::data_elements::actions::ActionType::NearbyShare 268 } 269 ActionType::InstantTethering => { 270 np_adv::legacy::data_elements::actions::ActionType::InstantTethering 271 } 272 ActionType::PhoneHub => np_adv::legacy::data_elements::actions::ActionType::PhoneHub, 273 } 274 } 275 } 276 277 // ensure bidirectional mapping 278 impl From<np_adv::legacy::data_elements::actions::ActionType> for ActionType { from(value: np_adv_de::actions::ActionType) -> Self279 fn from(value: np_adv_de::actions::ActionType) -> Self { 280 match value { 281 np_adv_de::actions::ActionType::CrossDevSdk => ActionType::CrossDevSdk, 282 np_adv_de::actions::ActionType::CallTransfer => ActionType::CallTransfer, 283 np_adv_de::actions::ActionType::ActiveUnlock => ActionType::ActiveUnlock, 284 np_adv_de::actions::ActionType::NearbyShare => ActionType::NearbyShare, 285 np_adv_de::actions::ActionType::InstantTethering => ActionType::InstantTethering, 286 np_adv_de::actions::ActionType::PhoneHub => ActionType::PhoneHub, 287 } 288 } 289 } 290 291 impl From<np_adv_dynamic::legacy::BoxedActionBits> for V0Actions { from(bits: np_adv_dynamic::legacy::BoxedActionBits) -> Self292 fn from(bits: np_adv_dynamic::legacy::BoxedActionBits) -> Self { 293 use np_adv_dynamic::legacy::BoxedActionBits; 294 match bits { 295 BoxedActionBits::Plaintext(x) => Self::Plaintext(V0ActionBits { bitfield: x.as_u32() }), 296 BoxedActionBits::Ciphertext(x) => { 297 Self::Encrypted(V0ActionBits { bitfield: x.as_u32() }) 298 } 299 } 300 } 301 } 302 303 impl TryFrom<V0Actions> for np_adv_dynamic::legacy::BoxedActionBits { 304 type Error = InvalidStackDataStructure; try_from(actions: V0Actions) -> Result<Self, InvalidStackDataStructure>305 fn try_from(actions: V0Actions) -> Result<Self, InvalidStackDataStructure> { 306 match actions { 307 V0Actions::Plaintext(action_bits) => { 308 let bits = 309 np_adv::legacy::data_elements::actions::ActionBits::<Plaintext>::try_from( 310 action_bits.bitfield, 311 ) 312 .map_err(|_| InvalidStackDataStructure)?; 313 Ok(bits.into()) 314 } 315 V0Actions::Encrypted(action_bits) => { 316 let bits = 317 np_adv::legacy::data_elements::actions::ActionBits::<Ciphertext>::try_from( 318 action_bits.bitfield, 319 ) 320 .map_err(|_| InvalidStackDataStructure)?; 321 Ok(bits.into()) 322 } 323 } 324 } 325 } 326 327 /// Discriminant for `SetV0ActionResult`. 328 #[repr(u8)] 329 pub enum SetV0ActionResultKind { 330 /// The attempt to set the action bit failed. The 331 /// action bits were yielded back to the caller, 332 /// unmodified. 333 Error = 0, 334 /// The attempt to set the action bit succeeded. 335 /// The updated action bits were yielded back to the caller. 336 Success = 1, 337 } 338 339 /// The result of attempting to set a particular action 340 /// bit on some `V0Actions`. 341 #[repr(C)] 342 #[allow(missing_docs)] 343 pub enum SetV0ActionResult { 344 Success(V0Actions), 345 Error(V0Actions), 346 } 347 348 impl FfiEnum for SetV0ActionResult { 349 type Kind = SetV0ActionResultKind; kind(&self) -> Self::Kind350 fn kind(&self) -> Self::Kind { 351 match self { 352 Self::Success(_) => SetV0ActionResultKind::Success, 353 Self::Error(_) => SetV0ActionResultKind::Error, 354 } 355 } 356 } 357 358 impl SetV0ActionResult { 359 declare_enum_cast! { into_success, Success, V0Actions } 360 declare_enum_cast! { into_error, Error, V0Actions } 361 } 362 363 impl V0Actions { 364 /// Constructs a new V0 actions DE with no declared boolean 365 /// actions and a zeroed context sync sequence number, 366 /// where the DE is intended for the given advertisement 367 /// kind (plaintext/encrypted). new_zeroed(kind: AdvertisementBuilderKind) -> Self368 pub fn new_zeroed(kind: AdvertisementBuilderKind) -> Self { 369 match kind { 370 AdvertisementBuilderKind::Public => Self::Plaintext(V0ActionBits { bitfield: 0 }), 371 AdvertisementBuilderKind::Encrypted => Self::Encrypted(V0ActionBits { bitfield: 0 }), 372 } 373 } 374 375 /// Gets the V0 Action bits as represented by a u32 where the last 8 bits are 376 /// always 0 since V0 actions can only hold up to 24 bits. as_u32(&self) -> u32377 pub fn as_u32(&self) -> u32 { 378 match self { 379 V0Actions::Plaintext(bits) => bits.bitfield, 380 V0Actions::Encrypted(bits) => bits.bitfield, 381 } 382 } 383 384 /// Return whether a boolean action type is set in this data element 385 #[allow(clippy::expect_used)] has_action(&self, action_type: ActionType) -> Result<bool, InvalidStackDataStructure>386 pub fn has_action(&self, action_type: ActionType) -> Result<bool, InvalidStackDataStructure> { 387 let boxed_action_bits = np_adv_dynamic::legacy::BoxedActionBits::try_from(*self)?; 388 let action_type = action_type.into(); 389 Ok(boxed_action_bits.has_action(action_type)) 390 } 391 392 /// Attempts to set the given action bit to the given boolean value. 393 /// This operation may fail if the requested action bit may not be 394 /// set for the kind of containing advertisement (public/encrypted) 395 /// that this action DE is intended to belong to. In this case, 396 /// the original action bits will be yielded back to the caller, 397 /// unaltered. set_action( self, action_type: ActionType, value: bool, ) -> Result<SetV0ActionResult, InvalidStackDataStructure>398 pub fn set_action( 399 self, 400 action_type: ActionType, 401 value: bool, 402 ) -> Result<SetV0ActionResult, InvalidStackDataStructure> { 403 let mut boxed_action_bits = np_adv_dynamic::legacy::BoxedActionBits::try_from(self)?; 404 let boxed_action_element = action_type.as_boxed_action_element(value); 405 match boxed_action_bits.set_action(boxed_action_element) { 406 Ok(()) => Ok(SetV0ActionResult::Success(boxed_action_bits.into())), 407 Err(_) => Ok(SetV0ActionResult::Error(self)), 408 } 409 } 410 } 411