| // 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 <lib/async/default.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/slab_allocator.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/rfcomm.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/session.h" |
| |
| namespace btlib { |
| namespace rfcomm { |
| |
| namespace { |
| |
| // Timeout system parameters (see RFCOMM 5.3, table 5.1) |
| // |
| // T1: timeout for (most) command frames; in RFCOMM, this only applies to SABM |
| // and DISC frames. 10-60 seconds, recommended value 20 seconds. |
| constexpr zx::duration kAcknowledgementTimer = zx::sec(20); |
| |
| // T1': timeout for SABM frames used to start DLCs with DLCI > 0. See RFCOMM |
| // 5.3. 60-300 seconds. |
| constexpr zx::duration kAcknowledgementTimerUserDLCs = zx::sec(300); |
| |
| // T2: timeout for multiplexer commands. 10-60 seconds, recommended value 20 |
| // seconds. |
| constexpr zx::duration kMuxResponseTimer = zx::sec(20); |
| |
| // The amount of time the multiplexer will wait when a startup conflict is |
| // detected. A conflict occurs when the local and remote multiplexers attempt to |
| // start at the same time. After delaying by the below amount, the local |
| // multiplexer will attempt to start up the multiplexer again. see RFCOMM 5.2.1. |
| constexpr zx::duration kMuxStartupConflictDelay = zx::msec(20); |
| |
| // Used to indicate that an outstanding multiplexer command does not pertain to |
| // any specific DLCI. This is applicable for multiplexer commands such as the |
| // Test command. |
| static constexpr DLCI kNoDLCI = 1; // 1 is an invalid DLCI. |
| |
| // Given a multiplexer command, find the DLCI which this command pertains to. |
| // Commands such as DLC Parameter Negotiation (PN) pertain to specific DLCs, |
| // whereas commands like the Test command or FCon/FCoff commands do not. |
| // Returns kNoDLCI for those commands which do not pertain to a specific DLCI. |
| DLCI GetDLCIFromMuxCommand(MuxCommand* mux_command) { |
| MuxCommandType type = mux_command->command_type(); |
| switch (type) { |
| case MuxCommandType::kDLCParameterNegotiation: |
| return static_cast<DLCParameterNegotiationCommand*>(mux_command) |
| ->params() |
| .dlci; |
| case MuxCommandType::kModemStatusCommand: |
| return static_cast<ModemStatusCommand*>(mux_command)->dlci(); |
| case MuxCommandType::kRemoteLineStatusCommand: |
| return static_cast<RemoteLineStatusCommand*>(mux_command)->dlci(); |
| case MuxCommandType::kRemotePortNegotiationCommand: |
| return static_cast<RemotePortNegotiationCommand*>(mux_command)->dlci(); |
| case MuxCommandType::kFlowControlOffCommand: |
| case MuxCommandType::kFlowControlOnCommand: |
| case MuxCommandType::kTestCommand: |
| case MuxCommandType::kNonSupportedCommandResponse: |
| return kNoDLCI; |
| default: |
| FXL_LOG(ERROR) << "Unexpected multiplexer command type"; |
| return kNoDLCI; |
| } |
| } |
| |
| // Returns true if this user DLCI "belongs to" the side of the session with the |
| // given |role|. See RFCOMM 5.2: "...this partitions the DLCI value space such |
| // that server applications on the non-initiating device are reachable on DLCIs |
| // 2,4,6,...,60, and server applications on the initiating device are reachable |
| // on 3,5,7,...,61." |
| constexpr bool IsValidLocalChannel(Role role, DLCI dlci) { |
| FXL_DCHECK(IsMultiplexerStarted(role)); |
| FXL_DCHECK(IsUserDLCI(dlci)); |
| return (role == Role::kInitiator ? 1 : 0) == dlci % 2; |
| } |
| |
| } // namespace |
| |
| void Session::SendUserData(DLCI dlci, common::ByteBufferPtr data) { |
| bool sent = SendFrame(std::make_unique<UserDataFrame>( |
| role_, credit_based_flow_, dlci, std::move(data))); |
| FXL_DCHECK(sent); |
| } |
| |
| std::unique_ptr<Session> Session::Create( |
| fbl::RefPtr<l2cap::Channel> l2cap_channel, |
| ChannelOpenedCallback channel_opened_cb) { |
| auto session = |
| std::unique_ptr<Session>(new Session(std::move(channel_opened_cb))); |
| if (!session->SetL2CAPChannel(l2cap_channel)) |
| return nullptr; |
| return session; |
| } |
| |
| Session::Session(ChannelOpenedCallback channel_opened_cb) |
| : role_(Role::kUnassigned), |
| channel_opened_cb_(std::move(channel_opened_cb)), |
| dispatcher_(async_get_default_dispatcher()), |
| initial_param_negotiation_state_( |
| ParameterNegotiationState::kNotNegotiated), |
| weak_ptr_factory_(this) {} |
| |
| bool Session::SetL2CAPChannel(fbl::RefPtr<l2cap::Channel> l2cap_channel) { |
| FXL_DCHECK(!l2cap_channel_); |
| FXL_DCHECK(l2cap_channel); |
| l2cap_channel_.Reset(l2cap_channel); |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| return l2cap_channel_->Activate( |
| [self](const auto& sdu) { |
| if (self) |
| self->RxCallback(sdu); |
| }, |
| [self]() { |
| if (self) |
| self->ClosedCallback(); |
| }, |
| dispatcher_); |
| } |
| |
| void Session::OpenRemoteChannel(ServerChannel server_channel, |
| ChannelOpenedCallback channel_opened_cb) { |
| if (!multiplexer_started()) { |
| tasks_pending_mux_startup_.emplace( |
| [this, server_channel, cb = std::move(channel_opened_cb)]() mutable { |
| OpenRemoteChannel(server_channel, std::move(cb)); |
| }); |
| if (role_ == Role::kUnassigned) |
| StartupMultiplexer(); |
| return; |
| } |
| |
| // RFCOMM 5.4: An RFCOMM entity making a new DLC on an existing session forms |
| // the DLCI by combining the Server Channel for the application on the other |
| // device, and the inverse of its own direction bit for the session." |
| DLCI dlci = ServerChannelToDLCI(server_channel, OppositeRole(role_)); |
| |
| if (initial_param_negotiation_state_ != |
| ParameterNegotiationState::kNegotiated) { |
| tasks_pending_parameter_negotiation_.emplace( |
| [this, server_channel, cb = std::move(channel_opened_cb)]() mutable { |
| OpenRemoteChannel(server_channel, std::move(cb)); |
| }); |
| |
| // If it's not started, start it |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNotNegotiated) |
| RunInitialParameterNegotiation(dlci); |
| |
| return; |
| } |
| |
| SendCommand( |
| FrameType::kSetAsynchronousBalancedMode, dlci, |
| [this, dlci, server_channel, cb = std::move(channel_opened_cb)]( |
| std::unique_ptr<Frame> response) mutable { |
| FXL_DCHECK(response); |
| FrameType type = FrameType(response->control()); |
| fbl::RefPtr<Channel> new_channel; |
| switch (type) { |
| case FrameType::kUnnumberedAcknowledgement: { |
| FXL_LOG(INFO) << "rfcomm: Channel " << static_cast<unsigned>(dlci) |
| << " started successfully"; |
| new_channel = fbl::AdoptRef(new internal::ChannelImpl(dlci, this)); |
| FXL_DCHECK(channels_.find(dlci) == channels_.end()); |
| channels_.emplace(dlci, new_channel); |
| break; |
| } |
| case FrameType::kDisconnectedMode: |
| FXL_LOG(WARNING) |
| << "rfcomm: Channel " << static_cast<unsigned>(dlci) |
| << " failed to start"; |
| break; |
| default: |
| FXL_LOG(WARNING) << "rfcomm: Unexpected response to SABM: " |
| << static_cast<unsigned>(type); |
| break; |
| } |
| |
| // Send the result. |
| async::PostTask(dispatcher_, |
| [server_channel, new_channel, cb_ = std::move(cb)] { |
| cb_(new_channel, server_channel); |
| }); |
| }); |
| } |
| void Session::RxCallback(const l2cap::SDU& sdu) { |
| l2cap::PDU::Reader reader(&sdu); |
| reader.ReadNext(sdu.length(), [&](const common::ByteBuffer& buffer) { |
| auto frame = Frame::Parse(credit_based_flow_, OppositeRole(role_), buffer); |
| if (!frame) { |
| FXL_LOG(ERROR) << "Could not parse frame"; |
| return; |
| } |
| |
| DLCI dlci = frame->dlci(); |
| |
| switch ((FrameType)frame->control()) { |
| case FrameType::kSetAsynchronousBalancedMode: |
| HandleSABM(dlci); |
| return; |
| |
| case FrameType::kUnnumberedAcknowledgement: |
| case FrameType::kDisconnectedMode: { |
| auto callbacks_it = outstanding_frames_.find(dlci); |
| if (callbacks_it == outstanding_frames_.end()) { |
| FXL_NOTIMPLEMENTED() << "Unsolicited UA or DM frame"; |
| return; |
| } |
| |
| // Cancel the timeout and run the callback. |
| callbacks_it->second.second->Cancel(); |
| async::PostTask( |
| dispatcher_, |
| [cb = std::move(callbacks_it->second.first), |
| fr = std::move(frame)]() mutable { cb(std::move(fr)); }); |
| |
| outstanding_frames_.erase(dlci); |
| |
| return; |
| } |
| |
| case FrameType::kUnnumberedInfoHeaderCheck: { |
| if (dlci == kMuxControlDLCI) { |
| HandleMuxCommand( |
| static_cast<MuxCommandFrame*>(frame.get())->TakeMuxCommand()); |
| return; |
| } else if (IsUserDLCI(dlci)) { |
| auto channel_it = channels_.find(dlci); |
| if (channel_it == channels_.end()) { |
| FXL_LOG(WARNING) << "rfcomm: User data received for unopened DLCI " |
| << static_cast<unsigned>(dlci); |
| return; |
| } |
| channel_it->second->Receive( |
| static_cast<UserDataFrame*>(frame.get())->TakeInformation()); |
| return; |
| } else { |
| FXL_LOG(WARNING) << "rfcomm: UIH frame on invalid DLCI " |
| << static_cast<unsigned>(dlci); |
| } |
| } |
| |
| default: |
| // TODO(gusss): implement better error handling here. |
| FXL_LOG(WARNING) << "rfcomm: Unrecognized frame type received: " |
| << (unsigned)frame->control(); |
| return; |
| } |
| }); |
| } |
| |
| void Session::ClosedCallback() { Closedown(); } |
| |
| void Session::SendCommand(FrameType frame_type, DLCI dlci, |
| CommandResponseCallback command_response_cb) { |
| FXL_DCHECK(frame_type == FrameType::kSetAsynchronousBalancedMode || |
| frame_type == FrameType::kDisconnect); |
| FXL_DCHECK(IsValidDLCI(dlci)); |
| FXL_DCHECK(outstanding_frames_.find(dlci) == outstanding_frames_.end()) |
| << "rfcomm: There is already an outstanding command frame for DLCI " |
| << static_cast<unsigned>(dlci); |
| |
| auto timeout_cb = std::make_unique<async::TaskClosure>([this, dlci] { |
| FXL_LOG(ERROR) << "rfcomm: Outstanding frame on DLCI " |
| << static_cast<unsigned>(dlci) |
| << " timed out; closing down session"; |
| Closedown(); |
| }); |
| |
| // Set response and timeout callbacks. |
| auto callbacks = |
| std::make_pair(std::move(command_response_cb), std::move(timeout_cb)); |
| outstanding_frames_.emplace(dlci, std::move(callbacks)); |
| |
| // A different timeout is used if this is a SABM command on a user data |
| // channel (RFCOMM 5.3). |
| zx::duration timeout = |
| (frame_type == FrameType::kSetAsynchronousBalancedMode && |
| IsUserDLCI(dlci)) |
| ? kAcknowledgementTimerUserDLCs |
| : kAcknowledgementTimer; |
| |
| std::unique_ptr<Frame> frame; |
| if (frame_type == FrameType::kSetAsynchronousBalancedMode) { |
| frame = std::make_unique<SetAsynchronousBalancedModeCommand>(role_, dlci); |
| } else { |
| frame = std::make_unique<DisconnectCommand>(role_, dlci); |
| } |
| |
| bool sent = SendFrame(std::move(frame), [this, timeout, dlci] { |
| outstanding_frames_[dlci].second->PostDelayed(dispatcher_, timeout); |
| }); |
| FXL_DCHECK(sent); |
| } |
| |
| void Session::SendResponse(FrameType frame_type, DLCI dlci) { |
| FXL_DCHECK(frame_type == FrameType::kUnnumberedAcknowledgement || |
| frame_type == FrameType::kDisconnectedMode); |
| FXL_DCHECK(IsValidDLCI(dlci)); |
| |
| std::unique_ptr<Frame> frame; |
| if (frame_type == FrameType::kUnnumberedAcknowledgement) |
| frame = std::make_unique<UnnumberedAcknowledgementResponse>(role_, dlci); |
| else |
| frame = std::make_unique<DisconnectedModeResponse>(role_, dlci); |
| |
| bool sent = SendFrame(std::move(frame)); |
| FXL_DCHECK(sent); |
| } |
| |
| bool Session::SendFrame(std::unique_ptr<Frame> frame, fit::closure sent_cb) { |
| FXL_DCHECK(frame); |
| const DLCI dlci = frame->dlci(); |
| const FrameType type = static_cast<FrameType>(frame->control()); |
| |
| // If the multiplexer isn't started, only startup frames should be sent. |
| FXL_DCHECK(multiplexer_started() || IsMuxStartupFrame(type, dlci)); |
| |
| // TODO(gusss): check that the DLC is actually open. |
| |
| // TODO(NET-1079, NET-1080): check flow control and queue the frame if it |
| // needs to be queued. |
| |
| // TODO(gusss): attach credits to frame. |
| |
| // Allocate and write the buffer. |
| auto buffer = common::NewSlabBuffer(frame->written_size()); |
| if (!buffer) { |
| FXL_LOG(WARNING) << "rfcomm: Couldn't allocate frame buffer (" |
| << frame->written_size() << ")"; |
| return false; |
| } |
| frame->Write(buffer->mutable_view()); |
| |
| if (l2cap_channel_->Send(std::move(buffer))) { |
| if (sent_cb) |
| sent_cb(); |
| return true; |
| } else { |
| FXL_LOG(ERROR) << "rfcomm: Failed to send frame"; |
| return false; |
| } |
| } |
| |
| void Session::SendMuxCommand(std::unique_ptr<MuxCommand> mux_command, |
| MuxResponseCallback callback) { |
| FXL_DCHECK(mux_command); |
| |
| // If we're not expecting a response, we can send right away. |
| if (!callback) { |
| auto frame = std::make_unique<MuxCommandFrame>(role_, credit_based_flow_, |
| std::move(mux_command)); |
| bool sent = SendFrame(std::move(frame)); |
| FXL_DCHECK(sent); |
| return; |
| } |
| |
| // If we're expecting a response at the mulitplexer level, store the callback |
| // that should be called when the response arrives. |
| MuxCommandType type = mux_command->command_type(); |
| DLCI dlci = GetDLCIFromMuxCommand(mux_command.get()); |
| OutstandingMuxCommand key = std::make_pair(type, dlci); |
| |
| FXL_DCHECK(outstanding_mux_commands_.find(key) == |
| outstanding_mux_commands_.end()) |
| << "rfcomm: Already an outstanding mux command for (command type," |
| << " dlci) = (" << static_cast<unsigned>(type) << ", " |
| << static_cast<unsigned>(dlci) << ")"; |
| |
| auto timeout_cb = |
| std::make_unique<async::TaskClosure>([this] { Closedown(); }); |
| |
| // Set response and timeout callbacks. |
| outstanding_mux_commands_.emplace( |
| key, std::make_pair(std::move(callback), std::move(timeout_cb))); |
| |
| auto frame = std::make_unique<MuxCommandFrame>(role_, credit_based_flow_, |
| std::move(mux_command)); |
| |
| bool sent = SendFrame(std::move(frame), [this, key] { |
| outstanding_mux_commands_[key].second->PostDelayed(dispatcher_, |
| kMuxResponseTimer); |
| }); |
| FXL_DCHECK(sent); |
| } |
| |
| void Session::StartupMultiplexer() { |
| if (role_ == Role::kNegotiating || multiplexer_started()) { |
| FXL_LOG(WARNING) << "rfcomm: StartupMultiplexer when starting or started"; |
| return; |
| } |
| |
| FXL_LOG(INFO) << "rfcomm: Starting multiplexer"; |
| |
| role_ = Role::kNegotiating; |
| |
| SendCommand(FrameType::kSetAsynchronousBalancedMode, kMuxControlDLCI, |
| [this](auto response) { |
| FXL_DCHECK(response); |
| |
| FrameType type = static_cast<FrameType>(response->control()); |
| FXL_DCHECK(type == FrameType::kUnnumberedAcknowledgement || |
| type == FrameType::kDisconnectedMode); |
| |
| switch (role_) { |
| case Role::kNegotiating: { |
| if (type == FrameType::kUnnumberedAcknowledgement) { |
| SetMultiplexerStarted(Role::kInitiator); |
| } else { |
| FXL_LOG(WARNING) |
| << "rfcomm: Remote multiplexer startup refused" |
| << " by remote"; |
| role_ = Role::kUnassigned; |
| } |
| return; |
| } |
| case Role::kUnassigned: |
| case Role::kInitiator: |
| case Role::kResponder: |
| // TODO(guss): should a UA be received in any of these |
| // cases? |
| FXL_LOG(WARNING) |
| << "rfcomm: Mux UA frame received in unexpected" |
| << " state"; |
| break; |
| default: |
| // TODO(gusss): shouldn't get here. |
| FXL_NOTREACHED(); |
| break; |
| } |
| }); |
| } |
| |
| void Session::HandleSABM(DLCI dlci) { |
| if (dlci == kMuxControlDLCI) { |
| // A SABM frame on the mux control DLCI indicates that we should start |
| // up the multiplexer. |
| switch (role_) { |
| case Role::kUnassigned: { |
| // We received a SABM request to start the multiplexer. Reply positively |
| // to the request, meaning that the peer becomes the initiator and this |
| // session becomes the responder. |
| SendResponse(FrameType::kUnnumberedAcknowledgement, dlci); |
| SetMultiplexerStarted(Role::kResponder); |
| return; |
| } |
| case Role::kNegotiating: { |
| // In this case, we have an outstanding request to start the |
| // multiplexer. Respond negatively and attempt startup again later. See |
| // RFCOMM 5.2.1. |
| |
| FXL_LOG(INFO) << "rfcomm: Resolving multiplexer startup conflict"; |
| |
| // "Undo" our multiplexer startup request by changing our role back, |
| // cancelling timeout, and removing callbacks. |
| role_ = Role::kUnassigned; |
| auto frame_it = outstanding_frames_.find(dlci); |
| FXL_DCHECK(frame_it != outstanding_frames_.end()); |
| frame_it->second.second->Cancel(); |
| outstanding_frames_.erase(frame_it); |
| |
| SendResponse(FrameType::kDisconnectedMode, kMuxControlDLCI); |
| async::PostDelayedTask( |
| dispatcher_, |
| [this] { |
| if (!multiplexer_started()) { |
| FXL_LOG(INFO) << "rfcomm: Retrying multiplexer startup"; |
| StartupMultiplexer(); |
| } |
| }, |
| kMuxStartupConflictDelay); |
| return; |
| } |
| case Role::kInitiator: |
| case Role::kResponder: |
| // TODO(gusss): should we send a DM in this case? |
| FXL_LOG(WARNING) << "rfcomm: Request to start already started" |
| << " multiplexer"; |
| return; |
| default: |
| FXL_NOTREACHED(); |
| return; |
| } |
| } |
| |
| // If it isn't a multiplexer startup request, it must be a request for a user |
| // channel. |
| |
| // TODO(NET-1301): unit test this case. |
| if (!IsUserDLCI(dlci) || !IsValidLocalChannel(role_, dlci)) { |
| FXL_LOG(WARNING) << "rfcomm: Remote requested invalid DLCI " |
| << static_cast<unsigned>(dlci); |
| SendResponse(FrameType::kDisconnectedMode, dlci); |
| return; |
| } |
| |
| // TODO(NET-1301): unit test this case. |
| if (channels_.find(dlci) != channels_.end()) { |
| // If the channel is already open, the remote is confused about the state of |
| // the Session. Send a DM and a DISC for that channel. |
| // TODO(NET-1274): do we want to just shut down the whole session here? |
| // Things would be in a nasty state at this point. |
| FXL_LOG(WARNING) << "rfcomm: Remote requested already open channel"; |
| SendResponse(FrameType::kDisconnectedMode, dlci); |
| SendCommand(FrameType::kDisconnect, dlci, [](auto response) { |
| // TODO(NET-1273): implement clean channel close + state reset |
| }); |
| |
| return; |
| } |
| |
| // Start the channel by first responding positively. |
| SendResponse(FrameType::kUnnumberedAcknowledgement, dlci); |
| |
| // Now form the channel and pass it off. |
| auto channel = fbl::AdoptRef(new internal::ChannelImpl(dlci, this)); |
| channels_.emplace(dlci, channel); |
| async::PostTask(dispatcher_, [this, dlci, channel] { |
| channel_opened_cb_(channel, DLCIToServerChannel(dlci)); |
| }); |
| |
| FXL_LOG(INFO) << "rfcomm: Remote peer opened channel with DLCI " |
| << static_cast<unsigned>(dlci); |
| } |
| |
| void Session::HandleMuxCommand(std::unique_ptr<MuxCommand> mux_command) { |
| FXL_DCHECK(mux_command); |
| MuxCommandType type = mux_command->command_type(); |
| |
| if (mux_command->command_response() == CommandResponse::kResponse) { |
| auto dlci = GetDLCIFromMuxCommand(mux_command.get()); |
| auto key = std::make_pair(type, dlci); |
| |
| auto command_it = outstanding_mux_commands_.find(key); |
| if (command_it == outstanding_mux_commands_.end()) { |
| FXL_LOG(WARNING) |
| << "Got response, but no outstanding command for (type, DLCI) = " |
| << "(" << unsigned(type) << ", " << unsigned(dlci) << ")"; |
| return; |
| } |
| |
| // Cancel the timeout and call the callback. |
| command_it->second.second->Cancel(); |
| async::PostTask( |
| dispatcher_, |
| [cmd = std::move(mux_command), |
| cb = std::move(outstanding_mux_commands_[key].first)]() mutable { |
| cb(std::move(cmd)); |
| }); |
| |
| outstanding_mux_commands_.erase(command_it); |
| |
| return; |
| } |
| |
| // Otherwise, it's a command. |
| switch (type) { |
| case MuxCommandType::kDLCParameterNegotiation: { |
| auto pn_command = std::unique_ptr<DLCParameterNegotiationCommand>( |
| static_cast<DLCParameterNegotiationCommand*>(mux_command.release())); |
| ParameterNegotiationParams received_params = pn_command->params(); |
| |
| if (!IsUserDLCI(received_params.dlci)) { |
| FXL_LOG(WARNING) |
| << "rfcomm: Received parameter negotiation command for invalid" |
| << " DLCI " << static_cast<unsigned>(received_params.dlci); |
| SendResponse(FrameType::kDisconnectedMode, received_params.dlci); |
| return; |
| } |
| |
| if (channels_negotiating_.find(received_params.dlci) != |
| channels_negotiating_.end() && |
| channels_negotiating_[received_params.dlci] != |
| ParameterNegotiationState::kNotNegotiated) { |
| // RFCOMM 5.5.3 states that supporting re-negotiation of DLCIs is |
| // optional, and that instead we may just reply with our own parameters. |
| FXL_LOG(WARNING) |
| << "rfcomm: Request to negotiate already-negotiated DLCI"; |
| SendResponse(FrameType::kDisconnectedMode, received_params.dlci); |
| ParameterNegotiationParams our_params = |
| GetIdealParameters(received_params.dlci); |
| our_params.credit_based_flow_handshake = |
| CreditBasedFlowHandshake::kSupportedResponse; |
| our_params.maximum_frame_size = maximum_frame_size_; |
| SendMuxCommand(std::make_unique<DLCParameterNegotiationCommand>( |
| CommandResponse::kResponse, our_params)); |
| return; |
| } |
| |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiated && |
| received_params.maximum_frame_size != maximum_frame_size_) { |
| // RFCOMM 5.5.3 states that a responder may issue a DM frame if they are |
| // unwilling to establish a connection. In this case, we use it to |
| // reject any non-initial PN command which attempts to change the |
| // maximum frame size. |
| FXL_LOG(WARNING) << "rfcomm: Peer requested different max frame size" |
| << " after initial negotiation complete; rejecting"; |
| SendResponse(FrameType::kDisconnectedMode, received_params.dlci); |
| return; |
| } |
| |
| ParameterNegotiationParams ideal_params = |
| GetIdealParameters(received_params.dlci); |
| |
| // Parameter negotiation described in GSM 5.4.6.3.1 (under table 5). |
| ParameterNegotiationParams negotiated_params; |
| // DLCI does not change. |
| negotiated_params.dlci = received_params.dlci; |
| // Respond with a positive credit-based flow handshake response iff we |
| // received a positive request. |
| negotiated_params.credit_based_flow_handshake = |
| received_params.credit_based_flow_handshake == |
| CreditBasedFlowHandshake::kSupportedRequest |
| ? CreditBasedFlowHandshake::kSupportedResponse |
| : CreditBasedFlowHandshake::kUnsupported; |
| // Priority does not change. |
| negotiated_params.priority = received_params.priority; |
| |
| // Accept their max frame size if this is the initial negotiation and if |
| // it's less than or equal to ours; otherwise, use ours. |
| if (initial_param_negotiation_state_ != |
| ParameterNegotiationState::kNegotiated) { |
| negotiated_params.maximum_frame_size = |
| received_params.maximum_frame_size <= |
| ideal_params.maximum_frame_size |
| ? received_params.maximum_frame_size |
| : ideal_params.maximum_frame_size; |
| } else { |
| FXL_DCHECK(received_params.maximum_frame_size == maximum_frame_size_); |
| negotiated_params.maximum_frame_size = maximum_frame_size_; |
| } |
| |
| negotiated_params.initial_credits = ideal_params.initial_credits; |
| |
| // Update settings. |
| |
| if (initial_param_negotiation_state_ != |
| ParameterNegotiationState::kNegotiated) { |
| // Set credit-based flow and max frame size only on initial PN. |
| credit_based_flow_ = received_params.credit_based_flow_handshake == |
| CreditBasedFlowHandshake::kSupportedRequest; |
| maximum_frame_size_ = negotiated_params.maximum_frame_size; |
| InitialParameterNegotiationComplete(); |
| } |
| |
| // TODO(NET-1130): set priority if/when priority is implemented. |
| |
| // TODO(NET-1079): receive credits when credit-based flow is implemented. |
| |
| FXL_LOG(INFO) << "rfcomm: Parameters negotiated:" |
| << " DLCI " << unsigned(negotiated_params.dlci) << "," |
| << " Credit-based flow " |
| << (negotiated_params.credit_based_flow_handshake == |
| CreditBasedFlowHandshake::kSupportedResponse |
| ? "on," |
| : "off,") |
| << " (credits " |
| << static_cast<unsigned>(negotiated_params.initial_credits) |
| << ")," |
| << " Priority " |
| << static_cast<unsigned>(negotiated_params.priority) << "," |
| << " Max frame size " |
| << negotiated_params.maximum_frame_size; |
| |
| // Respond with the negotiated params. |
| SendMuxCommand(std::make_unique<DLCParameterNegotiationCommand>( |
| CommandResponse::kResponse, negotiated_params)); |
| |
| channels_negotiating_[negotiated_params.dlci] = |
| ParameterNegotiationState::kNegotiated; |
| |
| return; |
| } |
| default: { |
| FXL_NOTIMPLEMENTED(); |
| break; |
| } |
| } |
| } |
| |
| void Session::SetMultiplexerStarted(Role role) { |
| FXL_DCHECK(role == Role::kInitiator || role == Role::kResponder); |
| |
| role_ = role; |
| FXL_LOG(INFO) << "rfcomm: Multiplexer started. Role: " |
| << (role == Role::kInitiator ? "initiator" : "responder"); |
| |
| // Run any pending tasks. |
| while (!tasks_pending_mux_startup_.empty()) { |
| async::PostTask(dispatcher_, std::move(tasks_pending_mux_startup_.front())); |
| tasks_pending_mux_startup_.pop(); |
| } |
| |
| // TODO(gusss): send frames from queue when queueing implemented |
| } |
| |
| void Session::Closedown() { |
| FXL_LOG(INFO) << "rfcomm: Closing session"; |
| // Deactivates the channel. |
| l2cap_channel_ = nullptr; |
| } |
| |
| void Session::RunInitialParameterNegotiation(DLCI dlci) { |
| FXL_DCHECK(multiplexer_started()) |
| << "Parameter negotiation requested before multiplexer started"; |
| FXL_DCHECK(initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNotNegotiated) |
| << "Initial parameter negotiation already run"; |
| |
| // Mark the DLCI as in the negotiating state. |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNegotiating; |
| initial_param_negotiation_state_ = ParameterNegotiationState::kNegotiating; |
| |
| auto params = GetIdealParameters(dlci); |
| |
| auto pn_command = std::make_unique<DLCParameterNegotiationCommand>( |
| CommandResponse::kCommand, params); |
| SendMuxCommand(std::move(pn_command), [this, dlci, priority = params.priority, |
| maximum_frame_size = |
| params.maximum_frame_size]( |
| auto mux_command) { |
| FXL_DCHECK(initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating || |
| initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiated); |
| |
| if (mux_command == nullptr) { |
| // A response of nullptr signals a DM response from the peer. |
| FXL_LOG(INFO) << "rfcomm: PN command for DLCI " |
| << static_cast<unsigned>(dlci) << " rejected"; |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNotNegotiated; |
| |
| // If initial parameter negotiation didn't already finish since we started |
| // this PN command, then reset it back to kNotNegotiated. |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating) |
| initial_param_negotiation_state_ = |
| ParameterNegotiationState::kNotNegotiated; |
| return; |
| } |
| |
| FXL_DCHECK(mux_command->command_type() == |
| MuxCommandType::kDLCParameterNegotiation && |
| mux_command->command_response() == CommandResponse::kResponse); |
| |
| auto pn_response = std::unique_ptr<DLCParameterNegotiationCommand>( |
| static_cast<DLCParameterNegotiationCommand*>(mux_command.release())); |
| auto params = pn_response->params(); |
| |
| if (dlci != params.dlci) { |
| FXL_LOG(WARNING) << "rfcomm: Remote changed DLCI in PN response"; |
| SendCommand(FrameType::kDisconnect, dlci); |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNotNegotiated; |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating) |
| initial_param_negotiation_state_ = |
| ParameterNegotiationState::kNotNegotiated; |
| return; |
| } |
| |
| // TODO(gusss): currently we completely ignore priority (other than this |
| // check) |
| if (params.priority != priority) |
| FXL_LOG(WARNING) << "rfcomm: Remote changed priority in PN response"; |
| |
| if (params.maximum_frame_size > maximum_frame_size) { |
| FXL_LOG(WARNING) |
| << "rfcomm: Peer's PN response contained an invalid max frame size"; |
| SendCommand(FrameType::kDisconnect, dlci); |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNotNegotiated; |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating) |
| initial_param_negotiation_state_ = |
| ParameterNegotiationState::kNotNegotiated; |
| return; |
| } |
| |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiated && |
| params.maximum_frame_size != maximum_frame_size_) { |
| FXL_LOG(WARNING) |
| << "rfcomm: Peer tried to change max frame size after initial param" |
| << " negotiation completed; rejecting"; |
| SendCommand(FrameType::kDisconnect, dlci); |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNotNegotiated; |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating) |
| initial_param_negotiation_state_ = |
| ParameterNegotiationState::kNotNegotiated; |
| return; |
| } |
| |
| // Only set these parameters on initial parameter negotiation. |
| if (initial_param_negotiation_state_ == |
| ParameterNegotiationState::kNegotiating) { |
| // Credit-based flow is turned on if the peer sends the correct |
| // response. |
| credit_based_flow_ = params.credit_based_flow_handshake == |
| CreditBasedFlowHandshake::kSupportedResponse; |
| |
| maximum_frame_size_ = params.maximum_frame_size; |
| InitialParameterNegotiationComplete(); |
| } |
| |
| // TODO(NET-1079): Handle credits here when credit-based flow implemented |
| // HandleReceivedCredits(dlci, params.initial_credits); |
| |
| FXL_LOG(INFO) << "rfcomm: Parameters negotiated:" |
| << " DLCI " << unsigned(params.dlci) << "," |
| << " Credit-based flow " |
| << (credit_based_flow_ ? "on" : "off") << " (credits " |
| << static_cast<unsigned>(params.initial_credits) << ")," |
| << " Priority " << static_cast<unsigned>(params.priority) |
| << "," |
| << " Max frame size " << maximum_frame_size_; |
| |
| // Set channel to not negotiating anymore. |
| FXL_DCHECK(channels_negotiating_.find(dlci) != channels_negotiating_.end()); |
| channels_negotiating_[dlci] = ParameterNegotiationState::kNegotiated; |
| }); |
| } |
| |
| ParameterNegotiationParams Session::GetIdealParameters(DLCI dlci) const { |
| FXL_DCHECK(IsValidDLCI(dlci)); |
| |
| // We set the MTU of the RFCOMM channel based on the MTUs of the underlying |
| // L2CAP link; we take the minimum of the two. |
| uint16_t maximum_frame_size = |
| (l2cap_channel_->rx_mtu() < l2cap_channel_->tx_mtu() |
| ? l2cap_channel_->rx_mtu() |
| : l2cap_channel_->tx_mtu()); |
| |
| // GSM Table 27. |
| Priority priority = 61; |
| if (dlci == kMuxControlDLCI) { |
| priority = 0; |
| } else if (dlci <= 7) { |
| priority = 7; |
| } else if (dlci <= 15) { |
| priority = 15; |
| } else if (dlci <= 23) { |
| priority = 23; |
| } else if (dlci <= 31) { |
| priority = 31; |
| } else if (dlci <= 39) { |
| priority = 39; |
| } else if (dlci <= 47) { |
| priority = 47; |
| } else if (dlci <= 55) { |
| priority = 55; |
| } |
| |
| return {dlci, |
| // We always attempt to enable credit-based flow (RFCOMM 5.5.3). |
| CreditBasedFlowHandshake::kSupportedRequest, |
| // TODO(NET-1079): send initial credits when credit-based flow |
| // implemented. |
| priority, maximum_frame_size, 0}; |
| } |
| |
| void Session::InitialParameterNegotiationComplete() { |
| FXL_LOG(INFO) << "rfcomm: Initial parameter negotiation complete"; |
| |
| initial_param_negotiation_state_ = ParameterNegotiationState::kNegotiated; |
| |
| while (!tasks_pending_parameter_negotiation_.empty()) { |
| async::PostTask(dispatcher_, |
| std::move(tasks_pending_parameter_negotiation_.front())); |
| tasks_pending_parameter_negotiation_.pop(); |
| } |
| |
| // TODO(gusss): send frames from queue when queueing is implemented. |
| } |
| |
| } // namespace rfcomm |
| } // namespace btlib |