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