1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "chpp/transport.h"
18
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <stddef.h>
22 #include <stdint.h>
23 #include <string.h>
24
25 #include "chpp/app.h"
26 #include "chpp/clients.h"
27 #include "chpp/clients/discovery.h"
28 #include "chpp/crc.h"
29 #include "chpp/link.h"
30 #include "chpp/log.h"
31 #include "chpp/macros.h"
32 #include "chpp/memory.h"
33 #include "chpp/platform/platform_link.h"
34 #include "chpp/services.h"
35 #include "chpp/time.h"
36
37 /************************************************
38 * Prototypes
39 ***********************************************/
40
41 static void chppSetRxState(struct ChppTransportState *context,
42 enum ChppRxState newState);
43 static size_t chppConsumePreamble(struct ChppTransportState *context,
44 const uint8_t *buf, size_t len);
45 static size_t chppConsumeHeader(struct ChppTransportState *context,
46 const uint8_t *buf, size_t len);
47 static size_t chppConsumePayload(struct ChppTransportState *context,
48 const uint8_t *buf, size_t len);
49 static size_t chppConsumeFooter(struct ChppTransportState *context,
50 const uint8_t *buf, size_t len);
51 static void chppAbortRxPacket(struct ChppTransportState *context);
52 #ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
53 static void chppProcessTransportLoopbackRequest(
54 struct ChppTransportState *context);
55 #endif
56 #ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
57 static void chppProcessTransportLoopbackResponse(
58 struct ChppTransportState *context);
59 #endif
60 static void chppSetResetComplete(struct ChppTransportState *context);
61 static void chppProcessResetAck(struct ChppTransportState *context);
62 static void chppProcessRxPacket(struct ChppTransportState *context);
63 static void chppProcessRxPayload(struct ChppTransportState *context);
64 static void chppClearRxDatagram(struct ChppTransportState *context);
65 static bool chppRxChecksumIsOk(const struct ChppTransportState *context);
66 static enum ChppTransportErrorCode chppRxHeaderCheck(
67 const struct ChppTransportState *context);
68 static bool chppRegisterRxAck(struct ChppTransportState *context);
69
70 static void chppEnqueueTxPacket(struct ChppTransportState *context,
71 uint8_t packetCode);
72 static bool chppHavePendingTxPayload(const struct ChppTransportState *context);
73 static bool chppShouldAttachPayload(const struct ChppTransportState *context,
74 bool resendPayload);
75 static bool chppShouldSendPossiblyEmptyPacket(
76 const struct ChppTransportState *context);
77 static size_t chppAddPreamble(uint8_t *buf);
78 static struct ChppTransportHeader *chppAddHeader(
79 struct ChppTransportState *context);
80 static void chppAddPayload(struct ChppTransportState *context);
81 static void chppAddFooter(struct ChppTransportState *context);
82 // Can not be static (used in tests).
83 size_t chppDequeueTxDatagram(struct ChppTransportState *context);
84 static void chppClearTxDatagramQueue(struct ChppTransportState *context);
85 static void chppTransportDoWork(struct ChppTransportState *context,
86 bool resendPayload);
87 static void chppAppendToPendingTxPacket(struct ChppTransportState *context,
88 const uint8_t *buf, size_t len);
89 static const char *chppGetPacketAttrStr(uint8_t packetCode);
90 static bool chppEnqueueTxDatagramLocked(struct ChppTransportState *context,
91 uint8_t packetCode, void *buf,
92 size_t len);
93 static enum ChppLinkErrorCode chppSendPendingPacket(
94 struct ChppTransportState *context);
95
96 static void chppResetTransportContext(struct ChppTransportState *context);
97 static void chppReset(struct ChppTransportState *context,
98 enum ChppTransportPacketAttributes resetType,
99 enum ChppTransportErrorCode error);
100 struct ChppAppHeader *chppTransportGetRequestTimeoutResponse(
101 struct ChppTransportState *context, enum ChppEndpointType type);
102 static const char *chppGetRxStatusLabel(enum ChppRxState state);
103 static void chppWorkHandleTimeout(struct ChppTransportState *context);
104
105 void chppCheckRxPacketTimeout(struct ChppTransportState *context, uint64_t now);
106
107 /************************************************
108 * Private Functions
109 ***********************************************/
110
111 /** Returns a string representation of the passed ChppRxState */
chppGetRxStatusLabel(enum ChppRxState state)112 static const char *chppGetRxStatusLabel(enum ChppRxState state) {
113 switch (state) {
114 case CHPP_STATE_PREAMBLE:
115 return "PREAMBLE (0)";
116 case CHPP_STATE_HEADER:
117 return "HEADER (1)";
118 case CHPP_STATE_PAYLOAD:
119 return "PAYLOAD (2)";
120 case CHPP_STATE_FOOTER:
121 return "FOOTER (3)";
122 }
123
124 return "invalid";
125 }
126
127 /**
128 * Called any time the Rx state needs to be changed. Ensures that the location
129 * counter among that state (rxStatus.locInState) is also reset at the same
130 * time.
131 *
132 * @param context State of the transport layer.
133 * @param newState Next Rx state.
134 */
chppSetRxState(struct ChppTransportState * context,enum ChppRxState newState)135 static void chppSetRxState(struct ChppTransportState *context,
136 enum ChppRxState newState) {
137 CHPP_LOGD(
138 "Changing RX transport state from %s to %s"
139 " after %" PRIuSIZE " bytes",
140 chppGetRxStatusLabel(context->rxStatus.state),
141 chppGetRxStatusLabel(newState), context->rxStatus.locInState);
142 context->rxStatus.locInState = 0;
143 context->rxStatus.state = newState;
144 }
145
146 /**
147 * Called by chppRxDataCb to find a preamble (i.e. packet start delimiter) in
148 * the incoming data stream.
149 * Moves the state to CHPP_STATE_HEADER as soon as it has seen a complete
150 * preamble.
151 * Any future backwards-incompatible versions of CHPP Transport will use a
152 * different preamble.
153 *
154 * @param context State of the transport layer.
155 * @param buf Input data.
156 * @param len Length of input data in bytes.
157 *
158 * @return Length of consumed data in bytes.
159 */
chppConsumePreamble(struct ChppTransportState * context,const uint8_t * buf,size_t len)160 static size_t chppConsumePreamble(struct ChppTransportState *context,
161 const uint8_t *buf, size_t len) {
162 size_t consumed = 0;
163
164 // TODO: Optimize loop, maybe using memchr() / memcmp() / SIMD, especially if
165 // serial port calling chppRxDataCb does not implement zero filter
166 while (consumed < len &&
167 context->rxStatus.locInState < CHPP_PREAMBLE_LEN_BYTES) {
168 size_t offset = context->rxStatus.locInState;
169 if ((offset == 0 && buf[consumed] == CHPP_PREAMBLE_BYTE_FIRST) ||
170 (offset == 1 && buf[consumed] == CHPP_PREAMBLE_BYTE_SECOND)) {
171 // Correct byte of preamble observed
172 context->rxStatus.locInState++;
173
174 } else if (buf[consumed] == CHPP_PREAMBLE_BYTE_FIRST) {
175 // Previous search failed but first byte of another preamble observed
176 context->rxStatus.locInState = 1;
177
178 } else {
179 // Continue search for a valid preamble from the start
180 context->rxStatus.locInState = 0;
181 }
182
183 consumed++;
184 }
185
186 // Let's see why we exited the above loop
187 if (context->rxStatus.locInState == CHPP_PREAMBLE_LEN_BYTES) {
188 // Complete preamble observed, move on to next state
189 context->rxStatus.packetStartTimeNs = chppGetCurrentTimeNs();
190 chppSetRxState(context, CHPP_STATE_HEADER);
191 }
192
193 return consumed;
194 }
195
196 /**
197 * Called by chppRxDataCb to process the packet header from the incoming data
198 * stream.
199 * Moves the Rx state to CHPP_STATE_PAYLOAD afterwards.
200 *
201 * @param context State of the transport layer.
202 * @param buf Input data.
203 * @param len Length of input data in bytes.
204 *
205 * @return Length of consumed data in bytes.
206 */
chppConsumeHeader(struct ChppTransportState * context,const uint8_t * buf,size_t len)207 static size_t chppConsumeHeader(struct ChppTransportState *context,
208 const uint8_t *buf, size_t len) {
209 CHPP_ASSERT(context->rxStatus.locInState <
210 sizeof(struct ChppTransportHeader));
211 size_t bytesToCopy = MIN(
212 len, (sizeof(struct ChppTransportHeader) - context->rxStatus.locInState));
213 memcpy(((uint8_t *)&context->rxHeader) + context->rxStatus.locInState, buf,
214 bytesToCopy);
215 context->rxStatus.locInState += bytesToCopy;
216
217 if (context->rxStatus.locInState == sizeof(struct ChppTransportHeader)) {
218 // Header fully copied. Move on
219
220 enum ChppTransportErrorCode headerCheckResult = chppRxHeaderCheck(context);
221 if (headerCheckResult != CHPP_TRANSPORT_ERROR_NONE) {
222 // Header fails consistency check. NACK and return to preamble state
223 chppEnqueueTxPacket(
224 context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(CHPP_TRANSPORT_ATTR_NONE,
225 headerCheckResult));
226 chppSetRxState(context, CHPP_STATE_PREAMBLE);
227
228 } else if (context->rxHeader.length == 0) {
229 // Non-payload packet
230 chppSetRxState(context, CHPP_STATE_FOOTER);
231
232 } else {
233 // Payload bearing packet
234 uint8_t *tempPayload;
235
236 if (context->rxDatagram.length == 0) {
237 // Packet is a new datagram
238 tempPayload = chppMalloc(context->rxHeader.length);
239 } else {
240 // Packet is a continuation of a fragmented datagram
241 tempPayload =
242 chppRealloc(context->rxDatagram.payload,
243 context->rxDatagram.length + context->rxHeader.length,
244 context->rxDatagram.length);
245 }
246
247 if (tempPayload == NULL) {
248 CHPP_LOG_OOM();
249 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_OOM);
250 chppSetRxState(context, CHPP_STATE_PREAMBLE);
251 } else {
252 context->rxDatagram.payload = tempPayload;
253 context->rxDatagram.length += context->rxHeader.length;
254 chppSetRxState(context, CHPP_STATE_PAYLOAD);
255 }
256 }
257 }
258
259 return bytesToCopy;
260 }
261
262 /**
263 * Called by chppRxDataCb to copy the payload, the length of which is determined
264 * by the header, from the incoming data stream.
265 * Moves the Rx state to CHPP_STATE_FOOTER afterwards.
266 *
267 * @param context State of the transport layer.
268 * @param buf Input data
269 * @param len Length of input data in bytes
270 *
271 * @return Length of consumed data in bytes
272 */
chppConsumePayload(struct ChppTransportState * context,const uint8_t * buf,size_t len)273 static size_t chppConsumePayload(struct ChppTransportState *context,
274 const uint8_t *buf, size_t len) {
275 CHPP_ASSERT(context->rxStatus.locInState < context->rxHeader.length);
276 size_t bytesToCopy =
277 MIN(len, (context->rxHeader.length - context->rxStatus.locInState));
278 memcpy(context->rxDatagram.payload + context->rxStatus.locInDatagram, buf,
279 bytesToCopy);
280 context->rxStatus.locInDatagram += bytesToCopy;
281 context->rxStatus.locInState += bytesToCopy;
282
283 if (context->rxStatus.locInState == context->rxHeader.length) {
284 // Entire packet payload copied. Move on
285 chppSetRxState(context, CHPP_STATE_FOOTER);
286 }
287
288 return bytesToCopy;
289 }
290
291 /**
292 * Called by chppRxDataCb to process the packet footer from the incoming data
293 * stream. Checks checksum, triggering the correct response (ACK / NACK).
294 * Moves the Rx state to CHPP_STATE_PREAMBLE afterwards.
295 *
296 * @param context State of the transport layer.
297 * @param buf Input data.
298 * @param len Length of input data in bytes.
299 *
300 * @return Length of consumed data in bytes.
301 */
chppConsumeFooter(struct ChppTransportState * context,const uint8_t * buf,size_t len)302 static size_t chppConsumeFooter(struct ChppTransportState *context,
303 const uint8_t *buf, size_t len) {
304 CHPP_ASSERT(context->rxStatus.locInState <
305 sizeof(struct ChppTransportFooter));
306 size_t bytesToCopy = MIN(
307 len, (sizeof(struct ChppTransportFooter) - context->rxStatus.locInState));
308 memcpy(((uint8_t *)&context->rxFooter) + context->rxStatus.locInState, buf,
309 bytesToCopy);
310
311 context->rxStatus.locInState += bytesToCopy;
312 if (context->rxStatus.locInState == sizeof(struct ChppTransportFooter)) {
313 // Footer copied. Move on
314
315 if (CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode) !=
316 CHPP_TRANSPORT_ERROR_NONE) {
317 CHPP_LOGE("RX packet len=%" PRIu16 " seq=%" PRIu8 " ackSeq=%" PRIu8
318 " attr=0x%" PRIx8 " ERR=%" PRIu8 " flags=0x%" PRIx8,
319 context->rxHeader.length, context->rxHeader.seq,
320 context->rxHeader.ackSeq,
321 (uint8_t)CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode),
322 (uint8_t)CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode),
323 context->rxHeader.flags);
324 } else {
325 CHPP_LOGD("RX packet len=%" PRIu16 " seq=%" PRIu8 " ackSeq=%" PRIu8
326 " attr=0x%" PRIx8 " err=%" PRIu8 " flags=0x%" PRIx8,
327 context->rxHeader.length, context->rxHeader.seq,
328 context->rxHeader.ackSeq,
329 (uint8_t)CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode),
330 (uint8_t)CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode),
331 context->rxHeader.flags);
332 }
333
334 if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
335 CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST) {
336 #ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
337 chppProcessTransportLoopbackRequest(context);
338 #endif
339
340 } else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
341 CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE) {
342 #ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
343 chppProcessTransportLoopbackResponse(context);
344 #endif
345
346 } else if (!chppRxChecksumIsOk(context)) {
347 CHPP_LOGE("Bad checksum seq=%" PRIu8 " len=%" PRIu16,
348 context->rxHeader.seq, context->rxHeader.length);
349 chppAbortRxPacket(context);
350 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_CHECKSUM); // NACK
351
352 } else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
353 CHPP_TRANSPORT_ATTR_RESET) {
354 CHPP_LOGI("RX RESET packet seq=%" PRIu8 " err=%" PRIu8,
355 context->rxHeader.seq,
356 CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode));
357 chppMutexUnlock(&context->mutex);
358 chppReset(context, CHPP_TRANSPORT_ATTR_RESET_ACK,
359 CHPP_TRANSPORT_ERROR_NONE);
360 chppMutexLock(&context->mutex);
361
362 } else if (context->resetState == CHPP_RESET_STATE_PERMANENT_FAILURE) {
363 // Only a reset is accepted in this state
364 CHPP_LOGE("RX discarded in perm fail seq=%" PRIu8 " len=%" PRIu16,
365 context->rxHeader.seq, context->rxHeader.length);
366 chppAbortRxPacket(context);
367
368 } else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
369 CHPP_TRANSPORT_ATTR_RESET_ACK) {
370 CHPP_LOGI("RX RESET-ACK packet seq=%" PRIu8, context->rxHeader.seq);
371 chppProcessResetAck(context);
372
373 } else if (context->resetState == CHPP_RESET_STATE_RESETTING) {
374 CHPP_LOGE("RX discarded in reset seq=%" PRIu8 " len=%" PRIu16,
375 context->rxHeader.seq, context->rxHeader.length);
376 chppAbortRxPacket(context);
377
378 } else {
379 chppProcessRxPacket(context);
380 }
381
382 // Done with this packet. Wait for next packet
383 chppSetRxState(context, CHPP_STATE_PREAMBLE);
384 }
385
386 return bytesToCopy;
387 }
388
389 /**
390 * Discards of an incomplete Rx packet during receive (e.g. due to a timeout or
391 * bad checksum).
392 *
393 * @param context State of the transport layer.
394 */
chppAbortRxPacket(struct ChppTransportState * context)395 static void chppAbortRxPacket(struct ChppTransportState *context) {
396 size_t undoLen = 0;
397 size_t undoLoc = 0;
398
399 switch (context->rxStatus.state) {
400 case (CHPP_STATE_PREAMBLE):
401 case (CHPP_STATE_HEADER): {
402 break;
403 }
404
405 case (CHPP_STATE_PAYLOAD): {
406 undoLen = context->rxHeader.length;
407 undoLoc = context->rxStatus.locInState;
408 break;
409 }
410
411 case (CHPP_STATE_FOOTER): {
412 undoLen = context->rxHeader.length;
413 undoLoc = context->rxHeader.length;
414 break;
415 }
416
417 default: {
418 CHPP_DEBUG_ASSERT(false);
419 }
420 }
421
422 if (undoLen > 0) {
423 // Packet has a payload we need to discard of
424
425 CHPP_ASSERT(context->rxDatagram.length >= undoLen);
426 CHPP_ASSERT(context->rxStatus.locInDatagram >= undoLoc);
427 context->rxDatagram.length -= undoLen;
428 context->rxStatus.locInDatagram -= undoLoc;
429
430 if (context->rxDatagram.length == 0) {
431 // Discarding this packet == discarding entire datagram
432 CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
433
434 } else {
435 // Discarding this packet == discarding part of datagram
436 uint8_t *tempPayload =
437 chppRealloc(context->rxDatagram.payload, context->rxDatagram.length,
438 context->rxDatagram.length + undoLen);
439
440 if (tempPayload == NULL) {
441 CHPP_LOG_OOM();
442 } else {
443 context->rxDatagram.payload = tempPayload;
444 }
445 }
446 }
447
448 chppSetRxState(context, CHPP_STATE_PREAMBLE);
449 }
450
451 /**
452 * Processes a request that is determined to be for a transport-layer loopback.
453 *
454 * @param context State of the transport layer.
455 */
456 #ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
chppProcessTransportLoopbackRequest(struct ChppTransportState * context)457 static void chppProcessTransportLoopbackRequest(
458 struct ChppTransportState *context) {
459 if (context->txStatus.linkBusy) {
460 CHPP_LOGE("Link busy; trans-loopback dropped");
461
462 } else {
463 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
464 context->txStatus.linkBusy = true;
465 context->linkBufferSize = 0;
466 context->linkBufferSize += chppAddPreamble(&linkTxBuffer[0]);
467
468 struct ChppTransportHeader *txHeader =
469 (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
470 context->linkBufferSize += sizeof(*txHeader);
471
472 *txHeader = context->rxHeader;
473 txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
474 CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE, txHeader->packetCode);
475
476 size_t payloadLen =
477 MIN(context->rxDatagram.length, chppTransportTxMtuSize(context));
478 chppAppendToPendingTxPacket(context, context->rxDatagram.payload,
479 payloadLen);
480 CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
481 chppClearRxDatagram(context);
482
483 chppAddFooter(context);
484
485 CHPP_LOGD("Trans-looping back len=%" PRIu16 " RX len=%" PRIuSIZE,
486 txHeader->length, context->rxDatagram.length);
487 enum ChppLinkErrorCode error = chppSendPendingPacket(context);
488
489 if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
490 chppLinkSendDoneCb(context, error);
491 }
492 }
493 }
494 #endif
495
496 /**
497 * Processes a response that is determined to be for a transport-layer loopback.
498 *
499 * @param context State of the transport layer.
500 */
501 #ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
chppProcessTransportLoopbackResponse(struct ChppTransportState * context)502 static void chppProcessTransportLoopbackResponse(
503 struct ChppTransportState *context) {
504 if (context->transportLoopbackData.length != context->rxDatagram.length) {
505 CHPP_LOGE("RX len=%" PRIuSIZE " != TX len=%" PRIuSIZE,
506 context->rxDatagram.length,
507 context->transportLoopbackData.length - CHPP_PREAMBLE_LEN_BYTES -
508 sizeof(struct ChppTransportHeader) -
509 sizeof(struct ChppTransportFooter));
510 context->loopbackResult = CHPP_APP_ERROR_INVALID_LENGTH;
511
512 } else if (memcmp(context->rxDatagram.payload,
513 context->transportLoopbackData.payload,
514 context->rxDatagram.length) != 0) {
515 CHPP_LOGE("RX & TX data don't match: len=%" PRIuSIZE,
516 context->rxDatagram.length);
517 context->loopbackResult = CHPP_APP_ERROR_INVALID_ARG;
518
519 } else {
520 context->loopbackResult = CHPP_APP_ERROR_NONE;
521
522 CHPP_LOGD("RX successful transport-loopback (payload len=%" PRIuSIZE ")",
523 context->rxDatagram.length);
524 }
525
526 context->transportLoopbackData.length = 0;
527 CHPP_FREE_AND_NULLIFY(context->transportLoopbackData.payload);
528 CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
529 chppClearRxDatagram(context);
530 }
531 #endif
532
533 /**
534 * Method to invoke when the reset sequence is completed.
535 *
536 * @param context State of the transport layer.
537 */
chppSetResetComplete(struct ChppTransportState * context)538 static void chppSetResetComplete(struct ChppTransportState *context) {
539 context->resetState = CHPP_RESET_STATE_NONE;
540 context->resetCount = 0;
541 chppConditionVariableSignal(&context->resetCondVar);
542 }
543
544 /**
545 * An incoming reset-ack packet indicates that a reset is complete at the other
546 * end of the CHPP link.
547 *
548 * @param context State of the transport layer.
549 */
chppProcessResetAck(struct ChppTransportState * context)550 static void chppProcessResetAck(struct ChppTransportState *context) {
551 if (context->resetState == CHPP_RESET_STATE_NONE) {
552 CHPP_LOGW("Unexpected reset-ack seq=%" PRIu8 " code=0x%" PRIx8,
553 context->rxHeader.seq, context->rxHeader.packetCode);
554 // In a reset race condition with both endpoints sending resets and
555 // reset-acks, the sent resets and reset-acks will both have a sequence
556 // number of 0.
557 // By ignoring the received reset-ack, the next expected sequence number
558 // will remain at 1 (following a reset with a sequence number of 0).
559 // Therefore, no further correction is necessary (beyond ignoring the
560 // received reset-ack), as the next packet (e.g. discovery) will have a
561 // sequence number of 1.
562
563 chppDatagramProcessDoneCb(context, context->rxDatagram.payload);
564 chppClearRxDatagram(context);
565
566 return;
567 }
568
569 chppSetResetComplete(context);
570 context->rxStatus.receivedPacketCode = context->rxHeader.packetCode;
571 context->rxStatus.expectedSeq = context->rxHeader.seq + 1;
572 chppRegisterRxAck(context);
573
574 // TODO: Configure transport layer based on (optional?) received config
575
576 chppDatagramProcessDoneCb(context, context->rxDatagram.payload);
577 chppClearRxDatagram(context);
578
579 #ifdef CHPP_CLIENT_ENABLED_DISCOVERY
580 if (context->appContext->isDiscoveryComplete) {
581 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
582 }
583 #else
584 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
585 #endif
586
587 // Inform the App Layer that a reset has completed
588 chppMutexUnlock(&context->mutex);
589 chppAppProcessReset(context->appContext);
590 chppMutexLock(&context->mutex);
591 }
592
593 /**
594 * Process a received, checksum-validated packet.
595 *
596 * @param context State of the transport layer.
597 */
chppProcessRxPacket(struct ChppTransportState * context)598 static void chppProcessRxPacket(struct ChppTransportState *context) {
599 uint64_t now = chppGetCurrentTimeNs();
600 context->rxStatus.lastGoodPacketTimeMs = (uint32_t)(now / CHPP_NSEC_PER_MSEC);
601 context->rxStatus.receivedPacketCode = context->rxHeader.packetCode;
602 bool gotExpectedAck = chppRegisterRxAck(context);
603
604 enum ChppTransportErrorCode errorCode = CHPP_TRANSPORT_ERROR_NONE;
605 if (context->rxHeader.length > 0 &&
606 context->rxHeader.seq != context->rxStatus.expectedSeq) {
607 // Out of order payload
608 errorCode = CHPP_TRANSPORT_ERROR_ORDER;
609 }
610
611 if ((gotExpectedAck && chppHavePendingTxPayload(context)) ||
612 errorCode == CHPP_TRANSPORT_ERROR_ORDER) {
613 // A pending packet was ACKed, or we need to send a NAK or duplicate ACK.
614 // Note: For a future ACK window > 1, makes more sense to cap the NACKs
615 // to one instead of flooding with out of order NACK errors.
616
617 // If the sender is retrying a packet we've already received successfully,
618 // send an ACK so it will continue normally
619 enum ChppTransportErrorCode errorCodeToSend = errorCode;
620 if (context->rxHeader.length > 0 &&
621 context->rxHeader.seq == context->rxStatus.expectedSeq - 1) {
622 // Pretend like we didn't actually send that last ackSeq so we'll send it
623 // again
624 context->txStatus.sentAckSeq--;
625 errorCodeToSend = CHPP_TRANSPORT_ERROR_NONE;
626 CHPP_LOGW("Got duplicate payload, resending ACK");
627 }
628
629 chppEnqueueTxPacket(
630 context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(CHPP_TRANSPORT_ATTR_NONE,
631 errorCodeToSend));
632 }
633
634 if (errorCode == CHPP_TRANSPORT_ERROR_ORDER) {
635 CHPP_LOGE("Out of order RX discarded seq=%" PRIu8 " expect=%" PRIu8
636 " len=%" PRIu16,
637 context->rxHeader.seq, context->rxStatus.expectedSeq,
638 context->rxHeader.length);
639 chppAbortRxPacket(context);
640
641 } else if (context->rxHeader.length > 0) {
642 // Process payload and send ACK
643 chppProcessRxPayload(context);
644 } else if (!chppHavePendingTxPayload(context)) {
645 // Nothing to send and nothing to receive, i.e. this is an ACK before an
646 // indefinite period of inactivity. Kick the work thread so it recalculates
647 // the notifier timeout.
648 chppNotifierSignal(&context->notifier,
649 CHPP_TRANSPORT_SIGNAL_RECALC_TIMEOUT);
650 }
651 }
652
653 /**
654 * Process the payload of a validated payload-bearing packet and send out the
655 * ACK.
656 *
657 * @param context State of the transport layer.
658 */
chppProcessRxPayload(struct ChppTransportState * context)659 static void chppProcessRxPayload(struct ChppTransportState *context) {
660 context->rxStatus.expectedSeq++; // chppProcessRxPacket() already confirms
661 // that context->rxStatus.expectedSeq ==
662 // context->rxHeader.seq, protecting against
663 // duplicate and out-of-order packets.
664
665 if (context->rxHeader.flags & CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM) {
666 // Packet is part of a larger datagram
667 CHPP_LOGD("RX packet for unfinished datagram. Seq=%" PRIu8 " len=%" PRIu16
668 ". Datagram len=%" PRIuSIZE ". Sending ACK=%" PRIu8,
669 context->rxHeader.seq, context->rxHeader.length,
670 context->rxDatagram.length, context->rxStatus.expectedSeq);
671
672 } else {
673 // End of this packet is end of a datagram
674
675 // Send the payload to the App Layer
676 // Note that it is up to the app layer to free the buffer using
677 // chppDatagramProcessDoneCb() after is is done.
678 chppMutexUnlock(&context->mutex);
679 chppAppProcessRxDatagram(context->appContext, context->rxDatagram.payload,
680 context->rxDatagram.length);
681 chppMutexLock(&context->mutex);
682
683 CHPP_LOGD("App layer processed datagram with len=%" PRIuSIZE
684 ", ending packet seq=%" PRIu8 ", len=%" PRIu16
685 ". Sending ACK=%" PRIu8 " (previously sent=%" PRIu8 ")",
686 context->rxDatagram.length, context->rxHeader.seq,
687 context->rxHeader.length, context->rxStatus.expectedSeq,
688 context->txStatus.sentAckSeq);
689 chppClearRxDatagram(context);
690 }
691
692 // Send ACK because we had RX a payload-bearing packet
693 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
694 }
695
696 /**
697 * Resets the incoming datagram state, i.e. after the datagram has been
698 * processed.
699 * Note that this is independent from freeing the payload. It is up to the app
700 * layer to inform the transport layer using chppDatagramProcessDoneCb() once it
701 * is done with the buffer so it is freed.
702 *
703 * @param context State of the transport layer.
704 */
chppClearRxDatagram(struct ChppTransportState * context)705 static void chppClearRxDatagram(struct ChppTransportState *context) {
706 context->rxStatus.locInDatagram = 0;
707 context->rxDatagram.length = 0;
708 context->rxDatagram.payload = NULL;
709 }
710
711 /**
712 * Validates the checksum of an incoming packet.
713 *
714 * @param context State of the transport layer.
715 *
716 * @return True if and only if the checksum is correct.
717 */
chppRxChecksumIsOk(const struct ChppTransportState * context)718 static bool chppRxChecksumIsOk(const struct ChppTransportState *context) {
719 uint32_t crc = chppCrc32(0, (const uint8_t *)&context->rxHeader,
720 sizeof(context->rxHeader));
721 crc = chppCrc32(
722 crc,
723 &context->rxDatagram
724 .payload[context->rxStatus.locInDatagram - context->rxHeader.length],
725 context->rxHeader.length);
726
727 if (context->rxFooter.checksum != crc) {
728 CHPP_LOGE("RX BAD checksum: footer=0x%" PRIx32 ", calc=0x%" PRIx32
729 ", len=%" PRIuSIZE,
730 context->rxFooter.checksum, crc,
731 (size_t)(context->rxHeader.length +
732 sizeof(struct ChppTransportHeader)));
733 }
734
735 return (context->rxFooter.checksum == crc);
736 }
737
738 /**
739 * Performs consistency checks on received packet header to determine if it is
740 * obviously corrupt / invalid / duplicate / out-of-order.
741 *
742 * @param context State of the transport layer.
743 *
744 * @return True if and only if header passes checks
745 */
chppRxHeaderCheck(const struct ChppTransportState * context)746 static enum ChppTransportErrorCode chppRxHeaderCheck(
747 const struct ChppTransportState *context) {
748 enum ChppTransportErrorCode result = CHPP_TRANSPORT_ERROR_NONE;
749
750 if (context->rxHeader.length > chppTransportRxMtuSize(context)) {
751 result = CHPP_TRANSPORT_ERROR_HEADER;
752 }
753
754 if (result != CHPP_TRANSPORT_ERROR_NONE) {
755 CHPP_LOGE("Bad header. seq=%" PRIu8 " expect=%" PRIu8 " len=%" PRIu16
756 " err=%" PRIu8,
757 context->rxHeader.seq, context->rxStatus.expectedSeq,
758 context->rxHeader.length, result);
759 }
760
761 return result;
762 }
763
764 /**
765 * Registers a received ACK. If an outgoing datagram is fully ACKed, it is
766 * popped from the TX queue.
767 *
768 * @param context State of the transport layer.
769 * @return true if we got an ACK for a pending TX packet
770 */
chppRegisterRxAck(struct ChppTransportState * context)771 static bool chppRegisterRxAck(struct ChppTransportState *context) {
772 uint8_t rxAckSeq = context->rxHeader.ackSeq;
773 bool gotExpectedAck = false;
774
775 if (context->rxStatus.receivedAckSeq != rxAckSeq) {
776 // A previously sent packet was actually ACKed
777 // Note: For a future ACK window >1, we should loop by # of ACKed packets
778 if ((uint8_t)(context->rxStatus.receivedAckSeq + 1) != rxAckSeq) {
779 CHPP_LOGE("Out of order ACK: last=%" PRIu8 " rx=%" PRIu8,
780 context->rxStatus.receivedAckSeq, rxAckSeq);
781 } else {
782 CHPP_LOGD(
783 "ACK received (last registered=%" PRIu8 ", received=%" PRIu8
784 "). Prior queue depth=%" PRIu8 ", front datagram=%" PRIu8
785 " at loc=%" PRIuSIZE " of len=%" PRIuSIZE,
786 context->rxStatus.receivedAckSeq, rxAckSeq,
787 context->txDatagramQueue.pending, context->txDatagramQueue.front,
788 context->txStatus.ackedLocInDatagram,
789 context->txDatagramQueue.datagram[context->txDatagramQueue.front]
790 .length);
791 gotExpectedAck = true;
792 context->rxStatus.receivedAckSeq = rxAckSeq;
793 if (context->txStatus.txAttempts > 1) {
794 CHPP_LOGW("Seq %" PRIu8 " ACK'd after %" PRIuSIZE " reTX",
795 context->rxHeader.ackSeq - 1,
796 context->txStatus.txAttempts - 1);
797 }
798 context->txStatus.txAttempts = 0;
799
800 // Process and if necessary pop from Tx datagram queue
801 context->txStatus.ackedLocInDatagram += chppTransportTxMtuSize(context);
802 if (context->txStatus.ackedLocInDatagram >=
803 context->txDatagramQueue.datagram[context->txDatagramQueue.front]
804 .length) {
805 // We are done with datagram
806
807 context->txStatus.ackedLocInDatagram = 0;
808 context->txStatus.sentLocInDatagram = 0;
809
810 // Note: For a future ACK window >1, we need to update the queue
811 // position of the datagram being sent as well (relative to the
812 // front-of-queue). e.g. context->txStatus.datagramBeingSent--;
813
814 chppDequeueTxDatagram(context);
815 }
816 }
817 } // else {nothing was ACKed}
818
819 return gotExpectedAck;
820 }
821
822 /**
823 * Enqueues an outgoing packet with the specified error code. The error code
824 * refers to the optional reason behind a NACK, if any. An error code of
825 * CHPP_TRANSPORT_ERROR_NONE indicates that no error was reported (i.e. either
826 * an ACK or an implicit NACK)
827 *
828 * Note that the decision as to whether to include a payload will be taken
829 * later, i.e. before the packet is being sent out from the queue. A payload is
830 * expected to be included if there is one or more pending Tx datagrams and we
831 * are not waiting on a pending ACK. A (repeat) payload is also included if we
832 * have received a NACK.
833 *
834 * Further note that even for systems with an ACK window greater than one, we
835 * would only need to send an ACK for the last (correct) packet, hence we only
836 * need a queue length of one here.
837 *
838 * @param context State of the transport layer.
839 * @param packetCode Error code and packet attributes to be sent.
840 */
chppEnqueueTxPacket(struct ChppTransportState * context,uint8_t packetCode)841 static void chppEnqueueTxPacket(struct ChppTransportState *context,
842 uint8_t packetCode) {
843 context->txStatus.packetCodeToSend = packetCode;
844
845 CHPP_LOGD("chppEnqueueTxPacket called with packet code=0x%" PRIx8,
846 packetCode);
847
848 // Notifies the main CHPP Transport Layer to run chppTransportDoWork().
849 chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_EVENT);
850 }
851
852 /**
853 * @return true if we have payload on the TX queue that either hasn't been sent
854 * or has been sent but not ACKed
855 */
chppHavePendingTxPayload(const struct ChppTransportState * context)856 static bool chppHavePendingTxPayload(const struct ChppTransportState *context) {
857 return (context->txDatagramQueue.pending > 0);
858 }
859
860 /**
861 * @return true if we have pending payload that should be included in the next
862 * outbound packet
863 */
chppShouldAttachPayload(const struct ChppTransportState * context,bool resendPayload)864 static bool chppShouldAttachPayload(const struct ChppTransportState *context,
865 bool resendPayload) {
866 // We should attach payload to an outbound packet if and only if:
867 // - We have payload to send on the queue AND
868 // - We haven't sent it yet, OR we are resending it (i.e. a retry)
869 bool havePayloadToSend = chppHavePendingTxPayload(context);
870 bool haventSentPayloadYet = (context->txStatus.txAttempts == 0);
871 if (resendPayload && !havePayloadToSend) {
872 CHPP_LOGE("Trying to resend non-existent payload!");
873 }
874 return (havePayloadToSend && (haventSentPayloadYet || resendPayload));
875 }
876
877 /**
878 * @return true if we should send a packet even if we don't have payload
879 */
chppShouldSendPossiblyEmptyPacket(const struct ChppTransportState * context)880 static bool chppShouldSendPossiblyEmptyPacket(
881 const struct ChppTransportState *context) {
882 // We should send a packet (even if we have no payload) if and only if:
883 // - We're sending an ACK for a newly received packet (we've updated our
884 // expectedSeq but haven't sent this yet)
885 // - We're sending a special packet code, e.g. RESET/RESET-ACK/NAK
886 return (context->rxStatus.expectedSeq != context->txStatus.sentAckSeq ||
887 context->txStatus.packetCodeToSend !=
888 CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(CHPP_TRANSPORT_ATTR_NONE,
889 CHPP_TRANSPORT_ERROR_NONE));
890 }
891
892 /**
893 * Adds a CHPP preamble to the beginning of buf.
894 *
895 * @param buf The CHPP preamble will be added to buf.
896 *
897 * @return Size of the added preamble.
898 */
chppAddPreamble(uint8_t * buf)899 static size_t chppAddPreamble(uint8_t *buf) {
900 buf[0] = CHPP_PREAMBLE_BYTE_FIRST;
901 buf[1] = CHPP_PREAMBLE_BYTE_SECOND;
902 return CHPP_PREAMBLE_LEN_BYTES;
903 }
904
905 /**
906 * Adds the packet header to link tx buffer.
907 *
908 * @param context State of the transport layer.
909 *
910 * @return Pointer to the added packet header.
911 */
chppAddHeader(struct ChppTransportState * context)912 static struct ChppTransportHeader *chppAddHeader(
913 struct ChppTransportState *context) {
914 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
915 struct ChppTransportHeader *txHeader =
916 (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
917 context->linkBufferSize += sizeof(*txHeader);
918
919 txHeader->packetCode = context->txStatus.packetCodeToSend;
920 context->txStatus.packetCodeToSend = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
921 context->txStatus.packetCodeToSend, CHPP_TRANSPORT_ERROR_NONE);
922
923 txHeader->ackSeq = context->rxStatus.expectedSeq;
924 context->txStatus.sentAckSeq = txHeader->ackSeq;
925
926 return txHeader;
927 }
928
929 /**
930 * Adds the packet payload to link tx buffer.
931 *
932 * @param context State of the transport layer.
933 */
chppAddPayload(struct ChppTransportState * context)934 static void chppAddPayload(struct ChppTransportState *context) {
935 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
936 struct ChppTransportHeader *txHeader =
937 (struct ChppTransportHeader *)&linkTxBuffer[CHPP_PREAMBLE_LEN_BYTES];
938
939 size_t remainingBytes =
940 context->txDatagramQueue.datagram[context->txDatagramQueue.front].length -
941 context->txStatus.ackedLocInDatagram;
942
943 CHPP_LOGD("Adding payload to seq=%" PRIu8 ", remainingBytes=%" PRIuSIZE
944 " of pending datagrams=%" PRIu8,
945 txHeader->seq, remainingBytes, context->txDatagramQueue.pending);
946
947 if (remainingBytes > chppTransportTxMtuSize(context)) {
948 // Send an unfinished part of a datagram
949 txHeader->flags = CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM;
950 txHeader->length = (uint16_t)chppTransportTxMtuSize(context);
951 } else {
952 // Send final (or only) part of a datagram
953 txHeader->flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM;
954 txHeader->length = (uint16_t)remainingBytes;
955 }
956
957 // Copy payload
958 chppAppendToPendingTxPacket(
959 context,
960 context->txDatagramQueue.datagram[context->txDatagramQueue.front]
961 .payload +
962 context->txStatus.ackedLocInDatagram,
963 txHeader->length);
964
965 context->txStatus.sentLocInDatagram =
966 context->txStatus.ackedLocInDatagram + txHeader->length;
967 }
968
969 /**
970 * Adds a footer (containing the checksum) to a packet.
971 *
972 * @param context State of the transport layer.
973 */
chppAddFooter(struct ChppTransportState * context)974 static void chppAddFooter(struct ChppTransportState *context) {
975 struct ChppTransportFooter footer;
976 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
977 size_t bufferSize = context->linkBufferSize;
978
979 footer.checksum = chppCrc32(0, &linkTxBuffer[CHPP_PREAMBLE_LEN_BYTES],
980 bufferSize - CHPP_PREAMBLE_LEN_BYTES);
981
982 CHPP_LOGD("Adding transport footer. Checksum=0x%" PRIx32 ", len: %" PRIuSIZE
983 " -> %" PRIuSIZE,
984 footer.checksum, bufferSize, bufferSize + sizeof(footer));
985
986 chppAppendToPendingTxPacket(context, (const uint8_t *)&footer,
987 sizeof(footer));
988 }
989
990 /**
991 * Dequeues the datagram at the front of the datagram tx queue, if any, and
992 * frees the payload. Returns the number of remaining datagrams in the queue.
993 *
994 * @param context State of the transport layer.
995 * @return Number of remaining datagrams in queue.
996 */
chppDequeueTxDatagram(struct ChppTransportState * context)997 size_t chppDequeueTxDatagram(struct ChppTransportState *context) {
998 if (context->txDatagramQueue.pending == 0) {
999 CHPP_LOGE("Can not dequeue empty datagram queue");
1000
1001 } else {
1002 CHPP_LOGD("Dequeuing front datagram with index=%" PRIu8 ", len=%" PRIuSIZE
1003 ". Queue depth: %" PRIu8 "->%d",
1004 context->txDatagramQueue.front,
1005 context->txDatagramQueue.datagram[context->txDatagramQueue.front]
1006 .length,
1007 context->txDatagramQueue.pending,
1008 context->txDatagramQueue.pending - 1);
1009
1010 CHPP_FREE_AND_NULLIFY(
1011 context->txDatagramQueue.datagram[context->txDatagramQueue.front]
1012 .payload);
1013 context->txDatagramQueue.datagram[context->txDatagramQueue.front].length =
1014 0;
1015
1016 context->txDatagramQueue.pending--;
1017 context->txDatagramQueue.front++;
1018 context->txDatagramQueue.front %= CHPP_TX_DATAGRAM_QUEUE_LEN;
1019 }
1020
1021 return context->txDatagramQueue.pending;
1022 }
1023
1024 /**
1025 * Flushes the Tx datagram queue of any pending packets.
1026 *
1027 * @param context State of the transport layer.
1028 */
chppClearTxDatagramQueue(struct ChppTransportState * context)1029 static void chppClearTxDatagramQueue(struct ChppTransportState *context) {
1030 while (chppHavePendingTxPayload(context)) {
1031 chppDequeueTxDatagram(context);
1032 }
1033 }
1034
1035 /**
1036 * Sends out a pending outgoing packet based on a notification from
1037 * chppEnqueueTxPacket().
1038 *
1039 * A payload may or may not be included be according the following:
1040 * No payload: If Tx datagram queue is empty OR we are waiting on a pending ACK.
1041 * New payload: If there is one or more pending Tx datagrams and we are not
1042 * waiting on a pending ACK.
1043 * Repeat payload: If we haven't received an ACK yet for our previous payload,
1044 * i.e. we have registered an explicit or implicit NACK.
1045 *
1046 * @param context State of the transport layer.
1047 * @param resendPayload true if we should always attach the queued
1048 * payload to the packet, false to only send it if it's the first attempt
1049 */
chppTransportDoWork(struct ChppTransportState * context,bool resendPayload)1050 static void chppTransportDoWork(struct ChppTransportState *context,
1051 bool resendPayload) {
1052 bool havePacketForLinkLayer = false;
1053 struct ChppTransportHeader *txHeader;
1054
1055 // Note: For a future ACK window >1, there needs to be a loop outside the lock
1056 chppMutexLock(&context->mutex);
1057
1058 bool sendPayload = chppShouldAttachPayload(context, resendPayload);
1059 if (!context->txStatus.linkBusy &&
1060 (sendPayload || chppShouldSendPossiblyEmptyPacket(context))) {
1061 havePacketForLinkLayer = true;
1062 context->txStatus.linkBusy = true;
1063
1064 context->linkBufferSize = 0;
1065 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
1066 const struct ChppLinkConfiguration linkConfig =
1067 context->linkApi->getConfig(context->linkContext);
1068 memset(linkTxBuffer, 0, linkConfig.txBufferLen);
1069
1070 // Add preamble
1071 context->linkBufferSize += chppAddPreamble(linkTxBuffer);
1072
1073 // Add header
1074 txHeader = chppAddHeader(context);
1075
1076 if (sendPayload) {
1077 // Either we haven't sent this payload yet, or we are retrying it
1078 // Note: For a future ACK window >1, we need to rewrite this payload
1079 // adding code to base the next packet on the sent location within the
1080 // last sent datagram, except for the case of a NACK (explicit or
1081 // timeout). For a NACK, we would need to base the next packet off the
1082 // last ACKed location.
1083 txHeader->seq = context->rxStatus.receivedAckSeq;
1084 context->txStatus.sentSeq = txHeader->seq;
1085
1086 if (context->txStatus.txAttempts > CHPP_TRANSPORT_MAX_RETX &&
1087 context->resetState != CHPP_RESET_STATE_RESETTING) {
1088 CHPP_LOGE("Resetting after %d reTX", CHPP_TRANSPORT_MAX_RETX);
1089 havePacketForLinkLayer = false;
1090
1091 chppMutexUnlock(&context->mutex);
1092 chppReset(context, CHPP_TRANSPORT_ATTR_RESET,
1093 CHPP_TRANSPORT_ERROR_MAX_RETRIES);
1094 chppMutexLock(&context->mutex);
1095
1096 } else {
1097 chppAddPayload(context);
1098 context->txStatus.txAttempts++;
1099 }
1100 } else if (chppHavePendingTxPayload(context)) {
1101 // We have pending payload but aren't sending it, for example if we're
1102 // just sending a NAK for a bad incoming payload-bearing packet
1103 CHPP_LOGI("Skipping attaching pending payload");
1104 }
1105
1106 chppAddFooter(context);
1107
1108 } else {
1109 CHPP_LOGW("DoWork nothing to send. linkBusy=%d, pending=%" PRIu8
1110 ", RX ACK=%" PRIu8 ", TX seq=%" PRIu8 ", RX state=%s",
1111 context->txStatus.linkBusy, context->txDatagramQueue.pending,
1112 context->rxStatus.receivedAckSeq, context->txStatus.sentSeq,
1113 chppGetRxStatusLabel(context->rxStatus.state));
1114 }
1115
1116 chppMutexUnlock(&context->mutex);
1117
1118 if (havePacketForLinkLayer) {
1119 CHPP_LOGD("TX->Link: len=%" PRIuSIZE " flags=0x%" PRIx8 " code=0x%" PRIx8
1120 " ackSeq=%" PRIu8 " seq=%" PRIu8 " payloadLen=%" PRIu16
1121 " pending=%" PRIu8,
1122 context->linkBufferSize, txHeader->flags, txHeader->packetCode,
1123 txHeader->ackSeq, txHeader->seq, txHeader->length,
1124 context->txDatagramQueue.pending);
1125 enum ChppLinkErrorCode error = chppSendPendingPacket(context);
1126
1127 if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
1128 // Platform implementation for platformLinkSend() is synchronous or an
1129 // error occurred. In either case, we should call chppLinkSendDoneCb()
1130 // here to release the contents of tx link buffer.
1131 chppLinkSendDoneCb(context, error);
1132 }
1133 }
1134
1135 #ifdef CHPP_CLIENT_ENABLED
1136 { // create a scope to declare timeoutResponse (C89).
1137 struct ChppAppHeader *timeoutResponse =
1138 chppTransportGetRequestTimeoutResponse(context, CHPP_ENDPOINT_CLIENT);
1139
1140 if (timeoutResponse != NULL) {
1141 CHPP_LOGE("Response timeout H#%" PRIu8 " cmd=%" PRIu16 " ID=%" PRIu8,
1142 timeoutResponse->handle, timeoutResponse->command,
1143 timeoutResponse->transaction);
1144 chppAppProcessRxDatagram(context->appContext, (uint8_t *)timeoutResponse,
1145 sizeof(struct ChppAppHeader));
1146 }
1147 }
1148 #endif // CHPP_CLIENT_ENABLED
1149 #ifdef CHPP_SERVICE_ENABLED
1150 { // create a scope to declare timeoutResponse (C89).
1151 struct ChppAppHeader *timeoutResponse =
1152 chppTransportGetRequestTimeoutResponse(context, CHPP_ENDPOINT_SERVICE);
1153
1154 if (timeoutResponse != NULL) {
1155 CHPP_LOGE("Response timeout H#%" PRIu8 " cmd=%" PRIu16 " ID=%" PRIu8,
1156 timeoutResponse->handle, timeoutResponse->command,
1157 timeoutResponse->transaction);
1158 chppAppProcessRxDatagram(context->appContext, (uint8_t *)timeoutResponse,
1159 sizeof(struct ChppAppHeader));
1160 }
1161 }
1162 #endif // CHPP_SERVICE_ENABLED
1163 }
1164
1165 /**
1166 * Appends data from a buffer of length len to a link tx buffer, updating its
1167 * length.
1168 *
1169 * @param context State of the transport layer.
1170 * @param buf Input data to be copied from.
1171 * @param len Length of input data in bytes.
1172 */
chppAppendToPendingTxPacket(struct ChppTransportState * context,const uint8_t * buf,size_t len)1173 static void chppAppendToPendingTxPacket(struct ChppTransportState *context,
1174 const uint8_t *buf, size_t len) {
1175 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
1176
1177 size_t bufferSize = context->linkBufferSize;
1178
1179 CHPP_ASSERT(bufferSize + len <=
1180 context->linkApi->getConfig(context->linkContext).txBufferLen);
1181 memcpy(&linkTxBuffer[bufferSize], buf, len);
1182 context->linkBufferSize += len;
1183 }
1184
1185 /**
1186 * @return A human readable form of the packet attribution.
1187 */
chppGetPacketAttrStr(uint8_t packetCode)1188 static const char *chppGetPacketAttrStr(uint8_t packetCode) {
1189 switch (CHPP_TRANSPORT_GET_ATTR(packetCode)) {
1190 case CHPP_TRANSPORT_ATTR_RESET:
1191 return "(RESET)";
1192 case CHPP_TRANSPORT_ATTR_RESET_ACK:
1193 return "(RESET-ACK)";
1194 case CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST:
1195 return "(LOOP-REQ)";
1196 case CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE:
1197 return "(LOOP-RES)";
1198 default:
1199 return "";
1200 }
1201 }
1202
1203 /**
1204 * Enqueues an outgoing datagram of a specified length. The payload must have
1205 * been allocated by the caller using chppMalloc.
1206 *
1207 * If enqueueing is successful, the payload will be freed by this function
1208 * once it has been sent out.
1209 * If enqueueing is unsuccessful, it is up to the caller to decide when or if
1210 * to free the payload and/or resend it later.
1211 *
1212 * ChppTransportState->mutex must be locked prior to invoking this method.
1213 *
1214 * @param context State of the transport layer.
1215 * @param packetCode Error code and packet attributes to be sent.
1216 * @param buf Datagram payload allocated through chppMalloc. Cannot be null.
1217 * @param len Datagram length in bytes.
1218 *
1219 * @return True informs the sender that the datagram was successfully enqueued.
1220 * False informs the sender that the queue was full.
1221 */
chppEnqueueTxDatagramLocked(struct ChppTransportState * context,uint8_t packetCode,void * buf,size_t len)1222 static bool chppEnqueueTxDatagramLocked(struct ChppTransportState *context,
1223 uint8_t packetCode, void *buf,
1224 size_t len) {
1225 bool success = false;
1226
1227 if (len == 0) {
1228 CHPP_DEBUG_ASSERT_LOG(false, "Enqueue TX len=0!");
1229
1230 } else {
1231 if ((len < sizeof(struct ChppAppHeader)) ||
1232 (CHPP_TRANSPORT_GET_ATTR(packetCode) != 0)) {
1233 CHPP_LOGD("Enqueue TX: code=0x%" PRIx8 "%s len=%" PRIuSIZE
1234 " pending=%" PRIu8,
1235 packetCode, chppGetPacketAttrStr(packetCode), len,
1236 (uint8_t)(context->txDatagramQueue.pending + 1));
1237 } else {
1238 struct ChppAppHeader *header = buf;
1239 CHPP_LOGD(
1240 "Enqueue TX: len=%" PRIuSIZE " H#%" PRIu8 " type=0x%" PRIx8
1241 " ID=%" PRIu8 " err=%" PRIu8 " cmd=0x%" PRIx16 " pending=%" PRIu8,
1242 len, header->handle, header->type, header->transaction, header->error,
1243 header->command, (uint8_t)(context->txDatagramQueue.pending + 1));
1244 }
1245
1246 if (context->txDatagramQueue.pending >= CHPP_TX_DATAGRAM_QUEUE_LEN) {
1247 CHPP_LOGE("Cannot enqueue TX datagram");
1248
1249 } else {
1250 uint16_t end =
1251 (context->txDatagramQueue.front + context->txDatagramQueue.pending) %
1252 CHPP_TX_DATAGRAM_QUEUE_LEN;
1253 context->txDatagramQueue.datagram[end].length = len;
1254 context->txDatagramQueue.datagram[end].payload = buf;
1255 context->txDatagramQueue.pending++;
1256
1257 if (context->txDatagramQueue.pending == 1) {
1258 // Queue was empty prior. Need to kickstart transmission.
1259 chppEnqueueTxPacket(context, packetCode);
1260 }
1261
1262 success = true;
1263 }
1264 }
1265
1266 return success;
1267 }
1268
1269 /**
1270 * Sends the pending outgoing packet over to the link
1271 * layer using Send() and updates the last Tx packet time.
1272 *
1273 * @param context State of the transport layer.
1274 *
1275 * @return Result of Send().
1276 */
chppSendPendingPacket(struct ChppTransportState * context)1277 static enum ChppLinkErrorCode chppSendPendingPacket(
1278 struct ChppTransportState *context) {
1279 enum ChppLinkErrorCode error =
1280 context->linkApi->send(context->linkContext, context->linkBufferSize);
1281
1282 context->txStatus.lastTxTimeNs = chppGetCurrentTimeNs();
1283
1284 return error;
1285 }
1286
1287 /**
1288 * Resets the transport state, maintaining the link layer parameters.
1289 *
1290 * @param context State of the transport layer.
1291 */
chppResetTransportContext(struct ChppTransportState * context)1292 static void chppResetTransportContext(struct ChppTransportState *context) {
1293 memset(&context->rxStatus, 0, sizeof(struct ChppRxStatus));
1294 memset(&context->rxDatagram, 0, sizeof(struct ChppDatagram));
1295
1296 memset(&context->txStatus, 0, sizeof(struct ChppTxStatus));
1297 memset(&context->txDatagramQueue, 0, sizeof(struct ChppTxDatagramQueue));
1298
1299 context->txStatus.sentSeq =
1300 UINT8_MAX; // So that the seq # of the first TX packet is 0
1301 context->resetState = CHPP_RESET_STATE_RESETTING;
1302 }
1303
1304 /**
1305 * Re-initializes the CHPP transport and app layer states, e.g. when receiving a
1306 * reset packet, and sends out a reset or reset-ack packet over the link in
1307 * order to reset the remote side or inform the counterpart of a reset,
1308 * respectively.
1309 *
1310 * If the link layer is busy, this function will reset the link as well.
1311 * This function retains and restores the platform-specific values of
1312 * transportContext.linkContext.
1313 *
1314 * @param transportContext State of the transport layer.
1315 * @param resetType Type of reset to send after resetting CHPP (reset vs.
1316 * reset-ack), as defined in the ChppTransportPacketAttributes struct.
1317 * @param error Provides the error that led to the reset.
1318 */
chppReset(struct ChppTransportState * transportContext,enum ChppTransportPacketAttributes resetType,enum ChppTransportErrorCode error)1319 static void chppReset(struct ChppTransportState *transportContext,
1320 enum ChppTransportPacketAttributes resetType,
1321 enum ChppTransportErrorCode error) {
1322 // TODO: Configure transport layer based on (optional?) received config before
1323 // datagram is wiped
1324
1325 chppMutexLock(&transportContext->mutex);
1326 struct ChppAppState *appContext = transportContext->appContext;
1327 transportContext->resetState = CHPP_RESET_STATE_RESETTING;
1328
1329 // Reset asynchronous link layer if busy
1330 if (transportContext->txStatus.linkBusy == true) {
1331 // TODO: Give time for link layer to finish before resorting to a reset
1332
1333 transportContext->linkApi->reset(transportContext->linkContext);
1334 }
1335
1336 // Free memory allocated for any ongoing rx datagrams
1337 if (transportContext->rxDatagram.length > 0) {
1338 transportContext->rxDatagram.length = 0;
1339 CHPP_FREE_AND_NULLIFY(transportContext->rxDatagram.payload);
1340 }
1341
1342 // Free memory allocated for any ongoing tx datagrams
1343 for (size_t i = 0; i < CHPP_TX_DATAGRAM_QUEUE_LEN; i++) {
1344 if (transportContext->txDatagramQueue.datagram[i].length > 0) {
1345 CHPP_FREE_AND_NULLIFY(
1346 transportContext->txDatagramQueue.datagram[i].payload);
1347 }
1348 }
1349
1350 // Reset Transport Layer but restore Rx sequence number and packet code
1351 // (context->rxHeader is not wiped in reset)
1352 chppResetTransportContext(transportContext);
1353 transportContext->rxStatus.receivedPacketCode =
1354 transportContext->rxHeader.packetCode;
1355 transportContext->rxStatus.expectedSeq = transportContext->rxHeader.seq + 1;
1356
1357 // Send reset or reset-ACK
1358 chppTransportSendResetLocked(transportContext, resetType, error);
1359 chppMutexUnlock(&transportContext->mutex);
1360
1361 // Inform the App Layer that a reset has completed
1362 if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
1363 chppAppProcessReset(appContext);
1364 } // else reset is sent out. Rx of reset-ack will indicate completion.
1365 }
1366
1367 /**
1368 * Checks for a timed out request and generates a timeout response if a timeout
1369 * has occurred.
1370 *
1371 * @param context State of the transport layer.
1372 * @param type The type of the endpoint.
1373 * @return App layer response header if a timeout has occurred. Null otherwise.
1374 */
chppTransportGetRequestTimeoutResponse(struct ChppTransportState * context,enum ChppEndpointType type)1375 struct ChppAppHeader *chppTransportGetRequestTimeoutResponse(
1376 struct ChppTransportState *context, enum ChppEndpointType type) {
1377 CHPP_DEBUG_NOT_NULL(context);
1378
1379 struct ChppAppState *appState = context->appContext;
1380 struct ChppAppHeader *response = NULL;
1381
1382 bool timeoutEndpointFound = false;
1383 uint8_t timedOutEndpointIdx;
1384 uint16_t timedOutCmd;
1385
1386 chppMutexLock(&context->mutex);
1387
1388 if (*getNextRequestTimeoutNs(appState, type) <= chppGetCurrentTimeNs()) {
1389 // Determine which request has timed out
1390 const uint8_t endpointCount = getRegisteredEndpointCount(appState, type);
1391 uint64_t firstTimeout = CHPP_TIME_MAX;
1392
1393 for (uint8_t endpointIdx = 0; endpointIdx < endpointCount; endpointIdx++) {
1394 const uint16_t cmdCount =
1395 getRegisteredEndpointOutReqCount(appState, endpointIdx, type);
1396 const struct ChppEndpointState *endpointState =
1397 getRegisteredEndpointState(appState, endpointIdx, type);
1398 const struct ChppOutgoingRequestState *reqStates =
1399 &endpointState->outReqStates[0];
1400 for (uint16_t cmdIdx = 0; cmdIdx < cmdCount; cmdIdx++) {
1401 const struct ChppOutgoingRequestState *reqState = &reqStates[cmdIdx];
1402
1403 if (reqState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT &&
1404 reqState->responseTimeNs != CHPP_TIME_NONE &&
1405 reqState->responseTimeNs < firstTimeout) {
1406 firstTimeout = reqState->responseTimeNs;
1407 timedOutEndpointIdx = endpointIdx;
1408 timedOutCmd = cmdIdx;
1409 timeoutEndpointFound = true;
1410 }
1411 }
1412 }
1413
1414 if (!timeoutEndpointFound) {
1415 CHPP_LOGE("Timeout at %" PRIu64 " but no endpoint",
1416 *getNextRequestTimeoutNs(appState, type) / CHPP_NSEC_PER_MSEC);
1417 chppRecalculateNextTimeout(appState, CHPP_ENDPOINT_CLIENT);
1418 }
1419 }
1420
1421 if (timeoutEndpointFound) {
1422 CHPP_LOGE("Endpoint=%" PRIu8 " cmd=%" PRIu16 " timed out",
1423 timedOutEndpointIdx, timedOutCmd);
1424 response = chppMalloc(sizeof(struct ChppAppHeader));
1425 if (response == NULL) {
1426 CHPP_LOG_OOM();
1427 } else {
1428 const struct ChppEndpointState *endpointState =
1429 getRegisteredEndpointState(appState, timedOutEndpointIdx, type);
1430 response->handle = endpointState->handle;
1431 response->type = type == CHPP_ENDPOINT_CLIENT
1432 ? CHPP_MESSAGE_TYPE_SERVICE_RESPONSE
1433 : CHPP_MESSAGE_TYPE_CLIENT_RESPONSE;
1434 response->transaction =
1435 endpointState->outReqStates[timedOutCmd].transaction;
1436 response->error = CHPP_APP_ERROR_TIMEOUT;
1437 response->command = timedOutCmd;
1438 }
1439 }
1440
1441 chppMutexUnlock(&context->mutex);
1442
1443 return response;
1444 }
1445
1446 /************************************************
1447 * Public Functions
1448 ***********************************************/
1449
chppTransportInit(struct ChppTransportState * transportContext,struct ChppAppState * appContext,void * linkContext,const struct ChppLinkApi * linkApi)1450 void chppTransportInit(struct ChppTransportState *transportContext,
1451 struct ChppAppState *appContext, void *linkContext,
1452 const struct ChppLinkApi *linkApi) {
1453 CHPP_NOT_NULL(transportContext);
1454 CHPP_NOT_NULL(appContext);
1455
1456 CHPP_ASSERT_LOG(!transportContext->initialized,
1457 "CHPP transport already init");
1458 CHPP_LOGD("Initializing CHPP transport");
1459
1460 chppResetTransportContext(transportContext);
1461 chppMutexInit(&transportContext->mutex);
1462 chppNotifierInit(&transportContext->notifier);
1463 chppConditionVariableInit(&transportContext->resetCondVar);
1464 #ifdef CHPP_ENABLE_WORK_MONITOR
1465 chppWorkMonitorInit(&transportContext->workMonitor);
1466 #endif
1467
1468 transportContext->appContext = appContext;
1469 transportContext->initialized = true;
1470
1471 CHPP_NOT_NULL(linkApi);
1472 CHPP_DEBUG_NOT_NULL(linkApi->init);
1473 CHPP_DEBUG_NOT_NULL(linkApi->deinit);
1474 CHPP_DEBUG_NOT_NULL(linkApi->send);
1475 CHPP_DEBUG_NOT_NULL(linkApi->doWork);
1476 CHPP_DEBUG_NOT_NULL(linkApi->reset);
1477 CHPP_DEBUG_NOT_NULL(linkApi->getConfig);
1478 CHPP_DEBUG_NOT_NULL(linkApi->getTxBuffer);
1479 transportContext->linkApi = linkApi;
1480
1481 CHPP_NOT_NULL(linkContext);
1482 linkApi->init(linkContext, transportContext);
1483 transportContext->linkContext = linkContext;
1484
1485 #ifdef CHPP_DEBUG_ASSERT_ENABLED
1486 const struct ChppLinkConfiguration linkConfig =
1487 linkApi->getConfig(linkContext);
1488 CHPP_ASSERT_LOG(
1489 linkConfig.txBufferLen > CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES,
1490 "The link TX buffer is too small");
1491 CHPP_ASSERT_LOG(
1492 linkConfig.rxBufferLen > CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES,
1493 "The link RX buffer is too small");
1494 #endif // CHPP_DEBUG_ASSERT_ENABLED
1495 }
1496
chppTransportDeinit(struct ChppTransportState * transportContext)1497 void chppTransportDeinit(struct ChppTransportState *transportContext) {
1498 CHPP_NOT_NULL(transportContext);
1499 CHPP_ASSERT_LOG(transportContext->initialized,
1500 "CHPP transport already deinitialized");
1501
1502 transportContext->linkApi->deinit(transportContext->linkContext);
1503 #ifdef CHPP_ENABLE_WORK_MONITOR
1504 chppWorkMonitorDeinit(&transportContext->workMonitor);
1505 #endif
1506 chppConditionVariableDeinit(&transportContext->resetCondVar);
1507 chppNotifierDeinit(&transportContext->notifier);
1508 chppMutexDeinit(&transportContext->mutex);
1509
1510 chppClearTxDatagramQueue(transportContext);
1511
1512 CHPP_FREE_AND_NULLIFY(transportContext->rxDatagram.payload);
1513
1514 transportContext->initialized = false;
1515 }
1516
chppTransportWaitForResetComplete(struct ChppTransportState * transportContext,uint64_t timeoutMs)1517 bool chppTransportWaitForResetComplete(
1518 struct ChppTransportState *transportContext, uint64_t timeoutMs) {
1519 bool success = true;
1520 chppMutexLock(&transportContext->mutex);
1521 while (success && transportContext->resetState != CHPP_RESET_STATE_NONE) {
1522 success = chppConditionVariableTimedWait(&transportContext->resetCondVar,
1523 &transportContext->mutex,
1524 timeoutMs * CHPP_NSEC_PER_MSEC);
1525 }
1526 chppMutexUnlock(&transportContext->mutex);
1527 return success;
1528 }
1529
chppRxDataCb(struct ChppTransportState * context,const uint8_t * buf,size_t len)1530 bool chppRxDataCb(struct ChppTransportState *context, const uint8_t *buf,
1531 size_t len) {
1532 CHPP_NOT_NULL(buf);
1533 CHPP_NOT_NULL(context);
1534
1535 chppCheckRxPacketTimeout(context, chppGetCurrentTimeNs());
1536
1537 CHPP_LOGD("RX %" PRIuSIZE " bytes: state=%s", len,
1538 chppGetRxStatusLabel(context->rxStatus.state));
1539 uint64_t now = chppGetCurrentTimeNs();
1540 context->rxStatus.lastDataTimeMs = (uint32_t)(now / CHPP_NSEC_PER_MSEC);
1541 context->rxStatus.numTotalDataBytes += len;
1542
1543 size_t consumed = 0;
1544 while (consumed < len) {
1545 chppMutexLock(&context->mutex);
1546 // TODO: Investigate fine-grained locking, e.g. separating variables that
1547 // are only relevant to a particular path.
1548 // Also consider removing some of the finer-grained locks altogether for
1549 // non-multithreaded environments with clear documentation.
1550
1551 switch (context->rxStatus.state) {
1552 case CHPP_STATE_PREAMBLE:
1553 consumed +=
1554 chppConsumePreamble(context, &buf[consumed], len - consumed);
1555 break;
1556
1557 case CHPP_STATE_HEADER:
1558 consumed += chppConsumeHeader(context, &buf[consumed], len - consumed);
1559 break;
1560
1561 case CHPP_STATE_PAYLOAD:
1562 consumed += chppConsumePayload(context, &buf[consumed], len - consumed);
1563 break;
1564
1565 case CHPP_STATE_FOOTER:
1566 consumed += chppConsumeFooter(context, &buf[consumed], len - consumed);
1567 break;
1568
1569 default:
1570 CHPP_DEBUG_ASSERT_LOG(false, "Invalid RX state %" PRIu8,
1571 context->rxStatus.state);
1572 chppSetRxState(context, CHPP_STATE_PREAMBLE);
1573 }
1574
1575 chppMutexUnlock(&context->mutex);
1576 }
1577
1578 return (context->rxStatus.state == CHPP_STATE_PREAMBLE &&
1579 context->rxStatus.locInState == 0);
1580 }
1581
chppRxPacketCompleteCb(struct ChppTransportState * context)1582 void chppRxPacketCompleteCb(struct ChppTransportState *context) {
1583 chppMutexLock(&context->mutex);
1584 if (context->rxStatus.state != CHPP_STATE_PREAMBLE) {
1585 CHPP_LOGE("RX pkt ended early: state=%s seq=%" PRIu8 " len=%" PRIu16,
1586 chppGetRxStatusLabel(context->rxStatus.state),
1587 context->rxHeader.seq, context->rxHeader.length);
1588 chppAbortRxPacket(context);
1589 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_HEADER); // NACK
1590 }
1591 chppMutexUnlock(&context->mutex);
1592 }
1593
chppEnqueueTxDatagramOrFail(struct ChppTransportState * context,void * buf,size_t len)1594 bool chppEnqueueTxDatagramOrFail(struct ChppTransportState *context, void *buf,
1595 size_t len) {
1596 bool success = false;
1597
1598 chppMutexLock(&context->mutex);
1599 bool resetting = (context->resetState == CHPP_RESET_STATE_RESETTING);
1600
1601 if (len == 0) {
1602 CHPP_DEBUG_ASSERT_LOG(false, "Enqueue datagram len=0!");
1603
1604 } else if (resetting || !chppEnqueueTxDatagramLocked(
1605 context, CHPP_TRANSPORT_ERROR_NONE, buf, len)) {
1606 uint8_t *handle = buf;
1607 CHPP_LOGE("Resetting=%d. Discarding %" PRIuSIZE " bytes for H#%" PRIu8,
1608 resetting, len, *handle);
1609
1610 CHPP_FREE_AND_NULLIFY(buf);
1611
1612 } else {
1613 success = true;
1614 }
1615 chppMutexUnlock(&context->mutex);
1616
1617 return success;
1618 }
1619
1620 // TODO(b/192359485): Consider removing this function, or making it more robust.
chppEnqueueTxErrorDatagram(struct ChppTransportState * context,enum ChppTransportErrorCode errorCode)1621 void chppEnqueueTxErrorDatagram(struct ChppTransportState *context,
1622 enum ChppTransportErrorCode errorCode) {
1623 chppMutexLock(&context->mutex);
1624 bool resetting = (context->resetState == CHPP_RESET_STATE_RESETTING);
1625 if (resetting) {
1626 CHPP_LOGE("Discarding app error 0x%" PRIx8 " (resetting)", errorCode);
1627 } else {
1628 switch (errorCode) {
1629 case CHPP_TRANSPORT_ERROR_OOM: {
1630 CHPP_LOGD("App layer enqueueing CHPP_TRANSPORT_ERROR_OOM");
1631 break;
1632 }
1633 case CHPP_TRANSPORT_ERROR_APPLAYER: {
1634 CHPP_LOGD("App layer enqueueing CHPP_TRANSPORT_ERROR_APPLAYER");
1635 break;
1636 }
1637 default: {
1638 // App layer should not invoke any other errors
1639 CHPP_DEBUG_ASSERT_LOG(false, "App enqueueing invalid err=%" PRIu8,
1640 errorCode);
1641 }
1642 }
1643 chppEnqueueTxPacket(context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
1644 CHPP_TRANSPORT_ATTR_NONE, errorCode));
1645 }
1646 chppMutexUnlock(&context->mutex);
1647 }
1648
chppTransportForceReset(struct ChppTransportState * context)1649 void chppTransportForceReset(struct ChppTransportState *context) {
1650 CHPP_LOGW("Forcing transport reset");
1651 chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_FORCE_RESET);
1652 }
1653
chppTransportGetTimeUntilNextDoWorkNs(struct ChppTransportState * context)1654 uint64_t chppTransportGetTimeUntilNextDoWorkNs(
1655 struct ChppTransportState *context) {
1656 uint64_t currentTime = chppGetCurrentTimeNs();
1657 // This function is called in the context of the transport worker thread.
1658 // As we do not know if the transport is used in the context of a service
1659 // or a client, we use the min of both timeouts.
1660 uint64_t nextDoWorkTime = chppAppGetNextTimerTimeoutNs(context->appContext);
1661 nextDoWorkTime =
1662 MIN(nextDoWorkTime, context->appContext->nextClientRequestTimeoutNs);
1663 nextDoWorkTime =
1664 MIN(nextDoWorkTime, context->appContext->nextServiceRequestTimeoutNs);
1665
1666 if (chppHavePendingTxPayload(context) ||
1667 context->resetState == CHPP_RESET_STATE_RESETTING) {
1668 nextDoWorkTime =
1669 MIN(nextDoWorkTime, CHPP_TRANSPORT_TX_TIMEOUT_NS +
1670 ((context->txStatus.lastTxTimeNs == 0)
1671 ? currentTime
1672 : context->txStatus.lastTxTimeNs));
1673 }
1674
1675 if (context->rxStatus.state != CHPP_STATE_PREAMBLE) {
1676 nextDoWorkTime = MIN(nextDoWorkTime, context->rxStatus.packetStartTimeNs +
1677 CHPP_TRANSPORT_RX_TIMEOUT_NS);
1678 }
1679
1680 if (nextDoWorkTime == CHPP_TIME_MAX) {
1681 CHPP_LOGD("NextDoWork=n/a currentTime=%" PRIu64,
1682 currentTime / CHPP_NSEC_PER_MSEC);
1683 return CHPP_TRANSPORT_TIMEOUT_INFINITE;
1684 }
1685
1686 CHPP_LOGD("NextDoWork=%" PRIu64 " currentTime=%" PRIu64 " delta=%" PRId64,
1687 nextDoWorkTime / CHPP_NSEC_PER_MSEC,
1688 currentTime / CHPP_NSEC_PER_MSEC,
1689 (nextDoWorkTime > currentTime ? nextDoWorkTime - currentTime : 0) /
1690 (int64_t)CHPP_NSEC_PER_MSEC);
1691
1692 return nextDoWorkTime <= currentTime ? CHPP_TRANSPORT_TIMEOUT_IMMEDIATE
1693 : nextDoWorkTime - currentTime;
1694 }
1695
chppWorkThreadStart(struct ChppTransportState * context)1696 void chppWorkThreadStart(struct ChppTransportState *context) {
1697 chppMutexLock(&context->mutex);
1698 chppTransportSendResetLocked(context, CHPP_TRANSPORT_ATTR_RESET,
1699 CHPP_TRANSPORT_ERROR_NONE);
1700 chppMutexUnlock(&context->mutex);
1701 CHPP_LOGD("CHPP Work Thread started");
1702
1703 uint32_t signals;
1704 do {
1705 uint64_t timeout = chppTransportGetTimeUntilNextDoWorkNs(context);
1706 if (timeout == CHPP_TRANSPORT_TIMEOUT_IMMEDIATE) {
1707 signals = chppNotifierGetSignal(&context->notifier);
1708 } else if (timeout == CHPP_TRANSPORT_TIMEOUT_INFINITE) {
1709 signals = chppNotifierWait(&context->notifier);
1710 } else {
1711 signals = chppNotifierTimedWait(&context->notifier, timeout);
1712 }
1713
1714 } while (chppWorkThreadHandleSignal(context, signals));
1715 }
1716
chppWorkThreadHandleSignal(struct ChppTransportState * context,uint32_t signals)1717 bool chppWorkThreadHandleSignal(struct ChppTransportState *context,
1718 uint32_t signals) {
1719 bool continueProcessing = false;
1720
1721 #ifdef CHPP_ENABLE_WORK_MONITOR
1722 chppWorkMonitorPreProcess(&context->workMonitor);
1723 #endif
1724
1725 if (signals & CHPP_TRANSPORT_SIGNAL_EXIT) {
1726 CHPP_LOGD("CHPP Work Thread terminated");
1727 } else {
1728 continueProcessing = true;
1729 if (signals == 0) {
1730 // Triggered by timeout.
1731 chppWorkHandleTimeout(context);
1732 } else {
1733 if (signals & CHPP_TRANSPORT_SIGNAL_FORCE_RESET) {
1734 chppReset(context, CHPP_TRANSPORT_ATTR_RESET,
1735 CHPP_TRANSPORT_ERROR_FORCED_RESET);
1736 }
1737 if (signals & CHPP_TRANSPORT_SIGNAL_EVENT) {
1738 chppTransportDoWork(context, /*resendPayload=*/false);
1739 }
1740 if (signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK) {
1741 context->linkApi->doWork(context->linkContext,
1742 signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK);
1743 }
1744 }
1745 }
1746
1747 #ifdef CHPP_ENABLE_WORK_MONITOR
1748 chppWorkMonitorPostProcess(&context->workMonitor);
1749 #endif
1750
1751 return continueProcessing;
1752 }
1753
1754 /**
1755 * Handle timeouts in the worker thread.
1756 *
1757 * Timeouts occurs when either:
1758 * 1. There are packets to send and last packet send was more than
1759 * CHPP_TRANSPORT_TX_TIMEOUT_NS ago
1760 * 2. We haven't received a response to a request in time
1761 * 3. We haven't received the reset ACK
1762 *
1763 * For 1 and 2, chppTransportDoWork should be called to respectively
1764 * - Transmit the packet
1765 * - Send a timeout response
1766 */
chppWorkHandleTimeout(struct ChppTransportState * context)1767 static void chppWorkHandleTimeout(struct ChppTransportState *context) {
1768 const uint64_t currentTimeNs = chppGetCurrentTimeNs();
1769 const bool isTxTimeout = chppHavePendingTxPayload(context) &&
1770 (currentTimeNs - context->txStatus.lastTxTimeNs >=
1771 CHPP_TRANSPORT_TX_TIMEOUT_NS);
1772 const bool isResetting = context->resetState == CHPP_RESET_STATE_RESETTING;
1773
1774 // Call chppTransportDoWork for both TX and request timeouts.
1775 if (isTxTimeout) {
1776 CHPP_LOGE("ACK timeout. Tx t=%" PRIu64 ", attempt %zu, isResetting=%d",
1777 context->txStatus.lastTxTimeNs / CHPP_NSEC_PER_MSEC,
1778 context->txStatus.txAttempts, isResetting);
1779 chppTransportDoWork(context, /*resendPayload=*/true);
1780 } else {
1781 const uint64_t requestTimeoutNs =
1782 MIN(context->appContext->nextClientRequestTimeoutNs,
1783 context->appContext->nextServiceRequestTimeoutNs);
1784 const bool isRequestTimeout = requestTimeoutNs <= currentTimeNs;
1785 if (isRequestTimeout) {
1786 chppTransportDoWork(context, /*resendPayload=*/false);
1787 }
1788 }
1789
1790 if (isResetting && (currentTimeNs - context->resetTimeNs >=
1791 CHPP_TRANSPORT_RESET_TIMEOUT_NS)) {
1792 if (context->resetCount + 1 < CHPP_TRANSPORT_MAX_RESET) {
1793 CHPP_LOGE("RESET-ACK timeout; retrying");
1794 context->resetCount++;
1795 chppReset(context, CHPP_TRANSPORT_ATTR_RESET,
1796 CHPP_TRANSPORT_ERROR_TIMEOUT);
1797 } else {
1798 CHPP_LOGE("RESET-ACK timeout; giving up");
1799 context->txStatus.txAttempts = 0;
1800 context->resetState = CHPP_RESET_STATE_PERMANENT_FAILURE;
1801 chppClearTxDatagramQueue(context);
1802 context->txStatus.packetCodeToSend = 0;
1803 }
1804 }
1805
1806 chppAppProcessTimeout(context->appContext, currentTimeNs);
1807 chppCheckRxPacketTimeout(context, currentTimeNs);
1808 }
1809
chppCheckRxPacketTimeout(struct ChppTransportState * context,uint64_t now)1810 void chppCheckRxPacketTimeout(struct ChppTransportState *context,
1811 uint64_t now) {
1812 chppMutexLock(&context->mutex);
1813 if (context->rxStatus.state != CHPP_STATE_PREAMBLE &&
1814 now >
1815 context->rxStatus.packetStartTimeNs + CHPP_TRANSPORT_RX_TIMEOUT_NS) {
1816 CHPP_LOGE("Packet RX timeout");
1817 chppAbortRxPacket(context);
1818 chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_TIMEOUT); // NACK
1819 }
1820 chppMutexUnlock(&context->mutex);
1821 }
1822
chppWorkThreadStop(struct ChppTransportState * context)1823 void chppWorkThreadStop(struct ChppTransportState *context) {
1824 chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_EXIT);
1825 }
1826
chppLinkSendDoneCb(struct ChppTransportState * context,enum ChppLinkErrorCode error)1827 void chppLinkSendDoneCb(struct ChppTransportState *context,
1828 enum ChppLinkErrorCode error) {
1829 if (error != CHPP_LINK_ERROR_NONE_SENT) {
1830 CHPP_LOGE("Async send failure: %" PRIu8, error);
1831 }
1832
1833 chppMutexLock(&context->mutex);
1834
1835 context->txStatus.linkBusy = false;
1836
1837 // No need to free anything as link Tx buffer is static. Likewise, we
1838 // keep linkBufferSize to assist testing.
1839
1840 chppMutexUnlock(&context->mutex);
1841 }
1842
chppDatagramProcessDoneCb(struct ChppTransportState * context,uint8_t * buf)1843 void chppDatagramProcessDoneCb(struct ChppTransportState *context,
1844 uint8_t *buf) {
1845 UNUSED_VAR(context);
1846
1847 CHPP_FREE_AND_NULLIFY(buf);
1848 }
1849
chppRunTransportLoopback(struct ChppTransportState * context,uint8_t * buf,size_t len)1850 uint8_t chppRunTransportLoopback(struct ChppTransportState *context,
1851 uint8_t *buf, size_t len) {
1852 UNUSED_VAR(buf);
1853 UNUSED_VAR(len);
1854 uint8_t result = CHPP_APP_ERROR_UNSUPPORTED;
1855 context->loopbackResult = result;
1856
1857 #ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
1858 result = CHPP_APP_ERROR_NONE;
1859 context->loopbackResult = CHPP_APP_ERROR_UNSPECIFIED;
1860
1861 if (len == 0 || len > chppTransportTxMtuSize(context)) {
1862 result = CHPP_APP_ERROR_INVALID_LENGTH;
1863 context->loopbackResult = result;
1864
1865 } else if (context->txStatus.linkBusy) {
1866 result = CHPP_APP_ERROR_BLOCKED;
1867 context->loopbackResult = result;
1868
1869 } else if (context->transportLoopbackData.payload != NULL) {
1870 result = CHPP_APP_ERROR_BUSY;
1871 context->loopbackResult = result;
1872
1873 } else if ((context->transportLoopbackData.payload = chppMalloc(len)) ==
1874 NULL) {
1875 result = CHPP_APP_ERROR_OOM;
1876 context->loopbackResult = result;
1877
1878 } else {
1879 uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
1880 context->transportLoopbackData.length = len;
1881 memcpy(context->transportLoopbackData.payload, buf, len);
1882
1883 context->txStatus.linkBusy = true;
1884 context->linkBufferSize = 0;
1885 const struct ChppLinkConfiguration linkConfig =
1886 context->linkApi->getConfig(context->linkContext);
1887 memset(linkTxBuffer, 0, linkConfig.txBufferLen);
1888 context->linkBufferSize += chppAddPreamble(linkTxBuffer);
1889
1890 struct ChppTransportHeader *txHeader =
1891 (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
1892 context->linkBufferSize += sizeof(*txHeader);
1893
1894 txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
1895 CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST, txHeader->packetCode);
1896
1897 size_t payloadLen = MIN(len, chppTransportTxMtuSize(context));
1898 txHeader->length = (uint16_t)payloadLen;
1899 chppAppendToPendingTxPacket(context, buf, payloadLen);
1900
1901 chppAddFooter(context);
1902
1903 CHPP_LOGD("Sending transport-loopback request (packet len=%" PRIuSIZE
1904 ", payload len=%" PRIu16 ", asked len was %" PRIuSIZE ")",
1905 context->linkBufferSize, txHeader->length, len);
1906 enum ChppLinkErrorCode error = chppSendPendingPacket(context);
1907
1908 if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
1909 // Either sent synchronously or an error has occurred
1910 chppLinkSendDoneCb(context, error);
1911
1912 if (error != CHPP_LINK_ERROR_NONE_SENT) {
1913 // An error has occurred
1914 CHPP_FREE_AND_NULLIFY(context->transportLoopbackData.payload);
1915 context->transportLoopbackData.length = 0;
1916 result = CHPP_APP_ERROR_UNSPECIFIED;
1917 }
1918 }
1919 }
1920
1921 if (result != CHPP_APP_ERROR_NONE) {
1922 CHPP_LOGE("Trans-loopback failure: %" PRIu8, result);
1923 }
1924 #endif
1925 return result;
1926 }
1927
chppTransportSendResetLocked(struct ChppTransportState * context,enum ChppTransportPacketAttributes resetType,enum ChppTransportErrorCode error)1928 void chppTransportSendResetLocked(struct ChppTransportState *context,
1929 enum ChppTransportPacketAttributes resetType,
1930 enum ChppTransportErrorCode error) {
1931 // Make sure CHPP is in an initialized state
1932 CHPP_ASSERT_LOG((context->txDatagramQueue.pending == 0 &&
1933 context->txDatagramQueue.front == 0),
1934 "Not init to send reset");
1935
1936 struct ChppTransportConfiguration *config =
1937 chppMalloc(sizeof(struct ChppTransportConfiguration));
1938 if (config == NULL) {
1939 CHPP_LOG_OOM();
1940 } else {
1941 // CHPP transport version
1942 config->version.major = 1;
1943 config->version.minor = 0;
1944 config->version.patch = 0;
1945
1946 config->reserved1 = 0;
1947 config->reserved2 = 0;
1948 config->reserved3 = 0;
1949
1950 if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
1951 CHPP_LOGD("Sending RESET-ACK");
1952 chppSetResetComplete(context);
1953 } else {
1954 CHPP_LOGD("Sending RESET");
1955 }
1956
1957 context->resetTimeNs = chppGetCurrentTimeNs();
1958
1959 chppEnqueueTxDatagramLocked(
1960 context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(resetType, error), config,
1961 sizeof(*config));
1962 }
1963 }
1964
chppTransportTxMtuSize(const struct ChppTransportState * context)1965 size_t chppTransportTxMtuSize(const struct ChppTransportState *context) {
1966 const struct ChppLinkConfiguration linkConfig =
1967 context->linkApi->getConfig(context->linkContext);
1968
1969 return linkConfig.txBufferLen - CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
1970 }
1971
chppTransportRxMtuSize(const struct ChppTransportState * context)1972 size_t chppTransportRxMtuSize(const struct ChppTransportState *context) {
1973 const struct ChppLinkConfiguration linkConfig =
1974 context->linkApi->getConfig(context->linkContext);
1975
1976 return linkConfig.rxBufferLen - CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
1977 }
1978