/** * @file shellmatta_ymodem.c * @brief ymodem functions of shellmatta * @author Simon Fischer */ #include "shellmatta_ymodem.h" #include "shellmatta_crc.h" #include "shellmatta_utils.h" #include /* current ymodem packet size */ uint16_t yModemPacketSize = YMODEM_PACKET_SIZE; /* packet counter - counts all correctly received packets during one transmission */ uint16_t shellmatta_ymodem_packet_counter = 0u; /* counts total amount of data bytes received during one transmission */ uint32_t shellmatta_ymodem_total_bytes_received = 0u; /* counts total amount of data bytes received during one packet */ uint16_t shellmatta_ymodem_byte_counter = 0u; uint8_t fileNameDelimiterPosition = 255u; uint8_t fileSizeDelimiterPosition = 255u; /* structure of a packet consisting of data and metadata */ shellmatta_ymodem_packet_t shellmatta_ymodem_packet; /* holds information about which data type is currently actice: YMODEM_NONE, YMODEM_HEADER, YMODEM_BODY, YMODEM_FOOTER */ shellmatta_ymodem_datatype_t shellmatta_ymodem_current_data_type = YMODEM_NONE; /* structure of ymodem callback functions */ shellmatta_ymodem_callbacks_t shellmatta_ymodem_callbacks; /* flag to disable byte processing in ymodem */ bool ymodem_is_enabled = true; /** * @brief Initialise the ymodem prior to actually receiving data * @param[in, out] handle shellmatta handle of the instance * @param[in] recvBuffer pointer to the buffer to save the received payload in * @param[in] fileSize pointer to the file size variable * @param[in] packetSize pointer to the packet size variable * @param[in] callbacks callback functions for the ymodem module */ void shellmatta_ymodem_init(shellmatta_handle_t handle, uint8_t* recvBuffer, uint32_t* fileSize, uint16_t* packetSize, shellmatta_ymodem_callbacks_t callbacks) { if ((void*)0u == recvBuffer) return; /* init packet structure */ shellmatta_ymodem_packet.packetNumber = 0u; shellmatta_ymodem_packet.reversePacketNumber = 0u; shellmatta_ymodem_packet.packetCrc = 0u; shellmatta_ymodem_packet.packetSize = packetSize; shellmatta_ymodem_packet.fileSize = fileSize; shellmatta_ymodem_packet.packetData = recvBuffer; /* init callbacks */ shellmatta_ymodem_callbacks = callbacks; /* init ymodem state */ shellmatta_ymodem_set_state(handle, WAIT_FOR_START); /* init current data type */ shellmatta_ymodem_current_data_type = YMODEM_NONE; } /** * @brief change the current state of the ymodem module * @param[in, out] handle shellmatta handle of the instance * @param[in] newState the state the ymodem module should be changed to */ void shellmatta_ymodem_set_state(shellmatta_handle_t handle, shellmatta_ymodem_state_t newState) { ((shellmatta_instance_t*)handle)->ymodemState = newState; } /** * @brief Returns the current state of the ymodem module * @param[in, out] handle shellmatta handle of the instance * @return the current state of the ymodem module */ shellmatta_ymodem_state_t shellmatta_ymodem_get_state(shellmatta_handle_t handle) { return ((shellmatta_instance_t*)handle)->ymodemState; } /** * @brief State machine that processes bytewise input during active ymodem transmission * @param[in, out] handle shellmatta handle of the instance * @param[in] byteIn the byte to be processed * @note won't do anything if ymodem module is not initialized */ void shellmatta_ymodem_receive_packet(shellmatta_handle_t handle, uint8_t byteIn) { static uint32_t fileSize = 0u; static char fileSizeStr[7u]; /* hopefully no more bytes than a million will ever be transmitted */ shellmatta_ymodem_rcv_retcode_t recvRetCode; /* skip byte processing if ymodem is not enabled */ if (!ymodem_is_enabled) { return; } recvRetCode = shellmatta_ymodem_receive_byte(handle, byteIn); switch (shellmatta_ymodem_get_state(handle)) { case INACTIVE: /* cant do anything if ymodem module was not correctly initialized */ return; case WAIT_FOR_START: /* wait for start symbol of a packet */ switch (shellmatta_ymodem_current_data_type) { case YMODEM_NONE: /* go here if the header packet is to be expected */ if (recvRetCode == SOH_RECEIVED || recvRetCode == STX_RECEIVED) { /* reset packet counter */ shellmatta_ymodem_packet_counter = 0u; shellmatta_ymodem_byte_counter = 0u; shellmatta_ymodem_set_state(handle, RECEIVE_PACKET); shellmatta_ymodem_current_data_type = YMODEM_HEADER; } break; case YMODEM_HEADER: /* go here if the first body packet is to be expected */ if (recvRetCode == STX_RECEIVED || SOH_RECEIVED) { shellmatta_ymodem_byte_counter = 0u; shellmatta_ymodem_set_state(handle, RECEIVE_PACKET); shellmatta_ymodem_current_data_type = YMODEM_BODY; } break; case YMODEM_BODY: /* go here if the data transmission loop is active */ if (recvRetCode == SOH_RECEIVED || recvRetCode == STX_RECEIVED) { shellmatta_ymodem_byte_counter = 0u; /* stay in body when SOH was received */ shellmatta_ymodem_set_state(handle, RECEIVE_PACKET); shellmatta_ymodem_current_data_type = YMODEM_BODY; } else /* go here if the end of transmission symbol is received */ if (recvRetCode == EOT_RECEIVED) { shellmatta_ymodem_byte_counter = 0u; /* answer with ACK */ shellmatta_ymodem_ack(handle); /* then send 0x43 symbol */ shellmatta_ymodem_control(handle, YMODEM_CRC); /* go to footer when EOT was received */ shellmatta_ymodem_current_data_type = YMODEM_FOOTER; /* then wait for SOH */ } break; case YMODEM_FOOTER: /* go here if the end of transmission packet is to be expected */ /* it _may_ be, that EOT is sent multiple times, repeat previous procedure */ if (recvRetCode == EOT_RECEIVED) { /* answer with ACK */ shellmatta_ymodem_ack(handle); /* then send 0x43 symbol */ shellmatta_ymodem_control(handle, YMODEM_CRC); } if (recvRetCode == SOH_RECEIVED) { /* reset packet counter */ shellmatta_ymodem_packet_counter = 0u; /* reset byte counter to avoid error by one caused by previously received EOT symbol */ shellmatta_ymodem_byte_counter = 0u; shellmatta_ymodem_set_state(handle, RECEIVE_PACKET); } break; default: break; } break; case RECEIVE_PACKET: /* receiving data */ if (recvRetCode == DATA_RECEIVED) { switch (shellmatta_ymodem_current_data_type) { case YMODEM_HEADER: switch (shellmatta_ymodem_byte_counter) { /* first two bytes are packet number and reverse packet number */ case 0: shellmatta_ymodem_packet.packetNumber = byteIn; break; case 1: shellmatta_ymodem_packet.reversePacketNumber = byteIn; break; default: break; } /* after packet numbers, data field begins */ if ((shellmatta_ymodem_byte_counter >= YMODEM_HEADER_DATA_OFFSET) && (shellmatta_ymodem_byte_counter < YMODEM_HEADER_CRC_POSITION)) { shellmatta_ymodem_packet.packetData[shellmatta_ymodem_byte_counter - YMODEM_HEADER_DATA_OFFSET] = byteIn; /* check for filename */ if (shellmatta_ymodem_byte_counter < fileNameDelimiterPosition) { /* find NULL-Terminator in filename */ if (byteIn == YMODEM_NULL) { fileNameDelimiterPosition = shellmatta_ymodem_byte_counter + 1; } } /* after filename the filesize begins */ else if (shellmatta_ymodem_byte_counter >= fileNameDelimiterPosition && (shellmatta_ymodem_byte_counter < fileSizeDelimiterPosition)) { /* find space as delimiter after filesize */ if (byteIn == YMODEM_SPACE) { fileSizeDelimiterPosition = shellmatta_ymodem_byte_counter; /* convert file size string to actual number */ fileSize = utils_shellAsciiToUInt32(fileSizeStr, fileSizeDelimiterPosition - fileNameDelimiterPosition); *shellmatta_ymodem_packet.fileSize = fileSize; } /* save characters in string otherwise */ else { fileSizeStr[shellmatta_ymodem_byte_counter - fileNameDelimiterPosition] = byteIn; } } else { /* some meta data from sender, possibly file date */ /* not needed for now */ } } /* after data field, crc begins */ if (shellmatta_ymodem_byte_counter >= YMODEM_HEADER_CRC_POSITION) { if (shellmatta_ymodem_byte_counter == YMODEM_HEADER_CRC_POSITION) { /* upper byte */ shellmatta_ymodem_packet.packetCrc = byteIn << 8u; } if (shellmatta_ymodem_byte_counter == YMODEM_HEADER_CRC_POSITION + 1) { /* lower byte */ shellmatta_ymodem_packet.packetCrc |= byteIn; /* change ymodem state to check packet */ shellmatta_ymodem_check_packet(handle); } } shellmatta_ymodem_byte_counter++; break; case YMODEM_BODY: switch (shellmatta_ymodem_byte_counter) { case 0: shellmatta_ymodem_packet.packetNumber = byteIn; break; case 1: shellmatta_ymodem_packet.reversePacketNumber = byteIn; break; default: break; } /* after data field, crc begins */ if (shellmatta_ymodem_byte_counter >= yModemPacketSize + YMODEM_HEADER_DATA_OFFSET) { if (shellmatta_ymodem_byte_counter == yModemPacketSize + YMODEM_HEADER_DATA_OFFSET) { /* upper byte */ shellmatta_ymodem_packet.packetCrc = byteIn << 8u; } if (shellmatta_ymodem_byte_counter == yModemPacketSize + YMODEM_HEADER_DATA_OFFSET + 1) { /* lower byte */ shellmatta_ymodem_packet.packetCrc |= byteIn; /* change ymodem state to check packet */ shellmatta_ymodem_check_packet(handle); } } else if ((shellmatta_ymodem_byte_counter > 1) && (shellmatta_ymodem_byte_counter < (yModemPacketSize + YMODEM_HEADER_DATA_OFFSET))) { shellmatta_ymodem_packet.packetData[shellmatta_ymodem_byte_counter - YMODEM_HEADER_DATA_OFFSET] = byteIn; } shellmatta_ymodem_byte_counter++; break; case YMODEM_FOOTER: /* reset packet counter */ shellmatta_ymodem_packet_counter = 0; switch (shellmatta_ymodem_byte_counter) { case 0: shellmatta_ymodem_packet.packetNumber = byteIn; break; case 1: shellmatta_ymodem_packet.reversePacketNumber = byteIn; break; default: break; } /* after data field, crc begins */ if (shellmatta_ymodem_byte_counter >= yModemPacketSize + YMODEM_HEADER_DATA_OFFSET) { if (shellmatta_ymodem_byte_counter == yModemPacketSize + YMODEM_HEADER_DATA_OFFSET) { /* upper byte */ shellmatta_ymodem_packet.packetCrc = byteIn << 8u; } if (shellmatta_ymodem_byte_counter == yModemPacketSize + YMODEM_HEADER_DATA_OFFSET + 1) { /* lower byte */ shellmatta_ymodem_packet.packetCrc |= byteIn; /* change ymodem state to check packet */ shellmatta_ymodem_check_packet(handle); } } else if ((shellmatta_ymodem_byte_counter > 1) && (shellmatta_ymodem_byte_counter < (yModemPacketSize + YMODEM_HEADER_DATA_OFFSET))) { /* save data */ shellmatta_ymodem_packet.packetData[shellmatta_ymodem_byte_counter - YMODEM_HEADER_DATA_OFFSET] = byteIn; } shellmatta_ymodem_byte_counter++; break; default: break; } } break; default: break; } } /** * @brief Gives a return code depending on symbol received at different states of the ymodem module * @param[in, out] handle shellmatta handle of the instance * @param[in] byteIn currently processed input byte */ shellmatta_ymodem_rcv_retcode_t shellmatta_ymodem_receive_byte(shellmatta_handle_t handle, uint8_t byteIn) { shellmatta_ymodem_rcv_retcode_t ret; if (WAIT_FOR_START == shellmatta_ymodem_get_state(handle)) { switch (byteIn) { case YMODEM_SOH: /* start of header -> default packet size */ yModemPacketSize = YMODEM_PACKET_SIZE; ret = SOH_RECEIVED; break; case YMODEM_STX: /* start of header -> extended packet size */ yModemPacketSize = YMODEM_PACKET_SIZE_1K; ret = STX_RECEIVED; break; case YMODEM_EOT: /* end of transmission -> data transmission complete */ ret = EOT_RECEIVED; break; case YMODEM_CA: /* cancel transmssion -> reset state machine */ ret = CA_RECEIVED; break; default: /* no other data should be received in this state */ ret = ERROR; break; } /* give packet size back to upper layer */ *shellmatta_ymodem_packet.packetSize = yModemPacketSize; } else if (RECEIVE_PACKET == shellmatta_ymodem_get_state(handle)) { ret = DATA_RECEIVED; } return ret; } /** * @brief Checks packet data for validity and sends ACK or NAK accordingly * @param[in, out] handle shellmatta handle of the instance * @note Only to be called after full packet is received * @note Will call yModemRecvPacketCallback when packet is received correctly * @note Will call ymodemTransmissionCompleteCallback when transmission is completed */ void shellmatta_ymodem_check_packet(shellmatta_handle_t handle) { uint16_t computedCrc = 0u; if (shellmatta_ymodem_packet.packetNumber != (0xFF - shellmatta_ymodem_packet.reversePacketNumber)) { shellmatta_ymodem_nak(handle); return; } /* compare to internal packet counter */ if (shellmatta_ymodem_packet.packetNumber != ((uint8_t)shellmatta_ymodem_packet_counter)) /* uint8_t cast to cause overflow */ { shellmatta_ymodem_nak(handle); return; } /* compare packet crc only after other checks succeeded */ computedCrc = crc16Calc((const char*)shellmatta_ymodem_packet.packetData, yModemPacketSize); //! uncomment to ignore crc check for debug purposes if (shellmatta_ymodem_packet.packetCrc != computedCrc) { shellmatta_ymodem_nak(handle); return; } /* if previous checks passed, packet is acknowledged */ (void) computedCrc; shellmatta_ymodem_total_bytes_received += shellmatta_ymodem_byte_counter; shellmatta_ymodem_byte_counter = 0u; shellmatta_ymodem_ack(handle); if (shellmatta_ymodem_current_data_type == YMODEM_HEADER) { /* send additional CRC symbol if the packet was a header packet */ shellmatta_ymodem_control(handle, YMODEM_CRC); } /* callback handling */ switch (shellmatta_ymodem_current_data_type) { case YMODEM_NONE: /* shouldn't happen */ break; case YMODEM_HEADER: /* nothing to do after receiving the header packet */ break; case YMODEM_BODY: /* call the ymodem receive packet callback */ if (NULL != shellmatta_ymodem_callbacks.yModemRecvPacketCallback) { shellmatta_ymodem_callbacks.yModemRecvPacketCallback(); } break; case YMODEM_FOOTER: /* call the ymodem transmission complete callback */ if (NULL != shellmatta_ymodem_callbacks.ymodemTransmissionCompleteCallback) { shellmatta_ymodem_callbacks.ymodemTransmissionCompleteCallback(); } break; default: break; } } /** * @brief Acknowledge handling. Will send ACK and set ymodem state back to WAIT_FOR_START * @param[in, out] handle shellmatta handle of the instance */ void shellmatta_ymodem_ack(shellmatta_handle_t handle) { shellmatta_ymodem_control(handle, YMODEM_ACK); shellmatta_ymodem_packet_counter++; shellmatta_ymodem_set_state(handle, WAIT_FOR_START); } /** * @brief Not-Acknowledge handling. Will send NAK and set ymodem state back to WAIT_FOR_START * @param[in, out] handle shellmatta handle of the instance */ void shellmatta_ymodem_nak(shellmatta_handle_t handle) { shellmatta_ymodem_set_state(handle, WAIT_FOR_START); /* set back current data type to prevent false forward stepping */ switch (shellmatta_ymodem_current_data_type) { case YMODEM_NONE: /* no handling needed */ break; case YMODEM_HEADER: shellmatta_ymodem_control(handle, YMODEM_NAK); break; case YMODEM_BODY: /* YMODEM_BODY stays in YMODEM_BODY */ shellmatta_ymodem_control(handle, YMODEM_NAK); shellmatta_ymodem_current_data_type = YMODEM_BODY; break; case YMODEM_FOOTER: /* YMODEM_FOOTER as well */ shellmatta_ymodem_current_data_type = YMODEM_FOOTER; break; default: break; } } /** * @brief forwards the given character to write-function without formatting * @param[in] handle shellmatta handle of the instance * @param[in] c character to be sent */ void shellmatta_ymodem_control(shellmatta_handle_t handle, const char c) { ((shellmatta_instance_t*)handle)->write(&c, 1); } /** * @brief reset function for the ymodem module * @param[in, out] handle shellmatta handle of the instance * @param[in] doCancel flag to execute the cancel-callback */ void shellmatta_ymodem_reset(shellmatta_handle_t handle, bool doCancel) { /* call cancel callback function */ if (doCancel) { /* send cancel symbol */ shellmatta_ymodem_control(handle, YMODEM_CA); if (NULL != shellmatta_ymodem_callbacks.yModemCancelCallback) { shellmatta_ymodem_callbacks.yModemCancelCallback(); } } /* reset global variables */ shellmatta_ymodem_byte_counter = 0u; shellmatta_ymodem_packet_counter = 0u; shellmatta_ymodem_total_bytes_received = 0u; fileNameDelimiterPosition = 255u; fileSizeDelimiterPosition = 255u; /* reset all ymodem_packet data */ shellmatta_ymodem_packet.packetNumber = 0u; shellmatta_ymodem_packet.reversePacketNumber = 0u; shellmatta_ymodem_packet.packetCrc = 0u; shellmatta_ymodem_packet.packetSize = 0u; shellmatta_ymodem_packet.fileSize = 0u; shellmatta_ymodem_packet.packetData = 0u; /* set ymodem to inactive */ shellmatta_ymodem_set_state(handle, INACTIVE); shellmatta_ymodem_current_data_type = YMODEM_NONE; #ifdef SHELLMATTA_TRANSPORT /* re-enable transport layer */ ((shellmatta_instance_t*)handle)->transportEnabled = true; #endif } shellmatta_ymodem_datatype_t shellmatta_ymodem_get_current_datatype(void) { return shellmatta_ymodem_current_data_type; } void shellmatta_ymodem_enable(void) { ymodem_is_enabled = true; } void shellmatta_ymodem_disable(void) { ymodem_is_enabled = false; }