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.Text; 11 using Antmicro.Renode.Core; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Network; 14 using Antmicro.Renode.Utilities; 15 16 namespace Antmicro.Renode.Peripherals.Network 17 { 18 public static class DA16200NetworkExtension 19 { CreateDA16200Network(this Emulation emulation, string name)20 public static void CreateDA16200Network(this Emulation emulation, string name) 21 { 22 emulation.ExternalsManager.AddExternal(new BasicNetwork<byte[], DA16200.NetworkAddress>(name), name); 23 } 24 } 25 26 public class DA16200 : AtCommandModem, IBasicNetworkNode<byte[], DA16200.NetworkAddress> 27 { DA16200(IMachine machine)28 public DA16200(IMachine machine) : base(machine) 29 { 30 sync = new object(); 31 address = new NetworkAddress(GenerateRandomAddress()); 32 dataModeState = new DataModeState(this); 33 } 34 PassthroughWriteChar(byte value)35 public override void PassthroughWriteChar(byte value) 36 { 37 if(!dataModeState.TryConsumeCharacter(value)) 38 { 39 dataModeState.Reset(); 40 PassthroughMode = false; 41 } 42 } 43 Reset()44 public override void Reset() 45 { 46 base.Reset(); 47 udpSendAddress = null; 48 udpSendPort = null; 49 dataModeState?.Reset(); 50 51 for(var i = 0; i < connections.Length; i++) 52 { 53 connections[i] = null; 54 } 55 } 56 WriteChar(byte value)57 public override void WriteChar(byte value) 58 { 59 if(!Enabled) 60 { 61 this.Log(LogLevel.Warning, "Modem is not enabled, ignoring incoming byte 0x{0:x2}", value); 62 return; 63 } 64 65 if(value == Escape && !PassthroughMode) 66 { 67 PassthroughMode = true; 68 return; 69 } 70 71 base.WriteChar(value); 72 } 73 ReceiveData(byte[] data, NetworkAddress source, NetworkAddress destination)74 public void ReceiveData(byte[] data, NetworkAddress source, NetworkAddress destination) 75 { 76 if(!TryGetConnectionByPort(destination.Port, out var connection)) 77 { 78 this.ErrorLog("No connection for local port {0}", destination.Port); 79 return; 80 } 81 82 var commandName = connection.Type == ConnectionType.UDP ? "TRDUS" : "TRDTC"; 83 var dataPrefix = Encoding.ASCII.GetBytes($"+{commandName}:{connection.ID},{source.Address},{source.Port},{data.Length},"); 84 var response = dataPrefix.Concat(data).ToArray(); 85 ExecuteWithDelay(() => SendBytes(response), DataResponseDelayMilliseconds); 86 } 87 88 public NetworkAddress NodeAddress => address; 89 90 public event BasicNetworkSendDataDelegate<byte[], NetworkAddress> TrySendData; 91 92 public ulong DataResponseDelayMilliseconds { get; set; } = 50; 93 94 public string IpAddress 95 { 96 get => address.Address; 97 set => address = new NetworkAddress(value); 98 } 99 SendData(int connectionId, NetworkAddress destination, byte[] data)100 private void SendData(int connectionId, NetworkAddress destination, byte[] data) 101 { 102 if(!TryGetConnection(connectionId, out var connection)) 103 { 104 this.ErrorLog("Invalid connection ID: {0}", connection); 105 SendResponse(Error); 106 return; 107 } 108 109 if(TrySendData == null) 110 { 111 this.WarningLog("Attempted to send data from a device not connected to a network"); 112 SendResponse(Error); 113 return; 114 } 115 116 switch(connection.Type) 117 { 118 case ConnectionType.UDP: 119 if(destination.Address == "0") 120 { 121 if(udpSendAddress == "") 122 { 123 this.ErrorLog("UPD send address was not specified. Data will not be send"); 124 SendResponse(Error); 125 return; 126 } 127 destination = destination.WithAddress(udpSendAddress); 128 } 129 if(destination.Port == 0) 130 { 131 if(!udpSendPort.HasValue) 132 { 133 this.ErrorLog("UPD send port was not specified. Data will not be send"); 134 SendResponse(Error); 135 return; 136 } 137 destination = destination.WithPort(udpSendPort.Value); 138 } 139 140 var source = address.WithPort(connection.LocalPort); 141 if(!TrySendData(data, source, destination)) 142 { 143 SendResponse(Error); 144 return; 145 } 146 147 SendResponse(Ok); 148 break; 149 default: 150 this.WarningLog("Connection type {0} is not supported", connection.Type); 151 break; 152 } 153 } 154 TryGetNewConnectionNumber(out int connectionId, params int[] reservedNumbers)155 private bool TryGetNewConnectionNumber(out int connectionId, params int[] reservedNumbers) 156 { 157 lock(sync) 158 { 159 for(var i = 0; i < connections.Length; i++) 160 { 161 if(connections[i] == null && !reservedNumbers.Any(reserved => reserved == i)) 162 { 163 connectionId = i; 164 return true; 165 } 166 } 167 168 connectionId = -1; 169 return false; 170 } 171 } 172 IsPortFree(ushort port)173 private bool IsPortFree(ushort port) 174 { 175 return !TryGetConnectionByPort(port, out _); 176 } 177 TryGetConnection(int connectionId, out Connection connection)178 private bool TryGetConnection(int connectionId, out Connection connection) 179 { 180 lock(sync) 181 { 182 if(connectionId < 0 || connectionId >= connections.Length) 183 { 184 connection = null; 185 return false; 186 } 187 188 connection = connections[connectionId]; 189 return connection != null; 190 } 191 } 192 TryGetConnectionByPort(ushort localPort, out Connection connection)193 private bool TryGetConnectionByPort(ushort localPort, out Connection connection) 194 { 195 lock(sync) 196 { 197 foreach(var conn in connections) 198 { 199 if(conn == null) 200 { 201 continue; 202 } 203 204 if(conn.LocalPort == localPort) 205 { 206 connection = conn; 207 return true; 208 } 209 } 210 211 connection = null; 212 return false; 213 } 214 } 215 GenerateRandomAddress()216 private string GenerateRandomAddress() 217 { 218 var rng = EmulationManager.Instance.CurrentEmulation.RandomGenerator; 219 var components = new byte[4]{ 192, 168, 0, 0 }; 220 221 for(var i = 2; i < components.Length; i++) 222 { 223 components[i] = (byte)rng.Next(1, byte.MaxValue); 224 } 225 return string.Join(".", components); 226 } 227 228 // Commands 229 230 [AtCommand("ATZ")] Atz()231 private Response Atz() 232 { 233 return Ok.WithParameters( 234 "Display result off", 235 "Echo {0}".FormatWith(EchoEnabled ? "on" : "off") 236 ); 237 } 238 239 [AtCommand("AT+WFMODE", CommandType.Write)] WfModeWrite(WiFiMode mode)240 private Response WfModeWrite(WiFiMode mode) 241 { 242 if(mode == WiFiMode.SoftAccessPoint) 243 { 244 this.WarningLog("Soft Access Point mode is currently not supported"); 245 return Error; 246 } 247 248 this.DebugLog("AT+WFMODE: WiFi Mode = {0}", mode); 249 return Ok; 250 } 251 252 [AtCommand("AT+RESTART")] Restart()253 private Response Restart() 254 { 255 ExecuteWithDelay(() => SendString("+INIT:DONE,0")); 256 return Ok; 257 } 258 259 [AtCommand("AT+WFCC", CommandType.Write)] WfccWrite(string countryCode)260 private Response WfccWrite(string countryCode) 261 { 262 this.DebugLog("AT+WFCC: Country code set to '{0}'", countryCode); 263 return Ok; 264 } 265 266 [AtCommand("AT+WFJAP", CommandType.Write)] WfjapWrite(string ssid, SecurityProtocol securityProtocol, int keyIndex, string password)267 private Response WfjapWrite(string ssid, SecurityProtocol securityProtocol, int keyIndex, string password) 268 { 269 this.DebugLog("AT+WFJAP: Connecting to '{0}' ({1}, index: {2}) with password '{3}'", ssid, securityProtocol, keyIndex, password); 270 return Ok; 271 } 272 273 // Socket commands 274 275 [AtCommand("AT+TRTALL")] Trall()276 private Response Trall() 277 { 278 lock(sync) 279 { 280 for(var i = 0; i < connections.Length; i++) 281 { 282 connections[i] = null; 283 } 284 return Ok; 285 } 286 } 287 288 [AtCommand("AT+TRTRM", CommandType.Write)] Trtrm(int connectionId, string ip = null, ushort? port = null)289 private Response Trtrm(int connectionId, string ip = null, ushort? port = null) 290 { 291 lock(sync) 292 { 293 if(!TryGetConnection(connectionId, out var _)) 294 { 295 this.ErrorLog("AT+TRTRM: Invalid connection id: {0}", connectionId); 296 return Error; 297 } 298 299 connections[connectionId] = null; 300 return Ok; 301 } 302 } 303 304 [AtCommand("AT+TRUSE", CommandType.Write)] Truse(ushort localPort)305 private Response Truse(ushort localPort) 306 { 307 lock(sync) 308 { 309 if(!IsPortFree(localPort)) 310 { 311 this.WarningLog("AT+TRUSE: Port {0} is already in use", localPort); 312 return Error; 313 } 314 315 if(!TryGetNewConnectionNumber(out var connectionNumber, DefaultTCPServerConnection, DefaultTCPClientConnection)) 316 { 317 return Error; 318 } 319 320 var connection = new Connection(this, connectionNumber, ConnectionType.UDP, localPort); 321 connections[connectionNumber] = connection; 322 323 return Ok.WithParameters($"+TRUSE:{connectionNumber}"); 324 } 325 } 326 327 [AtCommand("AT+TRUR", CommandType.Write)] Trur(string address, ushort port)328 private Response Trur(string address, ushort port) 329 { 330 udpSendAddress = address; 331 udpSendPort = port; 332 return Ok.WithParameters($"+TRUR:{DefaultUDPConnection}"); 333 } 334 335 private readonly object sync; 336 private readonly Connection[] connections = new Connection[MaxConnections]; 337 private readonly DataModeState dataModeState; 338 private NetworkAddress address; 339 340 private string udpSendAddress; 341 private ushort? udpSendPort; 342 343 // We assume that there is 10 maximum connections possible, because when 344 // sending data there is no separator between the connection id and the length 345 // of the message. When chenging this number the connection id decoding logic 346 // in DataModeState:TryConsumeCharacter will also need to be updated. 347 private const int MaxConnections = 10; 348 private const int DefaultTCPServerConnection = 0; 349 private const int DefaultTCPClientConnection = 1; 350 private const int DefaultUDPConnection = 2; 351 352 public class NetworkAddress 353 { NetworkAddress(string address, ushort port = 0)354 public NetworkAddress(string address, ushort port = 0) 355 { 356 Address = address; 357 Port = port; 358 } 359 WithAddress(string address)360 public NetworkAddress WithAddress(string address) 361 { 362 return new NetworkAddress(address, Port); 363 } 364 WithPort(ushort port)365 public NetworkAddress WithPort(ushort port) 366 { 367 return new NetworkAddress(Address, port); 368 } 369 Equals(object obj)370 public override bool Equals(object obj) 371 { 372 if(ReferenceEquals(this, obj)) 373 { 374 return true; 375 } 376 377 if(!(obj is NetworkAddress address)) 378 { 379 return false; 380 } 381 382 return address.Address == Address; 383 } 384 GetHashCode()385 public override int GetHashCode() 386 { 387 return Address.GetHashCode(); 388 } 389 ToString()390 public override string ToString() 391 { 392 return $"{Address}:{Port}"; 393 } 394 395 public string Address { get; } 396 public ushort Port { get; } 397 } 398 399 private class Connection 400 { Connection(DA16200 owner, int connectionId, ConnectionType connectionType, ushort localPort)401 public Connection(DA16200 owner, int connectionId, ConnectionType connectionType, ushort localPort) 402 { 403 this.owner = owner; 404 ID = connectionId; 405 Type = connectionType; 406 LocalPort = localPort; 407 } 408 409 public ConnectionType Type { get; } 410 public int ID { get; } 411 public ushort LocalPort { get; } 412 413 private readonly DA16200 owner; 414 } 415 416 private class DataModeState 417 { DataModeState(DA16200 owner)418 public DataModeState(DA16200 owner) 419 { 420 this.owner = owner; 421 dataBuffer = new List<byte>(); 422 } 423 TryConsumeCharacter(byte value)424 public bool TryConsumeCharacter(byte value) 425 { 426 if(bytesToSkip > 0) 427 { 428 bytesToSkip--; 429 return true; 430 } 431 432 if(!dataMode.HasValue) 433 { 434 if(Enum.IsDefined(typeof(DataMode), (DataMode)value)) 435 { 436 dataMode = (DataMode)value; 437 return true; 438 } 439 440 owner.ErrorLog("Invalid data mode value: {0}", value); 441 return false; 442 } 443 444 if(!connectionId.HasValue) 445 { 446 var cid = (int)(value - '0'); 447 if(cid < 0 || cid > 9) 448 { 449 owner.ErrorLog("Invalid connection ID byte: 0x{0:X}", value); 450 return false; 451 } 452 453 connectionId = cid; 454 // Skip ',' 455 switch(dataMode) 456 { 457 case DataMode.H: 458 case DataMode.M: 459 bytesToSkip = 1; 460 break; 461 } 462 return true; 463 } 464 465 if(!dataLength.HasValue) 466 { 467 return AggregateAndTryParseInt(value, ref dataLength); 468 } 469 470 if(remoteAddress == null) 471 { 472 if(value == ',') 473 { 474 remoteAddress = Encoding.ASCII.GetString(dataBuffer.ToArray()); 475 dataBuffer.Clear(); 476 return true; 477 } 478 else 479 { 480 dataBuffer.Add(value); 481 return true; 482 } 483 } 484 485 if(!remotePort.HasValue) 486 { 487 int? portInt = null; 488 var result = AggregateAndTryParseInt(value, ref portInt); 489 if(portInt.HasValue) 490 { 491 remotePort = (ushort)portInt; 492 } 493 return result; 494 } 495 496 if(dataLength == 0) 497 { 498 if(value == '\r' || value == '\n') 499 { 500 dataToSend = dataBuffer.ToArray(); 501 dataBuffer.Clear(); 502 } 503 else 504 { 505 dataBuffer.Add(value); 506 return true; 507 } 508 } 509 else 510 { 511 dataBuffer.Add(value); 512 if(dataBuffer.Count != dataLength) 513 { 514 return true; 515 } 516 dataToSend = dataBuffer.ToArray(); 517 dataBuffer.Clear(); 518 } 519 520 owner.SendData((int)connectionId, new NetworkAddress(remoteAddress, remotePort.Value), dataToSend); 521 return false; 522 } 523 Reset()524 public void Reset() 525 { 526 dataMode = null; 527 connectionId = null; 528 dataLength = null; 529 remoteAddress = null; 530 remotePort = null; 531 dataToSend = null; 532 533 dataBuffer.Clear(); 534 } 535 AggregateAndTryParseInt(byte value, ref int? number)536 private bool AggregateAndTryParseInt(byte value, ref int? number) 537 { 538 if(value == ',') 539 { 540 var str = Encoding.ASCII.GetString(dataBuffer.ToArray()); 541 dataBuffer.Clear(); 542 543 if(!int.TryParse(str, out var parsed)) 544 { 545 owner.ErrorLog("String '{0}' is not a valid number", str); 546 return false; 547 } 548 549 number = parsed; 550 return true; 551 } 552 else 553 { 554 dataBuffer.Add(value); 555 return true; 556 } 557 } 558 559 private int bytesToSkip; 560 private DataMode? dataMode; 561 private int? connectionId; 562 private int? dataLength; 563 private string remoteAddress; 564 private ushort? remotePort; 565 private byte[] dataToSend; 566 567 private readonly List<byte> dataBuffer; 568 569 private readonly DA16200 owner; 570 571 private enum DataMode : byte 572 { 573 S = (byte)'S', 574 M = (byte)'M', 575 H = (byte)'H', 576 } 577 } 578 579 private enum WiFiMode 580 { 581 Station = 0, 582 SoftAccessPoint = 1, 583 } 584 585 private enum SecurityProtocol 586 { 587 Open = 0, 588 WEP = 1, 589 WPA = 2, 590 WPA2 = 3, 591 WPA_WPA2 = 4, 592 WPA3_OWE = 5, 593 WPA3_SAE = 6, 594 WPA2_RSN_WPA3_SAE = 7, 595 } 596 597 private enum ConnectionType 598 { 599 TCPServer, 600 TCPClient, 601 UDP, 602 } 603 } 604 } 605