#include "bluetooth/bluetooth_le_device.h"
#include "bluetooth/bluetooth_privilege.h"
#include "bluetooth/bluetooth_util.h"
+using common::optional;
namespace extension {
namespace bluetooth {
namespace {
+const std::string kData = "data";
+const std::string kUuid = "uuid";
+const std::string kUuids = "uuids";
+const std::string kSolicitationUuids = "solicitationuuids";
+const std::string kServiceData = "serviceData";
+
class ParsedDataHolder {
public:
ParsedDataHolder() : valid_(false) {
common::tools::HexToBin(p_data, size, (unsigned char*)pointer(), length_);
}
- const char* const pointer() const {
+ const char* pointer() const {
return pointer_.get();
}
- const int length() const {
+ int length() const {
return length_;
}
class BluetoothLEServiceData : public ParsedDataHolder {
public:
- const std::string& uuid() const {
+ const UUID& uuid() const {
return uuid_;
}
return data_;
}
- static bool Construct(const picojson::value& obj, BluetoothLEServiceData* out) {
- if (!obj.is<picojson::object>() || !ParseUUID(obj, out) || !ParseData(obj, out)) {
- return false;
+ static optional<BluetoothLEServiceData> Construct(const picojson::value& service_data_obj) {
+ ScopeLogger();
+
+ const auto& data = service_data_obj.get(kData);
+ const auto& uuid_str = service_data_obj.get(kUuid);
+ if (!data.is<std::string>() || !uuid_str.is<std::string>()) {
+ LoggerE("Invalid data type in service data");
+ return {};
}
- out->set_valid();
+ auto uuid = UUID::create(uuid_str.get<std::string>());
+ if (!uuid) {
+ return {};
+ }
- return true;
+ return BluetoothLEServiceData{std::move(*uuid), data.get<std::string>()};
}
private:
- static bool ParseUUID(const picojson::value& obj, BluetoothLEServiceData* out) {
- ScopeLogger();
- const auto& uuid = obj.get("uuid");
- if (uuid.is<std::string>()) {
- out->uuid_ = uuid.get<std::string>();
- } else {
- return false;
- }
-
- return true;
- }
+ BluetoothLEServiceData(UUID&& uuid, const std::string& data_str) : uuid_{std::move(uuid)} {
+ ScopeLogger("UUID: %s, data_str: %s", uuid_.uuid_128_bit.c_str(), data_str.c_str());
- static bool ParseData(const picojson::value& obj, BluetoothLEServiceData* out) {
- ScopeLogger();
- const auto& data = obj.get("data");
- if (data.is<std::string>()) {
- out->data_.Parse(data.get<std::string>());
- } else {
- return false;
- }
+ data_.Parse(data_str);
- return true;
+ set_valid();
}
- std::string uuid_;
+ const UUID uuid_;
HexData data_;
};
return include_name_;
}
- const std::vector<std::string>& service_uuids() const {
+ const std::vector<UUID>& service_uuids() const {
return service_uuids_;
}
- const std::vector<std::string>& solicitation_uuids() const {
+ const std::vector<UUID>& solicitation_uuids() const {
return solicitation_uuids_;
}
return include_tx_power_level_;
}
- const BluetoothLEServiceData& service_data() const {
+ const std::vector<BluetoothLEServiceData>& service_data() const {
return service_data_;
}
static bool ParseServiceUUIDs(const picojson::value& obj, BluetoothLEAdvertiseData* out) {
ScopeLogger();
- const auto& service_uuids = obj.get("uuids");
+ const auto& service_uuids = obj.get(kUuids);
if (service_uuids.is<picojson::array>()) {
- for (const auto& i : service_uuids.get<picojson::array>()) {
- if (i.is<std::string>()) {
- out->service_uuids_.push_back(i.get<std::string>());
+ for (const auto& uuid : service_uuids.get<picojson::array>()) {
+ if (!uuid.is<std::string>()) {
+ LoggerE("uuid is not a string");
+ return false;
+ }
+
+ auto uuid_obj = UUID::create(uuid.get<std::string>());
+ if (uuid_obj) {
+ out->service_uuids_.push_back(*uuid_obj);
} else {
return false;
}
}
} else if (!service_uuids.is<picojson::null>()) {
+ LoggerE("Invalid service_uuids type");
return false;
}
static bool ParseSolicitationUUIDs(const picojson::value& obj, BluetoothLEAdvertiseData* out) {
ScopeLogger();
- const auto& solicitation_uuids = obj.get("solicitationuuids");
+ const auto& solicitation_uuids = obj.get(kSolicitationUuids);
if (solicitation_uuids.is<picojson::array>()) {
- for (const auto& i : solicitation_uuids.get<picojson::array>()) {
- if (i.is<std::string>()) {
- out->solicitation_uuids_.push_back(i.get<std::string>());
+ for (const auto& uuid : solicitation_uuids.get<picojson::array>()) {
+ if (!uuid.is<std::string>()) {
+ LoggerE("uuid is not a string");
+ return false;
+ }
+
+ auto uuid_obj = UUID::create(uuid.get<std::string>());
+ if (uuid_obj) {
+ out->solicitation_uuids_.push_back(*uuid_obj);
} else {
return false;
}
static bool ParseServiceData(const picojson::value& obj, BluetoothLEAdvertiseData* out) {
ScopeLogger();
- const auto& service_data = obj.get("serviceData");
- BluetoothLEServiceData data;
- if (BluetoothLEServiceData::Construct(service_data, &data)) {
- out->service_data_ = std::move(data);
- } else if (!service_data.is<picojson::null>()) {
+
+ const auto& service_data_obj = obj.get(kServiceData);
+ if (service_data_obj.is<picojson::null>()) {
+ return true;
+ } else if (!service_data_obj.is<picojson::object>()) {
return false;
}
+ /*
+ * Currently, only advertising of a single service data object is supported
+ * by the Web API. In the future, support for advertising arrays of those
+ * may be added.
+ *
+ * TODO: if supported, parse here the whole array of service data instances.
+ */
+ auto service_data = BluetoothLEServiceData::Construct(service_data_obj);
+ if (!service_data) {
+ return false;
+ }
+
+ out->service_data_.emplace_back(std::move(*service_data));
return true;
}
}
bool include_name_;
- std::vector<std::string> service_uuids_;
- std::vector<std::string> solicitation_uuids_;
+ std::vector<UUID> service_uuids_;
+ std::vector<UUID> solicitation_uuids_;
int appearance_;
bool include_tx_power_level_;
- BluetoothLEServiceData service_data_;
+ std::vector<BluetoothLEServiceData> service_data_;
BluetoothLEManufacturerData manufacturer_data_;
};
}
// constants
-
const std::string kAction = "action";
-const std::string kData = "data";
// scan-related
const std::string kOnScanSuccess = "onsuccess";
const std::string kOnScanError = "onerror";
}
}
+void BluetoothLEAdapter::IsScanning(picojson::object& out) {
+ ScopeLogger();
+
+ bool is_scanning;
+ int ret = bt_adapter_le_is_discovering(&is_scanning);
+
+ if (BT_ERROR_NONE != ret) {
+ LogAndReportError(
+ PlatformResult(ErrorCode::UNKNOWN_ERR, "Failed to check for scanning in progress"), &out,
+ ("Failed to check for scanning in progress: %d (%s)", ret, get_error_message(ret)));
+ } else {
+ scanning_ = is_scanning;
+ ReportSuccess(picojson::value(is_scanning), out);
+ }
+}
+
void BluetoothLEAdapter::StartAdvertise(const picojson::value& data, picojson::object& out) {
ScopeLogger();
CHECK_BACKWARD_COMPABILITY_PRIVILEGE_ACCESS(Privilege::kBluetooth, Privilege::kBluetoothAdmin,
}
}
- for (const auto& i : advertise_data.service_uuids()) {
- ret = bt_adapter_le_add_advertising_service_uuid(advertiser, packet_type, i.c_str());
+ for (const auto& uuid : advertise_data.service_uuids()) {
+ /*
+ * This native function accepts 16 or 128 bit UUIDs.
+ * To handle also 32 bit UUIDs, we pass all UUIDs in their canonical form.
+ *
+ * Note:
+ * The documentation of Native Bluetooth API says, this function advertises 128-bit UUIDs,
+ * that have 32-bit equivalents in the full 128-bit form.
+ * However, as of the day of writing this comment, packets advertised
+ * by the device contain these UUIDs in their 32-bit formats,
+ * i.e. AD fields with 0x04 («Incomplete List of 32-bit Service Class UUIDs») type.
+ * For example, "12345678-0000-1000-8000-00805F9B34FB" is advertised as "12345678".
+ */
+ ret = bt_adapter_le_add_advertising_service_uuid(advertiser, packet_type,
+ uuid.uuid_128_bit.c_str());
if (BT_ERROR_NONE != ret) {
LogAndReportError(util::GetBluetoothError(ret, "Failed to create advertiser"), &out,
("bt_adapter_le_add_advertising_service_uuid() failed with: %d (%s)", ret,
}
}
- for (const auto& i : advertise_data.solicitation_uuids()) {
- ret =
- bt_adapter_le_add_advertising_service_solicitation_uuid(advertiser, packet_type, i.c_str());
+ for (const auto& uuid : advertise_data.solicitation_uuids()) {
+ /*
+ * This native function accepts 16 or 128 bit UUIDs.
+ * To handle also 32 bit UUIDs, we pass all UUIDs in their canonical form.
+ *
+ * Note:
+ * The documentation of Native Bluetooth API says, it advertises 128-bit UUIDs,
+ * that have 32-bit equivalents in the full 128-bit form.
+ * However, as of the day of writing this comment, packets advertised
+ * by the device contain these UUIDs in their 32-bit formats,
+ * i.e. AD fields with 0x1F («List of 32-bit Service Solicitation UUIDs») type.
+ * For example, "12345678-0000-1000-8000-00805F9B34FB" is advertised as "12345678".
+ */
+ ret = bt_adapter_le_add_advertising_service_solicitation_uuid(advertiser, packet_type,
+ uuid.uuid_128_bit.c_str());
if (BT_ERROR_NONE != ret) {
LogAndReportError(
util::GetBluetoothError(ret, "Failed to create advertiser"), &out,
}
}
- const auto& service_data = advertise_data.service_data();
- if (service_data.uuid().empty() && nullptr == service_data.data().pointer()) {
+ if (advertise_data.service_data().empty()) {
LoggerD("service data is empty");
} else {
- ret = bt_adapter_le_add_advertising_service_data(
- advertiser, packet_type, service_data.uuid().c_str(), service_data.data().pointer(),
- service_data.data().length());
- if (BT_ERROR_NONE != ret) {
- LogAndReportError(util::GetBluetoothError(ret, "Failed to create advertiser"), &out,
- ("bt_adapter_le_add_advertising_service_data() failed with: %d (%s)", ret,
- get_error_message(ret)));
- return;
+ for (const auto& service_data_obj : advertise_data.service_data()) {
+ ret = bt_adapter_le_add_advertising_service_data(
+ advertiser, packet_type, service_data_obj.uuid().ShortestPossibleFormat().c_str(),
+ service_data_obj.data().pointer(), service_data_obj.data().length());
+ if (BT_ERROR_NONE != ret) {
+ std::string error_message = "Failed to create advertiser";
+ if (BT_ERROR_QUOTA_EXCEEDED == ret && !service_data_obj.uuid().To16Bit()) {
+ error_message =
+ "Failed to start advertising: only 16 bit values of BluetoothLEServiceData::uuid are "
+ "supported";
+ }
+
+ LogAndReportError(util::GetBluetoothError(ret, error_message), &out,
+ ("bt_adapter_le_add_advertising_service_data() failed with: %d (%s)", ret,
+ get_error_message(ret)));
+ return;
+ }
}
}
// device found
LoggerD("Device found");
picojson::value data{picojson::object{}};
- const auto& r = BluetoothLEDevice::ToJson(info, &data.get<picojson::object>());
- if (r) {
+ const auto& ret = BluetoothLEDevice::ToJson(info, &data.get<picojson::object>());
+ if (ret) {
data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanSuccess)));
data_obj->insert(std::make_pair(kData, data));
} else {
- LogAndReportError(r, data_obj, ("Failed to parse Bluetooth LE device"));
+ LogAndReportError(ret, data_obj, ("Failed to parse Bluetooth LE device"));
data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanError)));
}
}