| // Copyright 2020 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/test_packets.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fcs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/frame_headers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" |
| |
| namespace bt::l2cap::testing { |
| |
| DynamicByteBuffer AclExtFeaturesInfoReq(l2cap::CommandId id, |
| hci_spec::ConnectionHandle handle) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle, length: 10) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x0a, |
| 0x00, |
| |
| // L2CAP B-frame header (length: 6, chanel-id: 0x0001 (ACL sig)) |
| 0x06, |
| 0x00, |
| 0x01, |
| 0x00, |
| |
| // Extended Features Information Request |
| // (ID, length: 2, type) |
| 0x0a, |
| id, |
| 0x02, |
| 0x00, |
| LowerBits( |
| static_cast<uint16_t>(InformationType::kExtendedFeaturesSupported)), |
| UpperBits( |
| static_cast<uint16_t>(InformationType::kExtendedFeaturesSupported)))); |
| } |
| |
| DynamicByteBuffer AclCommandRejectNotUnderstoodRsp( |
| l2cap::CommandId id, hci_spec::ConnectionHandle handle, ChannelId chan_id) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 10 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x0a, |
| 0x00, |
| // L2CAP B-frame header (length: 6 bytes, channel-id: 0x0001 (ACL sig)) |
| 0x06, |
| 0x00, |
| LowerBits(chan_id), |
| UpperBits(chan_id), |
| // Information Response (type, ID, length: 2) |
| l2cap::kCommandRejectCode, |
| id, |
| 0x02, |
| 0x00, |
| // Reason = Not Understood |
| LowerBits(static_cast<uint16_t>(RejectReason::kNotUnderstood)), |
| UpperBits(static_cast<uint16_t>(RejectReason::kNotUnderstood)))); |
| } |
| |
| DynamicByteBuffer AclExtFeaturesInfoRsp(l2cap::CommandId id, |
| hci_spec::ConnectionHandle handle, |
| l2cap::ExtendedFeatures features) { |
| const auto features_bytes = ToBytes(features); |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 16 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x10, |
| 0x00, |
| // L2CAP B-frame header (length: 12 bytes, channel-id: 0x0001 (ACL sig)) |
| 0x0c, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Information Response (type, ID, length: 8) |
| l2cap::kInformationResponse, |
| id, |
| 0x08, |
| 0x00, |
| // Type = Features Mask |
| 0x02, |
| 0x00, |
| // Result = Success |
| 0x00, |
| 0x00, |
| // Data (Mask) |
| features_bytes[0], |
| features_bytes[1], |
| features_bytes[2], |
| features_bytes[3])); |
| } |
| |
| DynamicByteBuffer AclFixedChannelsSupportedInfoReq( |
| l2cap::CommandId id, hci_spec::ConnectionHandle handle) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle, length: 10) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x0a, |
| 0x00, |
| |
| // L2CAP B-frame header (length: 6, chanel-id: 0x0001 (ACL sig)) |
| 0x06, |
| 0x00, |
| 0x01, |
| 0x00, |
| |
| // Fixed Channels Supported Information Request |
| // (ID, length: 2, info type) |
| l2cap::kInformationRequest, |
| id, |
| 0x02, |
| 0x00, |
| LowerBits( |
| static_cast<uint16_t>(InformationType::kFixedChannelsSupported)), |
| UpperBits( |
| static_cast<uint16_t>(InformationType::kFixedChannelsSupported)))); |
| } |
| |
| DynamicByteBuffer AclFixedChannelsSupportedInfoRsp( |
| l2cap::CommandId id, |
| hci_spec::ConnectionHandle handle, |
| l2cap::FixedChannelsSupported chan_mask) { |
| const auto chan_bytes = ToBytes(chan_mask); |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 20 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x14, |
| 0x00, |
| // L2CAP B-frame header (length: 16 bytes, channel-id: 0x0001 (ACL sig)) |
| 0x10, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Information Response (type, ID, length: 12) |
| l2cap::kInformationResponse, |
| id, |
| 0x0c, |
| 0x00, |
| // Type = Fixed Channels Supported |
| 0x03, |
| 0x00, |
| // Result = Success |
| 0x00, |
| 0x00, |
| // Data (Mask) |
| chan_bytes[0], |
| chan_bytes[1], |
| chan_bytes[2], |
| chan_bytes[3], |
| chan_bytes[4], |
| chan_bytes[5], |
| chan_bytes[6], |
| chan_bytes[7])); |
| } |
| |
| DynamicByteBuffer AclNotSupportedInformationResponse( |
| l2cap::CommandId id, hci_spec::ConnectionHandle handle) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 12 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x0c, |
| 0x00, |
| // L2CAP B-frame header (length: 8 bytes, channel-id: 0x0001 (ACL sig)) |
| 0x08, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Information Response (type, ID, length: 4) |
| l2cap::kInformationResponse, |
| id, |
| 0x04, |
| 0x00, |
| // Type = invalid type |
| 0xFF, |
| 0xFF, |
| // Result |
| LowerBits(static_cast<uint16_t>(l2cap::InformationResult::kNotSupported)), |
| UpperBits( |
| static_cast<uint16_t>(l2cap::InformationResult::kNotSupported)))); |
| } |
| |
| DynamicByteBuffer AclConfigReq(l2cap::CommandId id, |
| hci_spec::ConnectionHandle handle, |
| l2cap::ChannelId dst_id, |
| l2cap::ChannelParameters params) { |
| const auto any_mode = |
| params.mode.value_or(l2cap::RetransmissionAndFlowControlMode::kBasic); |
| const auto mtu = params.max_rx_sdu_size.value_or(l2cap::kMaxMTU); |
| |
| BT_ASSERT_MSG( |
| std::holds_alternative<l2cap::RetransmissionAndFlowControlMode>(any_mode), |
| "Channel mode is unsupported for configuration request."); |
| const auto mode = std::get<l2cap::RetransmissionAndFlowControlMode>(any_mode); |
| switch (mode) { |
| case l2cap::RetransmissionAndFlowControlMode::kBasic: |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle, length: 16 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x10, |
| 0x00, |
| // L2CAP B-frame header (length: 12, channel-id: 0x0001 (ACL sig)) |
| 0x0c, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Configuration Request (ID, length: 8, |dst_id|, flags: 0, |
| 0x04, |
| id, |
| 0x08, |
| 0x00, |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| 0x00, |
| 0x00, |
| // MTU option: (ID: 1, length: 2, mtu) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu))); |
| case l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission: |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle, length: 27 bytes) |
| LowerBits(handle), |
| UpperBits(handle), |
| 0x1b, |
| 0x00, |
| // L2CAP B-frame header (length: 23, channel-id: 0x0001 (ACL sig)) |
| 0x17, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Configuration Request (ID, length: 19, |dst_id|, flags: 0, |
| 0x04, |
| id, |
| 0x13, |
| 0x00, |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| 0x00, |
| 0x00, |
| // MTU option: (ID: 1, length: 2, mtu) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu), |
| // Retransmission & Flow Control option (Type, Length = 9, mode, |
| // fields) |
| 0x04, |
| 0x09, |
| static_cast<uint8_t>(mode), |
| l2cap::kErtmMaxUnackedInboundFrames, |
| l2cap::kErtmMaxInboundRetransmissions, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| LowerBits(l2cap::kMaxInboundPduPayloadSize), |
| UpperBits(l2cap::kMaxInboundPduPayloadSize))); |
| default: |
| BT_ASSERT_MSG(false, "unsupported mode"); |
| } |
| } |
| |
| DynamicByteBuffer AclConfigRsp(l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId src_id, |
| l2cap::ChannelParameters params) { |
| const auto any_mode = |
| params.mode.value_or(l2cap::RetransmissionAndFlowControlMode::kBasic); |
| const auto mtu = params.max_rx_sdu_size.value_or(l2cap::kMaxMTU); |
| |
| BT_ASSERT_MSG( |
| std::holds_alternative<l2cap::RetransmissionAndFlowControlMode>(any_mode), |
| "Channel mode is unsupported for configuration response."); |
| const auto mode = std::get<l2cap::RetransmissionAndFlowControlMode>(any_mode); |
| switch (mode) { |
| case l2cap::RetransmissionAndFlowControlMode::kBasic: |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 18 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x12, |
| 0x00, |
| // L2CAP B-frame header (length: 14 bytes, channel-id: 0x0001 (ACL |
| // sig)) |
| 0x0e, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Configuration Response (ID, length: 10, src cid, flags: 0, |
| // result: success) |
| 0x05, |
| id, |
| 0x0a, |
| 0x00, |
| LowerBits(src_id), |
| UpperBits(src_id), |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| // MTU option: (ID: 1, length: 2, mtu) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu))); |
| case l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission: { |
| const auto rtx_timeout = kErtmReceiverReadyPollTimerMsecs; |
| const auto monitor_timeout = kErtmMonitorTimerMsecs; |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 29 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x1d, |
| 0x00, |
| // L2CAP B-frame header (length: 25 bytes, channel-id: 0x0001 (ACL |
| // sig)) |
| 0x19, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Configuration Response (ID, length: 21, src cid, flags: 0, |
| // result: success) |
| 0x05, |
| id, |
| 0x15, |
| 0x00, |
| LowerBits(src_id), |
| UpperBits(src_id), |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| // MTU option: (ID: 1, length: 2, mtu) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu), |
| // Retransmission & Flow Control option (Type, Length = 9, mode, |
| // fields) |
| 0x04, |
| 0x09, |
| static_cast<uint8_t>(mode), |
| l2cap::kErtmMaxUnackedInboundFrames, |
| l2cap::kErtmMaxInboundRetransmissions, |
| LowerBits(rtx_timeout), |
| UpperBits(rtx_timeout), |
| LowerBits(monitor_timeout), |
| UpperBits(monitor_timeout), |
| LowerBits(l2cap::kMaxInboundPduPayloadSize), |
| UpperBits(l2cap::kMaxInboundPduPayloadSize))); |
| } |
| default: |
| BT_ASSERT_MSG(false, "unsupported mode"); |
| } |
| } |
| |
| DynamicByteBuffer AclConnectionReq(l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId src_id, |
| l2cap::Psm psm) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link_handle|, length: 12 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x0c, |
| 0x00, |
| |
| // L2CAP B-frame header (length: 8 bytes, channel-id: 0x0001 (ACL sig)) |
| 0x08, |
| 0x00, |
| 0x01, |
| 0x00, |
| |
| // Connection Request (ID, length: 4, |psm|, |src_id|) |
| 0x02, |
| id, |
| 0x04, |
| 0x00, |
| LowerBits(psm), |
| UpperBits(psm), |
| LowerBits(src_id), |
| UpperBits(src_id))); |
| } |
| |
| DynamicByteBuffer AclConnectionRsp(l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId src_id, |
| l2cap::ChannelId dst_id, |
| l2cap::ConnectionResult result) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link handle|, length: 16 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x10, |
| 0x00, |
| // L2CAP B-frame header: length 12, channel-id 1 (signaling) |
| 0x0c, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Connection Response (0x03), id, length 8 |
| l2cap::kConnectionResponse, |
| id, |
| 0x08, |
| 0x00, |
| // destination cid |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| // source cid |
| LowerBits(src_id), |
| UpperBits(src_id), |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result)), |
| // Status (no further information available) |
| 0x00, |
| 0x00)); |
| } |
| |
| DynamicByteBuffer AclDisconnectionReq(l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId src_id, |
| l2cap::ChannelId dst_id) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link handle|, length: 12 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x0c, |
| 0x00, |
| // L2CAP B-frame header: length 8, channel-id 1 (signaling) |
| 0x08, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Disconnection Request, id, length 4 |
| l2cap::kDisconnectionRequest, |
| id, |
| 0x04, |
| 0x00, |
| // Destination CID |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id))); |
| } |
| |
| DynamicByteBuffer AclDisconnectionRsp(l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId src_id, |
| l2cap::ChannelId dst_id) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link handle|, length: 12 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x0c, |
| 0x00, |
| // L2CAP B-frame header: length 8, channel-id 1 (signaling) |
| 0x08, |
| 0x00, |
| 0x01, |
| 0x00, |
| // Disconnection Response, id, length 4 |
| l2cap::kDisconnectionResponse, |
| id, |
| 0x04, |
| 0x00, |
| // Destination CID |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id))); |
| } |
| |
| DynamicByteBuffer AclConnectionParameterUpdateReq( |
| l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| uint16_t interval_min, |
| uint16_t interval_max, |
| uint16_t peripheral_latency, |
| uint16_t timeout_multiplier) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link handle|, length: 16 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x10, |
| 0x00, |
| // L2CAP B-frame header: length 12, channel-id 5 (LE signaling) |
| 0x0c, |
| 0x00, |
| 0x05, |
| 0x00, |
| // Connection Parameter Update Request (0x12), id, length 8 |
| l2cap::kConnectionParameterUpdateRequest, |
| id, |
| 0x08, |
| 0x00, |
| // interval min |
| LowerBits(interval_min), |
| UpperBits(interval_min), |
| // interval max |
| LowerBits(interval_max), |
| UpperBits(interval_max), |
| // peripheral latency |
| LowerBits(peripheral_latency), |
| UpperBits(peripheral_latency), |
| // timeout multiplier |
| LowerBits(timeout_multiplier), |
| UpperBits(timeout_multiplier))); |
| } |
| |
| DynamicByteBuffer AclConnectionParameterUpdateRsp( |
| l2cap::CommandId id, |
| hci_spec::ConnectionHandle link_handle, |
| ConnectionParameterUpdateResult result) { |
| return DynamicByteBuffer(StaticByteBuffer( |
| // ACL data header (handle: |link handle|, length: 10 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x0a, |
| 0x00, |
| // L2CAP B-frame header: length 6, channel-id 5 (LE signaling) |
| 0x06, |
| 0x00, |
| 0x05, |
| 0x00, |
| // Connection Parameter Update Response (0x13), id, length 2 |
| l2cap::kConnectionParameterUpdateResponse, |
| id, |
| 0x02, |
| 0x00, |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result)))); |
| } |
| |
| DynamicByteBuffer AclSFrame(hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId channel_id, |
| l2cap::internal::SupervisoryFunction function, |
| uint8_t receive_seq_num, |
| bool is_poll_request, |
| bool is_poll_response) { |
| StaticByteBuffer acl_packet{ |
| // ACL data header (handle: |link handle|, length: 8 bytes) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| 0x08, |
| 0x00, |
| |
| // L2CAP B-frame header: length 4, channel-id |
| 0x04, |
| 0x00, |
| LowerBits(channel_id), |
| UpperBits(channel_id), |
| |
| // Enhanced Control Field: F is_poll_response, P is_poll_request, |
| // Supervisory function, |
| // Type S-Frame, ReqSeq receive_seq_num |
| (is_poll_response ? 0b1000'0000 : 0) | (is_poll_request ? 0b1'0000 : 0) | |
| (static_cast<uint8_t>(function) << 2) | 0b1, |
| receive_seq_num & 0b11'1111, |
| |
| // Frame Check Sequence |
| 0x00, |
| 0x00}; |
| const FrameCheckSequence fcs = ComputeFcs( |
| acl_packet.view(sizeof(hci_spec::ACLDataHeader), |
| acl_packet.size() - sizeof(hci_spec::ACLDataHeader) - |
| sizeof(FrameCheckSequence))); |
| acl_packet[acl_packet.size() - 2] = LowerBits(fcs.fcs); |
| acl_packet[acl_packet.size() - 1] = UpperBits(fcs.fcs); |
| return DynamicByteBuffer(acl_packet); |
| } |
| |
| DynamicByteBuffer AclIFrame(hci_spec::ConnectionHandle link_handle, |
| l2cap::ChannelId channel_id, |
| uint8_t receive_seq_num, |
| uint8_t tx_seq, |
| bool is_poll_response, |
| const ByteBuffer& payload) { |
| const uint16_t l2cap_size = |
| static_cast<uint16_t>(sizeof(internal::SimpleInformationFrameHeader) + |
| payload.size() + sizeof(FrameCheckSequence)); |
| const uint16_t acl_size = l2cap_size + sizeof(BasicHeader); |
| StaticByteBuffer headers( |
| // ACL data header (handle: |link handle|, length) |
| LowerBits(link_handle), |
| UpperBits(link_handle), |
| LowerBits(acl_size), |
| UpperBits(acl_size), |
| |
| // L2CAP B-frame header: length, channel-id |
| LowerBits(l2cap_size), |
| UpperBits(l2cap_size), |
| LowerBits(channel_id), |
| UpperBits(channel_id), |
| |
| // Enhanced Control Field: F is_poll_response, TxSeq tx_seq, Type I-Frame, |
| // ReqSeq receive_seq_num |
| (is_poll_response ? 0b1000'0000 : 0) | ((tx_seq << 1) & 0b111'1110), |
| receive_seq_num & 0b11'1111); |
| |
| FrameCheckSequence fcs = |
| ComputeFcs(headers.view(sizeof(hci_spec::ACLDataHeader), acl_size)); |
| fcs = ComputeFcs(payload.view(), fcs); |
| |
| DynamicByteBuffer acl_packet(headers.size() + payload.size() + sizeof(fcs)); |
| headers.Copy(&acl_packet); |
| auto payload_destination = acl_packet.mutable_view(headers.size()); |
| payload.Copy(&payload_destination); |
| acl_packet[acl_packet.size() - 2] = LowerBits(fcs.fcs); |
| acl_packet[acl_packet.size() - 1] = UpperBits(fcs.fcs); |
| return acl_packet; |
| } |
| |
| } // namespace bt::l2cap::testing |