242 lines
6.7 KiB
C
242 lines
6.7 KiB
C
/***************************************************************************
|
|
* Copyright (C) 2022 - 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
* Niccolò Izzo IU2KIN *
|
|
* Frederik Saraci IU2NRO *
|
|
* Silvano Seva IU2KWO *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
|
***************************************************************************/
|
|
|
|
#include "drivers/USB/usb_vcom.h"
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include "core/xmodem.h"
|
|
#include <string.h>
|
|
#include "core/crc.h"
|
|
|
|
#define SOH (0x01) // start of 128-byte data packet
|
|
#define STX (0x02) // start of 1024-byte data packet
|
|
#define EOT (0x04) // End Of Transmission
|
|
#define ACK (0x06) // ACKnowledge, receive OK
|
|
#define NAK (0x15) // Negative ACKnowledge, receiver ERROR, retry
|
|
#define CAN (0x18) // two CAN in succession will abort transfer
|
|
#define CRC (0x43) // 'C' == 0x43, request 16-bit CRC, use in place of first NAK for CRC mode
|
|
#define ABT1 (0x41) // 'A' == 0x41, assume try abort by user typing
|
|
#define ABT2 (0x61) // 'a' == 0x61, assume try abort by user typing
|
|
|
|
/**
|
|
* @internal
|
|
* Collect a given amount of data from serial port.
|
|
*
|
|
* @param ptr: pointer to destination buffer.
|
|
* @param size: number of bytes to be retrieved.
|
|
*/
|
|
static void waitForData(uint8_t *ptr, size_t size)
|
|
{
|
|
size_t curSize = 0;
|
|
|
|
while(curSize < size)
|
|
{
|
|
ssize_t recvd = vcom_readBlock(ptr + curSize, size - curSize);
|
|
if(recvd >= 0) curSize += recvd;
|
|
}
|
|
}
|
|
|
|
|
|
void xmodem_sendPacket(const void *data, size_t size, uint8_t blockNum)
|
|
{
|
|
// Bad payload size, null block number or null data pointer: do not send
|
|
if(((size != 128) && (size != 1024)) || (data == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint8_t buf[3] = {SOH, 0x00, 0x00};
|
|
|
|
// Override header to STX for 1kB packets
|
|
if(size > 128)
|
|
{
|
|
buf[0] = STX;
|
|
}
|
|
|
|
// Sequence number
|
|
buf[1] = blockNum;
|
|
buf[2] = blockNum ^ 0xFF;
|
|
|
|
uint16_t crc = crc_ccitt(data, size);
|
|
|
|
// Send header, then data and finally CRC
|
|
vcom_writeBlock(buf, 3);
|
|
vcom_writeBlock(data, size);
|
|
|
|
buf[0] = crc >> 8;
|
|
buf[1] = crc & 0xFF;
|
|
vcom_writeBlock(buf, 2);
|
|
}
|
|
|
|
size_t xmodem_receivePacket(void* data, uint8_t expectedBlockNum)
|
|
{
|
|
// Get first byte
|
|
uint8_t status = 0;
|
|
while((status != STX) && (status != SOH))
|
|
{
|
|
waitForData(&status, 1);
|
|
}
|
|
|
|
// Get sequence number
|
|
uint8_t seq[2] = {0};
|
|
waitForData(seq, 2);
|
|
|
|
// Determine payload size and get data
|
|
size_t blockSize = 128;
|
|
if(status == STX) blockSize = 1024;
|
|
waitForData(((uint8_t *) data), blockSize);
|
|
|
|
// Get CRC
|
|
uint8_t crc[2] = {0};
|
|
waitForData(crc, 2);
|
|
|
|
// First sanity check: sequence number
|
|
if((seq[0] ^ seq[1]) != 0xFF) return 0;
|
|
if(expectedBlockNum != seq[0]) return 0;
|
|
|
|
// Second sanity check: CRC
|
|
uint16_t dataCrc = crc_ccitt(data, blockSize);
|
|
if((crc[0] != (dataCrc >> 8)) || (crc[1] != (dataCrc & 0xFF))) return 0;
|
|
|
|
return blockSize;
|
|
}
|
|
|
|
ssize_t xmodem_sendData(size_t size, int (*callback)(uint8_t *, size_t))
|
|
{
|
|
// Wait for the start command from the receiver, only CRC mode is supported.
|
|
uint8_t cmd = 0;
|
|
while(cmd != CRC)
|
|
{
|
|
waitForData(&cmd, 1);
|
|
}
|
|
|
|
// Send data.
|
|
uint8_t dataBuf[1024];
|
|
uint8_t blockNum = 1;
|
|
size_t sentSize = 0;
|
|
|
|
while(sentSize < size)
|
|
{
|
|
size_t remaining = size - sentSize;
|
|
size_t blockSize = 1024;
|
|
if(remaining < blockSize) blockSize = remaining;
|
|
|
|
// Request data, stop transfer on failure
|
|
if(callback(dataBuf, blockSize) < 0)
|
|
{
|
|
cmd = CAN;
|
|
vcom_writeBlock(&cmd, 1);
|
|
return -1;
|
|
}
|
|
|
|
// Pad data to 128 or 1024 bytes, if necessary
|
|
size_t padSize = 0;
|
|
if(blockSize < 128)
|
|
{
|
|
padSize = 128 - blockSize;
|
|
}
|
|
else if(blockSize < 1024)
|
|
{
|
|
padSize = 1024 - blockSize;
|
|
}
|
|
|
|
uint8_t *ptr = dataBuf + padSize + 1;
|
|
memset(ptr, 0x1A, padSize);
|
|
|
|
// Send packet and wait for ACK, resend on NACK.
|
|
bool ok = false;
|
|
do
|
|
{
|
|
blockSize += padSize;
|
|
xmodem_sendPacket(dataBuf, blockSize, blockNum);
|
|
|
|
cmd = 0;
|
|
while((cmd != ACK) && (cmd != NAK))
|
|
{
|
|
waitForData(&cmd, 1);
|
|
if(cmd == ACK) ok = true;
|
|
}
|
|
}
|
|
while(ok == false);
|
|
|
|
sentSize += blockSize - padSize;
|
|
blockNum++;
|
|
}
|
|
|
|
// End of transfer
|
|
cmd = EOT;
|
|
vcom_writeBlock(&cmd, 1);
|
|
while(cmd != ACK)
|
|
{
|
|
waitForData(&cmd, 1);
|
|
}
|
|
|
|
return sentSize;
|
|
}
|
|
|
|
ssize_t xmodem_receiveData(size_t size, void (*callback)(uint8_t *, size_t))
|
|
{
|
|
uint8_t dataBuf[1024];
|
|
uint8_t command = 0;
|
|
uint8_t blockNum = 1;
|
|
size_t rcvdSize = 0;
|
|
|
|
// Request data transfer in CRC mode
|
|
command = CRC;
|
|
vcom_writeBlock(&command, 1);
|
|
|
|
while(rcvdSize < size)
|
|
{
|
|
size_t blockSize = xmodem_receivePacket(dataBuf, blockNum);
|
|
if(blockSize == 0)
|
|
{
|
|
// Bad packet, send NACK
|
|
command = NAK;
|
|
}
|
|
else
|
|
{
|
|
// New data arrived
|
|
size_t delta = size - rcvdSize;
|
|
if(blockSize < delta) delta = blockSize;
|
|
callback(dataBuf, delta);
|
|
|
|
rcvdSize += delta;
|
|
blockNum++;
|
|
|
|
// ACK and go on
|
|
command = ACK;
|
|
}
|
|
|
|
vcom_writeBlock(&command, 1);
|
|
}
|
|
|
|
// Wait for EOT from the sender, ACK and return
|
|
uint8_t status = 0;
|
|
while(status != EOT)
|
|
{
|
|
waitForData(&status, 1);
|
|
}
|
|
|
|
command = ACK;
|
|
vcom_writeBlock(&command, 1);
|
|
|
|
return rcvdSize;
|
|
}
|