File: System\Data\SqlClient\TdsParserSafeHandles.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="TdsParserSafeHandles.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data.SqlClient {
 
    using System;
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Threading;
    using System.Runtime.ConstrainedExecution;
 
    internal sealed class SNILoadHandle : SafeHandle {
        internal static readonly SNILoadHandle SingletonInstance = new SNILoadHandle();
 
        internal readonly SNINativeMethodWrapper.SqlAsyncCallbackDelegate ReadAsyncCallbackDispatcher  = new SNINativeMethodWrapper.SqlAsyncCallbackDelegate(ReadDispatcher);
        internal readonly SNINativeMethodWrapper.SqlAsyncCallbackDelegate WriteAsyncCallbackDispatcher = new SNINativeMethodWrapper.SqlAsyncCallbackDelegate(WriteDispatcher);
 
        private readonly UInt32            _sniStatus        = TdsEnums.SNI_UNINITIALIZED;
        private readonly EncryptionOptions _encryptionOption;
 
        private SNILoadHandle() : base(IntPtr.Zero, true) {
            // SQL BU DT 346588 - from security review - SafeHandle guarantees this is only called once.
            // The reason for the safehandle is guaranteed initialization and termination of SNI to
            // ensure SNI terminates and cleans up properly.
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                
                _sniStatus = SNINativeMethodWrapper.SNIInitialize();
 
                UInt32 value = 0;
 
                // VSDevDiv 479597: If initialize fails, don't call QueryInfo.
                if (TdsEnums.SNI_SUCCESS == _sniStatus) {
                    // Query OS to find out whether encryption is supported.
                    SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_CLIENT_ENCRYPT_POSSIBLE, ref value);
                }
 
                _encryptionOption = (value == 0) ? EncryptionOptions.NOT_SUP : EncryptionOptions.OFF;
 
                base.handle = (IntPtr) 1; // Initialize to non-zero dummy variable.
            }
        }
 
        public override bool IsInvalid {
            get {
                return (IntPtr.Zero == base.handle);
            }
        }
 
        override protected bool ReleaseHandle() {
            if (base.handle != IntPtr.Zero) {
                if (TdsEnums.SNI_SUCCESS == _sniStatus) {
                    LocalDBAPI.ReleaseDLLHandles();
                    SNINativeMethodWrapper.SNITerminate();
                }
                base.handle = IntPtr.Zero;
            }
 
            return true;
        }
 
        public UInt32 SNIStatus {
            get {
                return _sniStatus;
            }
        }
 
        public EncryptionOptions Options {
            get {
                return _encryptionOption;
            }
        }
 
        static private void ReadDispatcher(IntPtr key, IntPtr packet, UInt32 error) {
            // This is the app-domain dispatcher for all async read callbacks, It 
            // simply gets the state object from the key that it is passed, and 
            // calls the state object's read callback.
            Debug.Assert(IntPtr.Zero != key, "no key passed to read callback dispatcher?");
            if (IntPtr.Zero != key) {
                // NOTE: we will get a null ref here if we don't get a key that
                //       contains a GCHandle to TDSParserStateObject; that is 
                //       very bad, and we want that to occur so we can catch it.
                GCHandle gcHandle = (GCHandle)key;
                TdsParserStateObject stateObj = (TdsParserStateObject)gcHandle.Target;
 
                if (null != stateObj) {
                    stateObj.ReadAsyncCallback(IntPtr.Zero, packet, error);
                }
            }
        }
 
        static private void WriteDispatcher(IntPtr key, IntPtr packet, UInt32 error) {
            // This is the app-domain dispatcher for all async write callbacks, It 
            // simply gets the state object from the key that it is passed, and 
            // calls the state object's write callback.
            Debug.Assert(IntPtr.Zero != key, "no key passed to write callback dispatcher?");
            if (IntPtr.Zero != key) {
                // NOTE: we will get a null ref here if we don't get a key that
                //       contains a GCHandle to TDSParserStateObject; that is 
                //       very bad, and we want that to occur so we can catch it.
                GCHandle gcHandle = (GCHandle)key;
                TdsParserStateObject stateObj = (TdsParserStateObject)gcHandle.Target;
 
                if (null != stateObj) {
                    stateObj.WriteAsyncCallback(IntPtr.Zero, packet, error);
                }
            }
        }
    }
 
    internal sealed class SNIHandle : SafeHandle {
        private readonly UInt32 _status = TdsEnums.SNI_UNINITIALIZED;
        private readonly bool   _fSync = false;
 
        // creates a physical connection
        internal SNIHandle(
            SNINativeMethodWrapper.ConsumerInfo myInfo, 
            string serverName,
            byte[] spnBuffer, 
            bool ignoreSniOpenTimeout, 
            int timeout, 
            out byte[] instanceName, 
            bool flushCache, 
            bool fSync,
            bool fParallel,
            TransparentNetworkResolutionState transparentNetworkResolutionState,
            int totalTimeout)
            : base(IntPtr.Zero, true) {
 
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {
                _fSync = fSync;
                instanceName = new byte[256]; // Size as specified by netlibs.
                if (ignoreSniOpenTimeout) {
                    // 
 
 
                    timeout = Timeout.Infinite; // -1 == native SNIOPEN_TIMEOUT_VALUE / INFINITE
                }
 
                int transparentNetworkResolutionStateNo = (int)transparentNetworkResolutionState;
                _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle,
                            spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, transparentNetworkResolutionStateNo, totalTimeout,
                            ADP.IsAzureSqlServerEndpoint(serverName));
            }
        }
 
        // constructs SNI Handle for MARS session
        internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent) : base(IntPtr.Zero, true) {
            RuntimeHelpers.PrepareConstrainedRegions();
            try {} finally {          
                _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync);
            }
        }
 
        public override bool IsInvalid {
            get {
                return (IntPtr.Zero == base.handle);
            }
        }
 
        override protected bool ReleaseHandle() {
            // NOTE: The SafeHandle class guarantees this will be called exactly once.
            IntPtr ptr = base.handle;
            base.handle = IntPtr.Zero;
            if (IntPtr.Zero != ptr) {
                if (0 != SNINativeMethodWrapper.SNIClose(ptr)) {
                    return false;   // SNIClose should never fail.
                }
            }
            return true;
        }
 
        internal UInt32 Status {
            get {
                return _status;
            }
        }
    }
 
    internal sealed class SNIPacket : SafeHandle {
 
        internal SNIPacket(SafeHandle sniHandle) : base(IntPtr.Zero, true) {
            SNINativeMethodWrapper.SNIPacketAllocate(sniHandle, SNINativeMethodWrapper.IOType.WRITE, ref base.handle);
            if (IntPtr.Zero == base.handle) {
                throw SQL.SNIPacketAllocationFailure();
            }
        }
 
        public override bool IsInvalid {
            get {
                return (IntPtr.Zero == base.handle);
            }
        }
 
        override protected bool ReleaseHandle() {
            // NOTE: The SafeHandle class guarantees this will be called exactly once.
            IntPtr ptr = base.handle;
            base.handle = IntPtr.Zero;
            if (IntPtr.Zero != ptr) {
               SNINativeMethodWrapper.SNIPacketRelease(ptr);
            }
            return true;
        }
    }
 
    internal sealed class WritePacketCache : IDisposable {
            private bool _disposed;
            private Stack<SNIPacket> _packets;
 
            public WritePacketCache() {
                _disposed = false;
                _packets = new Stack<SNIPacket>();
            }
 
            public SNIPacket Take(SNIHandle sniHandle) {
                SNIPacket packet;
                if (_packets.Count > 0) {
                    // Success - reset the packet
                    packet = _packets.Pop();
                    SNINativeMethodWrapper.SNIPacketReset(sniHandle, SNINativeMethodWrapper.IOType.WRITE, packet, SNINativeMethodWrapper.ConsumerNumber.SNI_Consumer_SNI);
                }
                else {
                    // Failed to take a packet - create a new one
                    packet = new SNIPacket(sniHandle);
                }
                return packet;
            }
 
            public void Add(SNIPacket packet) {
                if (!_disposed) {
                    _packets.Push(packet);
                }
                else {
                    // If we're disposed, then get rid of any packets added to us
                    packet.Dispose();
                }
            }
 
            public void Clear() {
                while (_packets.Count > 0) {
                    _packets.Pop().Dispose();
                }
            }
 
            public void Dispose() {
                if (!_disposed) {
                    _disposed = true;
                    Clear();
                }
            }
        }
}