fbpx Qt-based TFTP class | University of Kentucky College of Engineering

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; naddItem(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);
}