diff options
Diffstat (limited to 'src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp')
-rw-r--r-- | src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp | 306 |
1 files changed, 306 insertions, 0 deletions
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" |