1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/enhanced_retransmission_mode_rx_engine.h"
16
17 #include <type_traits>
18
19 namespace bt::l2cap::internal {
20
21 namespace {
22
23 template <typename T>
TryCopyFromPdu(const PDU & pdu)24 std::optional<T> TryCopyFromPdu(const PDU& pdu) {
25 if (pdu.length() < sizeof(T))
26 return std::nullopt;
27
28 StaticByteBuffer<sizeof(T)> buf;
29 pdu.Copy(&buf, 0, sizeof(T));
30 return buf.template To<T>();
31 }
32
33 std::variant<std::monostate,
34 const SimpleInformationFrameHeader,
35 const SimpleStartOfSduFrameHeader,
36 const SimpleSupervisoryFrame>
GetFrameHeaderFromPdu(const PDU & pdu)37 GetFrameHeaderFromPdu(const PDU& pdu) {
38 const auto control_field_opt = TryCopyFromPdu<EnhancedControlField>(pdu);
39 if (!control_field_opt) {
40 // TODO(fxbug.dev/42080889): Add metric counting runt frames.
41 return std::monostate();
42 }
43
44 const auto& control_field = control_field_opt.value();
45 if (control_field.designates_supervisory_frame()) {
46 const auto frame_opt = TryCopyFromPdu<SimpleSupervisoryFrame>(pdu);
47 if (!frame_opt) {
48 // TODO(fxbug.dev/42080889): Add metric counting runt S-frames.
49 return std::monostate();
50 }
51 return frame_opt.value();
52 }
53
54 if (control_field.designates_start_of_segmented_sdu()) {
55 const auto frame_opt = TryCopyFromPdu<SimpleStartOfSduFrameHeader>(pdu);
56 if (!frame_opt) {
57 // TODO(fxbug.dev/42080889): Add metric counting runt Start-of-SDU frames.
58 return std::monostate();
59 }
60 return frame_opt.value();
61 }
62
63 const auto frame_opt = TryCopyFromPdu<SimpleInformationFrameHeader>(pdu);
64 if (!frame_opt) {
65 // TODO(fxbug.dev/42080889): Add metric counting runt I-frames.
66 return std::monostate();
67 }
68 return frame_opt.value();
69 }
70
71 template <typename T>
72 constexpr bool kContainsEnhancedControlField =
73 std::is_base_of_v<EnhancedControlField, T>;
74
IsMpsValid(const PDU &)75 bool IsMpsValid(const PDU&) {
76 // TODO(quiche): Check PDU's length against the MPS.
77 return true;
78 }
79
80 } // namespace
81
82 using Engine = EnhancedRetransmissionModeRxEngine;
83
EnhancedRetransmissionModeRxEngine(SendFrameCallback send_frame_callback,ConnectionFailureCallback connection_failure_callback)84 Engine::EnhancedRetransmissionModeRxEngine(
85 SendFrameCallback send_frame_callback,
86 ConnectionFailureCallback connection_failure_callback)
87 : next_seqnum_(0),
88 remote_is_busy_(false),
89 send_frame_callback_(std::move(send_frame_callback)),
90 connection_failure_callback_(std::move(connection_failure_callback)) {}
91
ProcessPdu(PDU pdu)92 ByteBufferPtr Engine::ProcessPdu(PDU pdu) {
93 // A note on validation (see Vol 3, Part A, 3.3.7):
94 //
95 // We skip step 1 (validation of the Channel ID), as a frame with an
96 // unrecognized Channel ID will not be delivered to us. (Various
97 // ChannelManagerTest test cases verify that LogicalLink directs frames
98 // to their proper channels.)
99 //
100 // We skip step 2 (validation of FCS), as we don't support FCS.
101 //
102 // Step 3 (size checking) is implemented in IsMpsValid(), and
103 // GetFrameHeaderFromPdu().
104 //
105 // TODO(quiche): Implement step 4/5 (Check SAR bits, close connection on
106 // error).
107
108 if (!IsMpsValid(pdu)) {
109 // TODO(quiche): Close connection.
110 // TODO(fxbug.dev/42080889): Add metric counting oversized frames.
111 return nullptr;
112 }
113
114 auto frame_header = GetFrameHeaderFromPdu(pdu);
115 auto frame_processor =
116 [this, pdu_to_process = std::move(pdu)](auto header) mutable {
117 // Run ProcessFrame first so it can perform the highest-priority actions
118 // like assigning RemoteBusy (Core Spec v5.0, Vol 3, Part A,
119 // Sec 8.6.5.9).
120 auto sdu = ProcessFrame(header, std::move(pdu_to_process));
121
122 // This implements the PassToTx action ("Pass the ReqSeq and F-bit
123 // value") per Core Spec v5.0, Vol 3, Part A, 8.6.5.6 and must come
124 // after updates to the RemoteBusy variable in order to avoid
125 // transmitting frames when the peer can't accept them.
126 if constexpr (kContainsEnhancedControlField<decltype(header)>) {
127 if (receive_seq_num_callback_) {
128 receive_seq_num_callback_(header.receive_seq_num(),
129 header.is_poll_response());
130 }
131 }
132 return sdu;
133 };
134 return std::visit(std::move(frame_processor), frame_header);
135 }
136
ProcessFrame(const SimpleInformationFrameHeader header,PDU pdu)137 ByteBufferPtr Engine::ProcessFrame(const SimpleInformationFrameHeader header,
138 PDU pdu) {
139 if (header.tx_seq() != next_seqnum_) {
140 // TODO(quiche): Send REJ frame.
141 // TODO(quiche): Add histogram for |header.tx_seq() - next_seqnum_|. This
142 // will give us an upper bound on the potential benefit of sending SREJ
143 // frames.
144 return nullptr;
145 }
146
147 // TODO(quiche): check if the frame is within the permitted window.
148
149 if (header.designates_part_of_segmented_sdu()) {
150 // TODO(quiche): Implement validation and handling of segmented frames.
151 return nullptr;
152 }
153
154 AdvanceSeqNum();
155
156 if (ack_seq_num_callback_) {
157 ack_seq_num_callback_(next_seqnum_);
158 }
159
160 SimpleReceiverReadyFrame ack_frame;
161 ack_frame.set_receive_seq_num(next_seqnum_);
162 send_frame_callback_(std::make_unique<DynamicByteBuffer>(
163 BufferView(&ack_frame, sizeof(ack_frame))));
164
165 const auto header_len = sizeof(header);
166 const auto footer_len = sizeof(FrameCheckSequence);
167 if (pdu.length() < header_len + footer_len) {
168 return nullptr;
169 }
170 const auto payload_len = pdu.length() - header_len - footer_len;
171 auto sdu = std::make_unique<DynamicByteBuffer>(payload_len);
172 pdu.Copy(sdu.get(), header_len, payload_len);
173 return sdu;
174 }
175
ProcessFrame(const SimpleStartOfSduFrameHeader,PDU)176 ByteBufferPtr Engine::ProcessFrame(const SimpleStartOfSduFrameHeader, PDU) {
177 // TODO(quiche): Implement validation and handling of Start-of-SDU frames.
178 return nullptr;
179 }
180
ProcessFrame(const SimpleSupervisoryFrame sframe,PDU)181 ByteBufferPtr Engine::ProcessFrame(const SimpleSupervisoryFrame sframe, PDU) {
182 // Core Spec v5, Vol 3, Part A, Sec 8.6.1.5: "S-Frames shall not be
183 // transmitted with both the F-bit and the P-bit set to 1 at the same time."
184 if (sframe.is_poll_request() && sframe.is_poll_response()) {
185 connection_failure_callback_();
186 return nullptr;
187 }
188
189 // Signal changes to our RemoteBusy variable per Core Spec v5.0, Vol 3, Part
190 // A, Sec 8.6.5.6.
191 const bool remote_is_busy =
192 sframe.function() == SupervisoryFunction::ReceiverNotReady;
193 if (remote_is_busy && !remote_is_busy_) {
194 if (remote_busy_set_callback_) {
195 remote_busy_set_callback_();
196 }
197 } else if (!remote_is_busy && remote_is_busy_) {
198 if (remote_busy_cleared_callback_) {
199 remote_busy_cleared_callback_();
200 }
201 }
202 remote_is_busy_ = remote_is_busy;
203
204 // Implements the "Send RRorRNR (F=1)" action of Core Spec, v5, Vol 3, Part A,
205 // Section 8.6.5.9, Table 8.6, "Recv RNR (P=1)" and "Send IorRRorRNR(F=1)"
206 // action of "Recv RR(P=1)." In the latter case, responding with an I-Frame
207 // (F=1) is indistinguishable from responding with an RR (F=1) then an I-Frame
208 // (F=0), so that optimization isn't implemented and we always respond with an
209 // RR or RNR (F=1), but not an I-Frame (F=1).
210 if (sframe.function() == SupervisoryFunction::ReceiverReady ||
211 sframe.function() == SupervisoryFunction::ReceiverNotReady) {
212 if (sframe.is_poll_request()) {
213 // See Core Spec, v5, Vol 3, Part A, Section 8.6.5.9, Table 8.6, "Recv
214 // RR(P=1)".
215 //
216 // Note, however, that there may be additional work to do if we're in the
217 // REJ_SENT state. See Core Spec, v5, Vol 3, Part A, Section 8.6.5.10,
218 // Table 8.7, "Recv RR(P=1)".
219 //
220 // TODO(fxbug.dev/42054996): Respond with RNR when LocalBusy.
221 SimpleReceiverReadyFrame poll_response;
222 poll_response.set_is_poll_response();
223 poll_response.set_receive_seq_num(next_seqnum_);
224 send_frame_callback_(std::make_unique<DynamicByteBuffer>(
225 BufferView(&poll_response, sizeof(poll_response))));
226 return nullptr;
227 }
228 }
229
230 // REJ S-Frames will still result in forwarding the acknowledgment via
231 // ReceiveSeqNumCallback after this call, per "PassToTx" actions for "Recv
232 // REJ" events in Core Spec v5.0, Vol 3, Part A, Sec 8.6.5.9–11.
233 if (sframe.function() == SupervisoryFunction::Reject) {
234 if (range_retransmit_set_callback_) {
235 range_retransmit_set_callback_(sframe.is_poll_request());
236 }
237 }
238
239 // SREJ S-Frames will still result in forwarding the acknowledgment via
240 // ReceiveSeqNumCallback after this call. The "Recv SREJ" events in Core Spec
241 // v5.0, Vol 3, Part A, Sec 8.6.5.9–11 call for different actions ("PassToTx"
242 // vs "PassToTxFbit") but we always pass both receive seq and poll response
243 // because the TxEngine has other behavior that branch on the same bit.
244 if (sframe.function() == SupervisoryFunction::SelectiveReject) {
245 if (single_retransmit_set_callback_) {
246 single_retransmit_set_callback_(sframe.is_poll_request());
247 }
248 }
249
250 return nullptr;
251 }
252
ProcessFrame(std::monostate,PDU)253 ByteBufferPtr Engine::ProcessFrame(std::monostate, PDU) {
254 // TODO(quiche): Close connection.
255 return nullptr;
256 }
257
AdvanceSeqNum()258 void Engine::AdvanceSeqNum() {
259 ++next_seqnum_;
260 if (next_seqnum_ > EnhancedControlField::kMaxSeqNum) {
261 next_seqnum_ = 0;
262 }
263 }
264
265 } // namespace bt::l2cap::internal
266