blob: 77b1ac583398ad021cc2339548c713e38de9691b [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 "modules/bluetooth/BluetoothUUID.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptRegexp.h"
#include "core/dom/ExceptionCode.h"
#include "platform/UUID.h"
#include "wtf/HashMap.h"
#include "wtf/HexNumber.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/WTFString.h"
namespace blink {
namespace {
typedef WTF::HashMap<String, unsigned> NameToAssignedNumberMap;
enum class GATTAttribute {
Service,
Characteristic,
Descriptor
};
NameToAssignedNumberMap* getAssignedNumberToServiceNameMap()
{
DEFINE_THREAD_SAFE_STATIC_LOCAL(NameToAssignedNumberMap, servicesMap, []() {
// https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
NameToAssignedNumberMap* services = new NameToAssignedNumberMap();
services->add("alert_notification", 0x1811);
services->add("automation_io", 0x1815);
services->add("battery_service", 0x180F);
services->add("blood_pressure", 0x1810);
services->add("body_composition", 0x181B);
services->add("bond_management", 0x181E);
services->add("continuous_glucose_monitoring", 0x181F);
services->add("current_time", 0x1805);
services->add("cycling_power", 0x1818);
services->add("cycling_speed_and_cadence", 0x1816);
services->add("device_information", 0x180A);
services->add("environmental_sensing", 0x181A);
services->add("generic_access", 0x1800);
services->add("generic_attribute", 0x1801);
services->add("glucose", 0x1808);
services->add("health_thermometer", 0x1809);
services->add("heart_rate", 0x180D);
services->add("human_interface_device", 0x1812);
services->add("immediate_alert", 0x1802);
services->add("indoor_positioning", 0x1821);
services->add("internet_protocol_support", 0x1820);
services->add("link_loss", 0x1803);
services->add("location_and_navigation", 0x1819);
services->add("next_dst_change", 0x1807);
services->add("phone_alert_status", 0x180E);
services->add("pulse_oximeter", 0x1822);
services->add("reference_time_update", 0x1806);
services->add("running_speed_and_cadence", 0x1814);
services->add("scan_parameters", 0x1813);
services->add("tx_power", 0x1804);
services->add("user_data", 0x181C);
services->add("weight_scale", 0x181D);
return services;
}());
return &servicesMap;
}
NameToAssignedNumberMap* getAssignedNumberForCharacteristicNameMap()
{
DEFINE_THREAD_SAFE_STATIC_LOCAL(NameToAssignedNumberMap, characteristicsMap, []() {
// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
NameToAssignedNumberMap* characteristics = new NameToAssignedNumberMap();
characteristics->add("aerobic_heart_rate_lower_limit", 0x2A7E);
characteristics->add("aerobic_heart_rate_upper_limit", 0x2A84);
characteristics->add("aerobic_threshold", 0x2A7F);
characteristics->add("age", 0x2A80);
characteristics->add("aggregate", 0x2A5A);
characteristics->add("alert_category_id", 0x2A43);
characteristics->add("alert_category_id_bit_mask", 0x2A42);
characteristics->add("alert_level", 0x2A06);
characteristics->add("alert_notification_control_point", 0x2A44);
characteristics->add("alert_status", 0x2A3F);
characteristics->add("altitude", 0x2AB3);
characteristics->add("anaerobic_heart_rate_lower_limit", 0x2A81);
characteristics->add("anaerobic_heart_rate_upper_limit", 0x2A82);
characteristics->add("anaerobic_threshold", 0x2A83);
characteristics->add("analog", 0x2A58);
characteristics->add("apparent_wind_direction", 0x2A73);
characteristics->add("apparent_wind_speed", 0x2A72);
characteristics->add("gap.appearance", 0x2A01);
characteristics->add("barometric_pressure_trend", 0x2AA3);
characteristics->add("battery_level", 0x2A19);
characteristics->add("blood_pressure_feature", 0x2A49);
characteristics->add("blood_pressure_measurement", 0x2A35);
characteristics->add("body_composition_feature", 0x2A9B);
characteristics->add("body_composition_measurement", 0x2A9C);
characteristics->add("body_sensor_location", 0x2A38);
characteristics->add("bond_management_control_point", 0x2AA4);
characteristics->add("bond_management_feature", 0x2AA5);
characteristics->add("boot_keyboard_input_report", 0x2A22);
characteristics->add("boot_keyboard_output_report", 0x2A32);
characteristics->add("boot_mouse_input_report", 0x2A33);
characteristics->add("gap.central_address_resolution_support", 0x2AA6);
characteristics->add("cgm_feature", 0x2AA8);
characteristics->add("cgm_measurement", 0x2AA7);
characteristics->add("cgm_session_run_time", 0x2AAB);
characteristics->add("cgm_session_start_time", 0x2AAA);
characteristics->add("cgm_specific_ops_control_point", 0x2AAC);
characteristics->add("cgm_status", 0x2AA9);
characteristics->add("csc_feature", 0x2A5C);
characteristics->add("csc_measurement", 0x2A5B);
characteristics->add("current_time", 0x2A2B);
characteristics->add("cycling_power_control_point", 0x2A66);
characteristics->add("cycling_power_feature", 0x2A65);
characteristics->add("cycling_power_measurement", 0x2A63);
characteristics->add("cycling_power_vector", 0x2A64);
characteristics->add("database_change_increment", 0x2A99);
characteristics->add("date_of_birth", 0x2A85);
characteristics->add("date_of_threshold_assessment", 0x2A86);
characteristics->add("date_time", 0x2A08);
characteristics->add("day_date_time", 0x2A0A);
characteristics->add("day_of_week", 0x2A09);
characteristics->add("descriptor_value_changed", 0x2A7D);
characteristics->add("gap.device_name", 0x2A00);
characteristics->add("dew_point", 0x2A7B);
characteristics->add("digital", 0x2A56);
characteristics->add("dst_offset", 0x2A0D);
characteristics->add("elevation", 0x2A6C);
characteristics->add("email_address", 0x2A87);
characteristics->add("exact_time_256", 0x2A0C);
characteristics->add("fat_burn_heart_rate_lower_limit", 0x2A88);
characteristics->add("fat_burn_heart_rate_upper_limit", 0x2A89);
characteristics->add("firmware_revision_string", 0x2A26);
characteristics->add("first_name", 0x2A8A);
characteristics->add("five_zone_heart_rate_limits", 0x2A8B);
characteristics->add("floor_number", 0x2AB2);
characteristics->add("gender", 0x2A8C);
characteristics->add("glucose_feature", 0x2A51);
characteristics->add("glucose_measurement", 0x2A18);
characteristics->add("glucose_measurement_context", 0x2A34);
characteristics->add("gust_factor", 0x2A74);
characteristics->add("hardware_revision_string", 0x2A27);
characteristics->add("heart_rate_control_point", 0x2A39);
characteristics->add("heart_rate_max", 0x2A8D);
characteristics->add("heart_rate_measurement", 0x2A37);
characteristics->add("heat_index", 0x2A7A);
characteristics->add("height", 0x2A8E);
characteristics->add("hid_control_point", 0x2A4C);
characteristics->add("hid_information", 0x2A4A);
characteristics->add("hip_circumference", 0x2A8F);
characteristics->add("humidity", 0x2A6F);
characteristics->add("ieee_11073-20601_regulatory_certification_data_list", 0x2A2A);
characteristics->add("indoor_positioning_configuration", 0x2AAD);
characteristics->add("intermediate_blood_pressure", 0x2A36);
characteristics->add("intermediate_temperature", 0x2A1E);
characteristics->add("irradiance", 0x2A77);
characteristics->add("language", 0x2AA2);
characteristics->add("last_name", 0x2A90);
characteristics->add("latitude", 0x2AAE);
characteristics->add("ln_control_point", 0x2A6B);
characteristics->add("ln_feature", 0x2A6A);
characteristics->add("local_east_coordinate.xml", 0x2AB1);
characteristics->add("local_north_coordinate", 0x2AB0);
characteristics->add("local_time_information", 0x2A0F);
characteristics->add("location_and_speed", 0x2A67);
characteristics->add("location_name", 0x2AB5);
characteristics->add("longitude", 0x2AAF);
characteristics->add("magnetic_declination", 0x2A2C);
characteristics->add("magnetic_flux_density_2D", 0x2AA0);
characteristics->add("magnetic_flux_density_3D", 0x2AA1);
characteristics->add("manufacturer_name_string", 0x2A29);
characteristics->add("maximum_recommended_heart_rate", 0x2A91);
characteristics->add("measurement_interval", 0x2A21);
characteristics->add("model_number_string", 0x2A24);
characteristics->add("navigation", 0x2A68);
characteristics->add("new_alert", 0x2A46);
characteristics->add("gap.peripheral_preferred_connection_parameters", 0x2A04);
characteristics->add("gap.peripheral_privacy_flag", 0x2A02);
characteristics->add("plx_continuous_measurement", 0x2A5F);
characteristics->add("plx_features", 0x2A60);
characteristics->add("plx_spot_check_measurement", 0x2A5E);
characteristics->add("pnp_id", 0x2A50);
characteristics->add("pollen_concentration", 0x2A75);
characteristics->add("position_quality", 0x2A69);
characteristics->add("pressure", 0x2A6D);
characteristics->add("protocol_mode", 0x2A4E);
characteristics->add("rainfall", 0x2A78);
characteristics->add("gap.reconnection_address", 0x2A03);
characteristics->add("record_access_control_point", 0x2A52);
characteristics->add("reference_time_information", 0x2A14);
characteristics->add("report", 0x2A4D);
characteristics->add("report_map", 0x2A4B);
characteristics->add("resting_heart_rate", 0x2A92);
characteristics->add("ringer_control_point", 0x2A40);
characteristics->add("ringer_setting", 0x2A41);
characteristics->add("rsc_feature", 0x2A54);
characteristics->add("rsc_measurement", 0x2A53);
characteristics->add("sc_control_point", 0x2A55);
characteristics->add("scan_interval_window", 0x2A4F);
characteristics->add("scan_refresh", 0x2A31);
characteristics->add("sensor_location", 0x2A5D);
characteristics->add("serial_number_string", 0x2A25);
characteristics->add("gatt.service_changed", 0x2A05);
characteristics->add("software_revision_string", 0x2A28);
characteristics->add("sport_type_for_aerobic_and_anaerobic_thresholds", 0x2A93);
characteristics->add("supported_new_alert_category", 0x2A47);
characteristics->add("supported_unread_alert_category", 0x2A48);
characteristics->add("system_id", 0x2A23);
characteristics->add("temperature", 0x2A6E);
characteristics->add("temperature_measurement", 0x2A1C);
characteristics->add("temperature_type", 0x2A1D);
characteristics->add("three_zone_heart_rate_limits", 0x2A94);
characteristics->add("time_accuracy", 0x2A12);
characteristics->add("time_source", 0x2A13);
characteristics->add("time_update_control_point", 0x2A16);
characteristics->add("time_update_state", 0x2A17);
characteristics->add("time_with_dst", 0x2A11);
characteristics->add("time_zone", 0x2A0E);
characteristics->add("true_wind_direction", 0x2A71);
characteristics->add("true_wind_speed", 0x2A70);
characteristics->add("two_zone_heart_rate_limit", 0x2A95);
characteristics->add("tx_power_level", 0x2A07);
characteristics->add("uncertainty", 0x2AB4);
characteristics->add("unread_alert_status", 0x2A45);
characteristics->add("user_control_point", 0x2A9F);
characteristics->add("user_index", 0x2A9A);
characteristics->add("uv_index", 0x2A76);
characteristics->add("vo2_max", 0x2A96);
characteristics->add("waist_circumference", 0x2A97);
characteristics->add("weight", 0x2A98);
characteristics->add("weight_measurement", 0x2A9D);
characteristics->add("weight_scale_feature", 0x2A9E);
characteristics->add("wind_chill", 0x2A79);
return characteristics;
}());
return &characteristicsMap;
}
NameToAssignedNumberMap* getAssignedNumberForDescriptorNameMap()
{
DEFINE_THREAD_SAFE_STATIC_LOCAL(NameToAssignedNumberMap, descriptorsMap, []() {
// https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
NameToAssignedNumberMap* descriptors = new NameToAssignedNumberMap();
descriptors->add("gatt.characteristic_extended_properties", 0x2900);
descriptors->add("gatt.characteristic_user_description", 0x2901);
descriptors->add("gatt.client_characteristic_configuration", 0x2902);
descriptors->add("gatt.server_characteristic_configuration", 0x2903);
descriptors->add("gatt.characteristic_presentation_format", 0x2904);
descriptors->add("gatt.characteristic_aggregate_format", 0x2905);
descriptors->add("valid_range", 0x2906);
descriptors->add("external_report_reference", 0x2907);
descriptors->add("report_reference", 0x2908);
descriptors->add("value_trigger_setting", 0x290A);
descriptors->add("es_configuration", 0x290B);
descriptors->add("es_measurement", 0x290C);
descriptors->add("es_trigger_setting", 0x290D);
return descriptors;
}());
return &descriptorsMap;
}
String getUUIDForGATTAttribute(GATTAttribute attribute, StringOrUnsignedLong name, ExceptionState& exceptionState)
{
// Implementation of BluetoothUUID.getService, BluetoothUUID.getCharacteristic
// and BluetoothUUID.getDescriptor algorithms:
// https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothuuid-getservice
// https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothuuid-getcharacteristic
// https://webbluetoothchrome.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 nameStr = name.getAsString();
// If name is a valid UUID, return name and abort these steps.
if (isValidUUID(nameStr))
return nameStr;
// If name is in the corresponding attribute map return
// BluetoothUUID.cannonicalUUID(alias).
NameToAssignedNumberMap* map = nullptr;
const char* attributeType = nullptr;
switch (attribute) {
case GATTAttribute::Service:
map = getAssignedNumberToServiceNameMap();
attributeType = "Service";
break;
case GATTAttribute::Characteristic:
map = getAssignedNumberForCharacteristicNameMap();
attributeType = "Characteristic";
break;
case GATTAttribute::Descriptor:
map = getAssignedNumberForDescriptorNameMap();
attributeType = "Descriptor";
break;
}
if (map->contains(nameStr))
return BluetoothUUID::canonicalUUID(map->get(nameStr));
StringBuilder errorMessage;
errorMessage.append("Invalid ");
errorMessage.append(attributeType);
errorMessage.append(" name: '");
errorMessage.append(nameStr);
errorMessage.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::Service:
errorMessage.append("https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx"
" e.g. 'alert_notification'.");
break;
case GATTAttribute::Characteristic:
errorMessage.append("https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx"
" e.g. 'aerobic_heart_rate_lower_limit'.");
break;
case GATTAttribute::Descriptor:
errorMessage.append("https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx"
" e.g. 'gatt.characteristic_presentation_format'.");
break;
}
// Otherwise, throw a SyntaxError.
exceptionState.throwDOMException(SyntaxError, errorMessage.toString());
return String();
}
} // namespace
// static
String BluetoothUUID::getService(StringOrUnsignedLong name, ExceptionState& exceptionState)
{
return getUUIDForGATTAttribute(GATTAttribute::Service, name, exceptionState);
}
// static
String BluetoothUUID::getCharacteristic(StringOrUnsignedLong name, ExceptionState& exceptionState)
{
return getUUIDForGATTAttribute(GATTAttribute::Characteristic, name, exceptionState);
}
// static
String BluetoothUUID::getDescriptor(StringOrUnsignedLong name, ExceptionState& exceptionState)
{
return getUUIDForGATTAttribute(GATTAttribute::Descriptor, name, exceptionState);
}
// static
String BluetoothUUID::canonicalUUID(unsigned alias)
{
StringBuilder builder;
builder.reserveCapacity(36 /* 36 chars or 128 bits, length of a UUID */);
appendUnsignedAsHexFixedSize(
alias,
builder, 8 /* 8 chars or 32 bits, prefix length */,
Lowercase);
builder.append("-0000-1000-8000-00805f9b34fb");
return builder.toString();
}
} // namespace blink