blob: a2edbfbd35b66217fe189fb2af1a08389e160a0c [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.
#ifndef GARNET_DRIVERS_BLUETOOTH_LIB_RFCOMM_FRAMES_H_
#define GARNET_DRIVERS_BLUETOOTH_LIB_RFCOMM_FRAMES_H_
#include <cstdlib>
#include "garnet/drivers/bluetooth/lib/common/packet_view.h"
#include "garnet/drivers/bluetooth/lib/rfcomm/mux_commands.h"
#include "garnet/drivers/bluetooth/lib/rfcomm/rfcomm.h"
namespace btlib {
namespace rfcomm {
class UserDataFrame;
class MuxCommandFrame;
// Represents an RFCOMM frame.
// The Frame class has two primary uses:
// 1. Constructing RFCOMM frames with all necessary fields, with the possible
// exception of the credits field; later, modifying the credits field and
// writing the frame to a buffer.
// 2. Interpreting an RFCOMM frame written into a buffer, but not modifying the
// underlying buffer or fields.
// The Frame class is designed to be heavily restricted to the above use cases.
//
// The Frame class hierarchy is as follows:
// Frame
// - SetAsynchronousBalancedModeCommand
// - DisconnectCommand
// - UnnumberedAcknowledgementResponse
// - DisconnectedModeResponse
// - UnnumberedInfoHeaderCheckFrame (abstract)
// - UserDataFrame
// - MuxCommandFrame
//
// To use Frame in the first way (to write RFCOMM frames), construct the
// subclass of Frame corresponding to the type of RFCOMM frame you would like.
// If it is a UIH frame, modify the frame's credits field using set_credits() if
// needed. Allocate a buffer using written_size() to determine the size of the
// frame when written. Finally, use Write() to write the frame into a buffer.
//
// To use Frame in the second way (as a frame parser), use Frame::Parse(). If
// needed, cast the returned Frame pointer to a specific subclass (see the
// documentation of Parse()). Read values using the accessors. Finally, if the
// frame contains user data or a multiplexer command, use the Take...()
// functions to take ownership of the frame's information (payload). This
// payload can then be passed to the next layer.
class Frame {
public:
// |role| is the local RFCOMM role. This is used to determine how to set the
// C/R bit. |control| is the control octet. Generally, this will be passed by
// casting a FrameType to a uint8_t. We take a uint8_t instead of a FrameType
// because Frame can represent frames of unsupported frame type (whose frame
// types are not enumerated in FrameType).
inline Frame(Role role, CommandResponse command_response, DLCI dlci,
uint8_t control, bool poll_final);
virtual ~Frame() = default;
// Parse a frame out of a ByteBuffer. If parsing fails, returns nullptr.
// Copies the payload from |buffer|. Does not take ownership of |buffer|.
// |credit_based_flow| indicates whether credit-based flow is turned on for
// this session. |role| is the RFCOMM role of the session on which the frame
// was formed. Thus, if the frame was formed by the remote peer and sent to
// the local session, Parse() should be called with the opposite role of the
// local session.
//
// The Frame returned by Parse should first have its type inspected. If it is
// not a UIH frame, it can be used as-is. No casting needs to be done to get
// all of the useful information out of the Frame; simply use the Frame
// accessors to read information about the frame. If the frame type is UIH,
// then the DLCI should be inspected. If the DLCI is a user data DLCI, the
// Frame should be converted to a UserDataFrame using ToUserDataFrame.
// Otherwise, if the DLCI is 0, the frame should be cast to a MuxCommandFrame
// using ToMuxCommandFrame.
//
// For UIH frames, this function will copy from |buffer|.
static std::unique_ptr<Frame> Parse(bool credit_based_flow, Role role,
const common::ByteBuffer& buffer);
// Write this into a buffer. The base implementation of Write() will simply
// write the address, control, length(=0), and FCS octets into a buffer. This
// is adequate for non-UIH frames.
virtual void Write(common::MutableBufferView buffer) const;
// The amount of space this frame takes up when written. Used to allocate the
// correct size for Write().
virtual inline size_t written_size() const {
// Address, control, length, FCS octets.
return 4 * sizeof(uint8_t);
}
// See GSM 5.2 to understand how we extract different fields from the frame.
// Returns Data Link Connection Identifier (DLCI) used to identify the
// specific DLC/channel which this frame pertains to.
inline DLCI dlci() const { return dlci_; }
// Returns whether this is a Command or a Response frame.
inline CommandResponse command_response() const { return command_response_; }
// Returns Control field with the Poll/Final bit set to 0. See GSM Table 2.
// The Control field encodes the frame type. This octet can be cast to a
// FrameType; however, this octet may not correspond to any of the octets in
// the FrameType enum, if the peer sent an unrecognized/unsupported frame
// type.
inline uint8_t control() const { return control_; }
// Returns the Poll/Final (P/F) bit. See RFCOMM 5.1.2, which indicates the
// various uses of the P/F bit in RFCOMM.
inline bool poll_final() const { return poll_final_; }
// Returns the length of the information field of this frame. This is the
// value which will be encoded in the length field of the frame, when the
// frame is written. For the default Frame implementation, returns 0, as there
// is no payload. This is overridden for UIH frames.
inline virtual InformationLength length() const { return 0; }
// Downcast this Frame to a Frame subclass. It is expected that the caller
// will first check that the frame is of the subclass they are downcasting to;
// for example, by checking that the DLCI is 0 for MuxCommandFrames, or
// checking that the DLCI is in [kMinUserDLCI, kMaxUserDLCI] for
// UserDataFrames.
//
// TODO(NET-1224): find a cleaner and less bug-prone way to do downcasting.
template <typename T>
static inline std::unique_ptr<T> DowncastFrame(std::unique_ptr<Frame> frame) {
static_assert(std::is_base_of<Frame, T>::value,
"Must be downcasting to a Frame subclass");
return std::unique_ptr<T>(static_cast<T*>(frame.release()));
}
protected:
// The size of the header. We consider the header to be the address octet,
// control octet, and and length octet(s).
virtual size_t header_size() const;
// Write the header of this frame into a buffer.
virtual void WriteHeader(common::MutableBufferView buffer) const;
// RFCOMM session parameters.
Role role_;
// Frame fields.
CommandResponse command_response_;
DLCI dlci_;
uint8_t control_;
bool poll_final_;
};
// Set Asynchronous Balanced Mode (SABM) command, described in GSM 5.3.1. Used
// to start up channels.
class SetAsynchronousBalancedModeCommand : public Frame {
public:
SetAsynchronousBalancedModeCommand(Role role, DLCI dlci);
};
// Disconnect (DISC) command, described in GSM 5.3.3. Used to close down
// channels, or the multiplexer session as a whole.
class DisconnectCommand : public Frame {
public:
DisconnectCommand(Role role, DLCI dlci);
};
// Unnumbered Acknowledgement (UA) response, described in GSM 5.3.2. Used as an
// acknowledgement to SABM and DISC commands.
class UnnumberedAcknowledgementResponse : public Frame {
public:
UnnumberedAcknowledgementResponse(Role role, DLCI dlci);
};
// Disconnected Mode (DM) response, described in GSM 5.3.3. This response is
// sent when commands are sent along a disconnected channel.
class DisconnectedModeResponse : public Frame {
public:
DisconnectedModeResponse(Role role, DLCI dlci);
};
// Unnumbered Information with Header Check frame. This abstract class is the
// superclass of both MuxCommandFrame (sent along DLCI 0) and UserDataFrame
// (sent along DLCIs 2-61).
//
// |credit_based_flow| is a session parameter specifying whether credit-based
// flow control is turned on or off for this session. It determines whether a
// credit field can appear in this frame; if it is set to true and set_credits()
// is used to set the credits to a nonzero amount, a credits field will appear.
// Note that, with this class, we cannot encode a frame with a credits field
// equal to 0. This isn't an issue, as sending a frame with credits=0 is
// functionally equivalent to a frame without a credits field.
class UnnumberedInfoHeaderCheckFrame : public Frame {
public:
UnnumberedInfoHeaderCheckFrame(Role role, bool credit_based_flow, DLCI dlci);
virtual ~UnnumberedInfoHeaderCheckFrame() = default;
// Frame overrides
virtual void Write(common::MutableBufferView buffer) const override = 0;
virtual size_t written_size() const override = 0;
virtual InformationLength length() const override = 0;
// Returns the number of credits contained in the credits field of this frame.
// See RFCOMM 6.5.2. If this frame does not contain a credits field, returns
// 0.
inline uint8_t credits() const { return has_credit_octet() ? credits_ : 0; }
// Sets the credits. This is the only field that may need to be changed
// after frame creation. This function should not be called if credit-based
// flow is off; if credit-based flow is off, then |credits_| should remain 0.
// This function also changes the poll/final bit to reflect the new amount of
// credits; P/F=0 iff credits=0. See RFCOMM 6.5.2.
void set_credits(uint8_t credits);
protected:
// Whether or not this frame contains the optional credit octet.
inline bool has_credit_octet() const {
return credit_based_flow_ && credits_;
}
// The size of the header. In this case, we define the header to be the
// address octet, control octet, length octet(s), and optional credits octets.
virtual size_t header_size() const override;
// Write the header of this frame, including the optional credits octet.
virtual void WriteHeader(common::MutableBufferView buffer) const override;
bool credit_based_flow_;
uint8_t credits_;
};
class UserDataFrame : public UnnumberedInfoHeaderCheckFrame {
public:
// |information| is the payload; "information" is RFCOMM/GSM's term for the
// payload of a frame. Frame takes ownership of |information|.
UserDataFrame(Role role, bool credit_based_flow, DLCI dlci,
common::ByteBufferPtr information);
// UnnumberedInfoHeaderCheckFrame overrides
void Write(common::MutableBufferView buffer) const override;
size_t written_size() const override;
inline InformationLength length() const override {
return information_->size();
}
// Transfers ownership of the information field (aka the payload) from this
// Frame to the caller. Future calls to TakeInformation() will return nullptr.
// It is expected that the Frame will be destructed soon after this call.
common::ByteBufferPtr TakeInformation();
private:
common::ByteBufferPtr information_;
};
// Represents a UIH frame encapsulating a multiplexer control channel command.
// These frames will always have DLCI=0 (the multiplexer control channel).
class MuxCommandFrame : public UnnumberedInfoHeaderCheckFrame {
public:
MuxCommandFrame(Role role, bool credit_based_flow,
std::unique_ptr<MuxCommand> mux_command);
// UnnumberedInfoHeaderCheckFrame overrides
void Write(common::MutableBufferView buffer) const override;
size_t written_size() const override;
inline InformationLength length() const override {
return mux_command_->written_size();
}
// Transfers ownership of the MuxCommand owned by this MuxCommandFrame. In the
// common usage of MuxCommandFrame, this will be called just before
// MuxCommandFrame is destructed. However, a call to TakeMuxCommand() before
// destruction is not necessary.
std::unique_ptr<MuxCommand> TakeMuxCommand();
private:
std::unique_ptr<MuxCommand> mux_command_;
};
} // namespace rfcomm
} // namespace btlib
#endif // GARNET_DRIVERS_BLUETOOTH_LIB_RFCOMM_FRAMES_H_