blob: 98841d494852f25333e4a11689736aeef839e9a0 [file] [log] [blame]
// Copyright 2018 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 "frames.h"
#include "garnet/drivers/bluetooth/lib/common/log.h"
#include "garnet/drivers/bluetooth/lib/common/slab_allocator.h"
#include "rfcomm.h"
namespace btlib {
namespace rfcomm {
namespace {
// FCS utilites. These come from GSM Annex B.
// CRC table for calculating FCS. GSM B.3.5.
const uint8_t crctable[] = {
0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C,
0x09, 0x98, 0xEA, 0x7B, 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69,
0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, 0x38, 0xA9, 0xDB, 0x4A,
0x3F, 0xAE, 0xDC, 0x4D, 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43,
0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, 0x2A, 0xBB, 0xC9, 0x58,
0x2D, 0xBC, 0xCE, 0x5F, 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05,
0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, 0x6C, 0xFD, 0x8F, 0x1E,
0x6B, 0xFA, 0x88, 0x19, 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17,
0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, 0x46, 0xD7, 0xA5, 0x34,
0x41, 0xD0, 0xA2, 0x33, 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21,
0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, 0xE0, 0x71, 0x03, 0x92,
0xE7, 0x76, 0x04, 0x95, 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B,
0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, 0xF2, 0x63, 0x11, 0x80,
0xF5, 0x64, 0x16, 0x87, 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD,
0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, 0xC4, 0x55, 0x27, 0xB6,
0xC3, 0x52, 0x20, 0xB1, 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF,
0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, 0x9E, 0x0F, 0x7D, 0xEC,
0x99, 0x08, 0x7A, 0xEB, 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9,
0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, 0xA8, 0x39, 0x4B, 0xDA,
0xAF, 0x3E, 0x4C, 0xDD, 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3,
0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, 0xBA, 0x2B, 0x59, 0xC8,
0xBD, 0x2C, 0x5E, 0xCF};
// FCS calculation function from GSM B.3.3.
uint8_t CalculateFCS(const common::ByteBuffer& p) {
uint8_t fcs = 0xFF;
size_t offset = 0;
size_t len = p.size();
while (len--) {
fcs = crctable[fcs ^ p[offset++]];
}
/* Ones compliment */
return (0xFF - fcs);
}
// FCS checking function from GSM B.3.4.
bool CheckFCS(uint8_t received_fcs, const common::ByteBuffer& p) {
uint8_t fcs = 0xFF;
size_t offset = 0;
size_t len = p.size();
while (len--) {
fcs = crctable[fcs ^ p[offset++]];
}
fcs = crctable[fcs ^ received_fcs];
return fcs == 0xCF;
}
constexpr uint8_t kControlMask = 0b11101111;
constexpr size_t kCRShift = 1;
constexpr size_t kPFShift = 4;
constexpr uint8_t kCRMask = 1 << kCRShift;
constexpr uint8_t kPFMask = 1 << kPFShift;
constexpr uint8_t kEAMask = 0b00000001;
constexpr size_t kAddressIndex = 0;
constexpr size_t kControlIndex = 1;
constexpr size_t kLengthIndex = 2;
constexpr size_t kLengthFirstOctetShift = 1;
constexpr size_t kLengthSecondOctetShift = 7;
constexpr size_t kDLCIShift = 2;
// Address field, control field, length field, FCS field. See RFCOMM 5.2.6.
constexpr size_t kMinimumFrameSize = 4;
// The length field in RFCOMM frames and multiplexer control commands can be
// extended from one octet to two. This function checks whether two octets are
// needed.
bool TwoOctetLength(size_t length) { return length > kMaxSingleOctetLength; }
// Logic for determining whether a frame will have a credit octet based on
// characteristics of the frame and of the Session forming the frame. See RFCOMM
// 6.5.2.
//
// |credit_based_flow| indicates whether credit-based flow control is turned on
// in this RFCOMM session.
inline bool FrameHasCreditOctet(bool credit_based_flow, FrameType frame_type,
bool poll_final) {
return credit_based_flow &&
(frame_type == FrameType::kUnnumberedInfoHeaderCheck) &&
poll_final == true;
}
} // namespace
Frame::Frame(Role role, CommandResponse command_response, DLCI dlci,
uint8_t control, bool poll_final)
: role_(role),
command_response_(command_response),
dlci_(dlci),
control_(control),
poll_final_(poll_final) {}
// To parse an RFCOMM frame, we must parse all the way until the end to check
// the FCS octet. This means there wouldn't be much benefit in adding Parse
// functions for each Frame subclass, as Frame::Parse will already parse all of
// the needed information.
std::unique_ptr<Frame> Frame::Parse(bool credit_based_flow, Role role,
const common::ByteBuffer& buffer) {
if (buffer.size() < kMinimumFrameSize) {
bt_log(TRACE, "rfcomm",
"buffer size of %zu is less than minimum frame size (%zu)",
buffer.size(), kMinimumFrameSize);
return nullptr;
}
// Parse the frame type and DLCI first and then determine if we can parse the
// frame. We can parse the frame if the multiplexer is started, or if the
// multiplexer isn't started and it's a valid pre-mux-startup frame.
DLCI dlci = buffer[kAddressIndex] >> kDLCIShift;
FrameType frame_type = (FrameType)(buffer[kControlIndex] & kControlMask);
if (!IsMultiplexerStarted(role) && !IsMuxStartupFrame(frame_type, dlci)) {
bt_log(TRACE, "rfcomm", "frame type %u before mux start",
static_cast<uint8_t>(frame_type));
return nullptr;
}
bool cr_bit = buffer[kAddressIndex] & kCRMask;
// See table 1 in 5.2.1.2, which describes exactly how the C/R bit is
// interpreted.
CommandResponse command_response;
if (IsMultiplexerStarted(role)) {
// See table 1 in 5.2.1.2, which describes exactly how the C/R bit is
// interpreted.
command_response = (role == Role::kInitiator && cr_bit) ||
(role == Role::kResponder && !cr_bit)
? CommandResponse::kCommand
: CommandResponse::kResponse;
} else {
// This is not defined explicitly in the spec. If the multiplexer isn't
// started, we assume the frame has the role the sender would take if
// multiplexer startup succeeds.
switch (frame_type) {
case FrameType::kSetAsynchronousBalancedMode:
command_response =
cr_bit ? CommandResponse::kCommand : CommandResponse::kResponse;
break;
case FrameType::kDisconnectedMode:
case FrameType::kUnnumberedAcknowledgement:
command_response =
cr_bit ? CommandResponse::kResponse : CommandResponse::kCommand;
break;
default:
// TODO(armansito): Add a unit test for this case.
bt_log(ERROR, "rfcomm", "malformed frame type: %u",
static_cast<unsigned int>(frame_type));
return nullptr;
}
}
bool poll_final = buffer[kControlIndex] & kPFMask;
InformationLength length = buffer[kLengthIndex] >> kLengthFirstOctetShift;
bool two_octet_length = !(buffer[kLengthIndex] & kEAMask);
if (two_octet_length) {
if (buffer.size() <= kLengthIndex + 1) {
bt_log(WARN, "rfcomm", "buffer ended unexpectedly while parsing length");
return nullptr;
}
length |= buffer[kLengthIndex + 1] << kLengthSecondOctetShift;
}
uint8_t credits = 0;
bool has_credit_octet =
FrameHasCreditOctet(credit_based_flow, frame_type, poll_final);
if (has_credit_octet) {
size_t credit_index = kLengthIndex + (two_octet_length ? 2 : 1);
if (buffer.size() <= credit_index) {
bt_log(WARN, "rfcomm", "buffer ended unexpectedly while parsing credits");
return nullptr;
}
credits = buffer[credit_index];
}
// Address and control, one or two length octets, plus a possible credits
// octet.
size_t header_size =
2 + (two_octet_length ? 2 : 1) + (has_credit_octet ? 1 : 0);
// Check the FCS before we do any allocation or copying.
size_t fcs_index = header_size + length;
if (buffer.size() <= fcs_index) {
bt_log(WARN, "rfcomm", "buffer ended unexpectedly while parsing FCS");
return nullptr;
}
uint8_t fcs = buffer[fcs_index];
size_t num_octets =
frame_type == FrameType::kUnnumberedInfoHeaderCheck ? 2 : 3;
if (!CheckFCS(fcs, buffer.view(0, num_octets))) {
bt_log(WARN, "rfcomm", "FCS check failed");
return nullptr;
}
if (frame_type == FrameType::kUnnumberedInfoHeaderCheck) {
// Determine whether to parse a MuxControlFrame or a UserDataFrame based on
// the DLCI.
if (dlci == kMuxControlDLCI) {
std::unique_ptr<MuxCommand> mux_command =
MuxCommand::Parse(buffer.view(header_size, length));
if (!mux_command) {
bt_log(WARN, "rfcomm", "unable to parse mux command");
return nullptr;
}
auto mux_command_frame = std::make_unique<MuxCommandFrame>(
role, credit_based_flow, std::move(mux_command));
mux_command_frame->set_credits(credits);
return mux_command_frame;
} else if (IsUserDLCI(dlci)) {
common::MutableByteBufferPtr information;
if (length > 0) {
information = common::NewSlabBuffer(length);
buffer.Copy(information.get(), header_size, length);
}
auto user_data_frame = std::make_unique<UserDataFrame>(
role, credit_based_flow, dlci, std::move(information));
user_data_frame->set_credits(credits);
return user_data_frame;
} else {
bt_log(WARN, "rfcomm",
"Parsed DLCI %u is not a valid multiplexer or user data channel",
dlci);
return nullptr;
}
} else {
return std::make_unique<Frame>(role, command_response, dlci,
uint8_t(frame_type), poll_final);
}
}
// Write this RFCOMM frame into a buffer.
void Frame::Write(common::MutableBufferView buffer) const {
ZX_DEBUG_ASSERT(buffer.size() >= header_size());
// Writes address, control, and length octets.
WriteHeader(buffer);
// Begin writing after header.
size_t offset = header_size();
ZX_DEBUG_ASSERT(buffer.size() > offset);
// FCS is calculated on address and control fields for UIH frames, and
// calculated on address, control, and length fields for all other frames.
size_t num_octets =
(FrameType)control_ == FrameType::kUnnumberedInfoHeaderCheck ? 2 : 3;
uint8_t fcs = CalculateFCS(buffer.view(0, num_octets));
buffer[offset] = fcs;
}
void Frame::WriteHeader(common::MutableBufferView buffer) const {
ZX_DEBUG_ASSERT(buffer.size() >= header_size());
size_t offset = 0;
uint8_t address = dlci_ << kDLCIShift;
// Set EA bit (DLCI/address is always 1 octet in length)
address |= kEAMask;
// Set C/R bit
uint8_t command_response_bit;
if (role_ == Role::kInitiator || role_ == Role::kResponder) {
// GSM Table 1.
command_response_bit = (role_ == Role::kInitiator &&
command_response_ == CommandResponse::kCommand) ||
(role_ == Role::kResponder &&
command_response_ == CommandResponse::kResponse);
} else if (role_ == Role::kUnassigned || role_ == Role::kNegotiating) {
// There are only specific frames which can be encoded when the multiplexer
// is not yet started.
ZX_DEBUG_ASSERT(IsMuxStartupFrame(FrameType(control_), dlci_));
// TODO(gusss): the spec does not say how we encode the C/R bit when the
// multiplexer is not yet started.
// Set the C/R bit as if startup succeeds (see GSM table 1)
// - SABM (command) frames become initiator, C/R = 1
// - UA (response) frames become responder, C/R = 1
// - DM (response) frames come from would-be responder, C/R = 1
command_response_bit = 1;
} else {
ZX_PANIC("unexpected role while writing frame: %u",
static_cast<unsigned int>(role_));
return;
}
address &= ~kCRMask;
address |= command_response_bit << kCRShift;
buffer[offset] = address;
++offset;
uint8_t control = control_;
control &= ~kPFMask;
control |= (poll_final_ ? 1 : 0) << kPFShift;
buffer[offset] = control;
++offset;
uint8_t length_octet = 0;
const InformationLength length_val = length();
bool two_octet_length = TwoOctetLength(length_val);
// Set E/A bit.
length_octet |= two_octet_length ? 0 : 1;
length_octet |= length_val << kLengthFirstOctetShift;
buffer[offset] = length_octet;
++offset;
if (two_octet_length) {
length_octet = length_val >> kLengthSecondOctetShift;
buffer[offset] = length_octet;
++offset;
}
}
size_t Frame::header_size() const {
// Address, control, and one or two length octets.
return sizeof(uint8_t) + sizeof(uint8_t) +
sizeof(uint8_t) * (TwoOctetLength(length()) ? 2 : 1);
}
MuxCommandFrame* Frame::AsMuxCommandFrame() {
ZX_DEBUG_ASSERT(dlci() == 0);
return static_cast<MuxCommandFrame*>(this);
}
UserDataFrame* Frame::AsUserDataFrame() {
ZX_DEBUG_ASSERT(IsUserDLCI(dlci()));
return static_cast<UserDataFrame*>(this);
}
UnnumberedInfoHeaderCheckFrame* Frame::AsUnnumberedInfoHeaderCheckFrame() {
FrameType type = static_cast<FrameType>(control());
ZX_DEBUG_ASSERT(type == FrameType::kUnnumberedInfoHeaderCheck);
return static_cast<UnnumberedInfoHeaderCheckFrame*>(this);
}
DisconnectCommand::DisconnectCommand(Role role, DLCI dlci)
: Frame(role,
// DISC frames are always commands.
CommandResponse::kCommand, dlci, uint8_t(FrameType::kDisconnect),
// Poll bit is always 1. See GSM 5.4.4.1.
true) {}
SetAsynchronousBalancedModeCommand::SetAsynchronousBalancedModeCommand(
Role role, DLCI dlci)
: Frame(role,
// SABM frames are always commands.
CommandResponse::kCommand, dlci,
uint8_t(FrameType::kSetAsynchronousBalancedMode),
// Poll bit is always 1. See GSM 5.4.4.1.
true) {}
UnnumberedAcknowledgementResponse::UnnumberedAcknowledgementResponse(Role role,
DLCI dlci)
: Frame(role,
// UA frames are always responses.
CommandResponse::kResponse, dlci,
uint8_t(FrameType::kUnnumberedAcknowledgement),
// Final bit is always 1. See GSM 5.4.4.2.
true) {}
DisconnectedModeResponse::DisconnectedModeResponse(Role role, DLCI dlci)
: Frame(role,
// DM frames are always responses.
CommandResponse::kResponse, dlci,
uint8_t(FrameType::kDisconnectedMode),
// Our implementation will always set the final bit to 1. The final
// bit in a DM response must be 1 when responding negatively to a
// SABM command (GSM 5.4.1). In all other cases, the P/F bit setting
// is unspecified and does not matter for DM frames. See, for
// example, GSM 5.4.4.2: "If an unsolicited DM response is received
// then the frame shall be processed irrespective of the P/F
// setting."
true) {}
UnnumberedInfoHeaderCheckFrame::UnnumberedInfoHeaderCheckFrame(
Role role, bool credit_based_flow, DLCI dlci)
: Frame(role,
// UIH frames are always commands.
CommandResponse::kCommand, dlci,
uint8_t(FrameType::kUnnumberedInfoHeaderCheck),
// P/F bit initially 0, as there should be no credits field (credits
// initialized to 0).
false),
credit_based_flow_(credit_based_flow),
// Initially no credits.
credits_(0) {}
void UnnumberedInfoHeaderCheckFrame::set_credits(uint8_t credits) {
// Can't set credits on a frame if credit-based flow is off.
if (!credit_based_flow_)
return;
credits_ = credits;
// P/F is used to indicate the presence of a credits octet. We turn on the
// credits octet if there are credits.
poll_final_ = credits != 0;
}
size_t UnnumberedInfoHeaderCheckFrame::header_size() const {
return Frame::header_size() // Address, control, length
+ sizeof(uint8_t) * (has_credit_octet() ? 1 : 0); // Credits
}
void UnnumberedInfoHeaderCheckFrame::WriteHeader(
common::MutableBufferView buffer) const {
ZX_DEBUG_ASSERT(buffer.size() >= header_size());
// Write address, control, length
Frame::WriteHeader(buffer);
// Write credit octet if it exists.
if (has_credit_octet()) {
size_t offset = Frame::header_size();
buffer[offset] = credits_;
}
}
UserDataFrame::UserDataFrame(Role role, bool credit_based_flow, DLCI dlci,
common::ByteBufferPtr information)
: UnnumberedInfoHeaderCheckFrame(role, credit_based_flow, dlci),
information_(std::move(information)) {}
void UserDataFrame::Write(common::MutableBufferView buffer) const {
ZX_DEBUG_ASSERT(buffer.size() >= written_size());
WriteHeader(buffer);
size_t offset = header_size();
if (information_) {
buffer.Write(*information_, offset);
offset += information_->size();
}
// FCS is always calculated over first two octets for UIH frames.
buffer[offset] = CalculateFCS(buffer.view(0, 2));
}
size_t UserDataFrame::written_size() const {
return UnnumberedInfoHeaderCheckFrame::header_size() +
length() // Information
+ sizeof(uint8_t); // FCS
}
common::ByteBufferPtr UserDataFrame::TakeInformation() {
return std::move(information_);
}
MuxCommandFrame::MuxCommandFrame(Role role, bool credit_based_flow,
std::unique_ptr<MuxCommand> mux_command)
: UnnumberedInfoHeaderCheckFrame(role, credit_based_flow, kMuxControlDLCI),
mux_command_(std::move(mux_command)) {}
void MuxCommandFrame::Write(common::MutableBufferView buffer) const {
ZX_DEBUG_ASSERT(buffer.size() >= written_size());
WriteHeader(buffer);
size_t offset = header_size();
mux_command_->Write(buffer.mutable_view(offset));
offset += mux_command_->written_size();
// FCS is always calculated over first two octets for UIH frames.
buffer[offset] = CalculateFCS(buffer.view(0, 2));
}
size_t MuxCommandFrame::written_size() const {
return UnnumberedInfoHeaderCheckFrame::header_size() +
+mux_command_->written_size() // MuxCommand
+ sizeof(uint8_t); // FCS
}
std::unique_ptr<MuxCommand> MuxCommandFrame::TakeMuxCommand() {
return std::move(mux_command_);
}
} // namespace rfcomm
} // namespace btlib