1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Diagnostics.CodeAnalysis;
4 using System.Diagnostics.Contracts;
5 using System.IO;
6 using System.ServiceModel.Channels;
7 using System.Web.Http.SelfHost.Properties;
8 
9 namespace System.Web.Http.SelfHost.ServiceModel.Channels
10 {
11     internal class BufferedOutputStream : Stream
12     {
13         private BufferManager _bufferManager;
14 
15         private byte[][] _chunks;
16 
17         private int _chunkCount;
18         private byte[] _currentChunk;
19         private int _currentChunkSize;
20         private int _maxSize;
21         private int _theMaxSizeQuota;
22         private int _totalSize;
23         private bool _callerReturnsBuffer;
24 
25         [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Used for internal checking")]
26         private bool _bufferReturned;
27 
28         [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Used for internal checking")]
29         private bool _initialized;
30 
31         // requires an explicit call to Init() by the caller
BufferedOutputStream()32         public BufferedOutputStream()
33         {
34             _chunks = new byte[4][];
35         }
36 
BufferedOutputStream(int initialSize, int maxSize, BufferManager bufferManager)37         public BufferedOutputStream(int initialSize, int maxSize, BufferManager bufferManager)
38             : this()
39         {
40             Reinitialize(initialSize, maxSize, bufferManager);
41         }
42 
43         public override bool CanRead
44         {
45             get { return false; }
46         }
47 
48         public override bool CanSeek
49         {
50             get { return false; }
51         }
52 
53         public override bool CanWrite
54         {
55             get { return true; }
56         }
57 
58         public override long Length
59         {
60             get { return _totalSize; }
61         }
62 
63         public override long Position
64         {
65             get { throw Error.NotSupported(SRResources.SeekNotSupported); }
66 
67             set { throw Error.NotSupported(SRResources.SeekNotSupported); }
68         }
69 
Reinitialize(int initialSize, int maxSizeQuota, BufferManager bufferManager)70         public void Reinitialize(int initialSize, int maxSizeQuota, BufferManager bufferManager)
71         {
72             Reinitialize(initialSize, maxSizeQuota, maxSizeQuota, bufferManager);
73         }
74 
Reinitialize(int initialSize, int maxSizeQuota, int effectiveMaxSize, BufferManager bufferManager)75         public void Reinitialize(int initialSize, int maxSizeQuota, int effectiveMaxSize, BufferManager bufferManager)
76         {
77             Contract.Assert(!_initialized, "Clear must be called before re-initializing stream");
78 
79             if (bufferManager == null)
80             {
81                 throw Error.ArgumentNull("bufferManager");
82             }
83 
84             _theMaxSizeQuota = maxSizeQuota;
85             _maxSize = effectiveMaxSize;
86             _bufferManager = bufferManager;
87             _currentChunk = bufferManager.TakeBuffer(initialSize);
88             _currentChunkSize = 0;
89             _totalSize = 0;
90             _chunkCount = 1;
91             _chunks[0] = _currentChunk;
92             _initialized = true;
93         }
94 
BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)95         public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
96         {
97             throw Error.NotSupported(SRResources.ReadNotSupported);
98         }
99 
EndRead(IAsyncResult asyncResult)100         public override int EndRead(IAsyncResult asyncResult)
101         {
102             throw Error.NotSupported(SRResources.ReadNotSupported);
103         }
104 
BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)105         public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
106         {
107             Write(buffer, offset, count);
108             return new CompletedAsyncResult(callback, state);
109         }
110 
EndWrite(IAsyncResult asyncResult)111         public override void EndWrite(IAsyncResult asyncResult)
112         {
113             CompletedAsyncResult.End(asyncResult);
114         }
115 
Clear()116         public void Clear()
117         {
118             if (!_callerReturnsBuffer)
119             {
120                 for (int i = 0; i < _chunkCount; i++)
121                 {
122                     _bufferManager.ReturnBuffer(_chunks[i]);
123                     _chunks[i] = null;
124                 }
125             }
126 
127             _callerReturnsBuffer = false;
128             _initialized = false;
129             _bufferReturned = false;
130             _chunkCount = 0;
131             _currentChunk = null;
132         }
133 
Close()134         public override void Close()
135         {
136             // Called directly or via base.Dispose, ensure all buffers are returned to the BufferManager
137             Clear();
138         }
139 
Flush()140         public override void Flush()
141         {
142         }
143 
Read(byte[] buffer, int offset, int count)144         public override int Read(byte[] buffer, int offset, int count)
145         {
146             throw Error.NotSupported(SRResources.ReadNotSupported);
147         }
148 
ReadByte()149         public override int ReadByte()
150         {
151             throw Error.NotSupported(SRResources.ReadNotSupported);
152         }
153 
Seek(long offset, SeekOrigin origin)154         public override long Seek(long offset, SeekOrigin origin)
155         {
156             throw Error.NotSupported(SRResources.SeekNotSupported);
157         }
158 
SetLength(long value)159         public override void SetLength(long value)
160         {
161             throw Error.NotSupported(SRResources.SeekNotSupported);
162         }
163 
ToMemoryStream()164         public MemoryStream ToMemoryStream()
165         {
166             int bufferSize;
167             byte[] buffer = ToArray(out bufferSize);
168             return new MemoryStream(buffer, 0, bufferSize);
169         }
170 
171         [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Out parameter is fine here.")]
ToArray(out int bufferSize)172         public byte[] ToArray(out int bufferSize)
173         {
174             Contract.Assert(_initialized, "No data to return from uninitialized stream");
175             Contract.Assert(!_bufferReturned, "ToArray cannot be called more than once");
176 
177             byte[] buffer;
178             if (_chunkCount == 1)
179             {
180                 buffer = _currentChunk;
181                 bufferSize = _currentChunkSize;
182                 _callerReturnsBuffer = true;
183             }
184             else
185             {
186                 buffer = _bufferManager.TakeBuffer(_totalSize);
187                 int offset = 0;
188                 int count = _chunkCount - 1;
189                 for (int i = 0; i < count; i++)
190                 {
191                     byte[] chunk = _chunks[i];
192                     Buffer.BlockCopy(chunk, 0, buffer, offset, chunk.Length);
193                     offset += chunk.Length;
194                 }
195 
196                 Buffer.BlockCopy(_currentChunk, 0, buffer, offset, _currentChunkSize);
197                 bufferSize = _totalSize;
198             }
199 
200             _bufferReturned = true;
201             return buffer;
202         }
203 
Skip(int size)204         public void Skip(int size)
205         {
206             WriteCore(null, 0, size);
207         }
208 
Write(byte[] buffer, int offset, int count)209         public override void Write(byte[] buffer, int offset, int count)
210         {
211             WriteCore(buffer, offset, count);
212         }
213 
WriteByte(byte value)214         public override void WriteByte(byte value)
215         {
216             Contract.Assert(_initialized, "Cannot write to uninitialized stream");
217             Contract.Assert(!_bufferReturned, "Cannot write to stream once ToArray has been called.");
218 
219             if (_totalSize == _maxSize)
220             {
221                 throw CreateQuotaExceededException(_maxSize);
222             }
223 
224             if (_currentChunkSize == _currentChunk.Length)
225             {
226                 AllocNextChunk(1);
227             }
228 
229             _currentChunk[_currentChunkSize++] = value;
230         }
231 
CreateQuotaExceededException(int maxSizeQuota)232         protected virtual Exception CreateQuotaExceededException(int maxSizeQuota)
233         {
234             return new InvalidOperationException(Error.Format(SRResources.BufferedOutputStreamQuotaExceeded, maxSizeQuota));
235         }
236 
WriteCore(byte[] buffer, int offset, int size)237         private void WriteCore(byte[] buffer, int offset, int size)
238         {
239             Contract.Assert(_initialized, "Cannot write to uninitialized stream");
240             Contract.Assert(!_bufferReturned, "Cannot write to stream once ToArray has been called.");
241 
242             if (size < 0)
243             {
244                 throw Error.ArgumentOutOfRange("size", size, SRResources.ValueMustBeNonNegative);
245             }
246 
247             if ((Int32.MaxValue - size) < _totalSize)
248             {
249                 throw CreateQuotaExceededException(_theMaxSizeQuota);
250             }
251 
252             int newTotalSize = _totalSize + size;
253             if (newTotalSize > _maxSize)
254             {
255                 throw CreateQuotaExceededException(_theMaxSizeQuota);
256             }
257 
258             int remainingSizeInChunk = _currentChunk.Length - _currentChunkSize;
259             if (size > remainingSizeInChunk)
260             {
261                 if (remainingSizeInChunk > 0)
262                 {
263                     if (buffer != null)
264                     {
265                         Buffer.BlockCopy(buffer, offset, _currentChunk, _currentChunkSize, remainingSizeInChunk);
266                     }
267 
268                     _currentChunkSize = _currentChunk.Length;
269                     offset += remainingSizeInChunk;
270                     size -= remainingSizeInChunk;
271                 }
272 
273                 AllocNextChunk(size);
274             }
275 
276             if (buffer != null)
277             {
278                 Buffer.BlockCopy(buffer, offset, _currentChunk, _currentChunkSize, size);
279             }
280 
281             _totalSize = newTotalSize;
282             _currentChunkSize += size;
283         }
284 
AllocNextChunk(int minimumChunkSize)285         private void AllocNextChunk(int minimumChunkSize)
286         {
287             int newChunkSize;
288             if (_currentChunk.Length > (Int32.MaxValue / 2))
289             {
290                 newChunkSize = Int32.MaxValue;
291             }
292             else
293             {
294                 newChunkSize = _currentChunk.Length * 2;
295             }
296 
297             if (minimumChunkSize > newChunkSize)
298             {
299                 newChunkSize = minimumChunkSize;
300             }
301 
302             byte[] newChunk = _bufferManager.TakeBuffer(newChunkSize);
303             if (_chunkCount == _chunks.Length)
304             {
305                 byte[][] newChunks = new byte[_chunks.Length * 2][];
306                 Array.Copy(_chunks, newChunks, _chunks.Length);
307                 _chunks = newChunks;
308             }
309 
310             _chunks[_chunkCount++] = newChunk;
311             _currentChunk = newChunk;
312             _currentChunkSize = 0;
313         }
314     }
315 }
316