blob: e38e3cf95118765f4baff10dca05c8623488ddbd [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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_frames.h"
#include <algorithm>
#include <limits>
#include <tuple>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
FidoBleFrame::FidoBleFrame() = default;
FidoBleFrame::FidoBleFrame(FidoBleDeviceCommand command,
std::vector<uint8_t> data)
: command_(command), data_(std::move(data)) {}
FidoBleFrame::FidoBleFrame(const FidoBleFrame&) = default;
FidoBleFrame& FidoBleFrame::operator=(const FidoBleFrame&) = default;
FidoBleFrame::FidoBleFrame(FidoBleFrame&&) = default;
FidoBleFrame& FidoBleFrame::operator=(FidoBleFrame&&) = default;
FidoBleFrame::~FidoBleFrame() = default;
bool FidoBleFrame::IsValid() const {
switch (command_) {
case FidoBleDeviceCommand::kPing:
case FidoBleDeviceCommand::kMsg:
case FidoBleDeviceCommand::kCancel:
case FidoBleDeviceCommand::kControl:
return true;
case FidoBleDeviceCommand::kKeepAlive:
case FidoBleDeviceCommand::kError:
return data_.size() == 1;
}
NOTREACHED();
return false;
}
FidoBleFrame::KeepaliveCode FidoBleFrame::GetKeepaliveCode() const {
DCHECK_EQ(command_, FidoBleDeviceCommand::kKeepAlive);
DCHECK_EQ(data_.size(), 1u);
return static_cast<KeepaliveCode>(data_[0]);
}
FidoBleFrame::ErrorCode FidoBleFrame::GetErrorCode() const {
DCHECK_EQ(command_, FidoBleDeviceCommand::kError);
DCHECK_EQ(data_.size(), 1u);
return static_cast<ErrorCode>(data_[0]);
}
std::pair<FidoBleFrameInitializationFragment,
base::queue<FidoBleFrameContinuationFragment>>
FidoBleFrame::ToFragments(size_t max_fragment_size) const {
DCHECK_LE(data_.size(), std::numeric_limits<uint16_t>::max());
DCHECK_GE(max_fragment_size, 3u);
// Cast is necessary to ignore too high bits.
auto data_view =
base::make_span(data_.data(), static_cast<uint16_t>(data_.size()));
// Subtract 3 to account for CMD, HLEN and LLEN bytes.
const size_t init_fragment_size =
std::min(max_fragment_size - 3, data_view.size());
FidoBleFrameInitializationFragment initial_fragment(
command_, data_view.size(), data_view.first(init_fragment_size));
base::queue<FidoBleFrameContinuationFragment> other_fragments;
data_view = data_view.subspan(init_fragment_size);
// Subtract 1 to account for SEQ byte.
for (auto cont_data :
fido_parsing_utils::SplitSpan(data_view, max_fragment_size - 1)) {
// High bit must stay cleared.
other_fragments.emplace(cont_data, other_fragments.size() & 0x7F);
}
return {initial_fragment, std::move(other_fragments)};
}
bool operator==(const FidoBleFrame& lhs, const FidoBleFrame& rhs) {
return std::forward_as_tuple(lhs.command(), lhs.data()) ==
std::forward_as_tuple(rhs.command(), rhs.data());
}
FidoBleFrameFragment::FidoBleFrameFragment() = default;
FidoBleFrameFragment::FidoBleFrameFragment(const FidoBleFrameFragment& frame) =
default;
FidoBleFrameFragment::~FidoBleFrameFragment() = default;
FidoBleFrameFragment::FidoBleFrameFragment(base::span<const uint8_t> fragment)
: fragment_(fragment) {}
bool FidoBleFrameInitializationFragment::Parse(
base::span<const uint8_t> data,
FidoBleFrameInitializationFragment* fragment) {
if (data.size() < 3)
return false;
const auto command = static_cast<FidoBleDeviceCommand>(data[0]);
const uint16_t data_length = (static_cast<uint16_t>(data[1]) << 8) + data[2];
if (static_cast<size_t>(data_length) + 3 < data.size())
return false;
*fragment =
FidoBleFrameInitializationFragment(command, data_length, data.subspan(3));
return true;
}
size_t FidoBleFrameInitializationFragment::Serialize(
std::vector<uint8_t>* buffer) const {
buffer->push_back(static_cast<uint8_t>(command_));
buffer->push_back((data_length_ >> 8) & 0xFF);
buffer->push_back(data_length_ & 0xFF);
buffer->insert(buffer->end(), fragment().begin(), fragment().end());
return fragment().size() + 3;
}
bool FidoBleFrameContinuationFragment::Parse(
base::span<const uint8_t> data,
FidoBleFrameContinuationFragment* fragment) {
if (data.empty())
return false;
const uint8_t sequence = data[0];
*fragment = FidoBleFrameContinuationFragment(data.subspan(1), sequence);
return true;
}
size_t FidoBleFrameContinuationFragment::Serialize(
std::vector<uint8_t>* buffer) const {
buffer->push_back(sequence_);
buffer->insert(buffer->end(), fragment().begin(), fragment().end());
return fragment().size() + 1;
}
FidoBleFrameAssembler::FidoBleFrameAssembler(
const FidoBleFrameInitializationFragment& fragment)
: data_length_(fragment.data_length()),
frame_(fragment.command(),
std::vector<uint8_t>(fragment.fragment().begin(),
fragment.fragment().end())) {}
bool FidoBleFrameAssembler::AddFragment(
const FidoBleFrameContinuationFragment& fragment) {
if (fragment.sequence() != sequence_number_)
return false;
sequence_number_ = (sequence_number_ + 1) & 0x7F;
if (static_cast<size_t>(data_length_) <
frame_.data().size() + fragment.fragment().size()) {
return false;
}
frame_.data().insert(frame_.data().end(), fragment.fragment().begin(),
fragment.fragment().end());
return true;
}
bool FidoBleFrameAssembler::IsDone() const {
return frame_.data().size() == data_length_;
}
FidoBleFrame* FidoBleFrameAssembler::GetFrame() {
return IsDone() ? &frame_ : nullptr;
}
FidoBleFrameAssembler::~FidoBleFrameAssembler() = default;
} // namespace device