1 // 2 // Copyright (c) 2010-2018 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using System.IO; 10 using Antmicro.Migrant; 11 using Antmicro.Renode.Utilities; 12 13 namespace Antmicro.Renode.Storage 14 { 15 public class SerializableStreamView : Stream, ISpeciallySerializable 16 { 17 // 18 // this data is not accessible filled with padding byte 19 // | | 20 // | Position = 0 | Position = length - 1 21 // | | | | 22 // v v v v 23 // *~~~~~~~+---------------------------*......# 24 // ^ ^ ^ ^ 25 // | | | | 26 // | offset | | 27 // | | length 28 // underlying stream's beginning | 29 // underlying stream's length 30 // SerializableStreamView(Stream stream, long? length = null, byte paddingByte = 0, long offset = 0)31 public SerializableStreamView(Stream stream, long? length = null, byte paddingByte = 0, long offset = 0) 32 { 33 if(!stream.CanSeek) 34 { 35 throw new ArgumentException("This wrapper is suitable only for seekable streams"); 36 } 37 38 SetLength(length ?? stream.Length - offset); 39 40 underlyingStream = stream; 41 underlyingStreamOffset = offset; 42 Position = 0; 43 PaddingByte = paddingByte; 44 } 45 Dispose(bool disposing)46 protected override void Dispose(bool disposing) 47 { 48 base.Dispose(disposing); 49 underlyingStream.Dispose(); 50 } 51 Flush()52 public override void Flush() 53 { 54 underlyingStream.Flush(); 55 } 56 Read(byte[] buffer, int offset, int count)57 public override int Read(byte[] buffer, int offset, int count) 58 { 59 var paddingSize = 0; 60 var bytesReadCount = underlyingStream.Read(buffer, offset, count); 61 if(bytesReadCount < count) 62 { 63 // pad the rest with `PaddingByte` 64 paddingSize = checked((int)Math.Min(count - bytesReadCount, Length - Position)); 65 for(var i = 0; i < paddingSize; i++) 66 { 67 buffer[i + bytesReadCount + offset] = PaddingByte; 68 } 69 paddingOffset += paddingSize; 70 } 71 return bytesReadCount + paddingSize; 72 } 73 Write(byte[] buffer, int offset, int count)74 public override void Write(byte[] buffer, int offset, int count) 75 { 76 if(Position + count > Length) 77 { 78 throw new ArgumentException($"There is no more space left in stream. Asked to write {count} bytes, but only {Length - Position} are left."); 79 } 80 81 // writing to the padding area extends the file, but not longer than provided `length` 82 var bytesToWriteCount = checked((int)Math.Min(count, Length - underlyingStream.Position)); 83 if(paddingOffset > 0) 84 { 85 // this effectively grows the file filling it with `PaddingByte` 86 for(var i = 0; i < paddingOffset; i++) 87 { 88 underlyingStream.WriteByte(PaddingByte); 89 } 90 paddingOffset = 0; 91 } 92 underlyingStream.Write(buffer, offset, bytesToWriteCount); 93 } 94 Seek(long offset, SeekOrigin origin)95 public override long Seek(long offset, SeekOrigin origin) 96 { 97 switch(origin) 98 { 99 case SeekOrigin.Begin: 100 Position = offset; 101 break; 102 103 case SeekOrigin.Current: 104 Position += offset; 105 break; 106 107 case SeekOrigin.End: 108 Position = Length + offset; 109 break; 110 111 default: 112 throw new ArgumentException("Unexpected seek origin"); 113 } 114 115 return Position; 116 } 117 SetLength(long value)118 public override void SetLength(long value) 119 { 120 if(value < 0 || underlyingStreamOffset > value) 121 { 122 throw new ArgumentException("Wrong offset/length values"); 123 } 124 125 this.length = value; 126 } 127 Load(PrimitiveReader reader)128 public void Load(PrimitiveReader reader) 129 { 130 var fileName = TemporaryFilesManager.Instance.GetTemporaryFile(); 131 underlyingStream = new FileStream(fileName, FileMode.OpenOrCreate); 132 underlyingStreamOffset = 0; 133 134 length = reader.ReadInt64(); 135 PaddingByte = reader.ReadByte(); 136 var numberOfBytes = reader.ReadInt64(); 137 reader.CopyTo(underlyingStream, numberOfBytes); 138 Position = reader.ReadInt64(); 139 } 140 Save(PrimitiveWriter writer)141 public void Save(PrimitiveWriter writer) 142 { 143 // we don't have to save offset as after deserialization we will treat it as 0 144 // we don't have to save paddingOffset as it will be recalculated by setting `Position` after deserialization 145 writer.Write(Length); 146 writer.Write(PaddingByte); 147 var initialPosition = Position; 148 // this seeks to the beginning of meaningful data in the stream 149 Position = 0; 150 writer.Write(underlyingStream.Length - underlyingStream.Position); 151 writer.CopyFrom(underlyingStream, underlyingStream.Length - underlyingStream.Position); 152 Position = initialPosition; 153 writer.Write(initialPosition); 154 } 155 156 public byte PaddingByte { get; private set; } 157 158 public override bool CanRead { get { return underlyingStream.CanRead; } } 159 160 public override bool CanSeek { get { return underlyingStream.CanSeek; } } 161 162 public override bool CanWrite { get { return underlyingStream.CanWrite; } } 163 164 public override long Length => length; 165 166 public override long Position 167 { 168 get 169 { 170 return underlyingStream.Position - underlyingStreamOffset + paddingOffset; 171 } 172 set 173 { 174 if(value > Length) 175 { 176 throw new ArgumentException("Setting position beyond the underlying stream is unsupported"); 177 } 178 else if(value < 0) 179 { 180 throw new ArgumentOutOfRangeException("Setting negative position is unsupported"); 181 } 182 183 if(underlyingStream.Length > underlyingStreamOffset + value) 184 { 185 underlyingStream.Seek(underlyingStreamOffset + value, SeekOrigin.Begin); 186 paddingOffset = 0; 187 } 188 else 189 { 190 underlyingStream.Seek(0, SeekOrigin.End); 191 paddingOffset = value - (underlyingStream.Length - underlyingStreamOffset); 192 } 193 } 194 } 195 SerializableStreamView()196 private SerializableStreamView() 197 { 198 // this is intended for deserialization only 199 } 200 201 private long paddingOffset; 202 private Stream underlyingStream; 203 private long underlyingStreamOffset; 204 private long length; 205 } 206 } 207 208