Chromium Code Reviews| Index: components/proximity_auth/ble/bluetooth_low_energy_weave_server.cc |
| diff --git a/components/proximity_auth/ble/bluetooth_low_energy_weave_server.cc b/components/proximity_auth/ble/bluetooth_low_energy_weave_server.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2e1c1c559003b6c6db046babc62d8ae86e98f953 |
| --- /dev/null |
| +++ b/components/proximity_auth/ble/bluetooth_low_energy_weave_server.cc |
| @@ -0,0 +1,340 @@ |
| +// Copyright 2016 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 "base/bind.h" |
| +#include "base/threading/thread_task_runner_handle.h" |
| +#include "base/time/time.h" |
| +#include "components/proximity_auth/ble/bluetooth_low_energy_weave_server.h" |
| +#include "device/bluetooth/bluetooth_adapter_factory.h" |
| +#include "device/bluetooth/bluetooth_uuid.h" |
| + |
| +namespace proximity_auth { |
| +namespace weave { |
| +namespace { |
| + |
| +using Property = BluetoothLocalGattCharacteristic::Property; |
| +using BluetoothUUID = device::BluetoothUUID; |
| + |
| +const uint32_t kAdvertisementRotationPeriodInSecs = 5; |
| +const uint32_t kEidRotationPeriodInHours = 8; |
| +const uint32_t kEidSeedRotationPeriodInDays = 14; |
| + |
| +// TODO(jingxuy): find out the right UUID |
| +const char kWeaveServiceUUID[] = "00000100-0004-1000-8000-001A11000100"; |
| + |
| +// The UUID of the BLE service that host the RX and TX characteristics. |
| +const char kRXCharacteristicUUID[] = "00000100-0004-1000-8000-001A11000101"; |
| +const char kTXCharacteristicUUID[] = "00000100-0004-1000-8000-001A11000102"; |
| + |
| +// TODO(jingxuy): verify these properties, currently just a draft version. |
| +const uint32_t kRXCharacteristicProperties = |
| + Property::PROPERTY_NOTIFY | Property::PROPERTY_READ; |
| + |
| +const uint32_t kTXCharacteristicProperties = Property::PROPERTY_WRITE; |
| + |
| +const uint32_t kCharacteristicPermissions = |
| + BluetoothLocalGattCharacteristic::Permission::PERMISSION_NONE; |
| + |
| +} // namespace |
| + |
| +// static. |
| +BluetoothLowEnergyWeaveServer::Factory* |
| + BluetoothLowEnergyWeaveServer::Factory::factory_instance_ = nullptr; |
| + |
| +// static. |
| +std::unique_ptr<BluetoothLowEnergyWeaveServer> |
| +BluetoothLowEnergyWeaveServer::Factory::NewInstance() { |
| + if (factory_instance_ == nullptr) { |
| + factory_instance_ = new Factory(); |
| + } |
| + return factory_instance_->BuildInstance(); |
| +} |
| + |
| +// static. |
| +void BluetoothLowEnergyWeaveServer::Factory::SetInstanceForTesting( |
| + Factory* factory) { |
| + factory_instance_ = factory; |
| +} |
| + |
| +std::unique_ptr<BluetoothLowEnergyWeaveServer> |
| +BluetoothLowEnergyWeaveServer::Factory::BuildInstance() { |
| + return std::unique_ptr<BluetoothLowEnergyWeaveServer>( |
| + new BluetoothLowEnergyWeaveServer()); |
| +} |
| + |
| +BluetoothLowEnergyWeaveServer::BluetoothLowEnergyWeaveServer() |
| + : task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| + ad_rotator_(BluetoothLowEnergyAdvertisementRotator::Factory::NewInstance( |
| + kWeaveServiceUUID)), |
| + weak_ptr_factory_(this) { |
| + adapter_ = |
| + BluetoothAdapter::CreateAdapter( |
| + base::Bind(&BluetoothLowEnergyWeaveServer::CreateAdapterCallback, |
| + weak_ptr_factory_.GetWeakPtr())) |
| + .get(); |
| + service_ = BluetoothLocalGattService::Create( |
| + adapter_.get(), BluetoothUUID(kWeaveServiceUUID), true, nullptr, this); |
| + rx_characteristic_ = BluetoothLocalGattCharacteristic::Create( |
| + BluetoothUUID(kRXCharacteristicUUID), kRXCharacteristicProperties, |
| + kCharacteristicPermissions, service_.get()); |
| + tx_characteristic_ = BluetoothLocalGattCharacteristic::Create( |
| + BluetoothUUID(kTXCharacteristicUUID), kTXCharacteristicProperties, |
| + kCharacteristicPermissions, service_.get()); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::OnCharacteristicReadRequest( |
| + const BluetoothDevice* bluetooth_device, |
| + const BluetoothLocalGattCharacteristic* characteristic, |
| + int offset, |
| + const ValueCallback& callback, |
| + const ErrorCallback& error_callback) { |
| + // Only care about the read on the rx_characteristic from registered devices. |
| + if (characteristic != rx_characteristic_.get() || |
| + !HasDevice(bluetooth_device->GetAddress())) { |
| + return; |
| + } |
| + |
| + // TODO(jingxuy): Do we even need to support this function? |
| + // I thought the client only gets informed of rx_characteristics changes and |
| + // wont't starting actively reading the value. |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::OnCharacteristicWriteRequest( |
| + const BluetoothDevice* bluetooth_device, |
| + const BluetoothLocalGattCharacteristic* characteristic, |
| + const Packet& value, |
| + int offset, |
| + const base::Closure& callback, |
| + const ErrorCallback& error_callback) { |
| + // Only care about the writes on the tx_characteristic from registered |
| + // devices. |
| + if (characteristic != tx_characteristic_.get() || |
| + !HasDevice(bluetooth_device->GetAddress())) { |
| + return; |
| + } |
| + |
| + auto it = map_to_server_connection_.find(bluetooth_device->GetAddress()); |
| + |
| + // Device is registered but it's not connected. |
| + if (it == map_to_server_connection_.end()) |
| + return; |
| + |
| + // TODO(jingxuy): do I need to care about offset? |
| + // TODO(jingxuy): what do I do with the error_callback? |
| + it->second->ReceivePacket(value); |
| + callback.Run(); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::OnNotificationsStart( |
| + const BluetoothDevice* bluetooth_device, |
| + const BluetoothLocalGattCharacteristic* characteristic) { |
| + const std::string bluetooth_address = bluetooth_device->GetAddress(); |
| + |
| + if (characteristic != rx_characteristic_.get() || |
| + !HasDevice(bluetooth_address) || IsConnected(bluetooth_address)) { |
| + return; |
| + } |
| + |
| + std::unique_ptr<BluetoothLowEnergyWeaveServerConnection> connection( |
| + new BluetoothLowEnergyWeaveServerConnection( |
| + bluetooth_device, weak_ptr_factory_.GetWeakPtr(), |
| + rx_characteristic_)); |
| + |
| + connection->Connect(); |
| + |
| + map_to_server_connection_.insert( |
| + std::make_pair(bluetooth_address, std::move(connection))); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::OnNotificationsStop( |
| + const BluetoothDevice* bluetooth_device, |
| + const BluetoothLocalGattCharacteristic* characteristic) { |
| + const std::string bluetooth_address = bluetooth_device->GetAddress(); |
| + |
| + if (characteristic != rx_characteristic_.get() || |
| + !HasDevice(bluetooth_address) || !IsConnected(bluetooth_address)) { |
| + return; |
| + } |
| + |
| + auto it = map_to_server_connection_.find(bluetooth_address); |
| + |
| + if (it->second->status() != ConnectionStatus::DISCONNECTED) { |
| + it->second->Disconnect(); |
| + } |
| + map_to_server_connection_.erase(bluetooth_address); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::RegisterDevice( |
| + const RemoteDevice& remote_device) { |
| + DCHECK(!HasDevice(remote_device.bluetooth_address)); |
| + |
| + map_to_remote_device_[remote_device.bluetooth_address] = remote_device; |
| + ad_rotator_->AddDevice(remote_device); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UnregisterDevice( |
| + const RemoteDevice& remote_device) { |
| + std::string bluetooth_address = remote_device.bluetooth_address; |
| + DCHECK(HasDevice(bluetooth_address)); |
| + |
| + if (IsAdvertising(bluetooth_address)) { |
| + UnregisterAdvertisement(bluetooth_address); |
| + } |
| + |
| + if (IsConnected(bluetooth_address)) { |
| + map_to_server_connection_[bluetooth_address]->Disconnect(); |
| + map_to_server_connection_.erase(bluetooth_address); |
| + } |
| + |
| + ad_rotator_->RemoveDevice(remote_device); |
| + map_to_remote_device_.erase(bluetooth_address); |
| +} |
| + |
| +bool BluetoothLowEnergyWeaveServer::HasDevice(std::string device_address) { |
| + return map_to_remote_device_.find(device_address) != |
| + map_to_remote_device_.end(); |
| +} |
| + |
| +const RemoteDevice& BluetoothLowEnergyWeaveServer::GetRemoteDevice( |
| + const BluetoothDevice* bluetooth_device) { |
| + DCHECK(HasDevice(bluetooth_device->GetAddress())); |
| + return map_to_remote_device_[bluetooth_device->GetAddress()]; |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::EnableAdvertising( |
| + const RemoteDevice& remote_device) { |
| + DCHECK(HasDevice(remote_device.bluetooth_address) || |
| + !ad_rotator_->HasDeviceWithAddress(remote_device.bluetooth_address)); |
| + |
| + ad_rotator_->AddDevice(remote_device); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::DisableAdvertising( |
| + const RemoteDevice& remote_device) { |
| + DCHECK(HasDevice(remote_device.bluetooth_address) || |
| + ad_rotator_->HasDeviceWithAddress(remote_device.bluetooth_address)); |
| + |
| + ad_rotator_->RemoveDevice(remote_device); |
| + |
| + if (map_to_advertisement_.find(remote_device.bluetooth_address) != |
| + map_to_advertisement_.end()) { |
| + UnregisterAdvertisement(remote_device.bluetooth_address); |
| + } |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::SetTaskRunnerForTesting( |
| + scoped_refptr<base::TaskRunner> task_runner) { |
| + task_runner_ = task_runner; |
| +} |
| + |
| +bool BluetoothLowEnergyWeaveServer::IsAdvertising( |
| + std::string bluetooth_address) { |
| + return map_to_advertisement_.find(bluetooth_address) != |
| + map_to_advertisement_.end(); |
| +} |
| + |
| +bool BluetoothLowEnergyWeaveServer::IsConnected(std::string device_address) { |
| + return map_to_server_connection_.find(device_address) != |
| + map_to_server_connection_.end(); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UpdateEidSeeds() { |
| + RefreshAdvertisement(); |
| + // TODO (jingxuy): make this follow 14 day boundaries. |
| + base::TimeDelta wait_time = |
| + base::TimeDelta::FromDays(kEidSeedRotationPeriodInDays); |
| + task_runner_->PostDelayedTask( |
| + FROM_HERE, base::Bind(&BluetoothLowEnergyWeaveServer::UpdateEidSeeds, |
| + weak_ptr_factory_.GetWeakPtr()), |
| + wait_time); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UpdateEids() { |
| + RefreshAdvertisement(); |
| + // TODO(jingxuy): make this follow 8 hour boundaries. |
| + base::TimeDelta wait_time = |
| + base::TimeDelta::FromHours(kEidRotationPeriodInHours); |
| + task_runner_->PostDelayedTask( |
| + FROM_HERE, base::Bind(&BluetoothLowEnergyWeaveServer::UpdateEids, |
| + weak_ptr_factory_.GetWeakPtr()), |
| + wait_time); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::RefreshAdvertisement() { |
|
Kyle Horimoto
2016/07/29 03:15:39
We can't change advertisements if connections are
jingxuy
2016/07/29 18:45:43
the advertisement will be taken down when a connec
Kyle Horimoto
2016/07/29 19:04:18
Yeah, that is what is wrong about this implementat
jingxuy
2016/07/29 20:26:01
oh, okay.
|
| + // Can't change content of current advertisement. |
| + // Hence unregister all current advertisement and put them on the top of the |
| + // advertising queue and retry advertising them again. |
| + // Unless some other application come in between the process and steal the |
| + // advertisement quota, we can still advertise everything that's unregistered. |
| + for (auto const& it : map_to_advertisement_) { |
| + UnregisterAdvertisement(it.first); |
| + ad_rotator_->CutAdvertisementLine(it.first); |
| + } |
| + Advertise(); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::RotateAdvertisement() { |
| + for (auto const& it : map_to_advertisement_) { |
| + UnregisterAdvertisement(it.first); |
| + } |
| + |
| + Advertise(); |
| + base::TimeDelta wait_time = |
| + base::TimeDelta::FromSeconds(kAdvertisementRotationPeriodInSecs); |
| + |
| + task_runner_->PostDelayedTask( |
| + FROM_HERE, base::Bind(&BluetoothLowEnergyWeaveServer::RotateAdvertisement, |
| + weak_ptr_factory_.GetWeakPtr()), |
| + wait_time); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::Advertise() { |
| + std::pair<std::string, std::unique_ptr<Advertisement>> ad = |
| + ad_rotator_->GetNextAdvertisement(); |
| + adapter_->RegisterAdvertisement( |
| + std::move(ad.second), |
| + base::Bind(&BluetoothLowEnergyWeaveServer::RegisterAdvertisementSuccess, |
| + weak_ptr_factory_.GetWeakPtr(), ad.first), |
| + base::Bind(&BluetoothLowEnergyWeaveServer::RegisterAdvertisementError, |
| + weak_ptr_factory_.GetWeakPtr())); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UnregisterAdvertisement( |
| + std::string bluetooth_address) { |
| + DCHECK(IsAdvertising(bluetooth_address)); |
| + map_to_advertisement_[bluetooth_address]->Unregister( |
| + base::Bind(&BluetoothLowEnergyWeaveServer::UnregisterAdvertisementSuccess, |
| + weak_ptr_factory_.GetWeakPtr(), bluetooth_address), |
| + base::Bind(&BluetoothLowEnergyWeaveServer::UnregisterAdvertisementError, |
| + weak_ptr_factory_.GetWeakPtr(), bluetooth_address)); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UnregisterAdvertisementSuccess( |
| + std::string bluetooth_address) { |
| + map_to_advertisement_.erase(bluetooth_address); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::UnregisterAdvertisementError( |
| + std::string bluetooth_address, |
| + BluetoothAdvertisement::ErrorCode error) { |
| + DCHECK(error == |
| + BluetoothAdvertisement::ErrorCode::ERROR_ADVERTISEMENT_DOES_NOT_EXIST); |
| + // The only way the unregister could fail is if the advertisement is not |
| + // registered to begin with. Which means in our use case it should never fail. |
| + // Hence remove the advertisement from the map. |
| + map_to_advertisement_.erase(bluetooth_address); |
| +} |
| + |
| +void BluetoothLowEnergyWeaveServer::RegisterAdvertisementSuccess( |
| + std::string bluetooth_address, |
| + scoped_refptr<BluetoothAdvertisement> advertisement) { |
| + map_to_advertisement_[bluetooth_address] = advertisement; |
| + ad_rotator_->RotateAdvertisement(); |
| + // Keep trying to advertise until failures. |
| + Advertise(); |
| +} |
| + |
| +} // namespace weave |
| + |
| +} // namespace |