blob: ae49580fd6859ca05caaa61c4153acebf90b71c0 [file] [log] [blame]
// 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/fido/fido_ble_device.h"
#include "base/bind.h"
#include "base/strings/string_piece.h"
#include "components/apdu/apdu_response.h"
#include "device/fido/fido_ble_frames.h"
#include "device/fido/fido_constants.h"
namespace device {
FidoBleDevice::FidoBleDevice(std::string address) : weak_factory_(this) {
connection_ = std::make_unique<FidoBleConnection>(
std::move(address),
base::BindRepeating(&FidoBleDevice::OnConnectionStatus,
base::Unretained(this)),
base::BindRepeating(&FidoBleDevice::OnStatusMessage,
base::Unretained(this)));
}
FidoBleDevice::FidoBleDevice(std::unique_ptr<FidoBleConnection> connection)
: connection_(std::move(connection)), weak_factory_(this) {}
FidoBleDevice::~FidoBleDevice() = default;
void FidoBleDevice::Connect() {
if (state_ != State::kInit)
return;
StartTimeout();
state_ = State::kBusy;
connection_->Connect();
}
void FidoBleDevice::SendPing(std::vector<uint8_t> data,
DeviceCallback callback) {
AddToPendingFrames(FidoBleDeviceCommand::kPing, std::move(data),
std::move(callback));
}
// static
std::string FidoBleDevice::GetId(base::StringPiece address) {
return std::string("ble:").append(address.begin(), address.end());
}
void FidoBleDevice::TryWink(WinkCallback callback) {
// U2F over BLE does not support winking.
std::move(callback).Run();
}
void FidoBleDevice::Cancel() {
if (state_ != State::kReady && state_ != State::kBusy)
return;
AddToPendingFrames(FidoBleDeviceCommand::kCancel, std::vector<uint8_t>(),
base::DoNothing());
}
std::string FidoBleDevice::GetId() const {
return GetId(connection_->address());
}
FidoBleConnection::ConnectionStatusCallback
FidoBleDevice::GetConnectionStatusCallbackForTesting() {
return base::BindRepeating(&FidoBleDevice::OnConnectionStatus,
base::Unretained(this));
}
FidoBleConnection::ReadCallback FidoBleDevice::GetReadCallbackForTesting() {
return base::BindRepeating(&FidoBleDevice::OnStatusMessage,
base::Unretained(this));
}
void FidoBleDevice::DeviceTransact(std::vector<uint8_t> command,
DeviceCallback callback) {
AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command),
std::move(callback));
}
base::WeakPtr<FidoDevice> FidoBleDevice::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void FidoBleDevice::OnResponseFrame(FrameCallback callback,
base::Optional<FidoBleFrame> frame) {
// The request is done, time to reset |transaction_|.
ResetTransaction();
state_ = frame ? State::kReady : State::kDeviceError;
auto self = GetWeakPtr();
std::move(callback).Run(std::move(frame));
// Executing callbacks may free |this|. Check |self| first.
if (self)
Transition();
}
void FidoBleDevice::ResetTransaction() {
transaction_.reset();
}
void FidoBleDevice::Transition() {
switch (state_) {
case State::kInit:
Connect();
break;
case State::kConnected:
StartTimeout();
state_ = State::kBusy;
connection_->ReadControlPointLength(base::BindOnce(
&FidoBleDevice::OnReadControlPointLength, base::Unretained(this)));
break;
case State::kReady:
if (!pending_frames_.empty()) {
FidoBleFrame frame;
FrameCallback callback;
std::tie(frame, callback) = std::move(pending_frames_.front());
pending_frames_.pop();
SendRequestFrame(std::move(frame), std::move(callback));
}
break;
case State::kBusy:
break;
case State::kDeviceError:
auto self = GetWeakPtr();
// Executing callbacks may free |this|. Check |self| first.
while (self && !pending_frames_.empty()) {
// Respond to any pending frames.
FrameCallback cb = std::move(pending_frames_.front().second);
pending_frames_.pop();
std::move(cb).Run(base::nullopt);
}
break;
}
}
void FidoBleDevice::AddToPendingFrames(FidoBleDeviceCommand cmd,
std::vector<uint8_t> request,
DeviceCallback callback) {
pending_frames_.emplace(
FidoBleFrame(cmd, std::move(request)),
base::BindOnce(
[](DeviceCallback callback, base::Optional<FidoBleFrame> frame) {
std::move(callback).Run(frame ? base::make_optional(frame->data())
: base::nullopt);
},
std::move(callback)));
Transition();
}
void FidoBleDevice::OnConnectionStatus(bool success) {
StopTimeout();
state_ = success ? State::kConnected : State::kDeviceError;
Transition();
}
void FidoBleDevice::OnReadControlPointLength(base::Optional<uint16_t> length) {
StopTimeout();
if (length) {
control_point_length_ = *length;
state_ = State::kReady;
} else {
state_ = State::kDeviceError;
}
Transition();
}
void FidoBleDevice::OnStatusMessage(std::vector<uint8_t> data) {
if (transaction_)
transaction_->OnResponseFragment(std::move(data));
}
void FidoBleDevice::SendRequestFrame(FidoBleFrame frame,
FrameCallback callback) {
state_ = State::kBusy;
transaction_.emplace(connection_.get(), control_point_length_);
transaction_->WriteRequestFrame(
std::move(frame),
base::BindOnce(&FidoBleDevice::OnResponseFrame, base::Unretained(this),
std::move(callback)));
}
void FidoBleDevice::StartTimeout() {
timer_.Start(FROM_HERE, kDeviceTimeout, this, &FidoBleDevice::OnTimeout);
}
void FidoBleDevice::StopTimeout() {
timer_.Stop();
}
void FidoBleDevice::OnTimeout() {
state_ = State::kDeviceError;
}
} // namespace device