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.Text; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Utilities; 12 13 namespace Antmicro.Renode.Network.ExternalControl 14 { 15 public static class IInstanceBasedCommandExtensions 16 { 17 public static Response InvokeHandledWithInstance<T>(this IInstanceBasedCommand<T> @this, List<byte> data, Predicate<T> instanceFilter = null) 18 where T : IEmulationElement 19 { 20 if(data.Count < PayloadOffset) 21 { 22 return Response.CommandFailed(@this.Identifier, $"Expected at least {PayloadOffset} bytes of payload"); 23 } 24 var id = BitConverter.ToInt32(data.GetRange(InstanceIdOffset, InstanceIdSize).ToArray(), 0); 25 26 // only non-negative instance ids are valid 27 if(id >= 0 && @this.Instances.TryGet(id, out var instance)) 28 { 29 return @this.Invoke(instance, data.GetRange(PayloadOffset, data.Count - PayloadOffset)); 30 } 31 32 // id set to a magic of -1 is used to register a new instance 33 if(id != -1) 34 { 35 return Response.CommandFailed(@this.Identifier, "Invalid instance id"); 36 } 37 38 // requested instance registration 39 40 if(!TryGetMachine(@this, data, out var machine, out var response)) 41 { 42 return response; 43 } 44 45 if(!TryGetName(@this.Identifier, data, NameOffset, out var name, out response)) 46 { 47 return response; 48 } 49 50 if(!@this.TryRegisterInstance(machine, name, instanceFilter, out id)) 51 { 52 return Response.CommandFailed(@this.Identifier, "Instance not found"); 53 } 54 55 return Response.Success(@this.Identifier, id.AsRawBytes()); 56 } 57 58 private static bool TryRegisterInstance<T>(this IInstanceBasedCommand<T> @this, IMachine machine, string name, Predicate<T> instanceFilter, out int id) 59 where T : IEmulationElement 60 { 61 id = default(int); 62 if(!EmulationManager.Instance.CurrentEmulation.TryGetEmulationElementByName(name, machine, out var instance)) 63 { 64 return false; 65 } 66 67 if(!(instance is T) || (!instanceFilter?.Invoke((T)instance) ?? false)) 68 { 69 return false; 70 } 71 72 @this.Instances.TryAdd((T)instance, out id); 73 return true; 74 } 75 TryGetName(Command command, List<byte> data, int offset, out string name, out Response response)76 public static bool TryGetName(Command command, List<byte> data, int offset, out string name, out Response response) 77 { 78 name = default(string); 79 response = default(Response); 80 81 if(!TryDecodeNameLength(command, data, offset, out var length, out response)) 82 { 83 return false; 84 } 85 86 return TryDecodeName(command, data, offset + NameLengthSize, length, out name, out response); 87 } 88 TryGetMachine(this ICommand @this, List<byte> data, out IMachine machine, out Response response)89 private static bool TryGetMachine(this ICommand @this, List<byte> data, out IMachine machine, out Response response) 90 { 91 machine = default(IMachine); 92 response = default(Response); 93 94 if(data.Count < MachineIdOffset + MachineIdSize) 95 { 96 response = Response.CommandFailed(@this.Identifier, $"Expected at least {MachineIdOffset + MachineIdSize} bytes of payload"); 97 return false; 98 } 99 100 var id = BitConverter.ToInt32(data.GetRange(MachineIdOffset, MachineIdSize).ToArray(), 0); 101 if(@this.Machines?.TryGetMachine(id, out machine) ?? false) 102 { 103 return true; 104 } 105 106 response = Response.CommandFailed(@this.Identifier, "Invalid machine id"); 107 return false; 108 } 109 TryDecodeNameLength(Command command, List<byte> data, int offset, out int length, out Response response)110 private static bool TryDecodeNameLength(Command command, List<byte> data, int offset, out int length, out Response response) 111 { 112 response = default(Response); 113 114 if(data.Count < offset + NameLengthSize) 115 { 116 length = default(int); 117 response = Response.CommandFailed(command, $"Expected at least {offset + NameLengthSize} bytes of payload"); 118 return false; 119 } 120 121 length = BitConverter.ToInt32(data.GetRange(offset, NameLengthSize).ToArray(), 0); 122 123 if(length <= 0) 124 { 125 response = Response.CommandFailed(command, "Provided length value is illegal"); 126 return false; 127 } 128 129 return true; 130 } 131 TryDecodeName(Command command, List<byte> data, int offset, int length, out string name, out Response response)132 private static bool TryDecodeName(Command command, List<byte> data, int offset, int length, out string name, out Response response) 133 { 134 name = default(string); 135 response = default(Response); 136 137 if(data.Count != offset + length) 138 { 139 response = Response.CommandFailed(command, $"Expected {offset + length} bytes of payload"); 140 return false; 141 } 142 143 try 144 { 145 name = Encoding.UTF8.GetString(data.GetRange(offset, length).ToArray()); 146 } 147 catch(Exception) 148 { 149 response = Response.CommandFailed(command, "Invalid name encoding"); 150 return false; 151 } 152 153 return true; 154 } 155 156 public const int HeaderSize = InstanceIdSize; 157 158 private const int PayloadOffset = InstanceIdOffset + InstanceIdSize; 159 private const int InstanceIdOffset = 0; 160 private const int InstanceIdSize = 4; 161 private const int MachineIdOffset = InstanceIdSize; 162 private const int MachineIdSize = 4; 163 private const int NameOffset = MachineIdOffset + MachineIdSize; 164 private const int NameLengthSize = 4; 165 } 166 167 public interface IInstanceBasedCommand<T> : ICommand 168 where T : IEmulationElement 169 { 170 InstanceCollection<T> Instances { get; } 171 Invoke(T instance, List<byte> data)172 Response Invoke(T instance, List<byte> data); 173 } 174 175 public class InstanceCollection<T> 176 { InstanceCollection()177 public InstanceCollection() 178 { 179 collection = new Dictionary<int, T>(); 180 } 181 182 public T this[int id] => collection[id]; 183 TryAdd(T instance, out int id)184 public bool TryAdd(T instance, out int id) 185 { 186 id = nextId; 187 188 if(nextId < 0) 189 { 190 return false; 191 } 192 nextId += 1; 193 194 collection.Add(id, instance); 195 return true; 196 } 197 Remove(int id)198 public void Remove(int id) => collection.Remove(id); 199 TryGet(int id, out T instance)200 public bool TryGet(int id, out T instance) => collection.TryGetValue(id, out instance); 201 202 private int nextId; 203 private readonly Dictionary<int, T> collection; 204 } 205 } 206