forked from auracaster/bumble_mirror
Added binary that can check for and add Apache 2.0 licenses. Run this binary during the build-rust workflow.
461 lines
18 KiB
Rust
461 lines
18 KiB
Rust
// 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!("{:?} ({name})", uuid))
|
|
.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!("{:?} ({name})", uuid))
|
|
.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)",
|
|
}
|
|
}
|
|
}
|