diff --git a/DEPS b/DEPS index 49187f5..373154e4c 100644 --- a/DEPS +++ b/DEPS
@@ -639,7 +639,7 @@ Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '05591bbeae6592fd924caec8e728a4ea86cbb8c9', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'a1d0f2731001d26f50a977fab3f3419125f5cc43', # commit position 20628 + Var('webrtc_git') + '/src.git' + '@' + '93e9134422757553944257f5c5e29848cb363676', # commit position 20628 'src/third_party/xdg-utils': { 'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/chrome/browser/sync/test/integration/single_client_user_events_sync_test.cc b/chrome/browser/sync/test/integration/single_client_user_events_sync_test.cc index c31485e..4b40dbd 100644 --- a/chrome/browser/sync/test/integration/single_client_user_events_sync_test.cc +++ b/chrome/browser/sync/test/integration/single_client_user_events_sync_test.cc
@@ -12,6 +12,7 @@ #include "chrome/browser/sync/test/integration/sync_integration_test_util.h" #include "chrome/browser/sync/test/integration/sync_test.h" #include "chrome/browser/sync/user_event_service_factory.h" +#include "components/sync/model/model_type_sync_bridge.h" #include "components/sync/protocol/user_event_specifics.pb.h" #include "components/sync/user_events/user_event_service.h" #include "components/variations/variations_associated_data.h" @@ -263,6 +264,25 @@ EXPECT_TRUE(ExpectUserEvents({testEvent1, consent1, consent2, testEvent3})); } +// Test that events that are logged before sync is enabled. +IN_PROC_BROWSER_TEST_F(SingleClientUserEventsSyncTest, LoggedBeforeSyncSetup) { + const UserEventSpecifics consent1 = CreateUserConsent(1); + const UserEventSpecifics consent2 = CreateUserConsent(2); + ASSERT_TRUE(SetupClients()); + syncer::UserEventService* event_service = + browser_sync::UserEventServiceFactory::GetForProfile(GetProfile(0)); + auto bridge = event_service->GetSyncBridge(); + // Wait for UserEventSyncBridge to be ready to receive events. + // TODO(crbug.com/761485): Remove when the store is initialized instantly. + ASSERT_TRUE(!bridge->change_processor()->IsTrackingMetadata()); + while (!bridge->change_processor()->IsTrackingMetadata()) + base::RunLoop().RunUntilIdle(); + event_service->RecordUserEvent(consent1); + ASSERT_TRUE(SetupSync()); + event_service->RecordUserEvent(consent2); + EXPECT_TRUE(ExpectUserEvents({consent1, consent2})); +} + IN_PROC_BROWSER_TEST_F(SingleClientUserEventsSyncTest, NoSessions) { const UserEventSpecifics specifics = CreateTestEvent(1); ASSERT_TRUE(SetupSync());
diff --git a/components/consent_auditor/consent_auditor.cc b/components/consent_auditor/consent_auditor.cc index d9da1af5..d2fba70 100644 --- a/components/consent_auditor/consent_auditor.cc +++ b/components/consent_auditor/consent_auditor.cc
@@ -13,6 +13,7 @@ #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/sync/driver/sync_driver_switches.h" +#include "components/sync/model/model_type_sync_bridge.h" #include "components/sync/user_events/user_event_service.h" using UserEventSpecifics = sync_pb::UserEventSpecifics; @@ -27,11 +28,11 @@ const char kLocalConsentLocaleKey[] = "locale"; UserEventSpecifics::UserConsent::ConsentStatus ToProtoEnum( - ConsentAuditor::ConsentStatus status) { + consent_auditor::ConsentStatus status) { switch (status) { - case ConsentAuditor::ConsentStatus::REVOKED: + case consent_auditor::ConsentStatus::REVOKED: return UserEventSpecifics::UserConsent::REVOKED; - case ConsentAuditor::ConsentStatus::GIVEN: + case consent_auditor::ConsentStatus::GIVEN: return UserEventSpecifics::UserConsent::GIVEN; } NOTREACHED(); @@ -72,6 +73,15 @@ return; std::unique_ptr<sync_pb::UserEventSpecifics> specifics = ConstructUserConsent( feature, consent_grd_ids, placeholder_replacements, status); + // For real usage, UserEventSyncBridge should always be ready to receive + // events when a consent gets recorded. + // FakeUserEventService doesn't have a sync bridge. + // TODO(crbug.com/709094, crbug.com/761485): Remove this check when the store + // initializes synchronously and is instantly ready to receive data. + DCHECK(!user_event_service_->GetSyncBridge() || + user_event_service_->GetSyncBridge() + ->change_processor() + ->IsTrackingMetadata()); user_event_service_->RecordUserEvent(std::move(specifics)); }
diff --git a/components/consent_auditor/consent_auditor.h b/components/consent_auditor/consent_auditor.h index 672a8a94..f8de2eaf 100644 --- a/components/consent_auditor/consent_auditor.h +++ b/components/consent_auditor/consent_auditor.h
@@ -25,9 +25,10 @@ namespace consent_auditor { +enum class ConsentStatus { REVOKED, GIVEN }; + class ConsentAuditor : public KeyedService { public: - enum class ConsentStatus { REVOKED, GIVEN }; ConsentAuditor(PrefService* pref_service, syncer::UserEventService* user_event_service, @@ -65,7 +66,7 @@ const std::string& feature, const std::vector<int>& consent_grd_ids, const std::vector<std::string>& placeholder_replacements, - ConsentAuditor::ConsentStatus status); + ConsentStatus status); PrefService* pref_service_; syncer::UserEventService* user_event_service_;
diff --git a/components/consent_auditor/consent_auditor_unittest.cc b/components/consent_auditor/consent_auditor_unittest.cc index a2a7e66e..c3cd309 100644 --- a/components/consent_auditor/consent_auditor_unittest.cc +++ b/components/consent_auditor/consent_auditor_unittest.cc
@@ -162,7 +162,7 @@ TEST_F(ConsentAuditorTest, RecordingEnabled) { consent_auditor()->RecordGaiaConsent("feature1", {}, {}, - ConsentAuditor::ConsentStatus::GIVEN); + ConsentStatus::GIVEN); auto& events = user_event_service()->GetRecordedUserEvents(); EXPECT_EQ(1U, events.size()); } @@ -171,7 +171,7 @@ base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndDisableFeature(switches::kSyncUserConsentEvents); consent_auditor()->RecordGaiaConsent("feature1", {}, {}, - ConsentAuditor::ConsentStatus::GIVEN); + ConsentStatus::GIVEN); auto& events = user_event_service()->GetRecordedUserEvents(); EXPECT_EQ(0U, events.size()); } @@ -181,7 +181,7 @@ std::vector<std::string> kPlaceholders = {"OK.", "user@example.com"}; base::Time t1 = base::Time::Now(); consent_auditor()->RecordGaiaConsent("feature1", kMessageIds, kPlaceholders, - ConsentAuditor::ConsentStatus::GIVEN); + ConsentStatus::GIVEN); base::Time t2 = base::Time::Now(); auto& events = user_event_service()->GetRecordedUserEvents(); EXPECT_EQ(1U, events.size());
diff --git a/device/BUILD.gn b/device/BUILD.gn index 03053e04..2d16a92 100644 --- a/device/BUILD.gn +++ b/device/BUILD.gn
@@ -73,6 +73,7 @@ "sensors/sensor_manager_chromeos_unittest.cc", "test/run_all_unittests.cc", "u2f/u2f_apdu_unittest.cc", + "u2f/u2f_ble_connection_unittest.cc", "u2f/u2f_ble_frames_unittest.cc", "u2f/u2f_discovery_unittest.cc", "u2f/u2f_message_unittest.cc",
diff --git a/device/u2f/BUILD.gn b/device/u2f/BUILD.gn index 3e0e89de..122fd73 100644 --- a/device/u2f/BUILD.gn +++ b/device/u2f/BUILD.gn
@@ -11,6 +11,8 @@ "u2f_apdu_command.h", "u2f_apdu_response.cc", "u2f_apdu_response.h", + "u2f_ble_connection.cc", + "u2f_ble_connection.h", "u2f_ble_discovery.cc", "u2f_ble_discovery.h", "u2f_ble_frames.cc",
diff --git a/device/u2f/u2f_ble_connection.cc b/device/u2f/u2f_ble_connection.cc new file mode 100644 index 0000000..d56506b --- /dev/null +++ b/device/u2f/u2f_ble_connection.cc
@@ -0,0 +1,573 @@ +// Copyright 2017 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 "device/u2f/u2f_ble_connection.h" + +#include <utility> + +#include "base/barrier_closure.h" +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_gatt_connection.h" +#include "device/bluetooth/bluetooth_gatt_notify_session.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_remote_gatt_service.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "device/u2f/u2f_ble_uuids.h" + +namespace device { + +namespace { + +constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) { + switch (error_code) { + case BluetoothDevice::ERROR_AUTH_CANCELED: + return "ERROR_AUTH_CANCELED"; + case BluetoothDevice::ERROR_AUTH_FAILED: + return "ERROR_AUTH_FAILED"; + case BluetoothDevice::ERROR_AUTH_REJECTED: + return "ERROR_AUTH_REJECTED"; + case BluetoothDevice::ERROR_AUTH_TIMEOUT: + return "ERROR_AUTH_TIMEOUT"; + case BluetoothDevice::ERROR_FAILED: + return "ERROR_FAILED"; + case BluetoothDevice::ERROR_INPROGRESS: + return "ERROR_INPROGRESS"; + case BluetoothDevice::ERROR_UNKNOWN: + return "ERROR_UNKNOWN"; + case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: + return "ERROR_UNSUPPORTED_DEVICE"; + default: + NOTREACHED(); + return ""; + } +} + +constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) { + switch (error_code) { + case BluetoothGattService::GATT_ERROR_UNKNOWN: + return "GATT_ERROR_UNKNOWN"; + case BluetoothGattService::GATT_ERROR_FAILED: + return "GATT_ERROR_FAILED"; + case BluetoothGattService::GATT_ERROR_IN_PROGRESS: + return "GATT_ERROR_IN_PROGRESS"; + case BluetoothGattService::GATT_ERROR_INVALID_LENGTH: + return "GATT_ERROR_INVALID_LENGTH"; + case BluetoothGattService::GATT_ERROR_NOT_PERMITTED: + return "GATT_ERROR_NOT_PERMITTED"; + case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED: + return "GATT_ERROR_NOT_AUTHORIZED"; + case BluetoothGattService::GATT_ERROR_NOT_PAIRED: + return "GATT_ERROR_NOT_PAIRED"; + case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED: + return "GATT_ERROR_NOT_SUPPORTED"; + default: + NOTREACHED(); + return ""; + } +} + +} // namespace + +U2fBleConnection::U2fBleConnection( + std::string device_address, + ConnectionStatusCallback connection_status_callback, + ReadCallback read_callback) + : address_(std::move(device_address)), + connection_status_callback_(std::move(connection_status_callback)), + read_callback_(std::move(read_callback)), + weak_factory_(this) { + BluetoothAdapterFactory::GetAdapter( + base::Bind(&U2fBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr())); + DCHECK(!address_.empty()); +} + +U2fBleConnection::~U2fBleConnection() { + if (adapter_) + adapter_->RemoveObserver(this); +} + +void U2fBleConnection::ReadControlPointLength( + ControlPointLengthCallback callback) { + const BluetoothRemoteGattService* u2f_service = GetU2fService(); + if (!u2f_service) { + std::move(callback).Run(base::nullopt); + return; + } + + BluetoothRemoteGattCharacteristic* control_point_length = + u2f_service->GetCharacteristic(*control_point_length_id_); + if (!control_point_length) { + DLOG(ERROR) << "No Control Point Length characteristic present."; + std::move(callback).Run(base::nullopt); + return; + } + + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + control_point_length->ReadRemoteCharacteristic( + base::Bind(OnReadControlPointLength, copyable_callback), + base::Bind(OnReadControlPointLengthError, copyable_callback)); +} + +void U2fBleConnection::ReadServiceRevisions(ServiceRevisionsCallback callback) { + const BluetoothRemoteGattService* u2f_service = GetU2fService(); + if (!u2f_service) { + std::move(callback).Run({}); + return; + } + + DCHECK(service_revision_id_ || service_revision_bitfield_id_); + BluetoothRemoteGattCharacteristic* service_revision = + service_revision_id_ + ? u2f_service->GetCharacteristic(*service_revision_id_) + : nullptr; + + BluetoothRemoteGattCharacteristic* service_revision_bitfield = + service_revision_bitfield_id_ + ? u2f_service->GetCharacteristic(*service_revision_bitfield_id_) + : nullptr; + + if (!service_revision && !service_revision_bitfield) { + DLOG(ERROR) << "Service Revision Characteristics do not exist."; + std::move(callback).Run({}); + return; + } + + // Start from a clean state. + service_revisions_.clear(); + + // In order to obtain the full set of supported service revisions it is + // possible that both the |service_revision_| and |service_revision_bitfield_| + // characteristics must be read. Potentially we need to take the union of + // the individually supported service revisions, hence the indirection to + // ReturnServiceRevisions() is introduced. + base::Closure copyable_callback = base::AdaptCallbackForRepeating( + base::BindOnce(&U2fBleConnection::ReturnServiceRevisions, + weak_factory_.GetWeakPtr(), std::move(callback))); + + // If the Service Revision Bitfield characteristic is not present, only + // attempt to read the Service Revision characteristic. + if (!service_revision_bitfield) { + service_revision->ReadRemoteCharacteristic( + base::Bind(&U2fBleConnection::OnReadServiceRevision, + weak_factory_.GetWeakPtr(), copyable_callback), + base::Bind(&U2fBleConnection::OnReadServiceRevisionError, + weak_factory_.GetWeakPtr(), copyable_callback)); + return; + } + + // If the Service Revision characteristic is not present, only + // attempt to read the Service Revision Bitfield characteristic. + if (!service_revision) { + service_revision_bitfield->ReadRemoteCharacteristic( + base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield, + weak_factory_.GetWeakPtr(), copyable_callback), + base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError, + weak_factory_.GetWeakPtr(), copyable_callback)); + return; + } + + // This is the case where both characteristics are present. These reads can + // happen in parallel, but both must finish before a result can be returned. + // Hence a BarrierClosure is introduced invoking ReturnServiceRevisions() once + // both characteristic reads are done. + base::RepeatingClosure barrier_closure = + base::BarrierClosure(2, copyable_callback); + + service_revision->ReadRemoteCharacteristic( + base::Bind(&U2fBleConnection::OnReadServiceRevision, + weak_factory_.GetWeakPtr(), barrier_closure), + base::Bind(&U2fBleConnection::OnReadServiceRevisionError, + weak_factory_.GetWeakPtr(), barrier_closure)); + + service_revision_bitfield->ReadRemoteCharacteristic( + base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield, + weak_factory_.GetWeakPtr(), barrier_closure), + base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError, + weak_factory_.GetWeakPtr(), barrier_closure)); +} + +void U2fBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, + WriteCallback callback) { + const BluetoothRemoteGattService* u2f_service = GetU2fService(); + if (!u2f_service) { + std::move(callback).Run(false); + return; + } + + BluetoothRemoteGattCharacteristic* control_point = + u2f_service->GetCharacteristic(*control_point_id_); + if (!control_point) { + DLOG(ERROR) << "Control Point characteristic not present."; + std::move(callback).Run(false); + return; + } + + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + control_point->WriteRemoteCharacteristic( + data, base::Bind(OnWrite, copyable_callback), + base::Bind(OnWriteError, copyable_callback)); +} + +void U2fBleConnection::WriteServiceRevision(ServiceRevision service_revision, + WriteCallback callback) { + const BluetoothRemoteGattService* u2f_service = GetU2fService(); + if (!u2f_service) { + std::move(callback).Run(false); + return; + } + + BluetoothRemoteGattCharacteristic* service_revision_bitfield = + u2f_service->GetCharacteristic(*service_revision_bitfield_id_); + if (!service_revision_bitfield) { + DLOG(ERROR) << "Service Revision Bitfield characteristic not present."; + std::move(callback).Run(false); + return; + } + + std::vector<uint8_t> payload; + switch (service_revision) { + case ServiceRevision::VERSION_1_1: + payload.push_back(0x80); + break; + case ServiceRevision::VERSION_1_2: + payload.push_back(0x40); + break; + default: + DLOG(ERROR) + << "Write Service Revision Failed: Unsupported Service Revision."; + std::move(callback).Run(false); + return; + } + + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + service_revision_bitfield->WriteRemoteCharacteristic( + payload, base::Bind(OnWrite, copyable_callback), + base::Bind(OnWriteError, copyable_callback)); +} + +void U2fBleConnection::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { + if (!adapter) { + DLOG(ERROR) << "Failed to get Adapter."; + OnConnectionError(); + return; + } + + DVLOG(2) << "Got Adapter: " << adapter->GetAddress(); + adapter_ = std::move(adapter); + adapter_->AddObserver(this); + CreateGattConnection(); +} + +void U2fBleConnection::CreateGattConnection() { + BluetoothDevice* device = adapter_->GetDevice(address_); + if (!device) { + DLOG(ERROR) << "Failed to get Device."; + OnConnectionError(); + return; + } + + device->CreateGattConnection( + base::Bind(&U2fBleConnection::OnCreateGattConnection, + weak_factory_.GetWeakPtr()), + base::Bind(&U2fBleConnection::OnCreateGattConnectionError, + weak_factory_.GetWeakPtr())); +} + +void U2fBleConnection::OnCreateGattConnection( + std::unique_ptr<BluetoothGattConnection> connection) { + connection_ = std::move(connection); + + BluetoothDevice* device = adapter_->GetDevice(address_); + if (!device) { + DLOG(ERROR) << "Failed to get Device."; + OnConnectionError(); + return; + } + + if (device->IsGattServicesDiscoveryComplete()) + ConnectToU2fService(); +} + +void U2fBleConnection::OnCreateGattConnectionError( + BluetoothDevice::ConnectErrorCode error_code) { + DLOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code); + OnConnectionError(); +} + +void U2fBleConnection::ConnectToU2fService() { + BluetoothDevice* device = adapter_->GetDevice(address_); + if (!device) { + DLOG(ERROR) << "Failed to get Device."; + OnConnectionError(); + return; + } + + DCHECK(device->IsGattServicesDiscoveryComplete()); + const std::vector<BluetoothRemoteGattService*> services = + device->GetGattServices(); + auto found = + std::find_if(services.begin(), services.end(), [](const auto* service) { + return service->GetUUID().canonical_value() == U2F_SERVICE_UUID; + }); + + if (found == services.end()) { + DLOG(ERROR) << "Failed to get U2F Service."; + OnConnectionError(); + return; + } + + const BluetoothRemoteGattService* u2f_service = *found; + u2f_service_id_ = u2f_service->GetIdentifier(); + DVLOG(2) << "Got U2F Service: " << *u2f_service_id_; + + for (const auto* characteristic : u2f_service->GetCharacteristics()) { + // NOTE: Since GetUUID() returns a temporary |uuid| can't be a reference, + // even though canonical_value() returns a const reference. + const std::string uuid = characteristic->GetUUID().canonical_value(); + if (uuid == U2F_CONTROL_POINT_LENGTH_UUID) { + control_point_length_id_ = characteristic->GetIdentifier(); + DVLOG(2) << "Got U2F Control Point Length: " << *control_point_length_id_; + } else if (uuid == U2F_CONTROL_POINT_UUID) { + control_point_id_ = characteristic->GetIdentifier(); + DVLOG(2) << "Got U2F Control Point: " << *control_point_id_; + } else if (uuid == U2F_STATUS_UUID) { + status_id_ = characteristic->GetIdentifier(); + DVLOG(2) << "Got U2F Status: " << *status_id_; + } else if (uuid == U2F_SERVICE_REVISION_UUID) { + service_revision_id_ = characteristic->GetIdentifier(); + DVLOG(2) << "Got U2F Service Revision: " << *service_revision_id_; + } else if (uuid == U2F_SERVICE_REVISION_BITFIELD_UUID) { + service_revision_bitfield_id_ = characteristic->GetIdentifier(); + DVLOG(2) << "Got U2F Service Revision Bitfield: " + << *service_revision_bitfield_id_; + } + } + + if (!control_point_length_id_ || !control_point_id_ || !status_id_ || + (!service_revision_id_ && !service_revision_bitfield_id_)) { + DLOG(ERROR) << "U2F characteristics missing."; + OnConnectionError(); + return; + } + + u2f_service->GetCharacteristic(*status_id_) + ->StartNotifySession( + base::Bind(&U2fBleConnection::OnStartNotifySession, + weak_factory_.GetWeakPtr()), + base::Bind(&U2fBleConnection::OnStartNotifySessionError, + weak_factory_.GetWeakPtr())); +} + +void U2fBleConnection::OnStartNotifySession( + std::unique_ptr<BluetoothGattNotifySession> notify_session) { + notify_session_ = std::move(notify_session); + DVLOG(2) << "Created notification session. Connection established."; + connection_status_callback_.Run(true); +} + +void U2fBleConnection::OnStartNotifySessionError( + BluetoothGattService::GattErrorCode error_code) { + DLOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code); + OnConnectionError(); +} + +void U2fBleConnection::OnConnectionError() { + connection_status_callback_.Run(false); + + connection_.reset(); + notify_session_.reset(); + + u2f_service_id_.reset(); + control_point_length_id_.reset(); + control_point_id_.reset(); + status_id_.reset(); + service_revision_id_.reset(); + service_revision_bitfield_id_.reset(); +} + +const BluetoothRemoteGattService* U2fBleConnection::GetU2fService() const { + if (!adapter_) { + DLOG(ERROR) << "No adapter present."; + return nullptr; + } + + const BluetoothDevice* device = adapter_->GetDevice(address_); + if (!device) { + DLOG(ERROR) << "No device present."; + return nullptr; + } + + if (!u2f_service_id_) { + DLOG(ERROR) << "Unknown U2F service id."; + return nullptr; + } + + const BluetoothRemoteGattService* u2f_service = + device->GetGattService(*u2f_service_id_); + if (!u2f_service) { + DLOG(ERROR) << "No U2F service present."; + return nullptr; + } + + return u2f_service; +} + +void U2fBleConnection::DeviceAdded(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (adapter != adapter_ || device->GetAddress() != address_) + return; + CreateGattConnection(); +} + +void U2fBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter, + BluetoothDevice* device, + const std::string& old_address) { + if (adapter != adapter_ || old_address != address_) + return; + address_ = device->GetAddress(); +} + +void U2fBleConnection::DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (adapter != adapter_ || device->GetAddress() != address_) + return; + if (!device->IsGattConnected()) { + DLOG(ERROR) << "GATT Disconnected: " << device->GetAddress(); + OnConnectionError(); + } +} + +void U2fBleConnection::GattCharacteristicValueChanged( + BluetoothAdapter* adapter, + BluetoothRemoteGattCharacteristic* characteristic, + const std::vector<uint8_t>& value) { + if (characteristic->GetIdentifier() != status_id_) + return; + DVLOG(2) << "Status characteristic value changed."; + read_callback_.Run(value); +} + +void U2fBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (adapter != adapter_ || device->GetAddress() != address_) + return; + ConnectToU2fService(); +} + +// static +void U2fBleConnection::OnReadControlPointLength( + ControlPointLengthCallback callback, + const std::vector<uint8_t>& value) { + if (value.size() != 2) { + DLOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes"; + std::move(callback).Run(base::nullopt); + return; + } + + uint16_t length = (value[0] << 8) | value[1]; + DVLOG(2) << "Control Point Length: " << length; + std::move(callback).Run(length); +} + +// static +void U2fBleConnection::OnReadControlPointLengthError( + ControlPointLengthCallback callback, + BluetoothGattService::GattErrorCode error_code) { + DLOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code); + std::move(callback).Run(base::nullopt); +} + +void U2fBleConnection::OnReadServiceRevision( + base::OnceClosure callback, + const std::vector<uint8_t>& value) { + std::string service_revision(value.begin(), value.end()); + DVLOG(2) << "Service Revision: " << service_revision; + + if (service_revision == "1.0") { + service_revisions_.insert(ServiceRevision::VERSION_1_0); + } else if (service_revision == "1.1") { + service_revisions_.insert(ServiceRevision::VERSION_1_1); + } else if (service_revision == "1.2") { + service_revisions_.insert(ServiceRevision::VERSION_1_2); + } else { + DLOG(ERROR) << "Unknown Service Revision: " << service_revision; + std::move(callback).Run(); + return; + } + + std::move(callback).Run(); +} + +void U2fBleConnection::OnReadServiceRevisionError( + base::OnceClosure callback, + BluetoothGattService::GattErrorCode error_code) { + DLOG(ERROR) << "Error reading Service Revision: " << ToString(error_code); + std::move(callback).Run(); +} + +void U2fBleConnection::OnReadServiceRevisionBitfield( + base::OnceClosure callback, + const std::vector<uint8_t>& value) { + if (value.empty()) { + DLOG(ERROR) << "Service Revision Bitfield is empty."; + std::move(callback).Run(); + return; + } + + if (value.size() != 1u) { + DLOG(ERROR) << "Service Revision Bitfield has unexpected size: " + << value.size() << ". Ignoring all but the first byte."; + } + + const uint8_t bitset = value[0]; + if (bitset & 0x3F) { + DLOG(ERROR) << "Service Revision Bitfield has unexpected bits set: 0x" + << std::hex << (bitset & 0x3F) + << ". Ignoring all but the first two bits."; + } + + if (bitset & 0x80) { + service_revisions_.insert(ServiceRevision::VERSION_1_1); + DVLOG(2) << "Detected Support for Service Revision 1.1"; + } + + if (bitset & 0x40) { + service_revisions_.insert(ServiceRevision::VERSION_1_2); + DVLOG(2) << "Detected Support for Service Revision 1.2"; + } + + std::move(callback).Run(); +} + +void U2fBleConnection::OnReadServiceRevisionBitfieldError( + base::OnceClosure callback, + BluetoothGattService::GattErrorCode error_code) { + DLOG(ERROR) << "Error reading Service Revision Bitfield: " + << ToString(error_code); + std::move(callback).Run(); +} + +void U2fBleConnection::ReturnServiceRevisions( + ServiceRevisionsCallback callback) { + std::move(callback).Run(std::move(service_revisions_)); +} + +// static +void U2fBleConnection::OnWrite(WriteCallback callback) { + DVLOG(2) << "Write succeeded."; + std::move(callback).Run(true); +} + +// static +void U2fBleConnection::OnWriteError( + WriteCallback callback, + BluetoothGattService::GattErrorCode error_code) { + DLOG(ERROR) << "Write Failed: " << ToString(error_code); + std::move(callback).Run(false); +} + +} // namespace device
diff --git a/device/u2f/u2f_ble_connection.h b/device/u2f/u2f_ble_connection.h new file mode 100644 index 0000000..a524ae7 --- /dev/null +++ b/device/u2f/u2f_ble_connection.h
@@ -0,0 +1,157 @@ +// Copyright 2017 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. + +#ifndef DEVICE_U2F_U2F_BLE_CONNECTION_H_ +#define DEVICE_U2F_U2F_BLE_CONNECTION_H_ + +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_service.h" + +namespace device { + +class BluetoothGattConnection; +class BluetoothGattNotifySession; +class BluetoothRemoteGattCharacteristic; +class BluetoothRemoteGattService; + +// A connection to the U2F service of an authenticator over BLE. Detailed +// specification of the BLE device can be found here: +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_gatt-service-description +// +// Currently this code does not handle devices that need pairing. This is fine +// for non-BlueZ platforms, as here accessing a protected characteristic will +// trigger an OS level dialog if pairing is required. However, for BlueZ +// platforms pairing must have been done externally, for example using the +// `bluetoothctl` command. +// +// TODO(crbug.com/763303): Add support for pairing from within this class and +// provide users with an option to manually specify a PIN code. +class U2fBleConnection : public BluetoothAdapter::Observer { + public: + enum class ServiceRevision { + VERSION_1_0, + VERSION_1_1, + VERSION_1_2, + }; + + // This callback informs clients repeatedly about changes in the device + // connection. This class makes an initial connection attempt on construction, + // which result in returned via this callback. Future invocations happen if + // devices connect or disconnect from the adapter. + using ConnectionStatusCallback = base::RepeatingCallback<void(bool)>; + using WriteCallback = base::OnceCallback<void(bool)>; + using ReadCallback = base::RepeatingCallback<void(std::vector<uint8_t>)>; + using ControlPointLengthCallback = + base::OnceCallback<void(base::Optional<uint16_t>)>; + using ServiceRevisionsCallback = + base::OnceCallback<void(std::set<ServiceRevision>)>; + + U2fBleConnection(std::string device_address, + ConnectionStatusCallback connection_status_callback, + ReadCallback read_callback); + ~U2fBleConnection() override; + + const std::string& address() const { return address_; } + + void ReadControlPointLength(ControlPointLengthCallback callback); + void ReadServiceRevisions(ServiceRevisionsCallback callback); + void WriteControlPoint(const std::vector<uint8_t>& data, WriteCallback); + void WriteServiceRevision(ServiceRevision service_revision, + WriteCallback callback); + + private: + // BluetoothAdapter::Observer: + void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; + void DeviceAddressChanged(BluetoothAdapter* adapter, + BluetoothDevice* device, + const std::string& old_address) override; + void DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) override; + void GattCharacteristicValueChanged( + BluetoothAdapter* adapter, + BluetoothRemoteGattCharacteristic* characteristic, + const std::vector<uint8_t>& value) override; + void GattServicesDiscovered(BluetoothAdapter* adapter, + BluetoothDevice* device) override; + + void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); + + void CreateGattConnection(); + void OnCreateGattConnection( + std::unique_ptr<BluetoothGattConnection> connection); + void OnCreateGattConnectionError( + BluetoothDevice::ConnectErrorCode error_code); + + void ConnectToU2fService(); + + void OnStartNotifySession( + std::unique_ptr<BluetoothGattNotifySession> notify_session); + void OnStartNotifySessionError( + BluetoothGattService::GattErrorCode error_code); + + void OnConnectionError(); + + const BluetoothRemoteGattService* GetU2fService() const; + + static void OnReadControlPointLength(ControlPointLengthCallback callback, + const std::vector<uint8_t>& value); + static void OnReadControlPointLengthError( + ControlPointLengthCallback callback, + BluetoothGattService::GattErrorCode error_code); + + void OnReadServiceRevision(base::OnceClosure callback, + const std::vector<uint8_t>& value); + void OnReadServiceRevisionError( + base::OnceClosure callback, + BluetoothGattService::GattErrorCode error_code); + + void OnReadServiceRevisionBitfield(base::OnceClosure callback, + const std::vector<uint8_t>& value); + void OnReadServiceRevisionBitfieldError( + base::OnceClosure callback, + BluetoothGattService::GattErrorCode error_code); + void ReturnServiceRevisions(ServiceRevisionsCallback callback); + + static void OnWrite(WriteCallback callback); + static void OnWriteError(WriteCallback callback, + BluetoothGattService::GattErrorCode error_code); + + std::string address_; + ConnectionStatusCallback connection_status_callback_; + ReadCallback read_callback_; + + scoped_refptr<BluetoothAdapter> adapter_; + std::unique_ptr<BluetoothGattConnection> connection_; + std::unique_ptr<BluetoothGattNotifySession> notify_session_; + + base::Optional<std::string> u2f_service_id_; + base::Optional<std::string> control_point_length_id_; + base::Optional<std::string> control_point_id_; + base::Optional<std::string> status_id_; + base::Optional<std::string> service_revision_id_; + base::Optional<std::string> service_revision_bitfield_id_; + + std::set<ServiceRevision> service_revisions_; + + base::WeakPtrFactory<U2fBleConnection> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(U2fBleConnection); +}; + +} // namespace device + +#endif // DEVICE_U2F_U2F_BLE_CONNECTION_H_
diff --git a/device/u2f/u2f_ble_connection_unittest.cc b/device/u2f/u2f_ble_connection_unittest.cc new file mode 100644 index 0000000..d8aee58 --- /dev/null +++ b/device/u2f/u2f_ble_connection_unittest.cc
@@ -0,0 +1,767 @@ +// Copyright 2017 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 "device/u2f/u2f_ble_connection.h" + +#include <utility> + +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/strings/string_piece.h" +#include "base/test/scoped_task_environment.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/bluetooth_test.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/bluetooth/test/mock_bluetooth_device.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_service.h" +#include "device/u2f/u2f_ble_uuids.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_ANDROID) +#include "device/bluetooth/test/bluetooth_test_android.h" +#elif defined(OS_MACOSX) +#include "device/bluetooth/test/bluetooth_test_mac.h" +#elif defined(OS_WIN) +#include "device/bluetooth/test/bluetooth_test_win.h" +#elif defined(OS_CHROMEOS) || defined(OS_LINUX) +#include "device/bluetooth/test/bluetooth_test_bluez.h" +#endif + +namespace device { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::IsEmpty; +using ::testing::Return; + +using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; +using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>; +using NiceMockBluetoothGattService = + ::testing::NiceMock<MockBluetoothGattService>; +using NiceMockBluetoothGattCharacteristic = + ::testing::NiceMock<MockBluetoothGattCharacteristic>; +using NiceMockBluetoothGattConnection = + ::testing::NiceMock<MockBluetoothGattConnection>; +using NiceMockBluetoothGattNotifySession = + ::testing::NiceMock<MockBluetoothGattNotifySession>; + +namespace { + +std::vector<uint8_t> ToByteVector(base::StringPiece str) { + return std::vector<uint8_t>(str.begin(), str.end()); +} + +BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter, + const std::string& address) { + const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices(); + auto found = std::find_if(devices.begin(), devices.end(), + [&address](const auto* device) { + return device->GetAddress() == address; + }); + return found != devices.end() ? *found : nullptr; +} + +class TestConnectionStatusCallback { + public: + void OnStatus(bool status) { + status_ = status; + run_loop_->Quit(); + } + + bool WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return status_; + } + + U2fBleConnection::ConnectionStatusCallback GetCallback() { + return base::BindRepeating(&TestConnectionStatusCallback::OnStatus, + base::Unretained(this)); + } + + private: + bool status_ = false; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +class TestReadCallback { + public: + void OnRead(std::vector<uint8_t> value) { + value_ = std::move(value); + run_loop_->Quit(); + } + + const std::vector<uint8_t> WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return value_; + } + + U2fBleConnection::ReadCallback GetCallback() { + return base::BindRepeating(&TestReadCallback::OnRead, + base::Unretained(this)); + } + + private: + std::vector<uint8_t> value_; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +class TestReadControlPointLengthCallback { + public: + void OnReadControlPointLength(base::Optional<uint16_t> value) { + value_ = std::move(value); + run_loop_->Quit(); + } + + const base::Optional<uint16_t>& WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return value_; + } + + U2fBleConnection::ControlPointLengthCallback GetCallback() { + return base::BindOnce( + &TestReadControlPointLengthCallback::OnReadControlPointLength, + base::Unretained(this)); + } + + private: + base::Optional<uint16_t> value_; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +class TestReadServiceRevisionsCallback { + public: + void OnReadServiceRevisions( + std::set<U2fBleConnection::ServiceRevision> revisions) { + revisions_ = std::move(revisions); + run_loop_->Quit(); + } + + const std::set<U2fBleConnection::ServiceRevision>& WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return revisions_; + } + + U2fBleConnection::ServiceRevisionsCallback GetCallback() { + return base::BindOnce( + &TestReadServiceRevisionsCallback::OnReadServiceRevisions, + base::Unretained(this)); + } + + private: + std::set<U2fBleConnection::ServiceRevision> revisions_; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +class TestWriteCallback { + public: + void OnWrite(bool success) { + success_ = success; + run_loop_->Quit(); + } + + bool WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return success_; + } + + U2fBleConnection::WriteCallback GetCallback() { + return base::BindOnce(&TestWriteCallback::OnWrite, base::Unretained(this)); + } + + private: + bool success_ = false; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +} // namespace + +class U2fBleConnectionTest : public ::testing::Test { + public: + U2fBleConnectionTest() { + ON_CALL(*adapter_, GetDevice(_)) + .WillByDefault(Invoke([this](const std::string& address) { + return GetMockDevice(adapter_.get(), address); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + } + + void AddU2Device(const std::string& device_address) { + auto u2f_device = std::make_unique<NiceMockBluetoothDevice>( + adapter_.get(), /* bluetooth_class */ 0u, + BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true, + /* connected */ false); + u2f_device_ = u2f_device.get(); + adapter_->AddMockDevice(std::move(u2f_device)); + + ON_CALL(*u2f_device_, GetGattServices()) + .WillByDefault( + Invoke(u2f_device_, &MockBluetoothDevice::GetMockServices)); + + ON_CALL(*u2f_device_, GetGattService(_)) + .WillByDefault( + Invoke(u2f_device_, &MockBluetoothDevice::GetMockService)); + AddU2fService(); + } + + void SetupConnectingU2fDevice(const std::string& device_address) { + auto run_cb_with_connection = [this, &device_address]( + const auto& callback, + const auto& error_callback) { + auto connection = std::make_unique<NiceMockBluetoothGattConnection>( + adapter_, device_address); + connection_ = connection.get(); + callback.Run(std::move(connection)); + }; + + auto run_cb_with_notify_session = [this](const auto& callback, + const auto& error_callback) { + auto notify_session = + std::make_unique<NiceMockBluetoothGattNotifySession>( + u2f_status_->GetWeakPtr()); + notify_session_ = notify_session.get(); + callback.Run(std::move(notify_session)); + }; + + ON_CALL(*u2f_device_, CreateGattConnection(_, _)) + .WillByDefault(Invoke(run_cb_with_connection)); + + ON_CALL(*u2f_device_, IsGattServicesDiscoveryComplete()) + .WillByDefault(Return(true)); + + ON_CALL(*u2f_status_, StartNotifySession(_, _)) + .WillByDefault(Invoke(run_cb_with_notify_session)); + } + + void SimulateDisconnect(const std::string& device_address) { + if (u2f_device_->GetAddress() != device_address) + return; + + u2f_device_->SetConnected(false); + adapter_->NotifyDeviceChanged(u2f_device_); + } + + void SimulateDeviceAddressChange(const std::string& old_address, + const std::string& new_address) { + if (!u2f_device_ || u2f_device_->GetAddress() != old_address) + return; + + ON_CALL(*u2f_device_, GetAddress()).WillByDefault(Return(new_address)); + + adapter_->NotifyDeviceChanged(u2f_device_); + for (auto& observer : adapter_->GetObservers()) + observer.DeviceAddressChanged(adapter_.get(), u2f_device_, old_address); + } + + void NotifyDeviceAdded(const std::string& device_address) { + auto* device = adapter_->GetDevice(device_address); + if (!device) + return; + + for (auto& observer : adapter_->GetObservers()) + observer.DeviceAdded(adapter_.get(), device); + } + + void NotifyStatusChanged(const std::vector<uint8_t>& value) { + for (auto& observer : adapter_->GetObservers()) + observer.GattCharacteristicValueChanged(adapter_.get(), u2f_status_, + value); + } + + void SetNextReadControlPointLengthReponse(bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*u2f_control_point_length_, ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + success ? callback.Run(value) + : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + })); + } + + void SetNextReadServiceRevisionResponse(bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*u2f_service_revision_, ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + success ? callback.Run(value) + : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + })); + } + + void SetNextReadServiceRevisionBitfieldResponse( + bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*u2f_service_revision_bitfield_, ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + success ? callback.Run(value) + : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + })); + } + + void SetNextWriteControlPointResponse(bool success) { + EXPECT_CALL(*u2f_control_point_, WriteRemoteCharacteristic(_, _, _)) + .WillOnce(Invoke([success](const auto& data, const auto& callback, + const auto& error_callback) { + success ? callback.Run() + : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + })); + } + + void SetNextWriteServiceRevisionResponse(bool success) { + EXPECT_CALL(*u2f_service_revision_bitfield_, + WriteRemoteCharacteristic(_, _, _)) + .WillOnce(Invoke([success](const auto& data, const auto& callback, + const auto& error_callback) { + success ? callback.Run() + : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + })); + } + + void AddU2fService() { + auto u2f_service = std::make_unique<NiceMockBluetoothGattService>( + u2f_device_, "u2f_service", BluetoothUUID(U2F_SERVICE_UUID), + /* is_primary */ true, /* is_local */ false); + u2f_service_ = u2f_service.get(); + u2f_device_->AddMockService(std::move(u2f_service)); + + ON_CALL(*u2f_service_, GetCharacteristics()) + .WillByDefault(Invoke( + u2f_service_, &MockBluetoothGattService::GetMockCharacteristics)); + + ON_CALL(*u2f_service_, GetCharacteristic(_)) + .WillByDefault(Invoke( + u2f_service_, &MockBluetoothGattService::GetMockCharacteristic)); + AddU2fCharacteristics(); + } + + void AddU2fCharacteristics() { + const bool is_local = false; + { + auto u2f_control_point = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + u2f_service_, "u2f_control_point", + BluetoothUUID(U2F_CONTROL_POINT_UUID), is_local, + BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PERMISSION_NONE); + u2f_control_point_ = u2f_control_point.get(); + u2f_service_->AddMockCharacteristic(std::move(u2f_control_point)); + } + + { + auto u2f_status = std::make_unique<NiceMockBluetoothGattCharacteristic>( + u2f_service_, "u2f_status", BluetoothUUID(U2F_STATUS_UUID), is_local, + BluetoothGattCharacteristic::PROPERTY_NOTIFY, + BluetoothGattCharacteristic::PERMISSION_NONE); + u2f_status_ = u2f_status.get(); + u2f_service_->AddMockCharacteristic(std::move(u2f_status)); + } + + { + auto u2f_control_point_length = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + u2f_service_, "u2f_control_point_length", + BluetoothUUID(U2F_CONTROL_POINT_LENGTH_UUID), is_local, + BluetoothGattCharacteristic::PROPERTY_READ, + BluetoothGattCharacteristic::PERMISSION_NONE); + u2f_control_point_length_ = u2f_control_point_length.get(); + u2f_service_->AddMockCharacteristic(std::move(u2f_control_point_length)); + } + + { + auto u2f_service_revision = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + u2f_service_, "u2f_service_revision", + BluetoothUUID(U2F_SERVICE_REVISION_UUID), is_local, + BluetoothGattCharacteristic::PROPERTY_READ, + BluetoothGattCharacteristic::PERMISSION_NONE); + u2f_service_revision_ = u2f_service_revision.get(); + u2f_service_->AddMockCharacteristic(std::move(u2f_service_revision)); + } + + { + auto u2f_service_revision_bitfield = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + u2f_service_, "u2f_service_revision_bitfield", + BluetoothUUID(U2F_SERVICE_REVISION_BITFIELD_UUID), is_local, + BluetoothGattCharacteristic::PROPERTY_READ | + BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PERMISSION_NONE); + u2f_service_revision_bitfield_ = u2f_service_revision_bitfield.get(); + u2f_service_->AddMockCharacteristic( + std::move(u2f_service_revision_bitfield)); + } + } + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; + + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<NiceMockBluetoothAdapter>(); + + MockBluetoothDevice* u2f_device_; + MockBluetoothGattService* u2f_service_; + + MockBluetoothGattCharacteristic* u2f_control_point_; + MockBluetoothGattCharacteristic* u2f_status_; + MockBluetoothGattCharacteristic* u2f_control_point_length_; + MockBluetoothGattCharacteristic* u2f_service_revision_; + MockBluetoothGattCharacteristic* u2f_service_revision_bitfield_; + + MockBluetoothGattConnection* connection_; + MockBluetoothGattNotifySession* notify_session_; +}; + +TEST_F(U2fBleConnectionTest, Address) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + auto connect_do_nothing = [](bool) {}; + auto read_do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + base::BindRepeating(connect_do_nothing), + base::BindRepeating(read_do_nothing)); + EXPECT_EQ(device_address, connection.address()); + AddU2Device(device_address); + + SimulateDeviceAddressChange(device_address, "new_device_address"); + EXPECT_EQ("new_device_address", connection.address()); +} + +TEST_F(U2fBleConnectionTest, DeviceNotPresent) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + auto do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_FALSE(result); +} + +TEST_F(U2fBleConnectionTest, PreConnected) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + + auto do_nothing = [](std::vector<uint8_t>) {}; + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(do_nothing)); + EXPECT_TRUE(connection_status_callback.WaitForResult()); +} + +TEST_F(U2fBleConnectionTest, PostConnected) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + auto do_nothing = [](std::vector<uint8_t>) {}; + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_FALSE(result); + + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + NotifyDeviceAdded(device_address); + EXPECT_TRUE(connection_status_callback.WaitForResult()); +} + +TEST_F(U2fBleConnectionTest, DeviceDisconnect) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto do_nothing = [](std::vector<uint8_t>) {}; + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_TRUE(result); + + SimulateDisconnect(device_address); + result = connection_status_callback.WaitForResult(); + EXPECT_FALSE(result); +} + +TEST_F(U2fBleConnectionTest, ReadStatusNotifications) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + TestReadCallback read_callback; + + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + read_callback.GetCallback()); + EXPECT_TRUE(connection_status_callback.WaitForResult()); + + std::vector<uint8_t> payload = ToByteVector("foo"); + NotifyStatusChanged(payload); + EXPECT_EQ(payload, read_callback.WaitForResult()); + + payload = ToByteVector("bar"); + NotifyStatusChanged(payload); + EXPECT_EQ(payload, read_callback.WaitForResult()); +} + +TEST_F(U2fBleConnectionTest, ReadControlPointLength) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto read_do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(read_do_nothing)); + EXPECT_TRUE(connection_status_callback.WaitForResult()); + + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(false, {}); + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + + // The Control Point Length should consist of exactly two bytes, hence we + // EXPECT_EQ(base::nullopt) for payloads of size 0, 1 and 3. + SetNextReadControlPointLengthReponse(true, {}); + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + + SetNextReadControlPointLengthReponse(true, {0xAB}); + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + + SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD}); + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(0xABCD, *length_callback.WaitForResult()); + + SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF}); + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); +} + +TEST_F(U2fBleConnectionTest, ReadServiceRevisions) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto read_do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(read_do_nothing)); + EXPECT_TRUE(connection_status_callback.WaitForResult()); + + TestReadServiceRevisionsCallback revisions_callback; + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + + SetNextReadServiceRevisionResponse(true, ToByteVector("bogus")); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + + SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_0)); + + SetNextReadServiceRevisionResponse(true, ToByteVector("1.1")); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1)); + + SetNextReadServiceRevisionResponse(true, ToByteVector("1.2")); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_2)); + + // Version 1.3 currently does not exist, so this should be treated as an + // error. + SetNextReadServiceRevisionResponse(true, ToByteVector("1.3")); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0x00}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0x80}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1)); + + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0x40}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_2)); + + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1, + U2fBleConnection::ServiceRevision::VERSION_1_2)); + + // All bits except the first two should be ignored. + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0xFF}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1, + U2fBleConnection::ServiceRevision::VERSION_1_2)); + + // All bytes except the first one should be ignored. + SetNextReadServiceRevisionResponse(false, {}); + SetNextReadServiceRevisionBitfieldResponse(true, {0xC0, 0xFF}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1, + U2fBleConnection::ServiceRevision::VERSION_1_2)); + + // The combination of a service revision string and bitfield should be + // supported as well. + SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); + SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), + ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_0, + U2fBleConnection::ServiceRevision::VERSION_1_1, + U2fBleConnection::ServiceRevision::VERSION_1_2)); +} + +TEST_F(U2fBleConnectionTest, WriteControlPoint) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto read_do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(read_do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_TRUE(result); + + TestWriteCallback write_callback; + SetNextWriteControlPointResponse(false); + connection.WriteControlPoint({}, write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_FALSE(result); + + SetNextWriteControlPointResponse(true); + connection.WriteControlPoint({}, write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_TRUE(result); +} + +TEST_F(U2fBleConnectionTest, WriteServiceRevision) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto read_do_nothing = [](std::vector<uint8_t>) {}; + + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(read_do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_TRUE(result); + + // Expect that errors are properly propagated. + TestWriteCallback write_callback; + SetNextWriteServiceRevisionResponse(false); + connection.WriteServiceRevision( + U2fBleConnection::ServiceRevision::VERSION_1_1, + write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_FALSE(result); + + // Expect a successful write of version 1.1. + SetNextWriteServiceRevisionResponse(true); + connection.WriteServiceRevision( + U2fBleConnection::ServiceRevision::VERSION_1_1, + write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_TRUE(result); + + // Expect a successful write of version 1.2. + SetNextWriteServiceRevisionResponse(true); + connection.WriteServiceRevision( + U2fBleConnection::ServiceRevision::VERSION_1_2, + write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_TRUE(result); + + // Writing version 1.0 to the bitfield is not intended, so this should fail. + connection.WriteServiceRevision( + U2fBleConnection::ServiceRevision::VERSION_1_0, + write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_FALSE(result); +} + +TEST_F(U2fBleConnectionTest, ReadsAndWriteFailWhenDisconnected) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestConnectionStatusCallback connection_status_callback; + + AddU2Device(device_address); + SetupConnectingU2fDevice(device_address); + auto do_nothing = [](std::vector<uint8_t>) {}; + U2fBleConnection connection(device_address, + connection_status_callback.GetCallback(), + base::BindRepeating(do_nothing)); + bool result = connection_status_callback.WaitForResult(); + EXPECT_TRUE(result); + + SimulateDisconnect(device_address); + result = connection_status_callback.WaitForResult(); + EXPECT_FALSE(result); + + // Reads should always fail on a disconnected device. + TestReadControlPointLengthCallback length_callback; + connection.ReadControlPointLength(length_callback.GetCallback()); + EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + + TestReadServiceRevisionsCallback revisions_callback; + connection.ReadServiceRevisions(revisions_callback.GetCallback()); + EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + + // Writes should always fail on a disconnected device. + TestWriteCallback write_callback; + connection.WriteServiceRevision( + U2fBleConnection::ServiceRevision::VERSION_1_1, + write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_FALSE(result); + + connection.WriteControlPoint({}, write_callback.GetCallback()); + result = write_callback.WaitForResult(); + EXPECT_FALSE(result); +} + +} // namespace device
diff --git a/device/u2f/u2f_ble_uuids.h b/device/u2f/u2f_ble_uuids.h index 8820e04..e3cfc482 100644 --- a/device/u2f/u2f_ble_uuids.h +++ b/device/u2f/u2f_ble_uuids.h
@@ -10,8 +10,8 @@ // U2F GATT Service's UUIDs as defined by the standard: // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h3_u2f-service // -// For details on how the short U2F Service UUID (0xfffd) was converted to the -// long one below, see +// For details on how the short UUIDs for U2F Service (0xFFFD) and U2F Service +// Revision (0x2A28) were converted to the long ones below, see // https://www.bluetooth.com/specifications/assigned-numbers/service-discovery static constexpr const char U2F_SERVICE_UUID[] = "0000fffd-0000-1000-8000-00805f9b34fb"; @@ -21,6 +21,10 @@ "f1d0fff2-deaa-ecee-b42f-c9ba7ed623bb"; static constexpr const char U2F_CONTROL_POINT_LENGTH_UUID[] = "f1d0fff3-deaa-ecee-b42f-c9ba7ed623bb"; +static constexpr const char U2F_SERVICE_REVISION_UUID[] = + "00002a28-0000-1000-8000-00805f9b34fb"; +static constexpr const char U2F_SERVICE_REVISION_BITFIELD_UUID[] = + "f1d0fff4-deaa-ecee-b42f-c9ba7ed623bb"; } // namespace device
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm index 4021fbe..e77d736b5 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm
@@ -231,10 +231,10 @@ [detailTextLabel setTextAlignment:_alignment]; // The width must be positive for CGContextRef to be valid. - UIEdgeInsets safeAreaInsets = SafeAreaInsetsForView(row); - CGFloat labelWidth = MAX( - 40, floorf(UIEdgeInsetsInsetRect(row.bounds, safeAreaInsets).size.width) - - kTextCellLeadingPadding); + UIEdgeInsets safeAreaInsets = SafeAreaInsetsForView(self.view); + CGRect rowBounds = UIEdgeInsetsInsetRect(self.view.bounds, safeAreaInsets); + CGFloat labelWidth = + MAX(40, floorf(rowBounds.size.width) - kTextCellLeadingPadding); CGFloat labelHeight = match.hasAnswer ? kAnswerLabelHeight : kTextDetailLabelHeight; CGFloat answerImagePadding = kAnswerImageWidth + kAnswerImageRightPadding; @@ -243,7 +243,7 @@ kTextCellLeadingPadding; LayoutRect detailTextLabelLayout = - LayoutRectMake(leadingPadding, CGRectGetWidth(self.view.bounds), + LayoutRectMake(leadingPadding, CGRectGetWidth(rowBounds), kDetailCellTopPadding, labelWidth, labelHeight); detailTextLabel.frame = LayoutRectGetRect(detailTextLabelLayout); @@ -259,8 +259,8 @@ OmniboxPopupTruncatingLabel* textLabel = row.textTruncatingLabel; [textLabel setTextAlignment:_alignment]; LayoutRect textLabelLayout = - LayoutRectMake(kTextCellLeadingPadding, CGRectGetWidth(self.view.bounds), - 0, labelWidth, kTextLabelHeight); + LayoutRectMake(kTextCellLeadingPadding, CGRectGetWidth(rowBounds), 0, + labelWidth, kTextLabelHeight); textLabel.frame = LayoutRectGetRect(textLabelLayout); // Set the text.
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp index 5958efa3..7371788 100644 --- a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp +++ b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp
@@ -797,15 +797,6 @@ DCHECK(GetDocument()); have_background_parser_ = true; - if (GetDocument()->GetFrame() && - GetDocument()->GetFrame()->FrameScheduler()) { - virtual_time_pauser_ = GetDocument() - ->GetFrame() - ->FrameScheduler() - ->CreateWebScopedVirtualTimePauser(); - virtual_time_pauser_.PauseVirtualTime(true); - } - // Make sure that a resolver is set up, so that the correct viewport // dimensions will be fed to the background parser and preload scanner. if (GetDocument()->Loader()) @@ -856,7 +847,6 @@ DCHECK(ShouldUseThreading()); DCHECK(have_background_parser_); - virtual_time_pauser_.PauseVirtualTime(false); have_background_parser_ = false; // Make this sync, as lsan triggers on some unittests if the task runner is
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.h b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.h index 80409d3..ed852d6 100644 --- a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.h +++ b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.h
@@ -285,8 +285,6 @@ bool tried_loading_link_headers_; bool added_pending_stylesheet_in_body_; bool is_waiting_for_stylesheets_; - - WebScopedVirtualTimePauser virtual_time_pauser_; }; } // namespace blink