123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- // 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/cable/fido_ble_transaction.h"
- #include <utility>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "components/device_event_log/device_event_log.h"
- #include "device/fido/cable/fido_ble_connection.h"
- #include "device/fido/fido_constants.h"
- namespace device {
- FidoBleTransaction::FidoBleTransaction(FidoBleConnection* connection,
- uint16_t control_point_length)
- : connection_(connection), control_point_length_(control_point_length) {
- buffer_.reserve(control_point_length_);
- }
- FidoBleTransaction::~FidoBleTransaction() = default;
- void FidoBleTransaction::WriteRequestFrame(FidoBleFrame request_frame,
- FrameCallback callback) {
- if (control_point_length_ < 3u) {
- FIDO_LOG(DEBUG) << "Control Point Length is too short: "
- << control_point_length_;
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
- return;
- }
- DCHECK(!request_frame_ && callback_.is_null());
- request_frame_ = std::move(request_frame);
- callback_ = std::move(callback);
- FidoBleFrameInitializationFragment request_init_fragment;
- std::tie(request_init_fragment, request_cont_fragments_) =
- request_frame_->ToFragments(control_point_length_);
- WriteRequestFragment(request_init_fragment);
- }
- void FidoBleTransaction::WriteRequestFragment(
- const FidoBleFrameFragment& fragment) {
- buffer_.clear();
- fragment.Serialize(&buffer_);
- DCHECK(!has_pending_request_fragment_write_);
- has_pending_request_fragment_write_ = true;
- FIDO_LOG(DEBUG) << "Writing request fragment: " +
- base::HexEncode(buffer_.data(), buffer_.size());
- // A weak pointer is required, since this call might time out. If that
- // happens, the current FidoBleTransaction could be destroyed.
- connection_->WriteControlPoint(
- buffer_, base::BindOnce(&FidoBleTransaction::OnRequestFragmentWritten,
- weak_factory_.GetWeakPtr()));
- // WriteRequestFragment() expects an invocation of OnRequestFragmentWritten()
- // soon after.
- StartTimeout();
- }
- static void WriteCancel(FidoBleConnection* connection) {
- FIDO_LOG(DEBUG) << "Writing control point 'Cancel'";
- connection->WriteControlPoint(
- {static_cast<uint8_t>(FidoBleDeviceCommand::kCancel), 0, 0},
- base::DoNothing());
- }
- void FidoBleTransaction::OnRequestFragmentWritten(bool success) {
- DCHECK(has_pending_request_fragment_write_);
- has_pending_request_fragment_write_ = false;
- StopTimeout();
- if (!success) {
- OnError(absl::nullopt);
- return;
- }
- if (!request_cont_fragments_.empty()) {
- auto next_request_fragment = std::move(request_cont_fragments_.front());
- request_cont_fragments_.pop();
- WriteRequestFragment(next_request_fragment);
- return;
- }
- if (cancel_pending_) {
- cancel_pending_ = false;
- cancel_sent_ = true;
- WriteCancel(connection_);
- }
- // The transaction wrote the full request frame. It is possible that the full
- // response frame was already received, at which point we process it and run
- // the completim callback.
- if (response_frame_assembler_ && response_frame_assembler_->IsDone()) {
- ProcessResponseFrame();
- return;
- }
- // Otherwise, a response should follow soon after.
- StartTimeout();
- }
- void FidoBleTransaction::OnResponseFragment(std::vector<uint8_t> data) {
- StopTimeout();
- if (!response_frame_assembler_) {
- FidoBleFrameInitializationFragment fragment;
- if (!FidoBleFrameInitializationFragment::Parse(data, &fragment)) {
- FIDO_LOG(ERROR) << "Malformed Frame Initialization Fragment";
- OnError(absl::nullopt);
- return;
- }
- response_frame_assembler_.emplace(fragment);
- } else {
- FidoBleFrameContinuationFragment fragment;
- if (!FidoBleFrameContinuationFragment::Parse(data, &fragment) ||
- !response_frame_assembler_->AddFragment(fragment)) {
- FIDO_LOG(ERROR) << "Malformed Frame Continuation Fragment";
- OnError(absl::nullopt);
- return;
- }
- }
- if (!response_frame_assembler_->IsDone()) {
- // Expect the next reponse fragment to arrive soon.
- StartTimeout();
- return;
- }
- // It is possible to receive the last response fragment before the write of
- // the last request fragment has been acknowledged. If this is the case, do
- // not run the completion callback yet.
- // It is OK to process keep alive frames before the request frame is
- // acknowledged.
- if (!has_pending_request_fragment_write_ ||
- response_frame_assembler_->GetFrame()->command() ==
- FidoBleDeviceCommand::kKeepAlive) {
- ProcessResponseFrame();
- }
- }
- void FidoBleTransaction::Cancel() {
- if (cancel_sent_) {
- return;
- }
- if (has_pending_request_fragment_write_) {
- // A mesasge is still being written. Signal that the cancelation should be
- // written once complete.
- cancel_pending_ = true;
- } else {
- cancel_sent_ = true;
- WriteCancel(connection_);
- }
- }
- void FidoBleTransaction::ProcessResponseFrame() {
- DCHECK(response_frame_assembler_ && response_frame_assembler_->IsDone());
- auto response_frame = std::move(*response_frame_assembler_->GetFrame());
- response_frame_assembler_.reset();
- DCHECK(request_frame_.has_value());
- if (response_frame.command() == request_frame_->command()) {
- request_frame_.reset();
- std::move(callback_).Run(std::move(response_frame));
- return;
- }
- if (response_frame.command() == FidoBleDeviceCommand::kKeepAlive) {
- if (!response_frame.IsValid()) {
- FIDO_LOG(ERROR) << "Got invalid KeepAlive Command.";
- OnError(absl::nullopt);
- return;
- }
- FIDO_LOG(DEBUG) << "CMD_KEEPALIVE: "
- << static_cast<int>(response_frame.GetKeepaliveCode());
- // Expect another reponse frame soon.
- StartTimeout();
- return;
- }
- if (response_frame.command() == FidoBleDeviceCommand::kError) {
- if (!response_frame.IsValid()) {
- FIDO_LOG(ERROR) << "Got invald Error Command.";
- OnError(absl::nullopt);
- return;
- }
- FIDO_LOG(ERROR) << "CMD_ERROR: "
- << static_cast<int>(response_frame.GetErrorCode());
- OnError(std::move(response_frame));
- return;
- }
- FIDO_LOG(ERROR) << "Got unexpected Command: "
- << static_cast<int>(response_frame.command());
- OnError(absl::nullopt);
- }
- void FidoBleTransaction::StartTimeout() {
- timer_.Start(FROM_HERE, kDeviceTimeout,
- base::BindOnce(&FidoBleTransaction::OnError,
- base::Unretained(this), absl::nullopt));
- }
- void FidoBleTransaction::StopTimeout() {
- timer_.Stop();
- }
- void FidoBleTransaction::OnError(absl::optional<FidoBleFrame> response_frame) {
- request_frame_.reset();
- request_cont_fragments_ = base::queue<FidoBleFrameContinuationFragment>();
- response_frame_assembler_.reset();
- // |callback_| might have been run due to a previous error.
- if (callback_)
- std::move(callback_).Run(std::move(response_frame));
- }
- } // namespace device
|