0

[Web Bluetooth]: Add manufacturer data to bluetooth-internals page

This CL adds a new column "Manufacturer Data" to the internal
about:bluetooth-internals page so that developers can inspect
manufacturer specific data from nearby Bluetooth devices.

Change-Id: I40fb78cf62cd285bdcb88ee9c6b98ff7f3b375a8
Bug: 707635
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2860010
Reviewed-by: David Roger <droger@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/master@{#880890}
This commit is contained in:
François Beaufort
2021-05-10 09:25:53 +00:00
committed by Chromium LUCI CQ
parent 03b39ef321
commit 613b3bf2a0
9 changed files with 71 additions and 3 deletions
chrome
device/bluetooth

@ -255,6 +255,7 @@ generate_grd("build_grd") {
"device_collection.js",
"device_details_page.js",
"device_table.js",
"device_utils.js",
"devices_page.js",
"expandable_list.js",
"bluetooth_internals.html",

@ -65,6 +65,7 @@
<th data-field="address">Address</th>
<th data-field="rssi.value">Latest RSSI</th>
<th data-field="services.length">Services</th>
<th data-field="manufacturerDataMap">Manufacturer Data</th>
<th data-field="isGattConnected">GATT Connection State</th>
<th></th>
</tr>

@ -12,6 +12,7 @@ import {$} from 'chrome://resources/js/util.m.js';
import {connectToDevice} from './device_broker.js';
import {ConnectionStatus} from './device_collection.js';
import {formatManufacturerDataMap} from './device_utils.js';
import {ObjectFieldSet} from './object_fieldset.js';
import {Page} from './page.js';
import {ServiceList} from './service_list.js';
@ -27,6 +28,7 @@ const PROPERTY_NAMES = {
isGattConnected: 'GATT Connected',
'rssi.value': 'Latest RSSI',
'services.length': 'Services',
manufacturerDataMap: 'Manufacturer Data',
};
/**
@ -166,12 +168,16 @@ export class DeviceDetailsPage extends Page {
serviceCount = services.length;
}
const manufacturerDataMapText =
formatManufacturerDataMap(this.deviceInfo.manufacturerDataMap);
const deviceViewObj = {
name: this.deviceInfo.nameForDisplay,
address: this.deviceInfo.address,
isGattConnected: connectedText,
'rssi.value': rssiValue,
'services.length': serviceCount,
manufacturerDataMap: manufacturerDataMapText,
};
this.deviceFieldSet_.setObject(deviceViewObj);

@ -10,14 +10,16 @@ import {define as crUiDefine} from 'chrome://resources/js/cr/ui.m.js';
import {$} from 'chrome://resources/js/util.m.js';
import {DeviceCollection} from './device_collection.js';
import {formatManufacturerDataMap} from './device_utils.js';
const COLUMNS = {
NAME: 0,
ADDRESS: 1,
RSSI: 2,
SERVICES: 3,
CONNECTION_STATE: 4,
LINKS: 5,
MANUFACTURER_DATA: 3,
SERVICES: 4,
CONNECTION_STATE: 5,
LINKS: 6,
};
/**
@ -230,6 +232,8 @@ DeviceTable.prototype = {
if (propName == 'isGattConnected') {
obj = obj ? 'Connected' : 'Not Connected';
} else if (propName == 'manufacturerDataMap') {
obj = formatManufacturerDataMap(obj);
}
const cell = row.cells[i];

@ -0,0 +1,19 @@
// Copyright 2021 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.
/**
* Format in a user readable way device manufacturer data map. Keys are
* Bluetooth company identifiers (unsigned short), values are bytes.
* @param {Map<string, array<number>>} manufacturerDataMap
* @return {string}
*/
export function formatManufacturerDataMap(manufacturerDataMap) {
return Object.entries(manufacturerDataMap)
.map(([key, value]) => {
const companyIdentifier = parseInt(key).toString(16).padStart(4, '0');
const data = value.map(v => v.toString(16).padStart(2, '0')).join('');
return `0x${companyIdentifier} 0x${data}`;
})
.join(' | ');
}

@ -93,11 +93,13 @@ suite('bluetooth_internals', function() {
var addressColumn = deviceRow.children[1];
var rssiColumn = deviceRow.children[2];
var servicesColumn = deviceRow.children[3];
var manufacturerDataColumn = deviceRow.children[4];
expectTrue(!!nameForDisplayColumn);
expectTrue(!!addressColumn);
expectTrue(!!rssiColumn);
expectTrue(!!servicesColumn);
expectTrue(!!manufacturerDataColumn);
adapterBroker.deviceChanged(deviceInfo);
@ -114,6 +116,28 @@ suite('bluetooth_internals', function() {
} else {
expectEquals('Unknown', servicesColumn.textContent);
}
if (deviceInfo.manufacturerDataMap) {
expectEquals(
formatManufacturerDataMap(deviceInfo.manufacturerDataMap),
manufacturerDataColumn.textContent);
}
}
/**
* Format in a user readable way device manufacturer data map. Keys are
* Bluetooth company identifiers (unsigned short), values are bytes.
* @param {Map<string, array<number>>} manufacturerDataMap
* @return {string}
*/
function formatManufacturerDataMap(manufacturerDataMap) {
return Object.entries(manufacturerDataMap)
.map(([key, value]) => {
const companyIdentifier = parseInt(key).toString(16).padStart(4, '0');
const data = value.map(v => v.toString(16).padStart(2, '0')).join('');
return `0x${companyIdentifier} 0x${data}`;
})
.join(' | ');
}
/**
@ -454,6 +478,7 @@ suite('bluetooth_internals', function() {
'isGattConnected',
'rssi.value',
'services.length',
'manufacturerDataMap',
].forEach(function(propName) {
var valueCell =
detailsPage.querySelector('fieldset [data-field="' + propName + '"]');
@ -468,6 +493,8 @@ suite('bluetooth_internals', function() {
if (propName === 'isGattConnected') {
value = value ? 'Connected' : 'Not Connected';
} else if (propName === 'manufacturerDataMap') {
value = formatManufacturerDataMap(value);
}
if (typeof (value) === 'boolean') {

@ -205,6 +205,7 @@ export function fakeDeviceInfo1() {
nameForDisplay: 'AAA',
rssi: {value: -40},
isGattConnected: false,
manufacturerDataMap: {'1': [1, 2], '2': [3, 4]},
serviceDataMap: {},
services: [],
};
@ -221,6 +222,7 @@ export function fakeDeviceInfo2() {
nameForDisplay: 'BBB',
rssi: null,
isGattConnected: false,
manufacturerDataMap: {},
serviceDataMap: {},
services: [],
};
@ -236,6 +238,7 @@ export function fakeDeviceInfo3() {
address: 'CC:CC:84:96:92:84',
name: 'CCC',
nameForDisplay: 'CCC',
manufacturerDataMap: {},
serviceDataMap: {},
isGattConnected: false,
};

@ -45,6 +45,9 @@ mojom::DeviceInfoPtr Device::ConstructDeviceInfoStruct(
device_info->rssi->value = device->GetInquiryRSSI().value();
}
for (auto const& it : device->GetManufacturerData())
device_info->manufacturer_data_map.insert_or_assign(it.first, it.second);
for (auto const& it : device->GetServiceData())
device_info->service_data_map.insert_or_assign(it.first, it.second);

@ -68,6 +68,10 @@ struct DeviceInfo {
bool is_gatt_connected;
RSSIWrapper? rssi;
// Important: the blobs associated with each key are arbitrary and untrusted.
// Please refer to the note on "The Rule of 2" at the top of this file.
map<uint16, array<uint8>> manufacturer_data_map;
// Important: the blobs associated with each UUID are arbitrary and untrusted.
// Please refer to the note on "The Rule of 2" at the top of this file.
map<UUID, array<uint8>> service_data_map;