1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using Antmicro.Renode.Core;
8 using Antmicro.Renode.Exceptions;
9 #if !PLATFORM_WINDOWS
10 using System;
11 using System.Collections.Generic;
12 using System.IO;
13 using System.Linq;
14 using Antmicro.Renode.Utilities;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Migrant;
17 using Antmicro.Migrant.Hooks;
18 using AntShell.Terminal;
19 using Mono.Unix;
20 #endif
21 
22 namespace Antmicro.Renode.Peripherals.Wireless
23 {
24     public static class SlipRadioExtensions
25     {
CreateSlipRadio(this Emulation emulation, string name, string fileName)26         public static void CreateSlipRadio(this Emulation emulation, string name, string fileName)
27         {
28 #if !PLATFORM_WINDOWS
29             emulation.ExternalsManager.AddExternal(new SlipRadio(fileName), name);
30 #else
31             throw new RecoverableException("Creating SlipRadio is not supported on Windows.");
32 #endif
33         }
34     }
35 
36 #if !PLATFORM_WINDOWS
37     public class SlipRadio : ISlipRadio
38     {
SlipRadio(string linkName)39         public SlipRadio(string linkName)
40         {
41             this.linkName = linkName;
42             buffer = new List<byte>();
43             Initialize();
44         }
45 
46         [PostDeserialization]
Initialize()47         private void Initialize()
48         {
49             ptyStream = new PtyUnixStream();
50             io = new IOProvider { Backend = new StreamIOSource(ptyStream) };
51             io.ByteRead += CharReceived;
52             CreateSymlink(linkName);
53         }
54 
ReceiveFrame(byte[] frame, IRadio sender)55         public virtual void ReceiveFrame(byte[] frame, IRadio sender)
56         {
57             EncapsulateAndSend(frame);
58         }
59 
CharReceived(int value)60         public void CharReceived(int value)
61         {
62             buffer.Add((byte)value);
63             if((byte)value == END)
64             {
65                 if(buffer.Count > 8)
66                 {
67                     HandleFrame(buffer.ToArray());
68                 }
69                 buffer.Clear();
70             }
71         }
72 
Reset()73         public void Reset()
74         {
75             buffer.Clear();
76         }
77 
Dispose()78         public void Dispose()
79         {
80             io.Dispose();
81             try
82             {
83                 symlink.Delete();
84             }
85             catch(FileNotFoundException e)
86             {
87                 throw new RecoverableException(string.Format("There was an error when removing symlink `{0}': {1}", symlink.FullName, e.Message));
88             }
89         }
90 
91         public event Action<IRadio, byte[]> FrameSent;
92 
93         public int Channel { get; set; }
94 
HandleFrame(byte[] frame)95         protected virtual void HandleFrame(byte[] frame)
96         {
97             var fs = FrameSent;
98             if(fs != null)
99             {
100                 fs.Invoke(this, frame);
101             }
102             else
103             {
104                 this.Log(LogLevel.Warning, "FrameSent is not initialized. Am I connected to medium?");
105             }
106         }
107 
Encapsulate(byte[] frame)108         protected virtual byte[] Encapsulate(byte[] frame)
109         {
110             var result = new List<byte>();
111 
112             foreach(var value in frame)
113             {
114                 switch(value)
115                 {
116                     case END:
117                         result.Add(ESC);
118                         result.Add(ESC_END);
119                         break;
120                     case ESC:
121                         result.Add(ESC);
122                         result.Add(ESC_ESC);
123                         break;
124                     default:
125                         result.Add(value);
126                         break;
127                 }
128             }
129             var engine = new CRCEngine(CRCPolynomial.CRC32);
130             var crc = engine.Calculate(result);
131             result.AddRange(new byte[] {(byte)(crc & 0xFF), (byte)((crc >> 8) & 0xFF), (byte)((crc >> 16) & 0xFF), (byte)((crc >> 24) & 0xFF)});
132             result.Add(END);
133             return result.ToArray();
134         }
135 
Decapsulate(byte[] frame)136         protected byte[] Decapsulate(byte[] frame)
137         {
138             var result = new List<byte>();
139             bool isEscaped = false;
140 
141             foreach(var value in frame)
142             {
143                 switch(value)
144                 {
145                     case END:
146                         return result.ToArray();
147                     case ESC:
148                         isEscaped = true;
149                         continue;
150                     case ESC_END:
151                         if(isEscaped)
152                         {
153                             result.Add(END);
154                             isEscaped = false;
155                         }
156                         else
157                         {
158                             result.Add(ESC_END);
159                         }
160                         break;
161                     case ESC_ESC:
162                         if(isEscaped)
163                         {
164                             result.Add(ESC);
165                             isEscaped = false;
166                         }
167                         else
168                         {
169                             result.Add(ESC_ESC);
170                         }
171                         break;
172                     default:
173                         isEscaped = false;
174                         result.Add(value);
175                         break;
176                 }
177             }
178 
179             Logger.Log(LogLevel.Error, "Received an unfinished frame of length {0}, dropping...", result.Count);
180             return new byte[0]; //TODO or nul?
181         }
182 
EncapsulateAndSend(byte[] data)183         protected void EncapsulateAndSend(byte[] data)
184         {
185             var encoded = Encapsulate(data);
186             ptyStream.Write(encoded, 0, encoded.Length);
187         }
188 
CreateSymlink(string linkName)189         private void CreateSymlink(string linkName)
190         {
191             if(File.Exists(linkName))
192             {
193                 try
194                 {
195                     File.Delete(linkName);
196                 }
197                 catch(Exception e)
198                 {
199                     throw new RecoverableException(string.Format("There was an error when removing existing `{0}' symlink: {1}", linkName, e.Message));
200                 }
201             }
202             try
203             {
204                 var slavePtyFile = new UnixFileInfo(ptyStream.SlaveName);
205                 symlink = slavePtyFile.CreateSymbolicLink(linkName);
206             }
207             catch(Exception e)
208             {
209                 throw new RecoverableException(string.Format("There was an error when when creating a symlink `{0}': {1}", linkName, e.Message));
210             }
211             Logger.Log(LogLevel.Info, "Created a Slip Radio pty connection to {0}", linkName);
212         }
213 
214         [Transient]
215         protected PtyUnixStream ptyStream;
216         [Transient]
217         private IOProvider io;
218 
219         private readonly List<byte> buffer;
220         private readonly string linkName;
221         private UnixSymbolicLinkInfo symlink;
222 
223         private const byte END = 0xC0;
224         private const byte ESC = 0xDB;
225         private const byte ESC_END = 0xDC;
226         private const byte ESC_ESC = 0xDD;
227     }
228 #endif
229 }
230