Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
authorRobert Adam <dev@robert-adam.de>2021-11-22 13:17:58 +0300
committerRobert Adam <dev@robert-adam.de>2022-03-27 10:49:58 +0300
commit1d45d991aa4d53b6c1bd7d7cae0126a21f3991e1 (patch)
treebd810f21a6cc43fa718e0f2807ac373403244edf /src/tests
parent06b56530997fb623c9690ad7bdb8d3f5915d48a0 (diff)
CHANGE: Use Protobuf for UDP messages
Previously Mumble was using a custom binary format for transmitting data via UDP (mainly audio). This has worked for a long time but besides being inconvenient for 3rdParty implementors (they had to manually re-implement encoding and decoding support for this format) this format was not very flexible and changes to the data format were very hard. In order to improve on this situation, this commit introduces changes that allow to use Protobuf for the UDP messages as well (it's already used for TCP). With that it should be relatively easy to extend/change the UDP packet formats in the future and 3rdParty implementors can now simply use Protobuf to handle decoding/encoding packets for them (much less work and much less prone to errors). Since the new Protobuf format is incompatible with the old UDP format, this commit also includes support for dealing with older clients or servers that don't recognize the new protocol yet. That way the new protocol format is only used if both the client and the server are recent enough to have it implemented (assumed to be the case >=1.5.0). Note also that the server will make sure that clients using the old and the new format can seamlessly communicate with one another. Therefore, on the surface it should not be noticeable to the user which protocol is currently used. Note also that the new protocol format only supports Opus as an audio codec. If one of the legacy codecs is to be used, the legacy packet format has to be used as well. However, all codecs except for Opus will be removed from Mumble in the future anyway. Fixes #4350
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/TestAudioReceiverBuffer/CMakeLists.txt35
-rw-r--r--src/tests/TestAudioReceiverBuffer/ServerUser.h23
-rw-r--r--src/tests/TestAudioReceiverBuffer/TestAudioReceiverBuffer.cpp276
-rw-r--r--src/tests/TestMumbleProtocol/CMakeLists.txt12
-rw-r--r--src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp306
-rw-r--r--src/tests/TestPacketDataStream/TestPacketDataStream.cpp2
7 files changed, 655 insertions, 1 deletions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 87392a1c3..129980708 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -24,6 +24,7 @@ endif()
if(server)
use_test("TestCrypt")
+ use_test("TestAudioReceiverBuffer")
endif()
# Shared tests
@@ -32,6 +33,7 @@ use_test("TestCryptographicRandom")
use_test("TestFFDHE")
use_test("TestPacketDataStream")
use_test("TestPasswordGenerator")
+use_test("TestMumbleProtocol")
use_test("TestSelfSignedCertificate")
use_test("TestServerAddress")
use_test("TestSSLLocks")
diff --git a/src/tests/TestAudioReceiverBuffer/CMakeLists.txt b/src/tests/TestAudioReceiverBuffer/CMakeLists.txt
new file mode 100644
index 000000000..8509f9e12
--- /dev/null
+++ b/src/tests/TestAudioReceiverBuffer/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright 2020-2021 The Mumble Developers. All rights reserved.
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file at the root of the
+# Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+add_executable(TestAudioReceiverBuffer TestAudioReceiverBuffer.cpp)
+
+set_target_properties(TestAudioReceiverBuffer PROPERTIES AUTOMOC ON)
+
+target_link_libraries(TestAudioReceiverBuffer PRIVATE shared Qt5::Test)
+
+target_include_directories(TestAudioReceiverBuffer PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# In order to be able to mock the ServerUser class, we have to extract the server-specific source and header
+# files into an isolated environment, such that they don't include/link with the remaining server files.
+set(CUSTOM_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
+file(MAKE_DIRECTORY "${CUSTOM_INCLUDE_DIR}")
+set(HEADER_TO_COPY "${CMAKE_SOURCE_DIR}/src/murmur/AudioReceiverBuffer.h")
+set(SOURCE_TO_COPY "${CMAKE_SOURCE_DIR}/src/murmur/AudioReceiverBuffer.cpp")
+get_filename_component(HEADER_NAME "${HEADER_TO_COPY}" NAME)
+get_filename_component(SOURCE_NAME "${SOURCE_TO_COPY}" NAME)
+set(COPIED_HEADER "${CUSTOM_INCLUDE_DIR}/${HEADER_NAME}")
+set(COPIED_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${SOURCE_NAME}")
+
+add_custom_command(OUTPUT "${COPIED_SOURCE}"
+ COMMAND ${CMAKE_COMMAND} -E copy "${HEADER_TO_COPY}" "${COPIED_HEADER}"
+ COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_TO_COPY}" "${COPIED_SOURCE}"
+ DEPENDS "${HEADER_TO_COPY}" "${SOURCE_TO_COPY}"
+)
+
+target_sources(TestAudioReceiverBuffer PRIVATE "${COPIED_SOURCE}")
+
+target_include_directories(TestAudioReceiverBuffer PRIVATE "${CUSTOM_INCLUDE_DIR}")
+
+add_test(NAME TestAudioReceiverBuffer COMMAND $<TARGET_FILE:TestAudioReceiverBuffer>)
diff --git a/src/tests/TestAudioReceiverBuffer/ServerUser.h b/src/tests/TestAudioReceiverBuffer/ServerUser.h
new file mode 100644
index 000000000..d07985227
--- /dev/null
+++ b/src/tests/TestAudioReceiverBuffer/ServerUser.h
@@ -0,0 +1,23 @@
+// Copyright 2021 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+
+// NOTE: This is merely a mock of the ServerUser class
+
+#include "Version.h"
+
+#include <string>
+
+struct ServerUser {
+ ServerUser(unsigned int uiSession, Version::mumble_raw_version_t version, bool deaf = false, bool selfDeaf = false,
+ const std::string context = "")
+ : uiSession(uiSession), uiVersion(version), bDeaf(deaf), bSelfDeaf(selfDeaf), ssContext(context) {}
+
+ unsigned int uiSession;
+ Version::mumble_raw_version_t uiVersion;
+ bool bDeaf;
+ bool bSelfDeaf;
+ std::string ssContext;
+};
diff --git a/src/tests/TestAudioReceiverBuffer/TestAudioReceiverBuffer.cpp b/src/tests/TestAudioReceiverBuffer/TestAudioReceiverBuffer.cpp
new file mode 100644
index 000000000..46cf8f5b5
--- /dev/null
+++ b/src/tests/TestAudioReceiverBuffer/TestAudioReceiverBuffer.cpp
@@ -0,0 +1,276 @@
+// Copyright 2021 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "AudioReceiverBuffer.h"
+#include "MumbleProtocol.h"
+#include "Version.h"
+
+#include <QObject>
+#include <QtTest>
+
+#include <array>
+#include <unordered_set>
+
+#include <QDebug>
+
+QDebug &operator<<(QDebug &stream, const ServerUser &user) {
+ int major, minor, patch;
+ Version::fromRaw(user.uiVersion, &major, &minor, &patch);
+ return stream.nospace() << "ServerUser{ session: " << user.uiSession << ", version: " << major << "." << minor
+ << "." << patch << ", deaf: " << user.bDeaf << ", selfDeaf: " << user.bSelfDeaf
+ << ", ssContext: " << QString::fromStdString(user.ssContext) << " }";
+}
+
+QDebug &operator<<(QDebug &stream, const AudioReceiver &receiver) {
+ return stream.nospace() << "AudioReceiver{ receiver: " << receiver.getReceiver()
+ << ", context: " << receiver.getContext()
+ << ", volAdj: " << receiver.getVolumeAdjustment().factor << " }";
+}
+
+bool operator<(const AudioReceiver &lhs, const AudioReceiver &rhs) {
+ // session IDs are supposed to be unique, so sorting by them should yield a unique ordering
+ return lhs.getReceiver().uiSession < rhs.getReceiver().uiSession;
+}
+
+
+Version::mumble_raw_version_t vOld1 = Version::toRaw(1, 2, 5);
+Version::mumble_raw_version_t vOld2 = Version::toRaw(1, 3, 1);
+Version::mumble_raw_version_t vOld3 = Version::toRaw(1, 4, 0);
+Version::mumble_raw_version_t vNew = Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION;
+
+std::array< ServerUser, 5 > users = { ServerUser(0, vOld1), ServerUser(1, vOld2), ServerUser(2, vOld3),
+ ServerUser(3, vNew), ServerUser(4, vNew) };
+
+ServerUser deafUser(5, vOld1, true);
+ServerUser selfDeafUser(6, vNew, false, true);
+ServerUser contextUser1(7, vNew, false, false, "context1");
+ServerUser contextUser2(8, vNew, false, false, "context2");
+ServerUser contextUser3(9, vNew, false, false, "context1");
+
+
+struct Range {
+ Range(ServerUser *begin, ServerUser *end) : begin(begin), end(end){};
+
+ ServerUser *begin;
+ ServerUser *end;
+};
+
+class PseudoEncoder {
+public:
+ PseudoEncoder() = default;
+
+ bool checkRequiresEncoding(Version::mumble_raw_version_t protocolVersion, Mumble::Protocol::audio_context_t context,
+ float volumeAdjustment) {
+ bool requiresEncoding = m_encodings == 0
+ || !Mumble::Protocol::protocolVersionsAreCompatible(m_protocolVersion, protocolVersion)
+ || m_context != context || m_volumeAdjustment != volumeAdjustment;
+
+ if (requiresEncoding) {
+ m_encodings++;
+
+ m_protocolVersion = protocolVersion;
+ m_context = context;
+ m_volumeAdjustment = volumeAdjustment;
+ }
+
+ return requiresEncoding;
+ }
+
+ std::size_t getAmountOfEncodings() const { return m_encodings; }
+
+ void reset() { m_encodings = 0; }
+
+protected:
+ std::size_t m_encodings = 0;
+ Version::mumble_raw_version_t m_protocolVersion = Version::UNKNOWN;
+ Mumble::Protocol::audio_context_t m_context = Mumble::Protocol::AudioContext::Invalid;
+ float m_volumeAdjustment = 0.0f;
+};
+
+class TestAudioReceiverBuffer : public QObject {
+ Q_OBJECT;
+private slots:
+ void test_preconditions() {
+ // Preconditions for these test to make any sense
+ QVERIFY(Mumble::Protocol::protocolVersionsAreCompatible(vOld1, vOld2));
+ QVERIFY(Mumble::Protocol::protocolVersionsAreCompatible(vOld2, vOld3));
+ QVERIFY(Mumble::Protocol::protocolVersionsAreCompatible(vOld1, vOld3));
+ QVERIFY(!Mumble::Protocol::protocolVersionsAreCompatible(vOld1, vNew));
+ QVERIFY(!Mumble::Protocol::protocolVersionsAreCompatible(vOld2, vNew));
+ QVERIFY(!Mumble::Protocol::protocolVersionsAreCompatible(vOld3, vNew));
+
+ // Make sure we are not accidentally using duplicate IDs for our dummy users
+ std::unordered_set< unsigned int > usedIDs;
+ for (const ServerUser &current : users) {
+ QVERIFY(usedIDs.find(current.uiSession) == usedIDs.end());
+ usedIDs.insert(current.uiSession);
+ }
+ for (const ServerUser &current : { deafUser, selfDeafUser, contextUser1, contextUser2, contextUser3 }) {
+ QVERIFY(usedIDs.find(current.uiSession) == usedIDs.end());
+ usedIDs.insert(current.uiSession);
+ }
+
+ QVERIFY(Mumble::Protocol::AudioContext::Normal < Mumble::Protocol::AudioContext::Whisper);
+ QVERIFY(Mumble::Protocol::AudioContext::Normal < Mumble::Protocol::AudioContext::Shout);
+ QVERIFY(Mumble::Protocol::AudioContext::Normal < Mumble::Protocol::AudioContext::Listen);
+
+ QVERIFY(contextUser1.ssContext == contextUser3.ssContext);
+ QVERIFY(contextUser1.ssContext != contextUser2.ssContext);
+ }
+
+ void test_addReceiver() {
+ AudioReceiverBuffer buffer;
+
+ ServerUser &sender = users[0];
+
+ buffer.addReceiver(sender, sender, Mumble::Protocol::AudioContext::Listen, false);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Whisper, false);
+ buffer.addReceiver(sender, users[2], Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, contextUser1, Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, selfDeafUser, Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, deafUser, Mumble::Protocol::AudioContext::Shout, false);
+
+ QCOMPARE(buffer.getReceivers(false).size(), static_cast< std::size_t >(3));
+ QVERIFY(buffer.getReceivers(true).empty());
+ }
+
+ void test_addReceiverPositional() {
+ AudioReceiverBuffer buffer;
+
+ ServerUser &sender = contextUser1;
+
+ buffer.addReceiver(sender, users[0], Mumble::Protocol::AudioContext::Normal, true);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Normal, true);
+ buffer.addReceiver(sender, contextUser2, Mumble::Protocol::AudioContext::Normal, true);
+ buffer.addReceiver(sender, contextUser3, Mumble::Protocol::AudioContext::Normal, true);
+
+ // There is only one receiver whose context matches that of the sender
+ QCOMPARE(buffer.getReceivers(true).size(), static_cast< std::size_t >(1));
+ // All other receivers will get the audio without positional data
+ QCOMPARE(buffer.getReceivers(false).size(), static_cast< std::size_t >(3));
+ }
+
+ void test_forceAddReceiver() {
+ AudioReceiverBuffer buffer;
+
+ ServerUser &sender = users[0];
+
+ buffer.forceAddReceiver(sender, Mumble::Protocol::AudioContext::Normal, false);
+
+ QCOMPARE(buffer.getReceivers(false).size(), static_cast< std::size_t >(1));
+ QVERIFY(buffer.getReceivers(true).empty());
+ }
+
+ void test_preprocessBuffer() {
+ AudioReceiverBuffer buffer;
+
+ ServerUser &sender = users[0];
+
+ buffer.addReceiver(sender, users[3], Mumble::Protocol::AudioContext::Listen, false,
+ VolumeAdjustment::fromFactor(1.2f));
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Whisper, false);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Normal, false);
+ buffer.addReceiver(sender, users[2], Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Listen, false);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, users[3], Mumble::Protocol::AudioContext::Listen, false,
+ VolumeAdjustment::fromFactor(1.4f));
+ buffer.addReceiver(sender, contextUser1, Mumble::Protocol::AudioContext::Shout, false);
+
+ buffer.preprocessBuffer();
+
+ // The preprocessing should have removed all duplicates of users[1] and users[3]
+ QCOMPARE(buffer.getReceivers(false).size(), static_cast< std::size_t >(4));
+ QVERIFY(buffer.getReceivers(true).empty());
+
+ const AudioReceiver *duplicateReceiver = nullptr;
+ const AudioReceiver *volumeReceiver = nullptr;
+ for (const AudioReceiver &current : buffer.getReceivers(false)) {
+ if (current.getReceiver().uiSession == users[1].uiSession) {
+ duplicateReceiver = &current;
+ } else if (current.getReceiver().uiSession == users[3].uiSession) {
+ volumeReceiver = &current;
+ }
+ }
+
+ QVERIFY(duplicateReceiver != nullptr);
+ QVERIFY(volumeReceiver != nullptr);
+
+ // Verify that the "Normal" speech receiver has survived (instead of one of the other contexts)
+ QCOMPARE(duplicateReceiver->getContext(), Mumble::Protocol::AudioContext::Normal);
+ // Verify that the highest volume adjustment has survived
+ QCOMPARE(volumeReceiver->getVolumeAdjustment().factor, 1.4f);
+ }
+
+ void test_encoding() {
+ AudioReceiverBuffer buffer;
+
+ ServerUser &sender = contextUser2;
+
+ buffer.addReceiver(sender, users[3], Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, users[1], Mumble::Protocol::AudioContext::Listen, false);
+ buffer.addReceiver(sender, users[4], Mumble::Protocol::AudioContext::Shout, false);
+ buffer.addReceiver(sender, users[2], Mumble::Protocol::AudioContext::Normal, false);
+ buffer.addReceiver(sender, users[0], Mumble::Protocol::AudioContext::Normal, false);
+ buffer.addReceiver(sender, contextUser1, Mumble::Protocol::AudioContext::Shout, false,
+ VolumeAdjustment::fromFactor(1.4f));
+ buffer.addReceiver(sender, contextUser2, Mumble::Protocol::AudioContext::Shout, false,
+ VolumeAdjustment::fromFactor(1.2f));
+ buffer.addReceiver(sender, contextUser3, Mumble::Protocol::AudioContext::Shout, false,
+ VolumeAdjustment::fromFactor(1.2f));
+
+ buffer.preprocessBuffer();
+
+ std::vector< AudioReceiver > receivers = buffer.getReceivers(false);
+ auto receiverRange = AudioReceiverBuffer::getReceiverRange(receivers.begin(), receivers.end());
+
+ std::size_t processedReceiver = 0;
+ PseudoEncoder encoder;
+
+ while (receiverRange.begin != receiverRange.end) {
+ qWarning("Opening a new range");
+ qWarning() << "Start:" << *receiverRange.begin;
+ qWarning() << "End:" << *(receiverRange.end - 1);
+
+ QVERIFY2(encoder.checkRequiresEncoding(receiverRange.begin->getReceiver().uiVersion,
+ receiverRange.begin->getContext(),
+ receiverRange.begin->getVolumeAdjustment().factor),
+ "Starting a new range, but no re-encoding is required");
+
+ for (auto it = receiverRange.begin; it != receiverRange.end; ++it) {
+ qWarning() << "Processing" << *it;
+ QVERIFY2(!encoder.checkRequiresEncoding(it->getReceiver().uiVersion, it->getContext(),
+ it->getVolumeAdjustment().factor),
+ "Mid-range re-encoding required");
+ processedReceiver++;
+ }
+
+ receiverRange = AudioReceiverBuffer::getReceiverRange(receiverRange.end, receivers.end());
+ }
+
+ QCOMPARE(processedReceiver, receivers.size());
+
+ std::size_t requiredReencodings = encoder.getAmountOfEncodings();
+
+ // Check all permutations of receivers in order to check that the one created by preprocessBuffer is in fact the
+ // one that minimizes the amount of re-encodings that are required in order to send the audio to all receivers.
+ do {
+ encoder.reset();
+
+ for (const AudioReceiver &current : receivers) {
+ encoder.checkRequiresEncoding(current.getReceiver().uiVersion, current.getContext(),
+ current.getVolumeAdjustment().factor);
+ }
+
+ QVERIFY2(encoder.getAmountOfEncodings() >= requiredReencodings,
+ "There exists a permutation of the receivers, that requires less re-encoding steps");
+ } while (std::next_permutation(receivers.begin(), receivers.end()));
+
+ qDebug() << "Sample receiver list required" << requiredReencodings << "encoding steps";
+ }
+};
+
+QTEST_MAIN(TestAudioReceiverBuffer)
+#include "TestAudioReceiverBuffer.moc"
diff --git a/src/tests/TestMumbleProtocol/CMakeLists.txt b/src/tests/TestMumbleProtocol/CMakeLists.txt
new file mode 100644
index 000000000..b6e3d2cd7
--- /dev/null
+++ b/src/tests/TestMumbleProtocol/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright 2020-2021 The Mumble Developers. All rights reserved.
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file at the root of the
+# Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+add_executable(TestMumbleProtocol TestMumbleProtocol.cpp)
+
+set_target_properties(TestMumbleProtocol PROPERTIES AUTOMOC ON)
+
+target_link_libraries(TestMumbleProtocol PRIVATE shared Qt5::Test)
+
+add_test(NAME TestMumbleProtocol COMMAND $<TARGET_FILE:TestMumbleProtocol>)
diff --git a/src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp b/src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp
new file mode 100644
index 000000000..85e0b502a
--- /dev/null
+++ b/src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp
@@ -0,0 +1,306 @@
+// Copyright 2021 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "MumbleProtocol.h"
+#include "MumbleUDP.pb.h"
+#include "Version.h"
+
+#include <QObject>
+#include <QtTest>
+
+#include <cstring>
+#include <sstream>
+#include <string>
+
+namespace Mumble {
+namespace Protocol {
+ // Add toString functions for use by QCOMPARE
+
+ char *toString(const Mumble::Protocol::AudioData &data) {
+ std::stringstream stream;
+ stream << "{ payload: {" << static_cast< const void * >(data.payload.data()) << ", " << data.payload.size()
+ << "}, frameNumber: " << data.frameNumber << ", isLastFrame: " << data.isLastFrame
+ << ", senderSession: " << data.senderSession << ", targetOrContext: " << data.targetOrContext
+ << ", usedCodec: " << static_cast< int >(data.usedCodec)
+ << ", containsPositionalData: " << data.containsPositionalData;
+ if (data.containsPositionalData) {
+ stream << ", position: {";
+
+ for (unsigned int i = 0; i < data.position.size(); ++i) {
+ stream << data.position[i];
+
+ if (i + 1 < data.position.size()) {
+ stream << ", ";
+ }
+ }
+
+ stream << "}";
+ }
+ stream << ", volumeAdjustment: " << data.volumeAdjustment.factor << " }";
+
+ std::string str = stream.str();
+
+ char *charArray = new char[str.size() + 1];
+
+ std::strcpy(charArray, str.c_str());
+
+ return charArray;
+ }
+
+ char *toString(const Mumble::Protocol::PingData &data) {
+ std::stringstream stream;
+
+ stream << "{ timestamp: " << data.timestamp
+ << ", requestAdditionalInformation: " << data.requestAdditionalInformation
+ << ", containsAdditionalInformation: " << data.containsAdditionalInformation
+ << ", userCount: " << data.userCount << ", maxUserCount: " << data.maxUserCount
+ << ", maxBandwidthPerUser: " << data.maxBandwidthPerUser << " }";
+
+ std::string str = stream.str();
+
+ char *charArray = new char[str.size() + 1];
+
+ std::strcpy(charArray, str.c_str());
+
+ return charArray;
+ }
+
+
+ template< Role role > class TestAudioEncoder : public UDPAudioEncoder< role > {
+ public:
+ using UDPAudioEncoder< role >::UDPAudioEncoder;
+
+ // Expose these functions publicly for testing-purposes
+ using UDPAudioEncoder< role >::getPreEncodedContext;
+ using UDPAudioEncoder< role >::getPreEncodedVolumeAdjustment;
+ };
+
+}; // namespace Protocol
+}; // namespace Mumble
+
+template< Mumble::Protocol::Role encoderRole, Mumble::Protocol::Role decoderRole > void do_test_ping() {
+ Mumble::Protocol::UDPPingEncoder< encoderRole > encoder;
+ Mumble::Protocol::UDPDecoder< decoderRole > decoder;
+
+ for (Version::mumble_raw_version_t version :
+ { Version::toRaw(1, 3, 0), Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION }) {
+ int major, minor, patch;
+ Version::fromRaw(version, &major, &minor, &patch);
+ qWarning("Using protocol version %d.%d.%d", major, minor, patch);
+
+ // Note: When the decoder is set to a version < PROTOBUF_INTRODUCTION_VERSION, it can decode pings in
+ // either format
+ encoder.setProtocolVersion(version);
+
+ Mumble::Protocol::PingData data;
+ data.timestamp = 42;
+
+ // Regular connectivity ping
+ auto encodedData = encoder.encodePingPacket(data);
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Ping);
+ QCOMPARE(decoder.getPingData(), data);
+
+ // Extended ping (request)
+ if (decoderRole == Mumble::Protocol::Role::Server) {
+ QVERIFY(encoderRole == Mumble::Protocol::Role::Client);
+
+ data.requestAdditionalInformation = true;
+
+ encodedData = encoder.encodePingPacket(data);
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Ping);
+ QCOMPARE(decoder.getPingData(), data);
+ } else {
+ QVERIFY(encoderRole == Mumble::Protocol::Role::Server);
+
+ data.containsAdditionalInformation = true;
+ data.userCount = 12;
+ data.maxUserCount = 42;
+ data.maxBandwidthPerUser = 512;
+
+ encodedData = encoder.encodePingPacket(data);
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Ping);
+ }
+ }
+}
+
+void printData(gsl::span< const Mumble::Protocol::byte > data) {
+ QString str = "Data is: { ";
+ for (Mumble::Protocol::byte current : data) {
+ str += QString::number(static_cast< int >(current)) + " ";
+ }
+ str += " }";
+
+ qDebug() << str;
+}
+
+template< Mumble::Protocol::Role encoderRole, Mumble::Protocol::Role decoderRole > void do_test_audio() {
+ Mumble::Protocol::UDPAudioEncoder< encoderRole > encoder;
+ Mumble::Protocol::UDPDecoder< decoderRole > decoder;
+
+ std::string payloadData = "I am the payload";
+
+ for (Version::mumble_raw_version_t version :
+ { Version::toRaw(1, 3, 0), Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION }) {
+ int major, minor, patch;
+ Version::fromRaw(version, &major, &minor, &patch);
+ qWarning("Using protocol version %d.%d.%d", major, minor, patch);
+
+ encoder.setProtocolVersion(version);
+ decoder.setProtocolVersion(version);
+
+ Mumble::Protocol::AudioData data;
+ data.payload = { reinterpret_cast< const Mumble::Protocol::byte * >(payloadData.c_str()), payloadData.size() };
+
+ data.frameNumber = 12;
+ data.containsPositionalData = true;
+ data.position = { 3, 2, 1 };
+ data.isLastFrame = true;
+ data.usedCodec = Mumble::Protocol::AudioCodec::Opus;
+ if (version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION
+ && decoderRole == Mumble::Protocol::Role::Client) {
+ // Transmitting volume adjustment is only supported in the new packet format
+ // and only in the server->client direction
+ data.volumeAdjustment = VolumeAdjustment::fromFactor(1.4f);
+ }
+
+ if (decoderRole == Mumble::Protocol::Role::Client) {
+ QVERIFY(encoder.getRole() == Mumble::Protocol::Role::Server);
+
+ data.targetOrContext = Mumble::Protocol::AudioContext::Shout;
+ data.senderSession = 42;
+ } else {
+ QVERIFY(encoder.getRole() == Mumble::Protocol::Role::Client);
+
+ data.targetOrContext = Mumble::Protocol::ReservedTargetIDs::ServerLoopback;
+ }
+
+ auto encodedData = encoder.encodeAudioPacket(data);
+ QVERIFY(!encodedData.empty());
+
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Audio);
+ QCOMPARE(decoder.getAudioData(), data);
+
+ qWarning() << "Partial re-encoding";
+
+ // Re-encode fields from the "variable" part
+ data.targetOrContext = Mumble::Protocol::AudioContext::Listen;
+ if (version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION
+ && decoderRole == Mumble::Protocol::Role::Client) {
+ data.volumeAdjustment = VolumeAdjustment::fromFactor(0.9f);
+ }
+
+ encodedData = encoder.updateAudioPacket(data);
+ QVERIFY(!encodedData.empty());
+
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Audio);
+ QCOMPARE(decoder.getAudioData(), data);
+
+ qWarning() << "Removing positional data";
+ // Update the audio packet to no longer contain positional data.
+ data.containsPositionalData = false;
+ data.targetOrContext = Mumble::Protocol::AudioContext::Normal;
+
+ encoder.dropPositionalData();
+
+ encodedData = encoder.updateAudioPacket(data);
+ QVERIFY(!encodedData.empty());
+
+ QVERIFY(decoder.decode(encodedData));
+
+ QCOMPARE(decoder.getMessageType(), Mumble::Protocol::UDPMessageType::Audio);
+ QCOMPARE(decoder.getAudioData(), data);
+ }
+}
+
+class TestMumbleProtocol : public QObject {
+ Q_OBJECT;
+private slots:
+ void test_ping_client_to_server() {
+ do_test_ping< Mumble::Protocol::Role::Client, Mumble::Protocol::Role::Server >();
+ }
+
+ void test_ping_server_to_client() {
+ do_test_ping< Mumble::Protocol::Role::Server, Mumble::Protocol::Role::Client >();
+ }
+
+ void test_audio_client_to_server() {
+ do_test_audio< Mumble::Protocol::Role::Client, Mumble::Protocol::Role::Server >();
+ }
+
+ void test_audio_server_to_client() {
+ do_test_audio< Mumble::Protocol::Role::Server, Mumble::Protocol::Role::Client >();
+ }
+
+ void test_preEncode_audio_context() {
+ Mumble::Protocol::TestAudioEncoder< Mumble::Protocol::Role::Server > encoder;
+
+ MumbleUDP::Audio msg;
+ std::vector< Mumble::Protocol::byte > buffer;
+
+ for (Mumble::Protocol::audio_context_t currentContext = Mumble::Protocol::AudioContext::begin;
+ currentContext < Mumble::Protocol::AudioContext::end; currentContext++) {
+ gsl::span< const Mumble::Protocol::byte > snippet = encoder.getPreEncodedContext(currentContext);
+
+ QVERIFY2(!snippet.empty(), "Unable to find pre-encoded snippet for audio context");
+
+ msg.set_context(currentContext);
+
+#if GOOGLE_PROTOBUF_VERSION >= 3002000
+ // ByteSizeLong() was introduced in Protobuf v3.2 as a replacement for ByteSize()
+ buffer.resize(msg.ByteSizeLong());
+#else
+ buffer.resize(msg.ByteSize());
+#endif
+ msg.SerializeWithCachedSizesToArray(buffer.data());
+
+ QCOMPARE(snippet.size(), buffer.size());
+ QVERIFY2(std::equal(snippet.begin(), snippet.end(), buffer.begin()), "Pre-encoded snippet is incorrect");
+ }
+
+ // Ensure that an unknown context yields an empty span
+ QVERIFY(encoder.getPreEncodedContext(Mumble::Protocol::AudioContext::end).empty());
+ }
+
+ void test_preEncode_volume_adjustments() {
+ Mumble::Protocol::TestAudioEncoder< Mumble::Protocol::Role::Server > encoder;
+
+ MumbleUDP::Audio msg;
+
+ constexpr int min = -60;
+ constexpr int max = 30;
+
+ for (int currentAdjustment = min; currentAdjustment <= max; ++currentAdjustment) {
+ msg.Clear();
+
+ gsl::span< const Mumble::Protocol::byte > snippet =
+ encoder.getPreEncodedVolumeAdjustment(VolumeAdjustment::fromDBAdjustment(currentAdjustment));
+
+ QVERIFY2(!snippet.empty(), "Unable to find pre-encoded snippet for volume adjustment");
+
+ msg.ParseFromArray(snippet.data(), snippet.size());
+
+ // This will perform a fuzzy-compare
+ QCOMPARE(msg.volume_adjustment(), std::pow(2.0f, currentAdjustment / 6.0f));
+ }
+
+ // Ensure that an unknown/unexpected volume adjustment yields an empty span
+ QVERIFY(encoder.getPreEncodedVolumeAdjustment(VolumeAdjustment::fromDBAdjustment(min - 1)).empty());
+ // We only expect pre-encoded values for integer dB adjustments
+ QVERIFY(encoder.getPreEncodedVolumeAdjustment(VolumeAdjustment(std::pow(2.0f, (min + 0.5) / 6.0f))).empty());
+ }
+};
+
+QTEST_MAIN(TestMumbleProtocol)
+#include "TestMumbleProtocol.moc"
diff --git a/src/tests/TestPacketDataStream/TestPacketDataStream.cpp b/src/tests/TestPacketDataStream/TestPacketDataStream.cpp
index 3b2dceffb..b705e1be8 100644
--- a/src/tests/TestPacketDataStream/TestPacketDataStream.cpp
+++ b/src/tests/TestPacketDataStream/TestPacketDataStream.cpp
@@ -3,8 +3,8 @@
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
-#include "Message.h"
#include "PacketDataStream.h"
+
#include <QObject>
#include <QtCore>
#include <QtNetwork>