Merge pull request #2112 from meshtastic/xmodem-proto

Add Chunked File Transfer to API
This commit is contained in:
Thomas Göttgens 2023-01-16 20:36:09 +01:00 committed by GitHub
commit d35b619063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 318 additions and 1 deletions

View File

@ -6,6 +6,7 @@
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "configuration.h"
#include "xmodem.h"
#if FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error FromRadio is too big
@ -31,6 +32,7 @@ void PhoneAPI::handleStartConfig()
if (!isConnected()) {
onConnectionChanged(true);
observe(&service.fromNumChanged);
observe(&xModem.packetReady);
}
// even if we were already connected - restart our state machine
@ -48,6 +50,7 @@ void PhoneAPI::close()
state = STATE_SEND_NOTHING;
unobserve(&service.fromNumChanged);
unobserve(&xModem.packetReady);
releasePhonePacket(); // Don't leak phone packets on shutdown
releaseQueueStatusPhonePacket();
@ -90,6 +93,10 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
LOG_INFO("Disconnecting from phone\n");
close();
break;
case ToRadio_xmodemPacket_tag:
LOG_INFO("Got xmodem packet\n");
xModem.handlePacket(toRadioScratch.xmodemPacket);
break;
default:
// Ignore nop messages
// LOG_DEBUG("Error: unexpected ToRadio variant\n");
@ -287,10 +294,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Do we have a message from the mesh?
LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n");
if (queueStatusPacketForPhone) {
fromRadioScratch.which_payload_variant = FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = *queueStatusPacketForPhone;
releaseQueueStatusPhonePacket();
} else if (xmodemPacketForPhone) {
fromRadioScratch.which_payload_variant = FromRadio_xmodemPacket_tag;
fromRadioScratch.xmodemPacket = *xmodemPacketForPhone;
free(xmodemPacketForPhone);
xmodemPacketForPhone = NULL;
} else if (packetForPhone) {
printPacket("phone downloaded packet", packetForPhone);
@ -353,6 +364,7 @@ bool PhoneAPI::available()
case STATE_SEND_MODULECONFIG:
case STATE_SEND_COMPLETE_ID:
return true;
case STATE_SEND_NODEINFO:
if (!nodeInfoForPhone)
nodeInfoForPhone = nodeDB.readNextInfo();
@ -365,6 +377,12 @@ bool PhoneAPI::available()
if (hasPacket)
return true;
if (!xmodemPacketForPhone)
xmodemPacketForPhone = xModem.getForPhone();
hasPacket = !!packetForPhone;
if (hasPacket)
return true;
if (!packetForPhone)
packetForPhone = service.getForPhone();
hasPacket = !!packetForPhone;

View File

@ -42,6 +42,9 @@ class PhoneAPI : public Observer<uint32_t> // FIXME, we shouldn't be inheriting
/// downloads it
MeshPacket *packetForPhone = NULL;
// file transfer packets destined for phone. Push it to the queue then free it.
XModem *xmodemPacketForPhone = NULL;
// Keep QueueStatus packet just as packetForPhone
QueueStatus *queueStatusPacketForPhone = NULL;

View File

@ -700,6 +700,7 @@ typedef struct _ToRadio {
This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link.
(Sending this message is optional for clients) */
bool disconnect;
/* File Transfer Chunk */
XModem xmodemPacket;
};
} ToRadio;

219
src/xmodem.cpp Normal file
View File

@ -0,0 +1,219 @@
/***********************************************************************************************************************
* based on XMODEM implementation by Georges Menie (www.menie.org)
***********************************************************************************************************************
* Copyright 2001-2019 Georges Menie (www.menie.org)
* All rights reserved.
*
* Adapted for protobuf encapsulation. this is not really Xmodem any more.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************************************************************/
#include "xmodem.h"
XModemAdapter xModem;
XModemAdapter::XModemAdapter()
{
xmodemStore = (XModem*)malloc(XModem_size);
}
unsigned short XModemAdapter::crc16_ccitt(const pb_byte_t *buffer, int length)
{
unsigned short crc16 = 0;
while(length != 0) {
crc16 = (unsigned char)(crc16 >> 8) | (crc16 << 8);
crc16 ^= *buffer;
crc16 ^= (unsigned char)(crc16 & 0xff) >> 4;
crc16 ^= (crc16 << 8) << 4;
crc16 ^= ((crc16 & 0xff) << 4) << 1;
buffer++;
length--;
}
return crc16;
}
int XModemAdapter::check(const pb_byte_t *buf, int sz, unsigned short tcrc)
{
unsigned short crc = crc16_ccitt(buf, sz);
if (crc == tcrc)
return 1;
else
return 0;
}
void XModemAdapter::sendControl(XModem_Control c) {
memset(xmodemStore, 0, XModem_size);
xmodemStore->control = c;
LOG_DEBUG("XModem: Notify Sending control %d.\n", c);
packetReady.notifyObservers(packetno);
}
XModem *XModemAdapter::getForPhone()
{
if(xmodemStore) {
return xmodemStore;
} else {
return NULL;
}
}
void XModemAdapter::handlePacket(XModem xmodemPacket)
{
switch(xmodemPacket.control) {
case XModem_Control_SOH:
case XModem_Control_STX:
if ((xmodemPacket.seq == 0) && !isReceiving && !isTransmitting) {
// NULL packet has the destination filename
memcpy(filename, &xmodemPacket.buffer.bytes, xmodemPacket.buffer.size);
if(xmodemPacket.control == XModem_Control_SOH) { // Receive this file and put to Flash
file = FSCom.open(filename, FILE_O_WRITE);
if(file) {
sendControl(XModem_Control_ACK);
isReceiving = true;
packetno = 1;
break;
}
sendControl(XModem_Control_NAK);
isReceiving = false;
break;
} else { // Transmit this file from Flash
LOG_INFO("XModem: Transmitting file %s\n", filename);
file = FSCom.open(filename, FILE_O_READ);
if (file) {
packetno = 1;
isTransmitting = true;
memset(xmodemStore, 0, XModem_size);
xmodemStore->control = XModem_Control_SOH;
xmodemStore->seq = packetno;
xmodemStore->buffer.size = file.read(xmodemStore->buffer.bytes, sizeof(XModem_buffer_t::bytes));
xmodemStore->crc16 = crc16_ccitt(xmodemStore->buffer.bytes, xmodemStore->buffer.size);
LOG_DEBUG("XModem: STX Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore->buffer.size);
if (xmodemStore->buffer.size < sizeof(XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
packetReady.notifyObservers(packetno);
break;
}
sendControl(XModem_Control_NAK);
isTransmitting = false;
break;
}
} else {
if (isReceiving) {
// normal file data packet
if ((xmodemPacket.seq == packetno) && check(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size, xmodemPacket.crc16)) {
// valid packet
file.write(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size);
sendControl(XModem_Control_ACK);
packetno++;
break;
}
// invalid packet
sendControl(XModem_Control_NAK);
break;
} else if (isTransmitting) {
// just received something weird.
sendControl(XModem_Control_CAN);
isTransmitting = false;
break;
}
}
break;
case XModem_Control_EOT:
// End of transmission
sendControl(XModem_Control_ACK);
file.close();
isReceiving = false;
break;
case XModem_Control_CAN:
// Cancel transmission and remove file
sendControl(XModem_Control_ACK);
file.close();
FSCom.remove(filename);
isReceiving = false;
break;
case XModem_Control_ACK:
// Acknowledge Send the next packet
if (isTransmitting) {
if (isEOT) {
sendControl(XModem_Control_EOT);
file.close();
LOG_INFO("XModem: Finished sending file %s\n", filename);
isTransmitting = false;
isEOT = false;
break;
}
retrans = MAXRETRANS; // reset retransmit counter
packetno++;
memset(xmodemStore, 0, XModem_size);
xmodemStore->control = XModem_Control_SOH;
xmodemStore->seq = packetno;
xmodemStore->buffer.size = file.read(xmodemStore->buffer.bytes, sizeof(XModem_buffer_t::bytes));
xmodemStore->crc16 = crc16_ccitt(xmodemStore->buffer.bytes, xmodemStore->buffer.size);
LOG_DEBUG("XModem: ACK Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore->buffer.size);
if (xmodemStore->buffer.size < sizeof(XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
packetReady.notifyObservers(packetno);
} else {
// just received something weird.
sendControl(XModem_Control_CAN);
}
break;
case XModem_Control_NAK:
// Negative acknowledge. Send the same buffer again
if (isTransmitting) {
if (--retrans <= 0) {
sendControl(XModem_Control_CAN);
file.close();
LOG_INFO("XModem: Retransmit timeout, cancelling file %s\n", filename);
isTransmitting = false;
break;
}
memset(xmodemStore, 0, XModem_size);
xmodemStore->control = XModem_Control_SOH;
xmodemStore->seq = packetno;
file.seek((packetno-1) * sizeof(XModem_buffer_t::bytes));
xmodemStore->buffer.size = file.read(xmodemStore->buffer.bytes, sizeof(XModem_buffer_t::bytes));
xmodemStore->crc16 = crc16_ccitt(xmodemStore->buffer.bytes, xmodemStore->buffer.size);
LOG_DEBUG("XModem: NAK Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore->buffer.size);
if (xmodemStore->buffer.size < sizeof(XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
packetReady.notifyObservers(packetno);
} else {
// just received something weird.
sendControl(XModem_Control_CAN);
}
break;
default:
// Unknown control character
break;
}
}

76
src/xmodem.h Normal file
View File

@ -0,0 +1,76 @@
/***********************************************************************************************************************
* based on XMODEM implementation by Georges Menie (www.menie.org)
***********************************************************************************************************************
* Copyright 2001-2019 Georges Menie (www.menie.org)
* All rights reserved.
*
* Adapted for protobuf encapsulation. this is not really Xmodem any more.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************************************************************/
#pragma once
#include "configuration.h"
#include "mesh/generated/xmodem.pb.h"
#include "FSCommon.h"
#define MAXRETRANS 25
class XModemAdapter
{
public:
// Called when we put a fragment in the outgoing memory
Observable<uint32_t> packetReady;
XModemAdapter();
void handlePacket(XModem xmodemPacket);
XModem *getForPhone();
private:
bool isReceiving = false;
bool isTransmitting = false;
bool isEOT = false;
int retrans = MAXRETRANS;
uint16_t packetno = 0;
#ifdef ARCH_NRF52
File file = File(FSCom);
#else
File file;
#endif
char filename[sizeof(XModem_buffer_t::bytes)] = {0};
protected:
XModem *xmodemStore = NULL;
unsigned short crc16_ccitt(const pb_byte_t *buffer, int length);
int check(const pb_byte_t *buf, int sz, unsigned short tcrc);
void sendControl(XModem_Control c);
};
extern XModemAdapter xModem;