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

MumbleProtocol.h « src - github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 22c424b5f9c58c3ee2b773a11ec070db5060e4dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// 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>.

#ifndef MUMBLE_MUMBLEPROTOCOL_H_
#define MUMBLE_MUMBLEPROTOCOL_H_

#include "MumbleUDP.pb.h"
#include "Version.h"
#include "VolumeAdjustment.h"

#include <cstdint>
#include <vector>

#include <gsl/span>

/**
 * "X-macro" for all Mumble Protobuf TCP messages types.
 *
 * Warning: Only append to the end. Never insert in between or remove an existing entry.
 */
#define MUMBLE_ALL_TCP_MESSAGES                         \
	PROCESS_MUMBLE_TCP_MESSAGE(Version, 0)              \
	PROCESS_MUMBLE_TCP_MESSAGE(UDPTunnel, 1)            \
	PROCESS_MUMBLE_TCP_MESSAGE(Authenticate, 2)         \
	PROCESS_MUMBLE_TCP_MESSAGE(Ping, 3)                 \
	PROCESS_MUMBLE_TCP_MESSAGE(Reject, 4)               \
	PROCESS_MUMBLE_TCP_MESSAGE(ServerSync, 5)           \
	PROCESS_MUMBLE_TCP_MESSAGE(ChannelRemove, 6)        \
	PROCESS_MUMBLE_TCP_MESSAGE(ChannelState, 7)         \
	PROCESS_MUMBLE_TCP_MESSAGE(UserRemove, 8)           \
	PROCESS_MUMBLE_TCP_MESSAGE(UserState, 9)            \
	PROCESS_MUMBLE_TCP_MESSAGE(BanList, 10)             \
	PROCESS_MUMBLE_TCP_MESSAGE(TextMessage, 11)         \
	PROCESS_MUMBLE_TCP_MESSAGE(PermissionDenied, 12)    \
	PROCESS_MUMBLE_TCP_MESSAGE(ACL, 13)                 \
	PROCESS_MUMBLE_TCP_MESSAGE(QueryUsers, 14)          \
	PROCESS_MUMBLE_TCP_MESSAGE(CryptSetup, 15)          \
	PROCESS_MUMBLE_TCP_MESSAGE(ContextActionModify, 16) \
	PROCESS_MUMBLE_TCP_MESSAGE(ContextAction, 17)       \
	PROCESS_MUMBLE_TCP_MESSAGE(UserList, 18)            \
	PROCESS_MUMBLE_TCP_MESSAGE(VoiceTarget, 19)         \
	PROCESS_MUMBLE_TCP_MESSAGE(PermissionQuery, 20)     \
	PROCESS_MUMBLE_TCP_MESSAGE(CodecVersion, 21)        \
	PROCESS_MUMBLE_TCP_MESSAGE(UserStats, 22)           \
	PROCESS_MUMBLE_TCP_MESSAGE(RequestBlob, 23)         \
	PROCESS_MUMBLE_TCP_MESSAGE(ServerConfig, 24)        \
	PROCESS_MUMBLE_TCP_MESSAGE(SuggestConfig, 25)       \
	PROCESS_MUMBLE_TCP_MESSAGE(PluginDataTransmission, 26)

/**
 * "X-macro" for all Mumble Protobuf UDP messages types.
 *
 * Warning: Only append to the end. Never insert in between or remove an existing entry.
 */
#define MUMBLE_ALL_UDP_MESSAGES          \
	PROCESS_MUMBLE_UDP_MESSAGE(Audio, 0) \
	PROCESS_MUMBLE_UDP_MESSAGE(Ping, 1)

namespace Mumble {
namespace Protocol {

	using byte = std::uint8_t;

	// The maximum allowed size in bytes of UDP packets (according to the Mumble protocol)
	constexpr std::size_t MAX_UDP_PACKET_SIZE = 1024;

#define PROCESS_MUMBLE_TCP_MESSAGE(name, value) name = value,
	/**
	 * Enum holding all possible TCP message types
	 */
	enum class TCPMessageType : byte { MUMBLE_ALL_TCP_MESSAGES };
#undef PROCESS_MUMBLE_TCP_MESSAGE
#define PROCESS_MUMBLE_UDP_MESSAGE(name, value) name = value,
	/**
	 * Enum holding all possible UDP message types
	 */
	enum class UDPMessageType : byte { MUMBLE_ALL_UDP_MESSAGES };
#undef PROCESS_MUMBLE_UDP_MESSAGE

	enum class LegacyUDPMessageType : byte { VoiceCELTAlpha, Ping, VoiceSpeex, VoiceCELTBeta, VoiceOpus };

	enum class AudioCodec {
		Opus,
		CELT_Alpha, // 0.7.0
		CELT_Beta,  // 0.11.0
		Speex,
	};

	namespace ReservedTargetIDs {
		constexpr unsigned int REGULAR_SPEECH  = 0;
		constexpr unsigned int SERVER_LOOPBACK = 31;
	}; // namespace ReservedTargetIDs

	using audio_context_t = byte;
	namespace AudioContext {
		constexpr audio_context_t INVALID = 0xFF; // This is the equivalent of -1 as a signed 8bit number
		constexpr audio_context_t NORMAL  = 0;
		constexpr audio_context_t SHOUT   = 1;
		constexpr audio_context_t WHISPER = 2;
		constexpr audio_context_t LISTEN  = 3;

		constexpr audio_context_t BEGIN = NORMAL;
		constexpr audio_context_t END   = LISTEN + 1;
	}; // namespace AudioContext

	enum class Role { Server, Client };

	constexpr Version::full_t PROTOBUF_INTRODUCTION_VERSION = Version::fromComponents(1, 5, 0);


	bool protocolVersionsAreCompatible(Version::full_t lhs, Version::full_t rhs);


	template< Role role > class ProtocolHandler {
	public:
		ProtocolHandler(Version::full_t protocolVersion = Version::UNKNOWN);

		Version::full_t getProtocolVersion() const;
		void setProtocolVersion(Version::full_t protocolVersion);

		constexpr Role getRole() const { return role; };

	protected:
		Version::full_t m_protocolVersion;
	};

	struct AudioData {
		std::uint32_t targetOrContext = ReservedTargetIDs::REGULAR_SPEECH;
		AudioCodec usedCodec          = AudioCodec::Opus;
		std::uint32_t senderSession   = 0;
		std::uint64_t frameNumber     = 0;
		gsl::span< const byte > payload;
		bool isLastFrame                  = false;
		bool containsPositionalData       = false;
		std::array< float, 3 > position   = { 0, 0, 0 };
		VolumeAdjustment volumeAdjustment = VolumeAdjustment::fromFactor(1.0f);

		friend bool operator==(const AudioData &lhs, const AudioData &rhs);
		friend bool operator!=(const AudioData &lhs, const AudioData &rhs);
	};

	struct PingData {
		std::uint64_t timestamp            = 0;
		bool requestAdditionalInformation  = false;
		bool containsAdditionalInformation = false;
		Version::full_t serverVersion      = Version::UNKNOWN;
		std::uint32_t userCount            = 0;
		std::uint32_t maxUserCount         = 0;
		std::uint32_t maxBandwidthPerUser  = 0;

		friend bool operator==(const PingData &lhs, const PingData &rhs);
		friend bool operator!=(const PingData &lhs, const PingData &rhs);
	};

	template< Role role > class UDPAudioEncoder : public ProtocolHandler< role > {
	public:
		UDPAudioEncoder(Version::full_t protocolVersion = Version::UNKNOWN);

		/**
		 * Encodes an audio packet based on the provided data.
		 * Note: Incremental encoding is also supported via the prepare and update functions.
		 *
		 * @param data The AudioData to encode
		 * @return A span to the encoded data (ready to be sent out)
		 */
		gsl::span< const byte > encodeAudioPacket(const AudioData &data);
		/**
		 * Prepares an audio packet by encoding the "static" part of the audio data. The static part contains
		 * things like the actual audio payload, its type and the sender's session.
		 * In order to also encode positional data, call addPositionalData after calling this function and
		 * to encode the rest of the audio data, call updateAudioPacket after that.
		 *
		 * Note: Calls to this function remove any previously encoded variable parts or positional audio
		 * from the audio packet.
		 *
		 * @param data The AudioData to encode (partially!)
		 */
		void prepareAudioPacket(const AudioData &data);
		/**
		 * This function assumes that an audio packet has already been prepared. In that case it will encode
		 * the "variable" part of the audio packet which contains e.g. audio context (or audio target) and
		 * volume adjustments (if supported by the used protocol).
		 *
		 * @param data The AudioData to encode (partially!)
		 * @return A span to the encoded audio packet (including the static part and potentially positional data)
		 */
		gsl::span< const byte > updateAudioPacket(const AudioData &data);
		/**
		 * This function assumes that an audio packet has already been prepared. In that case it will encode
		 * the given positional data (if any) into the audio packet.
		 *
		 * Note: A call to this function invalidates any variable part that might have been added to the audio
		 * packet before. Thus, another call to updateAudioPacket is required after calling this function.
		 *
		 * @param data The AudioData to take the positional data from
		 */
		void addPositionalData(const AudioData &data);
		/**
		 * This function assumes that an audio packet has already been prepared. In that case it will remove
		 * positional data from the audio packet that was previously added using addPositionalData.
		 *
		 * Note: A call to this function invalidates any variable part that might have been added to the audio
		 * packet before. Thus, another call to updateAudioPacket is required after calling this function.
		 *
		 */
		void dropPositionalData();

	protected:
		static constexpr const int preEncodedDBAdjustmentBegin = -60;
		static constexpr const int preEncodedDBAdjustmentEnd   = 30 + 1;

		std::vector< byte > m_byteBuffer;
		std::size_t m_staticPartSize      = 0;
		std::size_t m_positionalAudioSize = 0;
		MumbleUDP::Audio m_audioMessage;
		std::vector< std::vector< byte > > m_preEncodedContext;
		std::vector< std::vector< byte > > m_preEncodedVolumeAdjustment;

		void prepareAudioPacket_legacy(const AudioData &data);
		gsl::span< const byte > updateAudioPacket_legacy(const AudioData &data);
		void addPositionalData_legacy(const AudioData &data);

		void prepareAudioPacket_protobuf(const AudioData &data);
		gsl::span< const byte > updateAudioPacket_protobuf(const AudioData &data);
		void addPositionalData_protobuf(const AudioData &data);

		void preparePreEncodedSnippets();

		gsl::span< const byte > getPreEncodedContext(audio_context_t context) const;
		gsl::span< const byte > getPreEncodedVolumeAdjustment(const VolumeAdjustment &adjustment) const;
	};

	template< Role role > class UDPPingEncoder : public ProtocolHandler< role > {
	public:
		UDPPingEncoder(Version::full_t protocolVersion = Version::UNKNOWN);

		gsl::span< const byte > encodePingPacket(const PingData &data);

	protected:
		std::vector< byte > m_byteBuffer;
		MumbleUDP::Ping m_pingMessage;

		gsl::span< const byte > encodePingPacket_legacy(const PingData &data);
		gsl::span< const byte > encodePingPacket_protobuf(const PingData &data);
	};

	template< Role role > class UDPDecoder : public ProtocolHandler< role > {
	public:
		UDPDecoder(Version::full_t protocolVersion = Version::UNKNOWN);

		gsl::span< byte > getBuffer();
		bool decode(const gsl::span< const byte > data, bool restrictToPing = false);
		bool decodePing(const gsl::span< const byte > data);

		UDPMessageType getMessageType() const;

		AudioData getAudioData() const;
		PingData getPingData() const;

	protected:
		std::vector< byte > m_byteBuffer;
		UDPMessageType m_messageType;
		AudioData m_audioData = {};
		PingData m_pingData   = {};
		MumbleUDP::Ping m_pingMessage;
		MumbleUDP::Audio m_audioMessage;

		bool decodePing_legacy(const gsl::span< const byte > data);
		bool decodePing_protobuf(const gsl::span< const byte > data);
		bool decodeAudio_legacy(const gsl::span< const byte > data, AudioCodec codec);
		bool decodeAudio_protobuf(const gsl::span< const byte > data);
	};

}; // namespace Protocol
}; // namespace Mumble

/**
 * This is merely a dummy-function (never used) that is required as a scope for dummy-switch statements on our message
 * type enums. These will cause a compiler error, if there are any entries that have the same numeric value (which we
 * never want to happen). See https://stackoverflow.com/a/50385277
 */
inline void ThisFunctionIsNeverCalledAndShouldSimplyBeOptimizedOut() {
#define ARRAY_NAME Mumble::Protocol::TCPMessageType
#define PROCESS_MUMBLE_TCP_MESSAGE(name, value) \
	case ARRAY_NAME::name:                      \
		break;
	switch (static_cast< ARRAY_NAME >(0)) {
		MUMBLE_ALL_TCP_MESSAGES
		default:
			break;
	}
#undef ARRAY_NAME
#undef PROCESS_MUMBLE_TCP_MESSAGE

#define ARRAY_NAME Mumble::Protocol::UDPMessageType
#define PROCESS_MUMBLE_UDP_MESSAGE(name, value) \
	case ARRAY_NAME::name:                      \
		break;
	switch (static_cast< ARRAY_NAME >(0)) {
		MUMBLE_ALL_UDP_MESSAGES
		default:
			break;
	}
#undef ARRAY_NAME
#undef PROCESS_MUMBLE_UDP_MESSAGE
}

#endif // MUMBLE_MUMBLEPROTOCOL_H_