From cace99375f8a0df417e3eacd758c8327cd3df77d Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 01:35:37 +0200 Subject: [PATCH 01/22] (feature) Improved exceptions for PermissionDenied and Reject packets. --- MumbleSharp/MumbleConnection.cs | 45 +++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 52fca5c..829112d 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -443,9 +443,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)); @@ -472,13 +474,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,13 +493,18 @@ 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(); - + { + //TODO: Reject use delegate instead of exception + var reject = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian); + throw new ProtocolViolationException($"{nameof(Reject)} Type={reject.Type}, Reason='{reject.Reason}'"); + } + break; case PacketType.UserList: _protocol.UserList(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); break; @@ -501,9 +512,15 @@ public void Process() case PacketType.SuggestConfig: _protocol.SuggestConfig(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); break; + case PacketType.PermissionDenied: + { + //TODO: PermissionDenied use delegate instead of exception + var premissionDenied = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian); + throw new UnauthorizedAccessException($"{nameof(PermissionDenied)} Type={premissionDenied.Type}, User={premissionDenied.Name} ({premissionDenied.Session}), ChannelId={premissionDenied.ChannelId} , Reason='{premissionDenied.Reason}'"); + } + break; case PacketType.Authenticate: - case PacketType.PermissionDenied: case PacketType.ACL: case PacketType.QueryUsers: case PacketType.VoiceTarget: From 834bab89295753bd6846ec0eb54a1c98d1a3f3f5 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 01:35:58 +0200 Subject: [PATCH 02/22] (feature) Added support for Channel Create/Move/Remove. --- MumbleSharp/BasicMumbleProtocol.cs | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 9616186..254e791 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -117,6 +117,37 @@ public virtual void SuggestConfig(SuggestConfig config) } #region Channels + protected virtual 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)); + + Connection.SendControl(PacketType.ChannelState, 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 + }); + } + protected virtual void SendChannelMove(Channel channel, uint parentChannelId) + { + Connection.SendControl(PacketType.ChannelState, new MumbleProto.ChannelState() + { + ChannelId = channel.Id, + Parent = parentChannelId, + }); + } + protected virtual void SendChannelRemove(Channel channel) + { + Connection.SendControl(PacketType.ChannelRemove, new MumbleProto.ChannelRemove() + { + ChannelId = channel.Id + }); + } + protected virtual void ChannelJoined(Channel channel) { } From 8d05963aee24cc1f15acff61c9b2bb86f0d45dbc Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 13:22:57 +0200 Subject: [PATCH 03/22] (feature) Support for all MumbleProto Packet types. Also includes full documentation taken from https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto --- MumbleSharp/BasicMumbleProtocol.cs | 231 +++++++++++++++++++++++++---- MumbleSharp/IMumbleProtocol.cs | 126 +++++++++++++++- MumbleSharp/MumbleConnection.cs | 35 +++-- MumbleSharp/Packets/PacketType.cs | 161 +++++++++++++++++--- 4 files changed, 484 insertions(+), 69 deletions(-) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 254e791..0284109 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -90,7 +90,7 @@ public void Close() } /// - /// Server has sent a version update + /// Server has sent a version update. /// /// public virtual void Version(Version version) @@ -99,7 +99,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. /// /// /// @@ -111,18 +111,22 @@ 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 virtual void SendChannelCreate(Channel channel) + 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)); - Connection.SendControl(PacketType.ChannelState, new MumbleProto.ChannelState() + SendChannelState(new MumbleProto.ChannelState() { //ChannelId = channel.Id, //for channel creation the ChannelId must not be set Parent = channel.Parent, @@ -132,21 +136,14 @@ protected virtual void SendChannelCreate(Channel channel) Temporary = channel.Temporary }); } - protected virtual void SendChannelMove(Channel channel, uint parentChannelId) + protected void SendChannelMove(Channel channel, uint parentChannelId) { - Connection.SendControl(PacketType.ChannelState, new MumbleProto.ChannelState() + SendChannelState(new MumbleProto.ChannelState() { ChannelId = channel.Id, Parent = parentChannelId, }); } - protected virtual void SendChannelRemove(Channel channel) - { - Connection.SendControl(PacketType.ChannelRemove, new MumbleProto.ChannelRemove() - { - ChannelId = channel.Id - }); - } protected virtual void ChannelJoined(Channel channel) { @@ -155,8 +152,10 @@ 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) @@ -183,7 +182,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.ChannelRemove, channelState); + } + + /// + /// Sent by the server when a channel has been removed and clients should be notified. /// /// public virtual void ChannelRemove(ChannelRemove channelRemove) @@ -194,6 +203,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 @@ -206,7 +224,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) @@ -247,7 +266,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) @@ -263,24 +292,77 @@ 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) { } + #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) { } + /// + /// Sent by the client when it wants permissions for a certain channel. + /// + /// + public void SendPermissionQuery(PermissionQuery permissionQuery) + { + + } + + /// + /// Sent by the server when it rejects the user connection. + /// + /// + public virtual void Reject(PermissionDenied permissionDenied) + { + } + + 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) @@ -298,7 +380,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) @@ -419,11 +501,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) @@ -431,9 +512,19 @@ 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) @@ -481,8 +572,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) { } @@ -491,5 +595,82 @@ 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) + { + } + + /// + /// 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..d82ff36 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(PermissionDenied permissionDenied); + + 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/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 829112d..4e4eea1 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -461,9 +461,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; @@ -499,34 +496,36 @@ public void Process() } break; case PacketType.Reject: - { - //TODO: Reject use delegate instead of exception - var reject = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian); - throw new ProtocolViolationException($"{nameof(Reject)} Type={reject.Type}, Reason='{reject.Reason}'"); - } + _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.PermissionDenied: - { - //TODO: PermissionDenied use delegate instead of exception - var premissionDenied = Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian); - throw new UnauthorizedAccessException($"{nameof(PermissionDenied)} Type={premissionDenied.Type}, User={premissionDenied.Name} ({premissionDenied.Session}), ChannelId={premissionDenied.ChannelId} , Reason='{premissionDenied.Reason}'"); - } + _protocol.PermissionDenied(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); break; - - case PacketType.Authenticate: 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()}"); } 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 } } From 8062b6f343f51e5b4f4e202c234da30527437f6b Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 15:25:29 +0200 Subject: [PATCH 04/22] (refactor) use DateTime.UtcNow instead of .Now for performance (see http://benhall.io/net-performance-tip-benchmarking/) --- MumbleSharp/MumbleConnection.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 4e4eea1..4538b01 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; } @@ -94,14 +96,14 @@ public void Close() public void 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(); @@ -234,7 +236,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 +297,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 +404,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) @@ -435,7 +437,9 @@ public void Process() 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) { @@ -564,7 +568,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; From f2a3d9556ddd9b83b231747b51036ea67628be9d Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 16:21:36 +0200 Subject: [PATCH 05/22] (feature) MumbleConnection.Process() now returns true if it did process something, this allows a caller thread to optimize how its loop sleeping strategy. --- MumbleClient/Program.cs | 5 ++++- MumbleGuiClient/Form1.cs | 2 ++ MumbleSharp/MumbleConnection.cs | 33 +++++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/MumbleClient/Program.cs b/MumbleClient/Program.cs index 72a324b..435be10 100644 --- a/MumbleClient/Program.cs +++ b/MumbleClient/Program.cs @@ -96,7 +96,10 @@ private static void UpdateLoop(MumbleConnection connection) { while (connection.State != ConnectionStates.Disconnected) { - connection.Process(); + if (connection.Process()) + Thread.Yield(); + else + Thread.Sleep(1); } } } diff --git a/MumbleGuiClient/Form1.cs b/MumbleGuiClient/Form1.cs index e3fa947..489e584 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; @@ -455,6 +456,7 @@ private void btnConnect_Click(object sender, EventArgs e) while (connection.Protocol.LocalUser == null) { connection.Process(); + Thread.Sleep(1); } } diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 4538b01..4b6445d 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -94,7 +94,12 @@ public void 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.UtcNow - _lastSentPing).TotalMilliseconds > PING_DELAY_MILLISECONDS) { @@ -105,9 +110,14 @@ public void Process() _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) { @@ -426,13 +436,13 @@ 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) { @@ -534,6 +544,8 @@ public void Process() throw new NotImplementedException($"{nameof(Process)} {nameof(PacketType)}.{type.ToString()}"); } } + + return true; } } @@ -584,17 +596,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; } } } From b800cfdb83ea7e82d849a4ea1847ca5b8c23e9d1 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 16:29:53 +0200 Subject: [PATCH 06/22] (fix) #42 Client sample projects. --- MumbleClient/MicrophoneRecorder.cs | 2 +- MumbleGuiClient/Extensions/MicrophoneRecorder.cs | 2 +- MumbleGuiClient/Form1.cs | 4 +++- MumbleSharp/MumbleSharp.csproj | 6 ------ 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/MumbleClient/MicrophoneRecorder.cs b/MumbleClient/MicrophoneRecorder.cs index 7571326..ec81d30 100644 --- a/MumbleClient/MicrophoneRecorder.cs +++ b/MumbleClient/MicrophoneRecorder.cs @@ -15,7 +15,7 @@ public MicrophoneRecorder(IMumbleProtocol protocol) _protocol = protocol; var sourceStream = new WaveInEvent { - WaveFormat = new WaveFormat(Constants.SAMPLE_RATE, Constants.SAMPLE_BITS, Constants.CHANNELS) + WaveFormat = new WaveFormat(Constants.DEFAULT_AUDIO_SAMPLE_RATE, Constants.DEFAULT_AUDIO_SAMPLE_BITS, Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) }; sourceStream.DataAvailable += VoiceDataAvailable; diff --git a/MumbleGuiClient/Extensions/MicrophoneRecorder.cs b/MumbleGuiClient/Extensions/MicrophoneRecorder.cs index 8a8edbf..a44680e 100644 --- a/MumbleGuiClient/Extensions/MicrophoneRecorder.cs +++ b/MumbleGuiClient/Extensions/MicrophoneRecorder.cs @@ -76,7 +76,7 @@ public void Record() sourceStream.Dispose(); sourceStream = new WaveInEvent { - WaveFormat = new WaveFormat(Constants.SAMPLE_RATE, Constants.SAMPLE_BITS, Constants.CHANNELS) + WaveFormat = new WaveFormat(Constants.DEFAULT_AUDIO_SAMPLE_RATE, Constants.DEFAULT_AUDIO_SAMPLE_BITS, Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) }; sourceStream.BufferMilliseconds = 10; sourceStream.DeviceNumber = SelectedDevice; diff --git a/MumbleGuiClient/Form1.cs b/MumbleGuiClient/Form1.cs index 489e584..2582025 100644 --- a/MumbleGuiClient/Form1.cs +++ b/MumbleGuiClient/Form1.cs @@ -450,8 +450,10 @@ private void btnConnect_Click(object sender, EventArgs e) tvUsers.Nodes.Clear(); } + string srvConnectName = textBoxUserName.Text + "@" + addr + ":" + port; + connection = new MumbleConnection(new IPEndPoint(Dns.GetHostAddresses(addr).First(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork), port), protocol); - connection.Connect(name, pass, new string[0], addr); + connection.Connect(name, pass, new string[0], srvConnectName); while (connection.Protocol.LocalUser == null) { diff --git a/MumbleSharp/MumbleSharp.csproj b/MumbleSharp/MumbleSharp.csproj index 3a86563..f68da51 100644 --- a/MumbleSharp/MumbleSharp.csproj +++ b/MumbleSharp/MumbleSharp.csproj @@ -43,15 +43,9 @@ - - ..\packages\NAudio.1.8.4\lib\net35\NAudio.dll - ..\Dependencies\NSpeex v1.1.1\lib\NSpeex.dll - - ..\packages\protobuf-net.2.3.7\lib\net40\protobuf-net.dll - From 351ffda500a4f35cffcf9a51c6c4a8a06cf955d4 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 11 Jul 2019 19:29:53 +0200 Subject: [PATCH 07/22] (fix) Reject message deserialization. --- MumbleSharp/BasicMumbleProtocol.cs | 4 ++-- MumbleSharp/IMumbleProtocol.cs | 4 ++-- MumbleSharp/MumbleConnection.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 0284109..f3283ca 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -337,8 +337,8 @@ public void SendPermissionQuery(PermissionQuery permissionQuery) /// /// Sent by the server when it rejects the user connection. /// - /// - public virtual void Reject(PermissionDenied permissionDenied) + /// + public virtual void Reject(Reject reject) { } diff --git a/MumbleSharp/IMumbleProtocol.cs b/MumbleSharp/IMumbleProtocol.cs index d82ff36..fe6b56b 100644 --- a/MumbleSharp/IMumbleProtocol.cs +++ b/MumbleSharp/IMumbleProtocol.cs @@ -179,8 +179,8 @@ public interface IMumbleProtocol /// /// Sent by the server when it rejects the user connection. /// - /// - void Reject(PermissionDenied permissionDenied); + /// + void Reject(Reject reject); void PermissionDenied(PermissionDenied permissionDenied); diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 4b6445d..126de75 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -510,7 +510,7 @@ public bool Process() } break; case PacketType.Reject: - _protocol.Reject(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); + _protocol.Reject(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); break; case PacketType.UserList: _protocol.UserList(Serializer.DeserializeWithLengthPrefix(_ssl, PrefixStyle.Fixed32BigEndian)); From 17bcb400141242781211646b8a5876f1f89dca23 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Mon, 15 Jul 2019 23:27:58 +0200 Subject: [PATCH 08/22] (feature) #42 Ability to set audio frame size. --- MumbleSharp/Audio/AudioDecodingBuffer.cs | 6 ++++-- MumbleSharp/Audio/AudioEncodingBuffer.cs | 4 ++-- MumbleSharp/Audio/CodecSet.cs | 4 ++-- MumbleSharp/Audio/Codecs/Opus/OpusCodec.cs | 18 ++++++++++-------- MumbleSharp/Audio/Codecs/Opus/OpusEncoder.cs | 2 +- MumbleSharp/BasicMumbleProtocol.cs | 6 ++++-- MumbleSharp/Constants.cs | 1 + MumbleSharp/Model/User.cs | 6 +++--- 8 files changed, 27 insertions(+), 20 deletions(-) diff --git a/MumbleSharp/Audio/AudioDecodingBuffer.cs b/MumbleSharp/Audio/AudioDecodingBuffer.cs index c64297e..ebf4cc1 100644 --- a/MumbleSharp/Audio/AudioDecodingBuffer.cs +++ b/MumbleSharp/Audio/AudioDecodingBuffer.cs @@ -12,15 +12,17 @@ public class AudioDecodingBuffer : IWaveProvider { private readonly int _sampleRate; + private readonly float _frameSize; public WaveFormat WaveFormat { get; private set; } private int _decodedOffset; private int _decodedCount; private readonly byte[] _decodedBuffer; - public AudioDecodingBuffer(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + public AudioDecodingBuffer(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float frameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { WaveFormat = new WaveFormat(sampleRate, sampleBits, sampleChannels); _sampleRate = sampleRate; + _frameSize = frameSize; _decodedBuffer = new byte[sampleRate * (sampleBits / 8) * sampleChannels]; } @@ -140,7 +142,7 @@ private bool FillBuffer() // _codec.Decode(null); var d = _codec.Decode(packet.Value.Data); - _nextSequenceToDecode = packet.Value.Sequence + d.Length / (_sampleRate / 20); + _nextSequenceToDecode = packet.Value.Sequence + d.Length / (int)(_sampleRate / _frameSize); Array.Copy(d, 0, _decodedBuffer, _decodedOffset, d.Length); _decodedCount += d.Length; diff --git a/MumbleSharp/Audio/AudioEncodingBuffer.cs b/MumbleSharp/Audio/AudioEncodingBuffer.cs index a76c3d6..defce78 100644 --- a/MumbleSharp/Audio/AudioEncodingBuffer.cs +++ b/MumbleSharp/Audio/AudioEncodingBuffer.cs @@ -17,9 +17,9 @@ public class AudioEncodingBuffer private TargettedSpeech? _unencodedItem; - public AudioEncodingBuffer(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + public AudioEncodingBuffer(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float frameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { - _codecs = new CodecSet(sampleRate, sampleBits, sampleChannels); + _codecs = new CodecSet(sampleRate, sampleBits, sampleChannels, frameSize); } /// diff --git a/MumbleSharp/Audio/CodecSet.cs b/MumbleSharp/Audio/CodecSet.cs index 3b6097d..d3fa566 100644 --- a/MumbleSharp/Audio/CodecSet.cs +++ b/MumbleSharp/Audio/CodecSet.cs @@ -14,12 +14,12 @@ public class CodecSet private readonly Lazy _speex; private readonly Lazy _opus; - public CodecSet(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + public CodecSet(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte sampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float frameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { _alpha = new Lazy(); _beta = new Lazy(); _speex = new Lazy(); - _opus = new Lazy(() => new OpusCodec(sampleRate, sampleBits, sampleChannels)); + _opus = new Lazy(() => new OpusCodec(sampleRate, sampleBits, sampleChannels, frameSize)); } protected internal IVoiceCodec GetCodec(SpeechCodecs codec) diff --git a/MumbleSharp/Audio/Codecs/Opus/OpusCodec.cs b/MumbleSharp/Audio/Codecs/Opus/OpusCodec.cs index bb76e98..a90bfd6 100644 --- a/MumbleSharp/Audio/Codecs/Opus/OpusCodec.cs +++ b/MumbleSharp/Audio/Codecs/Opus/OpusCodec.cs @@ -6,22 +6,24 @@ namespace MumbleSharp.Audio.Codecs.Opus public class OpusCodec : IVoiceCodec { - readonly OpusDecoder _decoder; - readonly OpusEncoder _encoder; - readonly int _sampleRate; + private readonly OpusDecoder _decoder; + private readonly OpusEncoder _encoder; + private readonly int _sampleRate; + private readonly float _frameSize; - public OpusCodec(int SampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte SampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte Channels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + public OpusCodec(int sampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte sampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte channels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float frameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { - _sampleRate = SampleRate; - _decoder = new OpusDecoder(SampleRate, Channels) { EnableForwardErrorCorrection = true }; - _encoder = new OpusEncoder(SampleRate, Channels) { EnableForwardErrorCorrection = true }; + _sampleRate = sampleRate; + _frameSize = frameSize; + _decoder = new OpusDecoder(sampleRate, channels) { EnableForwardErrorCorrection = true }; + _encoder = new OpusEncoder(sampleRate, channels) { EnableForwardErrorCorrection = true }; } public byte[] Decode(byte[] encodedData) { if (encodedData == null) { - _decoder.Decode(null, 0, 0, new byte[_sampleRate / 20], 0); + _decoder.Decode(null, 0, 0, new byte[(int)(_sampleRate / _frameSize)], 0); return null; } diff --git a/MumbleSharp/Audio/Codecs/Opus/OpusEncoder.cs b/MumbleSharp/Audio/Codecs/Opus/OpusEncoder.cs index 7ca76d7..240cd59 100644 --- a/MumbleSharp/Audio/Codecs/Opus/OpusEncoder.cs +++ b/MumbleSharp/Audio/Codecs/Opus/OpusEncoder.cs @@ -69,7 +69,7 @@ public OpusEncoder(int srcSamplingRate, int srcChannelCount) throw new ArgumentOutOfRangeException("srcChannelCount"); IntPtr error; - var encoder = NativeMethods.opus_encoder_create(srcSamplingRate, srcChannelCount, 2048, out error); + var encoder = NativeMethods.opus_encoder_create(srcSamplingRate, srcChannelCount, (int)Application.Voip, out error); if ((NativeMethods.OpusErrors)error != NativeMethods.OpusErrors.Ok) { throw new Exception("Exception occured while creating encoder"); diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index f3283ca..653d290 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -61,11 +61,13 @@ public IEnumerable Channels private int _audioSampleRate; private byte _audioSampleBits; private byte _audioSampleChannels; - public BasicMumbleProtocol(int audioSampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte audioSampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte audioSampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + private float _audioFrameSize; + public BasicMumbleProtocol(int audioSampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte audioSampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte audioSampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float audioFrameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { _audioSampleRate = audioSampleRate; _audioSampleBits = audioSampleBits; _audioSampleChannels = audioSampleChannels; + _audioFrameSize = audioFrameSize; } /// @@ -373,7 +375,7 @@ public virtual void ServerSync(ServerSync serverSync) //Get the local user LocalUser = UserDictionary[serverSync.Session]; - _encodingBuffer = new AudioEncodingBuffer(_audioSampleRate, _audioSampleBits, _audioSampleChannels); + _encodingBuffer = new AudioEncodingBuffer(_audioSampleRate, _audioSampleBits, _audioSampleChannels, _audioFrameSize); _encodingThread.Start(); ReceivedServerSync = true; diff --git a/MumbleSharp/Constants.cs b/MumbleSharp/Constants.cs index a17dae5..61e0d16 100644 --- a/MumbleSharp/Constants.cs +++ b/MumbleSharp/Constants.cs @@ -6,5 +6,6 @@ public static class Constants public const int DEFAULT_AUDIO_SAMPLE_RATE = 48000; public const byte DEFAULT_AUDIO_SAMPLE_BITS = 16; public const byte DEFAULT_AUDIO_SAMPLE_CHANNELS = 1; + public const byte DEFAULT_AUDIO_FRAME_SIZE = 20; } } diff --git a/MumbleSharp/Model/User.cs b/MumbleSharp/Model/User.cs index 1734747..8fedfc0 100644 --- a/MumbleSharp/Model/User.cs +++ b/MumbleSharp/Model/User.cs @@ -39,10 +39,10 @@ public Channel Channel private readonly CodecSet _codecs; - public User(IMumbleProtocol owner, uint id, int audioSampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte audioSampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte audioSampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS) + public User(IMumbleProtocol owner, uint id, int audioSampleRate = Constants.DEFAULT_AUDIO_SAMPLE_RATE, byte audioSampleBits = Constants.DEFAULT_AUDIO_SAMPLE_BITS, byte audioSampleChannels = Constants.DEFAULT_AUDIO_SAMPLE_CHANNELS, float audioFrameSize = Constants.DEFAULT_AUDIO_FRAME_SIZE) { - _codecs = new CodecSet(audioSampleRate, audioSampleBits, audioSampleChannels); - _buffer = new AudioDecodingBuffer(audioSampleRate, audioSampleBits, audioSampleChannels); + _codecs = new CodecSet(audioSampleRate, audioSampleBits, audioSampleChannels, audioFrameSize); + _buffer = new AudioDecodingBuffer(audioSampleRate, audioSampleBits, audioSampleChannels, audioFrameSize); _owner = owner; Id = id; } From 06e2e814e2e5a5ccb883cc0d272228413dfcd9fe Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Tue, 16 Jul 2019 17:23:28 +0200 Subject: [PATCH 09/22] (feature) Support for mumble protocol permissions query. --- MumbleSharp/BasicMumbleProtocol.cs | 20 ++++- MumbleSharp/Model/Channel.cs | 1 + MumbleSharp/Model/Permissions.cs | 123 +++++++++++++++++++++++++++++ MumbleSharp/MumbleSharp.csproj | 7 ++ 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 MumbleSharp/Model/Permissions.cs diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 653d290..5e6ee80 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -315,6 +315,7 @@ public virtual void ContextAction(ContextAction contextAction) /// public void SendContextActionModify(ContextActionModify contextActionModify) { + Connection.SendControl(PacketType.ContextActionModify, contextActionModify); } #region permissions @@ -324,7 +325,21 @@ public void SendContextActionModify(ContextActionModify contextActionModify) /// public virtual void PermissionQuery(PermissionQuery permissionQuery) { - + if (permissionQuery.Flush) + { + foreach(var channel in ChannelDictionary.Values) + { + channel.Permissions = 0; // Permissions.DEFAULT_PERMISSIONS; + } + } + else + { + Channel channel; + if (!ChannelDictionary.TryGetValue(permissionQuery.ChannelId, out channel)) + throw new Exception($"Recieved a {nameof(PermissionQuery)} for an unknown channel ({permissionQuery.ChannelId})"); + + channel.Permissions = (Permission)permissionQuery.Permissions; + } } /// @@ -333,7 +348,7 @@ public virtual void PermissionQuery(PermissionQuery permissionQuery) /// public void SendPermissionQuery(PermissionQuery permissionQuery) { - + Connection.SendControl(PacketType.PermissionQuery, permissionQuery); } /// @@ -654,6 +669,7 @@ public void SendRequestUserStats(UserStats userStats) /// public void SendRequestBlob(RequestBlob requestBlob) { + Connection.SendControl(PacketType.RequestBlob, requestBlob); } /// diff --git a/MumbleSharp/Model/Channel.cs b/MumbleSharp/Model/Channel.cs index 05b54ea..d736ecc 100644 --- a/MumbleSharp/Model/Channel.cs +++ b/MumbleSharp/Model/Channel.cs @@ -20,6 +20,7 @@ public class Channel public int Position { get; set; } public uint Id { get; private set; } public uint Parent { get; private 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(); 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/MumbleSharp.csproj b/MumbleSharp/MumbleSharp.csproj index 1dd1e94..64c876a 100644 --- a/MumbleSharp/MumbleSharp.csproj +++ b/MumbleSharp/MumbleSharp.csproj @@ -43,9 +43,15 @@ + + ..\..\..\nw\Products\Development\windows\packages\NAudio.1.9.0\lib\net35\NAudio.dll + ..\Dependencies\NSpeex v1.1.1\lib\NSpeex.dll + + ..\..\..\nw\Products\Development\windows\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll + @@ -82,6 +88,7 @@ + From 22e8f22d8f18adea7a5eb1051787c418fe0fa7fc Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Tue, 16 Jul 2019 17:40:08 +0200 Subject: [PATCH 10/22] (fix) Channel create. --- MumbleSharp/BasicMumbleProtocol.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 5e6ee80..a7a356e 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -135,7 +135,8 @@ protected void SendChannelCreate(Channel channel) Position = channel.Position, Name = channel.Name, Description = channel.Description, - Temporary = channel.Temporary + 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) @@ -190,7 +191,7 @@ public virtual void ChannelState(ChannelState channelState) /// public void SendChannelState(ChannelState channelState) { - Connection.SendControl(PacketType.ChannelRemove, channelState); + Connection.SendControl(PacketType.ChannelState, channelState); } /// From 8b119a559226f446e98c674480a2e8c6dacd02fb Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Tue, 23 Jul 2019 15:35:53 +0200 Subject: [PATCH 11/22] Add SpeechTarget to Channel.SendVoice --- MumbleSharp/Model/Channel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MumbleSharp/Model/Channel.cs b/MumbleSharp/Model/Channel.cs index d736ecc..ea55995 100644 --- a/MumbleSharp/Model/Channel.cs +++ b/MumbleSharp/Model/Channel.cs @@ -79,11 +79,14 @@ 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) { + //TODO: remove debug console.writeline + Console.WriteLine($"{nameof(SendVoice)} {target.ToString()}"); + Owner.SendVoice( buffer, - target: whisper ? SpeechTarget.WhisperToChannel : SpeechTarget.Normal, + target: target, targetId: Id ); } From 40c458f44569b3d5640e7841c85eda59c559de1e Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 25 Jul 2019 07:44:36 +0200 Subject: [PATCH 12/22] (feature) send voice to speech targets --- MumbleSharp/Audio/AudioEncodingBuffer.cs | 26 ++++++++++-- MumbleSharp/BasicMumbleProtocol.cs | 54 ++++++++++++------------ MumbleSharp/Model/Channel.cs | 3 -- 3 files changed, 50 insertions(+), 33 deletions(-) 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 7450997..0fbb089 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 { @@ -421,45 +422,44 @@ private void EncodingThreadEntry() IsEncodingThreadRunning = true; while (IsEncodingThreadRunning) { - byte[] packet = null; try { - packet = _encodingBuffer.Encode(TransmissionCodec); - } - catch { } - - if (packet != null) - { - int maxSize = 480; + EncodedTargettedSpeech? encodedTargettedSpeech = _encodingBuffer.Encode(TransmissionCodec); - //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; + } } } + catch { } //beware! can take a lot of power, because infinite loop without sleep } diff --git a/MumbleSharp/Model/Channel.cs b/MumbleSharp/Model/Channel.cs index ea55995..86e079f 100644 --- a/MumbleSharp/Model/Channel.cs +++ b/MumbleSharp/Model/Channel.cs @@ -81,9 +81,6 @@ public void SendMessage(string message, bool recursive) public void SendVoice(ArraySegment buffer, SpeechTarget target = SpeechTarget.Normal) { - //TODO: remove debug console.writeline - Console.WriteLine($"{nameof(SendVoice)} {target.ToString()}"); - Owner.SendVoice( buffer, target: target, From ea156ed63b81cb2c968edde2d6efd8e88fc36fba Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Thu, 25 Jul 2019 07:45:12 +0200 Subject: [PATCH 13/22] (fix) mumble disallows being deaf without being muted. --- MumbleSharp/Model/User.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; } From f5ccd472e29520940bf6e2ff9465a44ffb1db6bd Mon Sep 17 00:00:00 2001 From: Meetsch <37514759+Meetsch@users.noreply.github.com> Date: Mon, 27 Jan 2020 12:47:58 +0100 Subject: [PATCH 14/22] MumbleGuiClient: (fix) avoid UI freezing --- MumbleGuiClient/Form1.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MumbleGuiClient/Form1.cs b/MumbleGuiClient/Form1.cs index 2582025..91bd0d6 100644 --- a/MumbleGuiClient/Form1.cs +++ b/MumbleGuiClient/Form1.cs @@ -252,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) From 6f790ede78a3159d5a3cdfae4327f1ef73daad93 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 20:16:29 +0100 Subject: [PATCH 15/22] (fix) #48 MumbleConnection.Close() could rise a NullReferenceException if the TCP or UDP socket failed to initialize. --- MumbleSharp/MumbleConnection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 126de75..2f28bee 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -88,8 +88,8 @@ public void Close() { State = ConnectionStates.Disconnecting; - _udp.Close(); - _tcp.Close(); + _udp?.Close(); + _tcp?.Close(); State = ConnectionStates.Disconnected; } From c666a06d8691e62909ea5a9b635288ebe1b01a6f Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 20:19:36 +0100 Subject: [PATCH 16/22] (feature) Catch and rethrow exceptions raised within threads. --- MumbleClient/Program.cs | 31 ++++++++++++++++++++------- MumbleSharp/BasicMumbleProtocol.cs | 34 ++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/MumbleClient/Program.cs b/MumbleClient/Program.cs index 435be10..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,14 +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 { - if (connection.Process()) - Thread.Yield(); - else - Thread.Sleep(1); + while (connection.State != ConnectionStates.Disconnected) + { + if (connection.Process()) + Thread.Yield(); + else + Thread.Sleep(1); + } + } catch (Exception ex) + { + exception = ex; } } } diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 0fbb089..7999c19 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -54,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; } @@ -87,14 +89,22 @@ 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; @@ -417,12 +427,13 @@ 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 { - try + while (IsEncodingThreadRunning) { EncodedTargettedSpeech? encodedTargettedSpeech = _encodingBuffer.Encode(TransmissionCodec); @@ -458,10 +469,15 @@ private void EncodingThreadEntry() currentOffcet += currentBlockSize; } } + else + { + Thread.Sleep(1); //avoids consuming a cpu core at 100% if there's nothing to encode... + } } - catch { } - - //beware! can take a lot of power, because infinite loop without sleep + } + catch (Exception ex) + { + exception = ex; } } From 7e16132e4c60ad282e384ba931f0036693609eb8 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 20:21:15 +0100 Subject: [PATCH 17/22] (upgrade) Upgrade to NAudio 1.10 --- MumbleClient/MumbleClient.csproj | 4 ++-- MumbleClient/packages.config | 2 +- MumbleGuiClient/MumbleGuiClient.csproj | 4 ++-- MumbleGuiClient/packages.config | 2 +- MumbleSharp/MumbleSharp.csproj | 4 ++-- MumbleSharp/packages.config | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) 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/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/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/MumbleSharp.csproj b/MumbleSharp/MumbleSharp.csproj index 749a662..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 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 From ec5cdd160adb7718a43b665e135be721a32a8628 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 20:24:37 +0100 Subject: [PATCH 18/22] (ignore) Updated MumbleSharp version number to 1.1 as a preparation to republish the NuGet. --- MumbleSharp/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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")] From 75eae74805698686119d4900f998a50f284b92c3 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 20:49:51 +0100 Subject: [PATCH 19/22] (fix) #39 Compatibility with Plumble by resetting the AudioDecodingBuffer's _nextSequenceToDecode when sequence == 0 to avoid skipping adding to encoded buffer if the emitting Mumble client decided to reset it's sequence number. --- MumbleSharp/Audio/AudioDecodingBuffer.cs | 3 +++ 1 file changed, 3 insertions(+) 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) From b0d4efa85d0b7c288a1effe286e01b5c96be886f Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 21:28:52 +0100 Subject: [PATCH 20/22] (fix) #23 Channel names are not being lost anymore when a ChannelState is received for whatever other property update. Also fixed the same issue in PermissionQuery and ServerSync. --- MumbleSharp/BasicMumbleProtocol.cs | 102 +++++++++++++++++++---------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 7999c19..4131976 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -182,15 +182,33 @@ protected virtual void ChannelLeft(Channel channel) /// 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; } ); @@ -257,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); @@ -347,18 +371,23 @@ public virtual void PermissionQuery(PermissionQuery permissionQuery) { if (permissionQuery.Flush) { - foreach(var channel in ChannelDictionary.Values) + foreach (var channel in ChannelDictionary.Values) { channel.Permissions = 0; // Permissions.DEFAULT_PERMISSIONS; } } - else + else if (permissionQuery.ShouldSerializeChannelId()) { Channel channel; if (!ChannelDictionary.TryGetValue(permissionQuery.ChannelId, out channel)) - throw new Exception($"Recieved a {nameof(PermissionQuery)} for an unknown channel ({permissionQuery.ChannelId})"); + throw new InvalidOperationException($"{nameof(PermissionQuery)} provided an unknown {permissionQuery.ChannelId}."); - channel.Permissions = (Permission)permissionQuery.Permissions; + if (permissionQuery.ShouldSerializePermissions()) + channel.Permissions = (Permission)permissionQuery.Permissions; + } + else + { + throw new InvalidOperationException($"{nameof(PermissionQuery)} must provide either {nameof(permissionQuery.Flush)} or {nameof(permissionQuery.ChannelId)}."); } } @@ -407,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(); @@ -572,7 +606,7 @@ public void SendPing(Ping ping) 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) From 282b03cf00b6fc4da906729b8fe034dc0b0fed0b Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 21:28:52 +0100 Subject: [PATCH 21/22] (fix) #23 Channel names are not being lost anymore when a ChannelState is received for whatever other property update. Also fixed the same issue in PermissionQuery and ServerSync. --- MumbleSharp/BasicMumbleProtocol.cs | 102 +++++++++++++++++++---------- MumbleSharp/Model/Channel.cs | 2 +- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/MumbleSharp/BasicMumbleProtocol.cs b/MumbleSharp/BasicMumbleProtocol.cs index 7999c19..4131976 100644 --- a/MumbleSharp/BasicMumbleProtocol.cs +++ b/MumbleSharp/BasicMumbleProtocol.cs @@ -182,15 +182,33 @@ protected virtual void ChannelLeft(Channel channel) /// 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; } ); @@ -257,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); @@ -347,18 +371,23 @@ public virtual void PermissionQuery(PermissionQuery permissionQuery) { if (permissionQuery.Flush) { - foreach(var channel in ChannelDictionary.Values) + foreach (var channel in ChannelDictionary.Values) { channel.Permissions = 0; // Permissions.DEFAULT_PERMISSIONS; } } - else + else if (permissionQuery.ShouldSerializeChannelId()) { Channel channel; if (!ChannelDictionary.TryGetValue(permissionQuery.ChannelId, out channel)) - throw new Exception($"Recieved a {nameof(PermissionQuery)} for an unknown channel ({permissionQuery.ChannelId})"); + throw new InvalidOperationException($"{nameof(PermissionQuery)} provided an unknown {permissionQuery.ChannelId}."); - channel.Permissions = (Permission)permissionQuery.Permissions; + if (permissionQuery.ShouldSerializePermissions()) + channel.Permissions = (Permission)permissionQuery.Permissions; + } + else + { + throw new InvalidOperationException($"{nameof(PermissionQuery)} must provide either {nameof(permissionQuery.Flush)} or {nameof(permissionQuery.ChannelId)}."); } } @@ -407,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(); @@ -572,7 +606,7 @@ public void SendPing(Ping ping) 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) diff --git a/MumbleSharp/Model/Channel.cs b/MumbleSharp/Model/Channel.cs index 86e079f..6056f59 100644 --- a/MumbleSharp/Model/Channel.cs +++ b/MumbleSharp/Model/Channel.cs @@ -19,7 +19,7 @@ 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 From 6b61b63dc39f50592897242c4f9ba3b6a7875e01 Mon Sep 17 00:00:00 2001 From: Michel Bagnol Date: Fri, 21 Feb 2020 21:44:16 +0100 Subject: [PATCH 22/22] (ignore) Changed a warning comment because #17 is not a bug anymore since the mumble packet is well formed within BasicMumbleProtocol's EncodingThread. --- MumbleSharp/MumbleConnection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MumbleSharp/MumbleConnection.cs b/MumbleSharp/MumbleConnection.cs index 2f28bee..c9de4de 100644 --- a/MumbleSharp/MumbleConnection.cs +++ b/MumbleSharp/MumbleConnection.cs @@ -126,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); }