diff --git a/MumbleClient/MumbleClient.csproj b/MumbleClient/MumbleClient.csproj
index e0f1d6d..7af7b52 100644
--- a/MumbleClient/MumbleClient.csproj
+++ b/MumbleClient/MumbleClient.csproj
@@ -39,8 +39,8 @@
false
-
- ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll
+
+ ..\packages\NAudio.1.10.0\lib\net35\NAudio.dll
..\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll
diff --git a/MumbleClient/Program.cs b/MumbleClient/Program.cs
index 72a324b..5dcfe6c 100644
--- a/MumbleClient/Program.cs
+++ b/MumbleClient/Program.cs
@@ -12,6 +12,8 @@ namespace MumbleClient
{
internal class Program
{
+ private static Exception _updateLoopThreadException = null;
+
private static void Main(string[] args)
{
string addr, name, pass;
@@ -59,8 +61,14 @@ private static void Main(string[] args)
MumbleConnection connection = new MumbleConnection(new IPEndPoint(Dns.GetHostAddresses(addr).First(a => a.AddressFamily == AddressFamily.InterNetwork), port), protocol);
connection.Connect(name, pass, new string[0], addr);
- Thread t = new Thread(a => UpdateLoop(connection)) {IsBackground = true};
- t.Start();
+ //Start the UpdateLoop thread, and collect a possible exception at termination
+ ThreadStart updateLoopThreadStart = new ThreadStart(() => UpdateLoop(connection, out _updateLoopThreadException));
+ updateLoopThreadStart += () => {
+ if(_updateLoopThreadException != null)
+ throw new Exception($"{nameof(UpdateLoop)} was terminated unexpectedly because of a {_updateLoopThreadException.GetType().ToString()}", _updateLoopThreadException);
+ };
+ Thread updateLoopThread = new Thread(updateLoopThreadStart) {IsBackground = true};
+ updateLoopThread.Start();
var r = new MicrophoneRecorder(protocol);
@@ -92,11 +100,21 @@ private static void DrawChannel(string indent, IEnumerable channels, IE
DrawChannel(indent + "\t", channels, users, channel);
}
- private static void UpdateLoop(MumbleConnection connection)
+ private static void UpdateLoop(MumbleConnection connection, out Exception exception)
{
- while (connection.State != ConnectionStates.Disconnected)
+ exception = null;
+ try
+ {
+ while (connection.State != ConnectionStates.Disconnected)
+ {
+ if (connection.Process())
+ Thread.Yield();
+ else
+ Thread.Sleep(1);
+ }
+ } catch (Exception ex)
{
- connection.Process();
+ exception = ex;
}
}
}
diff --git a/MumbleClient/packages.config b/MumbleClient/packages.config
index 1603fa4..83bcd44 100644
--- a/MumbleClient/packages.config
+++ b/MumbleClient/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/MumbleGuiClient/Form1.cs b/MumbleGuiClient/Form1.cs
index aa3cd4d..91bd0d6 100644
--- a/MumbleGuiClient/Form1.cs
+++ b/MumbleGuiClient/Form1.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Net;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using MumbleSharp;
@@ -251,7 +252,10 @@ private void btnSend_Click(object sender, EventArgs e)
private void mumbleUpdater_Tick(object sender, EventArgs e)
{
if (connection != null)
- connection.Process();
+ if (connection.Process())
+ Thread.Yield();
+ else
+ Thread.Sleep(1);
}
private void tvUsers_MouseDoubleClick(object sender, MouseEventArgs e)
@@ -457,6 +461,7 @@ private void btnConnect_Click(object sender, EventArgs e)
while (connection.Protocol.LocalUser == null)
{
connection.Process();
+ Thread.Sleep(1);
}
}
diff --git a/MumbleGuiClient/MumbleGuiClient.csproj b/MumbleGuiClient/MumbleGuiClient.csproj
index 11d5c0e..f9eca8d 100644
--- a/MumbleGuiClient/MumbleGuiClient.csproj
+++ b/MumbleGuiClient/MumbleGuiClient.csproj
@@ -32,8 +32,8 @@
4
-
- ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll
+
+ ..\packages\NAudio.1.10.0\lib\net35\NAudio.dll
diff --git a/MumbleGuiClient/packages.config b/MumbleGuiClient/packages.config
index abd5fbc..abd13ac 100644
--- a/MumbleGuiClient/packages.config
+++ b/MumbleGuiClient/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/MumbleSharp/Audio/AudioDecodingBuffer.cs b/MumbleSharp/Audio/AudioDecodingBuffer.cs
index a6b79a8..2c47794 100644
--- a/MumbleSharp/Audio/AudioDecodingBuffer.cs
+++ b/MumbleSharp/Audio/AudioDecodingBuffer.cs
@@ -69,6 +69,9 @@ public int Read(byte[] buffer, int offset, int count)
/// The codec to use to decode this packet
public void AddEncodedPacket(long sequence, byte[] data, IVoiceCodec codec)
{
+ if(sequence == 0)
+ _nextSequenceToDecode = 0;
+
if (_codec == null)
_codec = codec;
else if (_codec != null && _codec != codec)
diff --git a/MumbleSharp/Audio/AudioEncodingBuffer.cs b/MumbleSharp/Audio/AudioEncodingBuffer.cs
index 09ea9f7..567fef4 100644
--- a/MumbleSharp/Audio/AudioEncodingBuffer.cs
+++ b/MumbleSharp/Audio/AudioEncodingBuffer.cs
@@ -45,7 +45,7 @@ public void Stop()
_unencodedBuffer.Add(new TargettedSpeech(stop: true));
}
- public byte[] Encode(SpeechCodecs codec)
+ public EncodedTargettedSpeech? Encode(SpeechCodecs codec)
{
//Get the codec
var codecInstance = _codecs.GetCodec(codec);
@@ -95,7 +95,10 @@ public byte[] Encode(SpeechCodecs codec)
byte[] b = new byte[frameBytes];
int read = _pcmBuffer.Read(new ArraySegment(b));
- return codecInstance.Encode(new ArraySegment(b, 0, read));
+ return new EncodedTargettedSpeech(
+ codecInstance.Encode(new ArraySegment(b, 0, read)),
+ _target,
+ _targetId);
}
else
{
@@ -107,7 +110,10 @@ public byte[] Encode(SpeechCodecs codec)
byte[] b = new byte[frameBytes];
int read = _pcmBuffer.Read(new ArraySegment(b));
- return codecInstance.Encode(new ArraySegment(b, 0, read));
+ return new EncodedTargettedSpeech(
+ codecInstance.Encode(new ArraySegment(b, 0, read)),
+ _target,
+ _targetId);
}
else return null;
}
@@ -133,6 +139,20 @@ private bool TryAddToEncodingBuffer(TargettedSpeech t, out bool stopped)
return true;
}
+ public struct EncodedTargettedSpeech
+ {
+ public readonly byte[] EncodedPcm;
+ public readonly SpeechTarget Target;
+ public readonly uint TargetId;
+
+ public EncodedTargettedSpeech(byte[] encodedPcm, SpeechTarget target, uint targetId)
+ {
+ TargetId = targetId;
+ Target = target;
+ EncodedPcm = encodedPcm;
+ }
+ }
+
///
/// PCM data targetted at a specific person
///
diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs
index 5143df5..4131976 100644
--- a/MumbleSharp/BasicMumbleProtocol.cs
+++ b/MumbleSharp/BasicMumbleProtocol.cs
@@ -10,6 +10,7 @@
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Version = MumbleProto.Version;
+using static MumbleSharp.Audio.AudioEncodingBuffer;
namespace MumbleSharp
{
@@ -53,7 +54,9 @@ public IEnumerable Channels
public User LocalUser { get; private set; }
private AudioEncodingBuffer _encodingBuffer;
+ private ThreadStart _encodingThreadStart;
private Thread _encodingThread;
+ private Exception _encodingThreadException;
private UInt32 sequenceIndex;
public bool IsEncodingThreadRunning { get; set; }
@@ -86,21 +89,29 @@ public virtual void Initialise(MumbleConnection connection)
{
Connection = connection;
- _encodingThread = new Thread(EncodingThreadEntry)
+ //Start the EncodingThreadEntry thread, and collect a possible exception at termination
+ _encodingThreadStart = new ThreadStart(() => EncodingThreadEntry(out _encodingThreadException));
+ _encodingThreadStart += () => {
+ if (_encodingThreadException != null)
+ throw new Exception($"{nameof(BasicMumbleProtocol)}'s {nameof(_encodingThread)} was terminated unexpectedly because of a {_encodingThreadException.GetType().ToString()}", _encodingThreadException);
+ };
+
+ _encodingThread = new Thread(_encodingThreadStart)
{
- IsBackground = true
+ IsBackground = true,
+ Priority = ThreadPriority.AboveNormal
};
}
public void Close()
{
- _encodingThread.Abort();
+ IsEncodingThreadRunning = false;
Connection = null;
LocalUser = null;
}
///
- /// Server has sent a version update
+ /// Server has sent a version update.
///
///
public virtual void Version(Version version)
@@ -109,7 +120,7 @@ public virtual void Version(Version version)
}
///
- /// Validate the certificate the server sends for itself. By default this will acept *all* certificates
+ /// Validate the certificate the server sends for itself. By default this will acept *all* certificates.
///
///
///
@@ -121,12 +132,41 @@ public virtual bool ValidateCertificate(object sender, X509Certificate certifica
return true;
}
+ ///
+ /// Sent by the server to inform the clients of suggested client configuration specified by the server administrator.
+ ///
+ ///
public virtual void SuggestConfig(SuggestConfig config)
{
}
#region Channels
+ protected void SendChannelCreate(Channel channel)
+ {
+ if (channel.Id != 0)
+ throw new ArgumentException("For channel creation the ChannelId cannot be forced, use 0 to avoid this error.", nameof(channel));
+
+ SendChannelState(new MumbleProto.ChannelState()
+ {
+ //ChannelId = channel.Id, //for channel creation the ChannelId must not be set
+ Parent = channel.Parent,
+ Position = channel.Position,
+ Name = channel.Name,
+ Description = channel.Description,
+ Temporary = channel.Temporary,
+ //MaxUsers = 0, //If this value is zero, the maximum number of users allowed in the channel is given by the server's "usersperchannel" setting.
+ });
+ }
+ protected void SendChannelMove(Channel channel, uint parentChannelId)
+ {
+ SendChannelState(new MumbleProto.ChannelState()
+ {
+ ChannelId = channel.Id,
+ Parent = parentChannelId,
+ });
+ }
+
protected virtual void ChannelJoined(Channel channel)
{
}
@@ -134,21 +174,41 @@ protected virtual void ChannelJoined(Channel channel)
protected virtual void ChannelLeft(Channel channel)
{
}
+
///
- /// Server has changed some detail of a channel
+ /// Used to communicate channel properties between the client and the server.
+ /// Sent by the server during the login process or when channel properties are updated.
///
///
public virtual void ChannelState(ChannelState channelState)
{
- var channel = ChannelDictionary.AddOrUpdate(channelState.ChannelId, i => new Channel(this, channelState.ChannelId, channelState.Name, channelState.Parent)
- {
- Temporary = channelState.Temporary,
- Description = channelState.Description,
- Position = channelState.Position
- },
+ if(!channelState.ShouldSerializeChannelId())
+ throw new InvalidOperationException($"{nameof(ChannelState)} must provide a {channelState.ChannelId}.");
+
+ var channel = ChannelDictionary.AddOrUpdate(channelState.ChannelId, i =>
+ {
+ //Add new channel to the dictionary
+ return new Channel(this, channelState.ChannelId, channelState.Name, channelState.Parent)
+ {
+ Temporary = channelState.Temporary,
+ Description = channelState.Description,
+ Position = channelState.Position
+ };
+ },
(i, c) =>
{
- c.Name = channelState.Name;
+ //Update existing channel in the dictionary
+ if (channelState.ShouldSerializeName())
+ c.Name = channelState.Name;
+ if (channelState.ShouldSerializeParent())
+ c.Parent = channelState.Parent;
+ if (channelState.ShouldSerializeTemporary())
+ c.Temporary = channelState.Temporary;
+ if (channelState.ShouldSerializeDescription())
+ c.Description = channelState.Description;
+ if (channelState.ShouldSerializePosition())
+ c.Position = channelState.Position;
+
return c;
}
);
@@ -162,7 +222,17 @@ public virtual void ChannelState(ChannelState channelState)
}
///
- /// Server has removed a channel
+ /// Used to communicate channel properties between the client and the server.
+ /// Client may use this message to update said channel properties.
+ ///
+ ///
+ public void SendChannelState(ChannelState channelState)
+ {
+ Connection.SendControl(PacketType.ChannelState, channelState);
+ }
+
+ ///
+ /// Sent by the server when a channel has been removed and clients should be notified.
///
///
public virtual void ChannelRemove(ChannelRemove channelRemove)
@@ -173,6 +243,15 @@ public virtual void ChannelRemove(ChannelRemove channelRemove)
ChannelLeft(c);
}
}
+
+ ///
+ /// Sent by the client when it wants a channel removed.
+ ///
+ ///
+ public void SendChannelRemove(ChannelRemove channelRemove)
+ {
+ Connection.SendControl(PacketType.ChannelRemove, channelRemove);
+ }
#endregion
#region users
@@ -185,7 +264,8 @@ protected virtual void UserLeft(User user)
}
///
- /// Server has changed some detail of a user
+ /// Sent by the server when it communicates new and changed users to client.
+ /// First seen during login procedure.
///
///
public virtual void UserState(UserState userState)
@@ -195,30 +275,36 @@ public virtual void UserState(UserState userState)
if (userState.ShouldSerializeSession())
{
bool added = false;
- User user = UserDictionary.AddOrUpdate(userState.Session, i => {
+ User user = UserDictionary.AddOrUpdate(userState.Session, i =>
+ {
+ //Add new user to the dictionary
added = true;
return new User(this, userState.Session, _audioSampleRate, _audioSampleBits, _audioSampleChannels);
- }, (i, u) => u);
-
- if (userState.ShouldSerializeSelfDeaf())
- user.SelfDeaf = userState.SelfDeaf;
- if (userState.ShouldSerializeSelfMute())
- user.SelfMuted = userState.SelfMute;
- if (userState.ShouldSerializeMute())
- user.Muted = userState.Mute;
- if (userState.ShouldSerializeDeaf())
- user.Deaf = userState.Deaf;
- if (userState.ShouldSerializeSuppress())
- user.Suppress = userState.Suppress;
- if (userState.ShouldSerializeName())
- user.Name = userState.Name;
- if (userState.ShouldSerializeComment())
- user.Comment = userState.Comment;
-
- if (userState.ShouldSerializeChannelId())
- user.Channel = ChannelDictionary[userState.ChannelId];
- else if(user.Channel == null)
- user.Channel = RootChannel;
+ }, (i, u) =>
+ {
+ //Update existing user in the dictionary
+ if (userState.ShouldSerializeSelfDeaf())
+ u.SelfDeaf = userState.SelfDeaf;
+ if (userState.ShouldSerializeSelfMute())
+ u.SelfMuted = userState.SelfMute;
+ if (userState.ShouldSerializeMute())
+ u.Muted = userState.Mute;
+ if (userState.ShouldSerializeDeaf())
+ u.Deaf = userState.Deaf;
+ if (userState.ShouldSerializeSuppress())
+ u.Suppress = userState.Suppress;
+ if (userState.ShouldSerializeName())
+ u.Name = userState.Name;
+ if (userState.ShouldSerializeComment())
+ u.Comment = userState.Comment;
+
+ if (userState.ShouldSerializeChannelId())
+ u.Channel = ChannelDictionary[userState.ChannelId];
+ else if (u.Channel == null)
+ u.Channel = RootChannel;
+
+ return u;
+ });
//if (added)
UserJoined(user);
@@ -226,7 +312,17 @@ public virtual void UserState(UserState userState)
}
///
- /// A user has been removed from the server (left, kicked or banned)
+ /// Sent by the client when it wishes to alter its state.
+ ///
+ ///
+ public void SendUserState(UserState userState)
+ {
+ Connection.SendControl(PacketType.UserState, userState);
+ }
+
+ ///
+ /// Used to communicate user leaving or being kicked.
+ /// Sent by the server when it informs the clients that a user is not present anymore.
///
///
public virtual void UserRemove(UserRemove userRemove)
@@ -242,24 +338,97 @@ public virtual void UserRemove(UserRemove userRemove)
if (user.Equals(LocalUser))
Connection.Close();
}
+
+ ///
+ /// Sent by the client when it attempts to kick a user.
+ ///
+ ///
+ public void SendUserRemove(UserRemove userRemove)
+ {
+ Connection.SendControl(PacketType.UserRemove, userRemove);
+ }
#endregion
public virtual void ContextAction(ContextAction contextAction)
{
}
- public virtual void ContextActionModify(ContextActionModify contextActionModify)
+ ///
+ /// Sent by the client when it wants to initiate a Context action.
+ ///
+ ///
+ public void SendContextActionModify(ContextActionModify contextActionModify)
{
+ Connection.SendControl(PacketType.ContextActionModify, contextActionModify);
}
+ #region permissions
+ ///
+ /// Sent by the server when it replies to the query or wants the user to resync all channel permissions.
+ ///
+ ///
public virtual void PermissionQuery(PermissionQuery permissionQuery)
{
-
+ if (permissionQuery.Flush)
+ {
+ foreach (var channel in ChannelDictionary.Values)
+ {
+ channel.Permissions = 0; // Permissions.DEFAULT_PERMISSIONS;
+ }
+ }
+ else if (permissionQuery.ShouldSerializeChannelId())
+ {
+ Channel channel;
+ if (!ChannelDictionary.TryGetValue(permissionQuery.ChannelId, out channel))
+ throw new InvalidOperationException($"{nameof(PermissionQuery)} provided an unknown {permissionQuery.ChannelId}.");
+
+ if (permissionQuery.ShouldSerializePermissions())
+ channel.Permissions = (Permission)permissionQuery.Permissions;
+ }
+ else
+ {
+ throw new InvalidOperationException($"{nameof(PermissionQuery)} must provide either {nameof(permissionQuery.Flush)} or {nameof(permissionQuery.ChannelId)}.");
+ }
+ }
+
+ ///
+ /// Sent by the client when it wants permissions for a certain channel.
+ ///
+ ///
+ public void SendPermissionQuery(PermissionQuery permissionQuery)
+ {
+ Connection.SendControl(PacketType.PermissionQuery, permissionQuery);
+ }
+
+ ///
+ /// Sent by the server when it rejects the user connection.
+ ///
+ ///
+ public virtual void Reject(Reject reject)
+ {
+ }
+
+ public virtual void PermissionDenied(PermissionDenied permissionDenied)
+ {
+ }
+
+ ///
+ /// Used by the client to send the authentication credentials to the server.
+ ///
+ ///
+ public void SendAuthenticate(Authenticate authenticate)
+ {
+ Connection.SendControl(PacketType.Authenticate, authenticate);
+ }
+
+ public virtual void Acl(Acl acl)
+ {
}
+ #endregion
#region server setup
///
- /// Initial connection to the server
+ /// ServerSync message is sent by the server when it has authenticated the user and finished synchronizing the server state.
///
///
public virtual void ServerSync(ServerSync serverSync)
@@ -267,9 +436,14 @@ public virtual void ServerSync(ServerSync serverSync)
if (LocalUser != null)
throw new InvalidOperationException("Second ServerSync Received");
+ if (!serverSync.ShouldSerializeSession())
+ throw new InvalidOperationException($"{nameof(ServerSync)} must provide a {nameof(serverSync.Session)}.");
+
//Get the local user
LocalUser = UserDictionary[serverSync.Session];
+ //TODO: handle the serverSync.WelcomeText, serverSync.Permissions, serverSync.MaxBandwidth
+
_encodingBuffer = new AudioEncodingBuffer(_audioSampleRate, _audioSampleBits, _audioSampleChannels, _audioFrameSize);
_encodingThread.Start();
@@ -277,7 +451,7 @@ public virtual void ServerSync(ServerSync serverSync)
}
///
- /// Some detail of the server configuration has changed
+ /// Sent by the server when it informs the clients on server configuration details.
///
///
public virtual void ServerConfig(ServerConfig serverConfig)
@@ -287,52 +461,57 @@ public virtual void ServerConfig(ServerConfig serverConfig)
#endregion
#region voice
- private void EncodingThreadEntry()
+ private void EncodingThreadEntry(out Exception exception)
{
+ exception = null;
IsEncodingThreadRunning = true;
- while (IsEncodingThreadRunning)
+ try
{
- byte[] packet = null;
- try
+ while (IsEncodingThreadRunning)
{
- packet = _encodingBuffer.Encode(TransmissionCodec);
- }
- catch { }
+ EncodedTargettedSpeech? encodedTargettedSpeech = _encodingBuffer.Encode(TransmissionCodec);
- if (packet != null)
- {
- int maxSize = 480;
-
- //taken from JS port
- for (int currentOffcet = 0; currentOffcet < packet.Length; )
+ if (encodedTargettedSpeech.HasValue)
{
- int currentBlockSize = Math.Min(packet.Length - currentOffcet, maxSize);
+ int maxSize = 480;
+
+ //taken from JS port
+ for (int currentOffcet = 0; currentOffcet < encodedTargettedSpeech.Value.EncodedPcm.Length;)
+ {
+ int currentBlockSize = Math.Min(encodedTargettedSpeech.Value.EncodedPcm.Length - currentOffcet, maxSize);
- byte type = TransmissionCodec == SpeechCodecs.Opus ? (byte)4 : (byte)0;
- //originaly [type = codec_type_id << 5 | whistep_chanel_id]. now we can talk only to normal chanel
- type = (byte)(type << 5);
- byte[] sequence = Var64.writeVarint64_alternative((UInt64)sequenceIndex);
+ byte type = TransmissionCodec == SpeechCodecs.Opus ? (byte)4 : (byte)0;
+ //originaly [type = codec_type_id << 5 | whistep_chanel_id].
+ var typeTarget = (byte)(type << 5 | (int)encodedTargettedSpeech.Value.Target);
+ byte[] sequence = Var64.writeVarint64_alternative((UInt64)sequenceIndex);
- // Client side voice header.
- byte[] voiceHeader = new byte[1 + sequence.Length];
- voiceHeader[0] = type;
- sequence.CopyTo(voiceHeader, 1);
+ // Client side voice header.
+ byte[] voiceHeader = new byte[1 + sequence.Length];
+ voiceHeader[0] = typeTarget;
+ sequence.CopyTo(voiceHeader, 1);
- byte[] header = Var64.writeVarint64_alternative((UInt64)currentBlockSize);
- byte[] packedData = new byte[voiceHeader.Length + header.Length + currentBlockSize];
+ byte[] header = Var64.writeVarint64_alternative((UInt64)currentBlockSize);
+ byte[] packedData = new byte[voiceHeader.Length + header.Length + currentBlockSize];
- Array.Copy(voiceHeader, 0, packedData, 0, voiceHeader.Length);
- Array.Copy(header, 0, packedData, voiceHeader.Length, header.Length);
- Array.Copy(packet, currentOffcet, packedData, voiceHeader.Length + header.Length, currentBlockSize);
+ Array.Copy(voiceHeader, 0, packedData, 0, voiceHeader.Length);
+ Array.Copy(header, 0, packedData, voiceHeader.Length, header.Length);
+ Array.Copy(encodedTargettedSpeech.Value.EncodedPcm, currentOffcet, packedData, voiceHeader.Length + header.Length, currentBlockSize);
- Connection.SendVoice(new ArraySegment(packedData));
+ Connection.SendVoice(new ArraySegment(packedData));
- sequenceIndex++;
- currentOffcet += currentBlockSize;
+ sequenceIndex++;
+ currentOffcet += currentBlockSize;
+ }
+ }
+ else
+ {
+ Thread.Sleep(1); //avoids consuming a cpu core at 100% if there's nothing to encode...
}
}
-
- //beware! can take a lot of power, because infinite loop without sleep
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
}
}
@@ -398,11 +577,10 @@ public void SendVoiceStop()
}
#endregion
-
-
-
+ #region ping
///
- /// Received a ping over the TCP connection
+ /// Received a ping over the TCP connection.
+ /// Server must reply to the client Ping packet with the same timestamp and its own good/late/lost/resync numbers. None of the fields is strictly required.
///
///
public virtual void Ping(Ping ping)
@@ -410,15 +588,25 @@ public virtual void Ping(Ping ping)
}
+ ///
+ /// Sent by the client to notify the server that the client is still alive.
+ ///
+ ///
+ public void SendPing(Ping ping)
+ {
+ Connection.SendControl(PacketType.Ping, ping);
+ }
+ #endregion
+
#region text messages
///
- /// Received a text message from the server
+ /// Received a text message from the server.
///
///
public virtual void TextMessage(TextMessage textMessage)
{
User user;
- if (!UserDictionary.TryGetValue(textMessage.Actor, out user)) //If we don't know the user for this packet, just ignore it
+ if (!textMessage.ShouldSerializeActor() || !UserDictionary.TryGetValue(textMessage.Actor, out user)) //If we don't know the user for this packet, just ignore it
return;
if (textMessage.ChannelIds == null || textMessage.ChannelIds.Length == 0)
@@ -460,8 +648,21 @@ protected virtual void PersonalMessageReceived(PersonalMessage message)
protected virtual void ChannelMessageReceived(ChannelMessage message)
{
}
+
+ ///
+ /// Used to send and broadcast text messages.
+ ///
+ ///
+ public void SendTextMessage(TextMessage textMessage)
+ {
+ Connection.SendControl(PacketType.TextMessage, textMessage);
+ }
#endregion
+ ///
+ /// Lists the registered users.
+ ///
+ ///
public virtual void UserList(UserList userList)
{
}
@@ -470,5 +671,83 @@ public virtual X509Certificate SelectCertificate(object sender, string targetHos
{
return null;
}
+
+ ///
+ /// Sent by the server to inform the client to refresh its registered user information.
+ ///
+ ///
+ public virtual void QueryUsers(QueryUsers queryUsers)
+ {
+ }
+
+ ///
+ /// The client should fill the IDs or Names of the users it wants to refresh.
+ /// The server fills the missing parts and sends the message back.
+ ///
+ ///
+ public void SendQueryUsers(QueryUsers queryUsers)
+ {
+ Connection.SendControl(PacketType.QueryUsers, queryUsers);
+ }
+
+ ///
+ /// Sent by the client when it wants to register or clear whisper targets.
+ /// Note: The first available target ID is 1 as 0 is reserved for normal talking. Maximum target ID is 30.
+ ///
+ ///
+ public void SendVoiceTarget(VoiceTarget voiceTarget)
+ {
+ Connection.SendControl(PacketType.VoiceTarget, voiceTarget);
+ }
+
+ ///
+ /// Used to communicate user stats between the server and clients.
+ ///
+ ///
+ public virtual void UserStats(UserStats userStats)
+ {
+ }
+
+ ///
+ /// Used to communicate user stats between the server and clients.
+ ///
+ ///
+ public void SendRequestUserStats(UserStats userStats)
+ {
+ Connection.SendControl(PacketType.UserStats, userStats);
+ }
+
+ ///
+ /// Used by the client to request binary data from the server.
+ /// By default large comments or textures are not sent within standard messages but instead the
+ /// hash is.
+ /// If the client does not recognize the hash it may request the resource when it needs it.
+ /// The client does so by sending a RequestBlob message with the correct fields filled with the user sessions or channel_ids it wants to receive.
+ /// The server replies to this by sending a new UserState/ChannelState message with the resources filled even if they would normally be transmitted as hashes.
+ ///
+ ///
+ public void SendRequestBlob(RequestBlob requestBlob)
+ {
+ Connection.SendControl(PacketType.RequestBlob, requestBlob);
+ }
+
+ ///
+ /// Relays information on the bans.
+ /// The server sends this list only after a client queries for it.
+ ///
+ ///
+ public virtual void BanList(BanList banList)
+ {
+ }
+
+ ///
+ /// Relays information on the bans.
+ /// The client may send the BanList message to either modify the list of bans or query them from the server.
+ ///
+ ///
+ public void SendBanList(BanList banList)
+ {
+ Connection.SendControl(PacketType.BanList, banList);
+ }
}
}
diff --git a/MumbleSharp/IMumbleProtocol.cs b/MumbleSharp/IMumbleProtocol.cs
index 653f89b..fe6b56b 100644
--- a/MumbleSharp/IMumbleProtocol.cs
+++ b/MumbleSharp/IMumbleProtocol.cs
@@ -11,7 +11,7 @@
namespace MumbleSharp
{
///
- /// An object which handles the higher level logic of a connection to a mumble server
+ /// An object which handles the higher level logic of a connection to a mumble server to support reception of all mumble packet types.
///
public interface IMumbleProtocol
{
@@ -37,54 +37,176 @@ public interface IMumbleProtocol
///
IEnumerable Users { get; }
+ ///
+ /// If true, this indicates that the connection was setup and the server accept this client
+ ///
bool ReceivedServerSync { get; }
SpeechCodecs TransmissionCodec { get; }
+ ///
+ /// Associates this protocol with an opening mumble connection
+ ///
+ ///
void Initialise(MumbleConnection connection);
+ ///
+ /// Validate the certificate the server sends for itself. By default this will acept *all* certificates.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors);
X509Certificate SelectCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers);
+ ///
+ /// Server has sent a version update.
+ ///
+ ///
void Version(Version version);
+ ///
+ /// Used to communicate channel properties between the client and the server.
+ /// Sent by the server during the login process or when channel properties are updated.
+ ///
+ ///
void ChannelState(ChannelState channelState);
+ ///
+ /// Sent by the server when it communicates new and changed users to client.
+ /// First seen during login procedure.
+ ///
+ ///
void UserState(UserState userState);
+ // Authenticate is only sent from client to server (see https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto)
+ //void Authenticate(Authenticate authenticate);
+
void CodecVersion(CodecVersion codecVersion);
void ContextAction(ContextAction contextAction);
- void ContextActionModify(ContextActionModify contextActionModify);
+ // ContextActionModify is only sent from client to server (see https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto)
+ //void ContextActionModify(ContextActionModify contextActionModify);
+ ///
+ /// Sent by the server when it replies to the query or wants the user to resync all channel permissions.
+ ///
+ ///
void PermissionQuery(PermissionQuery permissionQuery);
+ ///
+ /// ServerSync message is sent by the server when it has authenticated the user and finished synchronizing the server state.
+ ///
+ ///
void ServerSync(ServerSync serverSync);
+ ///
+ /// Sent by the server when it informs the clients on server configuration details.
+ ///
+ ///
void ServerConfig(ServerConfig serverConfig);
+ ///
+ /// Received a voice packet from the server
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
void EncodedVoice(byte[] packet, uint userSession, long sequence, IVoiceCodec codec, SpeechTarget target);
+ ///
+ /// Received a UDP ping from the server
+ ///
+ ///
void UdpPing(byte[] packet);
+ ///
+ /// Received a ping over the TCP connection.
+ /// Server must reply to the client Ping packet with the same timestamp and its own good/late/lost/resync numbers. None of the fields is strictly required.
+ ///
+ ///
void Ping(Ping ping);
+ ///
+ /// Used to communicate user leaving or being kicked.
+ /// Sent by the server when it informs the clients that a user is not present anymore.
+ ///
+ ///
void UserRemove(UserRemove userRemove);
+ ///
+ /// Sent by the server when a channel has been removed and clients should be notified.
+ ///
+ ///
void ChannelRemove(ChannelRemove channelRemove);
+ ///
+ /// Received a text message from the server.
+ ///
+ ///
void TextMessage(TextMessage textMessage);
+ ///
+ /// Lists the registered users.
+ ///
+ ///
void UserList(UserList userList);
+ ///
+ /// Sent by the server to inform the clients of suggested client configuration specified by the server administrator.
+ ///
+ ///
void SuggestConfig(SuggestConfig suggestedConfiguration);
+ ///
+ /// Get a voice decoder for the specified user/codec combination
+ ///
+ ///
+ ///
+ ///
IVoiceCodec GetCodec(uint user, SpeechCodecs codec);
void SendVoice(ArraySegment pcm, SpeechTarget target, uint targetId);
void SendVoiceStop();
+
+ ///
+ /// Sent by the server when it rejects the user connection.
+ ///
+ ///
+ void Reject(Reject reject);
+
+ void PermissionDenied(PermissionDenied permissionDenied);
+
+ void Acl(Acl acl);
+
+ ///
+ /// Sent by the server to inform the client to refresh its registered user information.
+ ///
+ ///
+ void QueryUsers(QueryUsers queryUsers);
+
+ // VoiceTarget is only sent from client to server (see https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto)
+ //void VoiceTarget(VoiceTarget voiceTarget);
+
+ ///
+ /// Used to communicate user stats between the server and clients.
+ ///
+ void UserStats(UserStats userStats);
+
+ // RequestBlob is only sent from client to server (see https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto)
+ //void RequestBlob(RequestBlob requestBlob);
+
+ ///
+ /// Relays information on the bans.
+ /// The server sends this list only after a client queries for it.
+ ///
+ void BanList(BanList banList);
}
}
diff --git a/MumbleSharp/Model/Channel.cs b/MumbleSharp/Model/Channel.cs
index 05b54ea..6056f59 100644
--- a/MumbleSharp/Model/Channel.cs
+++ b/MumbleSharp/Model/Channel.cs
@@ -19,7 +19,8 @@ public class Channel
public string Description { get; set; }
public int Position { get; set; }
public uint Id { get; private set; }
- public uint Parent { get; private set; }
+ public uint Parent { get; internal set; }
+ public Permission Permissions { get; internal set; }
// Using a concurrent dictionary as a concurrent hashset (why doesn't .net provide a concurrent hashset?!) - http://stackoverflow.com/a/18923091/108234
private readonly ConcurrentDictionary _users = new ConcurrentDictionary();
@@ -78,11 +79,11 @@ public void SendMessage(string message, bool recursive)
SendMessage(messages, recursive);
}
- public void SendVoice(ArraySegment buffer, bool whisper = false)
+ public void SendVoice(ArraySegment buffer, SpeechTarget target = SpeechTarget.Normal)
{
Owner.SendVoice(
buffer,
- target: whisper ? SpeechTarget.WhisperToChannel : SpeechTarget.Normal,
+ target: target,
targetId: Id
);
}
diff --git a/MumbleSharp/Model/Permissions.cs b/MumbleSharp/Model/Permissions.cs
new file mode 100644
index 0000000..73be254
--- /dev/null
+++ b/MumbleSharp/Model/Permissions.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MumbleSharp.Model
+{
+ public class Permissions
+ {
+ public const Permission DEFAULT_PERMISSIONS = Permission.Traverse | Permission.Enter | Permission.Speak | Permission.Whisper | Permission.TextMessage;
+ }
+
+ [Flags]
+ public enum Permission : uint
+ {
+ //https://github.com/mumble-voip/mumble/blob/master/src/ACL.h
+ //https://github.com/mumble-voip/mumble/blob/master/src/ACL.cpp
+
+ ///
+ /// This represents no privileges.
+ ///
+ None = 0x0,
+
+ ///
+ /// This represents total access to the channel, including the ability to change group and ACL information.
+ /// This privilege implies all other privileges.
+ ///
+ Write = 0x1,
+
+ ///
+ /// This represents the permission to traverse the channel.
+ /// If a user is denied this privilege, he will be unable to access this channel and any sub-channels in any way, regardless of other permissions in the sub-channels.
+ ///
+ Traverse = 0x2,
+
+ ///
+ /// This represents the permission to join the channel.
+ /// If you have a hierarchical channel structure, you might want to give everyone Traverse, but restrict Enter in the root of your hierarchy.
+ ///
+ Enter = 0x4,
+
+ ///
+ /// This represents the permission to speak in a channel.
+ /// Users without this privilege will be suppressed by the server (seen as muted), and will be unable to speak until they are unmuted by someone with the appropriate privileges.
+ ///
+ Speak = 0x8,
+
+ ///
+ /// This represents the permission to mute and deafen other users.
+ /// Once muted, a user will stay muted until he is unmuted by another privileged user or reconnects to the server.
+ ///
+ MuteDeafen = 0x10,
+
+ ///
+ /// This represents the permission to move a user to another channel or kick him from the server.
+ /// To actually move the user, either the moving user must have Move privileges in the destination channel, or the user must normally be allowed to enter the channel.
+ /// Users with this privilege can move users into channels the target user normally wouldn't have permission to enter.
+ ///
+ Move = 0x20,
+
+ ///
+ /// This represents the permission to make sub-channels.
+ /// The user making the sub-channel will be added to the admin group of the sub-channel.
+ ///
+ MakeChannel = 0x40,
+
+ ///
+ /// This represents the permission to link channels.
+ /// Users in linked channels hear each other, as long as the speaking user has the speak privilege in the channel of the listener.
+ /// You need the link privilege in both channels to create a link, but just in either channel to remove it.
+ ///
+ LinkChannel = 0x80,
+
+ ///
+ /// This represents the permission to whisper to this channel from the outside.
+ /// This works exactly like the speak privilege, but applies to packets spoken with the Whisper key held down.
+ /// This may be used to broadcast to a hierarchy of channels without linking.
+ ///
+ Whisper = 0x100,
+
+ ///
+ /// This represents the permission to write text messages to other users in this channel.
+ ///
+ TextMessage = 0x200,
+
+ ///
+ /// This represents the permission to make a temporary subchannel.
+ /// The user making the sub-channel will be added to the admin group of the sub-channel.
+ /// Temporary channels are not stored and disappear when the last user leaves.
+ ///
+ MakeTempChannel = 0x400,
+
+ // --- Root channel only ---
+
+ ///
+ /// This represents the permission to forcibly remove users from the server.
+ /// Root channel only.
+ ///
+ Kick = 0x10000,
+
+ ///
+ /// This represents the permission to permanently remove users from the server.
+ /// Root channel only.
+ ///
+ Ban = 0x20000,
+
+ ///
+ /// This represents the permission to register and unregister users on the server.
+ /// Root channel only.
+ ///
+ Register = 0x40000,
+
+ ///
+ /// This represents the permission to register oneself on the server.
+ /// Root channel only.
+ ///
+ SelfRegister = 0x80000,
+
+ Cached = 0x8000000,
+ All = 0xf07ff
+ };
+}
diff --git a/MumbleSharp/Model/User.cs b/MumbleSharp/Model/User.cs
index 293c6da..61dc695 100644
--- a/MumbleSharp/Model/User.cs
+++ b/MumbleSharp/Model/User.cs
@@ -114,12 +114,12 @@ public void SendMuteDeaf()
if(this.Id == _owner.LocalUser.Id)
{
- userstate.SelfMute = this.SelfMuted;
+ userstate.SelfMute = this.SelfMuted || this.SelfDeaf; //mumble disallows being deaf without being muted
userstate.SelfDeaf = this.SelfDeaf;
} else
{
userstate.UserId = this.Id;
- userstate.Mute = this.Muted;
+ userstate.Mute = this.Muted || this.Deaf; //mumble disallows being deaf without being muted
userstate.Deaf = this.Deaf;
}
diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs
index 52fca5c..c9de4de 100644
--- a/MumbleSharp/MumbleConnection.cs
+++ b/MumbleSharp/MumbleConnection.cs
@@ -18,6 +18,8 @@ namespace MumbleSharp
///
public class MumbleConnection
{
+ private static double PING_DELAY_MILLISECONDS = 5000;
+
public float? TcpPingAverage { get; set; }
public float? TcpPingVariance { get; set; }
public uint? TcpPingPackets { get; set; }
@@ -86,26 +88,36 @@ public void Close()
{
State = ConnectionStates.Disconnecting;
- _udp.Close();
- _tcp.Close();
+ _udp?.Close();
+ _tcp?.Close();
State = ConnectionStates.Disconnected;
}
- public void Process()
+ ///
+ /// Processes a received network packet.
+ /// This method should be called periodically.
+ ///
+ /// true, if a packet was processed. When this returns true you may want to recall the Process() method as soon as possible as their might be a queue on the network stack (like after a simple Thread.Yield() instead of a more relaxed Thread.Sleep(1) if it returned false).
+ public bool Process()
{
- if ((DateTime.Now - _lastSentPing).TotalSeconds > 5)
+ if ((DateTime.UtcNow - _lastSentPing).TotalMilliseconds > PING_DELAY_MILLISECONDS)
{
_tcp.SendPing();
if (_udp.IsConnected)
_udp.SendPing();
- _lastSentPing = DateTime.Now;
+ _lastSentPing = DateTime.UtcNow;
}
- _tcp.Process();
- _udp.Process();
+
+ _tcpProcessed = _tcp.Process();
+ _udpProcessed = _udp.IsConnected ? _udp.Process() : false;
+ return _tcpProcessed || _udpProcessed;
}
+ //declared outside method for alloc optimization
+ private bool _tcpProcessed;
+ private bool _udpProcessed;
public void SendControl(PacketType type, T packet)
{
@@ -114,9 +126,8 @@ public void SendControl(PacketType type, T packet)
public void SendVoice(ArraySegment packet)
{
- //This is *totally wrong*
- //the packet contains raw encoded voice data, but we need to put it into the proper packet format
- //UPD: packet prepare before this method called. See basic protocol
+ //The packet must be a well formed Mumble packet as described in https://mumble-protocol.readthedocs.org/en/latest/voice_data.html#packet-format
+ //The packet is created in BasicMumbleProtocol's EncodingThread
_tcp.SendVoice(PacketType.UDPTunnel, packet);
}
@@ -234,7 +245,7 @@ private void ReceivePing(Ping ping)
if (ping.ShouldSerializeTimestamp() && ping.Timestamp != 0)
{
var mostRecentPingtime =
- (float)TimeSpan.FromTicks(DateTime.Now.Ticks - (long)ping.Timestamp).TotalMilliseconds;
+ (float)TimeSpan.FromTicks(DateTime.UtcNow.Ticks - (long)ping.Timestamp).TotalMilliseconds;
//The ping time is the one-way transit time.
mostRecentPingtime /= 2;
@@ -295,10 +306,10 @@ public void Connect(string username, string password, string[] tokens, string se
_reader = new BinaryReader(_ssl);
_writer = new BinaryWriter(_ssl);
- DateTime startWait = DateTime.Now;
+ DateTime startWait = DateTime.UtcNow;
while (!_ssl.IsAuthenticated)
{
- if (DateTime.Now - startWait > TimeSpan.FromSeconds(2))
+ if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(2))
throw new TimeoutException("Timed out waiting for ssl authentication");
System.Threading.Thread.Sleep(10);
@@ -402,7 +413,7 @@ public void SendPing()
// otherwise the stats will be throw off by the time it takes to connect.
if (_connection._shouldSetTimestampWhenPinging)
{
- ping.Timestamp = (ulong) DateTime.Now.Ticks;
+ ping.Timestamp = (ulong)DateTime.UtcNow.Ticks;
}
if (_connection.TcpPingAverage.HasValue)
@@ -424,18 +435,20 @@ public void SendPing()
- public void Process()
+ public bool Process()
{
if (!_client.Connected)
throw new InvalidOperationException("Not connected");
if (!_netStream.DataAvailable)
- return;
+ return false;
lock (_ssl)
{
PacketType type = (PacketType)IPAddress.NetworkToHostOrder(_reader.ReadInt16());
+#if DEBUG
Console.WriteLine("{0:HH:mm:ss}: {1}", DateTime.Now, type.ToString());
+#endif
switch (type)
{
@@ -443,9 +456,11 @@ public void Process()
_protocol.Version(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
case PacketType.CryptSetup:
- var cryptSetup = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
- _connection.ProcessCryptState(cryptSetup);
- SendPing();
+ {
+ var cryptSetup = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
+ _connection.ProcessCryptState(cryptSetup);
+ SendPing();
+ }
break;
case PacketType.ChannelState:
_protocol.ChannelState(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
@@ -459,9 +474,6 @@ public void Process()
case PacketType.ContextAction:
_protocol.ContextAction(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
- case PacketType.ContextActionModify:
- _protocol.ContextActionModify(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
- break;
case PacketType.PermissionQuery:
_protocol.PermissionQuery(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
@@ -472,13 +484,17 @@ public void Process()
_protocol.ServerConfig(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
case PacketType.UDPTunnel:
- var length = IPAddress.NetworkToHostOrder(_reader.ReadInt32());
- _connection.ReceiveDecryptedUdp(_reader.ReadBytes(length));
+ {
+ var length = IPAddress.NetworkToHostOrder(_reader.ReadInt32());
+ _connection.ReceiveDecryptedUdp(_reader.ReadBytes(length));
+ }
break;
case PacketType.Ping:
- var ping = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
- _connection.ReceivePing(ping);
- _protocol.Ping(ping);
+ {
+ var ping = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
+ _connection.ReceivePing(ping);
+ _protocol.Ping(ping);
+ }
break;
case PacketType.UserRemove:
_protocol.UserRemove(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
@@ -487,33 +503,48 @@ public void Process()
_protocol.ChannelRemove(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
case PacketType.TextMessage:
- var message = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
- _protocol.TextMessage(message);
+ {
+ var message = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian);
+ _protocol.TextMessage(message);
+ }
break;
-
case PacketType.Reject:
- throw new NotImplementedException();
-
+ _protocol.Reject(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
case PacketType.UserList:
_protocol.UserList(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
-
case PacketType.SuggestConfig:
_protocol.SuggestConfig(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
break;
-
- case PacketType.Authenticate:
case PacketType.PermissionDenied:
+ _protocol.PermissionDenied(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
case PacketType.ACL:
+ _protocol.Acl(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
case PacketType.QueryUsers:
- case PacketType.VoiceTarget:
+ _protocol.QueryUsers(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
case PacketType.UserStats:
- case PacketType.RequestBlob:
+ _protocol.UserStats(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
case PacketType.BanList:
+ _protocol.BanList(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian));
+ break;
+
+
+ //The following PacketTypes are only sent from client to server (see https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto)
+ case PacketType.Authenticate:
+ case PacketType.ContextActionModify:
+ case PacketType.RequestBlob:
+ case PacketType.VoiceTarget:
default:
throw new NotImplementedException($"{nameof(Process)} {nameof(PacketType)}.{type.ToString()}");
}
}
+
+ return true;
}
}
@@ -548,7 +579,7 @@ public void Close()
public void SendPing()
{
- long timestamp = DateTime.Now.Ticks;
+ long timestamp = DateTime.UtcNow.Ticks;
byte[] buffer = new byte[9];
buffer[0] = 1 << 5;
@@ -564,17 +595,18 @@ public void SendPing()
_client.Send(buffer, buffer.Length);
}
- public void Process()
+ public bool Process()
{
- if (_client.Client == null)
- return;
- if (_client.Available == 0)
- return;
+ if (_client.Client == null
+ || _client.Available == 0)
+ return false;
IPEndPoint sender = _host;
byte[] data = _client.Receive(ref sender);
_connection.ReceivedEncryptedUdp(data);
+
+ return true;
}
}
}
diff --git a/MumbleSharp/MumbleSharp.csproj b/MumbleSharp/MumbleSharp.csproj
index bc5c1b5..d85fc26 100644
--- a/MumbleSharp/MumbleSharp.csproj
+++ b/MumbleSharp/MumbleSharp.csproj
@@ -43,8 +43,8 @@
-
- ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll
+
+ ..\packages\NAudio.1.10.0\lib\net35\NAudio.dll
..\Dependencies\NSpeex v1.1.1\lib\NSpeex.dll
@@ -87,6 +87,7 @@
+
diff --git a/MumbleSharp/Packets/PacketType.cs b/MumbleSharp/Packets/PacketType.cs
index 096e6e0..abbbd42 100644
--- a/MumbleSharp/Packets/PacketType.cs
+++ b/MumbleSharp/Packets/PacketType.cs
@@ -4,32 +4,145 @@ namespace MumbleSharp.Packets
public enum PacketType
:short
{
- Version = 0,
- UDPTunnel = 1,
- Authenticate = 2,
- Ping = 3,
- Reject = 4,
- ServerSync = 5,
- ChannelRemove = 6,
- ChannelState = 7,
- UserRemove = 8,
- UserState = 9,
- BanList = 10,
- TextMessage = 11,
- PermissionDenied= 12,
- ACL = 13,
- QueryUsers = 14,
- CryptSetup = 15,
+ Version = 0,
+
+ ///
+ /// Not used. Not even for tunneling UDP through TCP.
+ ///
+ UDPTunnel = 1,
+
+ ///
+ /// Used by the client to send the authentication credentials to the server.
+ ///
+ Authenticate = 2,
+
+ ///
+ /// Sent by the client to notify the server that the client is still alive.
+ /// Server must reply to the packet with the same timestamp and its own good/late/lost/resync numbers. None of the fields is strictly required.
+ ///
+ Ping = 3,
+
+ ///
+ /// Sent by the server when it rejects the user connection.
+ ///
+ Reject = 4,
+
+ ///
+ /// ServerSync message is sent by the server when it has authenticated the user and finished synchronizing the server state.
+ ///
+ ServerSync = 5,
+
+ ///
+ /// Sent by the client when it wants a channel removed. Sent by the server when a channel has been removed and clients should be notified.
+ ///
+ ChannelRemove = 6,
+
+ ///
+ /// Used to communicate channel properties between the client and the server.
+ /// Sent by the server during the login process or when channel properties are updated.
+ /// Client may use this message to update said channel properties.
+ ///
+ ChannelState = 7,
+
+ ///
+ /// Used to communicate user leaving or being kicked. May be sent by the client
+ /// when it attempts to kick a user. Sent by the server when it informs the clients that a user is not present anymore.
+ ///
+ UserRemove = 8,
+
+ ///
+ /// Sent by the server when it communicates new and changed users to client.
+ /// First seen during login procedure. May be sent by the client when it wishes to alter its state.
+ ///
+ UserState = 9,
+
+ ///
+ /// Relays information on the bans.
+ /// The client may send the BanList message to either modify the list of bans or query them from the server.
+ /// The server sends this list only after a client queries for it.
+ ///
+ BanList = 10,
+
+ ///
+ /// Used to send and broadcast text messages.
+ ///
+ TextMessage = 11,
+
+
+ PermissionDenied = 12,
+
+
+ ACL = 13,
+
+ ///
+ /// Client may use this message to refresh its registered user information.
+ /// The client should fill the IDs or Names of the users it wants to refresh.
+ /// The server fills the missing parts and sends the message back.
+ ///
+ QueryUsers = 14,
+
+ ///
+ /// Used to initialize and resync the UDP encryption. Either side may request a resync by sending the message without any values filled.
+ /// The resync is performed by sending the message with only the client or server nonce filled.
+ ///
+ CryptSetup = 15,
+
+
ContextActionModify= 16,
- ContextAction = 17,
- UserList = 18,
- VoiceTarget = 19,
+
+ ///
+ /// Sent by the client when it wants to initiate a Context action.
+ ///
+ ContextAction = 17,
+
+ ///
+ /// Lists the registered users.
+ ///
+ UserList = 18,
+
+ ///
+ /// Sent by the client when it wants to register or clear whisper targets.
+ /// Note: The first available target ID is 1 as 0 is reserved for normal talking. Maximum target ID is 30.
+ ///
+ VoiceTarget = 19,
+
+ ///
+ /// Sent by the client when it wants permissions for a certain channel.
+ /// Sent by the server when it replies to the query or wants the user to resync all channel permissions.
+ ///
PermissionQuery = 20,
- CodecVersion = 21,
- UserStats = 22,
- RequestBlob = 23,
- ServerConfig = 24,
- SuggestConfig = 25,
+
+ ///
+ /// Sent by the server to notify the users of the version of the CELT codec they should use.
+ /// This may change during the connection when new users join.
+ ///
+ CodecVersion = 21,
+
+ ///
+ /// Used to communicate user stats between the server and clients.
+ ///
+ UserStats = 22,
+
+ ///
+ /// Used by the client to request binary data from the server.
+ /// By default large comments or textures are not sent within standard messages but instead the hash is.
+ /// If the client does not recognize the hash it may request the resource when it needs it.
+ /// The client does so by sending a RequestBlob message with the correct fields filled with the user sessions or channel_ids it wants to receive.
+ /// The server replies to this by sending a new UserState/ChannelState message with the resources filled even if they would normally be transmitted as hashes.
+ ///
+ RequestBlob = 23,
+
+ ///
+ /// Sent by the server when it informs the clients on server configuration details.
+ ///
+ ServerConfig = 24,
+
+ ///
+ /// Sent by the server to inform the clients of suggested client configuration specified by the server administrator.
+ ///
+ SuggestConfig = 25,
+
+
Empty = 32767
}
}
diff --git a/MumbleSharp/Properties/AssemblyInfo.cs b/MumbleSharp/Properties/AssemblyInfo.cs
index 53da0f4..30a8121 100644
--- a/MumbleSharp/Properties/AssemblyInfo.cs
+++ b/MumbleSharp/Properties/AssemblyInfo.cs
@@ -10,7 +10,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MumbleSharp")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,6 +32,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.1.0.0")]
[assembly: InternalsVisibleTo("MumbleSharpTest")]
diff --git a/MumbleSharp/packages.config b/MumbleSharp/packages.config
index 1603fa4..83bcd44 100644
--- a/MumbleSharp/packages.config
+++ b/MumbleSharp/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file