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..13e11b0238501a4cecbc70c967e0ce66bb6261f5 |
--- /dev/null |
+++ b/components/proximity_auth/ble/bluetooth_low_energy_connection.cc |
@@ -0,0 +1,360 @@ |
+// 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 "base/time/time.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, |
+ const BluetoothUUID remote_service_uuid, |
+ const BluetoothUUID to_peripheral_char_uuid, |
+ const BluetoothUUID from_peripheral_char_uuid, |
+ scoped_ptr<BluetoothGattConnection> gatt_connection) |
+ : Connection(device), |
+ adapter_(adapter), |
+ remote_service_({remote_service_uuid, ""}), |
+ to_peripheral_char_({to_peripheral_char_uuid, ""}), |
+ from_peripheral_char_({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()); |
+ |
+ start_time_ = base::TimeTicks::Now(); |
+ SetStatus(IN_PROGRESS); |
+ |
+ // We should set the status to IN_PROGRESS before calling this function, as we |
+ // can set the status to CONNECTED by an inner call of |
+ // HandleCharacteristicUpdate(). |
+ ScanRemoteCharacteristics(GetRemoteService()); |
+ |
+ adapter_->AddObserver(this); |
+} |
+ |
+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(); |
+ |
+ 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.canonical_value() |
+ : "") |
+ << (from_peripheral_char_.id.empty() |
+ ? ", " + from_peripheral_char_.uuid.canonical_value() |
+ : "") << " not found."; |
+ Disconnect(); |
+ } |
+ } |
+} |
+ |
+void BluetoothLowEnergyConnection::GattCharacteristicAdded( |
+ BluetoothAdapter* adapter, |
+ BluetoothGattCharacteristic* characteristic) { |
+ VLOG(1) << "New char found: " << characteristic->GetUUID().canonical_value(); |
+ HandleCharacteristicUpdate(characteristic); |
+} |
+ |
+// 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>(ToUint32(value)); |
+ |
+ switch (signal) { |
+ case ControlSignal::kInvitationResponseSignal: |
+ connect_signal_response_pending_ = false; |
+ CompleteConnection(); |
+ break; |
+ case ControlSignal::kInviteToConnectSignal: |
+ case ControlSignal::kSendSignal: |
+ // TODO(sacomoto): Actually handle the message and call OnBytesReceived |
+ // when complete. |
+ case ControlSignal::kDisconnectSignal: |
+ Disconnect(); |
+ break; |
+ } |
+ } |
+} |
+ |
+scoped_ptr<WireMessage> BluetoothLowEnergyConnection::DeserializeWireMessage( |
+ bool* is_incomplete_message) { |
+ return FakeWireMessage::Deserialize(received_bytes(), is_incomplete_message); |
+} |
+ |
+void BluetoothLowEnergyConnection::ScanRemoteCharacteristics( |
+ BluetoothGattService* service) { |
+ if (service) { |
+ std::vector<device::BluetoothGattCharacteristic*> characteristics = |
+ service->GetCharacteristics(); |
+ for (auto iter = characteristics.begin(); iter != characteristics.end(); |
+ iter++) { |
+ HandleCharacteristicUpdate(*iter); |
+ } |
+ } |
+} |
+ |
+void BluetoothLowEnergyConnection::HandleCharacteristicUpdate( |
+ BluetoothGattCharacteristic* characteristic) { |
+ // Checks if |characteristic| is equal to |from_peripheral_char_| or |
+ // |to_peripheral_char_|. |
+ UpdateCharacteristicsStatus(characteristic); |
+ |
+ // Starts a notify session for |from_peripheral_char_|. |
+ 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"; |
+ VLOG(1) << "Time elapsed: " << base::TimeTicks::Now() - start_time_; |
+ SetStatus(CONNECTED); |
+ } |
+} |
+ |
+void BluetoothLowEnergyConnection::StartNotifySession() { |
+ BluetoothGattCharacteristic* characteristic = |
+ GetGattCharacteristic(from_peripheral_char_.id); |
+ if (!characteristic) { |
+ VLOG(1) << "Characteristic " << from_peripheral_char_.uuid.canonical_value() |
+ << " 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.canonical_value(); |
+} |
+ |
+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; |
+ |
+ // 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( |
+ ToString(static_cast<uint32>(ControlSignal::kInviteToConnectSignal))))); |
+ } |
+} |
+ |
+void BluetoothLowEnergyConnection::UpdateCharacteristicsStatus( |
+ BluetoothGattCharacteristic* characteristic) { |
+ if (characteristic) { |
+ BluetoothUUID uuid = characteristic->GetUUID(); |
+ if (to_peripheral_char_.uuid == uuid) |
+ to_peripheral_char_.id = characteristic->GetIdentifier(); |
+ if (from_peripheral_char_.uuid == uuid) |
+ 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; |
+ } |
+ if (remote_service_.id.empty()) { |
+ std::vector<BluetoothGattService*> services = |
+ remote_device->GetGattServices(); |
+ for (auto iter = services.begin(); iter != services.end(); ++iter) |
+ if ((*iter)->GetUUID() == remote_service_.uuid) { |
+ remote_service_.id = (*iter)->GetIdentifier(); |
+ break; |
+ } |
+ } |
+ 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::ToUint32(const std::vector<uint8>& bytes) { |
+ return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); |
+} |
+ |
+// TODO(sacomoto): make this robust to byte ordering in both sides of the |
+// SmartLock BLE socket. |
+const std::string BluetoothLowEnergyConnection::ToString(const uint32 value) { |
+ char bytes[4] = {}; |
+ |
+ bytes[0] = static_cast<uint8>(value); |
+ bytes[1] = static_cast<uint8>(value >> 8); |
+ bytes[2] = static_cast<uint8>(value >> 12); |
+ bytes[3] = static_cast<uint8>(value >> 24); |
+ |
+ return std::string(bytes); |
+} |
+ |
+} // namespace proximity_auth |