1 //
2 // Copyright (c) 2010-2024 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.Net.Sockets;
11 using System.Text;
12 
13 using Antmicro.Renode.Core;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Renode.Network.ExternalControl;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Utilities.Packets;
19 
20 namespace Antmicro.Renode.Network
21 {
22     public static class ExternalControlServerExtensions
23     {
CreateExternalControlServer(this Emulation emulation, string name, int port)24         public static void CreateExternalControlServer(this Emulation emulation, string name, int port)
25         {
26             emulation.ExternalsManager.AddExternal(new ExternalControlServer(port), name);
27         }
28     }
29 
30     public class ExternalControlServer : IDisposable, IExternal, IEmulationElement
31     {
ExternalControlServer(int port)32         public ExternalControlServer(int port)
33         {
34             socketServerProvider.BufferSize = 0x10;
35 
36             commandHandlers = new CommandHandlerCollection();
37             commandHandlers.EventReported += SendEventResponse;
38             commandHandlers.Register(new RunFor(this));
39             commandHandlers.Register(new GetTime(this));
40             commandHandlers.Register(new ADC(this));
41             commandHandlers.Register(new GPIOPort(this));
42 
43             var getMachineHandler = new GetMachine(this);
44             Machines = getMachineHandler;
45             commandHandlers.Register(getMachineHandler);
46 
47             socketServerProvider.ConnectionAccepted += delegate
48             {
49                 lock(locker)
50                 {
51                     if(state == State.Disposed)
52                     {
53                         return;
54                     }
55                     this.Log(LogLevel.Noisy, "State change: {0} -> {1}", state, State.Handshake);
56                     state = State.Handshake;
57                 }
58                 this.Log(LogLevel.Debug, "Connection established");
59             };
60             socketServerProvider.ConnectionClosed += delegate
61             {
62                 lock(locker)
63                 {
64                     if(state == State.Disposed)
65                     {
66                         return;
67                     }
68                     commandHandlers.ClearActivation();
69                     this.Log(LogLevel.Noisy, "State change: {0} -> {1}", state, State.NotConnected);
70                     state = State.NotConnected;
71                 }
72                 this.Log(LogLevel.Debug, "Connection closed");
73             };
74 
75             socketServerProvider.DataBlockReceived += OnBytesWritten;
76             socketServerProvider.Start(port);
77 
78             this.Log(LogLevel.Info, "{0}: Listening on port {1}", nameof(ExternalControlServer), port);
79         }
80 
Dispose()81         public void Dispose()
82         {
83             lock(locker)
84             {
85                 this.Log(LogLevel.Noisy, "State change: {0} -> {1}", state, State.Disposed);
86                 state = State.Disposed;
87             }
88             commandHandlers.Dispose();
89             socketServerProvider.Stop();
90         }
91 
92         public IMachineContainer Machines { get; }
93 
IsHeaderValid()94         private bool IsHeaderValid()
95         {
96             if(!header.HasValue)
97             {
98                 return false;
99             }
100 
101             if(header.Value.Magic != Magic)
102             {
103                 return false;
104             }
105 
106             if(header.Value.dataSize > (uint)Int32.MaxValue)
107             {
108                 return false;
109             }
110 
111             return true;
112         }
113 
TryActivateCommands(List<byte> data)114         private bool TryActivateCommands(List<byte> data)
115         {
116             foreach(var pair in data.Split(2))
117             {
118                 var command = (Command)pair[0];
119                 var version = pair[1];
120 
121                 if(commandHandlers.TryActivate(command, version))
122                 {
123                     this.Log(LogLevel.Noisy, "{0} (version 0x{1:X}) activated", command, version);
124                     continue;
125                 }
126 
127                 var message = commandHandlers.TryGetVersion(command, out var expectedVersion)
128                     ? $"Encountered invalid version (0x{version:X}) for {command}, expected 0x{expectedVersion:X}"
129                     : $"Encountered unknown command 0x{command:X}";
130 
131                 this.Log(LogLevel.Error, message);
132                 SendResponse(Response.FatalError(message));
133                 return false;
134             }
135 
136             return true;
137         }
138 
StepReceiveFiniteStateMachine(State currentState)139         private State? StepReceiveFiniteStateMachine(State currentState)
140         {
141             switch(currentState)
142             {
143                 case State.Handshake:
144                     if(buffer.Count < HandshakeHeaderSize)
145                     {
146                         return null;
147                     }
148                     commandsToActivate = BitConverter.ToUInt16(buffer.GetRange(0, HandshakeHeaderSize).ToArray(), 0);
149                     buffer.RemoveRange(0, HandshakeHeaderSize);
150                     this.Log(LogLevel.Noisy, "{0} commands to activate", commandsToActivate);
151 
152                     return State.WaitingForHandshakeData;
153 
154                 case State.WaitingForHandshakeData:
155                     if(commandsToActivate > 0 && buffer.Count >= 2)
156                     {
157                         var toActivate = (int)Math.Min(commandsToActivate, buffer.Count / 2);
158 
159                         lock(locker)
160                         {
161                             AssertNotDisposed();
162                             if(!TryActivateCommands(buffer.GetRange(0, toActivate * 2)))
163                             {
164                                 socketServerProvider.Stop();
165                                 return null;
166                             }
167                         }
168 
169                         buffer.RemoveRange(0, toActivate * 2);
170                         commandsToActivate -= toActivate;
171                     }
172 
173                     if(commandsToActivate > 0)
174                     {
175                         return null;
176                     }
177 
178                     SendResponse(Response.SuccessfulHandshake());
179 
180                     return State.WaitingForHeader;
181 
182                 case State.WaitingForHeader:
183                     if(buffer.Count < HeaderSize)
184                     {
185                         return null;
186                     }
187 
188                     header = Packet.Decode<ExternalControlProtocolHeader>(buffer);
189                     if(!IsHeaderValid())
190                     {
191                         var message = $"Encountered invalid header: {header}";
192                         this.Log(LogLevel.Error, message);
193                         lock(locker)
194                         {
195                             SendResponse(Response.FatalError(message));
196                             socketServerProvider.Stop();
197                         }
198                         return null;
199                     }
200 
201                     this.Log(LogLevel.Noisy, "Received header: {0}", header);
202                     buffer.RemoveRange(0, HeaderSize);
203 
204                     return State.WaitingForData;
205 
206                 case State.WaitingForData:
207                     if(buffer.Count < header.Value.dataSize)
208                     {
209                         return null;
210                     }
211 
212                     TryHandleCommand(out var response, header.Value.command, buffer.GetRange(0, (int)header.Value.dataSize));
213 
214                     buffer.RemoveRange(0, (int)header.Value.dataSize);
215                     header = null;
216 
217                     SendResponse(response);
218 
219                     return State.WaitingForHeader;
220 
221                 case State.NotConnected:
222                 default:
223                     throw new Exception("Unreachable");
224             }
225         }
226 
OnBytesWritten(byte[] data)227         private void OnBytesWritten(byte[] data)
228         {
229             buffer.AddRange(data);
230             this.Log(LogLevel.Noisy, "Received new data: {0}", Misc.PrettyPrintCollectionHex(data));
231             this.Log(LogLevel.Debug, "Current buffer: {0}", Misc.PrettyPrintCollectionHex(buffer));
232 
233             var lockedState = state;
234             while(lockedState != State.Disposed && lockedState != State.NotConnected)
235             {
236                 var nextState = (State?)null;
237                 try
238                 {
239                     nextState = StepReceiveFiniteStateMachine(lockedState);
240                 }
241                 catch(ServerDisposedException)
242                 {
243                     return;
244                 }
245 
246                 lock(locker)
247                 {
248                     if(!nextState.HasValue || state == State.Disposed || state == State.NotConnected)
249                     {
250                         return;
251                     }
252                     this.Log(LogLevel.Noisy, "State change: {0} -> {1}", state, nextState.Value);
253                     state = nextState.Value;
254                     lockedState = state;
255                 }
256             }
257         }
258 
TryHandleCommand(out Response response, Command command, List<byte> data)259         private bool TryHandleCommand(out Response response, Command command, List<byte> data)
260         {
261             ICommand commandHandler;
262             lock(locker)
263             {
264                 AssertNotDisposed();
265                 commandHandler = commandHandlers.GetHandler(command);
266             }
267 
268             if(commandHandler == null)
269             {
270                 response = Response.InvalidCommand(command);
271                 return true;
272             }
273 
274             try
275             {
276                 // Create an ICanReportEvents interface or something similar if
277                 // we ever have more command handlers that need to do this.
278                 if(commandHandler is RunFor)
279                 {
280                     EventsEnabled = true;
281                 }
282 
283                 response = commandHandler.Invoke(data);
284                 return true;
285             }
286             catch(RecoverableException e)
287             {
288                 this.Log(LogLevel.Error, "{0} command error: {1}", command, e.Message);
289                 response = Response.CommandFailed(command, e.Message);
290                 return false;
291             }
292             finally
293             {
294                 if(commandHandler is RunFor)
295                 {
296                     EventsEnabled = false;
297                 }
298             }
299         }
300 
SendEventResponse(Response response)301         private void SendEventResponse(Response response)
302         {
303             if(EventsEnabled)
304             {
305                 SendResponse(response);
306             }
307         }
308 
SendResponse(Response response)309         private void SendResponse(Response response)
310         {
311             var bytes = response.GetBytes();
312             lock(locker)
313             {
314                 AssertNotDisposed();
315                 socketServerProvider.Send(bytes);
316             }
317             this.Log(LogLevel.Debug, "Response sent: {0}", response);
318             this.Log(LogLevel.Noisy, "Bytes sent: {0}", Misc.PrettyPrintCollectionHex(bytes));
319         }
320 
AssertNotDisposed()321         private void AssertNotDisposed()
322         {
323             if(state == State.Disposed)
324             {
325                 throw new ServerDisposedException();
326             }
327         }
328 
329         public bool EventsEnabled = false;
330 
331         private State state = State.NotConnected;
332         private int commandsToActivate = 0;
333         private ExternalControlProtocolHeader? header;
334 
335         private readonly List<byte> buffer = new List<byte>();
336         private readonly CommandHandlerCollection commandHandlers;
337         private readonly SocketServerProvider socketServerProvider = new SocketServerProvider(emitConfigBytes: false);
338 
339         private readonly object locker = new object();
340 
341         private const int HeaderSize = 7;
342         private const int HandshakeHeaderSize = 2;
343         private const string Magic = "RE";
344 
345         [LeastSignificantByteFirst]
346         private struct ExternalControlProtocolHeader
347         {
ToStringAntmicro.Renode.Network.ExternalControlServer.ExternalControlProtocolHeader348             public override string ToString()
349             {
350                 return $"{{ magic: {Misc.PrettyPrintCollectionHex(magic)} ({Magic}), command: 0x{(byte)command} ({command}), dataSize: {dataSize} }}";
351             }
352 
353             public string Magic
354             {
355                 get
356                 {
357                     try
358                     {
359                         return Encoding.ASCII.GetString(magic);
360                     }
361                     catch
362                     {
363                         return "<invalid>";
364                     }
365                 }
366             }
367 
368 #pragma warning disable 649
369             [PacketField, Width(2)]
370             public byte[] magic;
371             [PacketField, Width(8)]
372             public Command command;
373             [PacketField, Width(32)]
374             public uint dataSize;
375 #pragma warning restore 649
376         }
377 
378         private class CommandHandlerCollection : IDisposable
379         {
CommandHandlerCollection()380             public CommandHandlerCollection()
381             {
382                 commandHandlers = new Dictionary<Command, ICommand>();
383                 activeCommandHandlers = new Dictionary<Command, ICommand>();
384             }
385 
Dispose()386             public void Dispose()
387             {
388                 activeCommandHandlers.Clear();
389                 foreach(var command in commandHandlers.Values.OfType<IDisposable>())
390                 {
391                     command.Dispose();
392                 }
393                 commandHandlers.Clear();
394             }
395 
Register(ICommand command)396             public void Register(ICommand command)
397             {
398                 commandHandlers.Add(command.Identifier, command);
399                 if(command is IHasEvents commandWithEvents)
400                 {
401                     commandWithEvents.EventReported += this.EventReported;
402                 }
403             }
404 
ClearActivation()405             public void ClearActivation()
406             {
407                 activeCommandHandlers.Clear();
408             }
409 
TryActivate(Command id, byte version)410             public bool TryActivate(Command id, byte version)
411             {
412                 if(activeCommandHandlers.ContainsKey(id))
413                 {
414                     return false;
415                 }
416 
417                 if(!commandHandlers.TryGetValue(id, out var command))
418                 {
419                     return false;
420                 }
421 
422                 if(command.Version != version)
423                 {
424                     return false;
425                 }
426 
427                 activeCommandHandlers.Add(command.Identifier, command);
428                 return true;
429             }
430 
GetHandler(Command id)431             public ICommand GetHandler(Command id)
432             {
433                 if(!activeCommandHandlers.TryGetValue(id, out var command))
434                 {
435                     return null;;
436                 }
437                 return command;
438             }
439 
TryGetVersion(Command id, out byte version)440             public bool TryGetVersion(Command id, out byte version)
441             {
442                 if(!commandHandlers.TryGetValue(id, out var command))
443                 {
444                     version = default(byte);
445                     return false;
446                 }
447 
448                 version = command.Version;
449                 return true;
450             }
451 
452             public event Action<Response> EventReported;
453 
454             private readonly Dictionary<Command, ICommand> commandHandlers;
455             private readonly Dictionary<Command, ICommand> activeCommandHandlers;
456         }
457 
458         private class ServerDisposedException : RecoverableException
459         {
ServerDisposedException()460             public ServerDisposedException()
461                 : base()
462             {
463             }
464         }
465 
466         private enum State
467         {
468             NotConnected,
469             Handshake,
470             WaitingForHandshakeData,
471             WaitingForHeader,
472             WaitingForData,
473             Disposed,
474         }
475     }
476 }
477