/* * Copyright (c) 2019 - 2024 Stefan Strobel * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * @file shellmatta_transport.c * @brief transport layer functions of shellmatta * @author Simon Fischer */ #ifdef SHELLMATTA_TRANSPORT #include "shellmatta_transport.h" #include "shellmatta.h" #include "shellmatta_crc.h" #include "shellmatta_utils.h" #include /** * @brief create a secured packet and pass it to the shellmatta write function * * @note The packetType, payloadLength and payload have to be preset! * * @param[in, out] transportLayer transport layer instance to work on * @param[in] packetType type of packet to send * @param[in] packet packet to send * @return errorcode */ static shellmatta_retCode_t shellmatta_transport_send(shellmatta_transport_layer_t *transportLayer, shellmatta_transport_packet_t *packet) { uint32_t crc; char *rawPacket = (char*)packet; shellmatta_transport_header_t *header = &packet->header; /** -# fill header */ header->startOfHeader = SHELLMATTA_TRANSPORT_START_OF_HEADER; header->protocolVersion = SHELLMATTA_TRANSPORT_PROTOCOL_VERSION; header->source = 0; header->destination = 0; header->sequenceH2S = transportLayer->sequenceH2S; header->sequenceS2H = ++transportLayer->sequenceS2H; crc = crc32Calc((char*) packet, SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength); /** -# append crc to end of payload */ rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength] = (uint8_t)(crc >> 24u); rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 1u] = (uint8_t)(crc >> 16u); rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 2u] = (uint8_t)(crc >> 8u); rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 3u] = (uint8_t)(crc); /** -# use original write function to send full buffer */ return transportLayer->writeFct((char*) rawPacket, SHELLMATTA_TRANSPORT_LENGTH_STATIC + header->payloadLength); } /** * @brief Initializes the transportLayerInst * @param[in, out] transportLayer transport layer instance to work on * @param[in] writeFct function pointer to output function * @return errorcode #SHELLMATTA_OK */ shellmatta_retCode_t shellmatta_transport_init( shellmatta_transport_layer_t *transportLayer, shellmatta_write_t writeFct) { /** -# clear instance and store write function */ memset(transportLayer, 0u, sizeof(shellmatta_transport_layer_t)); transportLayer->writeFct = writeFct; return SHELLMATTA_OK; } /** * @brief Configures the transport layer * @param[in, out] handle shellmatta handle of the instance * @param[in] mandatory enforce using the transport layer * @param[in] disableAutoFlush enforce manual flushing of the output packet * @param[in] customCrcFct use a custom crc generation (default NULL) * @return errorcode */ shellmatta_retCode_t shellmatta_transport_configure(shellmatta_handle_t handle, bool mandatory, bool disableAutoFlush, shellmatta_transport_crc_t customCrcFct) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { shellmatta_transport_layer_t *transportLayer = &inst->transportLayer; transportLayer->mandatory = mandatory; transportLayer->disableAutoFlush = disableAutoFlush; transportLayer->customCrcFct = customCrcFct; /** -# set the transport layer active when configured as mandatory */ if(true == mandatory) { transportLayer->active = true; } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief Resets all values of tranportLayerInst except for sequence counters * @param[in, out] handle shellmatta handle of the instance * @return errorcode */ shellmatta_retCode_t shellmatta_transport_reset(shellmatta_handle_t handle) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { /** -# set transport layer back to initial state */ shellmatta_transport_layer_t *transportLayer = &inst->transportLayer; transportLayer->state = SHELLMATTA_TRANSPORT_STATE_WAIT; transportLayer->active = transportLayer->mandatory; transportLayer->headerIndex = 0u; transportLayer->payloadIndex = 0u; transportLayer->crcIndex = 0u; memset(&transportLayer->inPacket, 0, sizeof(transportLayer->inPacket)); memset(&transportLayer->outPacket, 0, sizeof(transportLayer->outPacket)); } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief processes the passed amount of data * @param[in, out] transportLayer transport layer instance to work on * @param[in] byte byte to process * @param[out] data payload of processed packet - or manual mode * @param[out] length length of processed packet * @return errorcode #SHELLMATTA_OK data is returned\n * #SHELLMATTA_BUSY packet reception ongoing\n * #SHELLMATTA_ERROR in case of crc error */ shellmatta_retCode_t shellmatta_transport_process(shellmatta_transport_layer_t *transportLayer, char byte, char **data, uint32_t *length) { shellmatta_retCode_t ret = SHELLMATTA_BUSY; char *rawPacket = (char*)&transportLayer->inPacket; shellmatta_transport_header_t *header = &transportLayer->inPacket.header; shellmatta_transport_packet_int_t intPacket; uint32_t refCrc; switch (transportLayer->state) { /** -# look for start of header */ case SHELLMATTA_TRANSPORT_STATE_WAIT: /** -# if start of header is found, continue transport layer fsm */ if (SHELLMATTA_TRANSPORT_START_OF_HEADER == byte) { memset(&transportLayer->inPacket, 0, sizeof(transportLayer->inPacket)); transportLayer->headerIndex = 1u; transportLayer->payloadIndex = 0u; transportLayer->crcIndex = 0u; header->startOfHeader = byte; transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_HEADER; } else if((true == transportLayer->mandatory)) { /** -# if transport layer is mandatory - throw away unrecognized bytes */ *data = transportLayer->inPacket.payload; *length = 0; transportLayer->active = true; ret = SHELLMATTA_OK; } else { /** -# otherwise just pass the payload as is */ transportLayer->inPacket.payload[0] = byte; *data = transportLayer->inPacket.payload; *length = 1; transportLayer->active = false; ret = SHELLMATTA_OK; } break; case SHELLMATTA_TRANSPORT_STATE_GET_HEADER: rawPacket[transportLayer->headerIndex] = byte; transportLayer->headerIndex ++; if (transportLayer->headerIndex == SHELLMATTA_TRANSPORT_LENGTH_HEADER) { //TODO check for errors in header data if (0u != header->payloadLength) { transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD; } else { transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_CRC; } } break; /** -# read payload for previously read length of payload */ case SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD: transportLayer->inPacket.payload[transportLayer->payloadIndex] = byte; transportLayer->payloadIndex ++; if (transportLayer->payloadIndex >= header->payloadLength) { transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_CRC; } break; /** -# read crc32 for four bytes */ case SHELLMATTA_TRANSPORT_STATE_GET_CRC: transportLayer->crcIndex ++; transportLayer->inPacket.crc |= ((uint8_t)byte << ((SHELLMATTA_TRANSPORT_LENGTH_CRC - transportLayer->crcIndex) * 8u)); if (transportLayer->crcIndex >= SHELLMATTA_TRANSPORT_LENGTH_CRC) { refCrc = SHELLMATTA_TRANSPORT_CALC_CRC(transportLayer, rawPacket, SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength); /** -# if crc is correct, further handling of data depends on type of packet */ if (transportLayer->inPacket.crc == refCrc) { transportLayer->active = true; /* crc is correct */ transportLayer->sequenceH2S++; switch (header->packetType) { case SHELLMATTA_TRANSPORT_PACKET_DATA: /** -# return pointer to payload and length */ *data = (char*)&transportLayer->inPacket.payload; *length = header->payloadLength; ret = SHELLMATTA_OK; break; case SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_REQUEST: /** -# send out packet with no payload */ intPacket.header.packetType = SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND; intPacket.header.payloadLength = 0u; (void)shellmatta_transport_send(transportLayer, (shellmatta_transport_packet_t *)&intPacket); break; case SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND: /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND - nothing to do */ break; case SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_REQUEST: /** @todo implement */ break; case SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND: /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND - nothing to do */ break; case SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_REQUEST: /** @todo implement */ break; case SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND: /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND - nothing to do */ break; case SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_REQUEST: /** @todo implement */ break; case SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND: /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND - nothing to do */ break; default: /** -# undo sequence counter increment on wrong packet type */ transportLayer->sequenceH2S--; break; } } else { /** -# return error if crc is incorrect */ ret = SHELLMATTA_ERROR; } /** -# reset state machine */ transportLayer->state = SHELLMATTA_TRANSPORT_STATE_WAIT; } break; default: break; } return ret; } /** * @brief Wrapper function for the write-function of shellmatta handle * * This function is used to transmit data with the tranport layer protocol.\n * The input data is copied into a new array along with the header and crc32.\n * The resulting buffer is the forwarded to the original write function.\n * * @note If length of data exceeds #UINT8_MAX, data is sent in multiple packets.\n * * @param[in, out] transportLayer transport layer instance to work on * @param[in] data pointer to input data to process * @param[in] length length of data to process * @return errorcode #SHELLMATTA_OK */ shellmatta_retCode_t shellmatta_transport_write(shellmatta_transport_layer_t *transportLayer, const char* data, uint32_t length) { shellmatta_retCode_t ret = SHELLMATTA_OK; uint32_t outPayloadLength = length; shellmatta_transport_packet_t *packet = &transportLayer->outPacket; shellmatta_transport_header_t *header = &transportLayer->outPacket.header; /** -# handle data with length bigger than max length */ uint32_t processedLength = 0u; uint32_t piledUpPayload; uint32_t splitLength; uint32_t restOfPayload; /** -# foot-controlled loop to send packets without payload length */ do { piledUpPayload = header->payloadLength; /* compute length of next payload split */ restOfPayload = (outPayloadLength - processedLength); if (restOfPayload > (SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH - piledUpPayload)) { splitLength = SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH - piledUpPayload; } else { splitLength = restOfPayload; } (void)memcpy(&packet->payload[piledUpPayload], &data[processedLength], splitLength); header->payloadLength += splitLength; processedLength += splitLength; if(header->payloadLength >= SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH) { /** -# packet is full - send */ header->packetType = SHELLMATTA_TRANSPORT_PACKET_DATA; ret = shellmatta_transport_send(transportLayer, packet); header->payloadLength = 0u; } } while (processedLength < outPayloadLength); return ret; } /** * @brief Send out piled up payload * * @param[in, out] handle shellmatta handle of the instance * @return errorcode */ shellmatta_retCode_t shellmatta_transport_flush(shellmatta_handle_t handle) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { shellmatta_transport_packet_t *packet = &inst->transportLayer.outPacket; if(0 != packet->header.payloadLength) { packet->header.packetType = SHELLMATTA_TRANSPORT_PACKET_DATA; ret = shellmatta_transport_send(&inst->transportLayer, packet); packet->header.payloadLength = 0u; } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } #endif