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.Collections;
9 using System.Collections.Generic;
10 using System.Linq;
11 using System.IO;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Migrant;
14 using Antmicro.Migrant.Hooks;
15 
16 namespace Antmicro.Renode.Utilities.RESD
17 {
18     public class LowLevelRESDParser
19     {
LowLevelRESDParser(ReadFilePath path)20         public LowLevelRESDParser(ReadFilePath path)
21         {
22             filePath = path;
23             ReadHeader();
24         }
25 
GetDataBlockEnumerator()26         public IEnumerable<IDataBlock> GetDataBlockEnumerator()
27         {
28             return new DataBlockEnumerator(this, null);
29         }
30 
31         public IEnumerable<DataBlock<T>> GetDataBlockEnumerator<T>() where T : RESDSample, new()
32         {
33             return new DataBlockEnumerator(this, typeof(T)).OfType<DataBlock<T>>();
34         }
35 
36         public long FirstBlockOffset { get; private set; }
37 
38         public string FilePath => serializedBuffer == null ? filePath : null;
39 
40         public event Action<LogLevel, string> LogCallback;
41 
GetNewReader()42         private SafeBinaryReader GetNewReader()
43         {
44             if(serializedBuffer == null)
45             {
46                 return new SafeBinaryReader(File.OpenRead(filePath));
47             }
48             else
49             {
50                 return new SafeBinaryReader(new MemoryStream(serializedBuffer));
51             }
52         }
53 
ReadHeader()54         private void ReadHeader()
55         {
56             using(var reader = GetNewReader())
57             {
58                 var magic = reader.ReadBytes(MagicValue.Length);
59                 if(!Enumerable.SequenceEqual(magic, MagicValue))
60                 {
61                     throw new RESDException($"Invalid magic number for RESD file, excepted: {Misc.PrettyPrintCollectionHex(MagicValue)}, got {Misc.PrettyPrintCollectionHex(magic)}");
62                 }
63 
64                 var version = reader.ReadByte();
65                 if(version != SupportedVersion)
66                 {
67                     throw new RESDException($"Version {version} is not supported");
68                 }
69 
70                 var padding = reader.ReadBytes(HeaderPaddingLength);
71                 if(padding.Length != HeaderPaddingLength || padding.Any(b => b != 0x00))
72                 {
73                     throw new RESDException($"Invalid padding in RESD header (expected {HeaderPaddingLength} zeros, got {Misc.PrettyPrintCollectionHex(padding)})");
74                 }
75 
76                 LogCallback?.Invoke(LogLevel.Debug, "RESD: Read header succesfully");
77                 FirstBlockOffset = reader.BaseStream.Position;
78             }
79         }
80 
81         [PreSerialization]
BeforeSerialization()82         private void BeforeSerialization()
83         {
84             if(serializedBuffer != null)
85             {
86                 return;
87             }
88 
89             var reader = GetNewReader();
90             serializedBuffer = reader.ReadBytes((int)reader.Length);
91         }
92 
93         [PostSerialization]
AfterSerialization()94         private void AfterSerialization()
95         {
96             serializedBuffer = null;
97         }
98 
99         private class DataBlockEnumerator : IEnumerator<IDataBlock>, IEnumerable<IDataBlock>
100         {
DataBlockEnumerator(LowLevelRESDParser parser, Type sampleFilter)101             public DataBlockEnumerator(LowLevelRESDParser parser, Type sampleFilter)
102             {
103                 this.parser = parser;
104                 this.sampleFilter = sampleFilter;
105 
106                 reader = parser.GetNewReader();
107                 reader.EndOfStreamEvent += (message) =>
108                 {
109                     throw new RESDException($"RESD file ended while reading data: {message} ({reader.BaseStream.Position} > {reader.BaseStream.Length})");
110                 };
111 
112                 Reset();
113             }
114 
Dispose()115             public void Dispose()
116             {
117                 reader.Dispose();
118             }
119 
MoveNext()120             public bool MoveNext()
121             {
122                 while(!reader.EOF)
123                 {
124                     limitedReader?.SeekToEnd();
125                     if(reader.EOF)
126                     {
127                         return false;
128                     }
129 
130                     var classType = sampleFilter;
131 
132                     var dataBlockHeader = DataBlockHeader.ReadFromStream(reader);
133                     limitedReader = reader.WithLength(reader.BaseStream.Position + (long)dataBlockHeader.Size);
134                     var sampleType = dataBlockHeader.SampleType;
135 
136                     if(classType != null)
137                     {
138                         var sampleTypeAttribute = classType.GetCustomAttributes(typeof(SampleTypeAttribute), true).FirstOrDefault() as SampleTypeAttribute;
139                         if(sampleTypeAttribute.SampleType != sampleType)
140                         {
141                             reader.SkipBytes((int)dataBlockHeader.Size);
142                             continue;
143                         }
144                     }
145                     else if(!TryFindTypeBySampleType(sampleType, out classType))
146                     {
147                         parser.LogCallback?.Invoke(LogLevel.Error, $"Could not find RESDSample implementator for {sampleType}");
148                         reader.SkipBytes((int)dataBlockHeader.Size);
149                         continue;
150                     }
151 
152                     Type dataBlockType;
153                     switch(dataBlockHeader.BlockType)
154                     {
155                         case BlockType.ConstantFrequencySamples:
156                             dataBlockType = typeof(ConstantFrequencySamplesDataBlock<>).MakeGenericType(new[] { classType });
157                             break;
158 
159                         default:
160                             // skip the rest of the unsupported block
161                             parser.LogCallback?.Invoke(LogLevel.Warning, $"RESD: Skipping unupported block of type {dataBlockHeader.BlockType} and size {dataBlockHeader.Size} bytes");
162                             reader.SkipBytes((int)dataBlockHeader.Size);
163                             continue;
164                     }
165 
166                     Current = (IDataBlock)dataBlockType.GetMethod("ReadFromStream").Invoke(null, new object[] { dataBlockHeader, limitedReader });
167                     return true;
168                 }
169                 return false;
170             }
171 
Reset()172             public void Reset()
173             {
174                 reader.BaseStream.Seek(parser.FirstBlockOffset, SeekOrigin.Begin);
175             }
176 
GetEnumerator()177             public IEnumerator<IDataBlock> GetEnumerator()
178             {
179                 return this;
180             }
181 
IEnumerable.GetEnumerator()182             IEnumerator IEnumerable.GetEnumerator()
183             {
184                 return this;
185             }
186 
187             public IDataBlock Current { get; private set; }
188             object IEnumerator.Current => Current;
189 
190             public long FirstBlockOffset { get; private set; }
191 
TryFindTypeBySampleType(SampleType sampleType, out Type type)192             private bool TryFindTypeBySampleType(SampleType sampleType, out Type type)
193             {
194                 foreach(var autoLoadedType in TypeManager.Instance.AutoLoadedTypes)
195                 {
196                     var sampleTypeAttribute = autoLoadedType.GetCustomAttributes(typeof(SampleTypeAttribute), true).FirstOrDefault() as SampleTypeAttribute;
197                     if(sampleTypeAttribute == null || sampleTypeAttribute.SampleType != sampleType)
198                     {
199                         continue;
200                     }
201 
202                     type = autoLoadedType;
203                     return true;
204                 }
205 
206                 type = typeof(object);
207                 return false;
208             }
209 
210             [PostDeserialization]
AfterDeserialization()211             private void AfterDeserialization()
212             {
213                 reader = parser.GetNewReader();
214                 reader.BaseStream.Seek(parser.FirstBlockOffset, SeekOrigin.Begin);
215             }
216 
217             [Transient]
218             private SafeBinaryReader reader;
219             [Transient]
220             private SafeBinaryReader limitedReader;
221 
222             private readonly LowLevelRESDParser parser;
223             private readonly Type sampleFilter;
224         }
225 
226         private byte[] serializedBuffer;
227         private readonly string filePath;
228 
229         private const byte SupportedVersion = 0x1;
230         private const int HeaderPaddingLength = 3;
231         private static readonly byte[] MagicValue = new byte[] { (byte)'R', (byte)'E', (byte)'S', (byte)'D' };
232     }
233 }
234