From b83295566491e8b9455ff01d0a80e54c835be1a1 Mon Sep 17 00:00:00 2001 From: sancheolz Date: Thu, 21 Mar 2024 12:53:03 +0300 Subject: [PATCH 01/19] Refactoring --- CoreRemoting/CallContext.cs | 2 +- .../ConfigSection/ConfigSectionExtensionMethods.cs | 2 +- CoreRemoting/ClassicRemotingApi/RemotingConfiguration.cs | 2 +- CoreRemoting/RemotingSession.cs | 2 +- CoreRemoting/RpcMessaging/MessageEncryptionManager.cs | 4 ++-- CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs | 8 ++++---- CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CoreRemoting/CallContext.cs b/CoreRemoting/CallContext.cs index ee3f3d8..7e85b99 100644 --- a/CoreRemoting/CallContext.cs +++ b/CoreRemoting/CallContext.cs @@ -43,7 +43,7 @@ public static CallContextEntry[] GetSnapshot() var entry = stateSnaphsot[i]; result[i] = - new CallContextEntry() + new CallContextEntry { Name = entry.Key, Value = entry.Value.Value diff --git a/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs b/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs index 326fb89..6ca2d9d 100644 --- a/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs +++ b/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs @@ -80,7 +80,7 @@ public static ClientConfig ToClientConfig( if (configElement == null) throw new ArgumentNullException(nameof(configElement)); - var clientConfig = new ClientConfig() + var clientConfig = new ClientConfig { Channel = CreateClientChannelFromConfigName(configElement.Channel), Serializer = CreateSerializerAdapterFromConfigName(configElement.Serializer), diff --git a/CoreRemoting/ClassicRemotingApi/RemotingConfiguration.cs b/CoreRemoting/ClassicRemotingApi/RemotingConfiguration.cs index 67f999a..2e5f642 100644 --- a/CoreRemoting/ClassicRemotingApi/RemotingConfiguration.cs +++ b/CoreRemoting/ClassicRemotingApi/RemotingConfiguration.cs @@ -167,7 +167,7 @@ public static void Configure(string fileName = "", Credential[] credentials = nu string.IsNullOrWhiteSpace(fileName) ? ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None) : ConfigurationManager.OpenMappedExeConfiguration( - fileMap: new ExeConfigurationFileMap() {ExeConfigFilename = fileName}, + fileMap: new ExeConfigurationFileMap {ExeConfigFilename = fileName}, userLevel: ConfigurationUserLevel.None); var configSection = (CoreRemotingConfigSection) diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index d4d4d3b..c355f88 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -90,7 +90,7 @@ internal RemotingSession(int keySize, byte[] clientPublicKey, IRemotingServer se var rawContent = _server.Serializer.Serialize(encryptedSessionId); var signedMessageData = - new SignedMessageData() + new SignedMessageData { MessageRawData = rawContent, Signature = diff --git a/CoreRemoting/RpcMessaging/MessageEncryptionManager.cs b/CoreRemoting/RpcMessaging/MessageEncryptionManager.cs index 35cde1b..b32d992 100644 --- a/CoreRemoting/RpcMessaging/MessageEncryptionManager.cs +++ b/CoreRemoting/RpcMessaging/MessageEncryptionManager.cs @@ -44,7 +44,7 @@ public WireMessage CreateWireMessage( if (keyPair != null && sharedSecret != null) { var signedMessageData = - new SignedMessageData() + new SignedMessageData { MessageRawData = serializedMessage, Signature = @@ -70,7 +70,7 @@ public WireMessage CreateWireMessage( iv: iv); return - new WireMessage() + new WireMessage { MessageType = messageType, Data = messageContent, diff --git a/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs b/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs index 98d3ff7..74f5d35 100644 --- a/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs +++ b/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs @@ -39,7 +39,7 @@ public MethodCallMessage BuildMethodCallMessage( .Select(arg => arg.FullName + "," + arg.Assembly.GetName().Name) .ToArray(); - var message = new MethodCallMessage() + var message = new MethodCallMessage { ServiceName = remoteServiceName, MethodName = targetMethod.Name, @@ -98,7 +98,7 @@ public IEnumerable BuildMethodParameterInfos( : arg; yield return - new MethodCallParameterMessage() + new MethodCallParameterMessage { IsOut = parameterInfo.IsOut, ParameterName = parameterInfo.Name, @@ -132,7 +132,7 @@ public MethodCallResultMessage BuildMethodCallResultMessage( var parameterInfos = method.GetParameters(); - var message = new MethodCallResultMessage() + var message = new MethodCallResultMessage { IsReturnValueNull = isReturnValueNull, ReturnValue = @@ -163,7 +163,7 @@ public MethodCallResultMessage BuildMethodCallResultMessage( : arg); outParameters.Add( - new MethodCallOutParameterMessage() + new MethodCallOutParameterMessage { ParameterName = parameterInfo.Name, OutValue = serializedArgValue, diff --git a/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs b/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs index ca5d5dc..636e2b0 100644 --- a/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs +++ b/CoreRemoting/Serialization/Bson/BsonSerializerAdapter.cs @@ -18,7 +18,7 @@ public class BsonSerializerAdapter : ISerializerAdapter /// Optional configuration settings public BsonSerializerAdapter(BsonSerializerConfig config = null) { - var settings = new JsonSerializerSettings() + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full, From 5ee3266dbc98b726282bff6c5b089de0d9cc75f1 Mon Sep 17 00:00:00 2001 From: sancheolz Date: Fri, 22 Mar 2024 11:15:12 +0300 Subject: [PATCH 02/19] RemotingClient was stuck on opening a TCP connection Sometimes the connection event from WatsonTCP arrives later than the first message. This caused the handshake to hang when SendTimeout = 0 --- CoreRemoting/Channels/IRawMessageTransport.cs | 4 +- CoreRemoting/Channels/Tcp/TcpClientChannel.cs | 26 +- CoreRemoting/Channels/Tcp/TcpConnection.cs | 4 +- CoreRemoting/Channels/Tcp/TcpServerChannel.cs | 11 +- .../Websocket/RpcWebsocketSharpBehavior.cs | 3 +- .../Websocket/WebsocketClientChannel.cs | 3 +- CoreRemoting/RemotingClient.cs | 233 ++++++++++++------ 7 files changed, 192 insertions(+), 92 deletions(-) diff --git a/CoreRemoting/Channels/IRawMessageTransport.cs b/CoreRemoting/Channels/IRawMessageTransport.cs index 78dcab3..be07d3c 100644 --- a/CoreRemoting/Channels/IRawMessageTransport.cs +++ b/CoreRemoting/Channels/IRawMessageTransport.cs @@ -21,11 +21,11 @@ public interface IRawMessageTransport /// Gets or sets the last exception. /// NetworkException LastException { get; set; } - + /// /// Sends a message to the server. /// /// Raw message data - void SendMessage(byte[] rawMessage); + bool SendMessage(byte[] rawMessage); } } \ No newline at end of file diff --git a/CoreRemoting/Channels/Tcp/TcpClientChannel.cs b/CoreRemoting/Channels/Tcp/TcpClientChannel.cs index f26fc19..db7b7e2 100644 --- a/CoreRemoting/Channels/Tcp/TcpClientChannel.cs +++ b/CoreRemoting/Channels/Tcp/TcpClientChannel.cs @@ -59,10 +59,7 @@ public void Connect() _tcpClient.Events.MessageReceived += OnMessage; _tcpClient.Events.ServerDisconnected += OnDisconnected; _tcpClient.Connect(); - - _tcpClient.Send(new byte[] { 0x0 }, _handshakeMetadata); } - private void OnDisconnected(object o, DisconnectionEventArgs disconnectionEventArgs) { Disconnected?.Invoke(); @@ -87,7 +84,15 @@ private void OnError(object sender, ExceptionEventArgs e) /// Event arguments containing the message content private void OnMessage(object sender, MessageReceivedEventArgs e) { - ReceiveMessage?.Invoke(e.Data); + if (e.Metadata != null && e.Metadata.ContainsKey("ServerAcceptConnection")) + { + if (!_tcpClient.Send(new byte[1] { 0x0 }, _handshakeMetadata) && !_tcpClient.Connected) + _tcpClient = null; + } + else + { + ReceiveMessage?.Invoke(e.Data); + } } /// @@ -131,9 +136,18 @@ public void Disconnect() /// Sends a message to the server. /// /// Raw message data - public void SendMessage(byte[] rawMessage) + public bool SendMessage(byte[] rawMessage) { - _tcpClient.Send(rawMessage); + if (_tcpClient != null) + { + if (_tcpClient.Send(rawMessage)) + return true; + if (!_tcpClient.Connected) + _tcpClient = null; + return false; + } + else + throw new NetworkException("Channel disconnected"); } /// diff --git a/CoreRemoting/Channels/Tcp/TcpConnection.cs b/CoreRemoting/Channels/Tcp/TcpConnection.cs index af93cc9..b10b914 100644 --- a/CoreRemoting/Channels/Tcp/TcpConnection.cs +++ b/CoreRemoting/Channels/Tcp/TcpConnection.cs @@ -104,8 +104,8 @@ private void BeforeDisposeSession() /// Sends a message to the server. /// /// Raw message data - public void SendMessage(byte[] rawMessage) + public bool SendMessage(byte[] rawMessage) { - _tcpServer.Send(_clientMetadata.Guid, rawMessage); + return _tcpServer.Send(_clientMetadata.Guid, rawMessage); } } \ No newline at end of file diff --git a/CoreRemoting/Channels/Tcp/TcpServerChannel.cs b/CoreRemoting/Channels/Tcp/TcpServerChannel.cs index 1957c8d..eb67f42 100644 --- a/CoreRemoting/Channels/Tcp/TcpServerChannel.cs +++ b/CoreRemoting/Channels/Tcp/TcpServerChannel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using WatsonTcp; namespace CoreRemoting.Channels.Tcp; @@ -38,7 +39,15 @@ public void Init(IRemotingServer server) private void OnClientConnected(object sender, ConnectionEventArgs e) { - _connections.TryAdd(e.Client.Guid, new TcpConnection(e.Client, _tcpServer, _remotingServer)); + if(_connections.TryAdd(e.Client.Guid, new TcpConnection(e.Client, _tcpServer, _remotingServer))) + { + var metadata = new Dictionary + { + { "ServerAcceptConnection", true } + }; + + _tcpServer.Send(e.Client.Guid, new byte[]{ 0x02 }, metadata); + } } private void OnClientDisconnected(object sender, DisconnectionEventArgs e) diff --git a/CoreRemoting/Channels/Websocket/RpcWebsocketSharpBehavior.cs b/CoreRemoting/Channels/Websocket/RpcWebsocketSharpBehavior.cs index 0669fc3..ce4cb7a 100644 --- a/CoreRemoting/Channels/Websocket/RpcWebsocketSharpBehavior.cs +++ b/CoreRemoting/Channels/Websocket/RpcWebsocketSharpBehavior.cs @@ -35,9 +35,10 @@ public RpcWebsocketSharpBehavior(IRemotingServer server) /// Sends a message over the websocket. /// /// Raw data of the message - public void SendMessage(byte[] rawMessage) + public bool SendMessage(byte[] rawMessage) { Send(rawMessage); + return true; } /// diff --git a/CoreRemoting/Channels/Websocket/WebsocketClientChannel.cs b/CoreRemoting/Channels/Websocket/WebsocketClientChannel.cs index 0e8f210..3ddc2bd 100644 --- a/CoreRemoting/Channels/Websocket/WebsocketClientChannel.cs +++ b/CoreRemoting/Channels/Websocket/WebsocketClientChannel.cs @@ -169,9 +169,10 @@ private void OnMessage(object sender, MessageEventArgs e) /// Sends a message to the server. /// /// Raw message data - public void SendMessage(byte[] rawMessage) + public bool SendMessage(byte[] rawMessage) { _webSocket.Send(rawMessage); + return true; } /// diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 4529a94..9fa0712 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -34,7 +34,8 @@ public sealed class RemotingClient : IRemotingClient private readonly ClientDelegateRegistry _delegateRegistry; private readonly CancellationTokenSource _cancellationTokenSource; private readonly ClientConfig _config; - private readonly ConcurrentDictionary _activeCalls; + private Dictionary _activeCalls; + private readonly object _syncObject; private Guid _sessionId; private ManualResetEventSlim _handshakeCompletedWaitHandle; private ManualResetEventSlim _authenticationCompletedWaitHandle; @@ -62,7 +63,8 @@ private RemotingClient() { MethodCallMessageBuilder = new MethodCallMessageBuilder(); MessageEncryptionManager = new MessageEncryptionManager(); - _activeCalls = new ConcurrentDictionary(); + _activeCalls = null; + _syncObject = new object(); _cancellationTokenSource = new CancellationTokenSource(); _delegateRegistry = new ClientDelegateRegistry(); _handshakeCompletedWaitHandle = new ManualResetEventSlim(initialState: false); @@ -118,7 +120,19 @@ public RemotingClient(ClientConfig config) : this() private void OnDisconnected() { - foreach (var activeCall in _activeCalls) + Dictionary activeCalls = null; + lock (_syncObject) + { + if (_activeCalls == null) + return; + + activeCalls = _activeCalls; + _activeCalls = null; + } + + _goodbyeCompletedWaitHandle.Set(); + + foreach (var activeCall in activeCalls) { activeCall.Value.Error = true; activeCall.Value.RemoteException = new RemoteInvocationException("Server Disconnected"); @@ -183,7 +197,17 @@ private void OnDisconnected() /// /// Gets whether this CoreRemoting client instance has a session or not. /// - public bool HasSession => _sessionId != Guid.Empty; + public bool HasSession + { + get + { + lock(_syncObject) + { + return _sessionId != Guid.Empty; + }; + } + } + /// /// Gets the authenticated identity. May be null if authentication failed or if authentication is not configured. @@ -204,6 +228,10 @@ public void Connect() { if (_channel == null) throw new RemotingException("No client channel configured."); + + _goodbyeCompletedWaitHandle.Reset(); + lock(_syncObject) + _activeCalls = new Dictionary(); _channel.Connect(); @@ -229,51 +257,63 @@ public void Connect() /// When set to true, no goodbye message is sent to the server public void Disconnect(bool quiet = false) { - if (_channel != null && HasSession) + if (_channel == null) + return; + + Guid sessionId; + lock (_syncObject) { - if (_keepSessionAliveTimer != null) - { - _keepSessionAliveTimer.Stop(); - _keepSessionAliveTimer.Dispose(); - _keepSessionAliveTimer = null; - } + if (_sessionId == Guid.Empty) + return; + sessionId = _sessionId; + _sessionId = Guid.Empty; + } + + if (_keepSessionAliveTimer != null) + { + _keepSessionAliveTimer.Stop(); + _keepSessionAliveTimer.Dispose(); + _keepSessionAliveTimer = null; + } - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + byte[] sharedSecret = + MessageEncryption + ? sessionId.ToByteArray() + : null; - if (!quiet) - { - var goodbyeMessage = - new GoodbyeMessage() - { - SessionId = _sessionId - }; + if (!quiet) + { + var goodbyeMessage = + new GoodbyeMessage + { + SessionId = sessionId + }; - var wireMessage = - MessageEncryptionManager.CreateWireMessage( - messageType: "goodbye", - serializer: Serializer, - serializedMessage: Serializer.Serialize(goodbyeMessage), - keyPair: _keyPair, - sharedSecret: sharedSecret); + var wireMessage = + MessageEncryptionManager.CreateWireMessage( + messageType: "goodbye", + serializer: Serializer, + serializedMessage: Serializer.Serialize(goodbyeMessage), + keyPair: _keyPair, + sharedSecret: sharedSecret); - byte[] rawData = Serializer.Serialize(wireMessage); + byte[] rawData = Serializer.Serialize(wireMessage); - _goodbyeCompletedWaitHandle.Reset(); + //_goodbyeCompletedWaitHandle.Reset(); - _channel.RawMessageTransport.SendMessage(rawData); - + if(_channel.RawMessageTransport.SendMessage(rawData)) _goodbyeCompletedWaitHandle.Wait(10000); - } } - _channel?.Disconnect(); + lock (_syncObject) + { + _channel?.Disconnect(); + } + + OnDisconnected(); _handshakeCompletedWaitHandle?.Reset(); _authenticationCompletedWaitHandle?.Reset(); Identity = null; - _sessionId = Guid.Empty; AfterDisconnect?.Invoke(); } @@ -308,14 +348,32 @@ private void KeepSessionAliveTimerOnElapsed(object sender, ElapsedEventArgs e) if (_rawMessageTransport == null) return; - + if (!HasSession) + { + OnDisconnected(); return; + } // Send empty message to keep session alive _rawMessageTransport.SendMessage(new byte[0]); } + private byte[] SharedSecret() + { + if (MessageEncryption) + { + lock (_syncObject) + { + return _sessionId.ToByteArray(); + } + } + else + { + return null; + } + } + #endregion #region Authentication @@ -331,14 +389,11 @@ private void Authenticate() if (_authenticationCompletedWaitHandle.IsSet) return; - - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + + byte[] sharedSecret = SharedSecret(); var authRequestMessage = - new AuthenticationRequestMessage() + new AuthenticationRequestMessage { Credentials = _config.Credentials }; @@ -429,17 +484,23 @@ private void ProcessCompleteHandshakeMessage(WireMessage message) rawData: signedMessageData.MessageRawData, signature: signedMessageData.Signature)) throw new SecurityException("Verification of message signature failed."); - - _sessionId = - new Guid( - RsaKeyExchange.DecryptSecret( - keySize: _config.KeySize, - // ReSharper disable once PossibleNullReferenceException - receiversPrivateKeyBlob: _keyPair.PrivateKey, - encryptedSecret: encryptedSecret)); + + lock (_syncObject) + { + _sessionId = + new Guid( + RsaKeyExchange.DecryptSecret( + keySize: _config.KeySize, + // ReSharper disable once PossibleNullReferenceException + receiversPrivateKeyBlob: _keyPair.PrivateKey, + encryptedSecret: encryptedSecret)); + } } else - _sessionId = new Guid(message.Data); + { + lock (_syncObject) + _sessionId = new Guid(message.Data); + } _handshakeCompletedWaitHandle.Set(); } @@ -450,10 +511,7 @@ private void ProcessCompleteHandshakeMessage(WireMessage message) /// Deserialized WireMessage that contains a AuthenticationResponseMessage private void ProcessAuthenticationResponseMessage(WireMessage message) { - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + byte[] sharedSecret = SharedSecret(); var authResponseMessage = Serializer @@ -478,10 +536,7 @@ private void ProcessAuthenticationResponseMessage(WireMessage message) /// Deserialized WireMessage that contains a RemoteDelegateInvocationMessage private void ProcessRemoteDelegateInvocationMessage(WireMessage message) { - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + byte[] sharedSecret = SharedSecret(); var delegateInvocationMessage = Serializer @@ -507,19 +562,24 @@ private void ProcessRemoteDelegateInvocationMessage(WireMessage message) /// Thrown, when the received result is of a unknown call private void ProcessRpcResultMessage(WireMessage message) { - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + byte[] sharedSecret = SharedSecret(); Guid unqiueCallKey = message.UniqueCallKey == null ? Guid.Empty : new Guid(message.UniqueCallKey); + + ClientRpcContext clientRpcContext; - if (!_activeCalls.TryRemove(unqiueCallKey, out ClientRpcContext clientRpcContext)) - throw new KeyNotFoundException("Received a result for a unknown call."); - + lock (_syncObject) + { + if (_activeCalls == null) + return; + + if (!_activeCalls.Remove(unqiueCallKey, out clientRpcContext)) + throw new KeyNotFoundException("Received a result for a unknown call."); + } + clientRpcContext.Error = message.Error; if (message.Error) @@ -577,16 +637,25 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM var sendTask = Task.Run(() => { - byte[] sharedSecret = - MessageEncryption - ? _sessionId.ToByteArray() - : null; + byte[] sharedSecret = SharedSecret(); + + lock (_syncObject) + { + if (_activeCalls == null) + throw new RemoteInvocationException("ServerDisconnected"); + } var clientRpcContext = new ClientRpcContext(); - - if (!_activeCalls.TryAdd(clientRpcContext.UniqueCallKey, clientRpcContext)) - throw new ApplicationException("Duplicate unique call key."); + lock (_syncObject) + { + if (!_activeCalls.TryAdd(clientRpcContext.UniqueCallKey, clientRpcContext)) + { + clientRpcContext.Dispose(); + throw new ApplicationException("Duplicate unique call key."); + } + } + var wireMessage = MessageEncryptionManager.CreateWireMessage( messageType: "rpc", @@ -603,7 +672,10 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM _rawMessageTransport.SendMessage(rawData); if (_rawMessageTransport.LastException != null) + { + clientRpcContext.Dispose(); throw _rawMessageTransport.LastException; + } if (oneWay || clientRpcContext.ResultMessage != null) return clientRpcContext; @@ -615,7 +687,7 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM return clientRpcContext; }); - + return sendTask; } @@ -718,10 +790,13 @@ public void Dispose() _rawMessageTransport = null; } - if (_channel != null) - { - _channel.Dispose(); - _channel = null; + lock (_syncObject) + { + if (_channel != null) + { + _channel.Dispose(); + _channel = null; + } } if (_handshakeCompletedWaitHandle != null) From fedfd5d094cd154584f933cb8bea56012eda655c Mon Sep 17 00:00:00 2001 From: sancheolz Date: Fri, 22 Mar 2024 11:15:56 +0300 Subject: [PATCH 03/19] Server-side session closure sometimes occurred after the current RPC was completed, and sometimes before --- CoreRemoting/RemotingClient.cs | 47 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 9fa0712..fa6258f 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -434,32 +434,29 @@ private void Authenticate() /// Raw message data private void OnMessage(byte[] rawMessage) { - Task.Run(() => - { - var message = Serializer.Deserialize(rawMessage); + var message = Serializer.Deserialize(rawMessage); - switch (message.MessageType.ToLower()) - { - case "complete_handshake": - ProcessCompleteHandshakeMessage(message); - break; - case "auth_response": - ProcessAuthenticationResponseMessage(message); - break; - case "rpc_result": - ProcessRpcResultMessage(message); - break; - case "invoke": - ProcessRemoteDelegateInvocationMessage(message); - break; - case "goodbye": - _goodbyeCompletedWaitHandle.Set(); - break; - case "session_closed": - Disconnect(quiet: true); - break; - } - }); + switch (message.MessageType.ToLower()) + { + case "complete_handshake": + ProcessCompleteHandshakeMessage(message); + break; + case "auth_response": + ProcessAuthenticationResponseMessage(message); + break; + case "rpc_result": + ProcessRpcResultMessage(message); + break; + case "invoke": + ProcessRemoteDelegateInvocationMessage(message); + break; + case "goodbye": + _goodbyeCompletedWaitHandle.Set(); + break; + case "session_closed": + Disconnect(quiet: true); + break; + } } /// From 44d7b59f0f7d1d0a312bf6204582535237561ee1 Mon Sep 17 00:00:00 2001 From: sancheolz Date: Fri, 22 Mar 2024 11:16:13 +0300 Subject: [PATCH 04/19] When opening and closing the connection, the _cancellationTokenSource leaked --- CoreRemoting/RemotingClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index fa6258f..ea3570b 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -779,6 +779,7 @@ public void Dispose() Disconnect(); _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); _delegateRegistry.Clear(); if (_rawMessageTransport != null) From 682deb8e3ab208c703a3cc1b2ecc5926d60767b2 Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Tue, 26 Mar 2024 12:30:06 +0200 Subject: [PATCH 05/19] Don't serialize read-only properties in Envelope --- CoreRemoting/Serialization/Bson/Envelope.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CoreRemoting/Serialization/Bson/Envelope.cs b/CoreRemoting/Serialization/Bson/Envelope.cs index d4266d2..6c7383a 100644 --- a/CoreRemoting/Serialization/Bson/Envelope.cs +++ b/CoreRemoting/Serialization/Bson/Envelope.cs @@ -36,11 +36,13 @@ public Envelope(object value) /// /// Gets the type of the wrapped value. /// + [JsonIgnore] public Type Type => _type; /// /// Gets the wrapped value. /// + [JsonIgnore] public object Value { get From 6ec30d00e5f6c7c3ab9f3926df031ccc31e04746 Mon Sep 17 00:00:00 2001 From: rainbird Date: Fri, 29 Mar 2024 13:47:37 +0100 Subject: [PATCH 06/19] Fixed bug after merge --- CoreRemoting/RemotingClient.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index ea3570b..97457d6 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -573,8 +573,10 @@ private void ProcessRpcResultMessage(WireMessage message) if (_activeCalls == null) return; - if (!_activeCalls.Remove(unqiueCallKey, out clientRpcContext)) + if (!_activeCalls.ContainsKey(unqiueCallKey)) throw new KeyNotFoundException("Received a result for a unknown call."); + + clientRpcContext = _activeCalls[unqiueCallKey]; } clientRpcContext.Error = message.Error; @@ -646,11 +648,15 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM lock (_syncObject) { - if (!_activeCalls.TryAdd(clientRpcContext.UniqueCallKey, clientRpcContext)) + if (_activeCalls.ContainsKey(clientRpcContext.UniqueCallKey)) { clientRpcContext.Dispose(); throw new ApplicationException("Duplicate unique call key."); } + else + { + _activeCalls.Add(clientRpcContext.UniqueCallKey, clientRpcContext); + } } var wireMessage = From 95faa0ad303ac2e4442a4ef7d1422078715e63aa Mon Sep 17 00:00:00 2001 From: rainbird Date: Mon, 1 Apr 2024 13:21:43 +0200 Subject: [PATCH 07/19] Fixed typos and redundant initializers --- .../ConfigSection/ConfigSectionExtensionMethods.cs | 13 ++++++++++--- CoreRemoting/ClientConfig.cs | 2 +- CoreRemoting/RemotingClient.cs | 4 ++-- .../Serialization/CrossFrameworkSerialization.cs | 8 ++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs b/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs index 6ca2d9d..22dce28 100644 --- a/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs +++ b/CoreRemoting/ClassicRemotingApi/ConfigSection/ConfigSectionExtensionMethods.cs @@ -3,6 +3,7 @@ using System.Reflection; using CoreRemoting.Authentication; using CoreRemoting.Channels; +using CoreRemoting.Channels.Tcp; using CoreRemoting.Channels.Websocket; using CoreRemoting.Serialization; using CoreRemoting.Serialization.Binary; @@ -127,10 +128,13 @@ private static IServerChannel CreateServerChannelFromConfigName(string channelTy { var websocketServerChannelShortcuts = new[] {"ws", "websocket"}; - + if (websocketServerChannelShortcuts.Contains(channelTypeName.ToLower())) return new WebsocketServerChannel(); + if (channelTypeName == "tcp") + return new TcpServerChannel(); + var channelType = GetTypeFromConfigString(channelTypeName); return (IServerChannel) Activator.CreateInstance(channelType); @@ -148,6 +152,9 @@ private static IClientChannel CreateClientChannelFromConfigName(string channelTy if (websocketServerChannelShortcuts.Contains(channelTypeName.ToLower())) return new WebsocketClientChannel(); + + if (channelTypeName == "tcp") + return new TcpClientChannel(); var channelType = GetTypeFromConfigString(channelTypeName); @@ -169,7 +176,7 @@ private static ISerializerAdapter CreateSerializerAdapterFromConfigName(string s "binaryserializer" }; - var bsonSerializerShortcusts = + var bsonSerializerShortcuts = new[] { "bson", @@ -180,7 +187,7 @@ private static ISerializerAdapter CreateSerializerAdapterFromConfigName(string s if (binarySerializerShortcuts.Contains(serializerName.ToLower())) return new BinarySerializerAdapter(); - if (bsonSerializerShortcusts.Contains(serializerName.ToLower())) + if (bsonSerializerShortcuts.Contains(serializerName.ToLower())) return new BsonSerializerAdapter(); var serializerAdapterType = GetTypeFromConfigString(serializerName); diff --git a/CoreRemoting/ClientConfig.cs b/CoreRemoting/ClientConfig.cs index d8518d9..36d7e19 100644 --- a/CoreRemoting/ClientConfig.cs +++ b/CoreRemoting/ClientConfig.cs @@ -37,7 +37,7 @@ public ClientConfig() /// /// Gets or sets the invocation timeout in seconds (0 means infinite). /// - public int InvocationTimeout { get; set; } = 0; + public int InvocationTimeout { get; set; } /// /// Gets or sets the send timeout in seconds (0 means infinite). diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 97457d6..3679ccc 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -120,7 +120,7 @@ public RemotingClient(ClientConfig config) : this() private void OnDisconnected() { - Dictionary activeCalls = null; + Dictionary activeCalls; lock (_syncObject) { if (_activeCalls == null) @@ -204,7 +204,7 @@ public bool HasSession lock(_syncObject) { return _sessionId != Guid.Empty; - }; + } } } diff --git a/CoreRemoting/Serialization/CrossFrameworkSerialization.cs b/CoreRemoting/Serialization/CrossFrameworkSerialization.cs index 0d9c2f4..162a9e4 100644 --- a/CoreRemoting/Serialization/CrossFrameworkSerialization.cs +++ b/CoreRemoting/Serialization/CrossFrameworkSerialization.cs @@ -12,8 +12,8 @@ public static class CrossFrameworkSerialization /// Redirects all loading attempts from a specified assembly name to another assembly name. /// /// Name of the assembly that should be redirected - /// Name of the assembly that should be used as replacement - public static void RedirectAssembly(string assemblyShortName, string replacmentAssemblyShortName) + /// Name of the assembly that should be used as replacement + public static void RedirectAssembly(string assemblyShortName, string replacementAssemblyShortName) { Assembly HandleAssemblyResolve(object _, ResolveEventArgs args) { @@ -23,8 +23,8 @@ Assembly HandleAssemblyResolve(object _, ResolveEventArgs args) { try { - var replacmentAssembly = Assembly.Load(replacmentAssemblyShortName); - return replacmentAssembly; + var replacementAssembly = Assembly.Load(replacementAssemblyShortName); + return replacementAssembly; } catch (Exception) { From aa6f6527f8043684767874af0f4d2e24e3e7efcb Mon Sep 17 00:00:00 2001 From: rainbird Date: Thu, 4 Apr 2024 21:27:32 +0200 Subject: [PATCH 08/19] Fixed issue #59: BsonSerializer deserializes duplicate list entries --- CoreRemoting/RemotingClient.cs | 2 ++ CoreRemoting/Serialization/Bson/Envelope.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 3679ccc..1524a1d 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -577,6 +577,8 @@ private void ProcessRpcResultMessage(WireMessage message) throw new KeyNotFoundException("Received a result for a unknown call."); clientRpcContext = _activeCalls[unqiueCallKey]; + + _activeCalls.Remove(unqiueCallKey); } clientRpcContext.Error = message.Error; diff --git a/CoreRemoting/Serialization/Bson/Envelope.cs b/CoreRemoting/Serialization/Bson/Envelope.cs index d4266d2..6c7383a 100644 --- a/CoreRemoting/Serialization/Bson/Envelope.cs +++ b/CoreRemoting/Serialization/Bson/Envelope.cs @@ -36,11 +36,13 @@ public Envelope(object value) /// /// Gets the type of the wrapped value. /// + [JsonIgnore] public Type Type => _type; /// /// Gets the wrapped value. /// + [JsonIgnore] public object Value { get From 6e462b0025b2c658f2e258b531b7f7c4aee4415c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A5=D1=83=D1=85=D0=BB=D0=B0=D0=B5=D0=B2?= Date: Tue, 7 May 2024 12:39:42 +0300 Subject: [PATCH 09/19] Do not hide errors during deserialization Deserialization errors may occur when using bison, for example, if the object being deserialized has several constructors, none of which is marked with the JsonConstructor attribute. As a result, the RPC may freeze. --- CoreRemoting/RemotingClient.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 1524a1d..b210da6 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -616,8 +616,10 @@ private void ProcessRpcResultMessage(WireMessage message) } catch (Exception e) { - Console.WriteLine(e); - throw; + clientRpcContext.Error = true; + clientRpcContext.RemoteException = new RemoteInvocationException( + message: e.Message, + innerEx: e.GetType().IsSerializable ? e : null); } } clientRpcContext.WaitHandle.Set(); From 7e6dc6415d84f4b759048998fd3c0ac84ebead7b Mon Sep 17 00:00:00 2001 From: yallie Date: Sat, 29 Jun 2024 03:13:36 +0300 Subject: [PATCH 10/19] Added a simulated missing method exception test. --- CoreRemoting.Tests/RemotingServicesTests.cs | 2 +- CoreRemoting.Tests/RpcTests.cs | 123 +++++++++++------- .../Tools/CustomMessageBuilder.cs | 48 +++++++ CoreRemoting.Tests/Tools/IEnumTestService.cs | 5 + CoreRemoting/Properties/AssemblyInfo.cs | 3 + CoreRemoting/RemotingClient.cs | 4 +- CoreRemoting/RemotingSession.cs | 2 +- 7 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 CoreRemoting.Tests/Tools/CustomMessageBuilder.cs create mode 100644 CoreRemoting/Properties/AssemblyInfo.cs diff --git a/CoreRemoting.Tests/RemotingServicesTests.cs b/CoreRemoting.Tests/RemotingServicesTests.cs index a99f1e5..fc9d87a 100644 --- a/CoreRemoting.Tests/RemotingServicesTests.cs +++ b/CoreRemoting.Tests/RemotingServicesTests.cs @@ -51,7 +51,7 @@ public void Marshal_should_register_a_service_instance() var testService = new TestService(); using var server = new RemotingServer(); - server.Start(); + server.Start(); string serviceName = RemotingServices.Marshal(testService, "test", typeof(ITestService), server.UniqueServerInstanceName); diff --git a/CoreRemoting.Tests/RpcTests.cs b/CoreRemoting.Tests/RpcTests.cs index 47bc59b..71e497a 100644 --- a/CoreRemoting.Tests/RpcTests.cs +++ b/CoreRemoting.Tests/RpcTests.cs @@ -14,12 +14,12 @@ public class RpcTests : IClassFixture private readonly ServerFixture _serverFixture; private readonly ITestOutputHelper _testOutputHelper; private bool _remoteServiceCalled; - + public RpcTests(ServerFixture serverFixture, ITestOutputHelper testOutputHelper) { _serverFixture = serverFixture; _testOutputHelper = testOutputHelper; - + _serverFixture.TestService.TestMethodFake = arg => { _remoteServiceCalled = true; @@ -36,10 +36,10 @@ void ClientAction() { var stopWatch = new Stopwatch(); stopWatch.Start(); - + using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort }); @@ -48,38 +48,38 @@ void ClientAction() _testOutputHelper.WriteLine($"Creating client took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + client.Connect(); stopWatch.Stop(); _testOutputHelper.WriteLine($"Establishing connection took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var proxy = client.CreateProxy(); - + stopWatch.Stop(); _testOutputHelper.WriteLine($"Creating proxy took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var result = proxy.TestMethod("test"); stopWatch.Stop(); _testOutputHelper.WriteLine($"Remote method invocation took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var result2 = proxy.TestMethod("test"); stopWatch.Stop(); _testOutputHelper.WriteLine($"Second remote method invocation took {stopWatch.ElapsedMilliseconds} ms"); - + Assert.Equal("test", result); Assert.Equal("test", result2); - + proxy.MethodWithOutParameter(out int methodCallCount); - + Assert.Equal(1, methodCallCount); } catch (Exception e) @@ -92,26 +92,26 @@ void ClientAction() var clientThread = new Thread(ClientAction); clientThread.Start(); clientThread.Join(); - + Assert.True(_remoteServiceCalled); - Assert.Equal(0, _serverFixture.ServerErrorCount); + Assert.Equal(0, _serverFixture.ServerErrorCount); } - + [Fact] public void Call_on_Proxy_should_be_invoked_on_remote_service_with_MessageEncryption() { _serverFixture.Server.Config.MessageEncryption = true; - + void ClientAction() { try { var stopWatch = new Stopwatch(); stopWatch.Start(); - + using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, ServerPort = _serverFixture.Server.Config.NetworkPort, MessageEncryption = true }); @@ -120,33 +120,33 @@ void ClientAction() _testOutputHelper.WriteLine($"Creating client took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + client.Connect(); stopWatch.Stop(); _testOutputHelper.WriteLine($"Establishing connection took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var proxy = client.CreateProxy(); - + stopWatch.Stop(); _testOutputHelper.WriteLine($"Creating proxy took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var result = proxy.TestMethod("test"); stopWatch.Stop(); _testOutputHelper.WriteLine($"Remote method invocation took {stopWatch.ElapsedMilliseconds} ms"); stopWatch.Reset(); stopWatch.Start(); - + var result2 = proxy.TestMethod("test"); stopWatch.Stop(); _testOutputHelper.WriteLine($"Second remote method invocation took {stopWatch.ElapsedMilliseconds} ms"); - + Assert.Equal("test", result); Assert.Equal("test", result2); } @@ -160,9 +160,9 @@ void ClientAction() var clientThread = new Thread(ClientAction); clientThread.Start(); clientThread.Join(); - + _serverFixture.Server.Config.MessageEncryption = true; - + Assert.True(_remoteServiceCalled); Assert.Equal(0, _serverFixture.ServerErrorCount); } @@ -179,7 +179,7 @@ void ClientAction() using var client = new RemotingClient( new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort, }); @@ -199,21 +199,21 @@ void ClientAction() var clientThread = new Thread(ClientAction); clientThread.Start(); clientThread.Join(); - + Assert.Equal("test", argumentFromServer); Assert.Equal(0, _serverFixture.ServerErrorCount); } - + [Fact] public void Events_should_work_remotly() { bool serviceEventCalled = false; bool customDelegateEventCalled = false; - + using var client = new RemotingClient( new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, SendTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort, @@ -237,18 +237,18 @@ public void Events_should_work_remotly() customDelegateEventCalled = true; customDelegateEventResetEvent.Set(); }; - + proxy.FireServiceEvent(); proxy.FireCustomDelegateEvent(); serviceEventResetEvent.Wait(1000); customDelegateEventResetEvent.Wait(1000); - + Assert.True(serviceEventCalled); Assert.True(customDelegateEventCalled); Assert.Equal(0, _serverFixture.ServerErrorCount); } - + [Fact] public void External_types_should_work_as_remote_service_parameters() { @@ -266,7 +266,7 @@ void ClientAction() { using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort, }); @@ -274,7 +274,7 @@ void ClientAction() client.Connect(); var proxy = client.CreateProxy(); - proxy.TestExternalTypeParameter(new DataClass() {Value = 42}); + proxy.TestExternalTypeParameter(new DataClass() { Value = 42 }); Assert.Equal(42, parameterValue.Value); } @@ -288,17 +288,17 @@ void ClientAction() var clientThread = new Thread(ClientAction); clientThread.Start(); clientThread.Join(); - + Assert.True(_remoteServiceCalled); Assert.Equal(0, _serverFixture.ServerErrorCount); } - + [Fact] public void Generic_methods_should_be_called_correctly() { using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort, }); @@ -307,16 +307,16 @@ public void Generic_methods_should_be_called_correctly() var proxy = client.CreateProxy(); var result = proxy.Echo("Yay"); - + Assert.Equal("Yay", result); } - + [Fact] public void Inherited_methods_should_be_called_correctly() { using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort, }); @@ -325,16 +325,16 @@ public void Inherited_methods_should_be_called_correctly() var proxy = client.CreateProxy(); var result = proxy.BaseMethod(); - + Assert.True(result); } - + [Fact] public void Enum_arguments_should_be_passed_correctly() { using var client = new RemotingClient(new ClientConfig() { - ConnectionTimeout = 0, + ConnectionTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort }); @@ -344,9 +344,40 @@ public void Enum_arguments_should_be_passed_correctly() var resultFirst = proxy.Echo(TestEnum.First); var resultSecond = proxy.Echo(TestEnum.Second); - + Assert.Equal(TestEnum.First, resultFirst); Assert.Equal(TestEnum.Second, resultSecond); } + + [Fact] + public void Missing_method_throws_MissingMethodException() + { + using var client = new RemotingClient(new ClientConfig() + { + ConnectionTimeout = 0, + InvocationTimeout = 0, + MessageEncryption = false, + ServerPort = _serverFixture.Server.Config.NetworkPort + }); + + // simulate MissingMethodException + var mb = new CustomMessageBuilder + { + ProcessMethodCallMessage = m => + { + if (m.MethodName == "TestMethod") + { + m.MethodName = "Missing Method"; + } + } + }; + + client.MethodCallMessageBuilder = mb; + client.Connect(); + + var proxy = client.CreateProxy(); + var result = proxy.TestMethod(null); + Assert.Fail(); + } } } \ No newline at end of file diff --git a/CoreRemoting.Tests/Tools/CustomMessageBuilder.cs b/CoreRemoting.Tests/Tools/CustomMessageBuilder.cs new file mode 100644 index 0000000..338e02b --- /dev/null +++ b/CoreRemoting.Tests/Tools/CustomMessageBuilder.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using CoreRemoting.RpcMessaging; +using CoreRemoting.Serialization; + +namespace CoreRemoting.Tests.Tools +{ + /// + /// Custom client-side RPC message processor. + /// + public class CustomMessageBuilder : IMethodCallMessageBuilder + { + public CustomMessageBuilder() + { + Builder = new MethodCallMessageBuilder(); + } + + public Action ProcessMethodCallMessage { get; set; } = m => { }; + + public Action> ProcessMethodParameterInfos { get; set; } = m => { }; + + public Action ProcessMethodCallResultMessage { get; set; } = m => { }; + + private MethodCallMessageBuilder Builder { get; set; } + + public MethodCallMessage BuildMethodCallMessage(ISerializerAdapter serializer, string remoteServiceName, MethodInfo targetMethod, object[] args) + { + var m = Builder.BuildMethodCallMessage(serializer, remoteServiceName, targetMethod, args); + ProcessMethodCallMessage(m); + return m; + } + + public IEnumerable BuildMethodParameterInfos(ISerializerAdapter serializer, MethodInfo targetMethod, object[] args) + { + var m = Builder.BuildMethodParameterInfos(serializer, targetMethod, args); + ProcessMethodParameterInfos(m); + return m; + } + + public MethodCallResultMessage BuildMethodCallResultMessage(ISerializerAdapter serializer, Guid uniqueCallKey, MethodInfo method, object[] args, object returnValue) + { + var m = Builder.BuildMethodCallResultMessage(serializer, uniqueCallKey, method, args, returnValue); + ProcessMethodCallResultMessage(m); + return m; + } + } +} diff --git a/CoreRemoting.Tests/Tools/IEnumTestService.cs b/CoreRemoting.Tests/Tools/IEnumTestService.cs index d7c7478..8cbac5f 100644 --- a/CoreRemoting.Tests/Tools/IEnumTestService.cs +++ b/CoreRemoting.Tests/Tools/IEnumTestService.cs @@ -9,4 +9,9 @@ public enum TestEnum public interface IEnumTestService { TestEnum Echo(TestEnum inputValue); + + TestEnum Echo2(TestEnum input) + { + return input; + } } \ No newline at end of file diff --git a/CoreRemoting/Properties/AssemblyInfo.cs b/CoreRemoting/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fc984af --- /dev/null +++ b/CoreRemoting/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("CoreRemoting.Tests")] \ No newline at end of file diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 1524a1d..5217fd1 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -147,12 +147,12 @@ private void OnDisconnected() /// /// Gets the proxy generator instance. /// - private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator(); + private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator(); /// /// Gets a utility object for building remoting messages. /// - internal IMethodCallMessageBuilder MethodCallMessageBuilder { get; } + internal IMethodCallMessageBuilder MethodCallMessageBuilder { get; set; } /// /// Gets a utility object to provide encryption of remoting messages. diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index c355f88..2e4a9f7 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -419,7 +419,7 @@ private void ProcessRpcMessage(WireMessage request) parameterValues = MapArguments(parameterValues, parameterTypes); MethodInfo method; - + if (callMessage.GenericArgumentTypeNames != null && callMessage.GenericArgumentTypeNames.Length > 0) { var methods = From a9c8041a16a143e7489fef0fa36352b92f360e5c Mon Sep 17 00:00:00 2001 From: yallie Date: Sat, 29 Jun 2024 22:41:57 +0300 Subject: [PATCH 11/19] Extracted GetMethodInfo method from ProcessRpcMessage. --- CoreRemoting.Tests/RpcTests.cs | 4 +- CoreRemoting.Tests/Tools/IEnumTestService.cs | 5 - CoreRemoting/RemotingSession.cs | 116 ++++++++++--------- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/CoreRemoting.Tests/RpcTests.cs b/CoreRemoting.Tests/RpcTests.cs index 71e497a..efe7778 100644 --- a/CoreRemoting.Tests/RpcTests.cs +++ b/CoreRemoting.Tests/RpcTests.cs @@ -376,8 +376,8 @@ public void Missing_method_throws_MissingMethodException() client.Connect(); var proxy = client.CreateProxy(); - var result = proxy.TestMethod(null); - Assert.Fail(); + //var result = proxy.TestMethod(null); + //Assert.Fail(); } } } \ No newline at end of file diff --git a/CoreRemoting.Tests/Tools/IEnumTestService.cs b/CoreRemoting.Tests/Tools/IEnumTestService.cs index 8cbac5f..d7c7478 100644 --- a/CoreRemoting.Tests/Tools/IEnumTestService.cs +++ b/CoreRemoting.Tests/Tools/IEnumTestService.cs @@ -9,9 +9,4 @@ public enum TestEnum public interface IEnumTestService { TestEnum Echo(TestEnum inputValue); - - TestEnum Echo2(TestEnum input) - { - return input; - } } \ No newline at end of file diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index 2e4a9f7..89c3aa4 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -385,15 +385,15 @@ private void ProcessRpcMessage(WireMessage request) sharedSecret: sharedSecret, sendersPublicKeyBlob: _clientPublicKeyBlob, sendersPublicKeySize: _keyPair?.KeySize ?? 0); - + var callMessage = _server.Serializer .Deserialize(decryptedRawMessage); - ServerRpcContext serverRpcContext = + ServerRpcContext serverRpcContext = new ServerRpcContext { - UniqueCallKey = + UniqueCallKey = request.UniqueCallKey == null ? Guid.Empty : new Guid(request.UniqueCallKey), @@ -403,7 +403,7 @@ private void ProcessRpcMessage(WireMessage request) }; var serializedResult = new byte[] { }; - + var service = _server.ServiceRegistry.GetService(callMessage.ServiceName); var serviceInterfaceType = _server.ServiceRegistry.GetServiceInterfaceType(callMessage.ServiceName); @@ -411,62 +411,14 @@ private void ProcessRpcMessage(WireMessage request) CallContext.RestoreFromSnapshot(callMessage.CallContextSnapshot); serverRpcContext.ServiceInstance = service; - + callMessage.UnwrapParametersFromDeserializedMethodCallMessage( - out var parameterValues, + out var parameterValues, out var parameterTypes); parameterValues = MapArguments(parameterValues, parameterTypes); - MethodInfo method; - - if (callMessage.GenericArgumentTypeNames != null && callMessage.GenericArgumentTypeNames.Length > 0) - { - var methods = - serviceInterfaceType.GetMethods().ToList(); - - foreach (var inheritedInterface in serviceInterfaceType.GetInterfaces()) - { - methods.AddRange(inheritedInterface.GetMethods()); - } - - method = - methods.SingleOrDefault(m => - m.IsGenericMethod && - m.Name.Equals(callMessage.MethodName, StringComparison.Ordinal)); - - if (method != null) - { - Type[] genericArguments = - callMessage.GenericArgumentTypeNames - .Select(typeName => Type.GetType(typeName)) - .ToArray(); - - method = method.MakeGenericMethod(genericArguments); - } - } - else - { - method = - serviceInterfaceType.GetMethod( - name: callMessage.MethodName, - types: parameterTypes); - - if (method == null) - { - foreach (var inheritedInterface in serviceInterfaceType.GetInterfaces()) - { - method = - inheritedInterface.GetMethod( - name: callMessage.MethodName, - types: parameterTypes); - - if (method != null) - break; - } - } - } - + var method = GetMethodInfo(callMessage, serviceInterfaceType, parameterTypes); if (method == null) throw new MissingMethodException( className: callMessage.ServiceName, @@ -587,6 +539,60 @@ private void ProcessRpcMessage(WireMessage request) CurrentSession.Value = null; } + private MethodInfo GetMethodInfo(MethodCallMessage callMessage, Type serviceInterfaceType, Type[] parameterTypes) + { + MethodInfo method; + + if (callMessage.GenericArgumentTypeNames != null && callMessage.GenericArgumentTypeNames.Length > 0) + { + var methods = + serviceInterfaceType.GetMethods().ToList(); + + foreach (var inheritedInterface in serviceInterfaceType.GetInterfaces()) + { + methods.AddRange(inheritedInterface.GetMethods()); + } + + method = + methods.SingleOrDefault(m => + m.IsGenericMethod && + m.Name.Equals(callMessage.MethodName, StringComparison.Ordinal)); + + if (method != null) + { + Type[] genericArguments = + callMessage.GenericArgumentTypeNames + .Select(typeName => Type.GetType(typeName)) + .ToArray(); + + method = method.MakeGenericMethod(genericArguments); + } + } + else + { + method = + serviceInterfaceType.GetMethod( + name: callMessage.MethodName, + types: parameterTypes); + + if (method == null) + { + foreach (var inheritedInterface in serviceInterfaceType.GetInterfaces()) + { + method = + inheritedInterface.GetMethod( + name: callMessage.MethodName, + types: parameterTypes); + + if (method != null) + break; + } + } + } + + return method; + } + /// /// Maps non serializable arguments into a serializable form. /// From 98e0c1969a3031a5076059e2ddb42e9447772e37 Mon Sep 17 00:00:00 2001 From: yallie Date: Sat, 29 Jun 2024 23:18:21 +0300 Subject: [PATCH 12/19] Added dotnet workflow. --- .github/workflows/dotnet.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..789431e --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,35 @@ +name: .NET + +on: + push: +# branches: +# - master +# - release/* + pull_request: + branches: + - master + - release/* + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal From f0779f733de354aae8badb1040b2fa76b8761491 Mon Sep 17 00:00:00 2001 From: yallie Date: Sun, 30 Jun 2024 00:02:35 +0300 Subject: [PATCH 13/19] Service resolution and method search on the server should report errors back to the client. --- CoreRemoting.Tests/RpcTests.cs | 32 +++++- CoreRemoting/RemotingSession.cs | 166 ++++++++++++++++++-------------- 2 files changed, 122 insertions(+), 76 deletions(-) diff --git a/CoreRemoting.Tests/RpcTests.cs b/CoreRemoting.Tests/RpcTests.cs index efe7778..124ee9a 100644 --- a/CoreRemoting.Tests/RpcTests.cs +++ b/CoreRemoting.Tests/RpcTests.cs @@ -350,12 +350,13 @@ public void Enum_arguments_should_be_passed_correctly() } [Fact] - public void Missing_method_throws_MissingMethodException() + public void Missing_method_throws_RemoteInvocationException() { using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, InvocationTimeout = 0, + SendTimeout = 0, MessageEncryption = false, ServerPort = _serverFixture.Server.Config.NetworkPort }); @@ -376,8 +377,33 @@ public void Missing_method_throws_MissingMethodException() client.Connect(); var proxy = client.CreateProxy(); - //var result = proxy.TestMethod(null); - //Assert.Fail(); + var ex = Assert.Throws(() => proxy.TestMethod(null)); + + // a localized message similar to "Method 'Missing method' not found" + Assert.NotNull(ex); + Assert.Contains("Missing Method", ex.Message); + } + + [Fact] + public void Missing_service_throws_RemoteInvocationException() + { + using var client = new RemotingClient(new ClientConfig() + { + ConnectionTimeout = 0, + InvocationTimeout = 0, + SendTimeout = 0, + MessageEncryption = false, + ServerPort = _serverFixture.Server.Config.NetworkPort + }); + + client.Connect(); + + var proxy = client.CreateProxy(); + var ex = Assert.Throws(() => proxy.Dispose()); + + // a localized message similar to "Service 'System.IDisposable' is not registered" + Assert.NotNull(ex); + Assert.Contains("IDisposable", ex.Message); } } } \ No newline at end of file diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index 89c3aa4..82905ff 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -402,82 +402,38 @@ private void ProcessRpcMessage(WireMessage request) Session = this }; - var serializedResult = new byte[] { }; - - var service = _server.ServiceRegistry.GetService(callMessage.ServiceName); - var serviceInterfaceType = - _server.ServiceRegistry.GetServiceInterfaceType(callMessage.ServiceName); - - CallContext.RestoreFromSnapshot(callMessage.CallContextSnapshot); - - serverRpcContext.ServiceInstance = service; - - callMessage.UnwrapParametersFromDeserializedMethodCallMessage( - out var parameterValues, - out var parameterTypes); - - parameterValues = MapArguments(parameterValues, parameterTypes); - - var method = GetMethodInfo(callMessage, serviceInterfaceType, parameterTypes); - if (method == null) - throw new MissingMethodException( - className: callMessage.ServiceName, - methodName: callMessage.MethodName); - - var oneWay = method.GetCustomAttribute() != null; - - if (_server.Config.AuthenticationRequired && !_isAuthenticated) - throw new NetworkException("Session is not authenticated."); - - object result = null; + var serializedResult = Array.Empty(); + var method = default(MethodInfo); + var parameterValues = Array.Empty(); + var parameterTypes = Array.Empty(); + var oneWay = false; try { - CurrentSession.Value = this; + var service = _server.ServiceRegistry.GetService(callMessage.ServiceName); + var serviceInterfaceType = + _server.ServiceRegistry.GetServiceInterfaceType(callMessage.ServiceName); - ((RemotingServer)_server).OnBeforeCall(serverRpcContext); + CallContext.RestoreFromSnapshot(callMessage.CallContextSnapshot); - result = method.Invoke(service, parameterValues); + serverRpcContext.ServiceInstance = service; - var returnType = method.ReturnType; + callMessage.UnwrapParametersFromDeserializedMethodCallMessage( + out parameterValues, + out parameterTypes); - if (result != null) - { - // Wait for result value if result is a Task - if (typeof(Task).IsAssignableFrom(returnType)) - { - var resultTask = (Task)result; - resultTask.Wait(); + parameterValues = MapArguments(parameterValues, parameterTypes); - if (returnType.IsGenericType) - { - result = returnType.GetProperty("Result")?.GetValue(resultTask); - } - else // ordinary non-generic task - { - result = null; - } - } - else if (returnType.GetCustomAttribute() != null) - { - var isRegisteredService = - returnType.IsInterface && - _server.ServiceRegistry - .GetAllRegisteredTypes().Any(s => - returnType.AssemblyQualifiedName != null && - returnType.AssemblyQualifiedName.Equals(s.AssemblyQualifiedName)); - - if (!isRegisteredService) - { - throw new InvalidOperationException( - $"Type '{returnType.AssemblyQualifiedName}' is not a registered service."); - } + method = GetMethodInfo(callMessage, serviceInterfaceType, parameterTypes); + if (method == null) + throw new MissingMethodException( + className: callMessage.ServiceName, + methodName: callMessage.MethodName); - result = new ServiceReference( - serviceInterfaceTypeName: returnType.FullName + ", " + returnType.Assembly.GetName().Name, - serviceName: returnType.FullName); - } - } + oneWay = method.GetCustomAttribute() != null; + + if (_server.Config.AuthenticationRequired && !_isAuthenticated) + throw new NetworkException("Session is not authenticated."); } catch (Exception ex) { @@ -486,21 +442,85 @@ private void ProcessRpcMessage(WireMessage request) message: ex.Message, innerEx: ex.GetType().IsSerializable ? ex : null); - ((RemotingServer)_server).OnAfterCall(serverRpcContext); - if (oneWay) return; serializedResult = _server.Serializer.Serialize(serverRpcContext.Exception); } - finally - { - CurrentSession.Value = null; - } + + object result = null; if (serverRpcContext.Exception == null) { + try + { + CurrentSession.Value = this; + + ((RemotingServer)_server).OnBeforeCall(serverRpcContext); + + result = method.Invoke(serverRpcContext.ServiceInstance, parameterValues); + + var returnType = method.ReturnType; + + if (result != null) + { + // Wait for result value if result is a Task + if (typeof(Task).IsAssignableFrom(returnType)) + { + var resultTask = (Task)result; + resultTask.Wait(); + + if (returnType.IsGenericType) + { + result = returnType.GetProperty("Result")?.GetValue(resultTask); + } + else // ordinary non-generic task + { + result = null; + } + } + else if (returnType.GetCustomAttribute() != null) + { + var isRegisteredService = + returnType.IsInterface && + _server.ServiceRegistry + .GetAllRegisteredTypes().Any(s => + returnType.AssemblyQualifiedName != null && + returnType.AssemblyQualifiedName.Equals(s.AssemblyQualifiedName)); + + if (!isRegisteredService) + { + throw new InvalidOperationException( + $"Type '{returnType.AssemblyQualifiedName}' is not a registered service."); + } + + result = new ServiceReference( + serviceInterfaceTypeName: returnType.FullName + ", " + returnType.Assembly.GetName().Name, + serviceName: returnType.FullName); + } + } + } + catch (Exception ex) + { + serverRpcContext.Exception = + new RemoteInvocationException( + message: ex.Message, + innerEx: ex.GetType().IsSerializable ? ex : null); + + ((RemotingServer)_server).OnAfterCall(serverRpcContext); + + if (oneWay) + return; + + serializedResult = + _server.Serializer.Serialize(serverRpcContext.Exception); + } + finally + { + CurrentSession.Value = null; + } + if (!oneWay) { serverRpcContext.MethodCallResultMessage = From fdeb18a6c959717e457074e33a73b32bb250de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A5=D1=83=D1=85=D0=BB=D0=B0=D0=B5=D0=B2?= Date: Mon, 12 Aug 2024 16:31:42 +0300 Subject: [PATCH 14/19] Preventing Leakage of the Remoting Session If the client process crashes, then checking idle sessions will not be able to close the session, because the _rawMessageTransport.SendMessage call will throw an exception. --- CoreRemoting/RemotingSession.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index 82905ff..c726c6d 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -750,9 +750,16 @@ public void Dispose() keyPair: _keyPair, messageType: "session_closed"); - _rawMessageTransport.SendMessage( - _server.Serializer.Serialize(wireMessage)); - + try + { + _rawMessageTransport.SendMessage( + _server.Serializer.Serialize(wireMessage)); + } + catch (Exception) + { + // ignored + } + BeforeDispose?.Invoke(); _keyPair?.Dispose(); @@ -774,4 +781,4 @@ public void Dispose() #endregion } -} \ No newline at end of file +} From e92c5f7be6b398a4e66b1c5b3d1e99fbf0368450 Mon Sep 17 00:00:00 2001 From: sancheolz Date: Wed, 25 Sep 2024 12:29:40 +0300 Subject: [PATCH 15/19] Use Async methods for WatsonTCP --- CoreRemoting/Channels/Tcp/TcpClientChannel.cs | 330 +++++++++--------- CoreRemoting/Channels/Tcp/TcpConnection.cs | 220 ++++++------ CoreRemoting/Channels/Tcp/TcpServerChannel.cs | 210 +++++------ CoreRemoting/CoreRemoting.csproj | 90 ++--- 4 files changed, 425 insertions(+), 425 deletions(-) diff --git a/CoreRemoting/Channels/Tcp/TcpClientChannel.cs b/CoreRemoting/Channels/Tcp/TcpClientChannel.cs index db7b7e2..791dc2e 100644 --- a/CoreRemoting/Channels/Tcp/TcpClientChannel.cs +++ b/CoreRemoting/Channels/Tcp/TcpClientChannel.cs @@ -1,165 +1,165 @@ -using System; -using System.Collections.Generic; -using WatsonTcp; - -namespace CoreRemoting.Channels.Tcp; - -/// -/// Client side TCP channel implementation. -/// -public class TcpClientChannel : IClientChannel, IRawMessageTransport -{ - private WatsonTcpClient _tcpClient; - private Dictionary _handshakeMetadata; - - /// - /// Event: Fires when a message is received from server. - /// - public event Action ReceiveMessage; - - /// - /// Event: Fires when an error is occurred. - /// - public event Action ErrorOccured; - - /// - public event Action Disconnected; - - /// - /// Initializes the channel. - /// - /// CoreRemoting client - public void Init(IRemotingClient client) - { - _tcpClient = new WatsonTcpClient(client.Config.ServerHostName, client.Config.ServerPort); - _tcpClient.Settings.NoDelay = true; - - _handshakeMetadata = - new Dictionary - { - { "MessageEncryption", client.MessageEncryption } - }; - - if (client.MessageEncryption) - _handshakeMetadata.Add("ShakeHands", Convert.ToBase64String(client.PublicKey)); - } - - /// - /// Establish a connection with the server. - /// - public void Connect() - { - if (_tcpClient == null) - throw new InvalidOperationException("Channel is not initialized."); - - if (_tcpClient.Connected) - return; - - _tcpClient.Events.ExceptionEncountered += OnError; - _tcpClient.Events.MessageReceived += OnMessage; - _tcpClient.Events.ServerDisconnected += OnDisconnected; - _tcpClient.Connect(); - } - private void OnDisconnected(object o, DisconnectionEventArgs disconnectionEventArgs) - { - Disconnected?.Invoke(); - } - - /// - /// Event procedure: Called when a error occurs on the TCP client layer. - /// - /// Event sender - /// Event arguments - private void OnError(object sender, ExceptionEventArgs e) - { - LastException = new NetworkException(e.Exception.Message, e.Exception); - - ErrorOccured?.Invoke(e.Exception.Message, e.Exception); - } - - /// - /// Event procedure: Called when a message from server is received. - /// - /// Sender of the event - /// Event arguments containing the message content - private void OnMessage(object sender, MessageReceivedEventArgs e) - { - if (e.Metadata != null && e.Metadata.ContainsKey("ServerAcceptConnection")) - { - if (!_tcpClient.Send(new byte[1] { 0x0 }, _handshakeMetadata) && !_tcpClient.Connected) - _tcpClient = null; - } - else - { - ReceiveMessage?.Invoke(e.Data); - } - } - - /// - /// Closes the connection. - /// - public void Disconnect() - { - if (_tcpClient == null) - return; - - _tcpClient.Events.MessageReceived -= OnMessage; - _tcpClient.Events.ExceptionEncountered -= OnError; - - if (_tcpClient.Connected) - { - try - { - _tcpClient.Disconnect(); - } - catch - { - // ignored - } - } - - _tcpClient.Dispose(); - _tcpClient = null; - } - - /// - /// Gets whether the connection is established or not. - /// - public bool IsConnected => _tcpClient?.Connected ?? false; - - /// - /// Gets the raw message transport component for this connection. - /// - public IRawMessageTransport RawMessageTransport => this; - - /// - /// Sends a message to the server. - /// - /// Raw message data - public bool SendMessage(byte[] rawMessage) - { - if (_tcpClient != null) - { - if (_tcpClient.Send(rawMessage)) - return true; - if (!_tcpClient.Connected) - _tcpClient = null; - return false; - } - else - throw new NetworkException("Channel disconnected"); - } - - /// - /// Gets or sets the last exception. - /// - public NetworkException LastException { get; set; } - - /// - /// Disconnect and free manages resources. - /// - public void Dispose() - { - Disconnect(); - } -} +using System; +using System.Collections.Generic; +using WatsonTcp; + +namespace CoreRemoting.Channels.Tcp; + +/// +/// Client side TCP channel implementation. +/// +public class TcpClientChannel : IClientChannel, IRawMessageTransport +{ + private WatsonTcpClient _tcpClient; + private Dictionary _handshakeMetadata; + + /// + /// Event: Fires when a message is received from server. + /// + public event Action ReceiveMessage; + + /// + /// Event: Fires when an error is occurred. + /// + public event Action ErrorOccured; + + /// + public event Action Disconnected; + + /// + /// Initializes the channel. + /// + /// CoreRemoting client + public void Init(IRemotingClient client) + { + _tcpClient = new WatsonTcpClient(client.Config.ServerHostName, client.Config.ServerPort); + _tcpClient.Settings.NoDelay = true; + + _handshakeMetadata = + new Dictionary + { + { "MessageEncryption", client.MessageEncryption } + }; + + if (client.MessageEncryption) + _handshakeMetadata.Add("ShakeHands", Convert.ToBase64String(client.PublicKey)); + } + + /// + /// Establish a connection with the server. + /// + public void Connect() + { + if (_tcpClient == null) + throw new InvalidOperationException("Channel is not initialized."); + + if (_tcpClient.Connected) + return; + + _tcpClient.Events.ExceptionEncountered += OnError; + _tcpClient.Events.MessageReceived += OnMessage; + _tcpClient.Events.ServerDisconnected += OnDisconnected; + _tcpClient.Connect(); + } + private void OnDisconnected(object o, DisconnectionEventArgs disconnectionEventArgs) + { + Disconnected?.Invoke(); + } + + /// + /// Event procedure: Called when a error occurs on the TCP client layer. + /// + /// Event sender + /// Event arguments + private void OnError(object sender, ExceptionEventArgs e) + { + LastException = new NetworkException(e.Exception.Message, e.Exception); + + ErrorOccured?.Invoke(e.Exception.Message, e.Exception); + } + + /// + /// Event procedure: Called when a message from server is received. + /// + /// Sender of the event + /// Event arguments containing the message content + private void OnMessage(object sender, MessageReceivedEventArgs e) + { + if (e.Metadata != null && e.Metadata.ContainsKey("ServerAcceptConnection")) + { + if (!_tcpClient.SendAsync(new byte[1] { 0x0 }, _handshakeMetadata).Result && !_tcpClient.Connected) + _tcpClient = null; + } + else + { + ReceiveMessage?.Invoke(e.Data); + } + } + + /// + /// Closes the connection. + /// + public void Disconnect() + { + if (_tcpClient == null) + return; + + _tcpClient.Events.MessageReceived -= OnMessage; + _tcpClient.Events.ExceptionEncountered -= OnError; + + if (_tcpClient.Connected) + { + try + { + _tcpClient.Disconnect(); + } + catch + { + // ignored + } + } + + _tcpClient.Dispose(); + _tcpClient = null; + } + + /// + /// Gets whether the connection is established or not. + /// + public bool IsConnected => _tcpClient?.Connected ?? false; + + /// + /// Gets the raw message transport component for this connection. + /// + public IRawMessageTransport RawMessageTransport => this; + + /// + /// Sends a message to the server. + /// + /// Raw message data + public bool SendMessage(byte[] rawMessage) + { + if (_tcpClient != null) + { + if (_tcpClient.SendAsync(rawMessage).Result) + return true; + if (!_tcpClient.Connected) + _tcpClient = null; + return false; + } + else + throw new NetworkException("Channel disconnected"); + } + + /// + /// Gets or sets the last exception. + /// + public NetworkException LastException { get; set; } + + /// + /// Disconnect and free manages resources. + /// + public void Dispose() + { + Disconnect(); + } +} diff --git a/CoreRemoting/Channels/Tcp/TcpConnection.cs b/CoreRemoting/Channels/Tcp/TcpConnection.cs index b10b914..6d58828 100644 --- a/CoreRemoting/Channels/Tcp/TcpConnection.cs +++ b/CoreRemoting/Channels/Tcp/TcpConnection.cs @@ -1,111 +1,111 @@ -using System; -using System.Collections.Generic; -using WatsonTcp; - -namespace CoreRemoting.Channels.Tcp; - -/// -/// TCP-Connection. -/// -public class TcpConnection : IRawMessageTransport -{ - private readonly ClientMetadata _clientMetadata; - private readonly WatsonTcpServer _tcpServer; - private readonly IRemotingServer _server; - private RemotingSession _session; - - /// - /// Craetes a new TCPConnection instance. - /// - /// Client info - /// TCP server obejct - /// Remoting server instance - public TcpConnection(ClientMetadata clientMetadata, WatsonTcpServer tcpServer, IRemotingServer server) - { - _clientMetadata = clientMetadata ?? throw new ArgumentNullException(nameof(clientMetadata)); - _tcpServer = tcpServer ?? throw new ArgumentNullException(nameof(tcpServer)); - _server = server ?? throw new ArgumentException(nameof(server)); - } - - /// - /// Event: Fires when a message is received from server. - /// - public event Action ReceiveMessage; - - /// - /// Event: Fires when an error is occurred. - /// - public event Action ErrorOccured; - - /// - /// Fires the ReceiveMessage event. - /// - /// Fehlermeldung - /// Ausnahme - internal void FireErrorOccured(string message, Exception ex) - { - ErrorOccured?.Invoke(message, ex); - } - - /// - /// Gets or sets the last exception. - /// - public NetworkException LastException { get; set; } - - /// - /// Fires the ReceiveMessage event. - /// - /// Message - /// Metadata - internal void FireReceiveMessage(byte[] rawMessage, Dictionary metadata) - { - if (_session == null) - { - byte[] clientPublicKey = null; - - if (metadata != null) - { - var messageEncryption = ((System.Text.Json.JsonElement)metadata["MessageEncryption"]).GetBoolean(); - - if (messageEncryption) - { - var shakeHands = ((System.Text.Json.JsonElement)metadata["ShakeHands"]).GetString(); - - if (shakeHands != null) - { - clientPublicKey = - Convert.FromBase64String(shakeHands); - } - } - } - - _session = - _server.SessionRepository.CreateSession( - clientPublicKey, - _server, - this); - - _session.BeforeDispose += BeforeDisposeSession; - } - else - ReceiveMessage?.Invoke(rawMessage); - } - - /// - /// Closes the internal websocket session. - /// - private void BeforeDisposeSession() - { - _session = null; - _tcpServer.DisconnectClient(_clientMetadata.Guid, MessageStatus.Shutdown); - } - - /// - /// Sends a message to the server. - /// - /// Raw message data - public bool SendMessage(byte[] rawMessage) - { - return _tcpServer.Send(_clientMetadata.Guid, rawMessage); - } +using System; +using System.Collections.Generic; +using WatsonTcp; + +namespace CoreRemoting.Channels.Tcp; + +/// +/// TCP-Connection. +/// +public class TcpConnection : IRawMessageTransport +{ + private readonly ClientMetadata _clientMetadata; + private readonly WatsonTcpServer _tcpServer; + private readonly IRemotingServer _server; + private RemotingSession _session; + + /// + /// Craetes a new TCPConnection instance. + /// + /// Client info + /// TCP server obejct + /// Remoting server instance + public TcpConnection(ClientMetadata clientMetadata, WatsonTcpServer tcpServer, IRemotingServer server) + { + _clientMetadata = clientMetadata ?? throw new ArgumentNullException(nameof(clientMetadata)); + _tcpServer = tcpServer ?? throw new ArgumentNullException(nameof(tcpServer)); + _server = server ?? throw new ArgumentException(nameof(server)); + } + + /// + /// Event: Fires when a message is received from server. + /// + public event Action ReceiveMessage; + + /// + /// Event: Fires when an error is occurred. + /// + public event Action ErrorOccured; + + /// + /// Fires the ReceiveMessage event. + /// + /// Fehlermeldung + /// Ausnahme + internal void FireErrorOccured(string message, Exception ex) + { + ErrorOccured?.Invoke(message, ex); + } + + /// + /// Gets or sets the last exception. + /// + public NetworkException LastException { get; set; } + + /// + /// Fires the ReceiveMessage event. + /// + /// Message + /// Metadata + internal void FireReceiveMessage(byte[] rawMessage, Dictionary metadata) + { + if (_session == null) + { + byte[] clientPublicKey = null; + + if (metadata != null) + { + var messageEncryption = ((System.Text.Json.JsonElement)metadata["MessageEncryption"]).GetBoolean(); + + if (messageEncryption) + { + var shakeHands = ((System.Text.Json.JsonElement)metadata["ShakeHands"]).GetString(); + + if (shakeHands != null) + { + clientPublicKey = + Convert.FromBase64String(shakeHands); + } + } + } + + _session = + _server.SessionRepository.CreateSession( + clientPublicKey, + _server, + this); + + _session.BeforeDispose += BeforeDisposeSession; + } + else + ReceiveMessage?.Invoke(rawMessage); + } + + /// + /// Closes the internal websocket session. + /// + private void BeforeDisposeSession() + { + _session = null; + _tcpServer.DisconnectClientAsync(_clientMetadata.Guid, MessageStatus.Shutdown); + } + + /// + /// Sends a message to the server. + /// + /// Raw message data + public bool SendMessage(byte[] rawMessage) + { + return _tcpServer.SendAsync(_clientMetadata.Guid, rawMessage).Result; + } } \ No newline at end of file diff --git a/CoreRemoting/Channels/Tcp/TcpServerChannel.cs b/CoreRemoting/Channels/Tcp/TcpServerChannel.cs index eb67f42..3c37560 100644 --- a/CoreRemoting/Channels/Tcp/TcpServerChannel.cs +++ b/CoreRemoting/Channels/Tcp/TcpServerChannel.cs @@ -1,105 +1,105 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using WatsonTcp; - -namespace CoreRemoting.Channels.Tcp; - -/// -/// Server side TCP channel implementation. -/// -public class TcpServerChannel : IServerChannel -{ - private IRemotingServer _remotingServer; - private WatsonTcpServer _tcpServer; - private readonly ConcurrentDictionary _connections; - - /// - /// Creates a new instance of the TcpServerChannel class. - /// - public TcpServerChannel() - { - _connections = new ConcurrentDictionary(); - } - - /// - /// Initializes the channel. - /// - /// CoreRemoting sever - public void Init(IRemotingServer server) - { - _remotingServer = server ?? throw new ArgumentNullException(nameof(server)); - - _tcpServer = new WatsonTcpServer(null, _remotingServer.Config.NetworkPort); - _tcpServer.Settings.NoDelay = true; - _tcpServer.Events.ClientConnected += OnClientConnected; - _tcpServer.Events.ClientDisconnected += OnClientDisconnected; - _tcpServer.Events.MessageReceived += OnTcpMessageReceived; - } - - private void OnClientConnected(object sender, ConnectionEventArgs e) - { - if(_connections.TryAdd(e.Client.Guid, new TcpConnection(e.Client, _tcpServer, _remotingServer))) - { - var metadata = new Dictionary - { - { "ServerAcceptConnection", true } - }; - - _tcpServer.Send(e.Client.Guid, new byte[]{ 0x02 }, metadata); - } - } - - private void OnClientDisconnected(object sender, DisconnectionEventArgs e) - { - _connections.TryRemove(e.Client.Guid, out _); - } - - private void OnTcpMessageReceived(object sender, MessageReceivedEventArgs e) - { - if (_connections.TryGetValue(e.Client.Guid, out TcpConnection connection)) - { - connection.FireReceiveMessage(e.Data, e.Metadata); - } - } - - /// - /// Start listening for client requests. - /// - public void StartListening() - { - if (_tcpServer == null) - throw new InvalidOperationException("Channel is not initialized."); - - _tcpServer.Start(); - } - - /// - /// Stop listening for client requests. - /// - public void StopListening() - { - if (_tcpServer == null) - return; - - _tcpServer.Stop(); - } - - /// - /// Gets whether the channel is listening or not. - /// - public bool IsListening => - _tcpServer?.IsListening ?? false; - - /// - /// Stops listening and frees managed resources. - /// - public void Dispose() - { - if (_tcpServer != null) - { - _tcpServer.Dispose(); - _tcpServer = null; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using WatsonTcp; + +namespace CoreRemoting.Channels.Tcp; + +/// +/// Server side TCP channel implementation. +/// +public class TcpServerChannel : IServerChannel +{ + private IRemotingServer _remotingServer; + private WatsonTcpServer _tcpServer; + private readonly ConcurrentDictionary _connections; + + /// + /// Creates a new instance of the TcpServerChannel class. + /// + public TcpServerChannel() + { + _connections = new ConcurrentDictionary(); + } + + /// + /// Initializes the channel. + /// + /// CoreRemoting sever + public void Init(IRemotingServer server) + { + _remotingServer = server ?? throw new ArgumentNullException(nameof(server)); + + _tcpServer = new WatsonTcpServer(null, _remotingServer.Config.NetworkPort); + _tcpServer.Settings.NoDelay = true; + _tcpServer.Events.ClientConnected += OnClientConnected; + _tcpServer.Events.ClientDisconnected += OnClientDisconnected; + _tcpServer.Events.MessageReceived += OnTcpMessageReceived; + } + + private void OnClientConnected(object sender, ConnectionEventArgs e) + { + if(_connections.TryAdd(e.Client.Guid, new TcpConnection(e.Client, _tcpServer, _remotingServer))) + { + var metadata = new Dictionary + { + { "ServerAcceptConnection", true } + }; + + _tcpServer.SendAsync(e.Client.Guid, new byte[]{ 0x02 }, metadata); + } + } + + private void OnClientDisconnected(object sender, DisconnectionEventArgs e) + { + _connections.TryRemove(e.Client.Guid, out _); + } + + private void OnTcpMessageReceived(object sender, MessageReceivedEventArgs e) + { + if (_connections.TryGetValue(e.Client.Guid, out TcpConnection connection)) + { + connection.FireReceiveMessage(e.Data, e.Metadata); + } + } + + /// + /// Start listening for client requests. + /// + public void StartListening() + { + if (_tcpServer == null) + throw new InvalidOperationException("Channel is not initialized."); + + _tcpServer.Start(); + } + + /// + /// Stop listening for client requests. + /// + public void StopListening() + { + if (_tcpServer == null) + return; + + _tcpServer.Stop(); + } + + /// + /// Gets whether the channel is listening or not. + /// + public bool IsListening => + _tcpServer?.IsListening ?? false; + + /// + /// Stops listening and frees managed resources. + /// + public void Dispose() + { + if (_tcpServer != null) + { + _tcpServer.Dispose(); + _tcpServer = null; + } + } +} diff --git a/CoreRemoting/CoreRemoting.csproj b/CoreRemoting/CoreRemoting.csproj index 16e55d9..30e2721 100644 --- a/CoreRemoting/CoreRemoting.csproj +++ b/CoreRemoting/CoreRemoting.csproj @@ -1,56 +1,56 @@ - - - - default + + + + default true - CoreRemoting - Hagen Siegel - Easy to use Remoting library for .NET Core and .NET Framework + CoreRemoting + Hagen Siegel + Easy to use Remoting library for .NET Core and .NET Framework 2024 Hagen Siegel - Remoting RPC Network - Hagen Siegel + Remoting RPC Network + Hagen Siegel 1.2.0.0 netstandard2.0 - https://github.com/theRainbird/CoreRemoting - - https://github.com/theRainbird/CoreRemoting.git - git + https://github.com/theRainbird/CoreRemoting + + https://github.com/theRainbird/CoreRemoting.git + git 1.2.0.0 - Added support for cross framework serialization - Fixed possible thread deadlocks - Improved service registration and methods for getting service registration metadata. - - - - bin\Debug\CoreRemoting.xml - - - - bin\Release\CoreRemoting.xml - - - - + + + + bin\Debug\CoreRemoting.xml + + + + bin\Release\CoreRemoting.xml + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + From 015589af881688c5c9d124aa50ecbe91327f3d45 Mon Sep 17 00:00:00 2001 From: sancheolz Date: Wed, 25 Sep 2024 12:30:25 +0300 Subject: [PATCH 16/19] Replace obsolete methods for Aes encryption --- CoreRemoting/Encryption/RsaKeyExchange.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreRemoting/Encryption/RsaKeyExchange.cs b/CoreRemoting/Encryption/RsaKeyExchange.cs index 2390c0f..c7deab9 100644 --- a/CoreRemoting/Encryption/RsaKeyExchange.cs +++ b/CoreRemoting/Encryption/RsaKeyExchange.cs @@ -21,7 +21,7 @@ public static EncryptedSecret EncryptSecret(int keySize, byte[] receiversPublicK using var receiversPublicKey = new RSACryptoServiceProvider(dwKeySize: keySize); receiversPublicKey.ImportCspBlob(receiversPublicKeyBlob); - using Aes aes = new AesCryptoServiceProvider(); + using Aes aes = Aes.Create(); // Encrypt the session key var keyFormatter = new RSAPKCS1KeyExchangeFormatter(receiversPublicKey); @@ -53,7 +53,7 @@ public static byte[] DecryptSecret(int keySize, byte[] receiversPrivateKeyBlob, using var receiversPrivateKey = new RSACryptoServiceProvider(dwKeySize: keySize); receiversPrivateKey.ImportCspBlob(receiversPrivateKeyBlob); - using Aes aes = new AesCryptoServiceProvider(); + using Aes aes = Aes.Create(); aes.IV = encryptedSecret.Iv; // Decrypt the session key From 95401dbc3a8b0e56e9c1218c29c6c41cb2f8e71c Mon Sep 17 00:00:00 2001 From: Alexey Yakovlev Date: Thu, 14 Nov 2024 03:49:50 +0300 Subject: [PATCH 17/19] Added a todo comment. --- CoreRemoting/RemotingSession.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index c726c6d..d9cc077 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -758,6 +758,7 @@ public void Dispose() catch (Exception) { // ignored + // TODO: dispatch the exception } BeforeDispose?.Invoke(); From a7437d8f3c0eb9d99ed6a34e4f981e05ef1388e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A5=D1=83=D1=85=D0=BB=D0=B0=D0=B5=D0=B2?= Date: Thu, 14 Nov 2024 12:21:45 +0300 Subject: [PATCH 18/19] Remove RemotingSession in a separate task to allow _currentlyProcessedMessagesCounter.Signal() to be executed, which we are waiting for in Dispose --- CoreRemoting/RemotingSession.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index d9cc077..d6e99f2 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -310,7 +310,7 @@ private void ProcessGoodbyeMessage(WireMessage request) _rawMessageTransport.SendMessage(_server.Serializer.Serialize(resultMessage)); - _server.SessionRepository.RemoveSession(_sessionId); + Task.Run(() => _server.SessionRepository.RemoveSession(_sessionId)); } /// From ff402de45de57b815df83d405221c6f38796015b Mon Sep 17 00:00:00 2001 From: Francois Botha Date: Tue, 26 Mar 2024 14:39:28 +0200 Subject: [PATCH 19/19] Avoid nested envelopes --- .../RpcMessaging/MethodCallMessageBuilder.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs b/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs index 74f5d35..a125d42 100644 --- a/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs +++ b/CoreRemoting/RpcMessaging/MethodCallMessageBuilder.cs @@ -153,14 +153,10 @@ public MethodCallResultMessage BuildMethodCallResultMessage( var isArgNull = arg == null; - var serializedArgValue = - serializer.Serialize( - serializer.EnvelopeNeededForParameterSerialization - ? typeof(Envelope) - : parameterInfo.ParameterType, - serializer.EnvelopeNeededForParameterSerialization - ? new Envelope(arg) - : arg); + var serializedArgValue = + serializer.EnvelopeNeededForParameterSerialization && arg is not Envelope + ? serializer.Serialize(typeof(Envelope), new Envelope(arg)) + : serializer.Serialize(parameterInfo.ParameterType, arg); outParameters.Add( new MethodCallOutParameterMessage