| // 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/uuid.h" | 
 | #include "third_party/blink/renderer/platform/wtf/hash_map.h" | 
 | #include "third_party/blink/renderer/platform/wtf/hex_number.h" | 
 | #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" | 
 | #include "third_party/blink/renderer/platform/wtf/text/wtf_string.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 (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) { | 
 |   StringBuilder builder; | 
 |   builder.ReserveCapacity(36 /* 36 chars or 128 bits, length of a UUID */); | 
 |   HexNumber::AppendUnsignedAsHexFixedSize( | 
 |       alias, builder, 8 /* 8 chars or 32 bits, prefix length */, | 
 |       HexNumber::kLowercase); | 
 |  | 
 |   builder.Append("-0000-1000-8000-00805f9b34fb"); | 
 |   return builder.ToString(); | 
 | } | 
 |  | 
 | }  // namespace blink |