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