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