1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Diagnostics.Contracts;
7 using System.IO;
8 using System.Runtime.InteropServices.WindowsRuntime;
9 using System.Runtime.InteropServices;
10 using System.Threading.Tasks;
11 using System.Threading;
12 using Windows.Foundation;
13 using Windows.Storage.Streams;
14 
15 namespace System.IO
16 {
17     /// <summary>
18     /// An <code>wrapper</code> for a managed stream that implements all WinRT stream operations.
19     /// This class must not implement any WinRT stream interfaces directly.
20     /// We never create instances of this class directly; instead we use classes defined in
21     /// the region Interface adapters to implement WinRT ifaces and create instances of those types.
22     /// See comment in that region for technical details.
23     /// </summary>
24     internal abstract class NetFxToWinRtStreamAdapter : IDisposable
25     {
26         #region Construction
27 
28         #region Interface adapters
29 
30         // Instances of private types defined in this section will be returned from NetFxToWinRtStreamAdapter.Create(..).
31         // Depending on the capabilities of the .NET stream for which we need to construct the adapter, we need to return
32         // an object that can be QIed (COM speak for "cast") to a well-defined set of ifaces.
33         // E.g, if the specified stream CanRead, but not CanSeek and not CanWrite, then we *must* return an object that
34         // can be QIed to IInputStream, but *not* IRandomAccessStream and *not* IOutputStream.
35         // There are two ways to do that:
36         //   - We could explicitly implement ICustomQueryInterface and respond to QI requests by analyzing the stream capabilities
37         //   - We can use the runtime's ability to do that for us, based on the ifaces the concrete class implements (or does not).
38         // The latter is much more elegant, and likely also faster.
39 
40 
41         private class InputStream : NetFxToWinRtStreamAdapter, IInputStream, IDisposable
42         {
InputStream(Stream stream, StreamReadOperationOptimization readOptimization)43             internal InputStream(Stream stream, StreamReadOperationOptimization readOptimization)
44                 : base(stream, readOptimization)
45             {
46             }
47         }
48 
49 
50         private class OutputStream : NetFxToWinRtStreamAdapter, IOutputStream, IDisposable
51         {
OutputStream(Stream stream, StreamReadOperationOptimization readOptimization)52             internal OutputStream(Stream stream, StreamReadOperationOptimization readOptimization)
53                 : base(stream, readOptimization)
54             {
55             }
56         }
57 
58 
59         private class RandomAccessStream : NetFxToWinRtStreamAdapter, IRandomAccessStream, IInputStream, IOutputStream, IDisposable
60         {
RandomAccessStream(Stream stream, StreamReadOperationOptimization readOptimization)61             internal RandomAccessStream(Stream stream, StreamReadOperationOptimization readOptimization)
62                 : base(stream, readOptimization)
63             {
64             }
65         }
66 
67 
68         private class InputOutputStream : NetFxToWinRtStreamAdapter, IInputStream, IOutputStream, IDisposable
69         {
InputOutputStream(Stream stream, StreamReadOperationOptimization readOptimization)70             internal InputOutputStream(Stream stream, StreamReadOperationOptimization readOptimization)
71                 : base(stream, readOptimization)
72             {
73             }
74         }
75 
76         #endregion Interface adapters
77 
78         // We may want to define different behaviour for different types of streams.
79         // For instance, ReadAsync treats MemoryStream special for performance reasons.
80         // The enum 'StreamReadOperationOptimization' describes the read optimization to employ for a
81         // given NetFxToWinRtStreamAdapter instance. In future, we might define other enums to follow a
82         // similar pattern, e.g. 'StreamWriteOperationOptimization' or 'StreamFlushOperationOptimization'.
83         private enum StreamReadOperationOptimization
84         {
85             AbstractStream = 0, MemoryStream
86         }
87 
88 
Create(Stream stream)89         internal static NetFxToWinRtStreamAdapter Create(Stream stream)
90         {
91             if (stream == null)
92                 throw new ArgumentNullException(nameof(stream));
93 
94             StreamReadOperationOptimization readOptimization = StreamReadOperationOptimization.AbstractStream;
95             if (stream.CanRead)
96                 readOptimization = DetermineStreamReadOptimization(stream);
97 
98             NetFxToWinRtStreamAdapter adapter;
99 
100             if (stream.CanSeek)
101                 adapter = new RandomAccessStream(stream, readOptimization);
102 
103             else if (stream.CanRead && stream.CanWrite)
104                 adapter = new InputOutputStream(stream, readOptimization);
105 
106             else if (stream.CanRead)
107                 adapter = new InputStream(stream, readOptimization);
108 
109             else if (stream.CanWrite)
110                 adapter = new OutputStream(stream, readOptimization);
111 
112             else
113                 throw new ArgumentException(SR.Argument_NotSufficientCapabilitiesToConvertToWinRtStream);
114 
115             return adapter;
116         }
117 
118 
DetermineStreamReadOptimization(Stream stream)119         private static StreamReadOperationOptimization DetermineStreamReadOptimization(Stream stream)
120         {
121             Debug.Assert(stream != null);
122 
123             if (CanApplyReadMemoryStreamOptimization(stream))
124                 return StreamReadOperationOptimization.MemoryStream;
125 
126             return StreamReadOperationOptimization.AbstractStream;
127         }
128 
129 
CanApplyReadMemoryStreamOptimization(Stream stream)130         private static bool CanApplyReadMemoryStreamOptimization(Stream stream)
131         {
132             MemoryStream memStream = stream as MemoryStream;
133             if (memStream == null)
134                 return false;
135 
136             ArraySegment<byte> arrSeg;
137             return memStream.TryGetBuffer(out arrSeg);
138         }
139 
140 
NetFxToWinRtStreamAdapter(Stream stream, StreamReadOperationOptimization readOptimization)141         private NetFxToWinRtStreamAdapter(Stream stream, StreamReadOperationOptimization readOptimization)
142         {
143             Debug.Assert(stream != null);
144             Debug.Assert(stream.CanRead || stream.CanWrite || stream.CanSeek);
145             Contract.EndContractBlock();
146 
147             Debug.Assert(!stream.CanRead || (stream.CanRead && this is IInputStream));
148             Debug.Assert(!stream.CanWrite || (stream.CanWrite && this is IOutputStream));
149             Debug.Assert(!stream.CanSeek || (stream.CanSeek && this is IRandomAccessStream));
150 
151             _readOptimization = readOptimization;
152             _managedStream = stream;
153         }
154 
155         #endregion Construction
156 
157 
158         #region Instance variables
159 
160         private Stream _managedStream = null;
161         private bool _leaveUnderlyingStreamOpen = true;
162         private readonly StreamReadOperationOptimization _readOptimization;
163 
164         #endregion Instance variables
165 
166 
167         #region Tools and Helpers
168 
169         /// <summary>
170         /// We keep tables for mappings between managed and WinRT streams to make sure to always return the same adapter for a given underlying stream.
171         /// However, in order to avoid global locks on those tables, several instances of this type may be created and then can race to be entered
172         /// into the appropriate map table. All except for the winning instances will be thrown away. However, we must ensure that when the losers are
173         /// finalized, they do not dispose the underlying stream. To ensure that, we must call this method on the winner to notify it that it is safe to
174         /// dispose the underlying stream.
175         /// </summary>
SetWonInitializationRace()176         internal void SetWonInitializationRace()
177         {
178             _leaveUnderlyingStreamOpen = false;
179         }
180 
181 
GetManagedStream()182         public Stream GetManagedStream()
183         {
184             return _managedStream;
185         }
186 
187 
EnsureNotDisposed()188         private Stream EnsureNotDisposed()
189         {
190             Stream str = _managedStream;
191 
192             if (str == null)
193             {
194                 ObjectDisposedException ex = new ObjectDisposedException(SR.ObjectDisposed_CannotPerformOperation);
195                 ex.SetErrorCode(__HResults.RO_E_CLOSED);
196                 throw ex;
197             }
198 
199             return str;
200         }
201 
202         #endregion Tools and Helpers
203 
204 
205         #region Common public interface
206 
207         /// <summary>Implements IDisposable.Dispose (IClosable.Close in WinRT)</summary>
IDisposable.Dispose()208         void IDisposable.Dispose()
209         {
210             Stream str = _managedStream;
211             if (str == null)
212                 return;
213 
214             _managedStream = null;
215 
216             if (!_leaveUnderlyingStreamOpen)
217                 str.Dispose();
218         }
219 
220         #endregion Common public interface
221 
222 
223         #region IInputStream public interface
224 
ReadAsync(IBuffer buffer, UInt32 count, InputStreamOptions options)225         public IAsyncOperationWithProgress<IBuffer, UInt32> ReadAsync(IBuffer buffer, UInt32 count, InputStreamOptions options)
226         {
227             if (buffer == null)
228             {
229                 // Mapped to E_POINTER.
230                 throw new ArgumentNullException(nameof(buffer));
231             }
232 
233             if (count < 0 || Int32.MaxValue < count)
234             {
235                 ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException(nameof(count));
236                 ex.SetErrorCode(__HResults.E_INVALIDARG);
237                 throw ex;
238             }
239 
240             if (buffer.Capacity < count)
241             {
242                 ArgumentException ex = new ArgumentException(SR.Argument_InsufficientBufferCapacity);
243                 ex.SetErrorCode(__HResults.E_INVALIDARG);
244                 throw ex;
245             }
246 
247             if (!(options == InputStreamOptions.None || options == InputStreamOptions.Partial || options == InputStreamOptions.ReadAhead))
248             {
249                 ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException(nameof(options),
250                                                                                  SR.ArgumentOutOfRange_InvalidInputStreamOptionsEnumValue);
251                 ex.SetErrorCode(__HResults.E_INVALIDARG);
252                 throw ex;
253             }
254 
255             // Commented due to a reported CCRewrite bug. Should uncomment when fixed:
256             //Contract.Ensures(Contract.Result<IAsyncOperationWithProgress<IBuffer, UInt32>>() != null);
257             //Contract.EndContractBlock();
258 
259             Stream str = EnsureNotDisposed();
260 
261             IAsyncOperationWithProgress<IBuffer, UInt32> readAsyncOperation;
262             switch (_readOptimization)
263             {
264                 case StreamReadOperationOptimization.MemoryStream:
265                     readAsyncOperation = StreamOperationsImplementation.ReadAsync_MemoryStream(str, buffer, count);
266                     break;
267 
268                 case StreamReadOperationOptimization.AbstractStream:
269                     readAsyncOperation = StreamOperationsImplementation.ReadAsync_AbstractStream(str, buffer, count, options);
270                     break;
271 
272                 // Use this pattern to add more optimisation options if necessary:
273                 //case StreamReadOperationOptimization.XxxxStream:
274                 //    readAsyncOperation = StreamOperationsImplementation.ReadAsync_XxxxStream(str, buffer, count, options);
275                 //    break;
276 
277                 default:
278                     Debug.Assert(false, "We should never get here. Someone forgot to handle an input stream optimisation option.");
279                     readAsyncOperation = null;
280                     break;
281             }
282 
283             return readAsyncOperation;
284         }
285 
286         #endregion IInputStream public interface
287 
288 
289         #region IOutputStream public interface
290 
WriteAsync(IBuffer buffer)291         public IAsyncOperationWithProgress<UInt32, UInt32> WriteAsync(IBuffer buffer)
292         {
293             if (buffer == null)
294             {
295                 // Mapped to E_POINTER.
296                 throw new ArgumentNullException(nameof(buffer));
297             }
298 
299             if (buffer.Capacity < buffer.Length)
300             {
301                 ArgumentException ex = new ArgumentException(SR.Argument_BufferLengthExceedsCapacity);
302                 ex.SetErrorCode(__HResults.E_INVALIDARG);
303                 throw ex;
304             }
305 
306             // Commented due to a reported CCRewrite bug. Should uncomment when fixed:
307             //Contract.Ensures(Contract.Result<IAsyncOperationWithProgress<UInt32, UInt32>>() != null);
308             //Contract.EndContractBlock();
309 
310             Stream str = EnsureNotDisposed();
311             return StreamOperationsImplementation.WriteAsync_AbstractStream(str, buffer);
312         }
313 
314 
FlushAsync()315         public IAsyncOperation<Boolean> FlushAsync()
316         {
317             Contract.Ensures(Contract.Result<IAsyncOperation<Boolean>>() != null);
318             Contract.EndContractBlock();
319 
320             Stream str = EnsureNotDisposed();
321             return StreamOperationsImplementation.FlushAsync_AbstractStream(str);
322         }
323 
324         #endregion IOutputStream public interface
325 
326 
327         #region IRandomAccessStream public interface
328 
329 
330         #region IRandomAccessStream public interface: Not cloning related
331 
Seek(UInt64 position)332         public void Seek(UInt64 position)
333         {
334             if (position > Int64.MaxValue)
335             {
336                 ArgumentException ex = new ArgumentException(SR.IO_CannotSeekBeyondInt64MaxValue);
337                 ex.SetErrorCode(__HResults.E_INVALIDARG);
338                 throw ex;
339             }
340 
341             // Commented due to a reported CCRewrite bug. Should uncomment when fixed:
342             //Contract.EndContractBlock();
343 
344             Stream str = EnsureNotDisposed();
345             Int64 pos = unchecked((Int64)position);
346 
347             Debug.Assert(str != null);
348             Debug.Assert(str.CanSeek, "The underlying str is expected to support Seek, but it does not.");
349             Debug.Assert(0 <= pos, "Unexpected pos=" + pos + ".");
350 
351             str.Seek(pos, SeekOrigin.Begin);
352         }
353 
354 
355         public bool CanRead
356         {
357             get
358             {
359                 Stream str = EnsureNotDisposed();
360                 return str.CanRead;
361             }
362         }
363 
364 
365         public bool CanWrite
366         {
367             get
368             {
369                 Stream str = EnsureNotDisposed();
370                 return str.CanWrite;
371             }
372         }
373 
374 
375         public UInt64 Position
376         {
377             get
378             {
379                 Contract.Ensures(Contract.Result<UInt64>() >= 0);
380 
381                 Stream str = EnsureNotDisposed();
382                 return (UInt64)str.Position;
383             }
384         }
385 
386 
387         public UInt64 Size
388         {
389             get
390             {
391                 Contract.Ensures(Contract.Result<UInt64>() >= 0);
392 
393                 Stream str = EnsureNotDisposed();
394                 return (UInt64)str.Length;
395             }
396 
397             set
398             {
399                 if (value > Int64.MaxValue)
400                 {
401                     ArgumentException ex = new ArgumentException(SR.IO_CannotSetSizeBeyondInt64MaxValue);
402                     ex.SetErrorCode(__HResults.E_INVALIDARG);
403                     throw ex;
404                 }
405 
406                 // Commented due to a reported CCRewrite bug. Should uncomment when fixed:
407                 //Contract.EndContractBlock();
408 
409                 Stream str = EnsureNotDisposed();
410 
411                 if (!str.CanWrite)
412                 {
413                     InvalidOperationException ex = new InvalidOperationException(SR.InvalidOperation_CannotSetStreamSizeCannotWrite);
414                     ex.SetErrorCode(__HResults.E_ILLEGAL_METHOD_CALL);
415                     throw ex;
416                 }
417 
418                 Int64 val = unchecked((Int64)value);
419 
420                 Debug.Assert(str != null);
421                 Debug.Assert(str.CanSeek, "The underlying str is expected to support Seek, but it does not.");
422                 Debug.Assert(0 <= val, "Unexpected val=" + val + ".");
423 
424                 str.SetLength(val);
425             }
426         }
427 
428         #endregion IRandomAccessStream public interface: Not cloning related
429 
430 
431         #region IRandomAccessStream public interface: Cloning related
432 
433         // We do not want to support the cloning-related operation for now.
434         // They appear to mainly target corner-case scenarios in Windows itself,
435         // and are (mainly) a historical artefact of abandoned early designs
436         // for IRandonAccessStream.
437         // Cloning can be added in future, however, it would be quite complex
438         // to support it correctly for generic streams.
439 
ThrowCloningNotSupported(String methodName)440         private static void ThrowCloningNotSupported(String methodName)
441         {
442             NotSupportedException nse = new NotSupportedException(SR.Format(SR.NotSupported_CloningNotSupported, methodName));
443             nse.SetErrorCode(__HResults.E_NOTIMPL);
444             throw nse;
445         }
446 
447 
CloneStream()448         public IRandomAccessStream CloneStream()
449         {
450             ThrowCloningNotSupported("CloneStream");
451             return null;
452         }
453 
454 
GetInputStreamAt(UInt64 position)455         public IInputStream GetInputStreamAt(UInt64 position)
456         {
457             ThrowCloningNotSupported("GetInputStreamAt");
458             return null;
459         }
460 
461 
GetOutputStreamAt(UInt64 position)462         public IOutputStream GetOutputStreamAt(UInt64 position)
463         {
464             ThrowCloningNotSupported("GetOutputStreamAt");
465             return null;
466         }
467         #endregion IRandomAccessStream public interface: Cloning related
468 
469         #endregion IRandomAccessStream public interface
470 
471     }  // class NetFxToWinRtStreamAdapter
472 }  // namespace
473 
474 // NetFxToWinRtStreamAdapter.cs
475