| // Copyright 2017 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 "signaling_channel.h" |
| |
| #include <lib/async/default.h> |
| #include <lib/fit/function.h> |
| #include <zircon/assert.h> |
| |
| #include "channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/slab_allocator.h" |
| |
| namespace bt { |
| namespace l2cap { |
| namespace internal { |
| |
| SignalingChannel::SignalingChannel(fbl::RefPtr<Channel> chan, |
| hci::Connection::Role role) |
| : is_open_(true), |
| chan_(std::move(chan)), |
| role_(role), |
| next_cmd_id_(0x01), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(chan_); |
| ZX_DEBUG_ASSERT(chan_->id() == kSignalingChannelId || |
| chan_->id() == kLESignalingChannelId); |
| |
| // Note: No need to guard against out-of-thread access as these callbacks are |
| // called on the L2CAP thread. |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| chan_->Activate( |
| [self](ByteBufferPtr sdu) { |
| if (self) |
| self->OnRxBFrame(std::move(sdu)); |
| }, |
| [self] { |
| if (self) |
| self->OnChannelClosed(); |
| }, |
| async_get_default_dispatcher()); |
| } |
| |
| SignalingChannel::~SignalingChannel() { |
| ZX_DEBUG_ASSERT(IsCreationThreadCurrent()); |
| } |
| |
| SignalingChannel::ResponderImpl::ResponderImpl(SignalingChannel* sig, |
| CommandCode code, CommandId id) |
| : sig_(sig), code_(code), id_(id) { |
| ZX_DEBUG_ASSERT(sig_); |
| } |
| |
| void SignalingChannel::ResponderImpl::Send(const ByteBuffer& rsp_payload) { |
| sig()->SendPacket(code_, id_, rsp_payload); |
| } |
| |
| void SignalingChannel::ResponderImpl::RejectNotUnderstood() { |
| sig()->SendCommandReject(id_, RejectReason::kNotUnderstood, BufferView()); |
| } |
| |
| void SignalingChannel::ResponderImpl::RejectInvalidChannelId( |
| ChannelId local_cid, ChannelId remote_cid) { |
| uint16_t ids[2]; |
| ids[0] = htole16(local_cid); |
| ids[1] = htole16(remote_cid); |
| sig()->SendCommandReject(id_, RejectReason::kInvalidCID, |
| BufferView(ids, sizeof(ids))); |
| } |
| |
| bool SignalingChannel::SendPacket(CommandCode code, uint8_t identifier, |
| const ByteBuffer& data) { |
| ZX_DEBUG_ASSERT(IsCreationThreadCurrent()); |
| return Send(BuildPacket(code, identifier, data)); |
| } |
| |
| bool SignalingChannel::Send(ByteBufferPtr packet) { |
| ZX_DEBUG_ASSERT(IsCreationThreadCurrent()); |
| ZX_DEBUG_ASSERT(packet); |
| ZX_DEBUG_ASSERT(packet->size() >= sizeof(CommandHeader)); |
| |
| if (!is_open()) |
| return false; |
| |
| // While 0x00 is an illegal command identifier (see v5.0, Vol 3, Part A, |
| // Section 4) we don't assert that here. When we receive a command that uses |
| // 0 as the identifier, we reject the command and use that identifier in the |
| // response rather than assert and crash. |
| __UNUSED SignalingPacket reply(packet.get(), |
| packet->size() - sizeof(CommandHeader)); |
| ZX_DEBUG_ASSERT(reply.header().code); |
| ZX_DEBUG_ASSERT(reply.payload_size() == le16toh(reply.header().length)); |
| ZX_DEBUG_ASSERT(chan_); |
| |
| return chan_->Send(std::move(packet)); |
| } |
| |
| ByteBufferPtr SignalingChannel::BuildPacket(CommandCode code, |
| uint8_t identifier, |
| const ByteBuffer& data) { |
| ZX_DEBUG_ASSERT(data.size() <= std::numeric_limits<uint16_t>::max()); |
| |
| auto buffer = NewSlabBuffer(sizeof(CommandHeader) + data.size()); |
| ZX_ASSERT(buffer); |
| |
| MutableSignalingPacket packet(buffer.get(), data.size()); |
| packet.mutable_header()->code = code; |
| packet.mutable_header()->id = identifier; |
| packet.mutable_header()->length = htole16(static_cast<uint16_t>(data.size())); |
| packet.mutable_payload_data().Write(data); |
| |
| return buffer; |
| } |
| |
| bool SignalingChannel::SendCommandReject(uint8_t identifier, |
| RejectReason reason, |
| const ByteBuffer& data) { |
| ZX_DEBUG_ASSERT(data.size() <= kCommandRejectMaxDataLength); |
| |
| constexpr size_t kMaxPayloadLength = |
| sizeof(CommandRejectPayload) + kCommandRejectMaxDataLength; |
| StaticByteBuffer<kMaxPayloadLength> rej_buf; |
| |
| MutablePacketView<CommandRejectPayload> reject(&rej_buf, data.size()); |
| reject.mutable_header()->reason = htole16(reason); |
| reject.mutable_payload_data().Write(data); |
| |
| return SendPacket(kCommandRejectCode, identifier, reject.data()); |
| } |
| |
| CommandId SignalingChannel::GetNextCommandId() { |
| // Recycling identifiers is permitted and only 0x00 is invalid (v5.0 Vol 3, |
| // Part A, Section 4). |
| const auto cmd = next_cmd_id_++; |
| if (next_cmd_id_ == kInvalidCommandId) { |
| next_cmd_id_ = 0x01; |
| } |
| |
| return cmd; |
| } |
| |
| void SignalingChannel::OnChannelClosed() { |
| ZX_DEBUG_ASSERT(IsCreationThreadCurrent()); |
| ZX_DEBUG_ASSERT(is_open()); |
| |
| is_open_ = false; |
| } |
| |
| void SignalingChannel::OnRxBFrame(ByteBufferPtr sdu) { |
| ZX_DEBUG_ASSERT(IsCreationThreadCurrent()); |
| |
| if (!is_open()) |
| return; |
| |
| DecodeRxUnit( |
| std::move(sdu), |
| fit::bind_member(this, &SignalingChannel::CheckAndDispatchPacket)); |
| } |
| |
| void SignalingChannel::CheckAndDispatchPacket(const SignalingPacket& packet) { |
| if (packet.size() > mtu()) { |
| // Respond with our signaling MTU. |
| uint16_t rsp_mtu = htole16(mtu()); |
| BufferView rej_data(&rsp_mtu, sizeof(rsp_mtu)); |
| SendCommandReject(packet.header().id, RejectReason::kSignalingMTUExceeded, |
| rej_data); |
| } else if (!packet.header().id) { |
| // "Signaling identifier 0x00 is an illegal identifier and shall never be |
| // used in any command" (v5.0, Vol 3, Part A, Section 4). |
| bt_log(TRACE, "l2cap", "illegal signaling cmd ID: 0x00; reject"); |
| SendCommandReject(packet.header().id, RejectReason::kNotUnderstood, |
| BufferView()); |
| } else if (!HandlePacket(packet)) { |
| SendCommandReject(packet.header().id, RejectReason::kNotUnderstood, |
| BufferView()); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace l2cap |
| } // namespace bt |