August 17, 2013
Qt-based TFTP class
#define MAXPACKETSIZE 512 #include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QTFTPWidget : public QWidget { Q_OBJECT public: explicit QTFTPWidget(QString localAddressString = QString(), int localPort = -1, QString remoteAddressString = QString(), int remotePort = -1, QWidget *parent = 0); ~QTFTPWidget(); bool putByteArray(QString filename, QByteArray transmittingFile); bool getByteArray(QString filename, QByteArray *requestedFile); inline QString errorString() { return(lastErrorString); } QString remoteAddressString(); QString localAddressString(); int remotePortNumber(); int localPortNumber(); public slots: inline void onSetLocalAddress(QString address) { localAddressSuppliedByUser=address; } inline void onSetLocalPort(int port) { localPortSuppliedByUser=port; } inline void onSetRemoteAddress(QString address) { remoteAddressSuppliedByUser=address; } inline void onSetRemotePort(int port) { remotePortSuppliedByUser=port; } private: QString localAddressSuppliedByUser, remoteAddressSuppliedByUser; int localPortSuppliedByUser, remotePortSuppliedByUser; QString lastErrorString; QUdpSocket *socket; QComboBox *localAddressComboBox; QSpinBox *localPortSpinBox; QLineEdit *remoteAddressLineEdit; QSpinBox *remotePortSpinBox; bool bindSocket(); QByteArray getFilePacket(QString filename); QByteArray putFilePacket(QString filename); }; QTFTPWidget::QTFTPWidget(QString localAddressString, int localPort, QString remoteAddressString, int remotePort, QWidget *parent) : QWidget(parent), localAddressSuppliedByUser(localAddressString), localPortSuppliedByUser(localPort), remoteAddressSuppliedByUser(remoteAddressString), remotePortSuppliedByUser(remotePort) { this->setWindowTitle(QString("TFTP Widget")); this->setLayout(new QVBoxLayout()); this->layout()->setSpacing(1); QWidget *widget=new QWidget(); widget->setLayout(new QHBoxLayout()); widget->layout()->setContentsMargins(0, 0, 0, 0); localAddressComboBox = new QComboBox(); localAddressComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); if (localAddressSuppliedByUser.isEmpty()){ QList hstList = QNetworkInterface::allAddresses(); for (int n=0; n addItem(string); } } } else { localAddressComboBox->addItem(localAddressSuppliedByUser); localAddressComboBox->setDisabled(true); } localPortSpinBox = new QSpinBox(); localPortSpinBox->setFixedWidth(100); localPortSpinBox->setMaximum(65535); if (localPortSuppliedByUser > 0){ localPortSpinBox->setMinimum(0); localPortSpinBox->setValue(localPortSuppliedByUser); localPortSpinBox->setDisabled(true); } else { localPortSpinBox->setMinimum(1024); localPortSpinBox->setValue(7755); } QLabel *label=new QLabel(QString("Local Address:")); label->setFixedWidth(120); widget->layout()->addWidget(label); widget->layout()->addWidget(localAddressComboBox); widget->layout()->addWidget(localPortSpinBox); this->layout()->addWidget(widget); widget=new QWidget(); widget->setLayout(new QHBoxLayout()); widget->layout()->setContentsMargins(0, 0, 0, 0); remoteAddressLineEdit = new QLineEdit(remoteAddressSuppliedByUser); remoteAddressLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); if (!remoteAddressSuppliedByUser.isEmpty()){ remoteAddressLineEdit->setReadOnly(true); } remotePortSpinBox = new QSpinBox(); remotePortSpinBox->setFixedWidth(100); remotePortSpinBox->setMaximum(65535); if (remotePortSuppliedByUser > 0){ remotePortSpinBox->setMinimum(0); remotePortSpinBox->setValue(remotePortSuppliedByUser); remotePortSpinBox->setDisabled(true); } else { remotePortSpinBox->setMinimum(1024); remotePortSpinBox->setValue(7755); } label=new QLabel(QString("Remote Address:")); label->setFixedWidth(120); widget->layout()->addWidget(label); widget->layout()->addWidget(remoteAddressLineEdit); widget->layout()->addWidget(remotePortSpinBox); this->layout()->addWidget(widget); widget=new QWidget(); widget->setLayout(new QHBoxLayout()); widget->layout()->setContentsMargins(0, 0, 0, 0); this->layout()->addWidget(widget); socket=NULL; } QTFTPWidget::~QTFTPWidget() { if (socket) delete socket; } bool QTFTPWidget::bindSocket() { // SEE IF WE ALREADY HAVE A BOUND SOCKET // AND IF SO, WE NEED TO DELETE IT if (socket != NULL) { delete socket; } // CREATE A NEW SOCKET socket=new QUdpSocket(); // AND SEE IF WE CAN BIND IT TO A LOCAL IP ADDRESS AND PORT if (localAddressSuppliedByUser.isEmpty()){ return(socket->bind(QHostAddress(localAddressComboBox->currentText()), localPortSpinBox->value())); } else { return(socket->bind(QHostAddress(localAddressSuppliedByUser), localPortSuppliedByUser)); } } QString QTFTPWidget::remoteAddressString() { return(remoteAddressLineEdit->text()); } QString QTFTPWidget::localAddressString() { return(localAddressComboBox->currentText()); } int QTFTPWidget::remotePortNumber() { return(remotePortSpinBox->value()); } int QTFTPWidget::localPortNumber() { return(localPortSpinBox->value()); } bool QTFTPWidget::putByteArray(QString filename, QByteArray transmittingFile) { // BIND OUR LOCAL SOCKET TO AN IP ADDRESS AND PORT if (!bindSocket()) { lastErrorString = socket->errorString(); return(false); } // MAKE A LOCAL COPY OF THE REMOTE HOST ADDRESS AND PORT NUMBER QHostAddress hostAddress(QHostAddress(remoteAddressLineEdit->text())); int portNumber = remotePortSpinBox->value(); // CREATE REQUEST PACKET AND SEND TO HOST // WAIT UNTIL MESSAGE HAS BEEN SENT, QUIT IF TIMEOUT IS REACHED QByteArray reqPacket=putFilePacket(filename); if (socket->writeDatagram(reqPacket, hostAddress, portNumber) != reqPacket.length()){ lastErrorString = QString("did not send packet to host :( %1").arg(socket->errorString()); return(false); } // CREATE PACKET COUNTERS TO KEEP TRACK OF MESSAGES unsigned short incomingPacketNumber=0; unsigned short outgoingPacketNumber=0; // NOW WAIT HERE FOR INCOMING DATA bool messageCompleteFlag=false; while (1){ // WAIT FOR AN INCOMING PACKET if (socket->hasPendingDatagrams() || socket->waitForReadyRead(10000)){ // ITERATE HERE AS LONG AS THERE IS ATLEAST A // PACKET HEADER'S WORTH OF DATA TO READ QByteArray incomingDatagram; incomingDatagram.resize(socket->pendingDatagramSize()); socket->readDatagram(incomingDatagram.data(), incomingDatagram.length()); // MAKE SURE FIRST BYTE IS 0 char *buffer=incomingDatagram.data(); char zeroByte=buffer[0]; if (zeroByte != 0x00) { lastErrorString = QString("Incoming packet has invalid first byte (%1).").arg((int)zeroByte); return(false); } // READ UNSIGNED SHORT PACKET NUMBER USING LITTLE ENDIAN FORMAT // FOR THE INCOMING UNSIGNED SHORT VALUE BUT BIG ENDIAN FOR THE // INCOMING DATA PACKET unsigned short incomingMessageCounter; *((char*)&incomingMessageCounter+1)=buffer[2]; *((char*)&incomingMessageCounter+0)=buffer[3]; // CHECK INCOMING MESSAGE ID NUMBER AND MAKE SURE IT MATCHES // WHAT WE ARE EXPECTING, OTHERWISE WE'VE LOST OR GAINED A PACKET if (incomingMessageCounter == incomingPacketNumber){ incomingPacketNumber++; } else { lastErrorString = QString("error on incoming packet number %1 vs expected %2").arg(incomingMessageCounter).arg(incomingPacketNumber); return(false); } // CHECK THE OPCODE FOR ANY ERROR CONDITIONS char opCode=buffer[1]; if (opCode != 0x04) { /* ack packet should have code 4 and should be ack+1 the packet we just sent */ lastErrorString = QString("Incoming packet returned invalid operation code (%1).").arg((int)opCode); return(false); } else { // SEE IF WE NEED TO SEND ANYMORE DATA PACKETS BY CHECKING END OF MESSAGE FLAG if (messageCompleteFlag) break; // SEND NEXT DATA PACKET TO HOST QByteArray transmitByteArray; transmitByteArray.append((char)0x00); transmitByteArray.append((char)0x03); // send data opcode transmitByteArray.append(*((char*)&outgoingPacketNumber+1)); transmitByteArray.append(*((char*)&outgoingPacketNumber)); // APPEND DATA THAT WE WANT TO SEND int numBytesAlreadySent=outgoingPacketNumber*MAXPACKETSIZE; int bytesLeftToSend=transmittingFile.length()-numBytesAlreadySent; if (bytesLeftToSend < MAXPACKETSIZE){ messageCompleteFlag=true; if (bytesLeftToSend > 0){ transmitByteArray.append((transmittingFile.data()+numBytesAlreadySent), bytesLeftToSend); } } else { transmitByteArray.append((transmittingFile.data()+numBytesAlreadySent), MAXPACKETSIZE); } // SEND THE PACKET AND MAKE SURE IT GETS SENT if (socket->writeDatagram(transmitByteArray, hostAddress, portNumber) != transmitByteArray.length()){ lastErrorString = QString("did not send data packet to host :( %1").arg(socket->errorString()); return(false); } // NOW THAT WE'VE SENT AN ACK SIGNAL, INCREMENT SENT MESSAGE COUNTER outgoingPacketNumber++; } } else { lastErrorString = QString("No message received from host :( %1").arg(socket->errorString()); return(false); } } lastErrorString = QString("no error"); return(true); } bool QTFTPWidget::getByteArray(QString filename, QByteArray *requestedFile) { // BIND OUR LOCAL SOCKET TO AN IP ADDRESS AND PORT if (!bindSocket()) { lastErrorString = socket->errorString(); return(false); } // MAKE A LOCAL COPY OF THE REMOTE HOST ADDRESS AND PORT NUMBER QHostAddress hostAddress(QHostAddress(remoteAddressLineEdit->text())); int portNumber = remotePortSpinBox->value(); // CLEAN OUT ANY INCOMING PACKETS while (socket->hasPendingDatagrams()){ QByteArray byteArray; byteArray.resize(socket->pendingDatagramSize()); socket->readDatagram(byteArray.data(), byteArray.length()); } // CREATE REQUEST PACKET AND SEND TO HOST // WAIT UNTIL MESSAGE HAS BEEN SENT, QUIT IF TIMEOUT IS REACHED QByteArray reqPacket=getFilePacket(filename); if (socket->writeDatagram(reqPacket, hostAddress, portNumber) != reqPacket.length()){ lastErrorString = QString("did not send packet to host :( %1").arg(socket->errorString()); return(false); } // CREATE PACKET COUNTERS TO KEEP TRACK OF MESSAGES unsigned short incomingPacketNumber=1; unsigned short outgoingPacketNumber=1; // NOW WAIT HERE FOR INCOMING DATA bool messageCompleteFlag=false; while (!messageCompleteFlag){ // WAIT FOR AN INCOMING PACKET if (socket->hasPendingDatagrams() || socket->waitForReadyRead(10000)){ // ITERATE HERE AS LONG AS THERE IS ATLEAST A // PACKET HEADER'S WORTH OF DATA TO READ QByteArray incomingDatagram; incomingDatagram.resize(socket->pendingDatagramSize()); socket->readDatagram(incomingDatagram.data(), incomingDatagram.length()); // MAKE SURE FIRST BYTE IS 0 char *buffer=incomingDatagram.data(); char zeroByte=buffer[0]; if (zeroByte != 0x00) { lastErrorString = QString("Incoming packet has invalid first byte (%1).").arg((int)zeroByte); return(false); } // READ UNSIGNED SHORT PACKET NUMBER USING LITTLE ENDIAN FORMAT // FOR THE INCOMING UNSIGNED SHORT VALUE BUT BIG ENDIAN FOR THE // INCOMING DATA PACKET unsigned short incomingMessageCounter; *((char*)&incomingMessageCounter+1)=buffer[2]; *((char*)&incomingMessageCounter+0)=buffer[3]; // CHECK INCOMING MESSAGE ID NUMBER AND MAKE SURE IT MATCHES // WHAT WE ARE EXPECTING, OTHERWISE WE'VE LOST OR GAINED A PACKET if (incomingMessageCounter == incomingPacketNumber){ incomingPacketNumber++; } else { lastErrorString = QString("error on incoming packet number %1 vs expected %2").arg(incomingMessageCounter).arg(incomingPacketNumber); return(false); } // COPY THE INCOMING FILE DATA QByteArray incomingByteArray(&buffer[4], incomingDatagram.length()-4); // SEE IF WE RECEIVED A COMPLETE 512 BYTES AND IF SO, // THEN THERE IS MORE INFORMATION ON THE WAY // OTHERWISE, WE'VE REACHED THE END OF THE RECEIVING FILE if (incomingByteArray.length() < MAXPACKETSIZE){ messageCompleteFlag=true; } // APPEND THE INCOMING DATA TO OUR COMPLETE FILE requestedFile->append(incomingByteArray); // CHECK THE OPCODE FOR ANY ERROR CONDITIONS char opCode=buffer[1]; if (opCode != 0x03) { /* ack packet should have code 3 (data) and should be ack+1 the packet we just sent */ lastErrorString = QString("Incoming packet returned invalid operation code (%1).").arg((int)opCode); return(false); } else { // SEND PACKET ACKNOWLEDGEMENT BACK TO HOST REFLECTING THE INCOMING PACKET NUMBER QByteArray ackByteArray; ackByteArray.append((char)0x00); ackByteArray.append((char)0x04); ackByteArray.append(*((char*)&incomingMessageCounter+1)); ackByteArray.append(*((char*)&incomingMessageCounter)); // SEND THE PACKET AND MAKE SURE IT GETS SENT if (socket->writeDatagram(ackByteArray, hostAddress, portNumber) != ackByteArray.length()){ lastErrorString = QString("did not send ack packet to host :( %1").arg(socket->errorString()); return(false); } // NOW THAT WE'VE SENT AN ACK SIGNAL, INCREMENT SENT MESSAGE COUNTER outgoingPacketNumber++; } } else { lastErrorString = QString("No message received from host :( %1").arg(socket->errorString()); return(false); } } return(true); } QByteArray QTFTPWidget::getFilePacket(QString filename) { QByteArray byteArray(filename.toLatin1()); byteArray.prepend((char)0x01); // OPCODE byteArray.prepend((char)0x00); byteArray.append((char)0x00); byteArray.append(QString("octet").toLatin1()); // MODE byteArray.append((char)0x00); return(byteArray); } QByteArray QTFTPWidget::putFilePacket(QString filename) { QByteArray byteArray((char)0x00); byteArray.append((char)0x02); // OPCODE byteArray.append(filename.toLatin1()); byteArray.append((char)0x00); byteArray.append(QString("octet").toLatin1()); // MODE byteArray.append((char)0x00); return(byteArray); } ();>