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