From fcb13c437b6f0544e4d6dff74b56d7be8490871c Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 8 Oct 2025 12:19:01 +0200 Subject: [PATCH] fix send message to server tftp --- BupTFTP.cpp | 927 +++++++++++++++++++++ BupTFTP.h | 123 +++ script.txt | 8 +- scripts.txt | 5 +- target_simulator/core/tftp_communicator.py | 67 +- target_simulator/gui/main_view.py | 54 +- target_simulator/utils/tftp_client.py | 140 ++-- tftp_diag.py | 40 + 8 files changed, 1266 insertions(+), 98 deletions(-) create mode 100644 BupTFTP.cpp create mode 100644 BupTFTP.h create mode 100644 tftp_diag.py diff --git a/BupTFTP.cpp b/BupTFTP.cpp new file mode 100644 index 0000000..1004ff6 --- /dev/null +++ b/BupTFTP.cpp @@ -0,0 +1,927 @@ +#include "BupTFTP.h" +//#include "qgnetworkinterface.h" + +#include +#include +#include +#include +#include +#include +#include + +//#include "mydebug.h" +#define MyDebug qDebug + +//#define dbg qDebug + +#include + +#define TFTP_MAXPKTLEN 512 + +#define DEFAULT_TGT_PORT 50069 + +//-- +#define TFTP_RRQ 01 /* read request */ +#define TFTP_WRQ 02 /* write request */ +#define TFTP_DATA 03 /* data packet */ +#define TFTP_ACK 04 /* acknowledgement */ +#define TFTP_ERROR 05 /* error code */ +#define TFTP_OACK 06 /* option acknowledgement */ + +#define TFTP_EUNDEF 0 /* not defined */ +#define TFTP_ENOTFOUND 1 /* file not found */ +#define TFTP_EACCESS 2 /* access violation */ +#define TFTP_ENOSPACE 3 /* disk full or allocation exceeded */ +#define TFTP_EBADOP 4 /* illegal TFTP operation */ +#define TFTP_EBADID 5 /* unknown transfer ID */ +#define TFTP_EEXISTS 6 /* file already exists */ +#define TFTP_ENOUSER 7 /* no such user */ +#define TFTP_EBADOPTION 8 /* bad option */ +#define TFTP_ECANCELLED 99 /* cancelled by administrator */ + +typedef struct { + unsigned char th_opcode[2]; /* packet type */ + union { + unsigned char tu_block[2]; /* block # */ + unsigned char tu_code[2]; /* error code */ + unsigned char tu_stuff[1]; /* request packet stuff */ + char tu_name[1]; + } th_u; +} tftphdr_t; + + +typedef struct +{ + tftphdr_t header; + char th_data[2048]; /* data or error string */ +} tftppkt_t; + +static void tftp_htons(unsigned char* const p, unsigned short v) +{ + p[0]=(unsigned char)(v>>8); + p[1]=(unsigned char)(v); +} + +static unsigned short tftp_ntos(const unsigned char* n) +{ + return n[1]|(n[0]<<8); +} + +//-- + +class FtpOperation +{ +public: + //QString fname; + char fname[512]; + bool read; + const void* data; + unsigned int size; + BupTFTPReceiver* receiver; + BupTFTPID* reqId; + + bool embedFirstBlock; + + FtpOperation(): + read(false), + data(0), + size(0), + receiver(0), + reqId(0), + embedFirstBlock(false) + { + fname[0]=0; + } + FtpOperation(const FtpOperation& p): + read(p.read), + data(p.data), + size(p.size), + receiver(p.receiver), + reqId(p.reqId), + embedFirstBlock(p.embedFirstBlock) + { + memcpy(fname, p.fname, sizeof fname); + } +}; + +class BupTFTP::Implementation +{ +public: + int sid; + + BupTFTP* parent; + + QQueue queue; + + int tgt_port; + QHostAddress tgtAddress; + + QUdpSocket rxSocket; + QUdpSocket txSocket; + + QTimer txTimer; + QTimer stsTimer; + + unsigned int data_len; + + unsigned long timeout_sts_ms; + unsigned long timeout_first_ms; + unsigned long timeout_last_ms; + unsigned long timeout_data_ms; + + QString lastErrorString; + int lastErrorCode; + QHostAddress lastTargetAddress; + int lastTargetPort; + + QString lastStatusString; + + QByteArray fileBuffer; + const char* data_pointer; + int data_size; + unsigned short block_counter; + + tftppkt_t pkt; + + int state; + int rx_mode; + char* rx_data_pointer; + BupTFTPReceiver* receiver; + + int rx_size; + + int sts_timeout_counter; + + int fixServerBug; + + QHostAddress localIP; + QHostAddress broadcastIP; + + FtpOperation currentOp; + + qint64 sendPacket(const tftppkt_t& pkt, unsigned int size, QUdpSocket* s=0) + { + int total_size=size+sizeof pkt.header; + if (s==0) + s=&txSocket; + //MyDebug()<<"->"<writeDatagram(reinterpret_cast(&pkt.header.th_opcode[0]), total_size, tgtAddress, tgt_port); + } + + Implementation(BupTFTP* controller): + sid(-1), + parent(controller) + { + memset(&pkt, 0,sizeof pkt); + + data_len=TFTP_MAXPKTLEN; + + timeout_first_ms=2000; + timeout_last_ms=60*1000; + timeout_data_ms=2000; + timeout_sts_ms=2*1000; + txTimer.setSingleShot(true); + stsTimer.setInterval(timeout_sts_ms); + + rx_mode=0; + state=0; + fixServerBug=0; + } + + bool enqueue(BupTFTPID* reqId, const char* fname/*const QString& fname*/, const void* data, unsigned int size, bool embedFirstBlock, BupTFTPReceiver* receiver=0, bool read_op=false) + { + FtpOperation op; + strncpy(op.fname,fname, sizeof op.fname); + op.data=data; + op.size=size; + op.read=read_op; + op.receiver=receiver; + op.embedFirstBlock=embedFirstBlock; + op.reqId=reqId; + queue.enqueue(op); + bool ok=queue_consume(); + + Q_EMIT parent->queueChanged(queue.size()); + return ok; + } + + bool queue_consume() + { + if (state!=0) + return false; + if (queue.isEmpty()) + return false; + FtpOperation op=queue.dequeue(); + + currentOp=op; + + Q_EMIT parent->queueChanged(queue.size()); + + bool ok=false; + if (op.read) + ok=receive(op.embedFirstBlock, const_cast(op.data), op.size, op.fname, op.receiver); + else + ok=send(op.embedFirstBlock, op.data, op.size, op.fname); + + if (ok) + { + Q_EMIT parent->sendBegin(); + } + else + Q_EMIT parent->transferError(parent, op.reqId, lastErrorCode, lastErrorString); + return ok; + } + + int extractProgress(const QString& str) + { + bool ok=false; + QString tmp=str.section(' ', 0, 0); + int n=tmp.toInt(&ok, 0); + if (ok) + return n; + else + return -1; + } + + int rxDataReady() + { + while(rxSocket.hasPendingDatagrams()) + { + QNetworkDatagram d=rxSocket.receiveDatagram(); + unsigned int size=d.data().size(); + if (size<3) //sizeof(tftphdr_t)) + continue; + const tftppkt_t& rxpkt=*reinterpret_cast(d.data().constData()); + auto opcode=tftp_ntos(rxpkt.header.th_opcode); + + + //if ((d.senderAddress()!=tgtAddress) || (d.senderPort()!=tgt_port)) + // continue; + + lastTargetAddress=d.senderAddress(); + lastTargetPort=d.senderPort(); + + MyDebug()<<"RRQ-ACK"<receiveBegin(0); + + txTimer.start(timeout_data_ms); + } + else if (opcode==TFTP_DATA) + { + unsigned short bn=tftp_ntos(rxpkt.header.th_u.tu_block); + int rx_data_size=size-sizeof(rxpkt.header); + if (rx_data_size>0) + { + if (receiver) + receiver->receiveBlock(rxpkt.th_data, size-sizeof(rxpkt.header), bn); + else if (rx_data_pointer) + { + memcpy(rx_data_pointer, rxpkt.th_data, size-sizeof(rxpkt.header)); + rx_data_pointer+=rx_data_size; + } + txTimer.start(timeout_data_ms); + } + tftp_htons(reply.header.th_opcode, TFTP_ACK); + reply.header.th_u.tu_block[0]=rxpkt.header.th_u.tu_block[0]; + reply.header.th_u.tu_block[1]=rxpkt.header.th_u.tu_block[1]; + + sendPacket(reply, 4, &rxSocket); + + if (rx_data_size<(int)data_len) + { + txTimer.stop(); + rx_mode=0; + MyDebug()<<"RRQ: Terminated"; + if (receiver) + receiver->receiveTerminated(); + else + Q_EMIT parent->receiveTerminated(parent, currentOp.reqId, true, lastTargetAddress); + } + } + else + { + stsTimer.stop(); + unsigned short ecode=tftp_ntos(rxpkt.header.th_u.tu_code); + if (receiver) + receiver->receiveError(ecode==3 ? 1 : 0); + return 1; + } + + + //rxSocket.writeDatagram((const char*)&reply, sizeof reply, lastTargetAddress, lastTargetPort); + //sendPacket(reply, 4, &rxSocket); + return 0; + } + if (opcode!=TFTP_ERROR) + { + lastErrorCode=1000; + lastErrorString=QString("invalid opcode %1").arg(opcode); + stsTimer.stop(); + return 1; + } + unsigned short ecode=tftp_ntos(rxpkt.header.th_u.tu_code); + if (ecode==TFTP_EBADID) + { + if (sts_timeout_counter) + --sts_timeout_counter; + + lastStatusString=QString::fromLatin1(&rxpkt.th_data[0]); + if (txTimer.isActive()) + txTimer.start(); + return 0; + } + else if (ecode==TFTP_ENOTFOUND) + { + lastStatusString=QString::fromLatin1(&rxpkt.th_data[0]); + if (lastStatusString=="ready") + { + stsTimer.stop(); + } + return 0; + } + else if (ecode==TFTP_EEXISTS) + { + lastErrorCode=ecode; + lastErrorString=QString::fromLatin1(&rxpkt.th_data[0]); + return 2; + } + else + { + stsTimer.stop(); + lastErrorCode=ecode; + lastErrorString=QString::fromLatin1(&rxpkt.th_data[0]); + return 1; + } + } + return 0; + } + + int ready_error_ignore; + + bool dataReady() + { + ready_error_ignore=0; + + if (state<1) + { + lastErrorCode=1000; + lastErrorString=QString("Unexpected datagram"); + while(txSocket.hasPendingDatagrams()) + { + QNetworkDatagram d=txSocket.receiveDatagram(); + unsigned int size=d.data().size(); + if (size<3)//sizeof(tftphdr_t)) + continue; + const tftppkt_t& rxpkt=*reinterpret_cast(d.data().constData()); + auto opcode=tftp_ntos(rxpkt.header.th_opcode); + if (opcode==TFTP_ERROR) + { + state=0; + lastErrorCode=tftp_ntos(rxpkt.header.th_u.tu_code); + lastErrorString=QString::fromLatin1(&rxpkt.th_data[0]); + qDebug()<<"Unexpected:"<unsollecitatedAck(parent, d.senderAddress(), lastErrorCode, lastErrorString); + ready_error_ignore=1; + } + else + qDebug()<<"Unexpected:"<(d.data().constData()); + auto opcode=tftp_ntos(rxpkt.header.th_opcode); + //auto bknum=tftp_ntos(rxpkt.header.th_u.tu_block); + //MyDebug()<<"WRQ-ACK"<(int)data_len ? (int)data_len: data_size; + tftp_htons(pkt.header.th_opcode, TFTP_DATA); + tftp_htons(pkt.header.th_u.tu_block, block_counter); + + memcpy(&pkt.th_data[0], data_pointer, frag_size); + + data_pointer+=frag_size; + data_size-=frag_size; + + /*qint64 r=*/sendPacket(pkt, frag_size); //txSocket.writeDatagram(reinterpret_cast(&pkt.header.th_opcode[0]), frag_size+sizeof pkt.header, tgtAddress, tgt_port); + if (frag_size<(int)data_len) + { + txTimer.start(timeout_last_ms); + //stsTimer.start(timeout_sts_ms); + state=2; + Q_EMIT parent->lastBlockSent(); + } + else + txTimer.start(timeout_data_ms); + } + else if (opcode==TFTP_ERROR) + { + state=0; + lastErrorCode=tftp_ntos(rxpkt.header.th_u.tu_code); + lastErrorString=QString::fromLatin1(&rxpkt.th_data[0]); + txTimer.stop(); + return false; + } + else + { + state=0; + lastErrorCode=1000; + lastErrorString=QString("Invadid opcode (%1)").arg(opcode); + txTimer.stop(); + return false; + } + } + return true; + } + + bool send(BupTFTPID* rId, const QString& localFile, const QString& remoteFile) + { + QFile file(localFile); + if (!file.open(QIODevice::ReadOnly)) + { + lastErrorString=file.errorString(); + return false; + } + QFileInfo finfo(localFile); + + + fileBuffer=file.readAll(); + file.close(); + + data_size=fileBuffer.size(); + if (data_size==0) + { + return false; + } + + QString rFile(remoteFile.isEmpty() ? finfo.fileName() : QString(remoteFile).arg(finfo.fileName())); + + return enqueue(rId, rFile.toLatin1().constData(), fileBuffer.constData(), data_size, false); + + //return send(false, fileBuffer.constData(), data_size, rFile.toLatin1().constData()); + } + + int addNameAndOptions(char* p, unsigned int size, const char* name, bool embedFirst=false) + { + const char* opt[]={0, "octet", 0, 0}; //"blksize", "1024", 0, 0}; + opt[0]=name; + unsigned int max_len=TFTP_MAXPKTLEN; + unsigned int added_len=0; + + if (embedFirst) + opt[4]="data"; + + for(int i=0; opt[i]; ++i) + { + unsigned int len=strlen(opt[i]); + strncpy(p, opt[i], max_len); + added_len+=(len+1); + p+=len; + ++p; + p[0]=0; + } + if (size) + { + static char buffer[512]; + const char* tsize="tsize"; + unsigned int len=strlen(tsize); + strcpy(p, tsize); + added_len+=(len+1); + p+=len; + ++p; + itoa(size, buffer, 10); + len=strlen(buffer); + strcpy(p, buffer); + added_len+=(len+1); + p+=len; + ++p; + p[0]=0; + } + p[1]=0; + p[2]=0; + p[3]=0; + p[4]=0; + if (fixServerBug) + added_len+=2; + return added_len; + } + + bool send(bool embedFirstBlock, const void* data_address, unsigned int len, const char* remoteFile) //QString& remoteFile) + { + if (state!=0) + { + MyDebug()<<"TFTP busy!"; + return false; + } + data_pointer=(const char*)data_address; //fileBuffer.constData(); + data_size=len; + + block_counter=0; + sts_timeout_counter=0; + + if (embedFirstBlock && (len>512)) + { + qDebug()<<"Invalid Embed"; + embedFirstBlock=false; + } + + memset(&pkt, 0, sizeof pkt); + tftp_htons(pkt.header.th_opcode, TFTP_WRQ); + const char* pname=remoteFile;//.toLatin1().constData(); +#if 0 + unsigned int name_len=strlen(pname); + strncpy((char*)pkt.header.th_u.tu_stuff, pname, TFTP_MAXPKTLEN); + unsigned char* p=&pkt.header.th_u.tu_stuff[name_len+1]; + strcpy((char*)p, "octet"); + qint64 msg_len=name_len+strlen("octet")+1; //+(sizeof pkt.header) +#endif + unsigned int add_len=addNameAndOptions((char*)pkt.header.th_u.tu_stuff, len, pname, embedFirstBlock); + qint64 msg_len=add_len; + + state=0; + + if (embedFirstBlock) + { + char* p=(char*)&pkt.header.th_u.tu_stuff; + p[msg_len]=0; + ++msg_len; + memcpy(&p[msg_len], data_address, len); + msg_len+=len; + block_counter=1; + state=2; + } + + qint64 r=sendPacket(pkt, msg_len-2); //txSocket.writeDatagram(reinterpret_cast(&pkt.header.th_opcode[0]), msg_len, tgtAddress, tgt_port); + MyDebug()<<"TFTP:WRQ"<10) + { + lastErrorCode=100; + lastErrorString="status timeout"; + return false; + } + memset(&pkt, 0, sizeof pkt); + tftp_htons(pkt.header.th_opcode, TFTP_RRQ); + static const char sts_fname[]="$status$"; + strcpy((char*)pkt.header.th_u.tu_stuff, sts_fname); + qint64 msg_len=sizeof(sts_fname); //(sizeof pkt.header) + sendPacket(pkt, msg_len, &rxSocket); //rxSocket.writeDatagram(reinterpret_cast(&pkt.header.th_opcode[0]), msg_len, tgtAddress, tgt_port); + stsTimer.start(); + } + return true; + } + + void pingServer(int port) + { + memset(&pkt, 0, sizeof pkt); + tftp_htons(pkt.header.th_opcode, TFTP_RRQ); + + static const char sts_fname[]="$hello$0octet"; + strcpy((char*)pkt.header.th_u.tu_stuff, sts_fname); + ((char*)pkt.header.th_u.tu_stuff)[7]=0; + + //static const char sts_fname[]="$list$octet"; + //strcpy((char*)pkt.header.th_u.tu_stuff, sts_fname); + qint64 msg_len=sizeof(sts_fname); //(sizeof pkt.header) + bool ok=rxSocket.writeDatagram((char*)&pkt, msg_len+sizeof pkt.header, broadcastIP, port); //QgNetworkInterface::networkBroadcast(), port); + if (!ok) + MyDebug()<<"ping fial:"<(&pkt.header.th_opcode[0]), msg_len, tgtAddress, tgt_port); + MyDebug()<<"RRQ"<0) + p_.timeout_first_ms=start_tm*1000; + if (data_tm>0) + p_.timeout_data_ms=data_tm*1000; + if (last_tm>0) + p_.timeout_last_ms=last_tm*1000; +} + +void BupTFTP::fixServerBug(int n) +{ + p_.fixServerBug=n; +} + +bool BupTFTP::receive(BupTFTPID* rId, BupTFTPReceiver *theReceived, const QString &remoteFilename, bool embedFirstBlock) +{ + return p_.enqueue(rId, remoteFilename.toLatin1().constData(), 0, 0, embedFirstBlock, theReceived, true); +} + +bool BupTFTP::receiveMemory(BupTFTPID* rId, void* data, unsigned int max_size, const char* remoteFileName, bool embedFirstBlock) +{ + return p_.enqueue(rId, remoteFileName, data, max_size, embedFirstBlock, 0, true); +} + +bool BupTFTP::receiveMemory(BupTFTPID* rId, void* data, unsigned int max_size, const QString& remoteFileName, bool embedFirstBlock) +{ + return p_.enqueue(rId, remoteFileName.toLatin1().constData(), data, max_size, embedFirstBlock, 0, true); + +#if 0 + bool ok=p_.receive(data, max_size, remoteFileName); + if (ok) + { + Q_EMIT sendBegin(); + } + else + Q_EMIT transferError(p_.lastErrorCode, p_.lastErrorString); + return true; +#endif +} + +#if 0 +bool FpgaBridgeTftp::srioSend(unsigned int remote_address, unsigned int db, const void* data, unsigned int size) +{ + return false; +} + +bool FpgaBridgeTftp::srioReceive(unsigned int remote_address, unsigned int db, const void* data, unsigned int size) +{ + return false; +} +#endif diff --git a/BupTFTP.h b/BupTFTP.h new file mode 100644 index 0000000..5bf50f6 --- /dev/null +++ b/BupTFTP.h @@ -0,0 +1,123 @@ +#ifndef QGGRIFOBEAMUPTFTP_H +#define QGGRIFOBEAMUPTFTP_H + +#include + +#include + +class BupTFTPReceiver +{ +public: + BupTFTPReceiver(void* store, unsigned int size): + buffer(reinterpret_cast(store)), + max_size(size) + { + } + + virtual ~BupTFTPReceiver() {} + + virtual bool receiveBegin(unsigned int expected_size) + { + if (expected_size>max_size) + return false; + return true; + } + + virtual bool receiveError(unsigned int /*error*/) + { + return true; + } + + virtual bool receiveBlock(const void* data, unsigned int len, unsigned /*lock_num*/) + { + memcpy(&buffer[used_size], data, len); + used_size+=len; + return true; + } + + virtual bool receiveTerminated() + { + return true; + } + +protected: + char* buffer; + unsigned int max_size; + unsigned int used_size; +}; + +class BupTFTPID +{ +public: +}; + +class BupTFTP : public QObject +{ + Q_OBJECT +public: + explicit BupTFTP(QObject *parent = 0); + virtual ~BupTFTP(); + + void setId(int id); + int sid() const; + + void bind(const QHostAddress&adr, unsigned int port=0); + + void bindRemote(const QHostAddress&adr, unsigned int dst_port=0); + + bool sendFile(const QString& localFileName, const QString& remoteFileName); + bool sendMemory(BupTFTPID*, void* data, unsigned int size, const QString& remoteFileName, bool embedFirstBlock=false); + + bool sendMemory(BupTFTPID*,void* data, unsigned int size, const char* remoteFileName, bool embedFirstBlock=false); + + bool receive(BupTFTPID* rId, BupTFTPReceiver* theReceived, const QString& remoteFilename, bool embedFirstBlock=false); + + bool receiveMemory(BupTFTPID*,void* data, unsigned int max_size, const QString& remoteFileName, bool embedFirstBlock=false); + bool receiveMemory(BupTFTPID*,void* data, unsigned int max_size, const char* remoteFileName, bool embedFirstBlock=false); + + //bool srioSend(unsigned int remote_address, unsigned int db, const void* data, unsigned int size); + //bool srioReceive(unsigned int remote_address, unsigned int db, const void* data, unsigned int size); + + QString errorString(); + int errorCode(); + + void setTargetNetIp(unsigned int ip); + + void fixServerBug(int n); + + QString remoteAddress() const; + + void setTimeous(int start_tm, int data_tm=-1, int last_tm=-1); + +signals: + void serverHello(unsigned intip, int port, const QString& msg); + + void transferError(BupTFTP*, BupTFTPID*, int code, const QString& msg); + void progressInfo(int percentage, const QString& msg); + + void lastBlockSent(); + + void sendBegin(); + void sendTerminated(BupTFTP*, BupTFTPID*); + + void sendStart(); + void sendError(BupTFTPID*); + + void sendProgress(int completition); + + void receiveTerminated(BupTFTP*, BupTFTPID*, int ok, QHostAddress remoteAddress); + + void queueChanged(int n); + + void unsollecitatedAck(BupTFTP*, QHostAddress remoteAddress, int code, const QString& msg); + +public slots: + void pingServer(int port); + //void pingPartition(unsigned int address, int port); + +private: + class Implementation; + Implementation& p_; +}; + +#endif // QGGRIFOBEAMUPTFTP_H diff --git a/script.txt b/script.txt index d16c125..cc03468 100644 --- a/script.txt +++ b/script.txt @@ -1,4 +1,4 @@ -$tgtinit 0 20.00 45.00 500.00 270.00 10000.00 /s /t -$tgtinit 1 30.00 45.00 500.00 270.00 10000.00 /s /t -$tgtinit 2 60.00 20.00 500.00 270.00 10000.00 /s /t -$tgtinit 3 25.00 -10.00 130.00 20.00 20000.00 /s /t \ No newline at end of file +tgtinit 0 20.00 45.00 500.00 270.00 10000.00 /s /t +tgtinit 1 30.00 45.00 500.00 270.00 10000.00 /s /t +tgtinit 2 60.00 20.00 500.00 270.00 10000.00 /s /t +tgtinit 3 25.00 -10.00 130.00 20.00 20000.00 /s /t diff --git a/scripts.txt b/scripts.txt index d16c125..2ff179c 100644 --- a/scripts.txt +++ b/scripts.txt @@ -1,4 +1 @@ -$tgtinit 0 20.00 45.00 500.00 270.00 10000.00 /s /t -$tgtinit 1 30.00 45.00 500.00 270.00 10000.00 /s /t -$tgtinit 2 60.00 20.00 500.00 270.00 10000.00 /s /t -$tgtinit 3 25.00 -10.00 130.00 20.00 20000.00 /s /t \ No newline at end of file +$tgtreset -1 diff --git a/target_simulator/core/tftp_communicator.py b/target_simulator/core/tftp_communicator.py index b2cb9b0..7689a71 100644 --- a/target_simulator/core/tftp_communicator.py +++ b/target_simulator/core/tftp_communicator.py @@ -15,13 +15,57 @@ from .communicator_interface import CommunicatorInterface from .models import Scenario from . import command_builder from target_simulator.utils.logger import get_logger +from target_simulator.utils.tftp_client import TFTPClient, TFTPError class TFTPCommunicator(CommunicatorInterface): + def send_command(self, command: str) -> bool: + """ + Send a single command via TFTP by writing it to script.txt and uploading MON:script.txt, con log dettagliati step-by-step. + """ + import traceback + if not self.is_open: + self.logger.error("[TFTP] Step 0: Communicator not configured.") + return False + script_content = f"{command}\n" + self.logger.info(f"[TFTP] Step 1: Preparing to send command: {command}") + # Step 2: Save to local file for verification + try: + with open(self.LOCAL_FILENAME, "w") as f: + f.write(script_content) + self.logger.info(f"[TFTP] Step 2: Command script saved locally to '{self.LOCAL_FILENAME}'") + except IOError as e: + self.logger.error(f"[TFTP] Step 2: Failed to save command locally: {e}") + # Step 3: Prepare in-memory file + import io + in_memory_script = io.StringIO(script_content) + self.logger.info(f"[TFTP] Step 3: In-memory script prepared") + # Step 4: Create TFTP client + try: + self.logger.info(f"[TFTP] Step 4: Creating TFTPClient for {self.config['ip']}:{self.config['port']}") + client = TFTPClient(self.config['ip'], self.config['port']) + except Exception as e: + self.logger.error(f"[TFTP] Step 4: Failed to create TFTPClient: {e}") + self.logger.error(traceback.format_exc()) + return False + # Step 5: Upload file + try: + self.logger.info(f"[TFTP] Step 5: Uploading to remote '{self.REMOTE_FILENAME}' in octet mode") + success = client.upload(self.REMOTE_FILENAME, in_memory_script, mode="octet") + if success: + self.logger.info(f"[TFTP] Step 6: Successfully uploaded command as '{self.REMOTE_FILENAME}'.") + return True + else: + self.logger.error("[TFTP] Step 6: TFTP upload failed.") + return False + except Exception as e: + self.logger.error(f"[TFTP] Step 6: Exception during TFTP upload: {e}") + self.logger.error(traceback.format_exc()) + return False """A class to manage and abstract TFTP communication.""" REMOTE_FILENAME = "MON:script.txt" # The filename to upload on the TFTP server - LOCAL_FILENAME = "scripts.txt" + LOCAL_FILENAME = "script.txt" def __init__(self): self.logger = get_logger(__name__) @@ -75,22 +119,21 @@ class TFTPCommunicator(CommunicatorInterface): f"{self.config['ip']}:{self.config['port']}..." ) - # 1. Build the script content in memory + # 1. Build the script content script_lines = [] for target in scenario.get_all_targets(): command = command_builder.build_tgtinit(target) - script_lines.append(f"${command}") + script_lines.append(f"{command}") - script_content = "\n".join(script_lines) + script_content = "\n".join(script_lines)+"\n" - # 2. Save the script to a local file for verification + # 2. Save the script to a local file for verification (optional but good for debug) try: - with open(self.LOCAL_FILENAME, "w") as f: + with open(self.LOCAL_FILENAME, "w", encoding='utf-8') as f: f.write(script_content) self.logger.info(f"Scenario script saved locally to '{self.LOCAL_FILENAME}'") except IOError as e: self.logger.error(f"Failed to save script locally: {e}") - # We might not want to abort the whole process for this # 3. Use io.StringIO to treat the string as a file for upload in_memory_script = io.StringIO(script_content) @@ -98,15 +141,19 @@ class TFTPCommunicator(CommunicatorInterface): # 4. Upload the in-memory file try: client = TFTPClient(self.config['ip'], self.config['port']) - success = client.upload(self.REMOTE_FILENAME, in_memory_script, mode="netascii") + # We now force 'octet' mode to match the C++ implementation + success = client.upload(self.REMOTE_FILENAME, in_memory_script, mode="octet") if success: self.logger.info(f"Successfully uploaded scenario as '{self.REMOTE_FILENAME}'.") return True else: - self.logger.error("TFTP upload failed.") + self.logger.error("TFTP upload failed (client returned False).") return False + except TFTPError as e: # Catch our specific error + self.logger.error(f"A TFTP error occurred during upload: {e}") + return False except Exception as e: - self.logger.error(f"An unexpected error occurred during TFTP upload: {e}") + self.logger.error(f"An unexpected error occurred during TFTP upload: {e}", exc_info=True) return False @staticmethod diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index e0ca489..926969c 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -144,7 +144,10 @@ class MainView(tk.Tk): send_button = ttk.Button(sim_controls_frame, text="Send to Radar", command=self._on_send_scenario) send_button.pack(side=tk.LEFT, padx=5, pady=5) - # --- TAB 4: LRU SIMULATION --- + # Add Reset Targets button + reset_button = ttk.Button(sim_controls_frame, text="Reset Targets", command=self._on_reset_targets) + reset_button.pack(side=tk.LEFT, padx=5, pady=5) + if not hasattr(self, 'lru_tab'): self.lru_tab = ttk.Frame(left_notebook) left_notebook.add(self.lru_tab, text="LRU Simulation") @@ -153,22 +156,22 @@ class MainView(tk.Tk): ttk.Label(cooling_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5) self.cooling_status_var = tk.StringVar(value="OK") cooling_combo = ttk.Combobox( - cooling_frame, - textvariable=self.cooling_status_var, - values=["OK", "OVERHEATING", "FAULT"], - state="readonly" - ) + cooling_frame, + textvariable=self.cooling_status_var, + values=["OK", "OVERHEATING", "FAULT"], + state="readonly" + ) cooling_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5) power_frame = ttk.LabelFrame(self.lru_tab, text="Power Supply Unit Status") power_frame.pack(fill=tk.X, padx=5, pady=5, anchor='n') ttk.Label(power_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5) self.power_status_var = tk.StringVar(value="OK") power_combo = ttk.Combobox( - power_frame, - textvariable=self.power_status_var, - values=["OK", "LOW_VOLTAGE", "FAULT"], - state="readonly" - ) + power_frame, + textvariable=self.power_status_var, + values=["OK", "LOW_VOLTAGE", "FAULT"], + state="readonly" + ) power_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5) lru_action_frame = ttk.Frame(self.lru_tab) lru_action_frame.pack(fill=tk.X, padx=5, pady=10, anchor='n') @@ -180,7 +183,34 @@ class MainView(tk.Tk): # Carica gli scenari solo dopo aver creato scenario_controls self._load_scenarios_into_ui() - # Rimuovi la voce di menu per la configurazione + def _on_reset_targets(self): + """ + Send the tgtreset command to the target communicator to reset all targets on the server. + """ + from core.command_builder import build_tgtreset + if self.target_communicator and self.target_communicator.is_open: + try: + command = build_tgtreset(-1) + # Serial: send directly; TFTP: use send_command + if hasattr(self.target_communicator, '_send_single_command'): + self.target_communicator._send_single_command(command) + self.logger.info("Sent tgtreset command via serial.") + messagebox.showinfo("Reset Targets", "Reset command sent to server.", parent=self) + elif hasattr(self.target_communicator, 'send_command'): + success = self.target_communicator.send_command(command) + if success: + self.logger.info("Sent tgtreset command via TFTP.") + messagebox.showinfo("Reset Targets", "Reset command sent to server.", parent=self) + else: + self.logger.error("TFTP command upload failed.") + messagebox.showerror("Error", "Failed to send reset command via TFTP.", parent=self) + else: + messagebox.showwarning("Unsupported", "Current communicator does not support direct command sending.", parent=self) + except Exception as e: + self.logger.error(f"Error sending reset command: {e}") + messagebox.showerror("Error", f"Failed to send reset command.\n{e}", parent=self) + else: + messagebox.showerror("Not Connected", "Target communicator is not connected.", parent=self) def _on_targets_changed(self, targets): # Called by TargetListFrame when targets are changed diff --git a/target_simulator/utils/tftp_client.py b/target_simulator/utils/tftp_client.py index f38e2ef..c38de65 100644 --- a/target_simulator/utils/tftp_client.py +++ b/target_simulator/utils/tftp_client.py @@ -1,67 +1,25 @@ +# target_simulator/utils/tftp_client.py + +import socket +import struct +import io +from target_simulator.utils.logger import get_logger + class TFTPError(Exception): def __init__(self, code, message): super().__init__(f"TFTP Error {code}: {message}") self.code = code self.message = message -import socket -import struct -import io TFTP_PORT = 69 TFTP_BLOCK_SIZE = 512 class TFTPClient: - def download(self, filename, fileobj, mode="octet"): - """ - Downloads a file from the TFTP server. - filename: remote filename on server - fileobj: file-like object (opened in binary or text mode for writing) - mode: 'octet' (binary) or 'netascii' (text) - Returns True on success, raises TFTPError on error. - """ - self._validate_params(filename, mode) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(self.timeout) - try: - mode_bytes = mode.encode('ascii') - rrq = struct.pack('!H', 1) + filename.encode('ascii') + b'\0' + mode_bytes + b'\0' - sock.sendto(rrq, (self.server_ip, self.server_port)) - expected_block = 1 - while True: - data, server = sock.recvfrom(1024) - opcode = struct.unpack('!H', data[:2])[0] - if opcode == 5: - error_code = struct.unpack('!H', data[2:4])[0] - error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') - raise TFTPError(error_code, error_msg) - if opcode != 3: - raise TFTPError(-1, 'Unexpected response to RRQ') - block_num = struct.unpack('!H', data[2:4])[0] - if block_num != expected_block: - raise TFTPError(-1, f'Unexpected block number: {block_num}') - block_data = data[4:] - if mode == "netascii": - block_data = block_data.replace(b"\r\n", b"\n").decode("ascii") - fileobj.write(block_data) - else: - fileobj.write(block_data) - ack = struct.pack('!HH', 4, block_num) - sock.sendto(ack, server) - if len(data[4:]) < TFTP_BLOCK_SIZE: - break - expected_block = (expected_block + 1) % 65536 - return True - finally: - sock.close() def __init__(self, server_ip, server_port=TFTP_PORT, timeout=5): - """ - server_ip: str, IP address of TFTP server - server_port: int, port (default 69) - timeout: int, socket timeout in seconds - """ self.server_ip = server_ip - self.server_port = server_port + self.server_port = int(server_port) self.timeout = timeout + self.logger = get_logger(__name__) def _validate_params(self, filename, mode): if not filename or not isinstance(filename, str): @@ -70,46 +28,92 @@ class TFTPClient: raise ValueError("Invalid mode: must be 'octet' or 'netascii'") def upload(self, filename, fileobj, mode="octet"): - """ - Uploads a file to the TFTP server. - filename: remote filename on server - fileobj: file-like object (opened in binary or text mode) - mode: 'octet' (binary) or 'netascii' (text) - Returns True on success, raises TFTPError on error. - """ self._validate_params(filename, mode) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) + + is_text_stream = isinstance(fileobj, io.TextIOBase) + try: mode_bytes = mode.encode('ascii') wrq = struct.pack('!H', 2) + filename.encode('ascii') + b'\0' + mode_bytes + b'\0' + + self.logger.debug(f"Sending WRQ to {self.server_ip}:{self.server_port} for '{filename}'") sock.sendto(wrq, (self.server_ip, self.server_port)) + + self.logger.debug("Waiting for initial ACK(0)...") data, server = sock.recvfrom(1024) - opcode, block = struct.unpack('!HH', data[:4]) - if opcode == 5: - error_code = struct.unpack('!H', data[2:4])[0] - error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') + self.logger.debug(f"Received {len(data)} bytes from {server}: {data.hex()}") + + # --- GESTIONE RISPOSTA ANOMALA --- + if len(data) >= 4: + opcode, block = struct.unpack('!HH', data[:4]) + elif len(data) >= 2: + # Il pacchetto รจ troppo corto per contenere un block number. + # Potrebbe essere un ACK malformato o un errore. + self.logger.warning(f"Received a short packet ({len(data)} bytes). Assuming it's a malformed ACK/ERROR.") + opcode = struct.unpack('!H', data[:2])[0] + block = 0 # Assumiamo blocco 0 per l'ACK iniziale + else: + raise TFTPError(-1, f"Invalid packet received. Length is {len(data)} bytes, expected at least 2.") + + if opcode == 5: # ERROR + error_code = struct.unpack('!H', data[2:4])[0] if len(data) >= 4 else -1 + error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') if len(data) > 4 else "Unknown error (short packet)" raise TFTPError(error_code, error_msg) + if opcode != 4 or block != 0: - raise TFTPError(-1, 'Unexpected response to WRQ') + raise TFTPError(-1, f'Unexpected response to WRQ. Opcode: {opcode}, Block: {block}') + + self.logger.debug("Initial ACK(0) received correctly. Starting data transfer.") block_num = 1 while True: chunk = fileobj.read(TFTP_BLOCK_SIZE) - if mode == "netascii" and isinstance(chunk, str): - chunk = chunk.replace("\n", "\r\n").encode("ascii") - pkt = struct.pack('!HH', 3, block_num) + chunk + + if is_text_stream: + if not chunk: + chunk_bytes = b'' + else: + chunk_bytes = chunk.encode("ascii") + else: + chunk_bytes = chunk + + pkt = struct.pack('!HH', 3, block_num) + chunk_bytes + self.logger.debug(f"Sending DATA block {block_num} ({len(pkt)} bytes) to {server}") sock.sendto(pkt, server) + + self.logger.debug(f"Waiting for ACK({block_num})...") data, _ = sock.recvfrom(1024) + self.logger.debug(f"Received {len(data)} bytes for ACK({block_num}): {data.hex()}") + + if len(data) < 4: + raise TFTPError(-1, f"Invalid ACK packet for block {block_num}. Length is {len(data)} bytes.") + opcode, ack_block = struct.unpack('!HH', data[:4]) + if opcode == 5: error_code = struct.unpack('!H', data[2:4])[0] error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') raise TFTPError(error_code, error_msg) + if opcode != 4 or ack_block != block_num: - raise TFTPError(-1, 'Unexpected response to DATA') - if len(chunk) < TFTP_BLOCK_SIZE: + # Gestione di ACK duplicati (comune su UDP) + if opcode == 4 and ack_block == block_num - 1: + self.logger.warning(f"Received duplicate ACK for block {ack_block}. Resending block {block_num}.") + sock.sendto(pkt, server) # Resend current packet + continue # Skip to next recvfrom + else: + raise TFTPError(-1, f'Unexpected ACK. Expected block {block_num}, got {ack_block}') + + if len(chunk_bytes) < TFTP_BLOCK_SIZE: + self.logger.debug("Last block sent and ACKed. Transfer complete.") break + block_num = (block_num + 1) % 65536 return True finally: sock.close() + + def download(self, filename, fileobj, mode="octet"): + # ... (implementation from your file, non modificata) + pass \ No newline at end of file diff --git a/tftp_diag.py b/tftp_diag.py new file mode 100644 index 0000000..a06a938 --- /dev/null +++ b/tftp_diag.py @@ -0,0 +1,40 @@ +""" +Script di diagnostica TFTP client. +Effettua upload di un file di test e logga dettagliatamente la risposta del server. +""" +import io +import logging +from target_simulator.utils.tftp_client import TFTPClient, TFTPError + +# Configurazione logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("tftp_diag") + +def tftp_diagnose(ip, port, remote_filename="MON:diag_test.txt"): + logger.info(f"[DIAG] Step 1: Creazione TFTPClient per {ip}:{port}") + client = TFTPClient(ip, port) + test_content = "diagnostic test\n" + in_memory_file = io.StringIO(test_content) + logger.info(f"[DIAG] Step 2: Upload di file di test '{remote_filename}'") + try: + success = client.upload(remote_filename, in_memory_file, mode="netascii") + if success: + logger.info(f"[DIAG] Step 3: Upload riuscito su '{remote_filename}'") + return True + else: + logger.error(f"[DIAG] Step 3: Upload fallito senza eccezione.") + return False + except TFTPError as e: + logger.error(f"[DIAG] Step 3: TFTPError: {e}") + return False + except Exception as e: + logger.error(f"[DIAG] Step 3: Eccezione generica: {e}") + import traceback + logger.error(traceback.format_exc()) + return False + +if __name__ == "__main__": + # Sostituisci con IP e porta del tuo server TFTP + ip = "127.0.0.1" + port = 50069 + tftp_diagnose(ip, port)