diff --git a/src/xmodem.cpp b/src/xmodem.cpp index 1d8c777600..f16c806d8f 100644 --- a/src/xmodem.cpp +++ b/src/xmodem.cpp @@ -50,6 +50,7 @@ #include "xmodem.h" #include "SPILock.h" +#include #ifdef FSCom @@ -119,11 +120,41 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) case meshtastic_XModem_Control_STX: if ((xmodemPacket.seq == 0) && !isReceiving && !isTransmitting) { // NULL packet has the destination filename - memcpy(filename, &xmodemPacket.buffer.bytes, xmodemPacket.buffer.size); + memset(filename, 0, sizeof(filename)); + size_t filenameLen = xmodemPacket.buffer.size; + if (filenameLen >= sizeof(filename)) + filenameLen = sizeof(filename) - 1; + memcpy(filename, xmodemPacket.buffer.bytes, filenameLen); + filename[filenameLen] = '\0'; + if (filename[0] == '\0') { + LOG_WARN("XModem: Rejecting empty filename"); + sendControl(meshtastic_XModem_Control_NAK); + isReceiving = false; + break; + } if (xmodemPacket.control == meshtastic_XModem_Control_SOH) { // Receive this file and put to Flash spiLock->lock(); - file = FSCom.open(filename, FILE_O_WRITE); + if (recvCommitPending) { + FSCom.remove(recvTmpPath); + recvCommitPending = false; + } + int plen = snprintf(recvTmpPath, sizeof(recvTmpPath), "%s.tmp", filename); + if (plen < 0 || (size_t)plen >= sizeof(recvTmpPath)) { + spiLock->unlock(); + LOG_WARN("XModem: Receive path too long for .tmp suffix"); + sendControl(meshtastic_XModem_Control_NAK); + isReceiving = false; + break; + } + if (FSCom.exists(recvTmpPath) && !FSCom.remove(recvTmpPath)) { + spiLock->unlock(); + LOG_WARN("XModem: Failed to remove existing temp before receive: %s", recvTmpPath); + sendControl(meshtastic_XModem_Control_NAK); + isReceiving = false; + break; + } + file = FSCom.open(recvTmpPath, FILE_O_WRITE); spiLock->unlock(); if (file) { sendControl(meshtastic_XModem_Control_ACK); @@ -131,6 +162,7 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) packetno = 1; break; } + LOG_WARN("XModem: Failed to open temp for receive: %s", recvTmpPath); sendControl(meshtastic_XModem_Control_NAK); isReceiving = false; break; @@ -186,24 +218,49 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) } break; case meshtastic_XModem_Control_EOT: - // End of transmission - sendControl(meshtastic_XModem_Control_ACK); + // End of transmission (receive): flush temp, rename to final, ACK only after commit + if (!isReceiving && !recvCommitPending) + break; + if (recvCommitPending) { + if (!renameFile(recvTmpPath, filename)) { + LOG_WARN("XModem: rename temp to final failed (retry): %s -> %s", recvTmpPath, filename); + sendControl(meshtastic_XModem_Control_NAK); + break; + } + sendControl(meshtastic_XModem_Control_ACK); + recvCommitPending = false; + memset(recvTmpPath, 0, sizeof(recvTmpPath)); + break; + } spiLock->lock(); file.flush(); file.close(); spiLock->unlock(); isReceiving = false; + if (!renameFile(recvTmpPath, filename)) { + LOG_WARN("XModem: rename temp to final failed: %s -> %s", recvTmpPath, filename); + sendControl(meshtastic_XModem_Control_NAK); + recvCommitPending = true; + break; + } + sendControl(meshtastic_XModem_Control_ACK); + memset(recvTmpPath, 0, sizeof(recvTmpPath)); break; case meshtastic_XModem_Control_CAN: - // Cancel transmission and remove file + // Cancel receive: drop partial temp only (final path unchanged) + if (!isReceiving && !recvCommitPending) + break; sendControl(meshtastic_XModem_Control_ACK); spiLock->lock(); - file.flush(); - file.close(); - - FSCom.remove(filename); + if (isReceiving) { + file.flush(); + file.close(); + } + FSCom.remove(recvTmpPath); spiLock->unlock(); isReceiving = false; + recvCommitPending = false; + memset(recvTmpPath, 0, sizeof(recvTmpPath)); break; case meshtastic_XModem_Control_ACK: // Acknowledge Send the next packet diff --git a/src/xmodem.h b/src/xmodem.h index 4cfcb43e18..8a68df3419 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -54,6 +54,7 @@ class XModemAdapter private: bool isReceiving = false; + bool recvCommitPending = false; bool isTransmitting = false; bool isEOT = false; @@ -68,6 +69,8 @@ class XModemAdapter #endif char filename[sizeof(meshtastic_XModem_buffer_t::bytes)] = {0}; + /** Receive scratch path (`filename` + `.tmp`); commit via renameFile on EOT. */ + char recvTmpPath[sizeof(filename) + 5] = {0}; protected: meshtastic_XModem xmodemStore = meshtastic_XModem_init_zero;