├── 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 |
--------------------------------------------------------------------------------