| // Copyright 2023 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| //! BLE advertisements. | |
| use crate::wrapper::assigned_numbers::{COMPANY_IDS, SERVICE_IDS}; | |
| use crate::wrapper::core::{Uuid128, Uuid16, Uuid32}; | |
| use itertools::Itertools; | |
| use nom::{combinator, multi, number}; | |
| use std::fmt; | |
| use strum::IntoEnumIterator; | |
| /// The numeric code for a common data type. | |
| /// | |
| /// For known types, see [CommonDataType], or use this type directly for non-assigned codes. | |
| #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] | |
| pub struct CommonDataTypeCode(u8); | |
| impl From<CommonDataType> for CommonDataTypeCode { | |
| fn from(value: CommonDataType) -> Self { | |
| let byte = match value { | |
| CommonDataType::Flags => 0x01, | |
| CommonDataType::IncompleteListOf16BitServiceClassUuids => 0x02, | |
| CommonDataType::CompleteListOf16BitServiceClassUuids => 0x03, | |
| CommonDataType::IncompleteListOf32BitServiceClassUuids => 0x04, | |
| CommonDataType::CompleteListOf32BitServiceClassUuids => 0x05, | |
| CommonDataType::IncompleteListOf128BitServiceClassUuids => 0x06, | |
| CommonDataType::CompleteListOf128BitServiceClassUuids => 0x07, | |
| CommonDataType::ShortenedLocalName => 0x08, | |
| CommonDataType::CompleteLocalName => 0x09, | |
| CommonDataType::TxPowerLevel => 0x0A, | |
| CommonDataType::ClassOfDevice => 0x0D, | |
| CommonDataType::SimplePairingHashC192 => 0x0E, | |
| CommonDataType::SimplePairingRandomizerR192 => 0x0F, | |
| // These two both really have type code 0x10! D: | |
| CommonDataType::DeviceId => 0x10, | |
| CommonDataType::SecurityManagerTkValue => 0x10, | |
| CommonDataType::SecurityManagerOutOfBandFlags => 0x11, | |
| CommonDataType::PeripheralConnectionIntervalRange => 0x12, | |
| CommonDataType::ListOf16BitServiceSolicitationUuids => 0x14, | |
| CommonDataType::ListOf128BitServiceSolicitationUuids => 0x15, | |
| CommonDataType::ServiceData16BitUuid => 0x16, | |
| CommonDataType::PublicTargetAddress => 0x17, | |
| CommonDataType::RandomTargetAddress => 0x18, | |
| CommonDataType::Appearance => 0x19, | |
| CommonDataType::AdvertisingInterval => 0x1A, | |
| CommonDataType::LeBluetoothDeviceAddress => 0x1B, | |
| CommonDataType::LeRole => 0x1C, | |
| CommonDataType::SimplePairingHashC256 => 0x1D, | |
| CommonDataType::SimplePairingRandomizerR256 => 0x1E, | |
| CommonDataType::ListOf32BitServiceSolicitationUuids => 0x1F, | |
| CommonDataType::ServiceData32BitUuid => 0x20, | |
| CommonDataType::ServiceData128BitUuid => 0x21, | |
| CommonDataType::LeSecureConnectionsConfirmationValue => 0x22, | |
| CommonDataType::LeSecureConnectionsRandomValue => 0x23, | |
| CommonDataType::Uri => 0x24, | |
| CommonDataType::IndoorPositioning => 0x25, | |
| CommonDataType::TransportDiscoveryData => 0x26, | |
| CommonDataType::LeSupportedFeatures => 0x27, | |
| CommonDataType::ChannelMapUpdateIndication => 0x28, | |
| CommonDataType::PbAdv => 0x29, | |
| CommonDataType::MeshMessage => 0x2A, | |
| CommonDataType::MeshBeacon => 0x2B, | |
| CommonDataType::BigInfo => 0x2C, | |
| CommonDataType::BroadcastCode => 0x2D, | |
| CommonDataType::ResolvableSetIdentifier => 0x2E, | |
| CommonDataType::AdvertisingIntervalLong => 0x2F, | |
| CommonDataType::ThreeDInformationData => 0x3D, | |
| CommonDataType::ManufacturerSpecificData => 0xFF, | |
| }; | |
| Self(byte) | |
| } | |
| } | |
| impl From<u8> for CommonDataTypeCode { | |
| fn from(value: u8) -> Self { | |
| Self(value) | |
| } | |
| } | |
| impl From<CommonDataTypeCode> for u8 { | |
| fn from(value: CommonDataTypeCode) -> Self { | |
| value.0 | |
| } | |
| } | |
| /// Data types for assigned type codes. | |
| /// | |
| /// See Bluetooth Assigned Numbers ยง 2.3 | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, strum_macros::EnumIter)] | |
| #[allow(missing_docs)] | |
| pub enum CommonDataType { | |
| Flags, | |
| IncompleteListOf16BitServiceClassUuids, | |
| CompleteListOf16BitServiceClassUuids, | |
| IncompleteListOf32BitServiceClassUuids, | |
| CompleteListOf32BitServiceClassUuids, | |
| IncompleteListOf128BitServiceClassUuids, | |
| CompleteListOf128BitServiceClassUuids, | |
| ShortenedLocalName, | |
| CompleteLocalName, | |
| TxPowerLevel, | |
| ClassOfDevice, | |
| SimplePairingHashC192, | |
| SimplePairingRandomizerR192, | |
| DeviceId, | |
| SecurityManagerTkValue, | |
| SecurityManagerOutOfBandFlags, | |
| PeripheralConnectionIntervalRange, | |
| ListOf16BitServiceSolicitationUuids, | |
| ListOf128BitServiceSolicitationUuids, | |
| ServiceData16BitUuid, | |
| PublicTargetAddress, | |
| RandomTargetAddress, | |
| Appearance, | |
| AdvertisingInterval, | |
| LeBluetoothDeviceAddress, | |
| LeRole, | |
| SimplePairingHashC256, | |
| SimplePairingRandomizerR256, | |
| ListOf32BitServiceSolicitationUuids, | |
| ServiceData32BitUuid, | |
| ServiceData128BitUuid, | |
| LeSecureConnectionsConfirmationValue, | |
| LeSecureConnectionsRandomValue, | |
| Uri, | |
| IndoorPositioning, | |
| TransportDiscoveryData, | |
| LeSupportedFeatures, | |
| ChannelMapUpdateIndication, | |
| PbAdv, | |
| MeshMessage, | |
| MeshBeacon, | |
| BigInfo, | |
| BroadcastCode, | |
| ResolvableSetIdentifier, | |
| AdvertisingIntervalLong, | |
| ThreeDInformationData, | |
| ManufacturerSpecificData, | |
| } | |
| impl CommonDataType { | |
| /// Iterate over the zero, one, or more matching types for the provided code. | |
| /// | |
| /// `0x10` maps to both Device Id and Security Manager TK Value, so multiple matching types | |
| /// may exist for a single code. | |
| pub fn for_type_code(code: CommonDataTypeCode) -> impl Iterator<Item = CommonDataType> { | |
| Self::iter().filter(move |t| CommonDataTypeCode::from(*t) == code) | |
| } | |
| /// Apply type-specific human-oriented formatting to data, if any is applicable | |
| pub fn format_data(&self, data: &[u8]) -> Option<String> { | |
| match self { | |
| Self::Flags => Some(Flags::matching(data).map(|f| format!("{f:?}")).join(",")), | |
| Self::CompleteListOf16BitServiceClassUuids | |
| | Self::IncompleteListOf16BitServiceClassUuids | |
| | Self::ListOf16BitServiceSolicitationUuids => { | |
| combinator::complete(multi::many0(Uuid16::parse_le))(data) | |
| .map(|(_res, uuids)| { | |
| uuids | |
| .into_iter() | |
| .map(|uuid| { | |
| SERVICE_IDS | |
| .get(&uuid) | |
| .map(|name| format!("{uuid:?} ({name})")) | |
| .unwrap_or_else(|| format!("{uuid:?}")) | |
| }) | |
| .join(", ") | |
| }) | |
| .ok() | |
| } | |
| Self::CompleteListOf32BitServiceClassUuids | |
| | Self::IncompleteListOf32BitServiceClassUuids | |
| | Self::ListOf32BitServiceSolicitationUuids => { | |
| combinator::complete(multi::many0(Uuid32::parse))(data) | |
| .map(|(_res, uuids)| uuids.into_iter().map(|u| format!("{u:?}")).join(", ")) | |
| .ok() | |
| } | |
| Self::CompleteListOf128BitServiceClassUuids | |
| | Self::IncompleteListOf128BitServiceClassUuids | |
| | Self::ListOf128BitServiceSolicitationUuids => { | |
| combinator::complete(multi::many0(Uuid128::parse_le))(data) | |
| .map(|(_res, uuids)| uuids.into_iter().map(|u| format!("{u:?}")).join(", ")) | |
| .ok() | |
| } | |
| Self::ServiceData16BitUuid => Uuid16::parse_le(data) | |
| .map(|(rem, uuid)| { | |
| format!( | |
| "service={:?}, data={}", | |
| SERVICE_IDS | |
| .get(&uuid) | |
| .map(|name| format!("{uuid:?} ({name})")) | |
| .unwrap_or_else(|| format!("{uuid:?}")), | |
| hex::encode_upper(rem) | |
| ) | |
| }) | |
| .ok(), | |
| Self::ServiceData32BitUuid => Uuid32::parse(data) | |
| .map(|(rem, uuid)| format!("service={:?}, data={}", uuid, hex::encode_upper(rem))) | |
| .ok(), | |
| Self::ServiceData128BitUuid => Uuid128::parse_le(data) | |
| .map(|(rem, uuid)| format!("service={:?}, data={}", uuid, hex::encode_upper(rem))) | |
| .ok(), | |
| Self::ShortenedLocalName | Self::CompleteLocalName => { | |
| std::str::from_utf8(data).ok().map(|s| format!("\"{s}\"")) | |
| } | |
| Self::TxPowerLevel => { | |
| let (_, tx) = | |
| combinator::complete(number::complete::i8::<_, nom::error::Error<_>>)(data) | |
| .ok()?; | |
| Some(tx.to_string()) | |
| } | |
| Self::ManufacturerSpecificData => { | |
| let (rem, id) = Uuid16::parse_le(data).ok()?; | |
| Some(format!( | |
| "company={}, data=0x{}", | |
| COMPANY_IDS | |
| .get(&id) | |
| .map(|s| s.to_string()) | |
| .unwrap_or_else(|| format!("{id:?}")), | |
| hex::encode_upper(rem) | |
| )) | |
| } | |
| _ => None, | |
| } | |
| } | |
| } | |
| impl fmt::Display for CommonDataType { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| CommonDataType::Flags => write!(f, "Flags"), | |
| CommonDataType::IncompleteListOf16BitServiceClassUuids => { | |
| write!(f, "Incomplete List of 16-bit Service Class UUIDs") | |
| } | |
| CommonDataType::CompleteListOf16BitServiceClassUuids => { | |
| write!(f, "Complete List of 16-bit Service Class UUIDs") | |
| } | |
| CommonDataType::IncompleteListOf32BitServiceClassUuids => { | |
| write!(f, "Incomplete List of 32-bit Service Class UUIDs") | |
| } | |
| CommonDataType::CompleteListOf32BitServiceClassUuids => { | |
| write!(f, "Complete List of 32-bit Service Class UUIDs") | |
| } | |
| CommonDataType::ListOf16BitServiceSolicitationUuids => { | |
| write!(f, "List of 16-bit Service Solicitation UUIDs") | |
| } | |
| CommonDataType::ListOf32BitServiceSolicitationUuids => { | |
| write!(f, "List of 32-bit Service Solicitation UUIDs") | |
| } | |
| CommonDataType::ListOf128BitServiceSolicitationUuids => { | |
| write!(f, "List of 128-bit Service Solicitation UUIDs") | |
| } | |
| CommonDataType::IncompleteListOf128BitServiceClassUuids => { | |
| write!(f, "Incomplete List of 128-bit Service Class UUIDs") | |
| } | |
| CommonDataType::CompleteListOf128BitServiceClassUuids => { | |
| write!(f, "Complete List of 128-bit Service Class UUIDs") | |
| } | |
| CommonDataType::ShortenedLocalName => write!(f, "Shortened Local Name"), | |
| CommonDataType::CompleteLocalName => write!(f, "Complete Local Name"), | |
| CommonDataType::TxPowerLevel => write!(f, "TX Power Level"), | |
| CommonDataType::ClassOfDevice => write!(f, "Class of Device"), | |
| CommonDataType::SimplePairingHashC192 => { | |
| write!(f, "Simple Pairing Hash C-192") | |
| } | |
| CommonDataType::SimplePairingHashC256 => { | |
| write!(f, "Simple Pairing Hash C 256") | |
| } | |
| CommonDataType::SimplePairingRandomizerR192 => { | |
| write!(f, "Simple Pairing Randomizer R-192") | |
| } | |
| CommonDataType::SimplePairingRandomizerR256 => { | |
| write!(f, "Simple Pairing Randomizer R 256") | |
| } | |
| CommonDataType::DeviceId => write!(f, "Device Id"), | |
| CommonDataType::SecurityManagerTkValue => { | |
| write!(f, "Security Manager TK Value") | |
| } | |
| CommonDataType::SecurityManagerOutOfBandFlags => { | |
| write!(f, "Security Manager Out of Band Flags") | |
| } | |
| CommonDataType::PeripheralConnectionIntervalRange => { | |
| write!(f, "Peripheral Connection Interval Range") | |
| } | |
| CommonDataType::ServiceData16BitUuid => { | |
| write!(f, "Service Data 16-bit UUID") | |
| } | |
| CommonDataType::ServiceData32BitUuid => { | |
| write!(f, "Service Data 32-bit UUID") | |
| } | |
| CommonDataType::ServiceData128BitUuid => { | |
| write!(f, "Service Data 128-bit UUID") | |
| } | |
| CommonDataType::PublicTargetAddress => write!(f, "Public Target Address"), | |
| CommonDataType::RandomTargetAddress => write!(f, "Random Target Address"), | |
| CommonDataType::Appearance => write!(f, "Appearance"), | |
| CommonDataType::AdvertisingInterval => write!(f, "Advertising Interval"), | |
| CommonDataType::LeBluetoothDeviceAddress => { | |
| write!(f, "LE Bluetooth Device Address") | |
| } | |
| CommonDataType::LeRole => write!(f, "LE Role"), | |
| CommonDataType::LeSecureConnectionsConfirmationValue => { | |
| write!(f, "LE Secure Connections Confirmation Value") | |
| } | |
| CommonDataType::LeSecureConnectionsRandomValue => { | |
| write!(f, "LE Secure Connections Random Value") | |
| } | |
| CommonDataType::LeSupportedFeatures => write!(f, "LE Supported Features"), | |
| CommonDataType::Uri => write!(f, "URI"), | |
| CommonDataType::IndoorPositioning => write!(f, "Indoor Positioning"), | |
| CommonDataType::TransportDiscoveryData => { | |
| write!(f, "Transport Discovery Data") | |
| } | |
| CommonDataType::ChannelMapUpdateIndication => { | |
| write!(f, "Channel Map Update Indication") | |
| } | |
| CommonDataType::PbAdv => write!(f, "PB-ADV"), | |
| CommonDataType::MeshMessage => write!(f, "Mesh Message"), | |
| CommonDataType::MeshBeacon => write!(f, "Mesh Beacon"), | |
| CommonDataType::BigInfo => write!(f, "BIGIInfo"), | |
| CommonDataType::BroadcastCode => write!(f, "Broadcast Code"), | |
| CommonDataType::ResolvableSetIdentifier => { | |
| write!(f, "Resolvable Set Identifier") | |
| } | |
| CommonDataType::AdvertisingIntervalLong => { | |
| write!(f, "Advertising Interval Long") | |
| } | |
| CommonDataType::ThreeDInformationData => write!(f, "3D Information Data"), | |
| CommonDataType::ManufacturerSpecificData => { | |
| write!(f, "Manufacturer Specific Data") | |
| } | |
| } | |
| } | |
| } | |
| /// Accumulates advertisement data to broadcast on a [crate::wrapper::device::Device]. | |
| #[derive(Debug, Clone, Default)] | |
| pub struct AdvertisementDataBuilder { | |
| encoded_data: Vec<u8>, | |
| } | |
| impl AdvertisementDataBuilder { | |
| /// Returns a new, empty instance. | |
| pub fn new() -> Self { | |
| Self { | |
| encoded_data: Vec::new(), | |
| } | |
| } | |
| /// Append advertising data to the builder. | |
| /// | |
| /// Returns an error if the data cannot be appended. | |
| pub fn append( | |
| &mut self, | |
| type_code: impl Into<CommonDataTypeCode>, | |
| data: &[u8], | |
| ) -> Result<(), AdvertisementDataBuilderError> { | |
| self.encoded_data.push( | |
| data.len() | |
| .try_into() | |
| .ok() | |
| .and_then(|len: u8| len.checked_add(1)) | |
| .ok_or(AdvertisementDataBuilderError::DataTooLong)?, | |
| ); | |
| self.encoded_data.push(type_code.into().0); | |
| self.encoded_data.extend_from_slice(data); | |
| Ok(()) | |
| } | |
| pub(crate) fn into_bytes(self) -> Vec<u8> { | |
| self.encoded_data | |
| } | |
| } | |
| /// Errors that can occur when building advertisement data with [AdvertisementDataBuilder]. | |
| #[derive(Debug, PartialEq, Eq, thiserror::Error)] | |
| pub enum AdvertisementDataBuilderError { | |
| /// The provided adv data is too long to be encoded | |
| #[error("Data too long")] | |
| DataTooLong, | |
| } | |
| #[derive(PartialEq, Eq, strum_macros::EnumIter)] | |
| #[allow(missing_docs)] | |
| /// Features in the Flags AD | |
| pub enum Flags { | |
| LeLimited, | |
| LeDiscoverable, | |
| NoBrEdr, | |
| BrEdrController, | |
| BrEdrHost, | |
| } | |
| impl fmt::Debug for Flags { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "{}", self.short_name()) | |
| } | |
| } | |
| impl Flags { | |
| /// Iterates over the flags that are present in the provided `flags` bytes. | |
| pub fn matching(flags: &[u8]) -> impl Iterator<Item = Self> + '_ { | |
| // The encoding is not clear from the spec: do we look at the first byte? or the last? | |
| // In practice it's only one byte. | |
| let first_byte = flags.first().unwrap_or(&0_u8); | |
| Self::iter().filter(move |f| { | |
| let mask = match f { | |
| Flags::LeLimited => 0x01_u8, | |
| Flags::LeDiscoverable => 0x02, | |
| Flags::NoBrEdr => 0x04, | |
| Flags::BrEdrController => 0x08, | |
| Flags::BrEdrHost => 0x10, | |
| }; | |
| mask & first_byte > 0 | |
| }) | |
| } | |
| /// An abbreviated form of the flag name. | |
| /// | |
| /// See [Flags::name] for the full name. | |
| pub fn short_name(&self) -> &'static str { | |
| match self { | |
| Flags::LeLimited => "LE Limited", | |
| Flags::LeDiscoverable => "LE General", | |
| Flags::NoBrEdr => "No BR/EDR", | |
| Flags::BrEdrController => "BR/EDR C", | |
| Flags::BrEdrHost => "BR/EDR H", | |
| } | |
| } | |
| /// The human-readable name of the flag. | |
| /// | |
| /// See [Flags::short_name] for a shorter string for use if compactness is important. | |
| pub fn name(&self) -> &'static str { | |
| match self { | |
| Flags::LeLimited => "LE Limited Discoverable Mode", | |
| Flags::LeDiscoverable => "LE General Discoverable Mode", | |
| Flags::NoBrEdr => "BR/EDR Not Supported", | |
| Flags::BrEdrController => "Simultaneous LE and BR/EDR (Controller)", | |
| Flags::BrEdrHost => "Simultaneous LE and BR/EDR (Host)", | |
| } | |
| } | |
| } |