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.Text;
9 using System.Threading;
10 using System.Collections.Concurrent;
11 using System.Globalization;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Network;
14 using Antmicro.Renode.Peripherals;
15 using Antmicro.Renode.Peripherals.Network;
16 using Antmicro.Renode.Peripherals.Wireless;
17 using Antmicro.Renode.Time;
18 using Antmicro.Renode.Exceptions;
19 using Antmicro.Renode.Utilities;
20 
21 namespace Antmicro.Renode.Testing
22 {
23     public static class NetworkInterfaceTesterExtensions
24     {
CreateNetworkInterfaceTester(this Emulation emulation, string name, IMACInterface iface)25         public static void CreateNetworkInterfaceTester(this Emulation emulation, string name, IMACInterface iface)
26         {
27             emulation.ExternalsManager.AddExternal(new NetworkInterfaceTester(iface), name);
28         }
29 
CreateNetworkInterfaceTester(this Emulation emulation, string name, IRadio iface)30         public static void CreateNetworkInterfaceTester(this Emulation emulation, string name, IRadio iface)
31         {
32             emulation.ExternalsManager.AddExternal(new NetworkInterfaceTester(iface), name);
33         }
34     }
35 
36     public class NetworkInterfaceTester : IExternal, IDisposable
37     {
NetworkInterfaceTester(IMACInterface iface)38         public NetworkInterfaceTester(IMACInterface iface)
39         {
40             this.iface = iface as IPeripheral;
41             if(this.iface == null)
42             {
43                 throw new ConstructionException("This tester can only be attached to an IPeripheral");
44             }
45 
46             iface.FrameReady += HandleFrame;
47             newFrameEvent = new AutoResetEvent(false);
48         }
49 
NetworkInterfaceTester(IRadio iface)50         public NetworkInterfaceTester(IRadio iface)
51         {
52             this.iface = iface as IPeripheral;
53             if(this.iface == null)
54             {
55                 throw new ConstructionException("This tester can only be attached to an IPeripheral");
56             }
57 
58             iface.FrameSent += HandleFrame;
59             newFrameEvent = new AutoResetEvent(false);
60         }
61 
TryWaitForOutgoingPacket(float timeout, out NetworkInterfaceTesterResult result)62         public bool TryWaitForOutgoingPacket(float timeout, out NetworkInterfaceTesterResult result)
63         {
64             var machine = iface.GetMachine();
65             var timeoutEvent = machine.LocalTimeSource.EnqueueTimeoutEvent((ulong)(1000 * timeout));
66 
67             do
68             {
69                 if(frames.TryTake(out result))
70                 {
71                     return true;
72                 }
73 
74                 WaitHandle.WaitAny(new [] { timeoutEvent.WaitHandle, newFrameEvent });
75             }
76             while(!timeoutEvent.IsTriggered);
77 
78             result = default(NetworkInterfaceTesterResult);
79             return false;
80         }
81 
TryWaitForOutgoingPacketWithBytesAtIndex(string bytes, int index, int maxPackets, float timeout, out NetworkInterfaceTesterResult result)82         public bool TryWaitForOutgoingPacketWithBytesAtIndex(string bytes, int index, int maxPackets, float timeout, out NetworkInterfaceTesterResult result)
83         {
84             if(bytes.Length % 2 != 0)
85             {
86                 throw new ArgumentException("Partial bytes specified in the search pattern.");
87             }
88 
89             int packetsChecked = 0;
90 
91             var machine = iface.GetMachine();
92             var timeoutEvent = machine.LocalTimeSource.EnqueueTimeoutEvent((ulong)(1000 * timeout));
93 
94             do
95             {
96                 while(packetsChecked < maxPackets && frames.TryTake(out var frame))
97                 {
98                     packetsChecked++;
99                     if(IsMatch(bytes, index, frame.bytes))
100                     {
101                         result = frame;
102                         return true;
103                     }
104                 }
105 
106                 WaitHandle.WaitAny(new [] { timeoutEvent.WaitHandle, newFrameEvent });
107             }
108             while(!timeoutEvent.IsTriggered && packetsChecked < maxPackets);
109 
110             result = new NetworkInterfaceTesterResult();
111             return false;
112         }
113 
SendFrame(string bytes)114         public void SendFrame(string bytes)
115         {
116             var data = HexStringToBytes(bytes);
117             if(iface is IMACInterface macIface)
118             {
119                 if(!EthernetFrame.TryCreateEthernetFrame(data, false, out var frame))
120                 {
121                     throw new ArgumentException("Couldn't create Ethernet frame.");
122                 }
123                 var vts = new TimeStamp(default(TimeInterval), EmulationManager.ExternalWorld);
124                 iface.GetMachine().HandleTimeDomainEvent(macIface.ReceiveFrame, frame, vts);
125             }
126             else if(iface is IRadio)
127             {
128                 throw new NotImplementedException("Sending frames is not implemented for Radio interfaces.");
129             }
130             else
131             {
132                 throw new NotImplementedException("Sending frames is not implemented for this peripheral.");
133             }
134         }
135 
Dispose()136         public void Dispose()
137         {
138             if(iface is IMACInterface mac)
139             {
140                 mac.FrameReady -= HandleFrame;
141             }
142             if(iface is IRadio radio)
143             {
144                 radio.FrameSent -= HandleFrame;
145             }
146         }
147 
IsMatch(string pattern, int index, byte[] packet)148         private bool IsMatch(string pattern, int index, byte[] packet)
149         {
150             if(index + (pattern.Length / 2) > packet.Length)
151             {
152                 return false;
153             }
154 
155             for(var i = 0; i < pattern.Length; i += 2)
156             {
157                 var currentByte = packet[index + (i / 2)];
158 
159                 if(!IsByteEqual(pattern, i, currentByte))
160                 {
161                     return false;
162                 }
163             }
164 
165             return true;
166         }
167 
HandleFrame(IRadio radio, byte[] frame)168         private void HandleFrame(IRadio radio, byte[] frame)
169         {
170             HandleFrameInner(frame);
171         }
172 
HandleFrame(EthernetFrame frame)173         private void HandleFrame(EthernetFrame frame)
174         {
175             HandleFrameInner(frame.Bytes);
176         }
177 
HandleFrameInner(byte[] bytes)178         private void HandleFrameInner(byte[] bytes)
179         {
180             TimeDomainsManager.Instance.TryGetVirtualTimeStamp(out var vts);
181             frames.Add(new NetworkInterfaceTesterResult(bytes, vts.TimeElapsed.TotalMilliseconds));
182             newFrameEvent.Set();
183         }
184 
HexCharToByte(char c)185         private byte HexCharToByte(char c)
186         {
187             c = Char.ToLower(c);
188 
189             if(c >= '0' && c <= '9')
190             {
191                 return (byte)(c - '0');
192             }
193 
194             if(c >= 'a' && c <= 'f')
195             {
196                 return (byte)(c - '0' - ('a' - '9') + 1);
197             }
198 
199             throw new ArgumentException(string.Format("{0} is not a valid hex number.", c));
200         }
201 
IsNibbleEqual(string match, int index, byte data)202         private bool IsNibbleEqual(string match, int index, byte data)
203         {
204             if(index >= match.Length)
205             {
206                 return false;
207             }
208 
209             var c = match[index];
210 
211             // Treat underscore as a wildcard
212             if(c == '_')
213             {
214                 return true;
215             }
216 
217             return data == HexCharToByte(c);
218         }
219 
IsByteEqual(string input, int index, byte data)220         private bool IsByteEqual(string input, int index, byte data)
221         {
222             // check index + 1 as we need to check both nibbles
223             if(index + 1 >= input.Length)
224             {
225                 return false;
226             }
227 
228             return IsNibbleEqual(input, index, (byte)((data & 0xF0) >> 4)) && IsNibbleEqual(input, index + 1, (byte)(data & 0x0F));
229         }
230 
HexStringToBytes(string data)231         private byte[] HexStringToBytes(string data)
232         {
233             if(data.Length % 2 == 1)
234             {
235                 data = "0" + data;
236             }
237 
238             var bytes = new byte[data.Length / 2];
239             for(var i = 0; i < bytes.Length; ++i)
240             {
241                 if(!Byte.TryParse(data.Substring(i * 2, 2), NumberStyles.HexNumber, null, out bytes[i]))
242                 {
243                     throw new ArgumentException($"Data not in hex format at index {i * 2} (\"{data.Substring(i * 2, 2)}\")");
244                 }
245             }
246 
247             return bytes;
248         }
249 
250         [Antmicro.Migrant.Constructor(false)]
251         private readonly AutoResetEvent newFrameEvent;
252         private readonly IPeripheral iface;
253         private readonly BlockingCollection<NetworkInterfaceTesterResult> frames = new BlockingCollection<NetworkInterfaceTesterResult>();
254     }
255 
256     public struct NetworkInterfaceTesterResult
257     {
NetworkInterfaceTesterResultAntmicro.Renode.Testing.NetworkInterfaceTesterResult258         public NetworkInterfaceTesterResult(byte[] bytes, double timestamp)
259         {
260             this.bytes = bytes;
261             this.timestamp = timestamp;
262         }
263 
264         public byte[] bytes;
265         public double timestamp;
266     }
267 }
268