1 //
2 // Copyright (c) 2010-2023 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Reflection;
11 using System.Text;
12 using System.Text.RegularExpressions;
13 using CookComputing.XmlRpc;
14 using Antmicro.Renode.Core;
15 
16 namespace Antmicro.Renode.RobotFramework
17 {
18     internal class XmlRpcServer : XmlRpcListenerService, IDisposable
19     {
XmlRpcServer(KeywordManager keywordManager)20         public XmlRpcServer(KeywordManager keywordManager)
21         {
22             this.keywordManager = keywordManager;
23         }
24 
25         [XmlRpcMethod("get_keyword_names")]
GetKeywordNames()26         public string[] GetKeywordNames()
27         {
28             return keywordManager.GetRegisteredKeywords();
29         }
30 
31         [XmlRpcMethod("run_keyword")]
RunKeyword(string keywordName, object[] arguments)32         public XmlRpcStruct RunKeyword(string keywordName, object[] arguments)
33         {
34             var result = new XmlRpcStruct();
35             KeywordManager.KeywordLookupResult lookupResult = default(KeywordManager.KeywordLookupResult);
36             try
37             {
38                 lookupResult = keywordManager.TryExecuteKeyword(keywordName, arguments, out var keywordResult);
39                 if(lookupResult == KeywordManager.KeywordLookupResult.Success)
40                 {
41                     if(keywordResult != null)
42                     {
43                         if(keywordResult is string resultString)
44                         {
45                             keywordResult = Regex.Replace(resultString, "(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]", "", RegexOptions.Compiled);
46                         }
47                         else if(keywordResult is IList<object> list)
48                         {
49                             keywordResult = list.ToArray();
50                         }
51                         result.Add(KeywordResultValue, keywordResult);
52                     }
53                     result.Add(KeywordResultStatus, KeywordResultPass);
54                 }
55             }
56             catch(Exception e)
57             {
58                 result.Clear();
59 
60                 result.Add(KeywordResultStatus, KeywordResultFail);
61                 result.Add(KeywordResultError, BuildRecursiveValueFromException(e, ex => ex.Message).StripNonSafeCharacters());
62                 result.Add(KeywordResultTraceback, BuildRecursiveValueFromException(e, ex => ex.StackTrace).StripNonSafeCharacters());
63             }
64             if(lookupResult == KeywordManager.KeywordLookupResult.KeywordNotFound)
65             {
66                 throw new XmlRpcFaultException(1, string.Format("Keyword \"{0}\" not found", keywordName));
67             }
68             if(lookupResult == KeywordManager.KeywordLookupResult.ArgumentsNotMatched)
69             {
70                 throw new XmlRpcFaultException(2, string.Format("Arguments types do not match any available keyword \"{0}\" : [{1}]", keywordName, string.Join(", ", arguments)));
71             }
72             return result;
73         }
74 
75         [XmlRpcMethod("stop_remote_server")]
Dispose()76         public void Dispose()
77         {
78             var robotFrontendEngine = (RobotFrameworkEngine)ObjectCreator.Instance.GetSurrogate(typeof(RobotFrameworkEngine));
79             robotFrontendEngine.Shutdown();
80         }
81 
BuildRecursiveValueFromException(Exception e, Func<Exception, string> generator)82         private static string BuildRecursiveValueFromException(Exception e, Func<Exception, string> generator)
83         {
84             var result = new StringBuilder();
85             while(e != null)
86             {
87                 if(!(e is TargetInvocationException))
88                 {
89                     // TargetInvocationException is only a container, it does not provide valuable information
90                     result.AppendFormat("{0}: {1}\n", e.GetType().Name, generator(e));
91                 }
92                 e = e.InnerException;
93             }
94 
95             return result.ToString();
96         }
97 
98         private readonly KeywordManager keywordManager;
99 
100         private const string KeywordResultValue = "return";
101         private const string KeywordResultStatus = "status";
102         private const string KeywordResultError = "error";
103         private const string KeywordResultTraceback = "traceback";
104 
105         private const string KeywordResultPass = "PASS";
106         private const string KeywordResultFail = "FAIL";
107     }
108 }
109 
110