// ------------------------------------------------------------------------------
// <copyright file="_HttpResponseStream.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// ------------------------------------------------------------------------------

namespace System.Net {
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Threading.Tasks;
    using System.Diagnostics.CodeAnalysis;

    unsafe class HttpResponseStream : Stream {
        private HttpListenerContext m_HttpContext;
        private long m_LeftToWrite = long.MinValue;
        private bool m_Closed;
        private bool m_InOpaqueMode;
        // The last write needs special handling to cancel.
        private HttpResponseStreamAsyncResult m_LastWrite;

        internal HttpResponseStream(HttpListenerContext httpContext) {
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::.ctor() HttpListenerContext##" + ValidationHelper.HashString(httpContext));
            m_HttpContext = httpContext;
        }

        internal UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS ComputeLeftToWrite() {
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::ComputeLeftToWrite() on entry m_LeftToWrite:" + m_LeftToWrite);
            UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
            if (!m_HttpContext.Response.ComputedHeaders) {
                flags = m_HttpContext.Response.ComputeHeaders();
            }
            if (m_LeftToWrite==long.MinValue) {
                UnsafeNclNativeMethods.HttpApi.HTTP_VERB method = m_HttpContext.GetKnownMethod();
                m_LeftToWrite = method != UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD ? m_HttpContext.Response.ContentLength64 : 0;
                GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::ComputeLeftToWrite() computed m_LeftToWrite:" + m_LeftToWrite);                
            }
            return flags;
        }

        public override bool CanSeek {
            get {
                return false;
            }
        }

        public override bool CanWrite {
            get {
                return true;
            }
        }

        public override bool CanRead {
            get {
                return false;
            }
        }

        internal bool Closed
        {
            get
            {
                return m_Closed;
            }
        }

        internal HttpListenerContext InternalHttpContext
        {
            get
            {
                return m_HttpContext;
            }
        }

        internal void SetClosedFlag()
        {
            m_Closed = true;
        }

        public override void Flush() {
        }

        public override Task FlushAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }

        public override long Length {
            get {
                throw new NotSupportedException(SR.GetString(SR.net_noseek));
            }

        }

        public override long Position {
            get {
                throw new NotSupportedException(SR.GetString(SR.net_noseek));
            }
            set {
                throw new NotSupportedException(SR.GetString(SR.net_noseek));
            }
        }

        public override long Seek(long offset, SeekOrigin origin) {
            throw new NotSupportedException(SR.GetString(SR.net_noseek));
        }

        public override void SetLength(long value) {
            throw new NotSupportedException(SR.GetString(SR.net_noseek));
        }

        public override int Read([In, Out] byte[] buffer, int offset, int size) {
            throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
        }

        [HostProtection(ExternalThreading=true)]
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
            throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
        }

        public override int EndRead(IAsyncResult asyncResult) {
            throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
        }

        public override void Write(byte[] buffer, int offset, int size) {
            if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Write", "");
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset);
            if (buffer==null) {
                throw new ArgumentNullException("buffer");
            }
            if (offset<0 || offset>buffer.Length) {
                throw new ArgumentOutOfRangeException("offset");
            }
            if (size<0 || size>buffer.Length-offset) {
                throw new ArgumentOutOfRangeException("size");
            }
            UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
            if (m_Closed || (size == 0 && m_LeftToWrite != 0)) {
                if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Write", "");
                return;
            }
            if (m_LeftToWrite>=0 && size>m_LeftToWrite) {
                throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig));
            }

            uint statusCode;
            uint dataToWrite = (uint)size;                                  
            SafeLocalFree bufferAsIntPtr = null;
            IntPtr pBufferAsIntPtr = IntPtr.Zero;
            bool sentHeaders = m_HttpContext.Response.SentHeaders;
            try {
                if (size == 0)
                {
                    statusCode = m_HttpContext.Response.SendHeaders(null, null, flags, false);
                }
                else
                {
                    fixed (byte* pDataBuffer = buffer) {
                        byte* pBuffer = pDataBuffer;
                        if (m_HttpContext.Response.BoundaryType == BoundaryType.Chunked) {
                            // 


                            string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture);
                            dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4);
                            bufferAsIntPtr = SafeLocalFree.LocalAlloc((int)dataToWrite);
                            pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle();
                            for (int i = 0; i < chunkHeader.Length; i++) {
                                Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]);
                            }
                            Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D);
                            Marshal.Copy(buffer, offset, IntPtrHelper.Add(pBufferAsIntPtr, chunkHeader.Length + 2), size);
                            Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D);
                            pBuffer = (byte*)pBufferAsIntPtr;
                            offset = 0;
                        }
                        UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                        dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                        dataChunk.pBuffer = (byte*)(pBuffer + offset);
                        dataChunk.BufferLength = dataToWrite;

                        flags |= m_LeftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
                        if (!sentHeaders) {
                            statusCode = m_HttpContext.Response.SendHeaders(&dataChunk, null, flags, false);
                        }
                        else {
                            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");

                            statusCode =
                                UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
                                    m_HttpContext.RequestQueueHandle,
                                    m_HttpContext.RequestId,
                                    (uint)flags,
                                    1,
                                    &dataChunk,
                                    null,
                                    SafeLocalFree.Zero,
                                    0,
                                    null,
                                    null);

                            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
                            if (m_HttpContext.Listener.IgnoreWriteExceptions) {
                                GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() suppressing error");
                                statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
                            }
                        }
                    }
                }
            }
            finally {
                if (bufferAsIntPtr != null) {
                    // free unmanaged buffer
                    bufferAsIntPtr.Close();
                }
            }

            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
                Exception exception = new HttpListenerException((int)statusCode);
                if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Write", exception);
                m_Closed = true;
                m_HttpContext.Abort();
                throw exception;
            }
            UpdateAfterWrite(dataToWrite);
            if(Logging.On)Logging.Dump(Logging.HttpListener, this, "Write", buffer, offset, (int)dataToWrite);
            if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Write", "");
        }


        [HostProtection(ExternalThreading=true)]
        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
           GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset);
            if (buffer==null) {
                throw new ArgumentNullException("buffer");
            }
            if (offset<0 || offset>buffer.Length) {
                throw new ArgumentOutOfRangeException("offset");
            }
            if (size<0 || size>buffer.Length-offset) {
                throw new ArgumentOutOfRangeException("size");
            }
            UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
            if (m_Closed || (size == 0 && m_LeftToWrite != 0)) {
                if(Logging.On)Logging.Exit(Logging.HttpListener, this, "BeginWrite", "");
                HttpResponseStreamAsyncResult result = new HttpResponseStreamAsyncResult(this, state, callback);
                result.InvokeCallback((uint) 0);
                return result;
            }
            if (m_LeftToWrite>=0 && size>m_LeftToWrite) {
                throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig));
            }

            uint statusCode;
            uint bytesSent = 0;
            flags |= m_LeftToWrite==size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
            bool sentHeaders = m_HttpContext.Response.SentHeaders;
            HttpResponseStreamAsyncResult asyncResult = new HttpResponseStreamAsyncResult(this, state, callback, buffer, offset, size, m_HttpContext.Response.BoundaryType==BoundaryType.Chunked, sentHeaders);
            
            // Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
            UpdateAfterWrite((uint)((m_HttpContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));

            try {
                if (!sentHeaders) {
                    statusCode = m_HttpContext.Response.SendHeaders(null, asyncResult, flags, false);
                }
                else {
                    GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");

                    m_HttpContext.EnsureBoundHandle();
                    statusCode =
                        UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
                            m_HttpContext.RequestQueueHandle,
                            m_HttpContext.RequestId,
                            (uint)flags,
                            asyncResult.dataChunkCount,
                            asyncResult.pDataChunks,
                            &bytesSent,
                            SafeLocalFree.Zero,
                            0,
                            asyncResult.m_pOverlapped,
                            null);

                    GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
                }
            }
            catch (Exception e) {
                if (Logging.On) Logging.Exception(Logging.HttpListener, this, "BeginWrite", e);
                asyncResult.InternalCleanup();
                m_Closed = true;
                m_HttpContext.Abort();
                throw;
            }

            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) {
                asyncResult.InternalCleanup();
                if (m_HttpContext.Listener.IgnoreWriteExceptions && sentHeaders) {
                    GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() suppressing error");
                }
                else {
                    Exception exception = new HttpListenerException((int)statusCode);
                    if (Logging.On) Logging.Exception(Logging.HttpListener, this, "BeginWrite", exception);
                    m_Closed = true;
                    m_HttpContext.Abort();
                    throw exception;
                }
            }

            if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess)
            {
                // IO operation completed synchronously - callback won't be called to signal completion.
                asyncResult.IOCompleted(statusCode, bytesSent);
            }

            // Last write, cache it for special cancelation handling.
            if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
            {
                m_LastWrite = asyncResult;
            }

            if(Logging.On)Logging.Exit(Logging.HttpListener, this, "BeginWrite", "");
            return asyncResult;
        }

        public override void EndWrite(IAsyncResult asyncResult) {
            if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndWrite", "");
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite() asyncResult#" + ValidationHelper.HashString(asyncResult));
            if (asyncResult==null) {
                throw new ArgumentNullException("asyncResult");
            }
            HttpResponseStreamAsyncResult castedAsyncResult = asyncResult as HttpResponseStreamAsyncResult;
            if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
                throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
            }
            if (castedAsyncResult.EndCalled) {
                throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite"));
            }
            castedAsyncResult.EndCalled = true;
            // wait & then check for errors
            object returnValue = castedAsyncResult.InternalWaitForCompletion();

            Exception exception = returnValue as Exception;
            if (exception!=null) {
                GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite() rethrowing exception:" + exception);
                if(Logging.On)Logging.Exception(Logging.HttpListener, this, "EndWrite", exception);
                m_Closed = true;
                m_HttpContext.Abort();
                throw exception;
            }
            // 


            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite()");
            if(Logging.On)Logging.Exit(Logging.HttpListener, this, "EndWrite", "");
        }

        void UpdateAfterWrite(uint dataWritten) {
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::UpdateAfterWrite() dataWritten:" + dataWritten + " m_LeftToWrite:" + m_LeftToWrite + " m_Closed:" + m_Closed);
            if (!m_InOpaqueMode)
            {
                if (m_LeftToWrite>0) {
                    // keep track of the data transferred
                    m_LeftToWrite -= dataWritten;
                }
                if (m_LeftToWrite==0) {
                    // in this case we already passed 0 as the flag, so we don't need to call HttpSendResponseEntityBody() when we Close()
                    m_Closed = true;
                }
            }
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::UpdateAfterWrite() dataWritten:" + dataWritten + " m_LeftToWrite:" + m_LeftToWrite + " m_Closed:" + m_Closed);
        }

        protected override void Dispose(bool disposing) {
            if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Close", "");
                
            try {
                if(disposing){
                    GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() m_Closed:" + m_Closed);
                    if (m_Closed) {
                        if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
                        return;
                    }
                    m_Closed = true;
                    UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
                    if (m_LeftToWrite>0  && !m_InOpaqueMode) {
                        throw new InvalidOperationException(SR.GetString(SR.net_io_notenoughbyteswritten));
                    }
                    bool sentHeaders = m_HttpContext.Response.SentHeaders;
                    if (sentHeaders && m_LeftToWrite==0) {
                        if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
                        return;
                    }
        
                    uint statusCode = 0;
                    if ((m_HttpContext.Response.BoundaryType==BoundaryType.Chunked || m_HttpContext.Response.BoundaryType==BoundaryType.None) && (String.Compare(m_HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)!=0)) {
                        if (m_HttpContext.Response.BoundaryType==BoundaryType.None) {
                            flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
                        }
                        fixed (void* pBuffer = NclConstants.ChunkTerminator)
                        {
                            UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunk = null;
                            if (m_HttpContext.Response.BoundaryType==BoundaryType.Chunked) {
                                UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                                dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                                dataChunk.pBuffer = (byte *)pBuffer;
                                dataChunk.BufferLength = (uint) NclConstants.ChunkTerminator.Length;
                                pDataChunk = &dataChunk;
                            }
                            if (!sentHeaders) {
                                statusCode = m_HttpContext.Response.SendHeaders(pDataChunk, null, flags, false);
                            }
                            else {
                                GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");
        
                                statusCode =
                                    UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
                                        m_HttpContext.RequestQueueHandle,
                                        m_HttpContext.RequestId,
                                        (uint)flags,
                                        pDataChunk!=null ? (ushort)1 : (ushort)0,
                                        pDataChunk,
                                        null,
                                        SafeLocalFree.Zero,
                                        0,
                                        null,
                                        null);
        
                                GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
                                if (m_HttpContext.Listener.IgnoreWriteExceptions) {
                                    GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() suppressing error");
                                    statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
                                }
                            }
                        }
                    }
                    else {
                        if (!sentHeaders) {
                            statusCode = m_HttpContext.Response.SendHeaders(null, null, flags, false);
                        }
                    }
                    if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
                        Exception exception = new HttpListenerException((int)statusCode);
                        if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Close", exception);
                        m_HttpContext.Abort();
                        throw exception;
                    }
                    m_LeftToWrite = 0;
                }
            }
            finally {
                base.Dispose(disposing);
            }
            if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Dispose", "");
        }

        internal void SwitchToOpaqueMode()
        {
            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::SwitchToOpaqueMode()");
            m_InOpaqueMode = true;
            m_LeftToWrite = long.MaxValue;
        }

        // The final Content-Length async write can only be cancelled by CancelIoEx.
        // [....] can only be cancelled by CancelSynchronousIo, but we don't attempt this right now.
        [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification =
            "It is safe to ignore the return value on a cancel operation because the connection is being closed")]
        internal void CancelLastWrite(CriticalHandle requestQueueHandle)
        {
            HttpResponseStreamAsyncResult asyncState = m_LastWrite;
            if (asyncState != null && !asyncState.IsCompleted)
            {
                UnsafeNclNativeMethods.CancelIoEx(requestQueueHandle, asyncState.m_pOverlapped);
            }
        }
    }

    unsafe class HttpResponseStreamAsyncResult : LazyAsyncResult {
        internal NativeOverlapped* m_pOverlapped;
        private UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[] m_DataChunks;
        internal bool m_SentHeaders;

        private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(Callback);

        internal ushort dataChunkCount {
            get {
                if (m_DataChunks == null) {
                    return 0;
                }
                else {
                    return (ushort)m_DataChunks.Length;
                }
            }
        }

        internal UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunks {
            get {
                if (m_DataChunks == null) {
                    return null;
                } 
                else  {
                    return (UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(m_DataChunks, 0));
                }
            }
        }

        internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback) : base(asyncObject, userState, callback) {
        }


        internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders): base(asyncObject, userState, callback){
            m_SentHeaders = sentHeaders;
            Overlapped overlapped = new Overlapped();
            overlapped.AsyncResult = this;

            if (size == 0) {
                m_DataChunks = null;
                m_pOverlapped = overlapped.Pack(s_IOCallback, null);
            }
            else {
                m_DataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];

                GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() m_pOverlapped:0x" + ((IntPtr)m_pOverlapped).ToString("x8"));

                object[] objectsToPin = new object[1 + m_DataChunks.Length];
                objectsToPin[m_DataChunks.Length] = m_DataChunks;


                int chunkHeaderOffset = 0;
                byte[] chunkHeaderBuffer = null;
                if (chunked) {
                    chunkHeaderBuffer = ConnectStream.GetChunkHeader(size, out chunkHeaderOffset);

                    m_DataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                    m_DataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    m_DataChunks[0].BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);

                    objectsToPin[0] = chunkHeaderBuffer;

                    m_DataChunks[1] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                    m_DataChunks[1].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    m_DataChunks[1].BufferLength = (uint)size;

                    objectsToPin[1] = buffer;

                    m_DataChunks[2] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                    m_DataChunks[2].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    m_DataChunks[2].BufferLength = (uint)NclConstants.CRLF.Length;

                    objectsToPin[2] = NclConstants.CRLF;

                }
                else {
                    m_DataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
                    m_DataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    m_DataChunks[0].BufferLength = (uint)size;

                    objectsToPin[0] = buffer;
                }

                // This call will pin needed memory
                m_pOverlapped = overlapped.Pack(s_IOCallback, objectsToPin);

                if (chunked)
                {
                    m_DataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer, chunkHeaderOffset));
                    m_DataChunks[1].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
                    m_DataChunks[2].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(NclConstants.CRLF, 0));
                }
                else
                {
                    m_DataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
                }

            }
        }

        internal void IOCompleted(uint errorCode, uint numBytes)
        {
            IOCompleted(this, errorCode, numBytes);
        }

        private static void IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
        {
            GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() errorCode:0x" + errorCode.ToString("x8") + " numBytes:" + numBytes);
            object result = null;
            try {
                if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
                    asyncResult.ErrorCode = (int)errorCode;
                    result = new HttpListenerException((int)errorCode);
                }
                else {
                    // if we sent headers and body together, numBytes will be the total, but we need to only account for the data
                    // result = numBytes;
                    if (asyncResult.m_DataChunks == null) {
                        result = (uint) 0;
                        if (Logging.On) { Logging.Dump(Logging.HttpListener, asyncResult, "Callback", IntPtr.Zero, 0); }
                    }
                    else {
                        result = asyncResult.m_DataChunks.Length == 1 ? asyncResult.m_DataChunks[0].BufferLength : 0;
                        if (Logging.On) { for (int i = 0; i < asyncResult.m_DataChunks.Length; i++) { Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult.m_DataChunks[0].pBuffer, (int)asyncResult.m_DataChunks[0].BufferLength); } }
                    }
                }
                GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() calling Complete()");
            }
            catch (Exception e) {
                result = e;
            }
            asyncResult.InvokeCallback(result);
        }

        private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) {
            Overlapped callbackOverlapped = Overlapped.Unpack(nativeOverlapped);
            HttpResponseStreamAsyncResult asyncResult = callbackOverlapped.AsyncResult as HttpResponseStreamAsyncResult;
            GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() errorCode:0x" + errorCode.ToString("x8") + " numBytes:" + numBytes + " nativeOverlapped:0x" + ((IntPtr)nativeOverlapped).ToString("x8"));
            
            IOCompleted(asyncResult, errorCode, numBytes);
        }

        // Will be called from the base class upon InvokeCallback()
        protected override void Cleanup() {
            base.Cleanup();
            if (m_pOverlapped != null) {
                Overlapped.Free(m_pOverlapped);
            }
        }

    }

}
