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