blob: 9a0c1d00d8105b1fca5db96d8e15f5e2d36b733b [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/connectivity/bluetooth/core/bt-host/l2cap/channel_configuration.h"
#include <endian.h>
#include <lib/fit/function.h>
#include <iterator>
#include <optional>
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/packet_view.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bt::l2cap::internal {
template <typename OptionT, typename PayloadT>
DynamicByteBuffer EncodeOption(PayloadT payload) {
DynamicByteBuffer buffer(OptionT::kEncodedSize);
MutablePacketView<ConfigurationOption> option(&buffer, OptionT::kPayloadLength);
option.mutable_header()->type = OptionT::kType;
option.mutable_header()->length = OptionT::kPayloadLength;
option.mutable_payload_data().WriteObj(payload);
return buffer;
}
// Compares length field in option header with expected option payload length for that option type.
// Returns true if lengths match, false otherwise.
template <typename OptionT>
bool CheckHeaderLengthField(PacketView<ConfigurationOption> option) {
if (option.header().length != OptionT::kPayloadLength) {
bt_log(WARN, "l2cap",
"received channel configuration option with incorrect length (type: %#.2x, "
"length: %hhu, expected length: %hhu)",
static_cast<uint8_t>(option.header().type), option.header().length,
OptionT::kPayloadLength);
return false;
}
return true;
}
// ChannelConfiguration::Reader implementation
bool ChannelConfiguration::ReadOptions(const ByteBuffer& options_payload) {
auto remaining_view = options_payload.view();
while (remaining_view.size() != 0) {
size_t bytes_read = ReadNextOption(remaining_view);
// Check for read failure
if (bytes_read == 0) {
return false;
}
remaining_view = remaining_view.view(bytes_read);
}
return true;
}
size_t ChannelConfiguration::ReadNextOption(const ByteBuffer& options) {
if (options.size() < sizeof(ConfigurationOption)) {
bt_log(WARN, "l2cap",
"tried to decode channel configuration option from buffer with invalid size (size: %lu)",
options.size());
return 0;
}
size_t remaining_size = options.size() - sizeof(ConfigurationOption);
PacketView<ConfigurationOption> option(&options, remaining_size);
// Check length against buffer bounds.
if (option.header().length > remaining_size) {
bt_log(WARN, "l2cap",
"decoded channel configuration option with length greater than remaining buffer size "
"(length: %hhu, remaining: %zu)",
option.header().length, remaining_size);
return 0;
}
switch (option.header().type) {
case OptionType::kMTU:
if (!CheckHeaderLengthField<MtuOption>(option)) {
return 0;
}
OnReadMtuOption(MtuOption(option.payload_data()));
return MtuOption::kEncodedSize;
case OptionType::kRetransmissionAndFlowControl:
if (!CheckHeaderLengthField<RetransmissionAndFlowControlOption>(option)) {
return 0;
}
OnReadRetransmissionAndFlowControlOption(
RetransmissionAndFlowControlOption(option.payload_data()));
return RetransmissionAndFlowControlOption::kEncodedSize;
case OptionType::kFlushTimeout:
if (!CheckHeaderLengthField<FlushTimeoutOption>(option)) {
return 0;
}
OnReadFlushTimeoutOption(FlushTimeoutOption(option.payload_data()));
return FlushTimeoutOption::kEncodedSize;
default:
bt_log(DEBUG, "l2cap", "decoded unsupported channel configuration option (type: %#.2x)",
static_cast<uint8_t>(option.header().type));
UnknownOption unknown_option(option.header().type, option.header().length,
option.payload_data());
size_t option_size = unknown_option.size();
OnReadUnknownOption(std::move(unknown_option));
return option_size;
}
}
// MtuOption implementation
ChannelConfiguration::MtuOption::MtuOption(const ByteBuffer& data_buf) {
auto& data = data_buf.As<MtuOptionPayload>();
mtu_ = letoh16(data.mtu);
}
DynamicByteBuffer ChannelConfiguration::MtuOption::Encode() const {
return EncodeOption<MtuOption>(MtuOptionPayload{htole16(mtu_)});
}
std::string ChannelConfiguration::MtuOption::ToString() const {
return fxl::StringPrintf("[type: MTU, mtu: %hu]", mtu_);
}
// RetransmissionAndFlowControlOption implementation
ChannelConfiguration::RetransmissionAndFlowControlOption
ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode() {
return RetransmissionAndFlowControlOption(ChannelMode::kBasic, 0, 0, 0, 0, 0);
}
ChannelConfiguration::RetransmissionAndFlowControlOption
ChannelConfiguration::RetransmissionAndFlowControlOption::MakeEnhancedRetransmissionMode(
uint8_t tx_window_size, uint8_t max_transmit, uint16_t rtx_timeout, uint16_t monitor_timeout,
uint16_t mps) {
return RetransmissionAndFlowControlOption(ChannelMode::kEnhancedRetransmission, tx_window_size,
max_transmit, rtx_timeout, monitor_timeout, mps);
}
ChannelConfiguration::RetransmissionAndFlowControlOption::RetransmissionAndFlowControlOption(
ChannelMode mode, uint8_t tx_window_size, uint8_t max_transmit, uint16_t rtx_timeout,
uint16_t monitor_timeout, uint16_t mps)
: mode_(mode),
tx_window_size_(tx_window_size),
max_transmit_(max_transmit),
rtx_timeout_(rtx_timeout),
monitor_timeout_(monitor_timeout),
mps_(mps) {}
ChannelConfiguration::RetransmissionAndFlowControlOption::RetransmissionAndFlowControlOption(
const ByteBuffer& data_buf) {
auto& option_payload = data_buf.As<RetransmissionAndFlowControlOptionPayload>();
mode_ = option_payload.mode;
tx_window_size_ = option_payload.tx_window_size;
max_transmit_ = option_payload.max_transmit;
rtx_timeout_ = letoh16(option_payload.rtx_timeout);
monitor_timeout_ = letoh16(option_payload.monitor_timeout);
mps_ = letoh16(option_payload.mps);
}
DynamicByteBuffer ChannelConfiguration::RetransmissionAndFlowControlOption::Encode() const {
RetransmissionAndFlowControlOptionPayload payload;
payload.mode = mode_;
payload.tx_window_size = tx_window_size_;
payload.max_transmit = max_transmit_;
payload.rtx_timeout = htole16(rtx_timeout_);
payload.monitor_timeout = htole16(monitor_timeout_);
payload.mps = mps_;
return EncodeOption<RetransmissionAndFlowControlOption>(payload);
}
std::string ChannelConfiguration::RetransmissionAndFlowControlOption::ToString() const {
return fxl::StringPrintf(
"[type: RtxFlowControl, mode: %hhu, tx window size: %hhu, max transmit: %hhu, rtx timeout: "
"%hu, monitor timeout: %hu, max pdu payload size: %hu]",
static_cast<uint8_t>(mode_), tx_window_size_, max_transmit_, rtx_timeout_, monitor_timeout_,
mps_);
}
// FlushTimeoutOption implementation
ChannelConfiguration::FlushTimeoutOption::FlushTimeoutOption(const ByteBuffer& data_buf) {
auto& option_payload = data_buf.As<FlushTimeoutOptionPayload>();
flush_timeout_ = letoh16(option_payload.flush_timeout);
}
DynamicByteBuffer ChannelConfiguration::FlushTimeoutOption::Encode() const {
FlushTimeoutOptionPayload payload;
payload.flush_timeout = htole16(flush_timeout_);
return EncodeOption<FlushTimeoutOption>(payload);
}
std::string ChannelConfiguration::FlushTimeoutOption::ToString() const {
return fxl::StringPrintf("[type: FlushTimeout, flush timeout: %hu]", flush_timeout_);
}
// UnknownOption implementation
ChannelConfiguration::UnknownOption::UnknownOption(OptionType type, uint8_t length,
const ByteBuffer& data)
: type_(type), payload_(BufferView(data, length)) {}
DynamicByteBuffer ChannelConfiguration::UnknownOption::Encode() const {
DynamicByteBuffer buffer(size());
MutablePacketView<ConfigurationOption> option(&buffer, payload_.size());
option.mutable_header()->type = type_;
option.mutable_header()->length = payload_.size();
// Raw data is already in little endian
option.mutable_payload_data().Write(payload_);
return buffer;
}
bool ChannelConfiguration::UnknownOption::IsHint() const {
// An option is a hint if its MSB is 1.
const uint8_t kMSBMask = 0x80;
return static_cast<uint8_t>(type_) & kMSBMask;
}
std::string ChannelConfiguration::UnknownOption::ToString() const {
return fxl::StringPrintf("[type: %#.2hhx, length: %zu]", type_, payload_.size());
}
// ChannelConfiguration implementation
ChannelConfiguration::ConfigurationOptions ChannelConfiguration::Options() const {
ConfigurationOptions options;
if (mtu_option_) {
options.push_back(ConfigurationOptionPtr(new MtuOption(*mtu_option_)));
}
if (retransmission_flow_control_option_) {
options.push_back(ConfigurationOptionPtr(
new RetransmissionAndFlowControlOption(*retransmission_flow_control_option_)));
}
if (flush_timeout_option_) {
options.push_back(ConfigurationOptionPtr(new FlushTimeoutOption(*flush_timeout_option_)));
}
return options;
}
std::string ChannelConfiguration::ToString() const {
std::string str("{");
std::vector<std::string> options;
if (mtu_option_) {
options.push_back(mtu_option_->ToString());
}
if (retransmission_flow_control_option_) {
options.push_back(retransmission_flow_control_option_->ToString());
}
if (flush_timeout_option_) {
options.push_back(flush_timeout_option_->ToString());
}
for (auto& option : unknown_options_) {
options.push_back(option.ToString());
}
for (auto it = options.begin(); it != options.end(); it++) {
str += *it;
if (it != options.end() - 1) {
str += ", ";
}
}
str += "}";
return str;
}
void ChannelConfiguration::Merge(ChannelConfiguration other) {
if (other.mtu_option_) {
mtu_option_ = other.mtu_option_;
}
if (other.retransmission_flow_control_option_) {
retransmission_flow_control_option_ = other.retransmission_flow_control_option_;
}
if (other.flush_timeout_option_) {
flush_timeout_option_ = other.flush_timeout_option_;
}
unknown_options_.insert(unknown_options_.end(),
std::make_move_iterator(other.unknown_options_.begin()),
std::make_move_iterator(other.unknown_options_.end()));
}
void ChannelConfiguration::OnReadUnknownOption(UnknownOption option) {
// Drop unknown hint options
if (!option.IsHint()) {
unknown_options_.push_back(std::move(option));
}
}
} // namespace bt::l2cap::internal