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