blob: 2fb29d72261ba6ea54ee64160fe4593d55d57614 [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/uuid.h"
namespace blink {
namespace {
typedef WTF::HashMap<String, unsigned> NameToAssignedNumberMap;
enum class GATTAttribute { kService, kCharacteristic, kDescriptor };
NameToAssignedNumberMap* GetAssignedNumberToServiceNameMap() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
NameToAssignedNumberMap, services_map,
({
// https://www.bluetooth.com/specifications/gatt/services
{"generic_access", 0x1800},
{"generic_attribute", 0x1801},
{"immediate_alert", 0x1802},
{"link_loss", 0x1803},
{"tx_power", 0x1804},
{"current_time", 0x1805},
{"reference_time_update", 0x1806},
{"next_dst_change", 0x1807},
{"glucose", 0x1808},
{"health_thermometer", 0x1809},
{"device_information", 0x180A},
{"heart_rate", 0x180D},
{"phone_alert_status", 0x180E},
{"battery_service", 0x180F},
{"blood_pressure", 0x1810},
{"alert_notification", 0x1811},
{"human_interface_device", 0x1812},
{"scan_parameters", 0x1813},
{"running_speed_and_cadence", 0x1814},
{"automation_io", 0x1815},
{"cycling_speed_and_cadence", 0x1816},
{"cycling_power", 0x1818},
{"location_and_navigation", 0x1819},
{"environmental_sensing", 0x181A},
{"body_composition", 0x181B},
{"user_data", 0x181C},
{"weight_scale", 0x181D},
{"bond_management", 0x181E},
{"continuous_glucose_monitoring", 0x181F},
{"internet_protocol_support", 0x1820},
{"indoor_positioning", 0x1821},
{"pulse_oximeter", 0x1822},
{"http_proxy", 0x1823},
{"transport_discovery", 0x1824},
{"object_transfer", 0x1825},
{"fitness_machine", 0x1826},
{"mesh_provisioning", 0x1827},
{"mesh_proxy", 0x1828},
{"reconnection_configuration", 0x1829},
}));
return &services_map;
}
NameToAssignedNumberMap* GetAssignedNumberForCharacteristicNameMap() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
NameToAssignedNumberMap, characteristics_map,
({
// https://www.bluetooth.com/specifications/gatt/characteristics
{"gap.device_name", 0x2A00},
{"gap.appearance", 0x2A01},
{"gap.peripheral_privacy_flag", 0x2A02},
{"gap.reconnection_address", 0x2A03},
{"gap.peripheral_preferred_connection_parameters", 0x2A04},
{"gatt.service_changed", 0x2A05},
{"alert_level", 0x2A06},
{"tx_power_level", 0x2A07},
{"date_time", 0x2A08},
{"day_of_week", 0x2A09},
{"day_date_time", 0x2A0A},
{"exact_time_100", 0x2A0B},
{"exact_time_256", 0x2A0C},
{"dst_offset", 0x2A0D},
{"time_zone", 0x2A0E},
{"local_time_information", 0x2A0F},
{"secondary_time_zone", 0x2A10},
{"time_with_dst", 0x2A11},
{"time_accuracy", 0x2A12},
{"time_source", 0x2A13},
{"reference_time_information", 0x2A14},
{"time_broadcast", 0x2A15},
{"time_update_control_point", 0x2A16},
{"time_update_state", 0x2A17},
{"glucose_measurement", 0x2A18},
{"battery_level", 0x2A19},
{"battery_power_state", 0x2A1A},
{"battery_level_state", 0x2A1B},
{"temperature_measurement", 0x2A1C},
{"temperature_type", 0x2A1D},
{"intermediate_temperature", 0x2A1E},
{"temperature_celsius", 0x2A1F},
{"temperature_fahrenheit", 0x2A20},
{"measurement_interval", 0x2A21},
{"boot_keyboard_input_report", 0x2A22},
{"system_id", 0x2A23},
{"model_number_string", 0x2A24},
{"serial_number_string", 0x2A25},
{"firmware_revision_string", 0x2A26},
{"hardware_revision_string", 0x2A27},
{"software_revision_string", 0x2A28},
{"manufacturer_name_string", 0x2A29},
{"ieee_11073-20601_regulatory_certification_data_list", 0x2A2A},
{"current_time", 0x2A2B},
{"magnetic_declination", 0x2A2C},
{"position_2d", 0x2A2F},
{"position_3d", 0x2A30},
{"scan_refresh", 0x2A31},
{"boot_keyboard_output_report", 0x2A32},
{"boot_mouse_input_report", 0x2A33},
{"glucose_measurement_context", 0x2A34},
{"blood_pressure_measurement", 0x2A35},
{"intermediate_cuff_pressure", 0x2A36},
{"heart_rate_measurement", 0x2A37},
{"body_sensor_location", 0x2A38},
{"heart_rate_control_point", 0x2A39},
{"removable", 0x2A3A},
{"service_required", 0x2A3B},
{"scientific_temperature_celsius", 0x2A3C},
{"string", 0x2A3D},
{"network_availability", 0x2A3E},
{"alert_status", 0x2A3F},
{"ringer_control_point", 0x2A40},
{"ringer_setting", 0x2A41},
{"alert_category_id_bit_mask", 0x2A42},
{"alert_category_id", 0x2A43},
{"alert_notification_control_point", 0x2A44},
{"unread_alert_status", 0x2A45},
{"new_alert", 0x2A46},
{"supported_new_alert_category", 0x2A47},
{"supported_unread_alert_category", 0x2A48},
{"blood_pressure_feature", 0x2A49},
{"hid_information", 0x2A4A},
{"report_map", 0x2A4B},
{"hid_control_point", 0x2A4C},
{"report", 0x2A4D},
{"protocol_mode", 0x2A4E},
{"scan_interval_window", 0x2A4F},
{"pnp_id", 0x2A50},
{"glucose_feature", 0x2A51},
{"record_access_control_point", 0x2A52},
{"rsc_measurement", 0x2A53},
{"rsc_feature", 0x2A54},
{"sc_control_point", 0x2A55},
{"digital", 0x2A56},
{"digital_output", 0x2A57},
{"analog", 0x2A58},
{"analog_output", 0x2A59},
{"aggregate", 0x2A5A},
{"csc_measurement", 0x2A5B},
{"csc_feature", 0x2A5C},
{"sensor_location", 0x2A5D},
{"plx_spot_check_measurement", 0x2A5E},
{"plx_continuous_measurement", 0x2A5F},
{"plx_features", 0x2A60},
{"pulse_oximetry_control_point", 0x2A62},
{"cycling_power_measurement", 0x2A63},
{"cycling_power_vector", 0x2A64},
{"cycling_power_feature", 0x2A65},
{"cycling_power_control_point", 0x2A66},
{"location_and_speed", 0x2A67},
{"navigation", 0x2A68},
{"position_quality", 0x2A69},
{"ln_feature", 0x2A6A},
{"ln_control_point", 0x2A6B},
{"elevation", 0x2A6C},
{"pressure", 0x2A6D},
{"temperature", 0x2A6E},
{"humidity", 0x2A6F},
{"true_wind_speed", 0x2A70},
{"true_wind_direction", 0x2A71},
{"apparent_wind_speed", 0x2A72},
{"apparent_wind_direction", 0x2A73},
{"gust_factor", 0x2A74},
{"pollen_concentration", 0x2A75},
{"uv_index", 0x2A76},
{"irradiance", 0x2A77},
{"rainfall", 0x2A78},
{"wind_chill", 0x2A79},
{"heat_index", 0x2A7A},
{"dew_point", 0x2A7B},
{"descriptor_value_changed", 0x2A7D},
{"aerobic_heart_rate_lower_limit", 0x2A7E},
{"aerobic_threshold", 0x2A7F},
{"age", 0x2A80},
{"anaerobic_heart_rate_lower_limit", 0x2A81},
{"anaerobic_heart_rate_upper_limit", 0x2A82},
{"anaerobic_threshold", 0x2A83},
{"aerobic_heart_rate_upper_limit", 0x2A84},
{"date_of_birth", 0x2A85},
{"date_of_threshold_assessment", 0x2A86},
{"email_address", 0x2A87},
{"fat_burn_heart_rate_lower_limit", 0x2A88},
{"fat_burn_heart_rate_upper_limit", 0x2A89},
{"first_name", 0x2A8A},
{"five_zone_heart_rate_limits", 0x2A8B},
{"gender", 0x2A8C},
{"heart_rate_max", 0x2A8D},
{"height", 0x2A8E},
{"hip_circumference", 0x2A8F},
{"last_name", 0x2A90},
{"maximum_recommended_heart_rate", 0x2A91},
{"resting_heart_rate", 0x2A92},
{"sport_type_for_aerobic_and_anaerobic_thresholds", 0x2A93},
{"three_zone_heart_rate_limits", 0x2A94},
{"two_zone_heart_rate_limit", 0x2A95},
{"vo2_max", 0x2A96},
{"waist_circumference", 0x2A97},
{"weight", 0x2A98},
{"database_change_increment", 0x2A99},
{"user_index", 0x2A9A},
{"body_composition_feature", 0x2A9B},
{"body_composition_measurement", 0x2A9C},
{"weight_measurement", 0x2A9D},
{"weight_scale_feature", 0x2A9E},
{"user_control_point", 0x2A9F},
{"magnetic_flux_density_2D", 0x2AA0},
{"magnetic_flux_density_3D", 0x2AA1},
{"language", 0x2AA2},
{"barometric_pressure_trend", 0x2AA3},
{"bond_management_control_point", 0x2AA4},
{"bond_management_feature", 0x2AA5},
{"gap.central_address_resolution_support", 0x2AA6},
{"cgm_measurement", 0x2AA7},
{"cgm_feature", 0x2AA8},
{"cgm_status", 0x2AA9},
{"cgm_session_start_time", 0x2AAA},
{"cgm_session_run_time", 0x2AAB},
{"cgm_specific_ops_control_point", 0x2AAC},
{"indoor_positioning_configuration", 0x2AAD},
{"latitude", 0x2AAE},
{"longitude", 0x2AAF},
{"local_north_coordinate", 0x2AB0},
{"local_east_coordinate.xml", 0x2AB1},
{"floor_number", 0x2AB2},
{"altitude", 0x2AB3},
{"uncertainty", 0x2AB4},
{"location_name", 0x2AB5},
{"uri", 0x2AB6},
{"http_headers", 0x2AB7},
{"http_status_code", 0x2AB8},
{"http_entity_body", 0x2AB9},
{"http_control_point", 0x2ABA},
{"https_security", 0x2ABB},
{"tds_control_point", 0x2ABC},
{"ots_feature", 0x2ABD},
{"object_name", 0x2ABE},
{"object_type", 0x2ABF},
{"object_size", 0x2AC0},
{"object_first_created", 0x2AC1},
{"object_last_modified", 0x2AC2},
{"object_id", 0x2AC3},
{"object_properties", 0x2AC4},
{"object_action_control_point", 0x2AC5},
{"object_list_control_point", 0x2AC6},
{"object_list_filter", 0x2AC7},
{"object_changed", 0x2AC8},
{"resolvable_private_address_only", 0x2AC9},
{"fitness_machine_feature", 0x2ACC},
{"treadmill_data", 0x2ACD},
{"cross_trainer_data", 0x2ACE},
{"step_climber_data", 0x2ACF},
{"stair_climber_data", 0x2AD0},
{"rower_data", 0x2AD1},
{"indoor_bike_data", 0x2AD2},
{"training_status", 0x2AD3},
{"supported_speed_range", 0x2AD4},
{"supported_inclination_range", 0x2AD5},
{"supported_resistance_level_range", 0x2AD6},
{"supported_heart_rate_range", 0x2AD7},
{"supported_power_range", 0x2AD8},
{"fitness_machine_control_point", 0x2AD9},
{"fitness_machine_status", 0x2ADA},
{"date_utc", 0x2AED},
}));
return &characteristics_map;
}
NameToAssignedNumberMap* GetAssignedNumberForDescriptorNameMap() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
NameToAssignedNumberMap, descriptors_map,
({
// https://www.bluetooth.com/specifications/gatt/descriptors
{"gatt.characteristic_extended_properties", 0x2900},
{"gatt.characteristic_user_description", 0x2901},
{"gatt.client_characteristic_configuration", 0x2902},
{"gatt.server_characteristic_configuration", 0x2903},
{"gatt.characteristic_presentation_format", 0x2904},
{"gatt.characteristic_aggregate_format", 0x2905},
{"valid_range", 0x2906},
{"external_report_reference", 0x2907},
{"report_reference", 0x2908},
{"number_of_digitals", 0x2909},
{"value_trigger_setting", 0x290A},
{"es_configuration", 0x290B},
{"es_measurement", 0x290C},
{"es_trigger_setting", 0x290D},
{"time_trigger_setting", 0x290E},
}));
return &descriptors_map;
}
String GetUUIDForGATTAttribute(GATTAttribute attribute,
StringOrUnsignedLong name,
ExceptionState& exception_state) {
// Implementation of BluetoothUUID.getService, BluetoothUUID.getCharacteristic
// and BluetoothUUID.getDescriptor algorithms:
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getservice
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getcharacteristic
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getdescriptor
// If name is an unsigned long, return BluetoothUUID.cannonicalUUI(name) and
// abort this steps.
if (name.IsUnsignedLong())
return BluetoothUUID::canonicalUUID(name.GetAsUnsignedLong());
String name_str = name.GetAsString();
// If name is a valid UUID, return name and abort these steps.
if (WTF::IsValidUUID(name_str))
return name_str;
// If name is in the corresponding attribute map return
// BluetoothUUID.cannonicalUUID(alias).
NameToAssignedNumberMap* map = nullptr;
const char* attribute_type = nullptr;
switch (attribute) {
case GATTAttribute::kService:
map = GetAssignedNumberToServiceNameMap();
attribute_type = "Service";
break;
case GATTAttribute::kCharacteristic:
map = GetAssignedNumberForCharacteristicNameMap();
attribute_type = "Characteristic";
break;
case GATTAttribute::kDescriptor:
map = GetAssignedNumberForDescriptorNameMap();
attribute_type = "Descriptor";
break;
}
if (map->Contains(name_str))
return BluetoothUUID::canonicalUUID(map->at(name_str));
StringBuilder error_message;
error_message.Append("Invalid ");
error_message.Append(attribute_type);
error_message.Append(" name: '");
error_message.Append(name_str);
error_message.Append(
"'. It must be a valid UUID alias (e.g. 0x1234), "
"UUID (lowercase hex characters e.g. "
"'00001234-0000-1000-8000-00805f9b34fb'), "
"or recognized standard name from ");
switch (attribute) {
case GATTAttribute::kService:
error_message.Append(
"https://www.bluetooth.com/specifications/gatt/services"
" e.g. 'alert_notification'.");
break;
case GATTAttribute::kCharacteristic:
error_message.Append(
"https://www.bluetooth.com/specifications/gatt/characteristics"
" e.g. 'aerobic_heart_rate_lower_limit'.");
break;
case GATTAttribute::kDescriptor:
error_message.Append(
"https://www.bluetooth.com/specifications/gatt/descriptors"
" e.g. 'gatt.characteristic_presentation_format'.");
break;
}
// Otherwise, throw a TypeError.
exception_state.ThrowTypeError(error_message.ToString());
return String();
}
} // namespace
// static
String BluetoothUUID::getService(StringOrUnsignedLong name,
ExceptionState& exception_state) {
return GetUUIDForGATTAttribute(GATTAttribute::kService, name,
exception_state);
}
// static
String BluetoothUUID::getCharacteristic(StringOrUnsignedLong name,
ExceptionState& exception_state) {
return GetUUIDForGATTAttribute(GATTAttribute::kCharacteristic, name,
exception_state);
}
// static
String BluetoothUUID::getDescriptor(StringOrUnsignedLong name,
ExceptionState& exception_state) {
return GetUUIDForGATTAttribute(GATTAttribute::kDescriptor, name,
exception_state);
}
// static
String BluetoothUUID::canonicalUUID(unsigned alias) {
return String::Format("%08x-0000-1000-8000-00805f9b34fb", alias);
}
} // namespace blink