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