You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1752 lines
63 KiB
1752 lines
63 KiB
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "chpp/transport.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "chpp/app.h"
|
|
#include "chpp/clients.h"
|
|
#include "chpp/clients/discovery.h"
|
|
#include "chpp/crc.h"
|
|
#include "chpp/link.h"
|
|
#include "chpp/log.h"
|
|
#include "chpp/macros.h"
|
|
#include "chpp/memory.h"
|
|
#include "chpp/platform/platform_link.h"
|
|
#include "chpp/time.h"
|
|
|
|
/************************************************
|
|
* Prototypes
|
|
***********************************************/
|
|
|
|
static void chppSetRxState(struct ChppTransportState *context,
|
|
enum ChppRxState newState);
|
|
static size_t chppConsumePreamble(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len);
|
|
static size_t chppConsumeHeader(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len);
|
|
static size_t chppConsumePayload(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len);
|
|
static size_t chppConsumeFooter(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len);
|
|
static void chppAbortRxPacket(struct ChppTransportState *context);
|
|
#ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
|
|
static void chppProcessTransportLoopbackRequest(
|
|
struct ChppTransportState *context);
|
|
#endif
|
|
#ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
|
|
static void chppProcessTransportLoopbackResponse(
|
|
struct ChppTransportState *context);
|
|
#endif
|
|
static void chppSetResetComplete(struct ChppTransportState *context);
|
|
static void chppProcessResetAck(struct ChppTransportState *context);
|
|
static void chppProcessRxPacket(struct ChppTransportState *context);
|
|
static void chppProcessRxPayload(struct ChppTransportState *context);
|
|
static void chppClearRxDatagram(struct ChppTransportState *context);
|
|
static bool chppRxChecksumIsOk(const struct ChppTransportState *context);
|
|
static enum ChppTransportErrorCode chppRxHeaderCheck(
|
|
const struct ChppTransportState *context);
|
|
static void chppRegisterRxAck(struct ChppTransportState *context);
|
|
|
|
static void chppEnqueueTxPacket(struct ChppTransportState *context,
|
|
uint8_t packetCode);
|
|
static size_t chppAddPreamble(uint8_t *buf);
|
|
static struct ChppTransportHeader *chppAddHeader(
|
|
struct ChppTransportState *context);
|
|
static void chppAddPayload(struct ChppTransportState *context);
|
|
static void chppAddFooter(struct PendingTxPacket *packet);
|
|
size_t chppDequeueTxDatagram(struct ChppTransportState *context);
|
|
static void chppClearTxDatagramQueue(struct ChppTransportState *context);
|
|
static void chppTransportDoWork(struct ChppTransportState *context);
|
|
static void chppAppendToPendingTxPacket(struct PendingTxPacket *packet,
|
|
const uint8_t *buf, size_t len);
|
|
static const char *chppGetPacketAttrStr(uint8_t packetCode);
|
|
static bool chppEnqueueTxDatagram(struct ChppTransportState *context,
|
|
uint8_t packetCode, void *buf, size_t len);
|
|
enum ChppLinkErrorCode chppSendPendingPacket(
|
|
struct ChppTransportState *context);
|
|
|
|
static void chppResetTransportContext(struct ChppTransportState *context);
|
|
static void chppReset(struct ChppTransportState *context,
|
|
enum ChppTransportPacketAttributes resetType,
|
|
enum ChppTransportErrorCode error);
|
|
#ifdef CHPP_CLIENT_ENABLED
|
|
struct ChppAppHeader *chppTransportGetClientRequestTimeoutResponse(
|
|
struct ChppTransportState *context);
|
|
#endif
|
|
|
|
/************************************************
|
|
* Private Functions
|
|
***********************************************/
|
|
|
|
/**
|
|
* Called any time the Rx state needs to be changed. Ensures that the location
|
|
* counter among that state (rxStatus.locInState) is also reset at the same
|
|
* time.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param newState Next Rx state.
|
|
*/
|
|
static void chppSetRxState(struct ChppTransportState *context,
|
|
enum ChppRxState newState) {
|
|
CHPP_LOGD("Changing RX transport state from %" PRIu8 " to %" PRIu8
|
|
" after %" PRIuSIZE " bytes",
|
|
context->rxStatus.state, newState, context->rxStatus.locInState);
|
|
context->rxStatus.locInState = 0;
|
|
context->rxStatus.state = newState;
|
|
}
|
|
|
|
/**
|
|
* Called by chppRxDataCb to find a preamble (i.e. packet start delimiter) in
|
|
* the incoming data stream.
|
|
* Moves the state to CHPP_STATE_HEADER as soon as it has seen a complete
|
|
* preamble.
|
|
* Any future backwards-incompatible versions of CHPP Transport will use a
|
|
* different preamble.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param buf Input data.
|
|
* @param len Length of input data in bytes.
|
|
*
|
|
* @return Length of consumed data in bytes.
|
|
*/
|
|
static size_t chppConsumePreamble(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len) {
|
|
size_t consumed = 0;
|
|
|
|
// TODO: Optimize loop, maybe using memchr() / memcmp() / SIMD, especially if
|
|
// serial port calling chppRxDataCb does not implement zero filter
|
|
while (consumed < len &&
|
|
context->rxStatus.locInState < CHPP_PREAMBLE_LEN_BYTES) {
|
|
size_t offset = context->rxStatus.locInState;
|
|
if ((offset == 0 && buf[consumed] == CHPP_PREAMBLE_BYTE_FIRST) ||
|
|
(offset == 1 && buf[consumed] == CHPP_PREAMBLE_BYTE_SECOND)) {
|
|
// Correct byte of preamble observed
|
|
context->rxStatus.locInState++;
|
|
|
|
} else if (buf[consumed] == CHPP_PREAMBLE_BYTE_FIRST) {
|
|
// Previous search failed but first byte of another preamble observed
|
|
context->rxStatus.locInState = 1;
|
|
|
|
} else {
|
|
// Continue search for a valid preamble from the start
|
|
context->rxStatus.locInState = 0;
|
|
}
|
|
|
|
consumed++;
|
|
}
|
|
|
|
// Let's see why we exited the above loop
|
|
if (context->rxStatus.locInState == CHPP_PREAMBLE_LEN_BYTES) {
|
|
// Complete preamble observed, move on to next state
|
|
context->rxStatus.packetStartTimeNs = chppGetCurrentTimeNs();
|
|
chppSetRxState(context, CHPP_STATE_HEADER);
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
/**
|
|
* Called by chppRxDataCb to process the packet header from the incoming data
|
|
* stream.
|
|
* Moves the Rx state to CHPP_STATE_PAYLOAD afterwards.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param buf Input data.
|
|
* @param len Length of input data in bytes.
|
|
*
|
|
* @return Length of consumed data in bytes.
|
|
*/
|
|
static size_t chppConsumeHeader(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len) {
|
|
CHPP_ASSERT(context->rxStatus.locInState <
|
|
sizeof(struct ChppTransportHeader));
|
|
size_t bytesToCopy = MIN(
|
|
len, (sizeof(struct ChppTransportHeader) - context->rxStatus.locInState));
|
|
memcpy(((uint8_t *)&context->rxHeader) + context->rxStatus.locInState, buf,
|
|
bytesToCopy);
|
|
context->rxStatus.locInState += bytesToCopy;
|
|
|
|
if (context->rxStatus.locInState == sizeof(struct ChppTransportHeader)) {
|
|
// Header fully copied. Move on
|
|
|
|
enum ChppTransportErrorCode headerCheckResult = chppRxHeaderCheck(context);
|
|
if (headerCheckResult != CHPP_TRANSPORT_ERROR_NONE) {
|
|
// Header fails consistency check. NACK and return to preamble state
|
|
chppEnqueueTxPacket(
|
|
context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(CHPP_TRANSPORT_ATTR_NONE,
|
|
headerCheckResult));
|
|
chppSetRxState(context, CHPP_STATE_PREAMBLE);
|
|
|
|
} else if (context->rxHeader.length == 0) {
|
|
// Non-payload packet
|
|
chppSetRxState(context, CHPP_STATE_FOOTER);
|
|
|
|
} else {
|
|
// Payload bearing packet
|
|
uint8_t *tempPayload;
|
|
|
|
if (context->rxDatagram.length == 0) {
|
|
// Packet is a new datagram
|
|
tempPayload = chppMalloc(context->rxHeader.length);
|
|
} else {
|
|
// Packet is a continuation of a fragmented datagram
|
|
tempPayload =
|
|
chppRealloc(context->rxDatagram.payload,
|
|
context->rxDatagram.length + context->rxHeader.length,
|
|
context->rxDatagram.length);
|
|
}
|
|
|
|
if (tempPayload == NULL) {
|
|
CHPP_LOG_OOM();
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_OOM);
|
|
chppSetRxState(context, CHPP_STATE_PREAMBLE);
|
|
} else {
|
|
context->rxDatagram.payload = tempPayload;
|
|
context->rxDatagram.length += context->rxHeader.length;
|
|
chppSetRxState(context, CHPP_STATE_PAYLOAD);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bytesToCopy;
|
|
}
|
|
|
|
/**
|
|
* Called by chppRxDataCb to copy the payload, the length of which is determined
|
|
* by the header, from the incoming data stream.
|
|
* Moves the Rx state to CHPP_STATE_FOOTER afterwards.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param buf Input data
|
|
* @param len Length of input data in bytes
|
|
*
|
|
* @return Length of consumed data in bytes
|
|
*/
|
|
static size_t chppConsumePayload(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len) {
|
|
CHPP_ASSERT(context->rxStatus.locInState < context->rxHeader.length);
|
|
size_t bytesToCopy =
|
|
MIN(len, (context->rxHeader.length - context->rxStatus.locInState));
|
|
memcpy(context->rxDatagram.payload + context->rxStatus.locInDatagram, buf,
|
|
bytesToCopy);
|
|
context->rxStatus.locInDatagram += bytesToCopy;
|
|
context->rxStatus.locInState += bytesToCopy;
|
|
|
|
if (context->rxStatus.locInState == context->rxHeader.length) {
|
|
// Entire packet payload copied. Move on
|
|
chppSetRxState(context, CHPP_STATE_FOOTER);
|
|
}
|
|
|
|
return bytesToCopy;
|
|
}
|
|
|
|
/**
|
|
* Called by chppRxDataCb to process the packet footer from the incoming data
|
|
* stream. Checks checksum, triggering the correct response (ACK / NACK).
|
|
* Moves the Rx state to CHPP_STATE_PREAMBLE afterwards.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param buf Input data.
|
|
* @param len Length of input data in bytes.
|
|
*
|
|
* @return Length of consumed data in bytes.
|
|
*/
|
|
static size_t chppConsumeFooter(struct ChppTransportState *context,
|
|
const uint8_t *buf, size_t len) {
|
|
CHPP_ASSERT(context->rxStatus.locInState <
|
|
sizeof(struct ChppTransportFooter));
|
|
size_t bytesToCopy = MIN(
|
|
len, (sizeof(struct ChppTransportFooter) - context->rxStatus.locInState));
|
|
memcpy(((uint8_t *)&context->rxFooter) + context->rxStatus.locInState, buf,
|
|
bytesToCopy);
|
|
|
|
context->rxStatus.locInState += bytesToCopy;
|
|
if (context->rxStatus.locInState == sizeof(struct ChppTransportFooter)) {
|
|
// Footer copied. Move on
|
|
|
|
if (CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode) !=
|
|
CHPP_TRANSPORT_ERROR_NONE) {
|
|
CHPP_LOGE("RX packet len=%" PRIu16 " seq=%" PRIu8 " ackSeq=%" PRIu8
|
|
" attr=0x%" PRIx8 " ERR=%" PRIu8 " flags=0x%" PRIx8,
|
|
context->rxHeader.length, context->rxHeader.seq,
|
|
context->rxHeader.ackSeq,
|
|
(uint8_t)CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode),
|
|
(uint8_t)CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode),
|
|
context->rxHeader.flags);
|
|
} else {
|
|
CHPP_LOGD("RX packet len=%" PRIu16 " seq=%" PRIu8 " ackSeq=%" PRIu8
|
|
" attr=0x%" PRIx8 " err=%" PRIu8 " flags=0x%" PRIx8,
|
|
context->rxHeader.length, context->rxHeader.seq,
|
|
context->rxHeader.ackSeq,
|
|
(uint8_t)CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode),
|
|
(uint8_t)CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode),
|
|
context->rxHeader.flags);
|
|
}
|
|
|
|
if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
|
|
CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST) {
|
|
#ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
|
|
chppProcessTransportLoopbackRequest(context);
|
|
#endif
|
|
|
|
} else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
|
|
CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE) {
|
|
#ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
|
|
chppProcessTransportLoopbackResponse(context);
|
|
#endif
|
|
|
|
} else if (!chppRxChecksumIsOk(context)) {
|
|
CHPP_LOGE("Bad checksum. seq=%" PRIu8 " len=%" PRIu16,
|
|
context->rxHeader.seq, context->rxHeader.length);
|
|
chppAbortRxPacket(context);
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_CHECKSUM); // NACK
|
|
|
|
} else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
|
|
CHPP_TRANSPORT_ATTR_RESET) {
|
|
CHPP_LOGI("RX RESET packet seq=%" PRIu8 " err=%" PRIu8,
|
|
context->rxHeader.seq,
|
|
CHPP_TRANSPORT_GET_ERROR(context->rxHeader.packetCode));
|
|
chppMutexUnlock(&context->mutex);
|
|
chppReset(context, CHPP_TRANSPORT_ATTR_RESET_ACK,
|
|
CHPP_TRANSPORT_ERROR_NONE);
|
|
chppMutexLock(&context->mutex);
|
|
|
|
} else if (context->resetState == CHPP_RESET_STATE_PERMANENT_FAILURE) {
|
|
// Only a reset is accepted in this state
|
|
CHPP_LOGE("RX discarded in perm fail. seq=%" PRIu8 " len=%" PRIu16,
|
|
context->rxHeader.seq, context->rxHeader.length);
|
|
chppAbortRxPacket(context);
|
|
|
|
} else if (CHPP_TRANSPORT_GET_ATTR(context->rxHeader.packetCode) ==
|
|
CHPP_TRANSPORT_ATTR_RESET_ACK) {
|
|
CHPP_LOGI("RX RESET-ACK packet. seq=%" PRIu8, context->rxHeader.seq);
|
|
chppProcessResetAck(context);
|
|
|
|
} else if (context->resetState == CHPP_RESET_STATE_RESETTING) {
|
|
CHPP_LOGE("RX discarded in reset. seq=%" PRIu8 " len=%" PRIu16,
|
|
context->rxHeader.seq, context->rxHeader.length);
|
|
chppAbortRxPacket(context);
|
|
|
|
} else {
|
|
chppProcessRxPacket(context);
|
|
}
|
|
|
|
// Done with this packet. Wait for next packet
|
|
chppSetRxState(context, CHPP_STATE_PREAMBLE);
|
|
}
|
|
|
|
return bytesToCopy;
|
|
}
|
|
|
|
/**
|
|
* Discards of an incomplete Rx packet during receive (e.g. due to a timeout or
|
|
* bad checksum).
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppAbortRxPacket(struct ChppTransportState *context) {
|
|
size_t undoLen = 0;
|
|
size_t undoLoc = 0;
|
|
|
|
switch (context->rxStatus.state) {
|
|
case (CHPP_STATE_PREAMBLE):
|
|
case (CHPP_STATE_HEADER): {
|
|
break;
|
|
}
|
|
|
|
case (CHPP_STATE_PAYLOAD): {
|
|
undoLen = context->rxHeader.length;
|
|
undoLoc = context->rxStatus.locInState;
|
|
break;
|
|
}
|
|
|
|
case (CHPP_STATE_FOOTER): {
|
|
undoLen = context->rxHeader.length;
|
|
undoLoc = context->rxHeader.length;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
CHPP_DEBUG_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
if (undoLen > 0) {
|
|
// Packet has a payload we need to discard of
|
|
|
|
CHPP_ASSERT(context->rxDatagram.length >= undoLen);
|
|
CHPP_ASSERT(context->rxStatus.locInDatagram >= undoLoc);
|
|
context->rxDatagram.length -= undoLen;
|
|
context->rxStatus.locInDatagram -= undoLoc;
|
|
|
|
if (context->rxDatagram.length == 0) {
|
|
// Discarding this packet == discarding entire datagram
|
|
CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
|
|
|
|
} else {
|
|
// Discarding this packet == discarding part of datagram
|
|
uint8_t *tempPayload =
|
|
chppRealloc(context->rxDatagram.payload, context->rxDatagram.length,
|
|
context->rxDatagram.length + undoLen);
|
|
|
|
if (tempPayload == NULL) {
|
|
CHPP_LOG_OOM();
|
|
} else {
|
|
context->rxDatagram.payload = tempPayload;
|
|
}
|
|
}
|
|
}
|
|
|
|
chppSetRxState(context, CHPP_STATE_PREAMBLE);
|
|
}
|
|
|
|
/**
|
|
* Processes a request that is determined to be for a transport-layer loopback.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
#ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
|
|
static void chppProcessTransportLoopbackRequest(
|
|
struct ChppTransportState *context) {
|
|
if (context->txStatus.linkBusy) {
|
|
CHPP_LOGE("Link busy; transport-loopback dropped");
|
|
|
|
} else {
|
|
context->txStatus.linkBusy = true;
|
|
context->pendingTxPacket.length = 0;
|
|
context->pendingTxPacket.length +=
|
|
chppAddPreamble(&context->pendingTxPacket.payload[0]);
|
|
|
|
struct ChppTransportHeader *txHeader =
|
|
(struct ChppTransportHeader *)&context->pendingTxPacket
|
|
.payload[context->pendingTxPacket.length];
|
|
context->pendingTxPacket.length += sizeof(*txHeader);
|
|
|
|
*txHeader = context->rxHeader;
|
|
txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
|
|
CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE, txHeader->packetCode);
|
|
|
|
size_t payloadLen =
|
|
MIN(context->rxDatagram.length, CHPP_TRANSPORT_TX_MTU_BYTES);
|
|
chppAppendToPendingTxPacket(&context->pendingTxPacket,
|
|
context->rxDatagram.payload, payloadLen);
|
|
CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
|
|
chppClearRxDatagram(context);
|
|
|
|
chppAddFooter(&context->pendingTxPacket);
|
|
|
|
CHPP_LOGI("Trans-looping back len=%" PRIu16 " RX len=%" PRIuSIZE,
|
|
txHeader->length, context->rxDatagram.length);
|
|
enum ChppLinkErrorCode error = chppSendPendingPacket(context);
|
|
|
|
if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
|
|
chppLinkSendDoneCb(&context->linkParams, error);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Processes a response that is determined to be for a transport-layer loopback.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
#ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
|
|
static void chppProcessTransportLoopbackResponse(
|
|
struct ChppTransportState *context) {
|
|
if (context->transportLoopbackData.length != context->rxDatagram.length) {
|
|
CHPP_LOGE("rx len=%" PRIuSIZE " != tx len=%" PRIuSIZE,
|
|
context->rxDatagram.length,
|
|
context->transportLoopbackData.length - CHPP_PREAMBLE_LEN_BYTES -
|
|
sizeof(struct ChppTransportHeader) -
|
|
sizeof(struct ChppTransportFooter));
|
|
context->loopbackResult = CHPP_APP_ERROR_INVALID_LENGTH;
|
|
|
|
} else if (memcmp(context->rxDatagram.payload,
|
|
context->transportLoopbackData.payload,
|
|
context->rxDatagram.length) != 0) {
|
|
CHPP_LOGE("rx & tx data don't match: len=%" PRIuSIZE,
|
|
context->rxDatagram.length);
|
|
context->loopbackResult = CHPP_APP_ERROR_INVALID_ARG;
|
|
|
|
} else {
|
|
context->loopbackResult = CHPP_APP_ERROR_NONE;
|
|
|
|
CHPP_LOGD("Rx successful transport-loopback (payload len=%" PRIuSIZE ")",
|
|
context->rxDatagram.length);
|
|
}
|
|
|
|
context->transportLoopbackData.length = 0;
|
|
CHPP_FREE_AND_NULLIFY(context->transportLoopbackData.payload);
|
|
CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
|
|
chppClearRxDatagram(context);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Method to invoke when the reset sequence is completed.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppSetResetComplete(struct ChppTransportState *context) {
|
|
context->resetState = CHPP_RESET_STATE_NONE;
|
|
context->resetCount = 0;
|
|
chppConditionVariableSignal(&context->resetCondVar);
|
|
}
|
|
|
|
/**
|
|
* An incoming reset-ack packet indicates that a reset is complete at the other
|
|
* end of the CHPP link.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppProcessResetAck(struct ChppTransportState *context) {
|
|
if (context->resetState == CHPP_RESET_STATE_NONE) {
|
|
CHPP_LOGE("Unexpected reset-ack seq=%" PRIu8 " code=0x%" PRIx8,
|
|
context->rxHeader.seq, context->rxHeader.packetCode);
|
|
// In a reset race condition with both endpoints sending resets and
|
|
// reset-acks, the sent resets and reset-acks will both have a sequence
|
|
// number of 0.
|
|
// By ignoring the received reset-ack, the next expected sequence number
|
|
// will remain at 1 (following a reset with a sequence number of 0).
|
|
// Therefore, no further correction is necessary (beyond ignoring the
|
|
// received reset-ack), as the next packet (e.g. discovery) will have a
|
|
// sequence number of 1.
|
|
|
|
chppDatagramProcessDoneCb(context, context->rxDatagram.payload);
|
|
chppClearRxDatagram(context);
|
|
|
|
return;
|
|
}
|
|
|
|
chppSetResetComplete(context);
|
|
context->rxStatus.receivedPacketCode = context->rxHeader.packetCode;
|
|
context->rxStatus.expectedSeq = context->rxHeader.seq + 1;
|
|
chppRegisterRxAck(context);
|
|
|
|
// TODO: Configure transport layer based on (optional?) received config
|
|
|
|
chppDatagramProcessDoneCb(context, context->rxDatagram.payload);
|
|
chppClearRxDatagram(context);
|
|
|
|
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
|
|
if (!context->appContext->isDiscoveryComplete) {
|
|
chppMutexUnlock(&context->mutex);
|
|
chppInitiateDiscovery(context->appContext);
|
|
chppMutexLock(&context->mutex);
|
|
} else {
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
|
|
}
|
|
#else
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
|
|
#endif
|
|
|
|
// Inform the App Layer that a reset has completed
|
|
chppMutexUnlock(&context->mutex);
|
|
chppAppProcessReset(context->appContext);
|
|
chppMutexLock(&context->mutex);
|
|
}
|
|
|
|
/**
|
|
* Process a received, checksum-validated packet.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppProcessRxPacket(struct ChppTransportState *context) {
|
|
uint64_t now = chppGetCurrentTimeNs();
|
|
context->rxStatus.lastGoodPacketTimeMs = (uint32_t)(now / CHPP_NSEC_PER_MSEC);
|
|
context->rxStatus.receivedPacketCode = context->rxHeader.packetCode;
|
|
chppRegisterRxAck(context);
|
|
|
|
enum ChppTransportErrorCode errorCode = CHPP_TRANSPORT_ERROR_NONE;
|
|
if (context->rxHeader.length > 0 &&
|
|
context->rxHeader.seq != context->rxStatus.expectedSeq) {
|
|
// Out of order payload
|
|
errorCode = CHPP_TRANSPORT_ERROR_ORDER;
|
|
}
|
|
|
|
if (context->txDatagramQueue.pending > 0 ||
|
|
errorCode == CHPP_TRANSPORT_ERROR_ORDER) {
|
|
// There are packets to send out (could be new or retx)
|
|
// Note: For a future ACK window > 1, makes more sense to cap the NACKs
|
|
// to one instead of flooding with out of order NACK errors.
|
|
chppEnqueueTxPacket(context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
|
|
CHPP_TRANSPORT_ATTR_NONE, errorCode));
|
|
}
|
|
|
|
if (errorCode == CHPP_TRANSPORT_ERROR_ORDER) {
|
|
CHPP_LOGE("Out of order RX discarded seq=%" PRIu8 " expect=%" PRIu8
|
|
" len=%" PRIu16,
|
|
context->rxHeader.seq, context->rxStatus.expectedSeq,
|
|
context->rxHeader.length);
|
|
chppAbortRxPacket(context);
|
|
|
|
} else if (context->rxHeader.length > 0) {
|
|
// Process payload and send ACK
|
|
chppProcessRxPayload(context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the payload of a validated payload-bearing packet and send out the
|
|
* ACK.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppProcessRxPayload(struct ChppTransportState *context) {
|
|
context->rxStatus.expectedSeq++; // chppProcessRxPacket() already confirms
|
|
// that context->rxStatus.expectedSeq ==
|
|
// context->rxHeader.seq, protecting against
|
|
// duplicate and out-of-order packets.
|
|
|
|
if (context->rxHeader.flags & CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM) {
|
|
// Packet is part of a larger datagram
|
|
CHPP_LOGD("RX packet for unfinished datagram. Seq=%" PRIu8 " len=%" PRIu16
|
|
". Datagram len=%" PRIuSIZE ". Sending ACK=%" PRIu8,
|
|
context->rxHeader.seq, context->rxHeader.length,
|
|
context->rxDatagram.length, context->rxStatus.expectedSeq);
|
|
|
|
} else {
|
|
// End of this packet is end of a datagram
|
|
|
|
// Send the payload to the App Layer
|
|
// Note that it is up to the app layer to free the buffer using
|
|
// chppDatagramProcessDoneCb() after is is done.
|
|
chppMutexUnlock(&context->mutex);
|
|
chppAppProcessRxDatagram(context->appContext, context->rxDatagram.payload,
|
|
context->rxDatagram.length);
|
|
chppMutexLock(&context->mutex);
|
|
|
|
CHPP_LOGD("App layer processed datagram with len=%" PRIuSIZE
|
|
", ending packet seq=%" PRIu8 ", len=%" PRIu16
|
|
". Sending ACK=%" PRIu8 " (previously sent=%" PRIu8 ")",
|
|
context->rxDatagram.length, context->rxHeader.seq,
|
|
context->rxHeader.length, context->rxStatus.expectedSeq,
|
|
context->txStatus.sentAckSeq);
|
|
chppClearRxDatagram(context);
|
|
}
|
|
|
|
// Send ACK because we had RX a payload-bearing packet
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
|
|
}
|
|
|
|
/**
|
|
* Resets the incoming datagram state, i.e. after the datagram has been
|
|
* processed.
|
|
* Note that this is independent from freeing the payload. It is up to the app
|
|
* layer to inform the transport layer using chppDatagramProcessDoneCb() once it
|
|
* is done with the buffer so it is freed.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppClearRxDatagram(struct ChppTransportState *context) {
|
|
context->rxStatus.locInDatagram = 0;
|
|
context->rxDatagram.length = 0;
|
|
context->rxDatagram.payload = NULL;
|
|
}
|
|
|
|
/**
|
|
* Validates the checksum of an incoming packet.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*
|
|
* @return True if and only if the checksum is correct.
|
|
*/
|
|
static bool chppRxChecksumIsOk(const struct ChppTransportState *context) {
|
|
uint32_t crc = chppCrc32(0, (const uint8_t *)&context->rxHeader,
|
|
sizeof(context->rxHeader));
|
|
crc = chppCrc32(
|
|
crc,
|
|
&context->rxDatagram
|
|
.payload[context->rxStatus.locInDatagram - context->rxHeader.length],
|
|
context->rxHeader.length);
|
|
|
|
#ifndef CHPP_CHECKSUM_ENABLED
|
|
CHPP_LOGD("Assuming Rx checksum 0x%" PRIx32 " = calculated 0x%" PRIx32,
|
|
context->rxFooter.checksum, crc);
|
|
crc = context->rxFooter.checksum;
|
|
#endif // CHPP_CHECKSUM_ENABLED
|
|
|
|
if (context->rxFooter.checksum != crc) {
|
|
CHPP_LOGE("Rx BAD checksum: footer=0x%" PRIx32 ", calc=0x%" PRIx32
|
|
", len=%" PRIuSIZE,
|
|
context->rxFooter.checksum, crc,
|
|
(size_t)(context->rxHeader.length +
|
|
sizeof(struct ChppTransportHeader)));
|
|
}
|
|
|
|
return (context->rxFooter.checksum == crc);
|
|
}
|
|
|
|
/**
|
|
* Performs consistency checks on received packet header to determine if it is
|
|
* obviously corrupt / invalid / duplicate / out-of-order.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*
|
|
* @return True if and only if header passes checks
|
|
*/
|
|
static enum ChppTransportErrorCode chppRxHeaderCheck(
|
|
const struct ChppTransportState *context) {
|
|
enum ChppTransportErrorCode result = CHPP_TRANSPORT_ERROR_NONE;
|
|
|
|
if (context->rxHeader.length > CHPP_TRANSPORT_RX_MTU_BYTES) {
|
|
result = CHPP_TRANSPORT_ERROR_HEADER;
|
|
}
|
|
|
|
if (result != CHPP_TRANSPORT_ERROR_NONE) {
|
|
CHPP_LOGE("Bad header. seq=%" PRIu8 " expect=%" PRIu8 " len=%" PRIu16
|
|
" err=%" PRIu8,
|
|
context->rxHeader.seq, context->rxStatus.expectedSeq,
|
|
context->rxHeader.length, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Registers a received ACK. If an outgoing datagram is fully ACKed, it is
|
|
* popped from the Tx queue.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppRegisterRxAck(struct ChppTransportState *context) {
|
|
uint8_t rxAckSeq = context->rxHeader.ackSeq;
|
|
|
|
if (context->rxStatus.receivedAckSeq != rxAckSeq) {
|
|
// A previously sent packet was actually ACKed
|
|
// Note: For a future ACK window >1, we should loop by # of ACKed packets
|
|
if ((uint8_t)(context->rxStatus.receivedAckSeq + 1) != rxAckSeq) {
|
|
CHPP_LOGE("Out of order ACK: last=%" PRIu8 " rx=%" PRIu8,
|
|
context->rxStatus.receivedAckSeq, rxAckSeq);
|
|
} else {
|
|
CHPP_LOGD(
|
|
"ACK received (last registered=%" PRIu8 ", received=%" PRIu8
|
|
"). Prior queue depth=%" PRIu8 ", front datagram=%" PRIu8
|
|
" at loc=%" PRIuSIZE " of len=%" PRIuSIZE,
|
|
context->rxStatus.receivedAckSeq, rxAckSeq,
|
|
context->txDatagramQueue.pending, context->txDatagramQueue.front,
|
|
context->txStatus.ackedLocInDatagram,
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front]
|
|
.length);
|
|
|
|
context->rxStatus.receivedAckSeq = rxAckSeq;
|
|
if (context->txStatus.txAttempts > 1) {
|
|
CHPP_LOGW("Seq %" PRIu8 " ACK'd after %" PRIuSIZE " reTX",
|
|
context->rxHeader.seq, context->txStatus.txAttempts - 1);
|
|
}
|
|
context->txStatus.txAttempts = 0;
|
|
|
|
// Process and if necessary pop from Tx datagram queue
|
|
context->txStatus.ackedLocInDatagram += CHPP_TRANSPORT_TX_MTU_BYTES;
|
|
if (context->txStatus.ackedLocInDatagram >=
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front]
|
|
.length) {
|
|
// We are done with datagram
|
|
|
|
context->txStatus.ackedLocInDatagram = 0;
|
|
context->txStatus.sentLocInDatagram = 0;
|
|
|
|
// Note: For a future ACK window >1, we need to update the queue
|
|
// position of the datagram being sent as well (relative to the
|
|
// front-of-queue). e.g. context->txStatus.datagramBeingSent--;
|
|
|
|
if (chppDequeueTxDatagram(context) == 0) {
|
|
context->txStatus.hasPacketsToSend = false;
|
|
}
|
|
}
|
|
}
|
|
} // else {nothing was ACKed}
|
|
}
|
|
|
|
/**
|
|
* Enqueues an outgoing packet with the specified error code. The error code
|
|
* refers to the optional reason behind a NACK, if any. An error code of
|
|
* CHPP_TRANSPORT_ERROR_NONE indicates that no error was reported (i.e. either
|
|
* an ACK or an implicit NACK)
|
|
*
|
|
* Note that the decision as to wheather to include a payload will be taken
|
|
* later, i.e. before the packet is being sent out from the queue. A payload is
|
|
* expected to be included if there is one or more pending Tx datagrams and we
|
|
* are not waiting on a pending ACK. A (repeat) payload is also included if we
|
|
* have received a NACK.
|
|
*
|
|
* Further note that even for systems with an ACK window greater than one, we
|
|
* would only need to send an ACK for the last (correct) packet, hence we only
|
|
* need a queue length of one here.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param packetCode Error code and packet attributes to be sent.
|
|
*/
|
|
static void chppEnqueueTxPacket(struct ChppTransportState *context,
|
|
uint8_t packetCode) {
|
|
context->txStatus.hasPacketsToSend = true;
|
|
context->txStatus.packetCodeToSend = packetCode;
|
|
|
|
CHPP_LOGD("chppEnqueueTxPacket called with packet code=0x%" PRIx8,
|
|
packetCode);
|
|
|
|
// Notifies the main CHPP Transport Layer to run chppTransportDoWork().
|
|
chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_EVENT);
|
|
}
|
|
|
|
/**
|
|
* Adds a CHPP preamble to the beginning of buf.
|
|
*
|
|
* @param buf The CHPP preamble will be added to buf.
|
|
*
|
|
* @return Size of the added preamble.
|
|
*/
|
|
static size_t chppAddPreamble(uint8_t *buf) {
|
|
buf[0] = CHPP_PREAMBLE_BYTE_FIRST;
|
|
buf[1] = CHPP_PREAMBLE_BYTE_SECOND;
|
|
return CHPP_PREAMBLE_LEN_BYTES;
|
|
}
|
|
|
|
/**
|
|
* Adds the packet header to pendingTxPacket.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*
|
|
* @return Pointer to the added packet header.
|
|
*/
|
|
static struct ChppTransportHeader *chppAddHeader(
|
|
struct ChppTransportState *context) {
|
|
struct ChppTransportHeader *txHeader =
|
|
(struct ChppTransportHeader *)&context->pendingTxPacket
|
|
.payload[context->pendingTxPacket.length];
|
|
context->pendingTxPacket.length += sizeof(*txHeader);
|
|
|
|
txHeader->packetCode = context->txStatus.packetCodeToSend;
|
|
context->txStatus.packetCodeToSend = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
|
|
context->txStatus.packetCodeToSend, CHPP_TRANSPORT_ERROR_NONE);
|
|
|
|
txHeader->ackSeq = context->rxStatus.expectedSeq;
|
|
context->txStatus.sentAckSeq = txHeader->ackSeq;
|
|
|
|
return txHeader;
|
|
}
|
|
|
|
/**
|
|
* Adds the packet payload to pendingTxPacket.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppAddPayload(struct ChppTransportState *context) {
|
|
struct ChppTransportHeader *txHeader =
|
|
(struct ChppTransportHeader *)&context->pendingTxPacket
|
|
.payload[CHPP_PREAMBLE_LEN_BYTES];
|
|
|
|
size_t remainingBytes =
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front].length -
|
|
context->txStatus.ackedLocInDatagram;
|
|
|
|
CHPP_LOGD("Adding payload to seq=%" PRIu8 ", remainingBytes=%" PRIuSIZE
|
|
" of pending datagrams=%" PRIu8,
|
|
txHeader->seq, remainingBytes, context->txDatagramQueue.pending);
|
|
|
|
if (remainingBytes > CHPP_TRANSPORT_TX_MTU_BYTES) {
|
|
// Send an unfinished part of a datagram
|
|
txHeader->flags = CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM;
|
|
txHeader->length = CHPP_TRANSPORT_TX_MTU_BYTES;
|
|
} else {
|
|
// Send final (or only) part of a datagram
|
|
txHeader->flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM;
|
|
txHeader->length = (uint16_t)remainingBytes;
|
|
}
|
|
|
|
// Copy payload
|
|
chppAppendToPendingTxPacket(
|
|
&context->pendingTxPacket,
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front]
|
|
.payload +
|
|
context->txStatus.ackedLocInDatagram,
|
|
txHeader->length);
|
|
|
|
context->txStatus.sentLocInDatagram =
|
|
context->txStatus.ackedLocInDatagram + txHeader->length;
|
|
}
|
|
|
|
/**
|
|
* Adds a footer (containing the checksum) to a packet.
|
|
*
|
|
* @param packet The packet from which to calculate the checksum and append the
|
|
* footer.
|
|
*/
|
|
static void chppAddFooter(struct PendingTxPacket *packet) {
|
|
struct ChppTransportFooter footer;
|
|
footer.checksum = chppCrc32(0, &packet->payload[CHPP_PREAMBLE_LEN_BYTES],
|
|
packet->length - CHPP_PREAMBLE_LEN_BYTES);
|
|
|
|
CHPP_LOGD("Adding transport footer. Checksum=0x%" PRIx32 ", len: %" PRIuSIZE
|
|
" -> %" PRIuSIZE,
|
|
footer.checksum, packet->length, packet->length + sizeof(footer));
|
|
|
|
chppAppendToPendingTxPacket(packet, (const uint8_t *)&footer, sizeof(footer));
|
|
}
|
|
|
|
/**
|
|
* Dequeues the datagram at the front of the datagram tx queue, if any, and
|
|
* frees the payload. Returns the number of remaining datagrams in the queue.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @return Number of remaining datagrams in queue.
|
|
*/
|
|
size_t chppDequeueTxDatagram(struct ChppTransportState *context) {
|
|
if (context->txDatagramQueue.pending == 0) {
|
|
CHPP_LOGE("Can not dequeue datagram because queue is empty");
|
|
|
|
} else {
|
|
CHPP_LOGD("Dequeuing front datagram with index=%" PRIu8 ", len=%" PRIuSIZE
|
|
". Queue depth: %" PRIu8 "->%d",
|
|
context->txDatagramQueue.front,
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front]
|
|
.length,
|
|
context->txDatagramQueue.pending,
|
|
context->txDatagramQueue.pending - 1);
|
|
|
|
CHPP_FREE_AND_NULLIFY(
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front]
|
|
.payload);
|
|
context->txDatagramQueue.datagram[context->txDatagramQueue.front].length =
|
|
0;
|
|
|
|
context->txDatagramQueue.pending--;
|
|
context->txDatagramQueue.front++;
|
|
context->txDatagramQueue.front %= CHPP_TX_DATAGRAM_QUEUE_LEN;
|
|
}
|
|
|
|
return context->txDatagramQueue.pending;
|
|
}
|
|
|
|
/**
|
|
* Flushes the Tx datagram queue of any pending packets.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppClearTxDatagramQueue(struct ChppTransportState *context) {
|
|
while (context->txDatagramQueue.pending > 0) {
|
|
chppDequeueTxDatagram(context);
|
|
}
|
|
context->txStatus.hasPacketsToSend = false;
|
|
}
|
|
|
|
/**
|
|
* Sends out a pending outgoing packet based on a notification from
|
|
* chppEnqueueTxPacket().
|
|
*
|
|
* A payload may or may not be included be according the following:
|
|
* No payload: If Tx datagram queue is empty OR we are waiting on a pending ACK.
|
|
* New payload: If there is one or more pending Tx datagrams and we are not
|
|
* waiting on a pending ACK.
|
|
* Repeat payload: If we haven't received an ACK yet for our previous payload,
|
|
* i.e. we have registered an explicit or implicit NACK.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppTransportDoWork(struct ChppTransportState *context) {
|
|
bool havePacketForLinkLayer = false;
|
|
struct ChppTransportHeader *txHeader;
|
|
struct ChppAppHeader *timeoutResponse = NULL;
|
|
|
|
// Note: For a future ACK window >1, there needs to be a loop outside the lock
|
|
chppMutexLock(&context->mutex);
|
|
|
|
if (context->txStatus.hasPacketsToSend && !context->txStatus.linkBusy) {
|
|
// There are pending outgoing packets and the link isn't busy
|
|
havePacketForLinkLayer = true;
|
|
context->txStatus.linkBusy = true;
|
|
|
|
context->pendingTxPacket.length = 0;
|
|
memset(&context->pendingTxPacket.payload, 0, CHPP_LINK_TX_MTU_BYTES);
|
|
|
|
// Add preamble
|
|
context->pendingTxPacket.length +=
|
|
chppAddPreamble(&context->pendingTxPacket.payload[0]);
|
|
|
|
// Add header
|
|
txHeader = chppAddHeader(context);
|
|
|
|
// If applicable, add payload
|
|
if ((context->txDatagramQueue.pending > 0)) {
|
|
// Note: For a future ACK window >1, we need to rewrite this payload
|
|
// adding code to base the next packet on the sent location within the
|
|
// last sent datagram, except for the case of a NACK (explicit or
|
|
// timeout). For a NACK, we would need to base the next packet off the
|
|
// last ACKed location.
|
|
|
|
txHeader->seq = context->rxStatus.receivedAckSeq;
|
|
context->txStatus.sentSeq = txHeader->seq;
|
|
|
|
if (context->txStatus.txAttempts > CHPP_TRANSPORT_MAX_RETX &&
|
|
context->resetState != CHPP_RESET_STATE_RESETTING) {
|
|
CHPP_LOGE("Resetting after %d retries", CHPP_TRANSPORT_MAX_RETX);
|
|
havePacketForLinkLayer = false;
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
chppReset(context, CHPP_TRANSPORT_ATTR_RESET,
|
|
CHPP_TRANSPORT_ERROR_MAX_RETRIES);
|
|
chppMutexLock(&context->mutex);
|
|
|
|
} else {
|
|
chppAddPayload(context);
|
|
context->txStatus.txAttempts++;
|
|
}
|
|
|
|
} else {
|
|
// No payload
|
|
context->txStatus.hasPacketsToSend = false;
|
|
}
|
|
|
|
chppAddFooter(&context->pendingTxPacket);
|
|
|
|
} else {
|
|
CHPP_LOGW(
|
|
"DoWork nothing to send. hasPackets=%d, linkBusy=%d, pending=%" PRIu8
|
|
", Rx ACK=%" PRIu8 ", Tx seq=%" PRIu8 ", RX state=%" PRIu8,
|
|
context->txStatus.hasPacketsToSend, context->txStatus.linkBusy,
|
|
context->txDatagramQueue.pending, context->rxStatus.receivedAckSeq,
|
|
context->txStatus.sentSeq, context->rxStatus.state);
|
|
}
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
|
|
if (havePacketForLinkLayer) {
|
|
CHPP_LOGD("TX->Link: len=%" PRIuSIZE " flags=0x%" PRIx8 " code=0x%" PRIx8
|
|
" ackSeq=%" PRIu8 " seq=%" PRIu8 " payloadLen=%" PRIu16
|
|
" pending=%" PRIu8,
|
|
context->pendingTxPacket.length, txHeader->flags,
|
|
txHeader->packetCode, txHeader->ackSeq, txHeader->seq,
|
|
txHeader->length, context->txDatagramQueue.pending);
|
|
enum ChppLinkErrorCode error = chppSendPendingPacket(context);
|
|
|
|
if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
|
|
// Platform implementation for platformLinkSend() is synchronous or an
|
|
// error occurred. In either case, we should call chppLinkSendDoneCb()
|
|
// here to release the contents of pendingTxPacket.
|
|
chppLinkSendDoneCb(&context->linkParams, error);
|
|
}
|
|
}
|
|
|
|
#ifdef CHPP_CLIENT_ENABLED
|
|
timeoutResponse = chppTransportGetClientRequestTimeoutResponse(context);
|
|
#endif
|
|
if (timeoutResponse != NULL) {
|
|
CHPP_LOGE("Response timeout H#%" PRIu8 " cmd=%" PRIu16 " ID=%" PRIu8,
|
|
timeoutResponse->handle, timeoutResponse->command,
|
|
timeoutResponse->transaction);
|
|
chppAppProcessRxDatagram(context->appContext, (uint8_t *)timeoutResponse,
|
|
sizeof(struct ChppAppHeader));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends data from a buffer of length len to a PendingTxPacket, updating its
|
|
* length.
|
|
*
|
|
* @param packet The PendingTxBuffer to be appended to.
|
|
* @param buf Input data to be copied from.
|
|
* @param len Length of input data in bytes.
|
|
*/
|
|
static void chppAppendToPendingTxPacket(struct PendingTxPacket *packet,
|
|
const uint8_t *buf, size_t len) {
|
|
CHPP_ASSERT(packet->length + len <= sizeof(packet->payload));
|
|
memcpy(&packet->payload[packet->length], buf, len);
|
|
packet->length += len;
|
|
}
|
|
|
|
/**
|
|
* @return A human readable form of the packet attribution.
|
|
*/
|
|
static const char *chppGetPacketAttrStr(uint8_t packetCode) {
|
|
switch (CHPP_TRANSPORT_GET_ATTR(packetCode)) {
|
|
case CHPP_TRANSPORT_ATTR_RESET:
|
|
return "(RESET)";
|
|
case CHPP_TRANSPORT_ATTR_RESET_ACK:
|
|
return "(RESET-ACK)";
|
|
case CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST:
|
|
return "(LOOP-REQ)";
|
|
case CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE:
|
|
return "(LOOP-RES)";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enqueues an outgoing datagram of a specified length. The payload must have
|
|
* been allocated by the caller using chppMalloc.
|
|
*
|
|
* If enqueueing is successful, the payload will be freed by this function
|
|
* once it has been sent out.
|
|
* If enqueueing is unsuccessful, it is up to the caller to decide when or if
|
|
* to free the payload and/or resend it later.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @param packetCode Error code and packet attributes to be sent.
|
|
* @param buf Datagram payload allocated through chppMalloc. Cannot be null.
|
|
* @param len Datagram length in bytes.
|
|
*
|
|
* @return True informs the sender that the datagram was successfully enqueued.
|
|
* False informs the sender that the queue was full.
|
|
*/
|
|
static bool chppEnqueueTxDatagram(struct ChppTransportState *context,
|
|
uint8_t packetCode, void *buf, size_t len) {
|
|
bool success = false;
|
|
|
|
if (len == 0) {
|
|
CHPP_LOGE("Enqueue tx with len 0");
|
|
CHPP_DEBUG_ASSERT(false);
|
|
|
|
} else {
|
|
if ((len < sizeof(struct ChppAppHeader)) ||
|
|
(CHPP_TRANSPORT_GET_ATTR(packetCode) != 0)) {
|
|
CHPP_LOGI("Enqueue TX: code=0x%" PRIx8 "%s len=%" PRIuSIZE
|
|
" pending=%" PRIu8,
|
|
packetCode, chppGetPacketAttrStr(packetCode), len,
|
|
(uint8_t)(context->txDatagramQueue.pending + 1));
|
|
} else {
|
|
struct ChppAppHeader *header = buf;
|
|
CHPP_LOGI(
|
|
"Enqueue TX: len=%" PRIuSIZE " H#%" PRIu8 " type=0x%" PRIx8
|
|
" ID=%" PRIu8 " err=%" PRIu8 " cmd=0x%" PRIx16 " pending=%" PRIu8,
|
|
len, header->handle, header->type, header->transaction, header->error,
|
|
header->command, (uint8_t)(context->txDatagramQueue.pending + 1));
|
|
}
|
|
|
|
chppMutexLock(&context->mutex);
|
|
|
|
if (context->txDatagramQueue.pending >= CHPP_TX_DATAGRAM_QUEUE_LEN) {
|
|
CHPP_LOGE("Cannot enqueue TX datagram");
|
|
|
|
} else {
|
|
uint16_t end =
|
|
(context->txDatagramQueue.front + context->txDatagramQueue.pending) %
|
|
CHPP_TX_DATAGRAM_QUEUE_LEN;
|
|
context->txDatagramQueue.datagram[end].length = len;
|
|
context->txDatagramQueue.datagram[end].payload = buf;
|
|
context->txDatagramQueue.pending++;
|
|
|
|
if (context->txDatagramQueue.pending == 1) {
|
|
// Queue was empty prior. Need to kickstart transmission.
|
|
chppEnqueueTxPacket(context, packetCode);
|
|
}
|
|
|
|
success = true;
|
|
}
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Sends the pending outgoing packet (context->pendingTxPacket) over to the link
|
|
* layer using chppPlatformLinkSend() and updates the last Tx packet time.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*
|
|
* @return Result of chppPlatformLinkSend().
|
|
*/
|
|
enum ChppLinkErrorCode chppSendPendingPacket(
|
|
struct ChppTransportState *context) {
|
|
enum ChppLinkErrorCode error = chppPlatformLinkSend(
|
|
&context->linkParams, context->pendingTxPacket.payload,
|
|
context->pendingTxPacket.length);
|
|
|
|
context->txStatus.lastTxTimeNs = chppGetCurrentTimeNs();
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Resets the transport state, maintaining the link layer parameters.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
*/
|
|
static void chppResetTransportContext(struct ChppTransportState *context) {
|
|
memset(&context->rxStatus, 0, sizeof(struct ChppRxStatus));
|
|
memset(&context->rxDatagram, 0, sizeof(struct ChppDatagram));
|
|
|
|
memset(&context->txStatus, 0, sizeof(struct ChppTxStatus));
|
|
memset(&context->txDatagramQueue, 0, sizeof(struct ChppTxDatagramQueue));
|
|
|
|
context->txStatus.sentSeq =
|
|
UINT8_MAX; // So that the seq # of the first TX packet is 0
|
|
context->resetState = CHPP_RESET_STATE_RESETTING;
|
|
}
|
|
|
|
/**
|
|
* Re-initializes the CHPP transport and app layer states, e.g. when receiving a
|
|
* reset packet, and sends out a reset or reset-ack packet over the link in
|
|
* order to reset the remote side or inform the counterpart of a reset,
|
|
* respectively.
|
|
*
|
|
* If the link layer is busy, this function will reset the link as well.
|
|
* This function retains and restores the platform-specific values of
|
|
* transportContext.linkParams.
|
|
*
|
|
* @param transportContext Maintains status for each transport layer instance.
|
|
* @param resetType Type of reset to send after resetting CHPP (reset vs.
|
|
* reset-ack), as defined in the ChppTransportPacketAttributes struct.
|
|
* @param error Provides the error that led to the reset.
|
|
*/
|
|
static void chppReset(struct ChppTransportState *transportContext,
|
|
enum ChppTransportPacketAttributes resetType,
|
|
enum ChppTransportErrorCode error) {
|
|
// TODO: Configure transport layer based on (optional?) received config before
|
|
// datagram is wiped
|
|
|
|
chppMutexLock(&transportContext->mutex);
|
|
struct ChppAppState *appContext = transportContext->appContext;
|
|
transportContext->resetState = CHPP_RESET_STATE_RESETTING;
|
|
|
|
// Reset asynchronous link layer if busy
|
|
if (transportContext->txStatus.linkBusy == true) {
|
|
// TODO: Give time for link layer to finish before resorting to a reset
|
|
|
|
chppPlatformLinkReset(&transportContext->linkParams);
|
|
}
|
|
|
|
// Free memory allocated for any ongoing rx datagrams
|
|
if (transportContext->rxDatagram.length > 0) {
|
|
transportContext->rxDatagram.length = 0;
|
|
CHPP_FREE_AND_NULLIFY(transportContext->rxDatagram.payload);
|
|
}
|
|
|
|
// Free memory allocated for any ongoing tx datagrams
|
|
for (size_t i = 0; i < CHPP_TX_DATAGRAM_QUEUE_LEN; i++) {
|
|
if (transportContext->txDatagramQueue.datagram[i].length > 0) {
|
|
CHPP_FREE_AND_NULLIFY(
|
|
transportContext->txDatagramQueue.datagram[i].payload);
|
|
}
|
|
}
|
|
|
|
// Reset Transport Layer but restore Rx sequence number and packet code
|
|
// (context->rxHeader is not wiped in reset)
|
|
chppResetTransportContext(transportContext);
|
|
transportContext->rxStatus.receivedPacketCode =
|
|
transportContext->rxHeader.packetCode;
|
|
transportContext->rxStatus.expectedSeq = transportContext->rxHeader.seq + 1;
|
|
|
|
// Send reset or reset-ACK
|
|
chppMutexUnlock(&transportContext->mutex);
|
|
chppTransportSendReset(transportContext, resetType, error);
|
|
|
|
// Inform the App Layer that a reset has completed
|
|
if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
|
|
chppAppProcessReset(appContext);
|
|
} // else reset is sent out. Rx of reset-ack will indicate completion.
|
|
}
|
|
|
|
/**
|
|
* Checks for a timed out client request and generates a timeout response if a
|
|
* client request timeout has occurred.
|
|
*
|
|
* @param context Maintains status for each transport layer instance.
|
|
* @return App layer response header if a timeout has occurred. Null otherwise.
|
|
*/
|
|
#ifdef CHPP_CLIENT_ENABLED
|
|
struct ChppAppHeader *chppTransportGetClientRequestTimeoutResponse(
|
|
struct ChppTransportState *context) {
|
|
struct ChppAppHeader *response = NULL;
|
|
|
|
bool timeoutClientFound = false;
|
|
uint8_t timedOutClient;
|
|
uint16_t timedOutCmd;
|
|
|
|
chppMutexLock(&context->mutex);
|
|
|
|
if (context->appContext->nextRequestTimeoutNs <= chppGetCurrentTimeNs()) {
|
|
// Determine which request has timed out
|
|
|
|
uint64_t lowestTimeout = CHPP_TIME_MAX;
|
|
for (uint8_t clientIdx = 0;
|
|
clientIdx < context->appContext->registeredClientCount; clientIdx++) {
|
|
for (uint16_t cmdIdx = 0;
|
|
cmdIdx <
|
|
context->appContext->registeredClients[clientIdx]->rRStateCount;
|
|
cmdIdx++) {
|
|
struct ChppRequestResponseState *rRState =
|
|
&context->appContext->registeredClientStates[clientIdx]
|
|
->rRStates[cmdIdx];
|
|
|
|
if (rRState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT &&
|
|
rRState->responseTimeNs != CHPP_TIME_NONE &&
|
|
rRState->responseTimeNs < lowestTimeout) {
|
|
lowestTimeout = rRState->responseTimeNs;
|
|
timedOutClient = clientIdx;
|
|
timedOutCmd = cmdIdx;
|
|
timeoutClientFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!timeoutClientFound) {
|
|
CHPP_LOGE("Timeout at %" PRIu64 " but no client",
|
|
context->appContext->nextRequestTimeoutNs / CHPP_NSEC_PER_MSEC);
|
|
chppClientRecalculateNextTimeout(context->appContext);
|
|
}
|
|
}
|
|
|
|
if (timeoutClientFound) {
|
|
CHPP_LOGE("Client=%" PRIu8 " cmd=%" PRIu16 " timed out", timedOutClient,
|
|
timedOutCmd);
|
|
response = chppMalloc(sizeof(struct ChppAppHeader));
|
|
if (response == NULL) {
|
|
CHPP_LOG_OOM();
|
|
} else {
|
|
response->handle = CHPP_SERVICE_HANDLE_OF_INDEX(timedOutClient);
|
|
response->type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE;
|
|
response->transaction =
|
|
context->appContext->registeredClientStates[timedOutClient]
|
|
->rRStates[timedOutCmd]
|
|
.transaction;
|
|
response->error = CHPP_APP_ERROR_TIMEOUT;
|
|
response->command = timedOutCmd;
|
|
}
|
|
}
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
|
|
return response;
|
|
}
|
|
#endif
|
|
|
|
/************************************************
|
|
* Public Functions
|
|
***********************************************/
|
|
|
|
void chppTransportInit(struct ChppTransportState *transportContext,
|
|
struct ChppAppState *appContext) {
|
|
CHPP_NOT_NULL(transportContext);
|
|
CHPP_NOT_NULL(appContext);
|
|
CHPP_ASSERT_LOG(!transportContext->initialized,
|
|
"CHPP transport already initialized");
|
|
|
|
CHPP_LOGD("Initializing CHPP transport");
|
|
|
|
chppResetTransportContext(transportContext);
|
|
chppMutexInit(&transportContext->mutex);
|
|
chppNotifierInit(&transportContext->notifier);
|
|
chppConditionVariableInit(&transportContext->resetCondVar);
|
|
|
|
transportContext->appContext = appContext;
|
|
transportContext->initialized = true;
|
|
|
|
chppPlatformLinkInit(&transportContext->linkParams);
|
|
}
|
|
|
|
void chppTransportDeinit(struct ChppTransportState *transportContext) {
|
|
CHPP_NOT_NULL(transportContext);
|
|
CHPP_ASSERT_LOG(transportContext->initialized,
|
|
"CHPP transport already deinitialized");
|
|
|
|
chppPlatformLinkDeinit(&transportContext->linkParams);
|
|
chppConditionVariableDeinit(&transportContext->resetCondVar);
|
|
chppNotifierDeinit(&transportContext->notifier);
|
|
chppMutexDeinit(&transportContext->mutex);
|
|
|
|
chppClearTxDatagramQueue(transportContext);
|
|
|
|
transportContext->initialized = false;
|
|
}
|
|
|
|
bool chppTransportWaitForResetComplete(
|
|
struct ChppTransportState *transportContext, uint64_t timeoutMs) {
|
|
bool success = true;
|
|
chppMutexLock(&transportContext->mutex);
|
|
while (success && transportContext->resetState != CHPP_RESET_STATE_NONE) {
|
|
success = chppConditionVariableTimedWait(&transportContext->resetCondVar,
|
|
&transportContext->mutex,
|
|
timeoutMs * CHPP_NSEC_PER_MSEC);
|
|
}
|
|
chppMutexUnlock(&transportContext->mutex);
|
|
return success;
|
|
}
|
|
|
|
bool chppRxDataCb(struct ChppTransportState *context, const uint8_t *buf,
|
|
size_t len) {
|
|
CHPP_NOT_NULL(buf);
|
|
CHPP_NOT_NULL(context);
|
|
|
|
chppMutexLock(&context->mutex);
|
|
if (context->rxStatus.state != CHPP_STATE_PREAMBLE &&
|
|
chppGetCurrentTimeNs() >
|
|
context->rxStatus.packetStartTimeNs + CHPP_TRANSPORT_RX_TIMEOUT_NS) {
|
|
CHPP_LOGE("Packet RX timeout");
|
|
chppAbortRxPacket(context);
|
|
}
|
|
chppMutexUnlock(&context->mutex);
|
|
|
|
CHPP_LOGD("RX %" PRIuSIZE " bytes: state=%" PRIu8, len,
|
|
context->rxStatus.state);
|
|
uint64_t now = chppGetCurrentTimeNs();
|
|
context->rxStatus.lastDataTimeMs = (uint32_t)(now / CHPP_NSEC_PER_MSEC);
|
|
context->rxStatus.numTotalDataBytes += len;
|
|
|
|
size_t consumed = 0;
|
|
while (consumed < len) {
|
|
chppMutexLock(&context->mutex);
|
|
// TODO: Investigate fine-grained locking, e.g. separating variables that
|
|
// are only relevant to a particular path.
|
|
// Also consider removing some of the finer-grained locks altogether for
|
|
// non-multithreaded environments with clear documentation.
|
|
|
|
switch (context->rxStatus.state) {
|
|
case CHPP_STATE_PREAMBLE:
|
|
consumed +=
|
|
chppConsumePreamble(context, &buf[consumed], len - consumed);
|
|
break;
|
|
|
|
case CHPP_STATE_HEADER:
|
|
consumed += chppConsumeHeader(context, &buf[consumed], len - consumed);
|
|
break;
|
|
|
|
case CHPP_STATE_PAYLOAD:
|
|
consumed += chppConsumePayload(context, &buf[consumed], len - consumed);
|
|
break;
|
|
|
|
case CHPP_STATE_FOOTER:
|
|
consumed += chppConsumeFooter(context, &buf[consumed], len - consumed);
|
|
break;
|
|
|
|
default:
|
|
CHPP_LOGE("Invalid RX state %" PRIu8, context->rxStatus.state);
|
|
CHPP_DEBUG_ASSERT(false);
|
|
chppSetRxState(context, CHPP_STATE_PREAMBLE);
|
|
}
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
}
|
|
|
|
return (context->rxStatus.state == CHPP_STATE_PREAMBLE &&
|
|
context->rxStatus.locInState == 0);
|
|
}
|
|
|
|
void chppRxPacketCompleteCb(struct ChppTransportState *context) {
|
|
chppMutexLock(&context->mutex);
|
|
if (context->rxStatus.state != CHPP_STATE_PREAMBLE) {
|
|
CHPP_LOGE("Rx pkt ended early: state=%" PRIu8 " packet=%" PRIu8
|
|
" len=%" PRIu16,
|
|
context->rxStatus.state, context->rxHeader.seq,
|
|
context->rxHeader.length);
|
|
chppAbortRxPacket(context);
|
|
chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_HEADER); // NACK
|
|
}
|
|
chppMutexUnlock(&context->mutex);
|
|
}
|
|
|
|
bool chppEnqueueTxDatagramOrFail(struct ChppTransportState *context, void *buf,
|
|
size_t len) {
|
|
bool success = false;
|
|
bool resetting = (context->resetState == CHPP_RESET_STATE_RESETTING);
|
|
|
|
if (len == 0) {
|
|
CHPP_LOGE("Enqueue datagram len 0");
|
|
CHPP_DEBUG_ASSERT(false);
|
|
|
|
} else if (resetting || !chppEnqueueTxDatagram(
|
|
context, CHPP_TRANSPORT_ERROR_NONE, buf, len)) {
|
|
uint8_t *handle = buf;
|
|
CHPP_LOGE("Resetting=%d. Discarding %" PRIuSIZE " bytes for H#%" PRIu8,
|
|
resetting, len, *handle);
|
|
|
|
CHPP_FREE_AND_NULLIFY(buf);
|
|
|
|
} else {
|
|
success = true;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// TODO(b/192359485): Consider removing this function, or making it more robust.
|
|
void chppEnqueueTxErrorDatagram(struct ChppTransportState *context,
|
|
enum ChppTransportErrorCode errorCode) {
|
|
bool resetting = (context->resetState == CHPP_RESET_STATE_RESETTING);
|
|
if (resetting) {
|
|
CHPP_LOGE("Discarding app error 0x%" PRIx8 " (resetting)", errorCode);
|
|
} else {
|
|
switch (errorCode) {
|
|
case CHPP_TRANSPORT_ERROR_OOM: {
|
|
CHPP_LOGD("App layer enqueueing CHPP_TRANSPORT_ERROR_OOM");
|
|
break;
|
|
}
|
|
case CHPP_TRANSPORT_ERROR_APPLAYER: {
|
|
CHPP_LOGD("App layer enqueueing CHPP_TRANSPORT_ERROR_APPLAYER");
|
|
break;
|
|
}
|
|
default: {
|
|
// App layer should not invoke any other errors
|
|
CHPP_LOGE("App enqueueing invalid err=%" PRIu8, errorCode);
|
|
CHPP_DEBUG_ASSERT(false);
|
|
}
|
|
}
|
|
chppEnqueueTxPacket(context, CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
|
|
CHPP_TRANSPORT_ATTR_NONE, errorCode));
|
|
}
|
|
}
|
|
|
|
uint64_t chppTransportGetTimeUntilNextDoWorkNs(
|
|
struct ChppTransportState *context) {
|
|
uint64_t currentTime = chppGetCurrentTimeNs();
|
|
uint64_t nextDoWorkTime = context->appContext->nextRequestTimeoutNs;
|
|
|
|
if (context->txStatus.hasPacketsToSend ||
|
|
context->resetState == CHPP_RESET_STATE_RESETTING) {
|
|
nextDoWorkTime =
|
|
MIN(nextDoWorkTime, CHPP_TRANSPORT_TX_TIMEOUT_NS +
|
|
((context->txStatus.lastTxTimeNs == 0)
|
|
? currentTime
|
|
: context->txStatus.lastTxTimeNs));
|
|
}
|
|
|
|
CHPP_LOGD("NextDoWork=%" PRIu64 " currentTime=%" PRIu64 " delta=%" PRId64,
|
|
nextDoWorkTime / CHPP_NSEC_PER_MSEC,
|
|
currentTime / CHPP_NSEC_PER_MSEC,
|
|
(nextDoWorkTime - currentTime) / (int64_t)CHPP_NSEC_PER_MSEC);
|
|
|
|
if (nextDoWorkTime == CHPP_TIME_MAX) {
|
|
return CHPP_TRANSPORT_TIMEOUT_INFINITE;
|
|
} else if (nextDoWorkTime <= currentTime) {
|
|
return CHPP_TRANSPORT_TIMEOUT_IMMEDIATE;
|
|
} else {
|
|
return nextDoWorkTime - currentTime;
|
|
}
|
|
}
|
|
|
|
void chppWorkThreadStart(struct ChppTransportState *context) {
|
|
chppTransportSendReset(context, CHPP_TRANSPORT_ATTR_RESET,
|
|
CHPP_TRANSPORT_ERROR_NONE);
|
|
CHPP_LOGD("CHPP Work Thread started");
|
|
|
|
uint32_t signals;
|
|
do {
|
|
uint64_t timeout = chppTransportGetTimeUntilNextDoWorkNs(context);
|
|
if (timeout == CHPP_TRANSPORT_TIMEOUT_IMMEDIATE) {
|
|
signals = chppNotifierGetSignal(&context->notifier);
|
|
} else if (timeout == CHPP_TRANSPORT_TIMEOUT_INFINITE) {
|
|
signals = chppNotifierWait(&context->notifier);
|
|
} else {
|
|
signals = chppNotifierTimedWait(&context->notifier, timeout);
|
|
}
|
|
|
|
} while (chppWorkThreadHandleSignal(context, signals));
|
|
}
|
|
|
|
bool chppWorkThreadHandleSignal(struct ChppTransportState *context,
|
|
uint32_t signals) {
|
|
if (signals & CHPP_TRANSPORT_SIGNAL_EXIT) {
|
|
CHPP_LOGD("CHPP Work Thread terminated");
|
|
return false;
|
|
}
|
|
|
|
if (signals & CHPP_TRANSPORT_SIGNAL_EVENT) {
|
|
chppTransportDoWork(context);
|
|
}
|
|
|
|
if (signals == 0) {
|
|
// Triggered by timeout
|
|
|
|
if (chppGetCurrentTimeNs() - context->txStatus.lastTxTimeNs >=
|
|
CHPP_TRANSPORT_TX_TIMEOUT_NS) {
|
|
CHPP_LOGE("ACK timeout. Tx t=%" PRIu64,
|
|
context->txStatus.lastTxTimeNs / CHPP_NSEC_PER_MSEC);
|
|
chppTransportDoWork(context);
|
|
}
|
|
|
|
if ((context->resetState == CHPP_RESET_STATE_RESETTING) &&
|
|
(chppGetCurrentTimeNs() - context->resetTimeNs >=
|
|
CHPP_TRANSPORT_RESET_TIMEOUT_NS)) {
|
|
if (context->resetCount + 1 < CHPP_TRANSPORT_MAX_RESET) {
|
|
CHPP_LOGE("RESET-ACK timeout; retrying");
|
|
context->resetCount++;
|
|
chppReset(context, CHPP_TRANSPORT_ATTR_RESET,
|
|
CHPP_TRANSPORT_ERROR_TIMEOUT);
|
|
} else {
|
|
CHPP_LOGE("RESET-ACK timeout; giving up");
|
|
context->resetState = CHPP_RESET_STATE_PERMANENT_FAILURE;
|
|
chppClearTxDatagramQueue(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK) {
|
|
chppPlatformLinkDoWork(&context->linkParams,
|
|
signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void chppWorkThreadStop(struct ChppTransportState *context) {
|
|
chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_EXIT);
|
|
}
|
|
|
|
void chppLinkSendDoneCb(struct ChppPlatformLinkParameters *params,
|
|
enum ChppLinkErrorCode error) {
|
|
if (error != CHPP_LINK_ERROR_NONE_SENT) {
|
|
CHPP_LOGE("Async send failure: %" PRIu8, error);
|
|
}
|
|
|
|
struct ChppTransportState *context =
|
|
container_of(params, struct ChppTransportState, linkParams);
|
|
|
|
chppMutexLock(&context->mutex);
|
|
|
|
context->txStatus.linkBusy = false;
|
|
|
|
// No need to free anything as pendingTxPacket.payload is static. Likewise, we
|
|
// keep pendingTxPacket.length to assist testing.
|
|
|
|
chppMutexUnlock(&context->mutex);
|
|
}
|
|
|
|
void chppDatagramProcessDoneCb(struct ChppTransportState *context,
|
|
uint8_t *buf) {
|
|
UNUSED_VAR(context);
|
|
|
|
CHPP_FREE_AND_NULLIFY(buf);
|
|
}
|
|
|
|
uint8_t chppRunTransportLoopback(struct ChppTransportState *context,
|
|
uint8_t *buf, size_t len) {
|
|
UNUSED_VAR(buf);
|
|
UNUSED_VAR(len);
|
|
uint8_t result = CHPP_APP_ERROR_UNSUPPORTED;
|
|
context->loopbackResult = result;
|
|
|
|
#ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
|
|
result = CHPP_APP_ERROR_NONE;
|
|
context->loopbackResult = CHPP_APP_ERROR_UNSPECIFIED;
|
|
|
|
if (len == 0 || len > CHPP_TRANSPORT_TX_MTU_BYTES) {
|
|
result = CHPP_APP_ERROR_INVALID_LENGTH;
|
|
context->loopbackResult = result;
|
|
|
|
} else if (context->txStatus.linkBusy) {
|
|
result = CHPP_APP_ERROR_BLOCKED;
|
|
context->loopbackResult = result;
|
|
|
|
} else if (context->transportLoopbackData.payload != NULL) {
|
|
result = CHPP_APP_ERROR_BUSY;
|
|
context->loopbackResult = result;
|
|
|
|
} else if ((context->transportLoopbackData.payload = chppMalloc(len)) ==
|
|
NULL) {
|
|
result = CHPP_APP_ERROR_OOM;
|
|
context->loopbackResult = result;
|
|
|
|
} else {
|
|
context->transportLoopbackData.length = len;
|
|
memcpy(context->transportLoopbackData.payload, buf, len);
|
|
|
|
context->txStatus.linkBusy = true;
|
|
context->pendingTxPacket.length = 0;
|
|
memset(&context->pendingTxPacket.payload, 0, CHPP_LINK_TX_MTU_BYTES);
|
|
context->pendingTxPacket.length +=
|
|
chppAddPreamble(&context->pendingTxPacket.payload[0]);
|
|
|
|
struct ChppTransportHeader *txHeader =
|
|
(struct ChppTransportHeader *)&context->pendingTxPacket
|
|
.payload[context->pendingTxPacket.length];
|
|
context->pendingTxPacket.length += sizeof(*txHeader);
|
|
|
|
txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
|
|
CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST, txHeader->packetCode);
|
|
|
|
size_t payloadLen = MIN(len, CHPP_TRANSPORT_TX_MTU_BYTES);
|
|
txHeader->length = (uint16_t)payloadLen;
|
|
chppAppendToPendingTxPacket(&context->pendingTxPacket, buf, payloadLen);
|
|
|
|
chppAddFooter(&context->pendingTxPacket);
|
|
|
|
CHPP_LOGD("Sending transport-loopback request (packet len=%" PRIuSIZE
|
|
", payload len=%" PRIu16 ", asked len was %" PRIuSIZE ")",
|
|
context->pendingTxPacket.length, txHeader->length, len);
|
|
enum ChppLinkErrorCode error = chppSendPendingPacket(context);
|
|
|
|
if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
|
|
// Either sent synchronously or an error has occurred
|
|
chppLinkSendDoneCb(&context->linkParams, error);
|
|
|
|
if (error != CHPP_LINK_ERROR_NONE_SENT) {
|
|
// An error has occurred
|
|
CHPP_FREE_AND_NULLIFY(context->transportLoopbackData.payload);
|
|
context->transportLoopbackData.length = 0;
|
|
result = CHPP_APP_ERROR_UNSPECIFIED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result != CHPP_APP_ERROR_NONE) {
|
|
CHPP_LOGE("Transport-loopback failure: %" PRIu8, result);
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void chppTransportSendReset(struct ChppTransportState *context,
|
|
enum ChppTransportPacketAttributes resetType,
|
|
enum ChppTransportErrorCode error) {
|
|
// Make sure CHPP is in an initialized state
|
|
CHPP_ASSERT_LOG((context->txDatagramQueue.pending == 0 &&
|
|
context->txDatagramQueue.front == 0),
|
|
"Not init to send reset");
|
|
|
|
struct ChppTransportConfiguration *config =
|
|
chppMalloc(sizeof(struct ChppTransportConfiguration));
|
|
|
|
// CHPP transport version
|
|
config->version.major = 1;
|
|
config->version.minor = 0;
|
|
config->version.patch = 0;
|
|
|
|
// Rx MTU size
|
|
config->rxMtu = CHPP_PLATFORM_LINK_RX_MTU_BYTES;
|
|
|
|
// Max Rx window size
|
|
// Note: current implementation does not support a window size >1
|
|
config->windowSize = 1;
|
|
|
|
// Advertised transport layer (ACK) timeout
|
|
config->timeoutInMs = CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS;
|
|
|
|
if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
|
|
CHPP_LOGD("Sending RESET-ACK");
|
|
} else {
|
|
CHPP_LOGD("Sending RESET");
|
|
}
|
|
|
|
if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
|
|
chppSetResetComplete(context);
|
|
}
|
|
|
|
context->resetTimeNs = chppGetCurrentTimeNs();
|
|
|
|
chppEnqueueTxDatagram(context,
|
|
CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(resetType, error),
|
|
config, sizeof(*config));
|
|
}
|