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