Commit 0d1db684 authored by ortuno's avatar ortuno Committed by Commit bot

bluetooth: Require frame to be connected for characteristic.readValue to succeed

Rejects characteristic.readValue if the frame is not connected or if
the frame disconnects during the operation.

The new adapter has a Health Thermometer service with a measurement interval
characteristic. We use this characteristic because it the only standard
characteristic that supports Read, Write and Notify.

Reconnection tests in follow up patch: http://crrev.com/2438963002

BUG=608538

Review-Url: https://codereview.chromium.org/2443473003
Cr-Commit-Position: refs/heads/master@{#427007}
parent 41961c05
...@@ -67,6 +67,7 @@ const char kBlacklistTestServiceUUID[] = "611c954a-263b-4f4a-aab6-01ddb953f985"; ...@@ -67,6 +67,7 @@ const char kBlacklistTestServiceUUID[] = "611c954a-263b-4f4a-aab6-01ddb953f985";
const char kDeviceInformationServiceUUID[] = "180a"; const char kDeviceInformationServiceUUID[] = "180a";
const char kGenericAccessServiceUUID[] = "1800"; const char kGenericAccessServiceUUID[] = "1800";
const char kGlucoseServiceUUID[] = "1808"; const char kGlucoseServiceUUID[] = "1808";
const char kHealthThermometerUUID[] = "1809";
const char kHeartRateServiceUUID[] = "180d"; const char kHeartRateServiceUUID[] = "180d";
const char kHumanInterfaceDeviceServiceUUID[] = "1812"; const char kHumanInterfaceDeviceServiceUUID[] = "1812";
const char kRequestDisconnectionServiceUUID[] = const char kRequestDisconnectionServiceUUID[] =
...@@ -79,6 +80,7 @@ const char kRequestDisconnectionCharacteristicUUID[] = ...@@ -79,6 +80,7 @@ const char kRequestDisconnectionCharacteristicUUID[] =
"01d7d88a-7451-419f-aeb8-d65e7b9277af"; "01d7d88a-7451-419f-aeb8-d65e7b9277af";
const char kBodySensorLocation[] = "2a38"; const char kBodySensorLocation[] = "2a38";
const char kDeviceNameUUID[] = "2a00"; const char kDeviceNameUUID[] = "2a00";
const char kMeasurementIntervalUUID[] = "2a21";
const char kHeartRateMeasurementUUID[] = "2a37"; const char kHeartRateMeasurementUUID[] = "2a37";
const char kSerialNumberStringUUID[] = "2a25"; const char kSerialNumberStringUUID[] = "2a25";
const char kPeripheralPrivacyFlagUUID[] = "2a02"; const char kPeripheralPrivacyFlagUUID[] = "2a02";
...@@ -183,6 +185,8 @@ LayoutTestBluetoothAdapterProvider::GetBluetoothAdapter( ...@@ -183,6 +185,8 @@ LayoutTestBluetoothAdapterProvider::GetBluetoothAdapter(
return GetTwoHeartRateServicesAdapter(); return GetTwoHeartRateServicesAdapter();
if (fake_adapter_name == "DisconnectingHeartRateAdapter") if (fake_adapter_name == "DisconnectingHeartRateAdapter")
return GetDisconnectingHeartRateAdapter(); return GetDisconnectingHeartRateAdapter();
if (fake_adapter_name == "DisconnectingHealthThermometerAdapter")
return GetDisconnectingHealthThermometer();
if (fake_adapter_name == "DisconnectingDuringServiceRetrievalAdapter") if (fake_adapter_name == "DisconnectingDuringServiceRetrievalAdapter")
return GetServicesDiscoveredAfterReconnectionAdapter(true /* disconnect */); return GetServicesDiscoveredAfterReconnectionAdapter(true /* disconnect */);
if (fake_adapter_name == "ServicesDiscoveredAfterReconnectionAdapter") if (fake_adapter_name == "ServicesDiscoveredAfterReconnectionAdapter")
...@@ -616,6 +620,53 @@ LayoutTestBluetoothAdapterProvider::GetHeartRateAdapter() { ...@@ -616,6 +620,53 @@ LayoutTestBluetoothAdapterProvider::GetHeartRateAdapter() {
return adapter; return adapter;
} }
// static
scoped_refptr<NiceMockBluetoothAdapter>
LayoutTestBluetoothAdapterProvider::GetDisconnectingHealthThermometer() {
scoped_refptr<NiceMockBluetoothAdapter> adapter(GetEmptyAdapter());
NiceMockBluetoothAdapter* adapter_ptr = adapter.get();
std::unique_ptr<NiceMockBluetoothDevice> device(GetConnectableDevice(
adapter_ptr, "Disconnecting Health Thermometer",
std::vector<BluetoothUUID>({BluetoothUUID(kGenericAccessServiceUUID),
BluetoothUUID(kHealthThermometerUUID)})));
device->AddMockService(GetGenericAccessService(device.get()));
device->AddMockService(GetDisconnectingService(adapter.get(), device.get()));
std::unique_ptr<NiceMockBluetoothGattService> health_thermometer(
GetBaseGATTService("Health Thermometer", device.get(),
kHealthThermometerUUID));
// Measurement Interval
std::unique_ptr<NiceMockBluetoothGattCharacteristic> measurement_interval(
GetBaseGATTCharacteristic(
"Measurement Interval", health_thermometer.get(),
kMeasurementIntervalUUID,
BluetoothRemoteGattCharacteristic::PROPERTY_READ));
NiceMockBluetoothGattCharacteristic* measurement_ptr =
measurement_interval.get();
ON_CALL(*measurement_interval, ReadRemoteCharacteristic(_, _))
.WillByDefault(RunCallbackWithResult<0 /* success_callback */>(
[adapter_ptr, measurement_ptr]() {
std::vector<uint8_t> interval({1});
for (auto& observer : adapter_ptr->GetObservers()) {
observer.GattCharacteristicValueChanged(
adapter_ptr, measurement_ptr, interval);
}
return interval;
}));
// TODO(crbug.com/608538): Mock Write and StartNotifySession.
health_thermometer->AddMockCharacteristic(std::move(measurement_interval));
device->AddMockService(std::move(health_thermometer));
adapter->AddMockDevice(std::move(device));
return adapter;
}
// static // static
scoped_refptr<NiceMockBluetoothAdapter> scoped_refptr<NiceMockBluetoothAdapter>
LayoutTestBluetoothAdapterProvider::GetEmptyNameHeartRateAdapter() { LayoutTestBluetoothAdapterProvider::GetEmptyNameHeartRateAdapter() {
...@@ -710,40 +761,16 @@ LayoutTestBluetoothAdapterProvider::GetTwoHeartRateServicesAdapter() { ...@@ -710,40 +761,16 @@ LayoutTestBluetoothAdapterProvider::GetTwoHeartRateServicesAdapter() {
scoped_refptr<NiceMockBluetoothAdapter> scoped_refptr<NiceMockBluetoothAdapter>
LayoutTestBluetoothAdapterProvider::GetDisconnectingHeartRateAdapter() { LayoutTestBluetoothAdapterProvider::GetDisconnectingHeartRateAdapter() {
scoped_refptr<NiceMockBluetoothAdapter> adapter(GetEmptyAdapter()); scoped_refptr<NiceMockBluetoothAdapter> adapter(GetEmptyAdapter());
NiceMockBluetoothAdapter* adapter_ptr = adapter.get();
std::unique_ptr<NiceMockBluetoothDevice> device( std::unique_ptr<NiceMockBluetoothDevice> device(
GetHeartRateDevice(adapter.get())); GetHeartRateDevice(adapter.get()));
NiceMockBluetoothDevice* device_ptr = device.get();
// TODO(ortuno): Implement the rest of the service's characteristics // TODO(ortuno): Implement the rest of the service's characteristics
// See: http://crbug.com/529975 // See: http://crbug.com/529975
device->AddMockService(GetGenericAccessService(device.get())); device->AddMockService(GetGenericAccessService(device.get()));
device->AddMockService(GetHeartRateService(adapter.get(), device.get())); device->AddMockService(GetHeartRateService(adapter.get(), device.get()));
device->AddMockService(GetDisconnectingService(adapter.get(), device.get()));
// Set up a service and a characteristic to disconnect the device when it's
// written to.
std::unique_ptr<NiceMockBluetoothGattService> disconnection_service =
GetBaseGATTService("Disconnection", device_ptr,
kRequestDisconnectionServiceUUID);
std::unique_ptr<NiceMockBluetoothGattCharacteristic>
disconnection_characteristic(GetBaseGATTCharacteristic(
"Disconnection Characteristic", disconnection_service.get(),
kRequestDisconnectionCharacteristicUUID,
BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE));
ON_CALL(*disconnection_characteristic, WriteRemoteCharacteristic(_, _, _))
.WillByDefault(Invoke([adapter_ptr, device_ptr](
const std::vector<uint8_t>& value, const base::Closure& success,
const BluetoothRemoteGattCharacteristic::ErrorCallback& error) {
device_ptr->SetConnected(false);
for (auto& observer : adapter_ptr->GetObservers())
observer.DeviceChanged(adapter_ptr, device_ptr);
success.Run();
}));
disconnection_service->AddMockCharacteristic(
std::move(disconnection_characteristic));
device->AddMockService(std::move(disconnection_service));
adapter->AddMockDevice(std::move(device)); adapter->AddMockDevice(std::move(device));
return adapter; return adapter;
...@@ -869,6 +896,8 @@ LayoutTestBluetoothAdapterProvider::GetFailingGATTOperationsAdapter() { ...@@ -869,6 +896,8 @@ LayoutTestBluetoothAdapterProvider::GetFailingGATTOperationsAdapter() {
std::unique_ptr<NiceMockBluetoothDevice> device( std::unique_ptr<NiceMockBluetoothDevice> device(
GetConnectableDevice(adapter.get(), "Errors Device", uuids)); GetConnectableDevice(adapter.get(), "Errors Device", uuids));
device->AddMockService(GetDisconnectingService(adapter.get(), device.get()));
std::unique_ptr<NiceMockBluetoothGattService> service( std::unique_ptr<NiceMockBluetoothGattService> service(
GetBaseGATTService("Errors Service", device.get(), errorsServiceUUID)); GetBaseGATTService("Errors Service", device.get(), errorsServiceUUID));
...@@ -1244,6 +1273,36 @@ LayoutTestBluetoothAdapterProvider::GetHeartRateService( ...@@ -1244,6 +1273,36 @@ LayoutTestBluetoothAdapterProvider::GetHeartRateService(
return heart_rate; return heart_rate;
} }
// static
std::unique_ptr<NiceMockBluetoothGattService>
LayoutTestBluetoothAdapterProvider::GetDisconnectingService(
MockBluetoothAdapter* adapter,
MockBluetoothDevice* device) {
// Set up a service and a characteristic to disconnect the device when it's
// written to.
std::unique_ptr<NiceMockBluetoothGattService> disconnection_service =
GetBaseGATTService("Disconnection", device,
kRequestDisconnectionServiceUUID);
std::unique_ptr<NiceMockBluetoothGattCharacteristic>
disconnection_characteristic(GetBaseGATTCharacteristic(
"Disconnection Characteristic", disconnection_service.get(),
kRequestDisconnectionCharacteristicUUID,
BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE));
ON_CALL(*disconnection_characteristic, WriteRemoteCharacteristic(_, _, _))
.WillByDefault(Invoke([adapter, device](
const std::vector<uint8_t>& value, const base::Closure& success,
const BluetoothRemoteGattCharacteristic::ErrorCallback& error) {
device->SetConnected(false);
for (auto& observer : adapter->GetObservers())
observer.DeviceChanged(adapter, device);
success.Run();
}));
disconnection_service->AddMockCharacteristic(
std::move(disconnection_characteristic));
return disconnection_service;
}
// Characteristics // Characteristics
// static // static
......
...@@ -295,12 +295,33 @@ class LayoutTestBluetoothAdapterProvider { ...@@ -295,12 +295,33 @@ class LayoutTestBluetoothAdapterProvider {
// GetGenericAccessService. // GetGenericAccessService.
// - Heart Rate Service - Characteristics as described in // - Heart Rate Service - Characteristics as described in
// GetHeartRateService. // GetHeartRateService.
// - Request Disconnection Service: // - Request Disconnection Service: - Characteristics as described in
// - Request Disconnection Characteristic - A write will cause the // GetDisconnectingService
// device to disconnect.
static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
GetDisconnectingHeartRateAdapter(); GetDisconnectingHeartRateAdapter();
// |DisconnectingHealthThermometerAdapter|
// Inherits from |EmptyAdapter|
// Internal Structure:
// - Disconnecting Health Thermometer Device
// - UUIDs:
// - Generic Access UUID (0x1800)
// - Health Thermometer UUID (0x1809)
// - Services:
// - Generic Access Service - Characteristics as described in
// GetGenericAccessService.
// - Request Disconnection Service: - Characteristics as described in
// GetDisconnectingService
// - Health Thermometer:
// - Measurement Interval (0x2a21):
// - Read: Calls GattCharacteristicValueChanged and success
// callback with [1].
// - GetProperties: Returns
// BluetoothRemoteGattCharacteristic::PROPERTY_READ
// TODO(crbug.com/608538): Mock Write and StartNotifySession.
static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
GetDisconnectingHealthThermometer();
// |ServicesDiscoveredAfterReconnectionAdapter|(disconnect) // |ServicesDiscoveredAfterReconnectionAdapter|(disconnect)
// Inherits from |HeartRateAdapter| // Inherits from |HeartRateAdapter|
// Internal Structure: // Internal Structure:
...@@ -439,6 +460,8 @@ class LayoutTestBluetoothAdapterProvider { ...@@ -439,6 +460,8 @@ class LayoutTestBluetoothAdapterProvider {
// - ErrorCharacteristic( // - ErrorCharacteristic(
// BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED) // BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED)
// errorUUID(0xA8) // errorUUID(0xA8)
// - Request Disconnection Service: - Characteristics as described in
// GetDisconnectingService
static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
GetFailingGATTOperationsAdapter(); GetFailingGATTOperationsAdapter();
...@@ -652,6 +675,16 @@ class LayoutTestBluetoothAdapterProvider { ...@@ -652,6 +675,16 @@ class LayoutTestBluetoothAdapterProvider {
GetHeartRateService(device::MockBluetoothAdapter* adapter, GetHeartRateService(device::MockBluetoothAdapter* adapter,
device::MockBluetoothDevice* device); device::MockBluetoothDevice* device);
// |DisconnectingService|
// Internal Structure:
// - Characteristics:
// - Request Disconnection Characteristic (
// 01d7d889-7451-419f-aeb8-d65e7b9277af)
// - Write: Sets the device to disconnected and calls DeviceChanged.
static std::unique_ptr<testing::NiceMock<device::MockBluetoothGattService>>
GetDisconnectingService(device::MockBluetoothAdapter* adapter,
device::MockBluetoothDevice* device);
// Characteristics // Characteristics
// |BaseCharacteristic|(identifier, service, uuid) // |BaseCharacteristic|(identifier, service, uuid)
......
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let measurement_interval;
return gattServer.getPrimaryService('health_thermometer')
.then(ht=> ht.getCharacteristic('measurement_interval'))
.then(mi => measurement_interval = mi)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => requestDisconnection.writeValue(
new Uint8Array([0])))
.then(() => assert_promise_rejects_with_message(
measurement_interval.readValue(),
new DOMException(
'GATT Server is disconnected. Cannot perform GATT operations.',
'NetworkError')));
});
}, 'Device disconnects before readValue. Reject with NetworkError.');
</script>
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: [errorUUID(0xA0)]}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let error_characteristic;
return gattServer
.getPrimaryService(errorUUID(0xA0))
.then(es => es.getCharacteristic(errorUUID(0xA1)))
.then(ec => error_characteristic = ec)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
error_characteristic.readValue(),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
});
});
}, 'Device disconnects during a readValue call that fails. ' +
'Reject with NetworkError.');
</script>
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let measurement_interval;
return gattServer
.getPrimaryService('health_thermometer')
.then(ht=> ht.getCharacteristic('measurement_interval'))
.then(mi => measurement_interval = mi)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
measurement_interval.readValue(),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
});
});
}, 'Device disconnects during a readValue call that succeeds. ' +
'Reject with NetworkError.');
</script>
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer.getPrimaryService('health_thermometer')
.then(service => service.getCharacteristic('measurement_interval'))
.then(measurement_interval => {
gattServer.disconnect();
return assert_promise_rejects_with_message(
measurement_interval.readValue(),
new DOMException(
'GATT Server is disconnected. Cannot perform GATT operations.',
'NetworkError'));
});
});
}, 'disconnect() called before readValue. Reject with NetworkError.');
</script>
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: [errorUUID(0xA0)]}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer.getPrimaryService(errorUUID(0xA0))
.then(service => service.getCharacteristic(errorUUID(0xA1)))
.then(error_characteristic => {
let promise = assert_promise_rejects_with_message(
error_characteristic.readValue(),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
});
}, 'disconnect() called during a readValue call that fails. ' +
'Reject with NetworkError.');
</script>
<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer
.getPrimaryService('health_thermometer')
.then(service => service.getCharacteristic('measurement_interval'))
.then(measurement_interval => {
let promise = assert_promise_rejects_with_message(
measurement_interval.readValue(),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
});
}, 'disconnect() called during a readValue call that succeeds. ' +
'Reject with NetworkError.');
</script>
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let measurement_interval;
return gattServer.getPrimaryService('health_thermometer')
.then(ht=> ht.getCharacteristic('measurement_interval'))
.then(mi => measurement_interval = mi)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => requestDisconnection.writeValue(
new Uint8Array([0])))
.then(() => assert_promise_rejects_with_message(
measurement_interval.CALLS([readValue()]),
new DOMException(
'GATT Server is disconnected. Cannot perform GATT operations.',
'NetworkError')));
});
}, 'Device disconnects before FUNCTION_NAME. Reject with NetworkError.');
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: [errorUUID(0xA0)]}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let error_characteristic;
return gattServer
.getPrimaryService(errorUUID(0xA0))
.then(es => es.getCharacteristic(errorUUID(0xA1)))
.then(ec => error_characteristic = ec)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
error_characteristic.CALLS([readValue()]),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
});
});
}, 'Device disconnects during a FUNCTION_NAME call that fails. ' +
'Reject with NetworkError.');
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => device.gatt.connect())
.then(gattServer => {
let measurement_interval;
return gattServer
.getPrimaryService('health_thermometer')
.then(ht=> ht.getCharacteristic('measurement_interval'))
.then(mi => measurement_interval = mi)
.then(() => gattServer.getPrimaryService(
request_disconnection_service_uuid))
.then(service => service.getCharacteristic(
request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
measurement_interval.CALLS([readValue()]),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
});
});
}, 'Device disconnects during a FUNCTION_NAME call that succeeds. ' +
'Reject with NetworkError.');
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer.getPrimaryService('health_thermometer')
.then(service => service.getCharacteristic('measurement_interval'))
.then(measurement_interval => {
gattServer.disconnect();
return assert_promise_rejects_with_message(
measurement_interval.CALLS([readValue()]),
new DOMException(
'GATT Server is disconnected. Cannot perform GATT operations.',
'NetworkError'));
});
});
}, 'disconnect() called before FUNCTION_NAME. Reject with NetworkError.');
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: [errorUUID(0xA0)]}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer.getPrimaryService(errorUUID(0xA0))
.then(service => service.getCharacteristic(errorUUID(0xA1)))
.then(error_characteristic => {
let promise = assert_promise_rejects_with_message(
error_characteristic.CALLS([readValue()]),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
});
}, 'disconnect() called during a FUNCTION_NAME call that fails. ' +
'Reject with NetworkError.');
'use strict';
promise_test(() => {
let val = new Uint8Array([1]);
return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['health_thermometer']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
return gattServer
.getPrimaryService('health_thermometer')
.then(service => service.getCharacteristic('measurement_interval'))
.then(measurement_interval => {
let promise = assert_promise_rejects_with_message(
measurement_interval.CALLS([readValue()]),
new DOMException(
'GATT Server disconnected while performing a GATT operation.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
});
}, 'disconnect() called during a FUNCTION_NAME call that succeeds. ' +
'Reject with NetworkError.');
...@@ -23,6 +23,11 @@ namespace blink { ...@@ -23,6 +23,11 @@ namespace blink {
namespace { namespace {
const char kGATTServerDisconnected[] =
"GATT Server disconnected while performing a GATT operation.";
const char kGATTServerNotConnected[] =
"GATT Server is disconnected. Cannot perform GATT operations.";
DOMDataView* ConvertWebVectorToDataView(const WebVector<uint8_t>& webVector) { DOMDataView* ConvertWebVectorToDataView(const WebVector<uint8_t>& webVector) {
static_assert(sizeof(*webVector.data()) == 1, static_assert(sizeof(*webVector.data()) == 1,
"uint8_t should be a single byte"); "uint8_t should be a single byte");
...@@ -119,16 +124,28 @@ class ReadValueCallback : public WebBluetoothReadValueCallbacks { ...@@ -119,16 +124,28 @@ class ReadValueCallback : public WebBluetoothReadValueCallbacks {
public: public:
ReadValueCallback(BluetoothRemoteGATTCharacteristic* characteristic, ReadValueCallback(BluetoothRemoteGATTCharacteristic* characteristic,
ScriptPromiseResolver* resolver) ScriptPromiseResolver* resolver)
: m_webCharacteristic(characteristic), m_resolver(resolver) {} : m_characteristic(characteristic), m_resolver(resolver) {
// We always check that the device is connected before constructing this
// object.
CHECK(m_characteristic->gatt()->connected());
m_characteristic->gatt()->AddToActiveAlgorithms(m_resolver.get());
}
void onSuccess(const WebVector<uint8_t>& value) override { void onSuccess(const WebVector<uint8_t>& value) override {
if (!m_resolver->getExecutionContext() || if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->activeDOMObjectsAreStopped()) m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return; return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
DOMDataView* domDataView = ConvertWebVectorToDataView(value); DOMDataView* domDataView = ConvertWebVectorToDataView(value);
if (m_webCharacteristic) if (m_characteristic)
m_webCharacteristic->setValue(domDataView); m_characteristic->setValue(domDataView);
m_resolver->resolve(domDataView); m_resolver->resolve(domDataView);
} }
...@@ -140,16 +157,30 @@ class ReadValueCallback : public WebBluetoothReadValueCallbacks { ...@@ -140,16 +157,30 @@ class ReadValueCallback : public WebBluetoothReadValueCallbacks {
if (!m_resolver->getExecutionContext() || if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->activeDOMObjectsAreStopped()) m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return; return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
m_resolver->reject(BluetoothError::take(m_resolver, error)); m_resolver->reject(BluetoothError::take(m_resolver, error));
} }
private: private:
WeakPersistent<BluetoothRemoteGATTCharacteristic> m_webCharacteristic; WeakPersistent<BluetoothRemoteGATTCharacteristic> m_characteristic;
Persistent<ScriptPromiseResolver> m_resolver; Persistent<ScriptPromiseResolver> m_resolver;
}; };
ScriptPromise BluetoothRemoteGATTCharacteristic::readValue( ScriptPromise BluetoothRemoteGATTCharacteristic::readValue(
ScriptState* scriptState) { ScriptState* scriptState) {
if (!gatt()->connected()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(NetworkError, kGATTServerNotConnected));
}
WebBluetooth* webbluetooth = WebBluetooth* webbluetooth =
BluetoothSupplement::fromScriptState(scriptState); BluetoothSupplement::fromScriptState(scriptState);
......
...@@ -96,6 +96,10 @@ class BluetoothRemoteGATTCharacteristic final ...@@ -96,6 +96,10 @@ class BluetoothRemoteGATTCharacteristic final
RegisteredEventListener&) override; RegisteredEventListener&) override;
private: private:
friend class ReadValueCallback;
BluetoothRemoteGATTServer* gatt() { return m_service->device()->gatt(); }
std::unique_ptr<WebBluetoothRemoteGATTCharacteristicInit> m_webCharacteristic; std::unique_ptr<WebBluetoothRemoteGATTCharacteristicInit> m_webCharacteristic;
Member<BluetoothRemoteGATTService> m_service; Member<BluetoothRemoteGATTService> m_service;
bool m_stopped; bool m_stopped;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment