public void TestRemoteMethodCallSerialization() { var call = new RemoteMethodCall(); call.ComponentName = "component"; call.CallId = Guid.NewGuid(); call.MethodName = "sampleMethod"; call.Parameters = new List<object> { 1, 2, 3 }; var xml = call.Serialize(); var roundtrip = new RemoteMethodCall(); Assert.IsTrue(roundtrip.Validate(xml)); roundtrip.Deserialize(xml); Assert.IsNotNull(roundtrip); Assert.AreEqual<Guid>(call.CallId, roundtrip.CallId); Assert.AreEqual<string>(call.ComponentName, roundtrip.ComponentName); Assert.AreEqual<string>(call.MethodName, roundtrip.MethodName); Assert.AreEqual<int>(call.Parameters.Count(), roundtrip.Parameters.Count()); int count = call.Parameters.Count(); for (int i = 0; i < count; i++) { Assert.AreEqual<int>((int)call.Parameters.Skip(i).First(), (int)roundtrip.Parameters.Skip(i).First()); } }
/// <summary> /// Processes an incoming RemoteMethodResponse /// </summary> /// <param name="response">The RemoteMethodResponse that is to be processed</param> protected virtual void processRemoteMethodResponse(RemoteMethodResponse response) { //get the RemoteMethodCall that triggered the response from the cache RemoteMethodCall call = unrepliedCalls.ContainsKey(response.CallId) ? unrepliedCalls[response.CallId] : null; //response is only processed, when the associated method call could be found if (call != null) { //stop the connection from timing out as we just received a message timeoutTimer.Stop(); //if call was synchronous => put the response value in the value cache and release wait handle if (synchronousCalls_WaitHandles.ContainsKey(call.CallId)) { synchonousCalls_ValueCache.Add(call.CallId, response.ResponseValue); synchronousCalls_WaitHandles[call.CallId].Set(); } //remove call from unreplied calls unrepliedCalls.Remove(response.CallId); //decrease the number of repsonses expected from the target client (connection will not time out when no responses are expected if (call.ResponseExpected) { this.expectedResponseCount--; } //if there are still call taht are unreplied, restart the timeout-timer if (unrepliedCalls.Any()) { timeoutTimer.Start(); } } }
/// <summary> /// Send a message to the target client /// </summary> /// <param name="msg">The message to process and send</param> protected virtual void sendMessage(RpcMessage msg) { //if message is a RemoteMethodCall, it will be cached to be able to process response messages if (msg is RemoteMethodCall) { RemoteMethodCall call = msg as RemoteMethodCall; if (call.ResponseExpected) { unrepliedCalls.Add(call.CallId, call); expectedResponseCount++; timeoutTimer.Start(); } } //process the message using the MessageProcessor and pass the result to the ConnectionManager to send try { this.WtlpClient.Send(msg.Serialize().ToString().GetBytesUTF8()); } catch (WtlpException ex) { if (ex.Error == Result.Timeout) { throw new TimeoutException(); } else { throw new RemoteErrorException(); } } }
public void TestRemoteMethodCallSerialization_MissingComponentNameElement() { var call = new RemoteMethodCall(); call.ComponentName = "component"; call.CallId = Guid.NewGuid(); call.MethodName = "sampleMethod"; call.Parameters = new List<object> { 1, 2, 3 }; var xml = call.Serialize(); xml.Element(XName.Get("ComponentName", xml.Name.NamespaceName)).Remove(); var roundtrip = new RemoteMethodCall(); Assert.IsFalse(roundtrip.Validate(xml)); }
protected object invokeRemoteMethod(string component, string name, object[] args, bool responseExpected) { var call = new RemoteMethodCall() { ComponentName = component, MethodName = name, Parameters = args.ToList <object>(), ResponseExpected = responseExpected }; //send the RemoteMethodCall sendMessage(call); //if a response is expected, wait for the recipient to send a response if (responseExpected) { //wait for the response using a EventWaitHandle if (!synchronousCalls_WaitHandles.ContainsKey(call.CallId)) { synchronousCalls_WaitHandles.Add(call.CallId, new EventWaitHandle(false, EventResetMode.ManualReset)); } synchronousCalls_WaitHandles[call.CallId].WaitOne(); //get the returned value from the cache object value = synchonousCalls_ValueCache[call.CallId]; //remove the value from value cache synchonousCalls_ValueCache.Remove(call.CallId); if (value is TimeoutException) { throw (value as TimeoutException); } else if (value is RemoteError) { throw new RemoteErrorException(value as RemoteError); } else { return(value); } } else { return(null); } }
/// <summary> /// Processes an incoming RemoteMethodCall /// </summary> /// <param name="call">The RemoteMethodCall that is to be processed</param> /// <param name="component">The component that will handle the call</param> protected virtual void processRemoteMethodCall(RemoteMethodCall call, IComponent component) { //get the call's result CallResult callResult = getCallResult(call, component); if (callResult is ErrorResult) { //result was error => send RemoteError sendMessage( new RemoteError((callResult as ErrorResult).ErrorCode) { CallId = call.CallId, ComponentName = call.ComponentName } ); } else if (callResult is ResponseResult) { //result was a response => send RemoteMethodResponse RemoteMethodResponse response = new RemoteMethodResponse(); response.CallId = call.CallId; response.ComponentName = call.ComponentName; response.ResponseValue = (callResult as ResponseResult).ResponseValue; sendMessage(response); } else if (callResult is VoidResult) { //result was VoidResult => do nothing } else { //should not happen, ResponseResult, ErrorResult and VoidResult are the only known subclasses of CallResult the logger.Error("ProcessMessage() encountered an unknown type of CallResult"); } //If a post-processing action has been specified in the call, invoke it now if (callResult.PostProcessingAction != null) { callResult.PostProcessingAction.Invoke(); } }
/// <summary> /// Tries to retrive a <see cref="CallResult"/> from the specified component for the speicified RemoteMethodCall using Reflection /// </summary> /// <param name="call">The RemoteMethodCall that needs to be answered</param> /// <param name="component">The component the RemoteMethodCall targeted</param> /// <returns>Returns the <see cref="CallResult"/> for the specified RemoteMethodCall</returns> protected virtual CallResult getCallResult(RemoteMethodCall call, IComponent component) { //search the component for a method taht can handle the call Dictionary <string, MethodInfo> callHandlers = new Dictionary <string, MethodInfo>(); foreach (MethodInfo mi in component.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)) { object[] attributes = mi.GetCustomAttributes(typeof(MethodCallHandlerAttribute), true); foreach (MethodCallHandlerAttribute attr in attributes) { if (!callHandlers.ContainsKey(attr.MethodName)) { callHandlers.Add(attr.MethodName, mi); } } } if (callHandlers.ContainsKey(call.MethodName)) { var attributes = callHandlers[call.MethodName].GetCustomAttributes(typeof(TrustLevelAttribute), true); //get the trust level from the method's TrustLevel attribute TrustLevelAttribute trustLevel; if (attributes.Any()) { trustLevel = (TrustLevelAttribute)attributes.First(); } else { //if no attribute could be found, fall back to the default (highest possible trustlevel) trustLevel = TrustLevelAttribute.MAX; } //check if the sender is authorized to make that method call if (trustLevel.RequiredTrustLevel > this.TrustLevel) { return(new ErrorResult(RemoteErrorCode.NotAuthorizedError)); } else { //invoke the call handler method and return it's result try { return((CallResult)callHandlers[call.MethodName].Invoke(component, call.Parameters.ToArray <object>())); } catch (Exception e) { if (e.GetType() == typeof(ArgumentException) || e.GetType() == typeof(TargetParameterCountException)) { return(new ErrorResult(RemoteErrorCode.InvalidParametersError)); } else { throw; } } } } //Invoked method could not be found => return ErrorResult else { return(new ErrorResult(RemoteErrorCode.MethodNotFoundError)); } }
/// <summary> /// Processes an incoming message (using the MessageProcessor for deserialization) /// </summary> /// <param name="message">The message to be processed</param> private void processMessage(ParsingResult message) { //parse the message XElement xmlMessage; RpcMessage msg = null; var body = message.Payload.ToStringUTF8(); try { //try to parse it as XML xmlMessage = XElement.Parse(body); switch (xmlMessage.Name.LocalName.ToLower()) { case "remotemethodcall": msg = new RemoteMethodCall(); break; case "remotemethodresponse": msg = new RemoteMethodResponse(); break; case "remoteerror": msg = new RemoteError(); break; } if (!msg.Validate(xmlMessage)) { logger.Error("Message from {0} could not be validated", this.Target); var error = new RemoteError(RemoteErrorCode.InvalidXmlError); sendMessage(error); return; } msg.Deserialize(xmlMessage); } //Catch XmlException and wrap it into a format exception (makes catching errors in the caller easier) catch (XmlException) { logger.Error("Could not parse message received from {0}", this.Target); RemoteError error = new RemoteError(RemoteErrorCode.InvalidXmlError); sendMessage(error); return; } //encryption is mandatory if TrustLevel is 2 or higher //if message was not encrypted it will result in a RemoteError and the connection will be reset if (this.TrustLevel >= 2 && !message.WasEncrypted && !(msg is RemoteMethodCall && (msg as RemoteMethodCall).MethodName == CoreMethods.SendResetNotice)) { RemoteError error = new RemoteError(RemoteErrorCode.EncryptionError); if (msg.CallId != Guid.Empty) { error.CallId = msg.CallId; } error.ComponentName = "Authentication"; //The RemoteError will not be encrypted WtlpClient.EncryptMessages = false; sendMessage(error); ResetConnection(); return; } if (msg is RemoteMethodCall) { //get the component responsible for handling the message string componentName = (msg as RpcMessage).ComponentName; var component = GetServerComponent(componentName); if (component == null) { //no component to handle the request was found => send a RemoteError as response and return RemoteError error = new RemoteError(RemoteErrorCode.ComponentNotFoundError); error.CallId = (msg as RemoteMethodCall).CallId; sendMessage(error); return; } var processingTask = new Task(delegate { processRemoteMethodCall(msg as RemoteMethodCall, component); }); processingTask.Start(); var heartBeatTask = new Task(delegate { var coreComponent = new CoreClientComponent(); coreComponent.ClientConnection = this; while (!processingTask.IsCompleted) { Thread.Sleep(25000); if (!processingTask.IsCompleted) { coreComponent.HeartbeatAsync(); } } }); heartBeatTask.Start(); } else if (msg is RemoteMethodResponse) { processRemoteMethodResponse(msg as RemoteMethodResponse); } else if (msg is RemoteError) { processRemoteError(msg as RemoteError); } else { logger.Error("ProcessMessage() encoutered an unknown type of Message"); sendMessage(new RemoteError(RemoteErrorCode.UnknownMessage)); } }
/// <summary> /// Processes an incoming message (using the MessageProcessor for deserialization) /// </summary> /// <param name="message">The message to be processed</param> private void processMessage(ParsingResult message) { //parse the message XElement xmlMessage; RpcMessage msg = null; var body = message.Payload.ToStringUTF8(); try { //try to parse it as XML xmlMessage = XElement.Parse(body); switch (xmlMessage.Name.LocalName.ToLower()) { case "remotemethodcall": msg = new RemoteMethodCall(); break; case "remotemethodresponse": msg = new RemoteMethodResponse(); break; case "remoteerror": msg = new RemoteError(); break; } if (!msg.Validate(xmlMessage)) { logger.Error("Message from {0} could not be validated", this.Target); var error = new RemoteError(RemoteErrorCode.InvalidXmlError); sendMessage(error); return; } msg.Deserialize(xmlMessage); } //Catch XmlException and wrap it into a format exception (makes catching errors in the caller easier) catch (XmlException) { logger.Error("Could not parse message received from {0}", this.Target); RemoteError error = new RemoteError(RemoteErrorCode.InvalidXmlError); sendMessage(error); return; } //encryption is mandatory if TrustLevel is 2 or higher //if message was not encrypted it will result in a RemoteError and the connection will be reset if (this.TrustLevel >= 2 && !message.WasEncrypted && !(msg is RemoteMethodCall && (msg as RemoteMethodCall).MethodName == CoreMethods.SendResetNotice)) { RemoteError error = new RemoteError(RemoteErrorCode.EncryptionError); if (msg.CallId != Guid.Empty) error.CallId = msg.CallId; error.ComponentName = "Authentication"; //The RemoteError will not be encrypted WtlpClient.EncryptMessages = false; sendMessage(error); ResetConnection(); return; } if (msg is RemoteMethodCall) { //get the component responsible for handling the message string componentName = (msg as RpcMessage).ComponentName; var component = GetServerComponent(componentName); if (component == null) { //no component to handle the request was found => send a RemoteError as response and return RemoteError error = new RemoteError(RemoteErrorCode.ComponentNotFoundError); error.CallId = (msg as RemoteMethodCall).CallId; sendMessage(error); return; } var processingTask = new Task(delegate { processRemoteMethodCall(msg as RemoteMethodCall, component); }); processingTask.Start(); var heartBeatTask = new Task(delegate { var coreComponent = new CoreClientComponent(); coreComponent.ClientConnection = this; while (!processingTask.IsCompleted) { Thread.Sleep(25000); if (!processingTask.IsCompleted) coreComponent.HeartbeatAsync(); } }); heartBeatTask.Start(); } else if (msg is RemoteMethodResponse) { processRemoteMethodResponse(msg as RemoteMethodResponse); } else if (msg is RemoteError) { processRemoteError(msg as RemoteError); } else { logger.Error("ProcessMessage() encoutered an unknown type of Message"); sendMessage(new RemoteError(RemoteErrorCode.UnknownMessage)); } }
protected object invokeRemoteMethod(string component, string name, object[] args, bool responseExpected) { var call = new RemoteMethodCall() { ComponentName = component, MethodName = name, Parameters = args.ToList<object>(), ResponseExpected = responseExpected }; //send the RemoteMethodCall sendMessage(call); //if a response is expected, wait for the recipient to send a response if (responseExpected) { //wait for the response using a EventWaitHandle if (!synchronousCalls_WaitHandles.ContainsKey(call.CallId)) synchronousCalls_WaitHandles.Add(call.CallId, new EventWaitHandle(false, EventResetMode.ManualReset)); synchronousCalls_WaitHandles[call.CallId].WaitOne(); //get the returned value from the cache object value = synchonousCalls_ValueCache[call.CallId]; //remove the value from value cache synchonousCalls_ValueCache.Remove(call.CallId); if (value is TimeoutException) { throw (value as TimeoutException); } else if (value is RemoteError) { throw new RemoteErrorException(value as RemoteError); } else { return value; } } else { return null; } }
/// <summary> /// Tries to retrive a <see cref="CallResult"/> from the specified component for the speicified RemoteMethodCall using Reflection /// </summary> /// <param name="call">The RemoteMethodCall that needs to be answered</param> /// <param name="component">The component the RemoteMethodCall targeted</param> /// <returns>Returns the <see cref="CallResult"/> for the specified RemoteMethodCall</returns> protected virtual CallResult getCallResult(RemoteMethodCall call, IComponent component) { //search the component for a method taht can handle the call Dictionary<string, MethodInfo> callHandlers = new Dictionary<string, MethodInfo>(); foreach (MethodInfo mi in component.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)) { object[] attributes = mi.GetCustomAttributes(typeof(MethodCallHandlerAttribute), true); foreach (MethodCallHandlerAttribute attr in attributes) { if (!callHandlers.ContainsKey(attr.MethodName)) { callHandlers.Add(attr.MethodName, mi); } } } if (callHandlers.ContainsKey(call.MethodName)) { var attributes = callHandlers[call.MethodName].GetCustomAttributes(typeof(TrustLevelAttribute), true); //get the trust level from the method's TrustLevel attribute TrustLevelAttribute trustLevel; if (attributes.Any()) trustLevel = (TrustLevelAttribute)attributes.First(); else //if no attribute could be found, fall back to the default (highest possible trustlevel) trustLevel = TrustLevelAttribute.MAX; //check if the sender is authorized to make that method call if (trustLevel.RequiredTrustLevel > this.TrustLevel) { return new ErrorResult(RemoteErrorCode.NotAuthorizedError); } else { //invoke the call handler method and return it's result try { return (CallResult)callHandlers[call.MethodName].Invoke(component, call.Parameters.ToArray<object>()); } catch (Exception e) { if (e.GetType() == typeof(ArgumentException) || e.GetType() == typeof(TargetParameterCountException)) { return new ErrorResult(RemoteErrorCode.InvalidParametersError); } else { throw; } } } } //Invoked method could not be found => return ErrorResult else { return new ErrorResult(RemoteErrorCode.MethodNotFoundError); } }
public void TestRemoteMethodCallSerialization_NoParameters() { var call = new RemoteMethodCall(); call.ComponentName = "component"; call.CallId = Guid.NewGuid(); call.MethodName = "sampleMethod"; var xml = call.Serialize(); var roundtrip = new RemoteMethodCall(); Assert.IsTrue(roundtrip.Validate(xml)); roundtrip.Deserialize(xml); Assert.IsNotNull(roundtrip); Assert.AreEqual<Guid>(call.CallId, roundtrip.CallId); Assert.AreEqual<string>(call.ComponentName, roundtrip.ComponentName); Assert.AreEqual<string>(call.MethodName, roundtrip.MethodName); }