diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 3f74e672d..f0b6edff1 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -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; diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index aa190ae37..ff1e7347c 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -42,6 +42,9 @@ class PhoneAPI : public Observer // 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; diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index 2e0b66629..4d2c629e0 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -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; diff --git a/src/xmodem.cpp b/src/xmodem.cpp new file mode 100644 index 000000000..4f61c6c96 --- /dev/null +++ b/src/xmodem.cpp @@ -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; + } +} diff --git a/src/xmodem.h b/src/xmodem.h new file mode 100644 index 000000000..6c2418926 --- /dev/null +++ b/src/xmodem.h @@ -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 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;