blob: 6d10ee59f4700c85279a0f7d4a4b49c6d00573cd [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod cxx;
use std::pin::Pin;
use crate::cxx::ffi;
// Definitions of the data type flags:
// https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/
const DATA_TYPE_FLAGS: u8 = 0x01;
const DATA_TYPE_SERVICE_UUIDS_16BIT_PARTIAL: u8 = 0x02;
const DATA_TYPE_SERVICE_UUIDS_16BIT_COMPLETE: u8 = 0x03;
const DATA_TYPE_SERVICE_UUIDS_32BIT_PARTIAL: u8 = 0x04;
const DATA_TYPE_SERVICE_UUIDS_32BIT_COMPLETE: u8 = 0x05;
const DATA_TYPE_SERVICE_UUIDS_128BIT_PARTIAL: u8 = 0x06;
const DATA_TYPE_SERVICE_UUIDS_128BIT_COMPLETE: u8 = 0x07;
const DATA_TYPE_LOCAL_NAME_SHORT: u8 = 0x08;
const DATA_TYPE_LOCAL_NAME_COMPLETE: u8 = 0x09;
const DATA_TYPE_TX_POWER_LEVEL: u8 = 0x0A;
const DATA_TYPE_SERVICE_DATA: u8 = 0x16;
const DATA_TYPE_MANUFACTURER_DATA: u8 = 0xFF;
const UUID_PLACEHOLDER: [u8; 16] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB,
];
pub fn parse(advertisement_data: &[u8], record: Pin<&mut ffi::ScanRecord>) -> bool {
parse_impl(advertisement_data, record).is_some()
}
fn parse_impl<'a>(
mut advertisement_data: &'a [u8],
mut record: Pin<&mut ffi::ScanRecord>,
) -> Option<()> {
// A reference for BLE advertising data:
// https://community.silabs.com/s/article/kba-bt-0201-bluetooth-advertising-data-basics
// The data consists of a sequence of lengths and packets, where:
// - a single byte <length> describes the length of the following packet.
// Packets must be at least 2 bytes in length; a packet must contain at least
// one byte of data.
// - a packet starting with a single byte <type> that describes the data type.
// - and the remainder is the packet data, interpreted according to <type>.
// TODO(dcheng): The C++ style guide very clearly discourages treating signed
// types as bitfields, and yet here we are.
let mut advertising_flags: i8 = -1;
let mut advertisement_name: &'a [u8] = &[];
// TODO(dcheng): It's unclear what the correct default value here is, as the
// original C++ implementation does not bother initializing this.
let mut tx_power: i8 = 0;
loop {
let Some((&length, remainder)) = advertisement_data.split_first() else {
break;
};
let length: usize = length.into();
if length <= 1 || length > remainder.len() {
return None;
}
let packet;
(packet, advertisement_data) = remainder.split_at(length);
// Length must be at least one, so this is always safe.
let (&data_type, data) = packet.split_first().unwrap();
match data_type {
// For flags, additional bytes past the first are silently ignored. The unwrap() is
// guaranteed to succeed, due to the length <= 1 check above.
DATA_TYPE_FLAGS => advertising_flags = *data.first().unwrap() as i8,
// TODO(dcheng): The core supplement says:
// Two Service or Service Class UUID data types are assigned to each size of
// Service UUID. One Service or Service Class UUID data type indicates that the
// Service or Service Class UUID list is incomplete and the other indicates the
// Service or Service Class UUID list is complete.
//
// A packet or data block shall not contain more than one instance for each
// Service or Service Class UUID data size.
//
// But the parser does not seem to consider that.
DATA_TYPE_SERVICE_UUIDS_16BIT_PARTIAL | DATA_TYPE_SERVICE_UUIDS_16BIT_COMPLETE => {
parse_service_uuids(data, UuidFormat::With16Bits, |uuid| {
ffi::add_service_uuid(record.as_mut(), uuid);
})?
}
DATA_TYPE_SERVICE_UUIDS_32BIT_PARTIAL | DATA_TYPE_SERVICE_UUIDS_32BIT_COMPLETE => {
parse_service_uuids(data, UuidFormat::With32Bits, |uuid| {
ffi::add_service_uuid(record.as_mut(), uuid);
})?
}
DATA_TYPE_SERVICE_UUIDS_128BIT_PARTIAL | DATA_TYPE_SERVICE_UUIDS_128BIT_COMPLETE => {
parse_service_uuids(data, UuidFormat::With128Bits, |uuid| {
ffi::add_service_uuid(record.as_mut(), uuid);
})?
}
DATA_TYPE_LOCAL_NAME_SHORT | DATA_TYPE_LOCAL_NAME_COMPLETE => {
// TODO(crbug.com/423064072): The Core Specification Supplement, Part A, Section
// 1.2, states that this should be a valid UTF-8 string. The original C++
// implementation certainly never bothered to validate, but maybe it's possible
// to be stricter...
advertisement_name = data;
}
// For TX power, additional bytes past the first are silently ignored. The unwrap() is
// guaranteed to succeed, due to the length <= 1 check above.
DATA_TYPE_TX_POWER_LEVEL => tx_power = *data.first().unwrap() as i8,
DATA_TYPE_SERVICE_DATA => {
if data.len() < 4 {
return None;
}
let (uuid, data) = data.split_at(2);
let uuid = parse_uuid(uuid, UuidFormat::With16Bits)?;
ffi::add_service_data(record.as_mut(), &uuid, data);
}
DATA_TYPE_MANUFACTURER_DATA => {
if data.len() < 4 {
return None;
}
// This unwrap() is safe due to the length check above.
let (key_bytes, data) = data.split_first_chunk::<2>().unwrap();
let manufacturer_key = u16::from_le_bytes(*key_bytes);
ffi::add_manufacturer_data(record.as_mut(), manufacturer_key, data);
}
// Unknown packet types are silently ignored.
_ => (),
}
}
ffi::set_advertising_flags(record.as_mut(), advertising_flags);
ffi::set_tx_power(record.as_mut(), tx_power);
ffi::set_advertisement_name(record.as_mut(), advertisement_name);
Some(())
}
use ffi::UuidFormat;
impl UuidFormat {
fn get_offset_and_len(self) -> (usize, usize) {
match self {
UuidFormat::With16Bits => (2, 2),
UuidFormat::With32Bits => (0, 4),
UuidFormat::With128Bits => (0, 16),
_ => unreachable!(),
}
}
}
/// Parses
fn parse_service_uuids<F>(bytes: &[u8], format: UuidFormat, mut f: F) -> Option<()>
where
F: FnMut(&[u8; 16]),
{
let (_, len) = format.get_offset_and_len();
let chunks = bytes.chunks_exact(len);
if !chunks.remainder().is_empty() {
return None;
}
for chunk in chunks {
let uuid = parse_uuid(chunk, format)?;
f(&uuid);
}
Some(())
}
fn parse_uuid(bytes: &[u8], format: UuidFormat) -> Option<[u8; 16]> {
let (offset, len) = format.get_offset_and_len();
if bytes.len() != len {
return None;
}
let mut uuid = UUID_PLACEHOLDER;
for (src, dst) in bytes.iter().rev().zip(uuid[offset..offset + len].iter_mut()) {
*dst = *src;
}
Some(uuid)
}
fn parse_service_uuids_for_test(
bytes: &[u8],
format: UuidFormat,
mut uuid_list_builder: Pin<&mut ffi::UuidListBuilderForTest>,
) -> bool {
parse_service_uuids(bytes, format, |uuid| {
uuid_list_builder.as_mut().add_uuid(uuid);
})
.is_some()
}
fn parse_uuid_for_test(bytes: &[u8], format: UuidFormat, out: &mut [u8; 16]) -> bool {
if let Some(uuid) = parse_uuid(bytes, format) {
*out = uuid;
true
} else {
false
}
}