xref: /aosp_15_r20/external/pigweed/pw_hdlc/router.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2024 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker 
15*61c4878aSAndroid Build Coastguard Worker #include "pw_hdlc/router.h"
16*61c4878aSAndroid Build Coastguard Worker 
17*61c4878aSAndroid Build Coastguard Worker #include <inttypes.h>
18*61c4878aSAndroid Build Coastguard Worker 
19*61c4878aSAndroid Build Coastguard Worker #include <algorithm>
20*61c4878aSAndroid Build Coastguard Worker 
21*61c4878aSAndroid Build Coastguard Worker #include "pw_hdlc/encoder.h"
22*61c4878aSAndroid Build Coastguard Worker #include "pw_log/log.h"
23*61c4878aSAndroid Build Coastguard Worker #include "pw_multibuf/multibuf.h"
24*61c4878aSAndroid Build Coastguard Worker #include "pw_multibuf/stream.h"
25*61c4878aSAndroid Build Coastguard Worker #include "pw_result/result.h"
26*61c4878aSAndroid Build Coastguard Worker #include "pw_stream/null_stream.h"
27*61c4878aSAndroid Build Coastguard Worker 
28*61c4878aSAndroid Build Coastguard Worker namespace pw::hdlc {
29*61c4878aSAndroid Build Coastguard Worker 
30*61c4878aSAndroid Build Coastguard Worker using ::pw::async2::Context;
31*61c4878aSAndroid Build Coastguard Worker using ::pw::async2::Pending;
32*61c4878aSAndroid Build Coastguard Worker using ::pw::async2::Poll;
33*61c4878aSAndroid Build Coastguard Worker using ::pw::async2::Ready;
34*61c4878aSAndroid Build Coastguard Worker using ::pw::channel::ByteReaderWriter;
35*61c4878aSAndroid Build Coastguard Worker using ::pw::channel::DatagramReaderWriter;
36*61c4878aSAndroid Build Coastguard Worker using ::pw::multibuf::Chunk;
37*61c4878aSAndroid Build Coastguard Worker using ::pw::multibuf::MultiBuf;
38*61c4878aSAndroid Build Coastguard Worker using ::pw::stream::CountingNullStream;
39*61c4878aSAndroid Build Coastguard Worker 
40*61c4878aSAndroid Build Coastguard Worker namespace {
41*61c4878aSAndroid Build Coastguard Worker 
42*61c4878aSAndroid Build Coastguard Worker /// HDLC encodes the contents of ``payload`` to ``writer``.
WriteMultiBufUIFrame(uint64_t address,const MultiBuf & payload,stream::Writer & writer)43*61c4878aSAndroid Build Coastguard Worker Status WriteMultiBufUIFrame(uint64_t address,
44*61c4878aSAndroid Build Coastguard Worker                             const MultiBuf& payload,
45*61c4878aSAndroid Build Coastguard Worker                             stream::Writer& writer) {
46*61c4878aSAndroid Build Coastguard Worker   Encoder encoder(writer);
47*61c4878aSAndroid Build Coastguard Worker   if (Status status = encoder.StartUnnumberedFrame(address); !status.ok()) {
48*61c4878aSAndroid Build Coastguard Worker     return status;
49*61c4878aSAndroid Build Coastguard Worker   }
50*61c4878aSAndroid Build Coastguard Worker   for (const Chunk& chunk : payload.Chunks()) {
51*61c4878aSAndroid Build Coastguard Worker     if (Status status = encoder.WriteData(chunk); !status.ok()) {
52*61c4878aSAndroid Build Coastguard Worker       return status;
53*61c4878aSAndroid Build Coastguard Worker     }
54*61c4878aSAndroid Build Coastguard Worker   }
55*61c4878aSAndroid Build Coastguard Worker   return encoder.FinishFrame();
56*61c4878aSAndroid Build Coastguard Worker }
57*61c4878aSAndroid Build Coastguard Worker 
58*61c4878aSAndroid Build Coastguard Worker /// Calculates the size of ``payload`` once HDLC-encoded.
CalculateSizeOnceEncoded(uint64_t address,const MultiBuf & payload)59*61c4878aSAndroid Build Coastguard Worker Result<size_t> CalculateSizeOnceEncoded(uint64_t address,
60*61c4878aSAndroid Build Coastguard Worker                                         const MultiBuf& payload) {
61*61c4878aSAndroid Build Coastguard Worker   CountingNullStream null_stream;
62*61c4878aSAndroid Build Coastguard Worker   Status encode_status = WriteMultiBufUIFrame(address, payload, null_stream);
63*61c4878aSAndroid Build Coastguard Worker   if (!encode_status.ok()) {
64*61c4878aSAndroid Build Coastguard Worker     return encode_status;
65*61c4878aSAndroid Build Coastguard Worker   }
66*61c4878aSAndroid Build Coastguard Worker   return null_stream.bytes_written();
67*61c4878aSAndroid Build Coastguard Worker }
68*61c4878aSAndroid Build Coastguard Worker 
69*61c4878aSAndroid Build Coastguard Worker /// Attempts to decode a frame from ``data``, advancing ``data`` forwards by
70*61c4878aSAndroid Build Coastguard Worker /// any bytes that are consumed.
DecodeFrame(Decoder & decoder,MultiBuf & data)71*61c4878aSAndroid Build Coastguard Worker std::optional<Frame> DecodeFrame(Decoder& decoder, MultiBuf& data) {
72*61c4878aSAndroid Build Coastguard Worker   size_t processed = 0;
73*61c4878aSAndroid Build Coastguard Worker   for (std::byte byte : data) {
74*61c4878aSAndroid Build Coastguard Worker     Result<Frame> frame_result = decoder.Process(byte);
75*61c4878aSAndroid Build Coastguard Worker     ++processed;
76*61c4878aSAndroid Build Coastguard Worker     if (frame_result.status().IsUnavailable()) {
77*61c4878aSAndroid Build Coastguard Worker       // No frame is yet available.
78*61c4878aSAndroid Build Coastguard Worker     } else if (frame_result.ok()) {
79*61c4878aSAndroid Build Coastguard Worker       data.DiscardPrefix(processed);
80*61c4878aSAndroid Build Coastguard Worker       return std::move(*frame_result);
81*61c4878aSAndroid Build Coastguard Worker     } else if (frame_result.status().IsDataLoss()) {
82*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("Discarding invalid incoming HDLC frame.");
83*61c4878aSAndroid Build Coastguard Worker     } else if (frame_result.status().IsResourceExhausted()) {
84*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("Discarding incoming HDLC frame: too large for buffer.");
85*61c4878aSAndroid Build Coastguard Worker     }
86*61c4878aSAndroid Build Coastguard Worker   }
87*61c4878aSAndroid Build Coastguard Worker   data.DiscardPrefix(processed);
88*61c4878aSAndroid Build Coastguard Worker   return std::nullopt;
89*61c4878aSAndroid Build Coastguard Worker }
90*61c4878aSAndroid Build Coastguard Worker 
91*61c4878aSAndroid Build Coastguard Worker }  // namespace
92*61c4878aSAndroid Build Coastguard Worker 
AddChannel(DatagramReaderWriter & channel,uint64_t receive_address,uint64_t send_address)93*61c4878aSAndroid Build Coastguard Worker Status Router::AddChannel(DatagramReaderWriter& channel,
94*61c4878aSAndroid Build Coastguard Worker                           uint64_t receive_address,
95*61c4878aSAndroid Build Coastguard Worker                           uint64_t send_address) {
96*61c4878aSAndroid Build Coastguard Worker   for (const ChannelData& data : channel_datas_) {
97*61c4878aSAndroid Build Coastguard Worker     if ((data.channel == &channel) ||
98*61c4878aSAndroid Build Coastguard Worker         (data.receive_address == receive_address) ||
99*61c4878aSAndroid Build Coastguard Worker         (data.send_address == send_address)) {
100*61c4878aSAndroid Build Coastguard Worker       return Status::AlreadyExists();
101*61c4878aSAndroid Build Coastguard Worker     }
102*61c4878aSAndroid Build Coastguard Worker   }
103*61c4878aSAndroid Build Coastguard Worker   channel_datas_.emplace_back(channel, receive_address, send_address);
104*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
105*61c4878aSAndroid Build Coastguard Worker }
106*61c4878aSAndroid Build Coastguard Worker 
RemoveChannel(DatagramReaderWriter & channel,uint64_t receive_address,uint64_t send_address)107*61c4878aSAndroid Build Coastguard Worker Status Router::RemoveChannel(DatagramReaderWriter& channel,
108*61c4878aSAndroid Build Coastguard Worker                              uint64_t receive_address,
109*61c4878aSAndroid Build Coastguard Worker                              uint64_t send_address) {
110*61c4878aSAndroid Build Coastguard Worker   auto channel_entry = std::find_if(
111*61c4878aSAndroid Build Coastguard Worker       channel_datas_.begin(),
112*61c4878aSAndroid Build Coastguard Worker       channel_datas_.end(),
113*61c4878aSAndroid Build Coastguard Worker       [&channel, receive_address, send_address](const ChannelData& data) {
114*61c4878aSAndroid Build Coastguard Worker         return (data.channel == &channel) &&
115*61c4878aSAndroid Build Coastguard Worker                (data.receive_address == receive_address) &&
116*61c4878aSAndroid Build Coastguard Worker                (data.send_address == send_address);
117*61c4878aSAndroid Build Coastguard Worker       });
118*61c4878aSAndroid Build Coastguard Worker   if (channel_entry == channel_datas_.end()) {
119*61c4878aSAndroid Build Coastguard Worker     return Status::NotFound();
120*61c4878aSAndroid Build Coastguard Worker   }
121*61c4878aSAndroid Build Coastguard Worker   if (channel_datas_.size() == 1) {
122*61c4878aSAndroid Build Coastguard Worker     channel_datas_.clear();
123*61c4878aSAndroid Build Coastguard Worker   } else {
124*61c4878aSAndroid Build Coastguard Worker     // Put the ChannelData in the back of
125*61c4878aSAndroid Build Coastguard Worker     // the list and pop it out to avoid shifting
126*61c4878aSAndroid Build Coastguard Worker     // all elements.
127*61c4878aSAndroid Build Coastguard Worker     std::swap(*channel_entry, channel_datas_.back());
128*61c4878aSAndroid Build Coastguard Worker     channel_datas_.pop_back();
129*61c4878aSAndroid Build Coastguard Worker   }
130*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
131*61c4878aSAndroid Build Coastguard Worker }
132*61c4878aSAndroid Build Coastguard Worker 
FindChannelForReceiveAddress(uint64_t receive_address)133*61c4878aSAndroid Build Coastguard Worker Router::ChannelData* Router::FindChannelForReceiveAddress(
134*61c4878aSAndroid Build Coastguard Worker     uint64_t receive_address) {
135*61c4878aSAndroid Build Coastguard Worker   for (auto& channel : channel_datas_) {
136*61c4878aSAndroid Build Coastguard Worker     if (channel.receive_address == receive_address) {
137*61c4878aSAndroid Build Coastguard Worker       return &channel;
138*61c4878aSAndroid Build Coastguard Worker     }
139*61c4878aSAndroid Build Coastguard Worker   }
140*61c4878aSAndroid Build Coastguard Worker   return nullptr;
141*61c4878aSAndroid Build Coastguard Worker }
142*61c4878aSAndroid Build Coastguard Worker 
PollDeliverIncomingFrame(Context & cx,const Frame & frame)143*61c4878aSAndroid Build Coastguard Worker Poll<> Router::PollDeliverIncomingFrame(Context& cx, const Frame& frame) {
144*61c4878aSAndroid Build Coastguard Worker   ConstByteSpan data = frame.data();
145*61c4878aSAndroid Build Coastguard Worker   uint64_t address = frame.address();
146*61c4878aSAndroid Build Coastguard Worker   ChannelData* channel = FindChannelForReceiveAddress(address);
147*61c4878aSAndroid Build Coastguard Worker   if (channel == nullptr) {
148*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("Received incoming HDLC packet with address %" PRIu64
149*61c4878aSAndroid Build Coastguard Worker                  ", but no channel with that incoming address is registered.",
150*61c4878aSAndroid Build Coastguard Worker                  address);
151*61c4878aSAndroid Build Coastguard Worker     return Ready();
152*61c4878aSAndroid Build Coastguard Worker   }
153*61c4878aSAndroid Build Coastguard Worker   Poll<Status> ready_to_write = channel->channel->PendReadyToWrite(cx);
154*61c4878aSAndroid Build Coastguard Worker   if (ready_to_write.IsPending()) {
155*61c4878aSAndroid Build Coastguard Worker     return Pending();
156*61c4878aSAndroid Build Coastguard Worker   }
157*61c4878aSAndroid Build Coastguard Worker   if (!ready_to_write->ok()) {
158*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("Channel at incoming HDLC address %" PRIu64
159*61c4878aSAndroid Build Coastguard Worker                  " became unwriteable. Status: %d",
160*61c4878aSAndroid Build Coastguard Worker                  channel->receive_address,
161*61c4878aSAndroid Build Coastguard Worker                  ready_to_write->code());
162*61c4878aSAndroid Build Coastguard Worker     return Ready();
163*61c4878aSAndroid Build Coastguard Worker   }
164*61c4878aSAndroid Build Coastguard Worker   Poll<std::optional<MultiBuf>> buffer =
165*61c4878aSAndroid Build Coastguard Worker       channel->channel->PendAllocateWriteBuffer(cx, data.size());
166*61c4878aSAndroid Build Coastguard Worker   if (buffer.IsPending()) {
167*61c4878aSAndroid Build Coastguard Worker     return Pending();
168*61c4878aSAndroid Build Coastguard Worker   }
169*61c4878aSAndroid Build Coastguard Worker   if (!buffer->has_value()) {
170*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR(
171*61c4878aSAndroid Build Coastguard Worker         "Unable to allocate a buffer of size %zu destined for incoming "
172*61c4878aSAndroid Build Coastguard Worker         "HDLC address %" PRIu64 ". Packet will be discarded.",
173*61c4878aSAndroid Build Coastguard Worker         data.size(),
174*61c4878aSAndroid Build Coastguard Worker         frame.address());
175*61c4878aSAndroid Build Coastguard Worker     return Ready();
176*61c4878aSAndroid Build Coastguard Worker   }
177*61c4878aSAndroid Build Coastguard Worker   std::copy(frame.data().begin(), frame.data().end(), (**buffer).begin());
178*61c4878aSAndroid Build Coastguard Worker   Status write_status = channel->channel->StageWrite(std::move(**buffer));
179*61c4878aSAndroid Build Coastguard Worker   if (!write_status.ok()) {
180*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR(
181*61c4878aSAndroid Build Coastguard Worker         "Failed to write a buffer of size %zu destined for incoming HDLC "
182*61c4878aSAndroid Build Coastguard Worker         "address %" PRIu64 ". Status: %d",
183*61c4878aSAndroid Build Coastguard Worker         data.size(),
184*61c4878aSAndroid Build Coastguard Worker         channel->receive_address,
185*61c4878aSAndroid Build Coastguard Worker         write_status.code());
186*61c4878aSAndroid Build Coastguard Worker   }
187*61c4878aSAndroid Build Coastguard Worker   return Ready();
188*61c4878aSAndroid Build Coastguard Worker }
189*61c4878aSAndroid Build Coastguard Worker 
DecodeAndWriteIncoming(Context & cx)190*61c4878aSAndroid Build Coastguard Worker void Router::DecodeAndWriteIncoming(Context& cx) {
191*61c4878aSAndroid Build Coastguard Worker   while (true) {
192*61c4878aSAndroid Build Coastguard Worker     if (decoded_frame_.has_value()) {
193*61c4878aSAndroid Build Coastguard Worker       if (PollDeliverIncomingFrame(cx, *decoded_frame_).IsPending()) {
194*61c4878aSAndroid Build Coastguard Worker         return;
195*61c4878aSAndroid Build Coastguard Worker       }
196*61c4878aSAndroid Build Coastguard Worker       // Zero out the frame delivery state.
197*61c4878aSAndroid Build Coastguard Worker       decoded_frame_ = std::nullopt;
198*61c4878aSAndroid Build Coastguard Worker     }
199*61c4878aSAndroid Build Coastguard Worker 
200*61c4878aSAndroid Build Coastguard Worker     while (incoming_data_.empty()) {
201*61c4878aSAndroid Build Coastguard Worker       Poll<Result<MultiBuf>> incoming = io_channel_.PendRead(cx);
202*61c4878aSAndroid Build Coastguard Worker       if (incoming.IsPending()) {
203*61c4878aSAndroid Build Coastguard Worker         return;
204*61c4878aSAndroid Build Coastguard Worker       }
205*61c4878aSAndroid Build Coastguard Worker       if (!incoming->ok()) {
206*61c4878aSAndroid Build Coastguard Worker         if (incoming->status().IsFailedPrecondition()) {
207*61c4878aSAndroid Build Coastguard Worker           PW_LOG_WARN("HDLC io_channel has closed.");
208*61c4878aSAndroid Build Coastguard Worker         } else {
209*61c4878aSAndroid Build Coastguard Worker           PW_LOG_ERROR("Unable to read from HDLC io_channel. Status: %d",
210*61c4878aSAndroid Build Coastguard Worker                        incoming->status().code());
211*61c4878aSAndroid Build Coastguard Worker         }
212*61c4878aSAndroid Build Coastguard Worker         return;
213*61c4878aSAndroid Build Coastguard Worker       }
214*61c4878aSAndroid Build Coastguard Worker       incoming_data_ = std::move(**incoming);
215*61c4878aSAndroid Build Coastguard Worker     }
216*61c4878aSAndroid Build Coastguard Worker 
217*61c4878aSAndroid Build Coastguard Worker     decoded_frame_ = DecodeFrame(decoder_, incoming_data_);
218*61c4878aSAndroid Build Coastguard Worker   }
219*61c4878aSAndroid Build Coastguard Worker }
220*61c4878aSAndroid Build Coastguard Worker 
TryFillBufferToEncodeAndSend(Context & cx)221*61c4878aSAndroid Build Coastguard Worker void Router::TryFillBufferToEncodeAndSend(Context& cx) {
222*61c4878aSAndroid Build Coastguard Worker   if (buffer_to_encode_and_send_.has_value()) {
223*61c4878aSAndroid Build Coastguard Worker     return;
224*61c4878aSAndroid Build Coastguard Worker   }
225*61c4878aSAndroid Build Coastguard Worker   for (size_t i = 0; i < channel_datas_.size(); ++i) {
226*61c4878aSAndroid Build Coastguard Worker     ChannelData& cd =
227*61c4878aSAndroid Build Coastguard Worker         channel_datas_[(next_first_read_index_ + i) % channel_datas_.size()];
228*61c4878aSAndroid Build Coastguard Worker     Poll<Result<MultiBuf>> buf_result = cd.channel->PendRead(cx);
229*61c4878aSAndroid Build Coastguard Worker     if (buf_result.IsPending()) {
230*61c4878aSAndroid Build Coastguard Worker       continue;
231*61c4878aSAndroid Build Coastguard Worker     }
232*61c4878aSAndroid Build Coastguard Worker     if (!buf_result->ok()) {
233*61c4878aSAndroid Build Coastguard Worker       if (buf_result->status().IsUnimplemented()) {
234*61c4878aSAndroid Build Coastguard Worker         PW_LOG_ERROR("Channel registered for outgoing HDLC address %" PRIu64
235*61c4878aSAndroid Build Coastguard Worker                      " is not readable.",
236*61c4878aSAndroid Build Coastguard Worker                      cd.send_address);
237*61c4878aSAndroid Build Coastguard Worker       }
238*61c4878aSAndroid Build Coastguard Worker       // We ignore FAILED_PRECONDITION (closed) because it will be handled
239*61c4878aSAndroid Build Coastguard Worker       // elsewhere. OUT_OF_RANGE just means we have finished writing. No
240*61c4878aSAndroid Build Coastguard Worker       // action is needed because the channel may still be receiving data.
241*61c4878aSAndroid Build Coastguard Worker       continue;
242*61c4878aSAndroid Build Coastguard Worker     }
243*61c4878aSAndroid Build Coastguard Worker     MultiBuf& buf = **buf_result;
244*61c4878aSAndroid Build Coastguard Worker     uint64_t target_address = cd.send_address;
245*61c4878aSAndroid Build Coastguard Worker     Result<size_t> encoded_size = CalculateSizeOnceEncoded(target_address, buf);
246*61c4878aSAndroid Build Coastguard Worker     if (!encoded_size.ok()) {
247*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR(
248*61c4878aSAndroid Build Coastguard Worker           "Unable to compute size of encoded packet for outgoing buffer of "
249*61c4878aSAndroid Build Coastguard Worker           "size %zu destined for outgoing HDLC address %" PRIu64
250*61c4878aSAndroid Build Coastguard Worker           ". Packet will be discarded.",
251*61c4878aSAndroid Build Coastguard Worker           buf.size(),
252*61c4878aSAndroid Build Coastguard Worker           target_address);
253*61c4878aSAndroid Build Coastguard Worker       continue;
254*61c4878aSAndroid Build Coastguard Worker     }
255*61c4878aSAndroid Build Coastguard Worker     buffer_to_encode_and_send_ =
256*61c4878aSAndroid Build Coastguard Worker         OutgoingBuffer{/*buffer=*/std::move(buf),
257*61c4878aSAndroid Build Coastguard Worker                        /*hdlc_encoded_size=*/*encoded_size,
258*61c4878aSAndroid Build Coastguard Worker                        /*target_address=*/target_address};
259*61c4878aSAndroid Build Coastguard Worker     // We received data, so ensure that we start by reading from a different
260*61c4878aSAndroid Build Coastguard Worker     // index next time.
261*61c4878aSAndroid Build Coastguard Worker     next_first_read_index_ =
262*61c4878aSAndroid Build Coastguard Worker         (next_first_read_index_ + 1) % channel_datas_.size();
263*61c4878aSAndroid Build Coastguard Worker     return;
264*61c4878aSAndroid Build Coastguard Worker   }
265*61c4878aSAndroid Build Coastguard Worker }
266*61c4878aSAndroid Build Coastguard Worker 
WriteOutgoingMessages(Context & cx)267*61c4878aSAndroid Build Coastguard Worker void Router::WriteOutgoingMessages(Context& cx) {
268*61c4878aSAndroid Build Coastguard Worker   while (io_channel_.is_write_open() &&
269*61c4878aSAndroid Build Coastguard Worker          io_channel_.PendReadyToWrite(cx).IsReady()) {
270*61c4878aSAndroid Build Coastguard Worker     TryFillBufferToEncodeAndSend(cx);
271*61c4878aSAndroid Build Coastguard Worker     if (!buffer_to_encode_and_send_.has_value()) {
272*61c4878aSAndroid Build Coastguard Worker       // No channels have new data to send.
273*61c4878aSAndroid Build Coastguard Worker       return;
274*61c4878aSAndroid Build Coastguard Worker     }
275*61c4878aSAndroid Build Coastguard Worker     uint64_t target_address = buffer_to_encode_and_send_->target_address;
276*61c4878aSAndroid Build Coastguard Worker     size_t hdlc_encoded_size = buffer_to_encode_and_send_->hdlc_encoded_size;
277*61c4878aSAndroid Build Coastguard Worker     Poll<std::optional<MultiBuf>> maybe_write_buffer =
278*61c4878aSAndroid Build Coastguard Worker         io_channel_.PendAllocateWriteBuffer(cx, hdlc_encoded_size);
279*61c4878aSAndroid Build Coastguard Worker     if (maybe_write_buffer.IsPending()) {
280*61c4878aSAndroid Build Coastguard Worker       // Channel cannot write any further messages until we can allocate.
281*61c4878aSAndroid Build Coastguard Worker       return;
282*61c4878aSAndroid Build Coastguard Worker     }
283*61c4878aSAndroid Build Coastguard Worker     // We've gotten the allocation: discard the future.
284*61c4878aSAndroid Build Coastguard Worker     if (!maybe_write_buffer->has_value()) {
285*61c4878aSAndroid Build Coastguard Worker       // We can't allocate a write buffer large enough for our encoded frame.
286*61c4878aSAndroid Build Coastguard Worker       // Sadly, we have to throw the frame away.
287*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR(
288*61c4878aSAndroid Build Coastguard Worker           "Unable to allocate a buffer of size %zu destined for outgoing "
289*61c4878aSAndroid Build Coastguard Worker           "HDLC address %" PRIu64 ". Packet will be discarded.",
290*61c4878aSAndroid Build Coastguard Worker           hdlc_encoded_size,
291*61c4878aSAndroid Build Coastguard Worker           target_address);
292*61c4878aSAndroid Build Coastguard Worker       buffer_to_encode_and_send_ = std::nullopt;
293*61c4878aSAndroid Build Coastguard Worker       continue;
294*61c4878aSAndroid Build Coastguard Worker     }
295*61c4878aSAndroid Build Coastguard Worker     MultiBuf write_buffer = std::move(**maybe_write_buffer);
296*61c4878aSAndroid Build Coastguard Worker     Status encode_status =
297*61c4878aSAndroid Build Coastguard Worker         WriteMultiBufUIFrame(target_address,
298*61c4878aSAndroid Build Coastguard Worker                              buffer_to_encode_and_send_->buffer,
299*61c4878aSAndroid Build Coastguard Worker                              pw::multibuf::Stream(write_buffer));
300*61c4878aSAndroid Build Coastguard Worker     buffer_to_encode_and_send_ = std::nullopt;
301*61c4878aSAndroid Build Coastguard Worker     if (!encode_status.ok()) {
302*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR(
303*61c4878aSAndroid Build Coastguard Worker           "Failed to encode a buffer destined for outgoing HDLC address "
304*61c4878aSAndroid Build Coastguard Worker           "%" PRIu64 ". Status: %d",
305*61c4878aSAndroid Build Coastguard Worker           target_address,
306*61c4878aSAndroid Build Coastguard Worker           encode_status.code());
307*61c4878aSAndroid Build Coastguard Worker       continue;
308*61c4878aSAndroid Build Coastguard Worker     }
309*61c4878aSAndroid Build Coastguard Worker     Status write_status = io_channel_.StageWrite(std::move(write_buffer));
310*61c4878aSAndroid Build Coastguard Worker     if (!write_status.ok()) {
311*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR(
312*61c4878aSAndroid Build Coastguard Worker           "Failed to write a buffer of size %zu destined for outgoing HDLC "
313*61c4878aSAndroid Build Coastguard Worker           "address %" PRIu64 ". Status: %d",
314*61c4878aSAndroid Build Coastguard Worker           hdlc_encoded_size,
315*61c4878aSAndroid Build Coastguard Worker           target_address,
316*61c4878aSAndroid Build Coastguard Worker           write_status.code());
317*61c4878aSAndroid Build Coastguard Worker     }
318*61c4878aSAndroid Build Coastguard Worker   }
319*61c4878aSAndroid Build Coastguard Worker }
320*61c4878aSAndroid Build Coastguard Worker 
Pend(Context & cx)321*61c4878aSAndroid Build Coastguard Worker Poll<> Router::Pend(Context& cx) {
322*61c4878aSAndroid Build Coastguard Worker   // We check for ability to read, but not write, because we may not always
323*61c4878aSAndroid Build Coastguard Worker   // attempt a write, which would cause us to miss that the channel has closed
324*61c4878aSAndroid Build Coastguard Worker   // for writes.
325*61c4878aSAndroid Build Coastguard Worker   //
326*61c4878aSAndroid Build Coastguard Worker   // Additionally, it is uncommon for a channel to remain readable but not
327*61c4878aSAndroid Build Coastguard Worker   // writeable: the reverse is more common (still readable while no longer
328*61c4878aSAndroid Build Coastguard Worker   // writeable).
329*61c4878aSAndroid Build Coastguard Worker   if (!io_channel_.is_read_open()) {
330*61c4878aSAndroid Build Coastguard Worker     return PendClose(cx);
331*61c4878aSAndroid Build Coastguard Worker   }
332*61c4878aSAndroid Build Coastguard Worker   DecodeAndWriteIncoming(cx);
333*61c4878aSAndroid Build Coastguard Worker   WriteOutgoingMessages(cx);
334*61c4878aSAndroid Build Coastguard Worker   RemoveClosedChannels();
335*61c4878aSAndroid Build Coastguard Worker   if (!io_channel_.is_read_open()) {
336*61c4878aSAndroid Build Coastguard Worker     return PendClose(cx);
337*61c4878aSAndroid Build Coastguard Worker   }
338*61c4878aSAndroid Build Coastguard Worker   return Pending();
339*61c4878aSAndroid Build Coastguard Worker }
340*61c4878aSAndroid Build Coastguard Worker 
PendClose(Context & cx)341*61c4878aSAndroid Build Coastguard Worker Poll<> Router::PendClose(Context& cx) {
342*61c4878aSAndroid Build Coastguard Worker   for (ChannelData& cd : channel_datas_) {
343*61c4878aSAndroid Build Coastguard Worker     // We ignore the status value from close.
344*61c4878aSAndroid Build Coastguard Worker     // If one or more channels are unable to close, they will remain after
345*61c4878aSAndroid Build Coastguard Worker     // `RemoveClosedChannels` and `channel_datas_.size()` will be nonzero.
346*61c4878aSAndroid Build Coastguard Worker     cd.channel->PendClose(cx).IgnorePoll();
347*61c4878aSAndroid Build Coastguard Worker   }
348*61c4878aSAndroid Build Coastguard Worker   RemoveClosedChannels();
349*61c4878aSAndroid Build Coastguard Worker   if (io_channel_.PendClose(cx).IsPending()) {
350*61c4878aSAndroid Build Coastguard Worker     return Pending();
351*61c4878aSAndroid Build Coastguard Worker   }
352*61c4878aSAndroid Build Coastguard Worker   if (channel_datas_.empty()) {
353*61c4878aSAndroid Build Coastguard Worker     return Ready();
354*61c4878aSAndroid Build Coastguard Worker   } else {
355*61c4878aSAndroid Build Coastguard Worker     return Pending();
356*61c4878aSAndroid Build Coastguard Worker   }
357*61c4878aSAndroid Build Coastguard Worker }
358*61c4878aSAndroid Build Coastguard Worker 
RemoveClosedChannels()359*61c4878aSAndroid Build Coastguard Worker void Router::RemoveClosedChannels() {
360*61c4878aSAndroid Build Coastguard Worker   auto first_to_remove = std::remove_if(
361*61c4878aSAndroid Build Coastguard Worker       channel_datas_.begin(), channel_datas_.end(), [](const ChannelData& cd) {
362*61c4878aSAndroid Build Coastguard Worker         return !cd.channel->is_read_or_write_open();
363*61c4878aSAndroid Build Coastguard Worker       });
364*61c4878aSAndroid Build Coastguard Worker   channel_datas_.erase(first_to_remove, channel_datas_.end());
365*61c4878aSAndroid Build Coastguard Worker }
366*61c4878aSAndroid Build Coastguard Worker 
367*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::hdlc
368