Chromium Code Reviews| Index: components/proximity_auth/ble/bluetooth_low_energy_connection.cc |
| diff --git a/components/proximity_auth/ble/bluetooth_low_energy_connection.cc b/components/proximity_auth/ble/bluetooth_low_energy_connection.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..43c83bf9dbc284d987e16d36d8870dddad91ad8f |
| --- /dev/null |
| +++ b/components/proximity_auth/ble/bluetooth_low_energy_connection.cc |
| @@ -0,0 +1,326 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "components/proximity_auth/ble/bluetooth_low_energy_connection.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/memory/ref_counted.h" |
| +#include "base/memory/weak_ptr.h" |
| +#include "components/proximity_auth/ble/fake_wire_message.h" |
| +#include "components/proximity_auth/connection_finder.h" |
| +#include "components/proximity_auth/wire_message.h" |
| +#include "device/bluetooth/bluetooth_adapter.h" |
| +#include "device/bluetooth/bluetooth_device.h" |
| +#include "device/bluetooth/bluetooth_gatt_characteristic.h" |
| +#include "device/bluetooth/bluetooth_gatt_connection.h" |
| +#include "device/bluetooth/bluetooth_gatt_notify_session.h" |
| +#include "device/bluetooth/bluetooth_uuid.h" |
| + |
| +using device::BluetoothAdapter; |
| +using device::BluetoothDevice; |
| +using device::BluetoothGattConnection; |
| +using device::BluetoothGattService; |
| +using device::BluetoothGattCharacteristic; |
| +using device::BluetoothGattNotifySession; |
| +using device::BluetoothUUID; |
| + |
| +namespace proximity_auth { |
| + |
| +BluetoothLowEnergyConnection::BluetoothLowEnergyConnection( |
| + const RemoteDevice& device, |
| + scoped_refptr<device::BluetoothAdapter> adapter, |
| + BluetoothUUID remote_service_uuid, |
| + const std::string& to_peripheral_char_uuid, |
| + const std::string& from_peripheral_char_uuid, |
| + scoped_ptr<BluetoothGattConnection> gatt_connection) |
| + : Connection(device), |
| + adapter_(adapter), |
| + remote_service_uuid_(remote_service_uuid), |
| + to_peripheral_char_uuid_(to_peripheral_char_uuid), |
| + from_peripheral_char_uuid_(from_peripheral_char_uuid), |
| + connection_(gatt_connection.Pass()), |
| + notify_session_pending_(false), |
| + connect_signal_response_pending_(false), |
| + weak_ptr_factory_(this) { |
| + DCHECK(connection_); |
| + DCHECK(adapter_); |
| + DCHECK(adapter_->IsInitialized()); |
| + |
| + SetStatus(IN_PROGRESS); |
| + adapter_->AddObserver(this); |
|
Tim Song
2015/05/11 08:02:39
Are all services going to be discovered as soon as
sacomoto
2015/05/11 12:04:04
Yes, you are right. We should scan the services a
|
| +} |
| + |
| +BluetoothLowEnergyConnection::~BluetoothLowEnergyConnection() { |
| + Disconnect(); |
| + if (adapter_) { |
| + adapter_->RemoveObserver(this); |
| + adapter_ = NULL; |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::Connect() { |
| + NOTREACHED(); |
| +} |
| + |
| +// This actually forgets the remote BLE device. This is safe as long as we only |
| +// connect to BLE devices advertising the SmartLock service (assuming this |
| +// device has no other being used). |
| +void BluetoothLowEnergyConnection::Disconnect() { |
| + StopNotifySession(); |
| + SetStatus(DISCONNECTED); |
| + if (connection_) { |
| + connection_.reset(); |
| + BluetoothDevice* device = GetRemoteDevice(); |
| + if (device) { |
| + VLOG(1) << "Forget device " << device->GetAddress(); |
| + device->Forget(base::Bind(&base::DoNothing)); |
| + } |
| + } |
| +} |
| + |
| +// TODO(sacomoto): Send a SmartLock BLE socket incoming signal. Implement a |
| +// sender with full support for messages larger than a single characteristic |
| +// value. |
| +void BluetoothLowEnergyConnection::SendMessageImpl( |
| + scoped_ptr<WireMessage> message) { |
| + DCHECK(!GetGattCharacteristic(to_peripheral_char_id_)); |
| + VLOG(1) << "Sending message " << message->Serialize(); |
| + |
| + std::string serialized_message = message->Serialize(); |
| + std::vector<uint8> bytes(serialized_message.begin(), |
| + serialized_message.end()); |
| + |
| + GetGattCharacteristic(to_peripheral_char_id_) |
| + ->WriteRemoteCharacteristic( |
| + bytes, base::Bind(&base::DoNothing), |
| + base::Bind( |
| + &BluetoothLowEnergyConnection::OnWriteRemoteCharacteristicError, |
| + weak_ptr_factory_.GetWeakPtr())); |
| +} |
| + |
| +void BluetoothLowEnergyConnection::DeviceRemoved(BluetoothAdapter* adapter, |
| + BluetoothDevice* device) { |
| + if (device && device->GetAddress() == GetRemoteDeviceAddress()) { |
| + VLOG(1) << "Device removed " << GetRemoteDeviceAddress(); |
| + Disconnect(); |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::GattDiscoveryCompleteForService( |
| + BluetoothAdapter* adapter, |
| + BluetoothGattService* service) { |
| + if (service && service->GetUUID() == remote_service_uuid_) { |
| + VLOG(1) << "All characteristics discovered for " |
| + << remote_service_uuid_.canonical_value(); |
| + |
| + std::vector<device::BluetoothGattCharacteristic*> characteristics = |
| + service->GetCharacteristics(); |
| + for (auto iter = characteristics.begin(); iter != characteristics.end(); |
| + iter++) { |
| + HandleCharacteristicUpdate(*iter); |
| + } |
| + |
| + if (to_peripheral_char_id_.empty() || from_peripheral_char_id_.empty()) { |
| + VLOG(1) << "Connection error, missing characteristics for SmartLock " |
| + "service.\n" |
| + << (to_peripheral_char_id_.empty() ? to_peripheral_char_uuid_ |
| + : "") |
| + << (from_peripheral_char_id_.empty() |
| + ? ", " + from_peripheral_char_uuid_ |
| + : "") << " not found."; |
| + Disconnect(); |
| + } |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::GattCharacteristicAdded( |
| + BluetoothAdapter* adapter, |
| + BluetoothGattCharacteristic* characteristic) { |
| + VLOG(1) << "new char found: " << characteristic->GetUUID().canonical_value(); |
| + HandleCharacteristicUpdate(characteristic); |
|
Tim Song
2015/05/11 08:02:39
Is it correct to call HandleCharacteristicUpdate()
sacomoto
2015/05/11 12:04:04
This was intentional. The idea was to have the con
Tim Song
2015/05/12 03:02:46
Thanks for the explanation. In this case, do you e
sacomoto
2015/05/12 07:23:19
Yes, we still need to override GattDiscoveryComple
Tim Song
2015/05/13 02:36:55
Can we at least get rid of the for loop iterating
|
| +} |
| + |
| +// TODO(sacomoto): Parse the SmartLock BLE socket incoming signal. Implement a |
| +// receiver with full suport for messages larger than a single characteristic |
| +// value. |
| +void BluetoothLowEnergyConnection::GattCharacteristicValueChanged( |
| + BluetoothAdapter* adapter, |
| + BluetoothGattCharacteristic* characteristic, |
| + const std::vector<uint8>& value) { |
| + DCHECK_EQ(adapter, adapter_.get()); |
| + |
| + VLOG(1) << "Characteristic value changed: " |
| + << characteristic->GetUUID().canonical_value(); |
| + |
| + if (characteristic->GetIdentifier() == from_peripheral_char_id_) { |
| + const ControlSignal signal = |
| + static_cast<ControlSignal>(ExtractUint32(value)); |
| + |
| + switch (signal) { |
| + case kInvitationResponseSignal: |
| + connect_signal_response_pending_ = false; |
| + CompleteConnection(); |
| + break; |
| + case kInviteToConnectSignal: |
| + case kSendSignal: |
| + // TODO(sacomoto): Actually handle the message and call OnBytesReceived |
| + // when complete. |
| + case kDisconnectSignal: |
| + Disconnect(); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +scoped_ptr<WireMessage> BluetoothLowEnergyConnection::DeserializeWireMessage( |
| + bool* is_incomplete_message) { |
| + return FakeWireMessage::Deserialize(received_bytes(), is_incomplete_message); |
| +} |
| + |
| +void BluetoothLowEnergyConnection::HandleCharacteristicUpdate( |
| + BluetoothGattCharacteristic* characteristic) { |
| + // Checks if |characteristic| is equal to |from_peripheral_char_uuid_| or |
| + // |to_peripheral_char_uuid_|. |
| + UpdateCharacteristicsStatus(characteristic); |
| + |
| + // Starts a notify session for |from_peripheral_char_uuid_|. |
| + if (characteristic->GetIdentifier() == from_peripheral_char_id_) |
| + StartNotifySession(); |
| + |
| + // Sends a invite to connect signal if ready. |
| + SendInviteToConnectSignal(); |
| +} |
| + |
| +void BluetoothLowEnergyConnection::CompleteConnection() { |
| + if (status() == IN_PROGRESS && !connect_signal_response_pending_ && |
| + !to_peripheral_char_id_.empty() && !from_peripheral_char_id_.empty() && |
| + notify_session_) { |
| + VLOG(1) << "Connection completed"; |
| + SetStatus(CONNECTED); |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::StartNotifySession() { |
| + BluetoothGattCharacteristic* characteristic = |
| + GetGattCharacteristic(from_peripheral_char_id_); |
| + if (!characteristic) { |
| + VLOG(1) << "Characteristic " << from_peripheral_char_uuid_ << " not found."; |
| + return; |
| + } |
| + |
| + if (notify_session_ || notify_session_pending_) { |
| + VLOG(1) << "Notify session already started."; |
| + return; |
| + } |
| + |
| + notify_session_pending_ = true; |
| + characteristic->StartNotifySession( |
| + base::Bind(&BluetoothLowEnergyConnection::OnNotifySessionStarted, |
| + weak_ptr_factory_.GetWeakPtr()), |
| + base::Bind(&BluetoothLowEnergyConnection::OnNotifySessionError, |
| + weak_ptr_factory_.GetWeakPtr())); |
| +} |
| + |
| +void BluetoothLowEnergyConnection::OnNotifySessionError( |
| + BluetoothGattService::GattErrorCode error) { |
| + VLOG(1) << "Error starting notification session: " << error; |
| + notify_session_pending_ = false; |
| +} |
| + |
| +void BluetoothLowEnergyConnection::OnNotifySessionStarted( |
| + scoped_ptr<BluetoothGattNotifySession> notify_session) { |
| + VLOG(1) << "Notification session started " |
| + << notify_session->GetCharacteristicIdentifier(); |
| + notify_session_ = notify_session.Pass(); |
| + notify_session_pending_ = false; |
| + |
| + // Sends an invite to connect signal if ready. |
| + SendInviteToConnectSignal(); |
| +} |
| + |
| +void BluetoothLowEnergyConnection::StopNotifySession() { |
| + if (notify_session_) { |
| + notify_session_->Stop(base::Bind(&base::DoNothing)); |
| + notify_session_.reset(); |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::OnWriteRemoteCharacteristicError( |
| + BluetoothGattService::GattErrorCode error) { |
| + VLOG(1) << "Error writing characteristic" << to_peripheral_char_uuid_; |
| +} |
| + |
| +void BluetoothLowEnergyConnection::SendInviteToConnectSignal() { |
| + if (status() == IN_PROGRESS && !connect_signal_response_pending_ && |
| + !to_peripheral_char_id_.empty() && !from_peripheral_char_id_.empty() && |
| + notify_session_) { |
| + VLOG(1) << "Sending invite to connect signal"; |
| + connect_signal_response_pending_ = true; |
| + const char connect_signal[4] = {}; |
|
Tim Song
2015/05/11 08:02:39
You should use use the kInviteToConnectSignal valu
sacomoto
2015/05/11 12:04:04
Done.
|
| + |
| + // The connection status is not CONNECTED yet, so in order to bypass the |
| + // check in SendMessage implementation we need to send the message using the |
| + // private implementation. |
| + SendMessageImpl( |
| + scoped_ptr<FakeWireMessage>(new FakeWireMessage(connect_signal))); |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyConnection::UpdateCharacteristicsStatus( |
| + BluetoothGattCharacteristic* characteristic) { |
| + if (characteristic) { |
| + std::string canonical_value = characteristic->GetUUID().canonical_value(); |
| + VLOG(1) << canonical_value; |
| + if (to_peripheral_char_uuid_ == canonical_value) { |
| + to_peripheral_char_id_ = characteristic->GetIdentifier(); |
| + } |
| + if (from_peripheral_char_uuid_ == canonical_value) { |
|
Tim Song
2015/05/11 08:02:39
nit : please be consistent and remove the braces f
sacomoto
2015/05/11 12:04:03
Done.
|
| + from_peripheral_char_id_ = characteristic->GetIdentifier(); |
| + } |
| + BluetoothGattService* service = characteristic->GetService(); |
| + if (service && service->GetUUID() == remote_service_uuid_) |
| + remote_service_id_ = service->GetIdentifier(); |
| + } |
| +} |
| + |
| +const std::string& BluetoothLowEnergyConnection::GetRemoteDeviceAddress() { |
| + return remote_device().bluetooth_address; |
| +} |
| + |
| +BluetoothDevice* BluetoothLowEnergyConnection::GetRemoteDevice() { |
| + if (!adapter_ || !adapter_->IsInitialized()) { |
| + VLOG(1) << "adapter not ready"; |
| + return NULL; |
| + } |
| + return adapter_->GetDevice(GetRemoteDeviceAddress()); |
| +} |
| + |
| +BluetoothGattService* BluetoothLowEnergyConnection::GetRemoteService() { |
| + BluetoothDevice* remote_device = GetRemoteDevice(); |
| + if (!remote_device) { |
| + VLOG(1) << "device not found"; |
| + return NULL; |
| + } |
| + return remote_device->GetGattService(remote_service_id_); |
| +} |
| + |
| +BluetoothGattCharacteristic* |
| +BluetoothLowEnergyConnection::GetGattCharacteristic( |
| + const std::string& gatt_characteristic) { |
| + BluetoothGattService* remote_service = GetRemoteService(); |
| + if (!remote_service) { |
| + VLOG(1) << "service not found"; |
| + return NULL; |
| + } |
| + return remote_service->GetCharacteristic(gatt_characteristic); |
| +} |
| + |
| +// TODO(sacomoto): make this robust to byte ordering in both sides of the |
| +// SmartLock BLE socket. |
| +uint32 BluetoothLowEnergyConnection::ExtractUint32( |
| + const std::vector<uint8>& bytes) { |
| + return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); |
| +} |
| + |
| +} // namespace proximity_auth |