| // 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_SESSION_H_ |
| #define GARNET_DRIVERS_BLUETOOTH_LIB_RFCOMM_SESSION_H_ |
| |
| #include <queue> |
| #include <unordered_map> |
| |
| #include <fbl/ref_ptr.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/function.h> |
| #include <lib/fxl/memory/weak_ptr.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/byte_buffer.h" |
| #include "garnet/drivers/bluetooth/lib/l2cap/channel.h" |
| #include "garnet/drivers/bluetooth/lib/l2cap/scoped_channel.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/channel.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/frames.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/rfcomm.h" |
| |
| namespace btlib { |
| namespace rfcomm { |
| |
| class Session { |
| public: |
| void SendUserData(DLCI dlci, common::ByteBufferPtr data); |
| |
| private: |
| friend class ChannelManager; |
| |
| // Returns nullptr if creation fails -- for example, if activating the L2CAP |
| // channel fails. |channel_opened_cb| will be called whenever a new channel is |
| // opened on this session. The callback will be dispatched onto the thread on |
| // which Session was created. |
| using ChannelOpenedCallback = |
| fit::function<void(fbl::RefPtr<Channel>, ServerChannel)>; |
| static std::unique_ptr<Session> Create( |
| fbl::RefPtr<l2cap::Channel> l2cap_channel, |
| ChannelOpenedCallback channel_opened_cb); |
| |
| // Should only be called from Create(). |
| Session(ChannelOpenedCallback channel_opened_cb); |
| |
| // Sets |l2cap_channel| as the Session's underlying L2CAP channel. |
| // |l2cap_channel| should not be activated. This function activates |
| // |l2cap_channel|; returns true iff channel activation succeeds. Should only |
| // be called from Create() during Session creation. |
| bool SetL2CAPChannel(fbl::RefPtr<l2cap::Channel> l2cap_channel); |
| |
| // Opens a remote channel and delivers it via |channel_opened_cb|. |
| void OpenRemoteChannel(ServerChannel server_channel, |
| ChannelOpenedCallback channel_opened_cb); |
| |
| // l2cap::Channel callbacks. |
| void RxCallback(const l2cap::SDU& sdu); |
| void ClosedCallback(); |
| |
| // Send a SABM or a DISC command. When a response (UA or DM) is received, |
| // it is passed to |command_response_cb|. If an error occurs, the callback |
| // is not called. Otherwise, if the callback is given, it will be called with |
| // a valid UA or DM frame. |
| using CommandResponseCallback = fit::function<void(std::unique_ptr<Frame>)>; |
| void SendCommand(FrameType frame_type, DLCI dlci, |
| CommandResponseCallback command_response_cb = nullptr); |
| |
| // Send a UA or DM response. |
| void SendResponse(FrameType frame_type, DLCI dlci); |
| |
| // The raw frame-sending function. This function should only be called by the |
| // other frame-sending functions. |sent_cb| is called synchronously once the |
| // frame is actually sent. |
| bool SendFrame(std::unique_ptr<Frame> frame, fit::closure sent_cb = nullptr); |
| |
| // Send a multiplexer command along the multiplexer control channel. |
| // |
| // All multiplexer commands come in command/response pairs (RFCOMM 5.4.6.2). |
| // This function takes an optional callback which will be called when the |
| // response is received, and will be passed a |nullptr| if a DM response was |
| // sent in response to the multiplexer command (meaning the command was |
| // declined). A DM response can only occur for specific commands, e.g. |
| // parameter negotiation (RFCOMM 5.5.3). |
| using MuxResponseCallback = fit::function<void(std::unique_ptr<MuxCommand>)>; |
| void SendMuxCommand(std::unique_ptr<MuxCommand> mux_command, |
| MuxResponseCallback callback = nullptr); |
| |
| // Handle an incoming SABM request. |
| void HandleSABM(DLCI dlci); |
| |
| // Handle incoming multiplexer command. |
| void HandleMuxCommand(std::unique_ptr<MuxCommand> mux_command); |
| |
| // Begin the multiplexer start-up routine described in RFCOMM 5.2.1. This |
| // function implements the "initiator" side of the multiplexer startup |
| // protocol. For the "responder" side, see HandleSABM(). |
| void StartupMultiplexer(); |
| |
| // Sets |role_|, and runs any Tasks that were pending multiplexer startup. |
| void SetMultiplexerStarted(Role role); |
| |
| inline bool multiplexer_started() { return IsMultiplexerStarted(role_); } |
| |
| void Closedown(); |
| |
| // Begin this session's initial parameter negotiation. Our RFCOMM |
| // implementation will initiate parameter negotiation at most once, before the |
| // first DLC is opened. This initial parameter negotiation is required by the |
| // spec. Any other PN which we participate in will be initiated by the remote. |
| // |
| // TODO(gusss): what happens when we set parameters and then receive a DISC/DM |
| // for that channel? Do we need to undo, somehow? For something like credits, |
| // we can probably just unset that entry in the credits map. |
| // |
| // TODO(gusss): does the spec say what happens (in terms of initial parameter |
| // negotiation) if we do initial parameter negotiation, it finishes, and the |
| // remote sends a DISC? Do we have to re-do initial parameter negotiation? |
| void RunInitialParameterNegotiation(DLCI dlci); |
| |
| // Get the ideal parameters for this session. This is used in both PN commands |
| // and PN responses to determine what our parameter set would be in the ideal |
| // case. The final parameters may be different after negotiation. |
| ParameterNegotiationParams GetIdealParameters(DLCI dlci) const; |
| |
| // Set initial parameter negotiation as complete and run any pending tasks. |
| void InitialParameterNegotiationComplete(); |
| |
| l2cap::ScopedChannel l2cap_channel_; |
| |
| // The RFCOMM role of this device for this particular Session. This is |
| // determined not when the object is created, but when the multiplexer control |
| // channel is set up. |
| Role role_; |
| |
| // Whether or not this Session is using credit-based flow, as described in the |
| // RFCOMM spec. Whether credit-based flow is being used is determined in the |
| // first Parameter Negotiation interaction. |
| bool credit_based_flow_; |
| |
| // Keeps track of opened channels. |
| std::unordered_map<DLCI, fbl::RefPtr<Channel>> channels_; |
| |
| // Called when the remote peer opens a new incoming channel. The session |
| // object constructs a new channel and then passes ownership of the channel |
| // via this callback. |
| ChannelOpenedCallback channel_opened_cb_; |
| |
| // This dispatcher is used for all tasks, including the ChannelOpenCallback |
| // passed in to Create(). |
| async_dispatcher_t* dispatcher_; |
| |
| // Tasks which are to be run once the multiplexer starts. |
| std::queue<fit::closure> tasks_pending_mux_startup_; |
| |
| // Called when a command frame or a multiplexer command doesn't receive a |
| // response. |
| using TimeoutCallback = async::TaskClosure; |
| |
| // Outstanding SABM and DISC commands awaiting responses. GSM 5.4.4.1 states |
| // that there can be at most one command with the P bit set to 1 outstanding |
| // on a given DLC at any time. Thus, we can identify outstanding frames by |
| // their DLCI. |
| using CommandResponseCallbacks = |
| std::pair<CommandResponseCallback, std::unique_ptr<TimeoutCallback>>; |
| std::unordered_map<DLCI, CommandResponseCallbacks> outstanding_frames_; |
| |
| // Outstanding multiplexer commands awaiting responses. We identify |
| // outstanding commands as a tuple of the command type and the DLCI referenced |
| // in the command, or kNoDLCI. Some multiplexer commands do not identify a |
| // DLCI (e.g. the Test command); these commands use kNoDLCI as their DLCI. |
| // |
| // The TimeoutCallback is called when the timeout elapses before a response is |
| // received. |
| using OutstandingMuxCommand = std::pair<MuxCommandType, DLCI>; |
| using MuxResponseCallbacks = |
| std::pair<MuxResponseCallback, std::unique_ptr<TimeoutCallback>>; |
| struct outstanding_mux_commands_hash { |
| inline size_t operator()(OutstandingMuxCommand key) const { |
| return ((uint8_t)std::get<0>(key) << 8) | std::get<1>(key); |
| } |
| }; |
| std::unordered_map<OutstandingMuxCommand, MuxResponseCallbacks, |
| outstanding_mux_commands_hash> |
| outstanding_mux_commands_; |
| |
| enum class ParameterNegotiationState { |
| kNotNegotiated, |
| kNegotiating, |
| kNegotiated |
| }; |
| |
| // Tracks whether the initial parameter negotiation has completed. RFCOMM |
| // requires that parameter negotiation run at least once, before any DLCs are |
| // opened. The first request to open a DLC to the remote peer will trigger |
| // initial parameter negotiation, which will delay all other channel opening |
| // requests until it completes. |
| ParameterNegotiationState initial_param_negotiation_state_; |
| |
| // Tasks which are to be run once parameter negotiation completes. |
| std::queue<fit::closure> tasks_pending_parameter_negotiation_; |
| |
| // Channels undergoing parameter negotiation. Multiple channels can be |
| // negotiated simultaneously. Note that the only parameter negotiation that |
| // our RFCOMM implementation will initiate is the required first PN; any other |
| // PNs which occur will be triggered by the remote. |
| std::unordered_map<DLCI, ParameterNegotiationState> channels_negotiating_; |
| |
| // The RX and TX MTU for this Session. This is determined during initial |
| // parameter negotiation, and is based on the MTU of the underlying L2CAP |
| // link. Our RFCOMM implementation refuses to change the maximum frame size |
| // once it is set the first time. |
| uint16_t maximum_frame_size_; |
| |
| fxl::WeakPtrFactory<Session> weak_ptr_factory_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(Session); |
| }; |
| |
| } // namespace rfcomm |
| } // namespace btlib |
| |
| #endif // GARNET_DRIVERS_BLUETOOTH_LIB_RFCOMM_SESSION_H_ |