├── msi ├── .gitignore └── assets │ ├── TwampClient.ico │ ├── TwampResponder.ico │ ├── Twamp_Install_Top.jpg │ └── Twamp_Install_Background.jpg ├── .gitignore ├── client ├── qml │ ├── jbQuick │ │ ├── ._qmldir │ │ ├── qmldir │ │ └── QChart.qml │ ├── images │ │ └── slider_handle.png │ ├── qml.qrc │ ├── LogDetailItem.qml │ ├── +android │ │ └── CustomSlider.qml │ ├── CustomSlider.qml │ └── client.qml ├── twamp.cpp ├── chartmodel.cpp ├── main.cpp ├── chartmodel.h ├── client.pro ├── twamp_client.h ├── twamp_test_worker.h ├── twamp_client.cpp ├── twamp_test_worker.cpp └── log_model_data.h ├── responder ├── qml │ ├── qml.qrc │ ├── LogDetailItem.qml │ └── responder.qml ├── main.cpp ├── responder.pro ├── twamp_responder.h ├── twamp_responder_worker.h ├── twamp_responder.cpp ├── log_model_data.h └── twamp_responder_worker.cpp ├── twamp-gui.pro ├── common ├── common.pro ├── twamp_common.cpp └── twamp_common.h ├── installer ├── packages │ └── com.demirten.twamp │ │ └── meta │ │ ├── package.xml │ │ ├── installscript.qs │ │ └── LICENSE └── config │ └── config.xml ├── README.md └── LICENSE /msi/.gitignore: -------------------------------------------------------------------------------- 1 | *.msi 2 | *.wixpdb 3 | *.txt 4 | *.bat 5 | wixobj/* 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | *.user 4 | deployment.pri 5 | Makefile 6 | build 7 | -------------------------------------------------------------------------------- /client/qml/jbQuick/._qmldir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/client/qml/jbQuick/._qmldir -------------------------------------------------------------------------------- /msi/assets/TwampClient.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/msi/assets/TwampClient.ico -------------------------------------------------------------------------------- /msi/assets/TwampResponder.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/msi/assets/TwampResponder.ico -------------------------------------------------------------------------------- /client/twamp.cpp: -------------------------------------------------------------------------------- 1 | #include "twamp.h" 2 | 3 | Twamp::Twamp(QObject *parent) : 4 | QObject(parent) 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /msi/assets/Twamp_Install_Top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/msi/assets/Twamp_Install_Top.jpg -------------------------------------------------------------------------------- /client/qml/images/slider_handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/client/qml/images/slider_handle.png -------------------------------------------------------------------------------- /msi/assets/Twamp_Install_Background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demirten/twamp-gui/HEAD/msi/assets/Twamp_Install_Background.jpg -------------------------------------------------------------------------------- /responder/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | responder.qml 4 | LogDetailItem.qml 5 | 6 | 7 | -------------------------------------------------------------------------------- /twamp-gui.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS = common client responder 4 | 5 | client.depends = common 6 | responder.depends = common 7 | 8 | # build the project sequentially as listed in SUBDIRS ! 9 | CONFIG += ordered 10 | -------------------------------------------------------------------------------- /client/chartmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "chartmodel.h" 2 | 3 | ChartModel::ChartModel(QObject *parent) : QObject(parent) 4 | { 5 | QTimer::singleShot(1000, this, SLOT(updateDataset1())); 6 | } 7 | 8 | QList ChartModel::dataset1() 9 | { 10 | return mDataset1; 11 | } 12 | 13 | void ChartModel::updateDataset1() 14 | { 15 | mDataset1.append(qrand() % 100); 16 | QTimer::singleShot(1000, this, SLOT(updateDataset1())); 17 | } 18 | -------------------------------------------------------------------------------- /client/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | client.qml 4 | LogDetailItem.qml 5 | jbQuick/QChart.js 6 | jbQuick/QChart.qml 7 | jbQuick/qmldir 8 | CustomSlider.qml 9 | images/slider_handle.png 10 | +android/CustomSlider.qml 11 | 12 | 13 | -------------------------------------------------------------------------------- /common/common.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += staticlib 3 | SOURCES = twamp_common.cpp 4 | HEADERS = twamp_common.h 5 | 6 | CONFIG(debug, debug|release){ 7 | DESTDIR = ./debug 8 | OBJECTS_DIR = debug/.obj 9 | MOC_DIR = debug/.moc 10 | RCC_DIR = debug/.rcc 11 | UI_DIR = debug/.ui 12 | } 13 | 14 | CONFIG(release, debug|release){ 15 | DESTDIR = ./ 16 | OBJECTS_DIR = .obj 17 | MOC_DIR = .moc 18 | RCC_DIR = .rcc 19 | UI_DIR = .ui 20 | } 21 | -------------------------------------------------------------------------------- /installer/packages/com.demirten.twamp/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Twamp Gui 4 | Twamp Gui Client and Responder 5 | 1.0.7 6 | 2020-07-25 7 | 8 | 9 | 10 | true 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/qml/jbQuick/qmldir: -------------------------------------------------------------------------------- 1 | ### qmldir --- 2 | ## 3 | ## Author: Julien Wintz 4 | ## Created: Thu Feb 13 14:36:00 2014 (+0100) 5 | ## Version: 6 | ## Last-Updated: 7 | ## By: 8 | ## Update #: 45 9 | ###################################################################### 10 | ## 11 | ### Change Log: 12 | ## 13 | ###################################################################### 14 | 15 | module jbQuick.Charts 16 | 17 | Chart 1.0 QChart.qml 18 | Charts 1.0 QChart.js 19 | -------------------------------------------------------------------------------- /installer/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Twamp Gui 4 | 1.0.7 5 | Twamp Gui Tools Installer 6 | Murat Demirten 7 | Twamp Gui 8 | @HomeDir@/TwampGui 9 | @TargetDir@/twamp-client.exe 10 | Run Twamp Client 11 | 12 | -------------------------------------------------------------------------------- /client/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "twamp_client.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | 10 | QQmlApplicationEngine engine; 11 | 12 | TwampClient client; 13 | 14 | QQmlContext* ctx = engine.rootContext(); 15 | ctx->setContextProperty("client", (TwampClient*)&client); 16 | 17 | engine.load(QUrl(QStringLiteral("qrc:/client.qml"))); 18 | 19 | return app.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /responder/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "twamp_responder.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | 10 | QQmlApplicationEngine engine; 11 | 12 | TwampResponder responder; 13 | 14 | QQmlContext* ctx = engine.rootContext(); 15 | ctx->setContextProperty("responder", (TwampResponder*)&responder); 16 | 17 | engine.load(QUrl(QStringLiteral("qrc:/responder.qml"))); 18 | 19 | return app.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /client/chartmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef CHARTMODEL_H 2 | #define CHARTMODEL_H 3 | 4 | #include 5 | #include 6 | 7 | class ChartData { 8 | 9 | }; 10 | 11 | class ChartModel : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit ChartModel(QObject *parent = 0); 16 | Q_PROPERTY(QList dataset1 READ dataset1 NOTIFY dataset1Changed()) 17 | QList dataset1(); 18 | private: 19 | QList mDataset1; 20 | private slots: 21 | void updateDataset1(); 22 | signals: 23 | void dataset1Changed(); 24 | }; 25 | 26 | #endif // CHARTMODEL_H 27 | -------------------------------------------------------------------------------- /client/client.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | QT += qml quick widgets 3 | TARGET = twamp-client 4 | 5 | CONFIG += qtquickcompiler 6 | 7 | SOURCES += \ 8 | main.cpp \ 9 | twamp_client.cpp \ 10 | twamp_test_worker.cpp 11 | 12 | RESOURCES += qml/qml.qrc 13 | RC_ICONS = ../msi/assets/TwampClient.ico 14 | 15 | HEADERS += \ 16 | twamp_client.h \ 17 | log_model_data.h \ 18 | twamp_test_worker.h 19 | 20 | INCLUDEPATH += "$$PWD/../common" 21 | 22 | CONFIG(debug, debug|release){ 23 | LIBS += -L"$$OUT_PWD/../common/debug" -lcommon 24 | } 25 | CONFIG(release, debug|release){ 26 | LIBS += -L"$$OUT_PWD/../common" -lcommon 27 | } 28 | 29 | OBJECTS_DIR = .obj 30 | MOC_DIR = .moc 31 | RCC_DIR = .rcc 32 | -------------------------------------------------------------------------------- /responder/responder.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | QT += qml quick widgets 3 | TARGET = twamp-responder 4 | 5 | CONFIG += qtquickcompiler 6 | 7 | # QMAKE_LFLAGS += -static-libgcc -static-libstdc++ 8 | 9 | SOURCES += \ 10 | main.cpp \ 11 | twamp_responder.cpp \ 12 | twamp_responder_worker.cpp 13 | 14 | RESOURCES += qml/qml.qrc 15 | RC_ICONS = ../msi/assets/TwampResponder.ico 16 | 17 | HEADERS += \ 18 | twamp_responder.h \ 19 | twamp_responder_worker.h \ 20 | log_model_data.h 21 | 22 | INCLUDEPATH += "$$PWD/../common" 23 | 24 | CONFIG(debug, debug|release){ 25 | LIBS += -L"$$OUT_PWD/../common/debug" -lcommon 26 | } 27 | CONFIG(release, debug|release){ 28 | LIBS += -L"$$OUT_PWD/../common" -lcommon 29 | } 30 | 31 | OBJECTS_DIR = .obj 32 | MOC_DIR = .moc 33 | RCC_DIR = .rcc 34 | -------------------------------------------------------------------------------- /responder/qml/LogDetailItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.2 4 | 5 | Rectangle { 6 | id: logDetailItem 7 | Layout.fillWidth: true 8 | implicitHeight: message.contentHeight + 6 9 | //height: message.contentHeight 10 | color: "#f5f5f5" 11 | visible: true 12 | 13 | Label { 14 | id: message 15 | anchors.left: parent.left 16 | anchors.leftMargin: 80 17 | color: "#555" 18 | //font.family: "Courier New" 19 | //font.bold: true 20 | font: Qt.font({ family: "monospace" }) 21 | } 22 | Label { 23 | id: content 24 | anchors.left: parent.left 25 | anchors.leftMargin: 300 26 | color: "#222" 27 | font: Qt.font({ family: "monospace" }) 28 | } 29 | function setText(label, data) { 30 | message.text = label; 31 | content.text = data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/qml/LogDetailItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.2 4 | 5 | Rectangle { 6 | id: logDetailItem 7 | Layout.fillWidth: true 8 | implicitHeight: message.contentHeight + 4 9 | //height: message.contentHeight 10 | color: "#f5f5f5" 11 | visible: true 12 | 13 | Label { 14 | id: message 15 | anchors.left: parent.left 16 | anchors.leftMargin: 80 17 | color: "#555" 18 | //font.family: "Courier New" 19 | //font.bold: true 20 | font: Qt.font({ family: "monospace" }) 21 | } 22 | Label { 23 | id: content 24 | anchors.left: parent.left 25 | anchors.leftMargin: 270 26 | color: "#222" 27 | font: Qt.font({ family: "monospace" }) 28 | } 29 | function setText(label, data) { 30 | message.text = label; 31 | content.text = data; 32 | if (label === "Warning") { 33 | message.color = "red" 34 | message.font.bold = true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twamp-gui 2 | 3 | Cross platform twamp client with Qt QML backend 4 | 5 | You must have a working Qt5 - QML Development environment, it is not tested with Qt4. 6 | 7 | ## Build on Linux 8 | 9 | It is tested on Debian Jessie and newer versions. You need to install following packages: 10 | 11 | ``` 12 | $ sudo apt-get install qt5-default qtdeclarative5-dev qml-module-qtquick-controls \ 13 | qml-module-qtquick-dialogs qml-module-qtquick-layouts qml-module-qtquick-window2 \ 14 | qml-module-qtquick2 \ 15 | libgl1-mesa-glx libgl1-mesa-dev 16 | ``` 17 | 18 | After that you need to follow standard build process of Qt applications: 19 | 20 | ``` 21 | $ qmake 22 | $ make 23 | $ responder/twamp-responder & 24 | $ client/twamp-client 25 | ``` 26 | 27 | > Note: Running twamp-responder with the default port (862) requires root access 28 | or `CAP_NET_BIND_SERVICE` Linux capability (see http://manpages.org/capabilities/7) 29 | 30 | ## Build on Windows 31 | 32 | Install latest Qt5 development kit and build the project. 33 | 34 | ## Build on Mac 35 | 36 | Install latest Qt5 development kit and build the project. 37 | 38 | ## Precompiled Binaries 39 | 40 | You can download precompiled binaries for several platforms: 41 | https://github.com/demirten/twamp-gui/releases 42 | 43 | ## Screenshots 44 | 45 | You can look at the screenshots on project page: http://demirten.github.io/twamp-gui/#screenshots 46 | -------------------------------------------------------------------------------- /client/qml/+android/CustomSlider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Layouts 1.1 4 | 5 | Item { 6 | id: slider 7 | 8 | Layout.fillWidth: true 9 | Layout.fillHeight: true 10 | 11 | property alias minimumValue: mSlider.minimumValue 12 | property alias maximumValue: mSlider.maximumValue 13 | property alias value: mSlider.value 14 | property alias stepSize: mSlider.stepSize 15 | 16 | RowLayout { 17 | anchors.fill: parent 18 | 19 | Slider { 20 | id: mSlider 21 | //anchors.right: valueContainer.left 22 | anchors.rightMargin: 10 23 | anchors.verticalCenter: parent.verticalCenter 24 | Layout.fillWidth: true 25 | Layout.minimumWidth: 150 26 | } 27 | 28 | Rectangle { 29 | id: valueContainer 30 | anchors.right: parent.right 31 | radius: 10 32 | color: "#eee" 33 | border.color: Qt.darker(color, 1.4) 34 | height: valueText.contentHeight + 20 35 | width: valueText.contentWidth + 20 36 | anchors.verticalCenter: parent.verticalCenter 37 | Text { 38 | id: valueText 39 | text: mSlider.value 40 | font.pointSize: 14 41 | font.bold: true 42 | color: "#555" 43 | anchors.verticalCenter: parent.verticalCenter 44 | anchors.horizontalCenter: parent.horizontalCenter 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /responder/twamp_responder.h: -------------------------------------------------------------------------------- 1 | #ifndef TWAMP_RESPONDER_H 2 | #define TWAMP_RESPONDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "twamp_common.h" 16 | #include "twamp_responder_worker.h" 17 | #include "log_model_data.h" 18 | 19 | 20 | class TwampResponder : public TwampCommon 21 | { 22 | Q_OBJECT 23 | public: 24 | TwampResponder(); 25 | ~TwampResponder(); 26 | Q_PROPERTY(QList logModel READ logModel NOTIFY logModelChanged) 27 | QList logModel(); 28 | 29 | private: 30 | QList mLogModel; 31 | QList mClients; 32 | int controlPort; 33 | int lightPort; 34 | bool running; 35 | QThread *workerThread; 36 | QElapsedTimer *logTimer; 37 | 38 | signals: 39 | void displayError(QString message); 40 | void logModelChanged(); 41 | void twampLogString(QString message); 42 | void twampLog(int type, QString message, QByteArray data, int status); 43 | void responderStarted(); 44 | void responderStopped(); 45 | 46 | 47 | private slots: 48 | void twampLogReceived(int, QString, QByteArray, int); 49 | void twampLogReceived(QString message); 50 | 51 | public slots: 52 | void startServer(int controlPort, int lightPort, bool collectLogs); 53 | void stopServer(); 54 | void clearLogs(); 55 | 56 | }; 57 | 58 | #endif // TWAMP_RESPONDER_H 59 | -------------------------------------------------------------------------------- /client/twamp_client.h: -------------------------------------------------------------------------------- 1 | #ifndef TWAMP_CLIENT_H 2 | #define TWAMP_CLIENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "twamp_common.h" 11 | #include "twamp_test_worker.h" 12 | #include "log_model_data.h" 13 | 14 | class TwampClient : public TwampCommon 15 | { 16 | Q_OBJECT 17 | public: 18 | TwampClient(); 19 | ~TwampClient(); 20 | Q_PROPERTY(QList datasetLatencies READ datasetLatencies NOTIFY datasetLatenciesChanged) 21 | QList datasetLatencies(); 22 | Q_PROPERTY(QList xValues READ xValues NOTIFY xValuesChanged) 23 | QList xValues(); 24 | Q_PROPERTY(QList logModel READ logModel NOTIFY logModelChanged) 25 | QList logModel(); 26 | 27 | private: 28 | QList latencies; 29 | QList x_values; 30 | QList mLogModel; 31 | QString destination; 32 | int port; 33 | unsigned int totalPackets; 34 | int interval; 35 | bool lightMode; 36 | int payload; 37 | bool running; 38 | 39 | QElapsedTimer *logTimer; 40 | 41 | QHostAddress destinationHost; 42 | 43 | QThread *workerThread; 44 | 45 | signals: 46 | void testStarted(unsigned int totalPackets); 47 | void testFinished(); 48 | void packetSent(int index, int totalPackets); 49 | void calculatedResults(double packetLoss, double minLatency, double maxLatency, double averageLatency, double averageJitter); 50 | void displayError(QString message); 51 | void datasetLatenciesChanged(); 52 | void xValuesChanged(); 53 | void logModelChanged(); 54 | 55 | private slots: 56 | void testLatencyReceived(int index, double latency); 57 | void twampLogReceived(int, QString, QByteArray, int); 58 | void twampLogReceived(QString message); 59 | 60 | public slots: 61 | bool startTest(bool lightMode, QString destination, int port, unsigned int totalPackets, int interval, int payload); 62 | bool stopTest(); 63 | 64 | }; 65 | 66 | #endif // TWAMP_CLIENT_H 67 | -------------------------------------------------------------------------------- /client/qml/CustomSlider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Controls.Styles 1.2 4 | import QtQuick.Layouts 1.1 5 | 6 | Item { 7 | id: slider 8 | 9 | Layout.fillWidth: true 10 | Layout.fillHeight: true 11 | implicitWidth: 220 12 | 13 | property alias minimumValue: mSlider.minimumValue 14 | property alias maximumValue: mSlider.maximumValue 15 | property alias value: mSlider.value 16 | property alias stepSize: mSlider.stepSize 17 | 18 | RowLayout { 19 | Slider { 20 | id: mSlider 21 | anchors.rightMargin: 10 22 | Layout.fillWidth: true 23 | Layout.minimumWidth: 150 24 | 25 | style:SliderStyle{ 26 | groove:Rectangle { 27 | implicitHeight: mSlider.height * 0.3 28 | implicitWidth: 100 29 | radius: height/2 30 | border.color: "#333" 31 | color: "#eee" 32 | Rectangle { 33 | height: parent.height 34 | width: styleData.handlePosition 35 | implicitHeight: 6 36 | implicitWidth: 100 37 | radius: height/2 38 | color: "#25b1e8" 39 | opacity: 0.7 40 | } 41 | } 42 | handle: Image { 43 | id: imgHandle 44 | source: "images/slider_handle.png" 45 | } 46 | } 47 | } 48 | 49 | Rectangle { 50 | id: valueContainer 51 | radius: 5 52 | color: "#eee" 53 | border.color: Qt.darker(color, 1.1) 54 | height: valueText.contentHeight + 5 55 | width: valueText.contentWidth + 10 56 | Text { 57 | id: valueText 58 | text: mSlider.value 59 | font.pointSize: 10 60 | color: "#555" 61 | anchors.verticalCenter: parent.verticalCenter 62 | anchors.horizontalCenter: parent.horizontalCenter 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /responder/twamp_responder_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef TWAMP_RESPONDER_WORKER_H 2 | #define TWAMP_RESPONDER_WORKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "twamp_common.h" 15 | #include "log_model_data.h" 16 | 17 | class Client { 18 | public: 19 | QTcpSocket *controlSocket; 20 | QUdpSocket *testSocket; 21 | QString address; 22 | quint16 port; 23 | quint16 testUdpPort; 24 | qint64 lastUpdate; 25 | bool setupResponseReceived; 26 | }; 27 | 28 | class TwampResponderWorker : public TwampCommon 29 | { 30 | Q_OBJECT 31 | public: 32 | TwampResponderWorker(int, int); 33 | ~TwampResponderWorker(); 34 | 35 | QString startErrors(); 36 | 37 | private: 38 | QList mClients; 39 | int controlPort; 40 | int lightPort; 41 | QString mStartErrors; 42 | struct twamp_time instanceStarted; 43 | bool running; 44 | QTimer *removeIdleClientsTimer; 45 | 46 | QHostAddress destinationHost; 47 | 48 | QTcpServer *controlServer; 49 | QUdpSocket *udpLightServer; 50 | 51 | QMutex clientMutex; 52 | 53 | void sendGreeting(Client *client); 54 | void sendServerStart(Client *client); 55 | void sendAcceptSession(Client *client, quint16 prefferdPort); 56 | void sendStartSessionsAck(Client *client); 57 | 58 | 59 | Client * getClientFromControlSocket(QTcpSocket *socket); 60 | Client * getClientFromTestSocket(QUdpSocket *socket); 61 | void removeClient(Client *client); 62 | void stopClient(Client *client); 63 | 64 | signals: 65 | void errorMessage(QString message); 66 | void logModelChanged(); 67 | void responderStarted(); 68 | void responderStopped(); 69 | void twampLogString(QString message); 70 | void twampLog(int type, QString message, QByteArray data, int status); 71 | 72 | 73 | private slots: 74 | void acceptNewControlClient(); 75 | void clientRead(); 76 | void clientDisconnected(); 77 | void clientTestPacketRead(); 78 | void twampLightRead(); 79 | void removeIdleClientsTimerDone(); 80 | 81 | public slots: 82 | void startServer(); 83 | void stopServer(); 84 | }; 85 | 86 | #endif // TWAMP_RESPONDER_WORKER_H 87 | -------------------------------------------------------------------------------- /installer/packages/com.demirten.twamp/meta/installscript.qs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the Qt Installer Framework. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see http://qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** 31 | ** $QT_END_LICENSE$ 32 | ** 33 | **************************************************************************/ 34 | 35 | function Component() 36 | { 37 | // default constructor 38 | } 39 | 40 | Component.prototype.createOperations = function() 41 | { 42 | component.createOperations(); 43 | 44 | if (systemInfo.productType === "windows") { 45 | component.addOperation("CreateShortcut", "@TargetDir@/twamp-client.exe", "@StartMenuDir@/TwampClient.lnk", 46 | "workingDirectory=@TargetDir@", "iconPath=@TargetDir@/twamp-client.exe", 47 | "iconId=2"); 48 | component.addOperation("CreateShortcut", "@TargetDir@/twamp-responder.exe", "@StartMenuDir@/TwampResponder.lnk", 49 | "workingDirectory=@TargetDir@", "iconPath=@TargetDir@/twamp-responder.exe", 50 | "iconId=2"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /responder/twamp_responder.cpp: -------------------------------------------------------------------------------- 1 | #include "twamp_responder.h" 2 | #include "twamp_responder_worker.h" 3 | #include 4 | #include 5 | 6 | #define MIN_UDP_PORT 8000 7 | #define MAX_UDP_PORT 65000 8 | 9 | TwampResponder::TwampResponder() 10 | { 11 | workerThread = NULL; 12 | logTimer = new QElapsedTimer(); 13 | } 14 | 15 | TwampResponder::~TwampResponder() 16 | { 17 | 18 | } 19 | 20 | void TwampResponder::startServer(int controlPort, int lightPort, bool collectLogs) 21 | { 22 | clearLogs(); 23 | logTimer->restart(); 24 | 25 | TwampResponderWorker *worker = new TwampResponderWorker(controlPort, lightPort); 26 | 27 | if (worker->startErrors().length() > 0) { 28 | displayError(worker->startErrors()); 29 | worker->deleteLater(); 30 | return; 31 | } 32 | 33 | workerThread = new QThread; 34 | worker->moveToThread(workerThread); 35 | connect(workerThread, SIGNAL(started()), worker, SLOT(startServer())); 36 | connect(worker, SIGNAL(errorMessage(QString)), this, SIGNAL(displayError(QString))); 37 | if (collectLogs) { 38 | connect(worker, SIGNAL(twampLog(int,QString,QByteArray,int)), this, 39 | SLOT(twampLogReceived(int,QString,QByteArray,int))); 40 | connect(worker, SIGNAL(twampLogString(QString)), this, SLOT(twampLogReceived(QString))); 41 | } 42 | connect(worker, SIGNAL(responderStarted()), this, SIGNAL(responderStarted())); 43 | connect(worker, SIGNAL(responderStopped()), this, SIGNAL(responderStopped())); 44 | connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); 45 | connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); 46 | workerThread->start(); 47 | } 48 | 49 | void TwampResponder::stopServer() 50 | { 51 | if (workerThread) { 52 | workerThread->quit(); 53 | workerThread = NULL; 54 | } 55 | emit responderStopped(); 56 | } 57 | 58 | QList TwampResponder::logModel() 59 | { 60 | return mLogModel; 61 | } 62 | 63 | void TwampResponder::twampLogReceived(int type, QString message, QByteArray data, int controlType) 64 | { 65 | double time = logTimer->nsecsElapsed() / 1000000000.0; 66 | LogModelData *l = new LogModelData(type, message, data, controlType, time); 67 | mLogModel.append(l); 68 | emit logModelChanged(); 69 | } 70 | 71 | void TwampResponder::twampLogReceived(QString message) 72 | { 73 | QByteArray arr; 74 | return twampLogReceived(TwampLogString, message, arr, StatusUnknown); 75 | } 76 | 77 | void TwampResponder::clearLogs() 78 | { 79 | foreach (QObject *obj, mLogModel) { 80 | obj->deleteLater(); 81 | } 82 | mLogModel.clear(); 83 | emit logModelChanged(); 84 | } 85 | -------------------------------------------------------------------------------- /client/twamp_test_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef TWAMP_TEST_WORKER_H 2 | #define TWAMP_TEST_WORKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "twamp_common.h" 10 | 11 | struct udpTestPacket { 12 | quint32 index; 13 | qint64 sent; 14 | qint64 received; 15 | quint32 coming_order; 16 | bool valid; 17 | float latency; 18 | }; 19 | 20 | class TwampTestWorker : public TwampCommon 21 | { 22 | Q_OBJECT 23 | public: 24 | TwampTestWorker(bool lightMode, QHostAddress destination, int port, unsigned int totalPackets, int interval, int payload); 25 | ~TwampTestWorker(); 26 | 27 | private: 28 | bool lightMode; 29 | QHostAddress destinationHost; 30 | int port; 31 | quint16 receiverUdpPort; 32 | unsigned int totalPackets; 33 | int interval; 34 | int payload; 35 | enum TestStatus status; 36 | 37 | int padding_length; 38 | struct twamp_message_test_unauthenticated *message; 39 | int message_length; 40 | struct twamp_message_reflector_unathenticated *reflector; 41 | int reflector_length; 42 | int received_packets; 43 | 44 | double min_latency; 45 | double max_latency; 46 | double average_latency; 47 | double average_jitter; 48 | quint32 sequence; 49 | 50 | QList packets; 51 | QUdpSocket *udpSocket; 52 | QTcpSocket *controlSocket; 53 | QByteArray controlSocketBuffer; 54 | 55 | /* Timers */ 56 | QTimer *controlHandshakeTimer; 57 | QTimer *udpSendTimer; 58 | QTimer *maxTestWaitTimer; 59 | QElapsedTimer *elapsedTimer; 60 | 61 | double getStdDev(double average); 62 | void processResults(); 63 | void startControlHandshake(); 64 | void abortControlHandshake(QString message = "Twamp Control Handshake Failed"); 65 | void sendControlSetupResponse(); 66 | void sendControlRequestSession(); 67 | void sendControlStartSessions(); 68 | void sendControlStopSessions(); 69 | void controlHandshakeFinished(); 70 | 71 | signals: 72 | void testFinished(); 73 | void testStarted(unsigned int totalPackets); 74 | void errorMessage(QString); 75 | void newTestLatency(int, double); 76 | void packetSent(int index, int totalPackets); 77 | void twampLogString(QString message); 78 | void twampLog(int type, QString message, QByteArray data, int status); 79 | void testResultsCalculated(double packetLoss, double min_latency, double max_latency, double average_latency, double average_jitter); 80 | 81 | public slots: 82 | void doTest(); 83 | void readReflectorMessage(); 84 | 85 | void controlSocketConnected(); 86 | void controlSocketError(QAbstractSocket::SocketError error); 87 | void controlSocketRead(); 88 | 89 | void udpSendTimerDone(); 90 | void controlHandshakeTimerDone(); 91 | void maxTestWaitTimerDone(); 92 | 93 | 94 | 95 | }; 96 | 97 | #endif // TWAMP_TEST_WORKER_H 98 | -------------------------------------------------------------------------------- /common/twamp_common.cpp: -------------------------------------------------------------------------------- 1 | #include "twamp_common.h" 2 | #include 3 | #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) 4 | #include 5 | #endif 6 | 7 | TwampCommon::TwampCommon(QObject *parent) : 8 | QObject(parent) 9 | { 10 | } 11 | 12 | struct twamp_time TwampCommon::getTwampTime() 13 | { 14 | struct twamp_time t; 15 | qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); 16 | t.seconds = (currentTime / 1000) + TWAMP_BASE_TIME_OFFSET; 17 | /* convert milliseconds to microseconds multiplying by 1000 */ 18 | t.fraction = (currentTime % 1000) * TWAMP_FLOAT_DENOM * 1000; 19 | return t; 20 | } 21 | 22 | int TwampCommon::twampTimeToTimeval (struct twamp_time *time, struct timeval *result) 23 | { 24 | result->tv_sec = (time_t)(time->seconds - TWAMP_BASE_TIME_OFFSET); 25 | result->tv_usec = (time_t)(time->fraction / TWAMP_FLOAT_DENOM); 26 | return 0; 27 | } 28 | 29 | float TwampCommon::timevalDiff (struct timeval *before, struct timeval *after) 30 | { 31 | time_t usecs = after->tv_usec - before->tv_usec; 32 | time_t secs = after->tv_sec - before->tv_sec; 33 | 34 | if (usecs < 0) { 35 | usecs += 1000000; 36 | secs--; 37 | } 38 | return (secs * 1000 + (usecs / 1000.0)); 39 | } 40 | 41 | quint16 TwampCommon::getErrorEstimate() 42 | { 43 | quint16 estimate = 0; 44 | 45 | #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) 46 | struct twamp_message_error_estimate *e = (struct twamp_message_error_estimate*)&estimate; 47 | struct timex ntp_conf; 48 | memset(&ntp_conf, 0, sizeof(ntp_conf)); 49 | 50 | if ((ntp_adjtime(&ntp_conf) != -1) && !(ntp_conf.status & STA_UNSYNC)) { 51 | /* NTP sync is active */ 52 | e->s = 1; 53 | 54 | /* Convert error estimate from microseconds 55 | to Multiplier*2^(-32)*2^Scale (in seconds) */ 56 | qint64 error = (ntp_conf.esterror << 32) / 1000000; 57 | 58 | /* Shift error until it fits into 8 bits */ 59 | while (error >= 0xFF) { 60 | e->scale++; 61 | error >>= 1; 62 | } 63 | 64 | /* Add one for rounding error */ 65 | e->multiplier = error + 1; 66 | } 67 | #endif 68 | 69 | return estimate; 70 | } 71 | 72 | QString TwampCommon::toHex(const QByteArray &data) 73 | { 74 | return toHex(data.constData(), data.size()); 75 | } 76 | 77 | QString TwampCommon::toHex(const char *data, unsigned int length) 78 | { 79 | QString resHex = ""; 80 | for (unsigned int i = 0; i < length; i++) { 81 | resHex.append(QString::number((unsigned char)data[i], 16).rightJustified(2, '0')); 82 | } 83 | return resHex; 84 | } 85 | 86 | QString TwampCommon::acceptString(uint8_t accept) 87 | { 88 | switch (accept) { 89 | case 0: return "OK"; 90 | case 1: return "Failure, reason unspecified (catch-all)."; 91 | case 2: return "Internal error."; 92 | case 3: return "Some aspect of request is not supported."; 93 | case 4: return "Cannot perform request due to permanent resource limitations."; 94 | case 5: return "Cannot perform request due to temporary resource limitations."; 95 | } 96 | return "Unknown accept value"; 97 | } 98 | -------------------------------------------------------------------------------- /client/qml/jbQuick/QChart.qml: -------------------------------------------------------------------------------- 1 | /* QChart.qml --- 2 | * 3 | * Author: Julien Wintz 4 | * Created: Thu Feb 13 20:59:40 2014 (+0100) 5 | * Version: 6 | * Last-Updated: jeu. mars 6 12:55:14 2014 (+0100) 7 | * By: Julien Wintz 8 | * Update #: 69 9 | */ 10 | 11 | /* Change Log: 12 | * 13 | */ 14 | 15 | import QtQuick 2.0 16 | 17 | import "QChart.js" as Charts 18 | 19 | Canvas { 20 | 21 | id: canvas; 22 | // /////////////////////////////////////////////////////////////// 23 | 24 | property var chart; 25 | property var chartData; 26 | property int chartType: 0; 27 | property bool chartAnimated: true; 28 | property alias chartAnimationEasing: chartAnimator.easing.type; 29 | property alias chartAnimationDuration: chartAnimator.duration; 30 | property int chartAnimationProgress: 0; 31 | 32 | // ///////////////////////////////////////////////////////////////// 33 | // Callbacks 34 | // ///////////////////////////////////////////////////////////////// 35 | 36 | onPaint: { 37 | if(!chart) { 38 | 39 | switch(chartType) { 40 | case Charts.ChartType.BAR: 41 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).Bar(chartData); 42 | break; 43 | case Charts.ChartType.DOUGHNUT: 44 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).Doughnut(chartData); 45 | break; 46 | case Charts.ChartType.LINE: 47 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).Line(chartData); 48 | break; 49 | case Charts.ChartType.PIE: 50 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).Pie(chartData); 51 | break; 52 | case Charts.ChartType.POLAR: 53 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).PolarArea(chartData); 54 | break; 55 | case Charts.ChartType.RADAR: 56 | chart = new Charts.Chart(canvas, canvas.getContext("2d")).Radar(chartData); 57 | break; 58 | default: 59 | console.log('Chart type should be specified.'); 60 | } 61 | 62 | chart.init(); 63 | 64 | if (chartAnimated) 65 | chartAnimator.start(); 66 | else 67 | chartAnimationProgress = 100; 68 | } 69 | 70 | chart.draw(chartAnimationProgress/100); 71 | } 72 | 73 | onHeightChanged: { 74 | requestPaint(); 75 | } 76 | 77 | onWidthChanged: { 78 | requestPaint(); 79 | } 80 | 81 | onChartAnimationProgressChanged: { 82 | requestPaint(); 83 | } 84 | 85 | // ///////////////////////////////////////////////////////////////// 86 | // Functions 87 | // ///////////////////////////////////////////////////////////////// 88 | 89 | function repaint() { 90 | chartAnimationProgress = 0; 91 | chartAnimator.start(); 92 | } 93 | 94 | // ///////////////////////////////////////////////////////////////// 95 | // Internals 96 | // ///////////////////////////////////////////////////////////////// 97 | 98 | PropertyAnimation { 99 | id: chartAnimator; 100 | target: canvas; 101 | property: "chartAnimationProgress"; 102 | to: 100; 103 | duration: 500; 104 | easing.type: Easing.InOutElastic; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /client/twamp_client.cpp: -------------------------------------------------------------------------------- 1 | #include "twamp_client.h" 2 | #include 3 | #include 4 | #include 5 | 6 | TwampClient::TwampClient() 7 | { 8 | running = false; 9 | 10 | logTimer = new QElapsedTimer(); 11 | workerThread = NULL; 12 | 13 | } 14 | 15 | TwampClient::~TwampClient() 16 | { 17 | 18 | } 19 | 20 | bool TwampClient::startTest(bool lightMode, QString destination, int port, unsigned int totalPackets, int interval, int payload) 21 | { 22 | if (running) { 23 | stopTest(); 24 | } 25 | this->lightMode = lightMode; 26 | this->destination = destination; 27 | this->port = port; 28 | this->totalPackets = totalPackets; 29 | this->interval = interval; 30 | 31 | QHostAddress address; 32 | if (address.setAddress(destination)) { 33 | destinationHost = address; 34 | } else { 35 | QHostInfo info = QHostInfo::fromName(destination); 36 | if (info.error() != QHostInfo::NoError) { 37 | emit displayError(info.errorString()); 38 | emit testFinished(); 39 | return false; 40 | } 41 | destinationHost = info.addresses().first(); 42 | } 43 | 44 | foreach (QObject *obj, mLogModel) { 45 | obj->deleteLater(); 46 | } 47 | mLogModel.clear(); 48 | 49 | running = true; 50 | 51 | x_values.clear(); 52 | latencies.clear(); 53 | for (unsigned int i = 0; i < totalPackets; i++) { 54 | x_values.append(QString::number(i)); 55 | latencies.append(0.0); 56 | } 57 | emit datasetLatenciesChanged(); 58 | 59 | workerThread = new QThread; 60 | TwampTestWorker* worker = new TwampTestWorker(lightMode, destinationHost, port, totalPackets, interval, payload); 61 | worker->moveToThread(workerThread); 62 | connect(workerThread, SIGNAL(started()), worker, SLOT(doTest())); 63 | connect(worker, SIGNAL(testFinished()), this, SIGNAL(testFinished())); 64 | connect(worker, SIGNAL(testStarted(quint32)), this, SIGNAL(testStarted(uint))); 65 | connect(worker, SIGNAL(newTestLatency(int,double)), this, SLOT(testLatencyReceived(int, double))); 66 | connect(worker, SIGNAL(errorMessage(QString)), this, SIGNAL(displayError(QString))); 67 | connect(worker, SIGNAL(packetSent(int,int)), this, SIGNAL(packetSent(int,int))); 68 | connect(worker, SIGNAL(twampLog(int,QString,QByteArray,int)), this, 69 | SLOT(twampLogReceived(int,QString,QByteArray,int))); 70 | connect(worker, SIGNAL(twampLogString(QString)), this, SLOT(twampLogReceived(QString))); 71 | connect(worker, SIGNAL(testResultsCalculated(double,double,double,double,double)), 72 | this, SIGNAL(calculatedResults(double,double,double,double,double))); 73 | connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); 74 | connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); 75 | workerThread->start(); 76 | 77 | logTimer->start(); 78 | return true; 79 | } 80 | 81 | bool TwampClient::stopTest() 82 | { 83 | if (running) { 84 | if (workerThread) { 85 | workerThread->quit(); 86 | workerThread = NULL; 87 | } 88 | } 89 | running = false; 90 | return true; 91 | } 92 | 93 | void TwampClient::testLatencyReceived(int index, double latency) 94 | { 95 | if (index >= latencies.size()) { 96 | return; 97 | } 98 | latencies[index] = latency; 99 | emit datasetLatenciesChanged(); 100 | } 101 | 102 | void TwampClient::twampLogReceived(int type, QString message, QByteArray data, int controlType) 103 | { 104 | double time = logTimer->nsecsElapsed() / 1000000000.0; 105 | LogModelData *l = new LogModelData(type, message, data, controlType, time); 106 | mLogModel.append(l); 107 | emit logModelChanged(); 108 | } 109 | 110 | void TwampClient::twampLogReceived(QString message) 111 | { 112 | QByteArray arr; 113 | return twampLogReceived(TwampLogString, message, arr, StatusUnknown); 114 | } 115 | 116 | QList TwampClient::datasetLatencies() 117 | { 118 | return latencies; 119 | } 120 | 121 | QList TwampClient::xValues() 122 | { 123 | return x_values; 124 | } 125 | 126 | QList TwampClient::logModel() 127 | { 128 | return mLogModel; 129 | } 130 | -------------------------------------------------------------------------------- /common/twamp_common.h: -------------------------------------------------------------------------------- 1 | #ifndef TWAMP_COMMON_H 2 | #define TWAMP_COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define TWAMP_CONTROL_MODE_UNDEFINED (0x00) 9 | #define TWAMP_CONTROL_MODE_OPEN (0x01) 10 | #define TWAMP_CONTROL_MODE_AUTHENTICATED (0x02) 11 | #define TWAMP_CONTROL_MODE_ENCRYPTED (0x04) 12 | #define TWAMP_CONTROL_MODE_DOCIPHER (TWAMP_CONTROL_MODE_AUTHENTICATED | TWAMP_CONTROL_MODE_ENCRYPTED) 13 | 14 | #define TWAMP_BASE_TIME_OFFSET 2208988800u 15 | #define TWAMP_FLOAT_DENOM 4294.967296 16 | 17 | #define TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_REQUEST_SESSION 5 18 | #define TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_START_SESSIONS 2 19 | #define TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_STOP_SESSIONS 3 20 | 21 | #pragma pack(push,1) 22 | struct twamp_time { 23 | uint32_t seconds; 24 | uint32_t fraction; 25 | }; 26 | 27 | struct twamp_session_sid { 28 | unsigned char sid[16]; 29 | }; 30 | 31 | struct twamp_session { 32 | int conf_sender; 33 | int conf_receiver; 34 | int sender_port; 35 | int sender_sock; 36 | int receiver_port; 37 | char *sender_address; 38 | char *receiver_address; 39 | struct twamp_session_sid sid; 40 | unsigned int padding_length; 41 | unsigned int start_time_seconds; 42 | unsigned int start_time_fraction; 43 | unsigned int timeout_seconds; 44 | unsigned int timeout_fraction; 45 | unsigned int dscp; 46 | unsigned int number_of_packets; 47 | unsigned int number_of_slots; 48 | }; 49 | 50 | struct twamp_message_hwmac { 51 | uint8_t hwmac[16]; 52 | }; 53 | 54 | struct twamp_message_sid { 55 | uint8_t sid[16]; 56 | }; 57 | 58 | struct twamp_message_server_greeting { 59 | uint8_t unused[12]; 60 | uint32_t modes; 61 | uint8_t challange[16]; 62 | uint8_t salt[16]; 63 | uint32_t count; 64 | uint8_t mbz[12]; 65 | }; 66 | 67 | struct twamp_message_setup_response { 68 | uint32_t mode; 69 | uint8_t keyid[80]; 70 | uint8_t token[64]; 71 | uint8_t client_iv[16]; 72 | }; 73 | 74 | struct twamp_message_server_start { 75 | uint8_t mbz[15]; 76 | uint8_t accept; 77 | uint8_t server_iv[16]; 78 | struct twamp_time start_time; 79 | uint8_t mbz_[8]; 80 | }; 81 | 82 | struct twamp_message_request_session { 83 | uint8_t five; 84 | uint8_t ipvn:4; 85 | uint8_t mbz:4; 86 | uint8_t conf_sender; /* 0 */ 87 | uint8_t conf_receiver; /* 0 */ 88 | uint32_t schedule_slots; /* 0 */ 89 | uint32_t packets; /* 0 */ 90 | uint16_t sender_port; 91 | uint16_t receiver_port; 92 | uint32_t sender_address[4]; 93 | uint32_t receiver_address[4]; 94 | struct twamp_message_sid sid; /* 0 */ 95 | uint32_t padding_length; 96 | struct twamp_time start_time; 97 | struct twamp_time timeout; 98 | uint32_t type_p_descriptor; 99 | uint8_t mbz_[8]; 100 | struct twamp_message_hwmac hwmac; 101 | }; 102 | 103 | struct twamp_message_accept_session { 104 | uint8_t accept; 105 | uint8_t mbz; 106 | uint16_t port; 107 | struct twamp_message_sid sid; 108 | uint8_t mbz_[12]; 109 | struct twamp_message_hwmac hwmac; 110 | }; 111 | 112 | struct twamp_message_start_sessions { 113 | uint8_t two; 114 | uint8_t mbz[15]; 115 | struct twamp_message_hwmac hwmac; 116 | }; 117 | 118 | struct twamp_message_start_ack { 119 | uint8_t accept; 120 | uint8_t mbz[15]; 121 | struct twamp_message_hwmac hwmac; 122 | }; 123 | 124 | struct twamp_message_stop_sessions { 125 | uint8_t three; 126 | uint8_t accept; 127 | uint8_t mbz[2]; 128 | uint32_t number_of_sessions; 129 | uint8_t mbz_[8]; 130 | struct twamp_message_hwmac hwmac; 131 | }; 132 | 133 | struct twamp_message_error_estimate { 134 | uint8_t s:1; 135 | uint8_t z:1; 136 | uint8_t scale:6; 137 | uint8_t multiplier; 138 | }; 139 | 140 | struct twamp_message_test_unauthenticated { 141 | uint32_t sequence_number; 142 | struct twamp_time timestamp; 143 | struct twamp_message_error_estimate error_estimate; 144 | }; 145 | 146 | struct twamp_message_reflector_unathenticated { 147 | uint32_t sequence_number; 148 | struct twamp_time timestamp; 149 | struct twamp_message_error_estimate error_estimate; 150 | uint8_t mbz[2]; 151 | struct twamp_time receive_timestamp; 152 | uint32_t sender_sequence_number; 153 | struct twamp_time sender_timestamp; 154 | struct twamp_message_error_estimate sender_error_estimate; 155 | uint8_t mbz_[2]; 156 | uint8_t sender_ttl; 157 | }; 158 | #pragma pack(pop) 159 | 160 | enum TestStatus { 161 | StatusUnknown = 0, 162 | HandshakeConnecting, 163 | HandshakeServerGreeting, 164 | HandshakeSetupResponse, 165 | HandshakeServerStart, 166 | HandshakeRequestSession, 167 | HandshakeAcceptSession, 168 | HandshakeStartSession, 169 | HandshakeStartSessionAck, 170 | HandshakeStopSession, 171 | HandshakeError, 172 | TestPacketSent, 173 | TestPacketReceived, 174 | StatusLast 175 | }; 176 | 177 | enum TwampLogType { 178 | TwampLogString, 179 | TwampLogPacket 180 | }; 181 | 182 | 183 | class TwampCommon : public QObject 184 | { 185 | Q_OBJECT 186 | public: 187 | explicit TwampCommon(QObject *parent = 0); 188 | 189 | enum TwampMode { Standard, Light }; 190 | 191 | struct twamp_time getTwampTime(); 192 | int twampTimeToTimeval (struct twamp_time *time, struct timeval *result); 193 | float timevalDiff (struct timeval *before, struct timeval *after); 194 | static quint16 getErrorEstimate(); 195 | static QString toHex(const QByteArray &data); 196 | static QString toHex(const char *data, unsigned int length); 197 | static QString acceptString(uint8_t accept); 198 | 199 | signals: 200 | 201 | public slots: 202 | 203 | }; 204 | 205 | #endif // TWAMP_COMMON_H 206 | -------------------------------------------------------------------------------- /responder/qml/responder.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Layouts 1.1 4 | import QtQuick.Dialogs 1.2 5 | import QtQuick.Window 2.1 6 | 7 | ApplicationWindow { 8 | id: window 9 | visible: true 10 | property int margin: 10 11 | title: qsTr("Twamp Responder") 12 | 13 | Connections { 14 | target: responder 15 | 16 | onResponderStarted: { 17 | btnStart.text = "STOP" 18 | } 19 | onResponderStopped: { 20 | btnStart.text = "START" 21 | } 22 | 23 | onDisplayError: { 24 | messageBox.text = message; 25 | messageBox.open(); 26 | } 27 | } 28 | 29 | statusBar: StatusBar { 30 | Label { width: parent.width; text: "About Twamp-Gui"; horizontalAlignment: Text.AlignRight; font.pixelSize: 12} 31 | MouseArea { 32 | anchors.fill: parent 33 | onClicked: { 34 | aboutDialog.open() 35 | } 36 | } 37 | } 38 | 39 | ColumnLayout { 40 | id: mainLayout 41 | anchors.margins: window.margin 42 | anchors.fill: parent 43 | spacing: 0 44 | 45 | GridLayout { 46 | id: optionsGrid 47 | columns: 6 48 | 49 | Text { text: "Twamp Control TCP Listen Port: "; horizontalAlignment: Text.AlignRight } 50 | SpinBox { id: controlPort; minimumValue: 862; maximumValue: 65535; value: 862; } 51 | Text { text: "Twamp Light UDP Listen Port: "; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 52 | SpinBox { id: lightPort; minimumValue: 862; maximumValue: 65535; value: 862; } 53 | Text { text: "Collect Logs: "; horizontalAlignment: Text.AlignRight } 54 | CheckBox { 55 | id: collectLogs 56 | text: "Enabled" 57 | } 58 | } 59 | GroupBox { 60 | Layout.fillWidth: true 61 | RowLayout { 62 | Button { 63 | id: btnStart 64 | text: "START" 65 | onClicked: { 66 | if (text == "START") { 67 | responder.startServer(controlPort.value, lightPort.value, collectLogs.checked) 68 | } else { 69 | responder.stopServer() 70 | } 71 | } 72 | } 73 | Button { 74 | id: btnClear 75 | text: "CLEAR LOGS" 76 | onClicked: { 77 | responder.clearLogs() 78 | } 79 | } 80 | } 81 | } 82 | 83 | ColumnLayout { 84 | Rectangle { 85 | implicitHeight: 500 86 | Layout.fillWidth: true 87 | Layout.fillHeight: true 88 | ScrollView { 89 | anchors.fill: parent 90 | 91 | ListView { 92 | id: logListView 93 | anchors.centerIn: parent 94 | model: responder.logModel 95 | anchors.fill: parent 96 | 97 | delegate: Rectangle { 98 | id: delegateRect 99 | width: ListView.view.width 100 | height: logSummary.height 101 | 102 | ColumnLayout { 103 | anchors.fill: parent 104 | spacing: 0 105 | 106 | Rectangle { 107 | id: logSummary 108 | Layout.fillWidth: true 109 | implicitHeight: timing.contentHeight + 4 110 | color: (index % 2 == 1) ? "#e7e7fe" : "#faf0d7" 111 | 112 | Text { 113 | id: timing 114 | text: modelData.timing 115 | color: "black" 116 | //font.pixelSize: 13 117 | font: Qt.font({ family: "Segoe UI, monospace" }) 118 | } 119 | 120 | Text { 121 | anchors.left: logSummary.left 122 | anchors.leftMargin: 150 123 | 124 | color: "black" 125 | //font.pixelSize: 13 126 | font: Qt.font({ family: "Segoe UI, monospace" }) 127 | text: modelData.summary 128 | } 129 | 130 | MouseArea { 131 | anchors.fill: parent 132 | Layout.fillWidth: true 133 | 134 | onClicked: { 135 | if (logDetail.children.length === 0) { 136 | var i; 137 | var heightAdd = 0; 138 | var detail = modelData.detail() 139 | 140 | for (i = 0; i < detail.length; i += 2) { 141 | var component = Qt.createComponent("qrc:/LogDetailItem.qml"); 142 | var object = component.createObject(logDetail); 143 | object.setText(detail[i], detail[i+1]) 144 | heightAdd += object.height 145 | } 146 | 147 | logDetail.height = heightAdd 148 | logDetail.savedHeight = heightAdd 149 | delegateRect.height += heightAdd 150 | } else { 151 | if (!logDetail.visible) { 152 | delegateRect.height += logDetail.savedHeight 153 | logDetail.visible = true 154 | } else { 155 | delegateRect.height -= logDetail.savedHeight 156 | logDetail.visible = false 157 | } 158 | } 159 | } 160 | } 161 | 162 | } 163 | ColumnLayout { 164 | id: logDetail 165 | Layout.fillWidth: true 166 | Layout.alignment: Qt.AlignLeft | Qt.AlignBottom 167 | spacing: 0 168 | property int savedHeight: 0 169 | 170 | } 171 | 172 | 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | } 181 | 182 | MessageDialog { 183 | id: messageBox; 184 | objectName: "msgBox"; 185 | icon: StandardIcon.Critical; 186 | 187 | title: "Error"; 188 | 189 | onAccepted: { 190 | close(); 191 | } 192 | } 193 | MessageDialog { 194 | id: aboutDialog; 195 | icon: StandardIcon.Information; 196 | 197 | title: "About Twamp Gui v1.0.7"; 198 | text: "Project home page:\nhttps://github.com/demirten/twamp-gui" 199 | informativeText: "Copyright © Murat Demirten " 200 | onAccepted: { 201 | close(); 202 | } 203 | } 204 | 205 | } 206 | 207 | -------------------------------------------------------------------------------- /client/qml/client.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Controls.Styles 1.2 4 | import QtQuick.Layouts 1.1 5 | import QtQuick.Dialogs 1.2 6 | import QtQuick.Window 2.1 7 | import "jbQuick" 8 | 9 | ApplicationWindow { 10 | id: window 11 | visible: true 12 | width: (Qt.platform.os === "ios" && Qt.platform.os === "android") ? Screen.width : (Screen.width / 2) 13 | height: (Qt.platform.os === "ios" && Qt.platform.os === "android") ? Screen.height : (Screen.height / 2) 14 | property int margin: 10 15 | title: qsTr("Twamp Client") 16 | 17 | Component.onCompleted: { 18 | if (Qt.platform.os !== "ios" && Qt.platform.os !== "android") { 19 | setX(Screen.width / 2 - width / 2); 20 | setY(Screen.height / 2 - height / 2); 21 | } 22 | } 23 | 24 | Connections { 25 | target: client 26 | 27 | onTestStarted: { 28 | progress.value = 0; 29 | progress.visible = true; 30 | sent.visible = true; 31 | sentProgressLabel.text = "0 of " + totalPackets; 32 | sentProgressLabel.visible = true 33 | testResults.visible = false 34 | btnStart.text = "STOP" 35 | } 36 | onTestFinished: { 37 | btnStart.text = "START" 38 | } 39 | 40 | onPacketSent: { 41 | sentProgressLabel.text = index + " of " + totalPackets 42 | progress.value = (index / totalPackets) * 0.95; 43 | } 44 | 45 | onDisplayError: { 46 | messageBox.text = message; 47 | messageBox.open(); 48 | } 49 | 50 | onDatasetLatenciesChanged: { 51 | lineChart.requestPaint(); 52 | } 53 | 54 | onCalculatedResults: { 55 | progress.value = 1 56 | testResults.visible = true 57 | 58 | var color = "green"; 59 | if (packetLoss == 0) color = "green"; 60 | else if (packetLoss < 1) color = "orange"; 61 | else color = "red"; 62 | packetLossLabel.text = "Packet Loss: %" + parseFloat(packetLoss).toFixed(2) + ""; 63 | averageLatencyLabel.text = "AvgLat: " + parseFloat(averageLatency).toFixed(2) + " ms"; 64 | averageJitterLabel.text = "StdDev: " + parseFloat(averageJitter).toFixed(2) + " ms"; 65 | minLatencyLabel.text = "MinLat: " + parseFloat(minLatency).toFixed(2) + " ms"; 66 | maxLatencyLabel.text = "MaxLat: " + parseFloat(maxLatency).toFixed(2) + " ms"; 67 | btnStart.text = "START" 68 | } 69 | 70 | } 71 | 72 | statusBar: StatusBar { 73 | Label { width: parent.width; text: "About Twamp-Gui"; horizontalAlignment: Text.AlignRight; font.pixelSize: 12} 74 | MouseArea { 75 | anchors.fill: parent 76 | onClicked: { 77 | aboutBox.open() 78 | } 79 | } 80 | } 81 | 82 | ColumnLayout { 83 | id: mainLayout 84 | //width: 700 85 | anchors.margins: window.margin 86 | anchors.fill: parent 87 | spacing: 0 88 | 89 | //width: Math.max(window.viewport.width, column.implicitWidth + 2 * column.spacing) 90 | //height: Math.max(window.viewport.height, column.implicitHeight + 2 * column.spacing) 91 | 92 | GridLayout { 93 | id: optionsGrid 94 | columns: getGridColumns() 95 | 96 | //anchors.top: parent.top 97 | //anchors.left: parent.left 98 | //Layout.fillWidth: true 99 | //Layout.fillHeight: true 100 | 101 | function getGridColumns() { 102 | if (Qt.platform.os == "android" || Qt.platform.os == "ios") { 103 | return 2 104 | } else { 105 | return (window.width > 1000) ? 6 : 4 106 | } 107 | } 108 | 109 | Text { text: "Destination"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 110 | TextField { id: destination; text: "127.0.0.1"; Layout.fillWidth: true; } 111 | Text { text: "Port"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 112 | TextField { id: port; text: "862"; Layout.fillWidth: true } 113 | Text { text: "Total Packets"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 114 | CustomSlider { id: total_packets; minimumValue: 5; maximumValue: 100; value: 20; stepSize: 1; Layout.fillWidth: true; } 115 | 116 | Text { text: "Interval (ms)"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 117 | CustomSlider {id: interval; minimumValue: 10; maximumValue: 1000; value: 50; stepSize: 10; Layout.fillWidth: true} 118 | Text { text: "Payload (byte)"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 119 | CustomSlider {id: payload; minimumValue: 0; maximumValue: 1472; value: 64; stepSize: 1} 120 | Text { text: "Light Mode"; Layout.fillWidth: true; horizontalAlignment: Text.AlignRight } 121 | CheckBox { id: light; checked: false; } 122 | 123 | } 124 | ColumnLayout { 125 | id: startGroup 126 | RowLayout { 127 | Button { 128 | id: btnStart 129 | text: "START" 130 | onClicked: { 131 | if (text == "START") { 132 | client.startTest(light.checked, destination.text, port.text, total_packets.value, interval.value, payload.value) 133 | text = "STOP" 134 | } else { 135 | client.stopTest() 136 | text = "START" 137 | } 138 | } 139 | } 140 | Text {id: sent; visible: false; text: "Sent Progress"; horizontalAlignment: Text.AlignRight; 141 | anchors.rightMargin: 10; Layout.fillWidth: true} 142 | Label {id: sentProgressLabel; visible: false; anchors.rightMargin: 20; 143 | horizontalAlignment: Text.AlignRight; } 144 | ProgressBar { id: progress; visible: false; height: 5; Layout.preferredWidth: 200; Layout.fillWidth: true} 145 | } 146 | 147 | } 148 | 149 | ColumnLayout { 150 | anchors.topMargin: 10 151 | spacing: 10 152 | 153 | Rectangle { 154 | Layout.fillWidth: true 155 | Layout.fillHeight: true 156 | Layout.preferredHeight: 200 157 | Layout.minimumHeight: 200 158 | Layout.maximumHeight: 250 159 | 160 | Chart { 161 | id: lineChart 162 | visible: true 163 | anchors.fill: parent 164 | anchors.topMargin: 5 165 | chartAnimated: false 166 | chartType: Charts.ChartType.LINE 167 | chartData: { 168 | "labels": client.xValues, 169 | "datasets": [ 170 | { 171 | fillColor: "rgba(0,152,18,0.2)", 172 | strokeColor: "rgba(151,187,205,1)", 173 | pointColor: "rgba(151,187,205,1)", 174 | pointStrokeColor: "#eee", 175 | pointDotStrokeWidth: 1, 176 | data: client.datasetLatencies 177 | } 178 | ] 179 | } 180 | } 181 | } 182 | 183 | Rectangle { 184 | Layout.fillWidth: true 185 | Layout.preferredHeight: packetLossLabel.contentHeight + 10 186 | Rectangle { 187 | id: testResults 188 | anchors.verticalCenter: parent.verticalCenter 189 | anchors.margins: 5 190 | Layout.fillWidth: true 191 | anchors.left: parent.left 192 | anchors.fill: parent 193 | visible: false 194 | RowLayout { 195 | anchors.fill: parent 196 | Text { id: packetLossLabel; } 197 | Text { id: averageLatencyLabel; } 198 | Text { id: minLatencyLabel; } 199 | Text { id: maxLatencyLabel; } 200 | Text { id: averageJitterLabel; } 201 | } 202 | } 203 | } 204 | 205 | Rectangle { 206 | Layout.fillWidth: true 207 | Layout.fillHeight: true 208 | Layout.preferredHeight: 200 209 | Layout.minimumHeight: 200 210 | ScrollView { 211 | anchors.fill: parent 212 | 213 | ListView { 214 | id: logListView 215 | anchors.centerIn: parent 216 | model: client.logModel 217 | anchors.fill: parent 218 | 219 | delegate: Rectangle { 220 | id: delegateRect 221 | width: ListView.view.width 222 | height: logSummary.height 223 | 224 | ColumnLayout { 225 | anchors.fill: parent 226 | spacing: 0 227 | Rectangle { 228 | id: logSummary 229 | Layout.fillWidth: true 230 | 231 | implicitHeight: timing.contentHeight + 4 232 | color: (index % 2 == 1) ? "#e7e7fe" : "#faf0d7" 233 | 234 | Text { 235 | id: timing 236 | text: modelData.timing 237 | 238 | color: "black" 239 | font: Qt.font({ family: "Segoe UI, monospace" }) 240 | } 241 | 242 | Text { 243 | anchors.left: logSummary.left 244 | anchors.leftMargin: 80 245 | 246 | color: "black" 247 | font: Qt.font({ family: "Segoe UI, monospace" }) 248 | text: modelData.summary 249 | } 250 | 251 | MouseArea { 252 | anchors.fill: parent 253 | onClicked: { 254 | if (logDetail.children.length === 0) { 255 | var i; 256 | var heightAdd = 0; 257 | var detail = modelData.detail() 258 | for (i = 0; i < detail.length; i += 2) { 259 | var component = Qt.createComponent("qrc:/LogDetailItem.qml"); 260 | var object = component.createObject(logDetail); 261 | object.setText(detail[i], detail[i+1]) 262 | heightAdd += object.height 263 | } 264 | delegateRect.height += heightAdd 265 | logDetail.savedHeight = Qt.binding(function() { return heightAdd }) 266 | } else { 267 | if (!logDetail.visible) { 268 | delegateRect.height += logDetail.savedHeight 269 | logDetail.visible = true 270 | } else { 271 | delegateRect.height -= logDetail.savedHeight 272 | logDetail.visible = false 273 | } 274 | } 275 | } 276 | } 277 | 278 | } 279 | ColumnLayout { 280 | id: logDetail 281 | Layout.fillWidth: true 282 | Layout.alignment: Qt.AlignLeft | Qt.AlignBottom 283 | spacing: 0 284 | 285 | property int savedHeight: 0 286 | 287 | } 288 | } 289 | 290 | } 291 | } 292 | } 293 | } 294 | } 295 | 296 | } 297 | 298 | MessageDialog { 299 | id: messageBox; 300 | objectName: "msgBox"; 301 | icon: StandardIcon.Critical; 302 | 303 | title: "Error"; 304 | 305 | onAccepted: { 306 | close(); 307 | } 308 | } 309 | MessageDialog { 310 | id: aboutBox; 311 | objectName: "msgBox"; 312 | icon: StandardIcon.Information; 313 | 314 | title: "About Twamp Gui v1.0.7"; 315 | text: "Project home page:\nhttps://github.com/demirten/twamp-gui\n\nCopyright © Murat Demirten " 316 | 317 | onAccepted: { 318 | close(); 319 | } 320 | } 321 | 322 | } 323 | 324 | -------------------------------------------------------------------------------- /client/twamp_test_worker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "twamp_test_worker.h" 5 | 6 | #define MIN_UDP_PORT 40000 7 | #define MAX_UDP_PORT 60000 8 | 9 | TwampTestWorker::TwampTestWorker(bool lightMode, QHostAddress destinationHost, int port, unsigned int totalPackets, int interval, int payload) 10 | { 11 | this->lightMode = lightMode; 12 | this->destinationHost = destinationHost; 13 | this->port = port; 14 | this->totalPackets = totalPackets; 15 | this->interval = interval; 16 | this->payload = payload; 17 | 18 | sequence = 0; 19 | received_packets = 0; 20 | status = StatusUnknown; 21 | 22 | controlHandshakeTimer = new QTimer(this); 23 | controlHandshakeTimer->setSingleShot(true); 24 | connect(controlHandshakeTimer, SIGNAL(timeout()), this, SLOT(controlHandshakeTimerDone())); 25 | 26 | udpSendTimer = new QTimer(this); 27 | connect(udpSendTimer, SIGNAL(timeout()), this, SLOT(udpSendTimerDone())); 28 | 29 | maxTestWaitTimer = new QTimer(this); 30 | maxTestWaitTimer->setSingleShot(true); 31 | connect(maxTestWaitTimer, SIGNAL(timeout()), this, SLOT(maxTestWaitTimerDone())); 32 | 33 | udpSocket = new QUdpSocket(this); 34 | for (int i = MIN_UDP_PORT; i < MAX_UDP_PORT; i++) { 35 | if (udpSocket->bind(QHostAddress::Any, i)) { 36 | break; 37 | } 38 | } 39 | 40 | connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readReflectorMessage())); 41 | 42 | if (payload < (int) sizeof(struct twamp_message_test_unauthenticated)) { 43 | payload = sizeof(struct twamp_message_test_unauthenticated); 44 | padding_length = 0; 45 | } else { 46 | padding_length = payload - sizeof(struct twamp_message_test_unauthenticated); 47 | } 48 | message_length = sizeof(struct twamp_message_test_unauthenticated) + padding_length; 49 | message = (struct twamp_message_test_unauthenticated*) calloc(1, message_length); 50 | 51 | reflector_length = sizeof(struct twamp_message_reflector_unathenticated) + padding_length; 52 | reflector = (struct twamp_message_reflector_unathenticated*) calloc(1, reflector_length); 53 | 54 | packets.reserve(totalPackets); 55 | for (unsigned int i = 0; i < totalPackets; i++) { 56 | struct udpTestPacket packet; 57 | packet.valid = false; 58 | packets.append(packet); 59 | } 60 | 61 | elapsedTimer = new QElapsedTimer(); 62 | } 63 | 64 | TwampTestWorker::~TwampTestWorker() 65 | { 66 | free(message); 67 | free(reflector); 68 | } 69 | 70 | void TwampTestWorker::startControlHandshake() 71 | { 72 | status = HandshakeConnecting; 73 | controlSocket = new QTcpSocket(this); 74 | controlSocketBuffer.clear(); 75 | connect(controlSocket, SIGNAL(connected()), this, SLOT(controlSocketConnected())); 76 | connect(controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), 77 | this, SLOT(controlSocketError(QAbstractSocket::SocketError))); 78 | connect(controlSocket, SIGNAL(readyRead()), this, SLOT(controlSocketRead())); 79 | controlHandshakeTimer->start(5 * 1000); 80 | controlSocket->connectToHost(destinationHost, port); 81 | emit twampLogString("[TCP] Connecting to " + destinationHost.toString() + ":" + QString::number(port)); 82 | } 83 | 84 | void TwampTestWorker::controlSocketConnected() 85 | { 86 | controlHandshakeTimer->stop(); 87 | emit twampLogString("[TCP] Connected"); 88 | status = HandshakeServerGreeting; 89 | } 90 | 91 | void TwampTestWorker::controlSocketError(QAbstractSocket::SocketError error) 92 | { 93 | (void) error; 94 | emit errorMessage(controlSocket->errorString()); 95 | emit testFinished(); 96 | } 97 | 98 | void TwampTestWorker::controlSocketRead() 99 | { 100 | controlSocketBuffer.append(controlSocket->readAll()); 101 | if (status == HandshakeServerGreeting) { 102 | if (controlSocketBuffer.length() < (int) sizeof(struct twamp_message_server_greeting)) { 103 | emit twampLog(TwampLogPacket, "Received short Greeting, size: " + QString::number(controlSocketBuffer.length()), 104 | controlSocketBuffer, HandshakeError); 105 | return; 106 | } else { 107 | emit twampLog(TwampLogPacket, "", controlSocketBuffer, HandshakeServerGreeting); 108 | sendControlSetupResponse(); 109 | } 110 | } else if (status == HandshakeSetupResponse) { 111 | if (controlSocketBuffer.length() < (int) sizeof(struct twamp_message_server_start)) { 112 | emit twampLog(TwampLogPacket, "Received short Server-Start, size: " + QString::number(controlSocketBuffer.length()), 113 | controlSocketBuffer, HandshakeError); 114 | return; 115 | } else { 116 | emit twampLog(TwampLogPacket, "", controlSocketBuffer, HandshakeServerStart); 117 | struct twamp_message_server_start *start = (struct twamp_message_server_start*)controlSocketBuffer.constData(); 118 | if (start->accept == 0) { 119 | sendControlRequestSession(); 120 | } else { 121 | abortControlHandshake(); 122 | } 123 | } 124 | } else if (status == HandshakeRequestSession) { 125 | if (controlSocketBuffer.length() < (int)sizeof(struct twamp_message_accept_session)) { 126 | emit twampLog(TwampLogPacket, "Received short Accept-Session, size: " + QString::number(controlSocketBuffer.length()), 127 | controlSocketBuffer, HandshakeError); 128 | return; 129 | } else { 130 | emit twampLog(TwampLogPacket, "", controlSocketBuffer, HandshakeAcceptSession); 131 | struct twamp_message_accept_session *accept_session = (struct twamp_message_accept_session*)controlSocketBuffer.constData(); 132 | if (accept_session->accept == 0) { 133 | receiverUdpPort = qFromBigEndian(accept_session->port); 134 | sendControlStartSessions(); 135 | } else { 136 | abortControlHandshake(); 137 | } 138 | } 139 | } else if (status == HandshakeStartSession) { 140 | if (controlSocketBuffer.length() < (int)sizeof(struct twamp_message_start_ack)) { 141 | emit twampLog(TwampLogPacket, "Receiver short Start-Sessions-Ack, size: " + QString::number(controlSocketBuffer.length()), 142 | controlSocketBuffer, HandshakeError); 143 | return; 144 | } else { 145 | emit twampLog(TwampLogPacket, "", controlSocketBuffer, HandshakeStartSessionAck); 146 | udpSendTimer->start(interval); 147 | } 148 | } 149 | controlSocketBuffer.clear(); 150 | } 151 | 152 | void TwampTestWorker::abortControlHandshake(QString message) 153 | { 154 | emit errorMessage(message); 155 | emit testFinished(); 156 | controlHandshakeFinished(); 157 | } 158 | 159 | void TwampTestWorker::sendControlSetupResponse() 160 | { 161 | struct twamp_message_setup_response setup_response; 162 | quint32 mode = TWAMP_CONTROL_MODE_OPEN; 163 | memset(&setup_response, 0, sizeof(struct twamp_message_setup_response)); 164 | qToBigEndian(mode, (uchar*)&setup_response.mode); 165 | controlSocket->write((const char*)&setup_response, sizeof(setup_response)); 166 | status = HandshakeSetupResponse; 167 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&setup_response, sizeof(setup_response)), HandshakeSetupResponse); 168 | } 169 | 170 | void TwampTestWorker::sendControlRequestSession() 171 | { 172 | struct twamp_message_request_session request_session; 173 | memset(&request_session, 0, sizeof(struct twamp_message_request_session)); 174 | 175 | quint32 timeout_seconds = 5; 176 | quint32 timeout_fraction = 0; 177 | quint32 dscp = 0; 178 | 179 | twamp_time now = getTwampTime(); 180 | 181 | request_session.five = 5; 182 | request_session.conf_sender = 0; 183 | request_session.conf_receiver = 0; 184 | request_session.schedule_slots = 0; 185 | request_session.packets = 0; 186 | /* use same port for both sending and receiving */ 187 | qToBigEndian(udpSocket->localPort(), (uchar*)&request_session.sender_port); 188 | qToBigEndian(udpSocket->localPort(), (uchar*)&request_session.receiver_port); 189 | 190 | if (destinationHost.protocol() == QAbstractSocket::IPv6Protocol) { 191 | request_session.ipvn = 6; 192 | Q_IPV6ADDR ip6 = destinationHost.toIPv6Address(); 193 | memcpy(&request_session.receiver_address, &ip6, sizeof(ip6)); 194 | } else { 195 | request_session.ipvn = 4; 196 | qToBigEndian(destinationHost.toIPv4Address(), (uchar*)&request_session.receiver_address); 197 | } 198 | 199 | qToBigEndian(padding_length, (uchar*)&request_session.padding_length); 200 | 201 | qToBigEndian(now.seconds, (uchar*)&request_session.start_time.seconds); 202 | qToBigEndian(now.fraction, (uchar*)&request_session.start_time.fraction); 203 | 204 | qToBigEndian(timeout_seconds, (uchar*)&request_session.timeout.seconds); 205 | qToBigEndian(timeout_fraction, (uchar*)&request_session.timeout.fraction); 206 | 207 | qToBigEndian(dscp, (uchar*)&request_session.type_p_descriptor); 208 | 209 | controlSocket->write((const char*)&request_session, sizeof(request_session)); 210 | status = HandshakeRequestSession; 211 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&request_session, sizeof(request_session)), HandshakeRequestSession); 212 | } 213 | 214 | void TwampTestWorker::sendControlStartSessions() 215 | { 216 | struct twamp_message_start_sessions start_sessions; 217 | memset(&start_sessions, 0, sizeof(struct twamp_message_start_sessions)); 218 | start_sessions.two = 2; 219 | controlSocket->write((const char*)&start_sessions, sizeof(start_sessions)); 220 | status = HandshakeStartSession; 221 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&start_sessions, sizeof(start_sessions)), HandshakeStartSession); 222 | } 223 | 224 | void TwampTestWorker::sendControlStopSessions() 225 | { 226 | struct twamp_message_stop_sessions stop_sessions; 227 | memset(&stop_sessions, 0, sizeof(struct twamp_message_stop_sessions)); 228 | stop_sessions.three = 3; 229 | quint32 number_of_sessions = 1; 230 | qToBigEndian(number_of_sessions, (uchar *)&stop_sessions.number_of_sessions); 231 | controlSocket->write((const char*)&stop_sessions, sizeof(stop_sessions)); 232 | status = HandshakeStopSession; 233 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&stop_sessions, sizeof(stop_sessions)), HandshakeStopSession); 234 | } 235 | 236 | void TwampTestWorker::doTest() 237 | { 238 | elapsedTimer->start(); 239 | emit testStarted(totalPackets); 240 | if (lightMode) { 241 | receiverUdpPort = (quint16) port; 242 | udpSendTimer->start(interval); 243 | } else { 244 | startControlHandshake(); 245 | } 246 | } 247 | 248 | void TwampTestWorker::udpSendTimerDone() 249 | { 250 | if ((int)sequence >= packets.size()) { 251 | /* this shouldn't happen */ 252 | return; 253 | } 254 | 255 | struct udpTestPacket *packet = &packets[sequence]; 256 | 257 | struct twamp_time now = getTwampTime(); 258 | 259 | packet->sent = elapsedTimer->nsecsElapsed() / 1000; 260 | packet->index = sequence; 261 | qToBigEndian(sequence, (uchar*)&message->sequence_number); 262 | qToBigEndian(now.seconds, (uchar*)&message->timestamp.seconds); 263 | qToBigEndian(now.fraction, (uchar*)&message->timestamp.fraction); 264 | qToBigEndian(TwampCommon::getErrorEstimate(), (uchar*)&message->error_estimate); 265 | 266 | sequence++; 267 | 268 | udpSocket->writeDatagram((const char*)message, message_length, destinationHost, receiverUdpPort); 269 | 270 | emit packetSent(sequence, totalPackets); 271 | 272 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)message, message_length), TestPacketSent); 273 | 274 | if (sequence == totalPackets) { 275 | udpSendTimer->stop(); 276 | maxTestWaitTimer->start(3 * 1000); 277 | } 278 | QCoreApplication::processEvents(); 279 | } 280 | 281 | void TwampTestWorker::readReflectorMessage() 282 | { 283 | while (udpSocket->hasPendingDatagrams()) { 284 | qint64 now = elapsedTimer->nsecsElapsed() / 1000; 285 | 286 | udpSocket->readDatagram((char*)reflector, reflector_length); 287 | quint32 index = qFromBigEndian(reflector->sender_sequence_number); 288 | if (packets.size() <= (int)index) { 289 | emit twampLogString("Wrong sequence number received: " + QString::number(index)); 290 | return; 291 | } 292 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)reflector, reflector_length), TestPacketReceived); 293 | 294 | struct twamp_message_reflector_unathenticated *response = (struct twamp_message_reflector_unathenticated*) reflector; 295 | struct timeval remote_received, remote_send; 296 | struct twamp_time tw_received, tw_send; 297 | tw_received.seconds = qFromBigEndian(response->receive_timestamp.seconds); 298 | tw_received.fraction = qFromBigEndian(response->receive_timestamp.fraction); 299 | tw_send.seconds = qFromBigEndian(response->timestamp.seconds); 300 | tw_send.fraction = qFromBigEndian(response->timestamp.fraction); 301 | twampTimeToTimeval(&tw_received, &remote_received); 302 | twampTimeToTimeval(&tw_send, &remote_send); 303 | 304 | float remoteProcessingDelay = timevalDiff(&remote_received, &remote_send); 305 | 306 | struct udpTestPacket *packet = &packets[index]; 307 | packet->received = now; 308 | packet->latency = ((now - packet->sent) / 1000.0) - remoteProcessingDelay; 309 | packet->valid = true; 310 | received_packets++; 311 | emit newTestLatency(index, packet->latency); 312 | 313 | if ((index + 1) == totalPackets) { 314 | /* make chartjs happy again */ 315 | //emit newTestLatency(index + 1, packet->latency / 1000.0); 316 | processResults(); 317 | } 318 | } 319 | } 320 | 321 | double TwampTestWorker::getStdDev(double average) 322 | { 323 | double avg = 0; 324 | double large, small; 325 | double d_offset, d_square, d_variance = 0; 326 | struct udpTestPacket *packet; 327 | 328 | for (int i = 0; i < packets.size(); i++) { 329 | packet = &packets[i]; 330 | if (!packet->valid) { 331 | continue; 332 | } 333 | 334 | avg = average; 335 | if (packet->latency < avg) { 336 | small = packet->latency; 337 | large = avg; 338 | } else { 339 | large = packet->latency; 340 | small = avg; 341 | } 342 | large -= small; 343 | 344 | d_offset = large; 345 | d_square = d_offset * d_offset; 346 | d_variance += d_square; 347 | } 348 | return (double) qSqrt(d_variance / totalPackets); 349 | } 350 | 351 | void TwampTestWorker::processResults() 352 | { 353 | double total = 0; 354 | quint32 success = 0; 355 | min_latency = 0; 356 | max_latency = 0; 357 | average_latency = 0; 358 | average_jitter = 0; 359 | 360 | struct udpTestPacket *packet; 361 | 362 | maxTestWaitTimer->stop(); 363 | 364 | for (int i = 0; i < packets.size(); i++) { 365 | packet = &packets[i]; 366 | if (!packet->valid) { 367 | continue; 368 | } 369 | success++; 370 | total += packet->latency; 371 | 372 | if (min_latency == 0 && max_latency == 0) { 373 | /* first record */ 374 | min_latency = packet->latency; 375 | max_latency = packet->latency; 376 | } else if (packet->latency < min_latency) { 377 | min_latency = packet->latency; 378 | } else if (packet->latency > max_latency) { 379 | max_latency = packet->latency; 380 | } 381 | } 382 | if (success > 0) { 383 | average_latency = total / success; 384 | average_jitter = getStdDev(average_latency); 385 | } 386 | double packetLoss = 100.0f * (totalPackets - success) / totalPackets; 387 | emit testResultsCalculated(packetLoss, min_latency, max_latency, average_latency, average_jitter); 388 | 389 | if (!lightMode) { 390 | sendControlStopSessions(); 391 | controlHandshakeFinished(); 392 | } 393 | emit testFinished(); 394 | } 395 | 396 | void TwampTestWorker::controlHandshakeTimerDone() 397 | { 398 | abortControlHandshake("Twamp Control Handshake Timeout"); 399 | } 400 | 401 | void TwampTestWorker::controlHandshakeFinished() 402 | { 403 | if (controlSocket) { 404 | if (controlSocket->isOpen()) { 405 | controlSocket->flush(); 406 | controlSocket->close(); 407 | } 408 | controlSocket->deleteLater(); 409 | controlSocket = NULL; 410 | } 411 | } 412 | 413 | void TwampTestWorker::maxTestWaitTimerDone() 414 | { 415 | if (received_packets == 0) { 416 | emit errorMessage("No response received"); 417 | } 418 | processResults(); 419 | } 420 | 421 | 422 | -------------------------------------------------------------------------------- /responder/log_model_data.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_MODEL_DATA_H 2 | #define LOG_MODEL_DATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "twamp_common.h" 11 | 12 | class LogModelData : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | LogModelData(int type, 17 | QString message, 18 | QByteArray data, 19 | int status, 20 | double time) 21 | { 22 | mMessage = message; 23 | mData = data; 24 | mType = (enum TwampLogType) type; 25 | mStatus = (enum TestStatus) status; 26 | mTime = time; 27 | } 28 | 29 | Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged) 30 | Q_PROPERTY(QString timing READ timing NOTIFY timingChanged) 31 | 32 | QString summary() { 33 | QString str = ""; 34 | if (mType == TwampLogString) { 35 | str = mMessage; 36 | } else if (mStatus == TestPacketReceived) { 37 | struct twamp_message_test_unauthenticated *msg = (struct twamp_message_test_unauthenticated *)mData.constData(); 38 | str += "[TEST] Packet Received :: Sequence " + QString::number(qFromBigEndian(msg->sequence_number)); 39 | } else if (mStatus == TestPacketSent) { 40 | struct twamp_message_reflector_unathenticated *msg = (struct twamp_message_reflector_unathenticated *)mData.constData(); 41 | str += "[TEST] Packet Sent :: Sequence " + QString::number(qFromBigEndian(msg->sequence_number));; 42 | } else if (mStatus == HandshakeServerGreeting) { 43 | str += "[CONTROL] Sent Server-Greeting"; 44 | } else if (mStatus == HandshakeSetupResponse) { 45 | str += "[CONTROL] Received Setup-Response"; 46 | } else if (mStatus == HandshakeServerStart) { 47 | str += "[CONTROL] Sent Server-Start"; 48 | } else if (mStatus == HandshakeRequestSession) { 49 | str += "[CONTROL] Received Request-Session"; 50 | } else if (mStatus == HandshakeAcceptSession) { 51 | str += "[CONTROL] Sent Accept-Session"; 52 | } else if (mStatus == HandshakeStartSession) { 53 | str += "[CONTROL] Received Start-Sessions"; 54 | } else if (mStatus == HandshakeStartSessionAck) { 55 | str += "[CONTROL] Sent Start-Sessions-ACK"; 56 | } else if (mStatus == HandshakeStopSession) { 57 | str += "[CONTROL] Received Stop-Sessions"; 58 | } else if (mStatus == HandshakeError) { 59 | str += "[CONTROL] Handshake Error: " + mMessage; 60 | } 61 | return str; 62 | } 63 | QString timing() { 64 | return QString::number(mTime, 'f', 03); 65 | } 66 | Q_INVOKABLE QStringList detail() { 67 | QStringList items; 68 | if (mType == TwampLogString) { 69 | return items; 70 | } 71 | 72 | /* initialize variables for switch block */ 73 | struct twamp_message_server_greeting *greeting; 74 | struct twamp_message_setup_response *setup_response; 75 | struct twamp_message_server_start *server_start; 76 | struct twamp_message_request_session *request_session; 77 | struct twamp_message_accept_session *accept_session; 78 | struct twamp_message_start_sessions *start_sessions; 79 | struct twamp_message_start_ack *start_ack; 80 | struct twamp_message_stop_sessions *stop_sessions; 81 | struct twamp_message_test_unauthenticated *msg_test_unauthenticated; 82 | struct twamp_message_reflector_unathenticated *msg_reflector_unauthenticated; 83 | quint32 modes; 84 | quint32 mode; 85 | qint64 seconds; 86 | double msecs; 87 | QDateTime timestamp; 88 | QStringList tmpList; 89 | QHostAddress senderAddr; 90 | QHostAddress receiverAddr; 91 | 92 | switch (mStatus) { 93 | case HandshakeServerGreeting: 94 | greeting = (struct twamp_message_server_greeting *)mData.constData(); 95 | 96 | items.append("Unused"); 97 | items.append(TwampCommon::toHex((const char*)greeting->unused, sizeof(greeting->unused))); 98 | 99 | modes = qFromBigEndian(greeting->modes); 100 | items.append("Supported Modes"); 101 | tmpList.clear(); 102 | if (modes == TWAMP_CONTROL_MODE_UNDEFINED) { 103 | tmpList.append("UNDEFINED - Server doesn't want to talk with us"); 104 | } 105 | if (modes & TWAMP_CONTROL_MODE_OPEN) { 106 | tmpList.append("Unauthenticated"); 107 | } 108 | if (modes & TWAMP_CONTROL_MODE_AUTHENTICATED) { 109 | tmpList.append("Authenticated"); 110 | } 111 | if (modes & TWAMP_CONTROL_MODE_ENCRYPTED) { 112 | tmpList.append("Encrypted"); 113 | } 114 | items.append(QString::number(modes) + " (" + tmpList.join(" | ") + ")"); 115 | 116 | items.append("Challenge"); 117 | items.append(TwampCommon::toHex((const char*)greeting->challange, sizeof(greeting->challange))); 118 | 119 | items.append("Salt"); 120 | items.append(TwampCommon::toHex((const char*)greeting->salt, sizeof(greeting->salt))); 121 | 122 | items.append("Count"); 123 | items.append(QString::number(qFromBigEndian(greeting->count))); 124 | 125 | items.append("MBZ"); 126 | items.append(TwampCommon::toHex((const char*)greeting->mbz, sizeof(greeting->mbz))); 127 | break; 128 | case HandshakeSetupResponse: 129 | setup_response = (struct twamp_message_setup_response*)mData.constData(); 130 | mode = qFromBigEndian(setup_response->mode); 131 | items.append("Mode"); 132 | items.append(QString::number(mode)); 133 | 134 | items.append("Key ID"); 135 | items.append(TwampCommon::toHex((const char*)setup_response->keyid, sizeof(setup_response->keyid))); 136 | 137 | items.append("Token"); 138 | items.append(TwampCommon::toHex((const char*)setup_response->token, sizeof(setup_response->token))); 139 | 140 | items.append("Client IV"); 141 | items.append(TwampCommon::toHex((const char*)setup_response->client_iv, sizeof(setup_response->client_iv))); 142 | break; 143 | case HandshakeServerStart: 144 | server_start = (struct twamp_message_server_start*)mData.constData(); 145 | items.append("MBZ1"); 146 | items.append(TwampCommon::toHex((const char*)server_start->mbz, sizeof(server_start->mbz))); 147 | 148 | items.append("Accept"); 149 | items.append(QString::number(server_start->accept) + " (" + TwampCommon::acceptString(server_start->accept) + ")"); 150 | 151 | items.append("Server IV"); 152 | items.append(TwampCommon::toHex((const char*)server_start->server_iv, sizeof(server_start->server_iv))); 153 | 154 | seconds = qFromBigEndian(server_start->start_time.seconds) - TWAMP_BASE_TIME_OFFSET; 155 | msecs = qFromBigEndian(server_start->start_time.fraction) / TWAMP_FLOAT_DENOM; 156 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 157 | items.append("Server Start Time"); 158 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 159 | 160 | items.append("MBZ2"); 161 | items.append(TwampCommon::toHex((const char*)server_start->mbz_, sizeof(server_start->mbz_))); 162 | break; 163 | case HandshakeRequestSession: 164 | request_session = (struct twamp_message_request_session*)mData.constData(); 165 | items.append("Five"); 166 | items.append(QString::number(request_session->five)); 167 | 168 | items.append("IP Version"); 169 | items.append(QString::number(request_session->ipvn)); 170 | 171 | items.append("Conf Sender"); 172 | items.append(QString::number(request_session->conf_sender)); 173 | 174 | items.append("Conf Receiver"); 175 | items.append(QString::number(request_session->conf_receiver)); 176 | 177 | items.append("Number of Slots"); 178 | items.append(QString::number(qFromBigEndian(request_session->schedule_slots))); 179 | 180 | items.append("Number of Packets"); 181 | items.append(QString::number(qFromBigEndian(request_session->packets)) + " (must be zero on TWAMP)"); 182 | 183 | items.append("Sender Port"); 184 | items.append(QString::number(qFromBigEndian(request_session->sender_port))); 185 | 186 | items.append("Receiver Port"); 187 | items.append(QString::number(qFromBigEndian(request_session->receiver_port))); 188 | 189 | if (request_session->ipvn == 6) { 190 | senderAddr.setAddress((quint8*)&request_session->sender_address); 191 | receiverAddr.setAddress((quint8*)&request_session->receiver_address); 192 | } else { 193 | senderAddr.setAddress(qFromBigEndian((quint32)request_session->sender_address[0])); 194 | receiverAddr.setAddress(qFromBigEndian((quint32)request_session->receiver_address[0])); 195 | } 196 | items.append("Sender Address"); 197 | items.append(senderAddr.toString()); 198 | items.append("Receiver Address"); 199 | items.append(receiverAddr.toString()); 200 | 201 | items.append("Session ID"); 202 | items.append(TwampCommon::toHex((const char*)&request_session->sid, sizeof(request_session->sid))); 203 | 204 | items.append("Padding Length"); 205 | items.append(QString::number(qFromBigEndian(request_session->padding_length))); 206 | 207 | seconds = qFromBigEndian(request_session->start_time.seconds) - TWAMP_BASE_TIME_OFFSET; 208 | msecs = qFromBigEndian(request_session->start_time.fraction) / TWAMP_FLOAT_DENOM; 209 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 210 | items.append("Start Time"); 211 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 212 | 213 | seconds = qFromBigEndian(request_session->timeout.seconds); 214 | msecs = qFromBigEndian(request_session->timeout.fraction); 215 | items.append("Timeout"); 216 | items.append(QString::number(seconds) + "." + QString::number(msecs) + " seconds"); 217 | 218 | items.append("Type-P Descriptor"); 219 | items.append(QString::number(qFromBigEndian(request_session->type_p_descriptor))); 220 | 221 | items.append("MBZ"); 222 | items.append(TwampCommon::toHex((const char*)&request_session->mbz_, sizeof(request_session->mbz_))); 223 | 224 | items.append("HWMAC"); 225 | items.append(TwampCommon::toHex((const char*)&request_session->hwmac, sizeof(request_session->hwmac))); 226 | 227 | break; 228 | case HandshakeAcceptSession: 229 | accept_session = (struct twamp_message_accept_session *)mData.constData(); 230 | 231 | items.append("Accept"); 232 | items.append(QString::number(accept_session->accept) + " (" + TwampCommon::acceptString(accept_session->accept) + ")"); 233 | 234 | items.append("MBZ1"); 235 | items.append(TwampCommon::toHex((const char *)&accept_session->mbz, sizeof(accept_session->mbz))); 236 | 237 | items.append("Receiver Port"); 238 | items.append(QString::number(qFromBigEndian(accept_session->port))); 239 | 240 | items.append("Session ID"); 241 | items.append(TwampCommon::toHex((const char*)&accept_session->sid, sizeof(accept_session->sid))); 242 | 243 | items.append("MBZ2"); 244 | items.append(TwampCommon::toHex((const char*)&accept_session->mbz_, sizeof(accept_session->mbz_))); 245 | 246 | items.append("HWMAC"); 247 | items.append(TwampCommon::toHex((const char*)&accept_session->hwmac, sizeof(accept_session->hwmac))); 248 | break; 249 | case HandshakeStartSession: 250 | start_sessions = (struct twamp_message_start_sessions*)mData.constData(); 251 | items.append("Two"); 252 | items.append(QString::number(start_sessions->two)); 253 | 254 | items.append("MBZ"); 255 | items.append(TwampCommon::toHex((const char*)&start_sessions->mbz, sizeof(start_sessions->mbz))); 256 | 257 | items.append("HWMAC"); 258 | items.append(TwampCommon::toHex((const char*)&start_sessions->hwmac, sizeof(start_sessions->hwmac))); 259 | break; 260 | case HandshakeStartSessionAck: 261 | start_ack = (struct twamp_message_start_ack*)mData.constData(); 262 | items.append("Accept"); 263 | items.append(QString::number(start_ack->accept) + " (" + TwampCommon::acceptString(start_ack->accept) + ")"); 264 | 265 | items.append("MBZ"); 266 | items.append(TwampCommon::toHex((const char *)&start_ack->mbz, sizeof(start_ack->mbz))); 267 | 268 | items.append("HWMAC"); 269 | items.append(TwampCommon::toHex((const char*)&start_ack->hwmac, sizeof(start_ack->hwmac))); 270 | break; 271 | case HandshakeStopSession: 272 | stop_sessions = (struct twamp_message_stop_sessions*)mData.constData(); 273 | items.append("Three"); 274 | items.append(QString::number(stop_sessions->three)); 275 | 276 | items.append("Accept"); 277 | items.append(QString::number(stop_sessions->accept) + " (" + TwampCommon::acceptString(stop_sessions->accept) + ")"); 278 | 279 | items.append("MBZ1"); 280 | items.append(TwampCommon::toHex((const char *)&stop_sessions->mbz, sizeof(stop_sessions->mbz))); 281 | 282 | items.append("Number of Sessions"); 283 | items.append(QString::number(qFromBigEndian(stop_sessions->number_of_sessions))); 284 | 285 | items.append("MBZ2"); 286 | items.append(TwampCommon::toHex((const char *)&stop_sessions->mbz_, sizeof(stop_sessions->mbz_))); 287 | 288 | items.append("HWMAC"); 289 | items.append(TwampCommon::toHex((const char*)&stop_sessions->hwmac, sizeof(stop_sessions->hwmac))); 290 | 291 | break; 292 | case TestPacketReceived: 293 | msg_test_unauthenticated = (struct twamp_message_test_unauthenticated *)mData.constData(); 294 | items.append("Sequence"); 295 | items.append(QString::number(qFromBigEndian(msg_test_unauthenticated->sequence_number))); 296 | seconds = qFromBigEndian(msg_test_unauthenticated->timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 297 | msecs = qFromBigEndian(msg_test_unauthenticated->timestamp.fraction) / TWAMP_FLOAT_DENOM; 298 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 299 | items.append("Sent"); 300 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 301 | break; 302 | case TestPacketSent: 303 | msg_reflector_unauthenticated = (struct twamp_message_reflector_unathenticated*)mData.constData(); 304 | items.append("Sender Sequence"); 305 | items.append(QString::number(qFromBigEndian(msg_reflector_unauthenticated->sequence_number))); 306 | items.append("Sender TTL"); 307 | items.append(QString::number(msg_reflector_unauthenticated->sender_ttl)); 308 | seconds = qFromBigEndian(msg_reflector_unauthenticated->receive_timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 309 | msecs = qFromBigEndian(msg_reflector_unauthenticated->receive_timestamp.fraction) / TWAMP_FLOAT_DENOM; 310 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 311 | items.append("Receive Timestamp"); 312 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 313 | seconds = qFromBigEndian(msg_reflector_unauthenticated->timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 314 | msecs = qFromBigEndian(msg_reflector_unauthenticated->timestamp.fraction) / TWAMP_FLOAT_DENOM; 315 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 316 | items.append("Sent Timestamp"); 317 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 318 | items.append("Reflector Sequence"); 319 | items.append(QString::number(qFromBigEndian(msg_reflector_unauthenticated->sequence_number))); 320 | break; 321 | default: 322 | break; 323 | } 324 | return items; 325 | } 326 | 327 | private: 328 | QString mMessage; 329 | QByteArray mData; 330 | enum TwampLogType mType; 331 | enum TestStatus mStatus; 332 | double mTime; 333 | signals: 334 | void summaryChanged(); 335 | void timingChanged(); 336 | }; 337 | 338 | #endif // LOG_MODEL_DATA_H 339 | -------------------------------------------------------------------------------- /client/log_model_data.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_MODEL_DATA_H 2 | #define LOG_MODEL_DATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "twamp_common.h" 11 | 12 | class LogModelData : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | LogModelData(int type, 17 | QString message, 18 | QByteArray data, 19 | int status, 20 | double time) 21 | { 22 | mMessage = message; 23 | mData = data; 24 | mType = (enum TwampLogType) type; 25 | mStatus = (enum TestStatus) status; 26 | mTime = time; 27 | } 28 | 29 | Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged) 30 | Q_PROPERTY(QString timing READ timing NOTIFY timingChanged) 31 | 32 | QString summary() { 33 | QString str = ""; 34 | if (mType == TwampLogString) { 35 | str = mMessage; 36 | } else if (mStatus == TestPacketSent) { 37 | struct twamp_message_test_unauthenticated *msg = (struct twamp_message_test_unauthenticated *)mData.constData(); 38 | str += "[TEST] Packet Sent :: Sequence " + QString::number(qFromBigEndian(msg->sequence_number)); 39 | } else if (mStatus == TestPacketReceived) { 40 | struct twamp_message_reflector_unathenticated *msg = (struct twamp_message_reflector_unathenticated *)mData.constData(); 41 | str += "[TEST] Packet Received :: Sequence " + QString::number(qFromBigEndian(msg->sender_sequence_number));; 42 | } else if (mStatus == HandshakeServerGreeting) { 43 | str += "[CONTROL] Received Server-Greeting"; 44 | } else if (mStatus == HandshakeSetupResponse) { 45 | str += "[CONTROL] Sent Setup-Response"; 46 | } else if (mStatus == HandshakeServerStart) { 47 | str += "[CONTROL] Received Server-Start"; 48 | } else if (mStatus == HandshakeRequestSession) { 49 | str += "[CONTROL] Sent Request-Session"; 50 | } else if (mStatus == HandshakeAcceptSession) { 51 | str += "[CONTROL] Received Accept-Session"; 52 | } else if (mStatus == HandshakeStartSession) { 53 | str += "[CONTROL] Sent Start-Sessions"; 54 | } else if (mStatus == HandshakeStartSessionAck) { 55 | str += "[CONTROL] Received Start-Sessions-ACK"; 56 | } else if (mStatus == HandshakeStopSession) { 57 | str += "[CONTROL] Sent Stop-Sessions"; 58 | } else if (mStatus == HandshakeError) { 59 | str += "[CONTROL] Handshake Error: " + mMessage; 60 | } 61 | return str; 62 | } 63 | QString timing() { 64 | return QString::number(mTime, 'f', 03); 65 | } 66 | Q_INVOKABLE QStringList detail() { 67 | QStringList items; 68 | if (mType == TwampLogString) { 69 | return items; 70 | } 71 | 72 | /* initialize variables for switch block */ 73 | struct twamp_message_server_greeting *greeting; 74 | struct twamp_message_setup_response *setup_response; 75 | struct twamp_message_server_start *server_start; 76 | struct twamp_message_request_session *request_session; 77 | struct twamp_message_accept_session *accept_session; 78 | struct twamp_message_start_sessions *start_sessions; 79 | struct twamp_message_start_ack *start_ack; 80 | struct twamp_message_stop_sessions *stop_sessions; 81 | struct twamp_message_test_unauthenticated *msg_test_unauthenticated; 82 | struct twamp_message_reflector_unathenticated *msg_reflector_unauthenticated; 83 | quint32 modes; 84 | quint32 mode; 85 | qint64 seconds; 86 | double msecs; 87 | QDateTime timestamp; 88 | QStringList tmpList; 89 | QHostAddress senderAddr; 90 | QHostAddress receiverAddr; 91 | 92 | switch (mStatus) { 93 | case HandshakeServerGreeting: 94 | greeting = (struct twamp_message_server_greeting *)mData.constData(); 95 | 96 | items.append("Unused"); 97 | items.append(TwampCommon::toHex((const char*)greeting->unused, sizeof(greeting->unused))); 98 | 99 | modes = qFromBigEndian(greeting->modes); 100 | items.append("Supported Modes"); 101 | tmpList.clear(); 102 | if (modes == TWAMP_CONTROL_MODE_UNDEFINED) { 103 | tmpList.append("UNDEFINED - Server doesn't want to talk with us"); 104 | } 105 | if (modes & TWAMP_CONTROL_MODE_OPEN) { 106 | tmpList.append("Unauthenticated"); 107 | } 108 | if (modes & TWAMP_CONTROL_MODE_AUTHENTICATED) { 109 | tmpList.append("Authenticated"); 110 | } 111 | if (modes & TWAMP_CONTROL_MODE_ENCRYPTED) { 112 | tmpList.append("Encrypted"); 113 | } 114 | items.append(QString::number(modes) + " (" + tmpList.join(" | ") + ")"); 115 | 116 | items.append("Challenge"); 117 | items.append(TwampCommon::toHex((const char*)greeting->challange, sizeof(greeting->challange))); 118 | 119 | items.append("Salt"); 120 | items.append(TwampCommon::toHex((const char*)greeting->salt, sizeof(greeting->salt))); 121 | 122 | items.append("Count"); 123 | items.append(QString::number(qFromBigEndian(greeting->count))); 124 | 125 | items.append("MBZ"); 126 | items.append(TwampCommon::toHex((const char*)greeting->mbz, sizeof(greeting->mbz))); 127 | break; 128 | case HandshakeSetupResponse: 129 | setup_response = (struct twamp_message_setup_response*)mData.constData(); 130 | mode = qFromBigEndian(setup_response->mode); 131 | items.append("Mode"); 132 | items.append(QString::number(mode)); 133 | 134 | items.append("Key ID"); 135 | items.append(TwampCommon::toHex((const char*)setup_response->keyid, sizeof(setup_response->keyid))); 136 | 137 | items.append("Token"); 138 | items.append(TwampCommon::toHex((const char*)setup_response->token, sizeof(setup_response->token))); 139 | 140 | items.append("Client IV"); 141 | items.append(TwampCommon::toHex((const char*)setup_response->client_iv, sizeof(setup_response->client_iv))); 142 | break; 143 | case HandshakeServerStart: 144 | server_start = (struct twamp_message_server_start*)mData.constData(); 145 | items.append("MBZ1"); 146 | items.append(TwampCommon::toHex((const char*)server_start->mbz, sizeof(server_start->mbz))); 147 | 148 | items.append("Accept"); 149 | items.append(QString::number(server_start->accept) + " (" + TwampCommon::acceptString(server_start->accept) + ")"); 150 | 151 | items.append("Server IV"); 152 | items.append(TwampCommon::toHex((const char*)server_start->server_iv, sizeof(server_start->server_iv))); 153 | 154 | seconds = qFromBigEndian(server_start->start_time.seconds) - TWAMP_BASE_TIME_OFFSET; 155 | msecs = qFromBigEndian(server_start->start_time.fraction) / TWAMP_FLOAT_DENOM; 156 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 157 | items.append("Server Start Time"); 158 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 159 | if (seconds > ((QDateTime::currentMSecsSinceEpoch() / 1000) + (5 * 365 * 86400))) { 160 | /* 5 year threshold enough to check violation */ 161 | items.append("Warning"); 162 | items.append("Server not using NTP Timestamps, violetes RFC"); 163 | } 164 | 165 | items.append("MBZ2"); 166 | items.append(TwampCommon::toHex((const char*)server_start->mbz_, sizeof(server_start->mbz_))); 167 | break; 168 | case HandshakeRequestSession: 169 | request_session = (struct twamp_message_request_session*)mData.constData(); 170 | items.append("Five"); 171 | items.append(QString::number(request_session->five)); 172 | 173 | items.append("IP Version"); 174 | items.append(QString::number(request_session->ipvn)); 175 | 176 | items.append("Conf Sender"); 177 | items.append(QString::number(request_session->conf_sender)); 178 | 179 | items.append("Conf Receiver"); 180 | items.append(QString::number(request_session->conf_receiver)); 181 | 182 | items.append("Number of Slots"); 183 | items.append(QString::number(qFromBigEndian(request_session->schedule_slots))); 184 | 185 | items.append("Number of Packets"); 186 | items.append(QString::number(qFromBigEndian(request_session->packets)) + " (must be zero on TWAMP)"); 187 | 188 | items.append("Sender Port"); 189 | items.append(QString::number(qFromBigEndian(request_session->sender_port))); 190 | 191 | items.append("Receiver Port"); 192 | items.append(QString::number(qFromBigEndian(request_session->receiver_port))); 193 | 194 | if (request_session->ipvn == 6) { 195 | senderAddr.setAddress((quint8*)&request_session->sender_address); 196 | receiverAddr.setAddress((quint8*)&request_session->receiver_address); 197 | } else { 198 | senderAddr.setAddress(qFromBigEndian((quint32)request_session->sender_address[0])); 199 | receiverAddr.setAddress(qFromBigEndian((quint32)request_session->receiver_address[0])); 200 | } 201 | items.append("Sender Address"); 202 | items.append(senderAddr.toString()); 203 | items.append("Receiver Address"); 204 | items.append(receiverAddr.toString()); 205 | 206 | items.append("Session ID"); 207 | items.append(TwampCommon::toHex((const char*)&request_session->sid, sizeof(request_session->sid))); 208 | 209 | items.append("Padding Length"); 210 | items.append(QString::number(qFromBigEndian(request_session->padding_length))); 211 | 212 | seconds = qFromBigEndian(request_session->start_time.seconds) - TWAMP_BASE_TIME_OFFSET; 213 | msecs = qFromBigEndian(request_session->start_time.fraction) / TWAMP_FLOAT_DENOM; 214 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 215 | items.append("Start Time"); 216 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 217 | 218 | seconds = qFromBigEndian(request_session->timeout.seconds); 219 | msecs = qFromBigEndian(request_session->timeout.fraction); 220 | items.append("Timeout"); 221 | items.append(QString::number(seconds) + "." + QString::number(msecs) + " seconds"); 222 | 223 | items.append("Type-P Descriptor"); 224 | items.append(QString::number(qFromBigEndian(request_session->type_p_descriptor))); 225 | 226 | items.append("MBZ"); 227 | items.append(TwampCommon::toHex((const char*)&request_session->mbz_, sizeof(request_session->mbz_))); 228 | 229 | items.append("HWMAC"); 230 | items.append(TwampCommon::toHex((const char*)&request_session->hwmac, sizeof(request_session->hwmac))); 231 | 232 | break; 233 | case HandshakeAcceptSession: 234 | accept_session = (struct twamp_message_accept_session *)mData.constData(); 235 | 236 | items.append("Accept"); 237 | items.append(QString::number(accept_session->accept) + " (" + TwampCommon::acceptString(accept_session->accept) + ")"); 238 | 239 | items.append("MBZ1"); 240 | items.append(TwampCommon::toHex((const char *)&accept_session->mbz, sizeof(accept_session->mbz))); 241 | 242 | items.append("Receiver Port"); 243 | items.append(QString::number(qFromBigEndian(accept_session->port))); 244 | 245 | items.append("Session ID"); 246 | items.append(TwampCommon::toHex((const char*)&accept_session->sid, sizeof(accept_session->sid))); 247 | 248 | items.append("MBZ2"); 249 | items.append(TwampCommon::toHex((const char*)&accept_session->mbz_, sizeof(accept_session->mbz_))); 250 | 251 | items.append("HWMAC"); 252 | items.append(TwampCommon::toHex((const char*)&accept_session->hwmac, sizeof(accept_session->hwmac))); 253 | break; 254 | case HandshakeStartSession: 255 | start_sessions = (struct twamp_message_start_sessions*)mData.constData(); 256 | items.append("Two"); 257 | items.append(QString::number(start_sessions->two)); 258 | 259 | items.append("MBZ"); 260 | items.append(TwampCommon::toHex((const char*)&start_sessions->mbz, sizeof(start_sessions->mbz))); 261 | 262 | items.append("HWMAC"); 263 | items.append(TwampCommon::toHex((const char*)&start_sessions->hwmac, sizeof(start_sessions->hwmac))); 264 | break; 265 | case HandshakeStartSessionAck: 266 | start_ack = (struct twamp_message_start_ack*)mData.constData(); 267 | items.append("Accept"); 268 | items.append(QString::number(start_ack->accept) + " (" + TwampCommon::acceptString(start_ack->accept) + ")"); 269 | 270 | items.append("MBZ"); 271 | items.append(TwampCommon::toHex((const char *)&start_ack->mbz, sizeof(start_ack->mbz))); 272 | 273 | items.append("HWMAC"); 274 | items.append(TwampCommon::toHex((const char*)&start_ack->hwmac, sizeof(start_ack->hwmac))); 275 | break; 276 | case HandshakeStopSession: 277 | stop_sessions = (struct twamp_message_stop_sessions*)mData.constData(); 278 | items.append("Three"); 279 | items.append(QString::number(stop_sessions->three)); 280 | 281 | items.append("Accept"); 282 | items.append(QString::number(stop_sessions->accept) + " (" + TwampCommon::acceptString(stop_sessions->accept) + ")"); 283 | 284 | items.append("MBZ1"); 285 | items.append(TwampCommon::toHex((const char *)&stop_sessions->mbz, sizeof(stop_sessions->mbz))); 286 | 287 | items.append("Number of Sessions"); 288 | items.append(QString::number(qFromBigEndian(stop_sessions->number_of_sessions))); 289 | 290 | items.append("MBZ2"); 291 | items.append(TwampCommon::toHex((const char *)&stop_sessions->mbz_, sizeof(stop_sessions->mbz_))); 292 | 293 | items.append("HWMAC"); 294 | items.append(TwampCommon::toHex((const char*)&stop_sessions->hwmac, sizeof(stop_sessions->hwmac))); 295 | 296 | break; 297 | case TestPacketSent: 298 | msg_test_unauthenticated = (struct twamp_message_test_unauthenticated *)mData.constData(); 299 | items.append("Sequence"); 300 | items.append(QString::number(qFromBigEndian(msg_test_unauthenticated->sequence_number))); 301 | seconds = qFromBigEndian(msg_test_unauthenticated->timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 302 | msecs = qFromBigEndian(msg_test_unauthenticated->timestamp.fraction) / TWAMP_FLOAT_DENOM; 303 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 304 | items.append("Sent"); 305 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 306 | break; 307 | case TestPacketReceived: 308 | msg_reflector_unauthenticated = (struct twamp_message_reflector_unathenticated*)mData.constData(); 309 | items.append("Sender Sequence"); 310 | items.append(QString::number(qFromBigEndian(msg_reflector_unauthenticated->sender_sequence_number))); 311 | items.append("Sender TTL"); 312 | items.append(QString::number(msg_reflector_unauthenticated->sender_ttl)); 313 | seconds = qFromBigEndian(msg_reflector_unauthenticated->receive_timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 314 | msecs = qFromBigEndian(msg_reflector_unauthenticated->receive_timestamp.fraction) / TWAMP_FLOAT_DENOM; 315 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 316 | items.append("Receive Timestamp"); 317 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 318 | if (seconds > ((QDateTime::currentMSecsSinceEpoch() / 1000) + (5 * 365 * 86400))) { 319 | /* 5 year threshold enough to check violation */ 320 | items.append("Warning"); 321 | items.append("Server not using NTP Timestamps, violetes RFC"); 322 | } 323 | seconds = qFromBigEndian(msg_reflector_unauthenticated->timestamp.seconds) - TWAMP_BASE_TIME_OFFSET; 324 | msecs = qFromBigEndian(msg_reflector_unauthenticated->timestamp.fraction) / TWAMP_FLOAT_DENOM; 325 | timestamp = QDateTime::fromMSecsSinceEpoch(seconds * 1000 + msecs / 1000); 326 | items.append("Sent Timestamp"); 327 | items.append(timestamp.toString("dd.MM.yyyy hh:mm:ss.zzz")); 328 | items.append("Reflector Sequence"); 329 | items.append(QString::number(qFromBigEndian(msg_reflector_unauthenticated->sequence_number))); 330 | break; 331 | default: 332 | break; 333 | } 334 | return items; 335 | } 336 | 337 | private: 338 | QString mMessage; 339 | QByteArray mData; 340 | enum TwampLogType mType; 341 | enum TestStatus mStatus; 342 | double mTime; 343 | signals: 344 | void summaryChanged(); 345 | void timingChanged(); 346 | }; 347 | 348 | #endif // LOG_MODEL_DATA_H 349 | -------------------------------------------------------------------------------- /responder/twamp_responder_worker.cpp: -------------------------------------------------------------------------------- 1 | #include "twamp_responder_worker.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define MIN_UDP_PORT 8000 7 | #define MAX_UDP_PORT 65000 8 | 9 | TwampResponderWorker::TwampResponderWorker(int controlPort, int lightPort) 10 | { 11 | running = false; 12 | mStartErrors = ""; 13 | 14 | this->controlPort = controlPort; 15 | this->lightPort = lightPort; 16 | 17 | controlServer = NULL; 18 | udpLightServer = NULL; 19 | 20 | removeIdleClientsTimer = new QTimer(this); 21 | connect(removeIdleClientsTimer, SIGNAL(timeout()), this, SLOT(removeIdleClientsTimerDone())); 22 | removeIdleClientsTimer->start(10 * 1000); 23 | 24 | /* bind control tcp socket server */ 25 | controlServer = new QTcpServer(this); 26 | if (!controlServer->listen(QHostAddress::Any, controlPort)) { 27 | mStartErrors = "Couldn't listen to tcp port: " + QString::number(controlPort); 28 | if (controlPort < 1024) { 29 | mStartErrors += "\nListening ports below <1024 requires admin privileges"; 30 | } 31 | return; 32 | } 33 | udpLightServer = new QUdpSocket(this); 34 | if (!udpLightServer->bind(QHostAddress::Any, lightPort)) { 35 | mStartErrors = "Couldn't listen to udp port: " + QString::number(lightPort); 36 | if (lightPort < 1024) { 37 | mStartErrors += "\nListening ports below <1024 requires admin privileges"; 38 | } 39 | return; 40 | } 41 | } 42 | 43 | TwampResponderWorker::~TwampResponderWorker() 44 | { 45 | stopServer(); 46 | } 47 | 48 | QString TwampResponderWorker::startErrors() 49 | { 50 | return mStartErrors; 51 | } 52 | 53 | void TwampResponderWorker::startServer() 54 | { 55 | instanceStarted = getTwampTime(); 56 | emit twampLogString("Responder started"); 57 | connect(controlServer, SIGNAL(newConnection()), this, SLOT(acceptNewControlClient())); 58 | connect(udpLightServer, SIGNAL(readyRead()), this, SLOT(twampLightRead())); 59 | emit responderStarted(); 60 | } 61 | 62 | void TwampResponderWorker::stopServer() 63 | { 64 | if (controlServer == NULL && udpLightServer == NULL) { 65 | return; 66 | } 67 | if (controlServer) { 68 | controlServer->disconnect(); 69 | controlServer->deleteLater(); 70 | controlServer = NULL; 71 | } 72 | if (udpLightServer) { 73 | udpLightServer->disconnect(); 74 | udpLightServer->deleteLater(); 75 | udpLightServer = NULL; 76 | } 77 | QMutexLocker locker(&clientMutex); 78 | foreach (Client client, mClients) { 79 | stopClient(&client); 80 | } 81 | mClients.clear(); 82 | emit twampLogString("Responder stopped"); 83 | emit responderStopped(); 84 | } 85 | 86 | void TwampResponderWorker::acceptNewControlClient() 87 | { 88 | QTcpSocket *socket = controlServer->nextPendingConnection(); 89 | Client client; 90 | client.controlSocket = socket; 91 | client.testSocket = NULL; 92 | client.address = socket->peerAddress().toString(); 93 | client.port = socket->peerPort(); 94 | client.lastUpdate = QDateTime::currentMSecsSinceEpoch(); 95 | client.setupResponseReceived = false; 96 | mClients.append(client); 97 | 98 | connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected())); 99 | connect(socket, SIGNAL(readyRead()), this, SLOT(clientRead())); 100 | 101 | emit twampLogString("[Control-TCP] New client accepted from: " + 102 | client.address + ":" + 103 | QString::number(client.port)); 104 | sendGreeting(&client); 105 | } 106 | 107 | void TwampResponderWorker::clientDisconnected() 108 | { 109 | QTcpSocket *socket = (QTcpSocket*) sender(); 110 | Client *client = getClientFromControlSocket(socket); 111 | if (client == NULL) { 112 | emit twampLogString("Couldn't get client from control socket: " + 113 | socket->peerAddress().toString() + ":" + 114 | QString::number(socket->peerPort())); 115 | return; 116 | } 117 | emit twampLogString("Client disconnected: " + client->address + ":" + QString::number(client->port)); 118 | removeClient(client); 119 | } 120 | 121 | void TwampResponderWorker::clientRead() 122 | { 123 | QTcpSocket *socket = (QTcpSocket*) sender(); 124 | QByteArray response = socket->readAll(); 125 | 126 | Client *client = getClientFromControlSocket(socket); 127 | if (client == NULL) { 128 | emit twampLogString("Couldn't get client from control socket: " + 129 | socket->peerAddress().toString() + ":" + 130 | QString::number(socket->peerPort())); 131 | socket->close(); 132 | socket->disconnect(); 133 | return; 134 | } 135 | 136 | if (!client->setupResponseReceived) { 137 | if (response.length() < (int) sizeof(struct twamp_message_setup_response)) { 138 | emit twampLog(TwampLogPacket, "Received short Setup-Response", response, HandshakeError); 139 | } else { 140 | emit twampLog(TwampLogPacket, "", response, HandshakeSetupResponse); 141 | client->setupResponseReceived = true; 142 | sendServerStart(client); 143 | } 144 | } else { 145 | quint8 type = *(quint8*)response.constData(); 146 | switch (type) { 147 | case TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_REQUEST_SESSION: 148 | if (response.length() < (int) sizeof(struct twamp_message_request_session)) { 149 | emit twampLog(TwampLogPacket, "Received short Request-Sessions", response, HandshakeError); 150 | } else { 151 | emit twampLog(TwampLogPacket, "", response, HandshakeRequestSession); 152 | struct twamp_message_request_session *session = (struct twamp_message_request_session*)response.constData(); 153 | if (session->packets != 0) { 154 | emit twampLogString("Number of Packets must be zero in TwampControl"); 155 | return; 156 | } 157 | client->testUdpPort = qFromBigEndian(session->sender_port); 158 | sendAcceptSession(client, qFromBigEndian(session->receiver_port)); 159 | } 160 | break; 161 | case TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_START_SESSIONS: 162 | if (response.length() < (int) sizeof(struct twamp_message_start_sessions)) { 163 | emit twampLog(TwampLogPacket, "Received short Start-Sessions", response, HandshakeError); 164 | } else { 165 | emit twampLog(TwampLogPacket, "", response, HandshakeStartSession); 166 | sendStartSessionsAck(client); 167 | } 168 | break; 169 | case TWAMP_CONTROL_PROTOCOL_PACKET_TYPE_STOP_SESSIONS: 170 | if (response.length() < (int) sizeof(struct twamp_message_stop_sessions)) { 171 | emit twampLog(TwampLogPacket, "Received short Stop-Sessions", response, HandshakeError); 172 | } else { 173 | emit twampLog(TwampLogPacket, "", response, HandshakeStopSession); 174 | socket->close(); 175 | } 176 | break; 177 | default: 178 | emit twampLogString("Uknown Twamp Protocol Sub-Type Received: " + QString::number(type)); 179 | socket->close(); 180 | break; 181 | } 182 | } 183 | } 184 | 185 | void TwampResponderWorker::removeIdleClientsTimerDone() 186 | { 187 | QList willRemove; 188 | qint64 now = QDateTime::currentMSecsSinceEpoch(); 189 | Client client; 190 | 191 | QMutexLocker locker(&clientMutex); 192 | 193 | for (int i = 0; i < mClients.size(); i++) { 194 | client = mClients[i]; 195 | if (client.lastUpdate > 0 && ((now - client.lastUpdate) > 15 * 1000)) { 196 | if (client.controlSocket) { 197 | emit twampLogString("Closing IDLE connection from " + client.address + ":" + QString::number(client.port)); 198 | client.controlSocket->disconnect(); 199 | client.controlSocket->close(); 200 | client.controlSocket->deleteLater(); 201 | } 202 | if (client.testSocket) { 203 | client.testSocket->disconnect(); 204 | client.testSocket->close(); 205 | client.testSocket->deleteLater(); 206 | } 207 | willRemove.push_front(i); 208 | } 209 | } 210 | foreach (int index, willRemove) { 211 | mClients.removeAt(index); 212 | } 213 | } 214 | 215 | void TwampResponderWorker::sendGreeting(Client *client) 216 | { 217 | if (client == NULL || client->controlSocket == NULL) { 218 | return; 219 | } 220 | qint64 random; 221 | int random1; 222 | int random2; 223 | struct twamp_message_server_greeting greeting; 224 | memset(&greeting, 0, sizeof(greeting)); 225 | 226 | qToBigEndian(TWAMP_CONTROL_MODE_OPEN, (uchar*)&greeting.modes); 227 | 228 | random = QDateTime::currentMSecsSinceEpoch(); 229 | memcpy(&greeting.challange, &random, 8); 230 | random = QDateTime::currentMSecsSinceEpoch(); 231 | memcpy(&greeting.challange[8], &random, 8); 232 | random = QDateTime::currentMSecsSinceEpoch(); 233 | memcpy(&greeting.salt, &random, 8); 234 | random1 = QRandomGenerator::global()->generate(); 235 | random2 = QRandomGenerator::global()->generate(); 236 | memcpy(&greeting.salt[8], &random1, 4); 237 | memcpy(&greeting.salt[12], &random2, 4); 238 | 239 | greeting.count = (1 << 12); 240 | 241 | client->controlSocket->write((const char*)&greeting, sizeof(greeting)); 242 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&greeting, sizeof(greeting)), HandshakeServerGreeting); 243 | } 244 | 245 | void TwampResponderWorker::sendServerStart(Client *client) 246 | { 247 | if (client == NULL || client->controlSocket == NULL) { 248 | return; 249 | } 250 | struct twamp_message_server_start start; 251 | memset(&start, 0, sizeof(start)); 252 | start.accept = 0; 253 | qToBigEndian(instanceStarted.seconds, (uchar*)&start.start_time.seconds); 254 | qToBigEndian(instanceStarted.fraction, (uchar*)&start.start_time.fraction); 255 | 256 | client->controlSocket->write((const char*)&start, sizeof(start)); 257 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&start, sizeof(start)), HandshakeServerStart); 258 | } 259 | 260 | void TwampResponderWorker::sendAcceptSession(Client *client, quint16 prefferedPort) 261 | { 262 | if (client == NULL || client->controlSocket == NULL) { 263 | return; 264 | } 265 | if (client->testSocket) { 266 | client->testSocket->close(); 267 | client->testSocket->deleteLater(); 268 | client->testSocket = NULL; 269 | } 270 | 271 | QUdpSocket *socket = new QUdpSocket(this); 272 | if (!socket->bind(QHostAddress::Any, prefferedPort)) { 273 | for (int i = MIN_UDP_PORT; i < MAX_UDP_PORT; i++) { 274 | if (socket->bind(QHostAddress::Any, i)) { 275 | break; 276 | } 277 | } 278 | } 279 | client->testSocket = socket; 280 | 281 | struct twamp_message_accept_session accept; 282 | memset(&accept, 0, sizeof(accept)); 283 | accept.accept = 0; 284 | qToBigEndian(socket->localPort(), (uchar*)&accept.port); 285 | for (int i = 0; i < 16; i++) { 286 | accept.sid.sid[i] = QRandomGenerator::global()->generate() & 0xFF; 287 | } 288 | 289 | connect(socket, SIGNAL(readyRead()), this, SLOT(clientTestPacketRead())); 290 | 291 | client->controlSocket->write((const char*)&accept, sizeof(accept)); 292 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&accept, sizeof(accept)), HandshakeAcceptSession); 293 | } 294 | 295 | void TwampResponderWorker::sendStartSessionsAck(Client *client) 296 | { 297 | struct twamp_message_start_ack ack; 298 | memset(&ack, 0, sizeof(ack)); 299 | ack.accept = 0; 300 | 301 | client->controlSocket->write((const char*)&ack, sizeof(ack)); 302 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)&ack, sizeof(ack)), HandshakeStartSessionAck); 303 | } 304 | 305 | Client * TwampResponderWorker::getClientFromControlSocket(QTcpSocket *socket) 306 | { 307 | Client *c; 308 | QMutexLocker locker(&clientMutex); 309 | for (int i = 0; i < mClients.size(); i++) { 310 | c = &mClients[i]; 311 | if (c->controlSocket == socket) { 312 | return c; 313 | } 314 | } 315 | return NULL; 316 | } 317 | 318 | Client * TwampResponderWorker::getClientFromTestSocket(QUdpSocket *socket) 319 | { 320 | Client *c; 321 | QMutexLocker locker(&clientMutex); 322 | for (int i = 0; i < mClients.size(); i++) { 323 | c = &mClients[i]; 324 | if (c->testSocket == socket) { 325 | return c; 326 | } 327 | } 328 | return NULL; 329 | } 330 | 331 | void TwampResponderWorker::stopClient(Client *client) 332 | { 333 | if (client->controlSocket) { 334 | client->controlSocket->disconnect(); 335 | client->controlSocket->close(); 336 | client->controlSocket->deleteLater(); 337 | client->controlSocket = NULL; 338 | } 339 | if (client->testSocket) { 340 | client->testSocket->disconnect(); 341 | client->testSocket->close(); 342 | client->testSocket->deleteLater(); 343 | client->testSocket = NULL; 344 | } 345 | } 346 | 347 | void TwampResponderWorker::removeClient(Client *client) 348 | { 349 | const Client *c; 350 | QMutexLocker locker(&clientMutex); 351 | 352 | stopClient(client); 353 | 354 | for (int i = 0; i < mClients.size(); i++) { 355 | c = &mClients.at(i); 356 | if (c == client) { 357 | mClients.removeAt(i); 358 | break; 359 | } 360 | } 361 | } 362 | 363 | void TwampResponderWorker::clientTestPacketRead() 364 | { 365 | struct twamp_time received = getTwampTime(); 366 | 367 | QUdpSocket *socket = (QUdpSocket*) sender(); 368 | 369 | char buf[1500]; 370 | int payload = 0; 371 | int rc = socket->readDatagram(buf, sizeof(buf)); 372 | if (rc > (int)sizeof(struct twamp_message_reflector_unathenticated)) { 373 | payload = rc - (int)sizeof(struct twamp_message_reflector_unathenticated); 374 | } 375 | 376 | struct twamp_message_test_unauthenticated *msg = (struct twamp_message_test_unauthenticated*)buf; 377 | 378 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)buf, rc), TestPacketReceived); 379 | 380 | Client *client = getClientFromTestSocket(socket); 381 | if (client == NULL) { 382 | emit twampLogString("Couldn't find client info from udp test socket"); 383 | return; 384 | } 385 | 386 | char outbuf[sizeof(struct twamp_message_reflector_unathenticated) + payload]; 387 | memset(outbuf, 0, sizeof(outbuf)); 388 | 389 | struct twamp_message_reflector_unathenticated *response = (struct twamp_message_reflector_unathenticated *) outbuf; 390 | 391 | struct twamp_time reflected = getTwampTime(); 392 | response->sequence_number = msg->sequence_number; 393 | qToBigEndian(reflected.seconds, (uchar*)&response->timestamp.seconds); 394 | qToBigEndian(reflected.fraction, (uchar*)&response->timestamp.fraction); 395 | qToBigEndian(received.seconds, (uchar*)&response->receive_timestamp.seconds); 396 | qToBigEndian(received.fraction, (uchar*)&response->receive_timestamp.fraction); 397 | response->sender_sequence_number = msg->sequence_number; 398 | response->sender_ttl = 255; 399 | response->sender_timestamp.seconds = msg->timestamp.seconds; 400 | response->sender_timestamp.fraction = msg->timestamp.fraction; 401 | response->sender_error_estimate = msg->error_estimate; 402 | qToBigEndian(TwampCommon::getErrorEstimate(), (uchar*)&response->error_estimate); 403 | 404 | socket->writeDatagram((const char*)outbuf, sizeof(outbuf), client->controlSocket->peerAddress(), client->testUdpPort); 405 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)outbuf, sizeof(outbuf)), TestPacketSent); 406 | } 407 | 408 | void TwampResponderWorker::twampLightRead() 409 | { 410 | struct twamp_time received = getTwampTime(); 411 | QUdpSocket *socket = (QUdpSocket*) sender(); 412 | 413 | char buf[1500]; 414 | 415 | QHostAddress peerAddress; 416 | quint16 peerPort; 417 | 418 | int rc = socket->readDatagram(buf, sizeof(buf), &peerAddress, &peerPort); 419 | int payload = rc - (int)sizeof(struct twamp_message_test_unauthenticated); 420 | 421 | struct twamp_message_test_unauthenticated *msg = (struct twamp_message_test_unauthenticated*)buf; 422 | 423 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)buf, rc), TestPacketReceived); 424 | 425 | char outbuf[sizeof(struct twamp_message_reflector_unathenticated) + payload]; 426 | memset(outbuf, 0, sizeof(outbuf)); 427 | 428 | struct twamp_message_reflector_unathenticated *response = (struct twamp_message_reflector_unathenticated *) outbuf; 429 | 430 | struct twamp_time reflected = getTwampTime(); 431 | response->sequence_number = msg->sequence_number; 432 | qToBigEndian(reflected.seconds, (uchar*)&response->timestamp.seconds); 433 | qToBigEndian(reflected.fraction, (uchar*)&response->timestamp.fraction); 434 | qToBigEndian(received.seconds, (uchar*)&response->receive_timestamp.seconds); 435 | qToBigEndian(received.fraction, (uchar*)&response->receive_timestamp.fraction); 436 | response->sender_sequence_number = msg->sequence_number; 437 | response->sender_timestamp.seconds = msg->timestamp.seconds; 438 | response->sender_timestamp.fraction = msg->timestamp.fraction; 439 | 440 | socket->writeDatagram((const char*)outbuf, sizeof(outbuf), peerAddress, peerPort); 441 | emit twampLog(TwampLogPacket, "", QByteArray((const char*)outbuf, sizeof(outbuf)), TestPacketSent); 442 | } 443 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /installer/packages/com.demirten.twamp/meta/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | --------------------------------------------------------------------------------