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