blob: 1cb5902c17fa7d18aa96484ee43d043b34c98cc7 [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/u2f/u2f_ble_transaction.h"
#include <utility>
#include "device/u2f/u2f_ble_connection.h"
#include "device/u2f/u2f_device.h"
namespace device {
U2fBleTransaction::U2fBleTransaction(U2fBleConnection* connection,
uint16_t control_point_length)
: connection_(connection),
control_point_length_(control_point_length),
weak_factory_(this) {
buffer_.reserve(control_point_length_);
}
U2fBleTransaction::~U2fBleTransaction() = default;
void U2fBleTransaction::WriteRequestFrame(U2fBleFrame request_frame,
FrameCallback callback) {
DCHECK(!request_frame_ && callback_.is_null());
request_frame_ = std::move(request_frame);
callback_ = std::move(callback);
U2fBleFrameInitializationFragment request_init_fragment;
std::tie(request_init_fragment, request_cont_fragments_) =
request_frame_->ToFragments(control_point_length_);
WriteRequestFragment(request_init_fragment);
}
void U2fBleTransaction::WriteRequestFragment(
const U2fBleFrameFragment& fragment) {
buffer_.clear();
fragment.Serialize(&buffer_);
// A weak pointer is required, since this call might time out. If that
// happens, the current U2fBleTransaction could be destroyed.
connection_->WriteControlPoint(
buffer_, base::BindOnce(&U2fBleTransaction::OnRequestFragmentWritten,
weak_factory_.GetWeakPtr()));
// WriteRequestFragment() expects an invocation of OnRequestFragmentWritten()
// soon after.
StartTimeout();
}
void U2fBleTransaction::OnRequestFragmentWritten(bool success) {
StopTimeout();
if (!success) {
OnError();
return;
}
if (request_cont_fragments_.empty()) {
// The transaction wrote the full request frame. A response should follow
// soon after.
StartTimeout();
return;
}
auto next_request_fragment = std::move(request_cont_fragments_.front());
request_cont_fragments_.pop();
WriteRequestFragment(next_request_fragment);
}
void U2fBleTransaction::OnResponseFragment(std::vector<uint8_t> data) {
StopTimeout();
if (!response_frame_assembler_) {
U2fBleFrameInitializationFragment fragment;
if (!U2fBleFrameInitializationFragment::Parse(data, &fragment)) {
DLOG(ERROR) << "Malformed Frame Initialization Fragment";
OnError();
return;
}
response_frame_assembler_.emplace(fragment);
} else {
U2fBleFrameContinuationFragment fragment;
if (!U2fBleFrameContinuationFragment::Parse(data, &fragment)) {
DLOG(ERROR) << "Malformed Frame Continuation Fragment";
OnError();
return;
}
response_frame_assembler_->AddFragment(fragment);
}
if (!response_frame_assembler_->IsDone()) {
// Expect the next reponse fragment to arrive soon.
StartTimeout();
return;
}
U2fBleFrame frame = std::move(*response_frame_assembler_->GetFrame());
response_frame_assembler_.reset();
ProcessResponseFrame(std::move(frame));
}
void U2fBleTransaction::ProcessResponseFrame(U2fBleFrame response_frame) {
if (response_frame.command() == request_frame_->command()) {
request_frame_.reset();
std::move(callback_).Run(std::move(response_frame));
return;
}
if (response_frame.command() == U2fCommandType::CMD_KEEPALIVE) {
DVLOG(2) << "CMD_KEEPALIVE: "
<< static_cast<uint8_t>(response_frame.GetKeepaliveCode());
// Expect another reponse frame soon.
StartTimeout();
return;
}
DCHECK_EQ(response_frame.command(), U2fCommandType::CMD_ERROR);
DLOG(ERROR) << "CMD_ERROR: "
<< static_cast<uint8_t>(response_frame.GetErrorCode());
OnError();
}
void U2fBleTransaction::StartTimeout() {
timer_.Start(FROM_HERE, U2fDevice::kDeviceTimeout, this,
&U2fBleTransaction::OnError);
}
void U2fBleTransaction::StopTimeout() {
timer_.Stop();
}
void U2fBleTransaction::OnError() {
request_frame_.reset();
request_cont_fragments_ = {};
response_frame_assembler_.reset();
std::move(callback_).Run(base::nullopt);
}
} // namespace device