1 // Copyright 2024 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/credit_based_flow_control_tx_engine.h"
16 
17 #include <pw_bluetooth/l2cap_frames.emb.h>
18 
19 #include <algorithm>
20 
21 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
23 
24 namespace bt::l2cap::internal {
25 namespace {
26 
27 namespace emboss = pw::bluetooth::emboss;
28 
29 constexpr auto kLeCreditBasedFlowControlMode =
30     CreditBasedFlowControlMode::kLeCreditBasedFlowControl;
31 
32 constexpr size_t kMinimumLeMtu = 23;
33 constexpr size_t kMinimumLeMps = 23;
34 constexpr auto kPduHeaderSize = emboss::KFramePduHeader::IntrinsicSizeInBytes();
35 constexpr auto kSduHeaderSize = emboss::KFrameSduHeader::IntrinsicSizeInBytes();
36 
37 constexpr uint32_t kMaxCredits = 65535;
38 
39 template <typename Enum>
EnumValue(const Enum & e)40 constexpr typename std::underlying_type<Enum>::type EnumValue(const Enum& e) {
41   return static_cast<typename std::underlying_type<Enum>::type>(e);
42 }
43 
44 // Returns the payload size of the next PDU needed to transmit the maximal
45 // amount of the remaining |total_payload_size| bytes.
NextPduPayloadSize(size_t total_payload_size,uint16_t max_pdu_size)46 uint16_t NextPduPayloadSize(size_t total_payload_size, uint16_t max_pdu_size) {
47   PW_DCHECK(max_pdu_size > kPduHeaderSize);
48   // Factor in the header size.
49   uint16_t max_payload_size = max_pdu_size - kPduHeaderSize;
50   // There is no risk of overflow in this static cast as any value of
51   // `total_payload_size` larger than uint16_t max will be bounded by the
52   // smaller value in `max_payload_size`.
53   return static_cast<uint16_t>(
54       std::min<size_t>(total_payload_size, max_payload_size));
55 }
56 
57 // Create and initialize a K-Frame with appropriate PDU header.
58 // Returns a tuple of the frame itself and a view of the payload.
CreateFrame(uint16_t payload_size,uint16_t channel_id)59 std::tuple<DynamicByteBuffer, MutableBufferView> CreateFrame(
60     uint16_t payload_size, uint16_t channel_id) {
61   uint16_t pdu_size = kPduHeaderSize + payload_size;
62 
63   DynamicByteBuffer buf(pdu_size);
64   auto header =
65       emboss::KFramePduHeaderWriter(buf.mutable_data(), kPduHeaderSize);
66   header.pdu_length().Write(payload_size);
67   header.channel_id().Write(channel_id);
68 
69   MutableBufferView payload = buf.mutable_view(kPduHeaderSize);
70   return std::make_tuple(std::move(buf), payload);
71 }
72 }  // namespace
73 
CreditBasedFlowControlTxEngine(ChannelId channel_id,uint16_t max_tx_sdu_size,TxChannel & channel,CreditBasedFlowControlMode mode,uint16_t max_tx_pdu_size,uint16_t initial_credits)74 CreditBasedFlowControlTxEngine::CreditBasedFlowControlTxEngine(
75     ChannelId channel_id,
76     uint16_t max_tx_sdu_size,
77     TxChannel& channel,
78     CreditBasedFlowControlMode mode,
79     uint16_t max_tx_pdu_size,
80     uint16_t initial_credits)
81     : TxEngine(channel_id, max_tx_sdu_size, channel),
82       mode_(mode),
83       max_tx_pdu_size_(max_tx_pdu_size),
84       credits_(initial_credits) {
85   // The enhanced flow control mode is not yet supported.
86   PW_CHECK(mode_ == kLeCreditBasedFlowControlMode,
87            "Credit based flow control mode unsupported: 0x%.2ux",
88            EnumValue(mode));
89 
90   PW_DCHECK(
91       mode != kLeCreditBasedFlowControlMode || max_tx_sdu_size > kMinimumLeMtu,
92       "Invalid MTU for LE mode: %d",
93       max_tx_sdu_size);
94   PW_DCHECK(
95       mode != kLeCreditBasedFlowControlMode || max_tx_pdu_size > kMinimumLeMps,
96       "Invalid MPS for LE mode: %d",
97       max_tx_pdu_size);
98 }
99 
100 CreditBasedFlowControlTxEngine::~CreditBasedFlowControlTxEngine() = default;
101 
NotifySduQueued()102 void CreditBasedFlowControlTxEngine::NotifySduQueued() { ProcessSdus(); }
103 
AddCredits(uint16_t credits)104 bool CreditBasedFlowControlTxEngine::AddCredits(uint16_t credits) {
105   if (static_cast<uint32_t>(credits_) + credits > kMaxCredits)
106     return false;
107 
108   credits_ += credits;
109 
110   // If there are queued SDUs, use the newly added credits to send them.
111   ProcessSdus();
112   return true;
113 }
114 
credits() const115 uint16_t CreditBasedFlowControlTxEngine::credits() const { return credits_; }
116 
segments_count() const117 size_t CreditBasedFlowControlTxEngine::segments_count() const {
118   return segments_.size();
119 }
120 
SegmentSdu(ByteBufferPtr sdu)121 void CreditBasedFlowControlTxEngine::SegmentSdu(ByteBufferPtr sdu) {
122   size_t payload_remaining = sdu->size() + kSduHeaderSize;
123 
124   do {
125     // A credit based flow control packet has a fairly simple segmentation
126     // scheme where each PDU contains (at most) |max_tx_pdu_size_| - header_size
127     // bytes. The only bit of (relatively minor) complexity is the SDU size
128     // field, which is only included in the first K-Frame/PDU of the SDU. This
129     // loop iterates over each potential packet (and will only execute once if
130     // the entire SDU fits in a single PDU).
131 
132     DynamicByteBuffer frame;
133     MutableBufferView payload;
134     std::tie(frame, payload) = CreateFrame(
135         NextPduPayloadSize(payload_remaining, max_tx_pdu_size_), channel_id());
136     PW_DCHECK(payload.size() <= payload_remaining);
137 
138     if (payload_remaining > sdu->size()) {
139       // First frame of the SDU, write the SDU header.
140       emboss::KFrameSduHeaderWriter header(payload.mutable_data(),
141                                            kSduHeaderSize);
142       header.sdu_length().Write(sdu->size());
143 
144       payload = payload.mutable_view(kSduHeaderSize);
145       payload_remaining -= kSduHeaderSize;
146       PW_DCHECK(payload_remaining == sdu->size());
147     }
148 
149     sdu->Copy(&payload, sdu->size() - payload_remaining, payload.size());
150     payload_remaining -= payload.size();
151 
152     segments_.push_back(std::make_unique<DynamicByteBuffer>(std::move(frame)));
153   } while (payload_remaining > 0);
154 }
155 
TrySendSegments()156 void CreditBasedFlowControlTxEngine::TrySendSegments() {
157   while (credits_ > 0 && !segments_.empty()) {
158     channel().SendFrame(std::move(segments_.front()));
159     segments_.pop_front();
160     --credits_;
161   }
162 }
163 
ProcessSdus()164 void CreditBasedFlowControlTxEngine::ProcessSdus() {
165   TrySendSegments();
166   while (credits_ > 0) {
167     std::optional<ByteBufferPtr> sdu = channel().GetNextQueuedSdu();
168 
169     if (!sdu)
170       break;
171     PW_CHECK(*sdu);
172 
173     if ((*sdu)->size() > max_tx_sdu_size()) {
174       bt_log(INFO,
175              "l2cap",
176              "SDU size exceeds channel TxMTU (channel-id: 0x%.4x)",
177              channel_id());
178       return;
179     }
180 
181     SegmentSdu(std::move(*sdu));
182     TrySendSegments();
183   }
184 }
185 
186 }  // namespace bt::l2cap::internal
187