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.Runtime.InteropServices;
6 using System.Threading;
7 
8 namespace System.Net
9 {
10     internal sealed unsafe class HttpResponseStreamAsyncResult : LazyAsyncResult
11     {
12         private readonly ThreadPoolBoundHandle _boundHandle;
13         internal NativeOverlapped* _pOverlapped;
14         private Interop.HttpApi.HTTP_DATA_CHUNK[] _dataChunks;
15         internal bool _sentHeaders;
16 
17         private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(Callback);
18 
19         internal ushort dataChunkCount
20         {
21             get
22             {
23                 if (_dataChunks == null)
24                 {
25                     return 0;
26                 }
27                 else
28                 {
29                     return (ushort)_dataChunks.Length;
30                 }
31             }
32         }
33 
34         internal Interop.HttpApi.HTTP_DATA_CHUNK* pDataChunks
35         {
36             get
37             {
38                 if (_dataChunks == null)
39                 {
40                     return null;
41                 }
42                 else
43                 {
44                     return (Interop.HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(_dataChunks, 0));
45                 }
46             }
47         }
48 
HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback)49         internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback) : base(asyncObject, userState, callback)
50         {
51         }
52 
GetChunkHeader(int size, out int offset)53         private static byte[] GetChunkHeader(int size, out int offset)
54         {
55             if (NetEventSource.IsEnabled) NetEventSource.Enter(null, $"size:{size}");
56 
57             uint Mask = 0xf0000000;
58             byte[] Header = new byte[10];
59             int i;
60             offset = -1;
61 
62             //
63             // Loop through the size, looking at each nibble. If it's not 0
64             // convert it to hex. Save the index of the first non-zero
65             // byte.
66             //
67             for (i = 0; i < 8; i++, size <<= 4)
68             {
69                 //
70                 // offset == -1 means that we haven't found a non-zero nibble
71                 // yet. If we haven't found one, and the current one is zero,
72                 // don't do anything.
73                 //
74                 if (offset == -1)
75                 {
76                     if ((size & Mask) == 0)
77                     {
78                         continue;
79                     }
80                 }
81 
82                 //
83                 // Either we have a non-zero nibble or we're no longer skipping
84                 // leading zeros. Convert this nibble to ASCII and save it.
85                 //
86                 uint Temp = (uint)size >> 28;
87 
88                 if (Temp < 10)
89                 {
90                     Header[i] = (byte)(Temp + '0');
91                 }
92                 else
93                 {
94                     Header[i] = (byte)((Temp - 10) + 'A');
95                 }
96 
97                 //
98                 // If we haven't found a non-zero nibble yet, we've found one
99                 // now, so remember that.
100                 //
101                 if (offset == -1)
102                 {
103                     offset = i;
104                 }
105             }
106 
107             Header[8] = (byte)'\r';
108             Header[9] = (byte)'\n';
109 
110             if (NetEventSource.IsEnabled) NetEventSource.Exit(null);
111             return Header;
112         }
113 
114         private const string CRLF = "\r\n";
115         private static readonly byte[] s_CRLFArray = new byte[] { (byte)'\r', (byte)'\n' };
116 
HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders, ThreadPoolBoundHandle boundHandle)117         internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders, ThreadPoolBoundHandle boundHandle) : base(asyncObject, userState, callback)
118         {
119             _boundHandle = boundHandle;
120             _sentHeaders = sentHeaders;
121 
122             if (size == 0)
123             {
124                 _dataChunks = null;
125                 _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: null);
126             }
127             else
128             {
129                 _dataChunks = new Interop.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
130 
131                 if (NetEventSource.IsEnabled) NetEventSource.Info(this, "m_pOverlapped:0x" + ((IntPtr)_pOverlapped).ToString("x8"));
132 
133                 object[] objectsToPin = new object[1 + _dataChunks.Length];
134                 objectsToPin[_dataChunks.Length] = _dataChunks;
135 
136 
137                 int chunkHeaderOffset = 0;
138                 byte[] chunkHeaderBuffer = null;
139                 if (chunked)
140                 {
141                     chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset);
142 
143                     _dataChunks[0] = new Interop.HttpApi.HTTP_DATA_CHUNK();
144                     _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
145                     _dataChunks[0].BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);
146 
147                     objectsToPin[0] = chunkHeaderBuffer;
148 
149                     _dataChunks[1] = new Interop.HttpApi.HTTP_DATA_CHUNK();
150                     _dataChunks[1].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
151                     _dataChunks[1].BufferLength = (uint)size;
152 
153                     objectsToPin[1] = buffer;
154 
155                     _dataChunks[2] = new Interop.HttpApi.HTTP_DATA_CHUNK();
156                     _dataChunks[2].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
157                     _dataChunks[2].BufferLength = (uint)s_CRLFArray.Length;
158 
159                     objectsToPin[2] = s_CRLFArray;
160                 }
161                 else
162                 {
163                     _dataChunks[0] = new Interop.HttpApi.HTTP_DATA_CHUNK();
164                     _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
165                     _dataChunks[0].BufferLength = (uint)size;
166 
167                     objectsToPin[0] = buffer;
168                 }
169 
170                 // This call will pin needed memory
171                 _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: objectsToPin);
172 
173                 if (chunked)
174                 {
175                     _dataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer, chunkHeaderOffset));
176                     _dataChunks[1].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
177                     _dataChunks[2].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(s_CRLFArray, 0));
178                 }
179                 else
180                 {
181                     _dataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
182                 }
183             }
184         }
185 
IOCompleted(uint errorCode, uint numBytes)186         internal void IOCompleted(uint errorCode, uint numBytes)
187         {
188             IOCompleted(this, errorCode, numBytes);
189         }
190 
IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)191         private static void IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
192         {
193             if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"errorCode:0x {errorCode.ToString("x8")} numBytes: {numBytes}");
194             object result = null;
195             try
196             {
197                 if (errorCode != Interop.HttpApi.ERROR_SUCCESS && errorCode != Interop.HttpApi.ERROR_HANDLE_EOF)
198                 {
199                     asyncResult.ErrorCode = (int)errorCode;
200                     result = new HttpListenerException((int)errorCode);
201                 }
202                 else
203                 {
204                     // if we sent headers and body together, numBytes will be the total, but we need to only account for the data
205                     if (asyncResult._dataChunks == null)
206                     {
207                         result = (uint)0;
208                         if (NetEventSource.IsEnabled) { NetEventSource.DumpBuffer(null, IntPtr.Zero, 0); }
209                     }
210                     else
211                     {
212                         result = asyncResult._dataChunks.Length == 1 ? asyncResult._dataChunks[0].BufferLength : 0;
213                         if (NetEventSource.IsEnabled) { for (int i = 0; i < asyncResult._dataChunks.Length; i++) { NetEventSource.DumpBuffer(null, (IntPtr)asyncResult._dataChunks[0].pBuffer, (int)asyncResult._dataChunks[0].BufferLength); } }
214                     }
215                 }
216                 if (NetEventSource.IsEnabled) NetEventSource.Info(null, "Calling Complete()");
217             }
218             catch (Exception e)
219             {
220                 result = e;
221             }
222             asyncResult.InvokeCallback(result);
223         }
224 
Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)225         private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
226         {
227             object state = ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
228             HttpResponseStreamAsyncResult asyncResult = state as HttpResponseStreamAsyncResult;
229             if (NetEventSource.IsEnabled) NetEventSource.Info(null, "errorCode:0x" + errorCode.ToString("x8") + " numBytes:" + numBytes + " nativeOverlapped:0x" + ((IntPtr)nativeOverlapped).ToString("x8"));
230 
231             IOCompleted(asyncResult, errorCode, numBytes);
232         }
233 
234         // Will be called from the base class upon InvokeCallback()
Cleanup()235         protected override void Cleanup()
236         {
237             base.Cleanup();
238             if (_pOverlapped != null)
239             {
240                 _boundHandle.FreeNativeOverlapped(_pOverlapped);
241             }
242         }
243     }
244 }
245