| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "services/data_decoder/ble_scan_parser_impl.h" |
| |
| namespace data_decoder { |
| |
| // Definitions of the data type flags: |
| // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ |
| const int kDataTypeFlags = 0x01; |
| const int kDataTypeServiceUuids16BitPartial = 0x02; |
| const int kDataTypeServiceUuids16BitComplete = 0x03; |
| const int kDataTypeServiceUuids32BitPartial = 0x04; |
| const int kDataTypeServiceUuids32BitComplete = 0x05; |
| const int kDataTypeServiceUuids128BitPartial = 0x06; |
| const int kDataTypeServiceUuids128BitComplete = 0x07; |
| const int kDataTypeLocalNameShort = 0x08; |
| const int kDataTypeLocalNameComplete = 0x09; |
| const int kDataTypeTxPowerLevel = 0x0A; |
| const int kDataTypeServiceData = 0x16; |
| const int kDataTypeManufacturerData = 0xFF; |
| |
| const char kUuidPrefix[] = "0000"; |
| const char kUuidSuffix[] = "-0000-1000-8000-00805F9B34FB"; |
| |
| BleScanParserImpl::BleScanParserImpl() = default; |
| |
| BleScanParserImpl::~BleScanParserImpl() = default; |
| |
| void BleScanParserImpl::Parse(const std::vector<uint8_t>& advertisement_data, |
| ParseCallback callback) { |
| std::move(callback).Run(ParseBleScan(advertisement_data)); |
| } |
| |
| mojom::ScanRecordPtr BleScanParserImpl::ParseBleScan( |
| base::span<const uint8_t> advertisement_data) { |
| uint8_t tx_power; |
| std::string advertisement_name; |
| std::vector<device::BluetoothUUID> service_uuids; |
| base::flat_map<std::string, std::vector<uint8_t>> service_data_map; |
| base::flat_map<uint16_t, std::vector<uint8_t>> manufacturer_data_map; |
| |
| int advertising_flags = -1; |
| |
| // A reference for BLE advertising data: https://bit.ly/2DUTnsk |
| for (size_t i = 0; i < advertisement_data.size();) { |
| uint8_t length = advertisement_data[i++]; |
| if (length <= 1 || length > advertisement_data.size() - i) { |
| return nullptr; |
| } |
| |
| // length includes the field_type byte. |
| uint8_t data_length = length - 1; |
| uint8_t field_type = advertisement_data[i++]; |
| |
| switch (field_type) { |
| case kDataTypeFlags: |
| advertising_flags = advertisement_data[i]; |
| break; |
| case kDataTypeServiceUuids16BitPartial: |
| case kDataTypeServiceUuids16BitComplete: |
| if (!ParseServiceUuids(advertisement_data.subspan(i, data_length), |
| UuidFormat::kFormat16Bit, &service_uuids)) { |
| return nullptr; |
| } |
| break; |
| case kDataTypeServiceUuids32BitPartial: |
| case kDataTypeServiceUuids32BitComplete: |
| if (!ParseServiceUuids(advertisement_data.subspan(i, data_length), |
| UuidFormat::kFormat32Bit, &service_uuids)) { |
| return nullptr; |
| } |
| break; |
| case kDataTypeServiceUuids128BitPartial: |
| case kDataTypeServiceUuids128BitComplete: |
| if (!ParseServiceUuids(advertisement_data.subspan(i, data_length), |
| UuidFormat::kFormat128Bit, &service_uuids)) { |
| return nullptr; |
| } |
| break; |
| case kDataTypeLocalNameShort: |
| case kDataTypeLocalNameComplete: { |
| base::span<const uint8_t> s = |
| advertisement_data.subspan(i, data_length); |
| advertisement_name = std::string(s.begin(), s.end()); |
| break; |
| } |
| case kDataTypeTxPowerLevel: |
| tx_power = advertisement_data[i]; |
| break; |
| case kDataTypeServiceData: { |
| if (data_length < 4) { |
| return nullptr; |
| } |
| |
| base::span<const uint8_t> uuid = advertisement_data.subspan(i, 2); |
| base::span<const uint8_t> data = |
| advertisement_data.subspan(i + 2, data_length - 2); |
| service_data_map[ParseUuid(uuid, UuidFormat::kFormat16Bit)] = |
| std::vector<uint8_t>(data.begin(), data.end()); |
| break; |
| } |
| case kDataTypeManufacturerData: { |
| if (data_length < 4) { |
| return nullptr; |
| } |
| |
| uint16_t manufacturer_key = (advertisement_data[i + 1] << 8); |
| manufacturer_key += advertisement_data[i]; |
| base::span<const uint8_t> s = |
| advertisement_data.subspan(i + 2, data_length - 2); |
| manufacturer_data_map[manufacturer_key] = |
| std::vector<uint8_t>(s.begin(), s.end()); |
| break; |
| } |
| default: |
| // Just ignore. We don't handle other data types. |
| break; |
| } |
| |
| i += data_length; |
| } |
| |
| return mojom::ScanRecord::New(advertising_flags, tx_power, advertisement_name, |
| service_uuids, service_data_map, |
| manufacturer_data_map); |
| } |
| |
| std::string BleScanParserImpl::ParseUuid(base::span<const uint8_t> bytes, |
| UuidFormat format) { |
| size_t length = bytes.size(); |
| if (!(format == UuidFormat::kFormat16Bit && length == 2) && |
| !(format == UuidFormat::kFormat32Bit && length == 4) && |
| !(format == UuidFormat::kFormat128Bit && length == 16)) { |
| return std::string(); |
| } |
| |
| std::string uuid = base::HexEncode(bytes.data(), bytes.size()); |
| |
| switch (format) { |
| case UuidFormat::kFormat16Bit: |
| return kUuidPrefix + uuid + kUuidSuffix; |
| case UuidFormat::kFormat32Bit: |
| return uuid + kUuidSuffix; |
| case UuidFormat::kFormat128Bit: |
| uuid.insert(8, 1, '-'); |
| uuid.insert(13, 1, '-'); |
| uuid.insert(18, 1, '-'); |
| uuid.insert(23, 1, '-'); |
| return uuid; |
| case UuidFormat::kFormatInvalid: |
| NOTREACHED(); |
| } |
| |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| bool BleScanParserImpl::ParseServiceUuids( |
| base::span<const uint8_t> bytes, |
| UuidFormat format, |
| std::vector<device::BluetoothUUID>* service_uuids) { |
| int uuid_length = 0; |
| switch (format) { |
| case UuidFormat::kFormat16Bit: |
| uuid_length = 2; |
| break; |
| case UuidFormat::kFormat32Bit: |
| uuid_length = 4; |
| break; |
| case UuidFormat::kFormat128Bit: |
| uuid_length = 16; |
| break; |
| case UuidFormat::kFormatInvalid: |
| NOTREACHED(); |
| return false; |
| } |
| |
| if (bytes.size() % uuid_length != 0) { |
| return false; |
| } |
| |
| for (size_t start = 0; start < bytes.size(); start += uuid_length) { |
| service_uuids->push_back(device::BluetoothUUID( |
| ParseUuid(bytes.subspan(start, uuid_length), format))); |
| } |
| |
| return true; |
| } |
| |
| } // namespace data_decoder |