// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.sourceforge.jtds.jdbc;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.SocketFactory;

import net.sourceforge.jtds.ssl.*;
import net.sourceforge.jtds.util.Logger;

/**
 * This class manages the physical connection to the SQL Server and
 * serialises its use amongst a number of virtual sockets.
 * This allows one physical connection to service a number of concurrent
 * statements.
 * <p>
 * Constraints and assumptions:
 * <ol>
 * <li>Callers will not attempt to read from the server without issuing a request first.
 * <li>The end of a server reply can be identified as byte 2 of the header is non zero.
 * </ol>
 * </p>
 * Comments:
 * <ol>
 * <li>This code will discard unread server data if a new request is issued.
 *    Currently the higher levels of the driver attempt to do this but may be
 *    we can just rely on this code instead.
 * <li>A cancel can be issued by a caller only if the server is currently sending
 *    data for the caller otherwise the cancel is ignored.
 * <li>Cancel packets on their own are returned as extra records appended to the
 *     previous packet so that the TdsCore module can process them.
 * </ol>
 * This version of the class will start to cache results to disk once a predetermined
 * maximum buffer memory threshold has been passed. Small result sets that will fit
 * within a specified limit (default 8 packets) will continue to be held in memory
 * (even if the memory threshold has been passed) in the interests of efficiency.
 *
 * @author
 *    Mike Hutchinson, Holger Rehn
 */
class SharedSocket {
    /**
     * This inner class contains the state information for the virtual socket.
     */
    static class VirtualSocket {
        /**
         * The stream ID of the stream objects owning this state.
         */
        final int id;
        /**
         * Memory resident packet queue.
         */
        final LinkedList pktQueue;
        /**
         * File object for disk packet queue.
         */
        File queueFile;
        /**
         * I/O Stream for disk packet queue.
         */
        RandomAccessFile diskQueue;
        /**
         * Number of packets cached to disk.
         */
        int pktsOnDisk;
        /**
         * Total of input packets in memory or disk.
         */
        int inputPkts;
        /**
         * Construct object to hold state information for each caller.
         * @param streamId the Response/Request stream id.
         */
        private VirtualSocket(int streamId) {
            id = streamId;
            pktQueue = new LinkedList();
        }
    }

    /**
     * The shared network socket.
     */
    private Socket socket;
    /**
     * The shared SSL network socket;
     */
    private Socket sslSocket;
    /**
     * Output stream for network socket.
     */
    private DataOutputStream out;
    /**
     * Input stream for network socket.
     */
    private DataInputStream in;
    /**
     * Current maximum input buffer size.
     */
    private int maxBufSize = TdsCore.MIN_PKT_SIZE;
    /**
     * last ID assigned to a VirtualSocket instance
     */
    private final AtomicInteger _LastID = new AtomicInteger();
    /**
     * Table of stream objects sharing this socket.
     */
    private final ConcurrentMap<Integer,VirtualSocket> _VirtualSockets = new ConcurrentHashMap<>();
    /**
     * The virtual socket of the object that is expecting a response from the server.
     */
    private VirtualSocket responseOwner;
    /**
     * Buffer for packet header.
     */
    private final byte hdrBuf[] = new byte[TDS_HDR_LEN];
    /**
     * The directory to buffer data to.
     */
    private final File bufferDir;
    /**
     * Total memory usage in all instances of the driver
     * NB. Access to this field should probably be synchronized
     * but in practice lost updates will not matter much and I think
     * all VMs tend to do atomic saves to integer variables.
     */
    private static int globalMemUsage;
    /**
     * Peak memory usage for debug purposes.
     */
    private static int peakMemUsage;
    /**
     * Max memory limit to use for buffers.
     * Only when this limit is exceeded will the driver
     * start caching to disk.
     */
    private static int memoryBudget = 100000; // 100K
    /**
     * Minimum number of packets that will be cached in memory
     * before the driver tries to write to disk even if
     * memoryBudget has been exceeded.
     */
    private static int minMemPkts = 8;
    /**
     * Global flag to indicate that security constraints mean
     * that attempts to create work files will fail.
     */
    private static boolean securityViolation;
    /**
     * Tds protocol version
     */
    private int tdsVersion;
    /**
     * The servertype one of Driver.SQLSERVER or Driver.SYBASE
     */
    protected final int serverType;
    /**
     * The character set to use for converting strings to/from bytes.
     */
    private CharsetInfo charsetInfo;
    /**
     * Count of packets received.
     */
    private int packetCount;
    /**
     * The server host name.
     */
    private String host;
    /**
     * The server port number.
     */
    private int port;
    /**
     * A cancel packet is pending.
     */
    private boolean cancelPending;
    /**
     * Synchronization monitor for {@link #cancelPending} and
     * {@link #responseOwner}.
     */
    private final Object cancelMonitor = new Object();
    /**
     * Buffer for TDS_DONE packets
     */
    private final byte doneBuffer[] = new byte[TDS_DONE_LEN];
    /**
     * How much of the doneBuffer has been filled with data, <TDS_DONE_LEN IFF partial packet read.
     */
    private int doneBufferFrag = 0;
    /**
     * TDS done token.
     */
    private static final int TDS_DONE_TOKEN = 253;
    /**
     * Length of a TDS_DONE token.
     */
    private static final int TDS_DONE_LEN  = 9;
    /**
     * Length of TDS packet header.
     */
    private static final int TDS_HDR_LEN   = 8;

    protected SharedSocket(File bufferDir, int tdsVersion, int serverType) {
    	this.bufferDir = bufferDir;
        this.tdsVersion = tdsVersion;
        this.serverType = serverType;
    }

    /**
     * Construct a <code>SharedSocket</code> object specifying host name and
     * port.
     *
     * @param connection the connection object
     * @throws IOException if socket open fails
     */
    SharedSocket(JtdsConnection connection) throws IOException, UnknownHostException {
        this(connection.getBufferDir(), connection.getTdsVersion(), connection.getServerType());
        host = connection.getServerName();
        port = connection.getPortNumber();
        socket = createSocketForJDBC3(connection);
        setOut(new DataOutputStream(socket.getOutputStream()));
        setIn(new DataInputStream(socket.getInputStream()));
        socket.setTcpNoDelay(connection.getTcpNoDelay());
        socket.setSoTimeout(connection.getSocketTimeout() * 1000);
        socket.setKeepAlive(connection.getSocketKeepAlive());
    }

   /**
    * Creates a {@link Socket} connection.
    *
    * @param connection
    *    the connection object
    *
    * @return
    *    a socket open to the host and port with the given timeout
    *
    * @throws IOException
    *    if socket open fails
    */
   private Socket createSocketForJDBC3( JtdsConnection connection ) throws IOException
   {
      final String host = connection.getServerName();
      final int port = connection.getPortNumber();
      final String bindAddress = connection.getBindAddress();
      final int loginTimeout = connection.getLoginTimeout();

      Socket socket = new Socket();
      InetSocketAddress address = new InetSocketAddress( host, port );

      // call Socket.bind(SocketAddress) if bindAddress parameter is set
      if( bindAddress != null && ! bindAddress.isEmpty() )
      {
         socket.bind( new InetSocketAddress( bindAddress, 0 ) );
      }

      // establish connection
      socket.connect( address, loginTimeout * 1000 );
      return socket;
   }

   String getMAC()
   {
      try
      {
         NetworkInterface nic = NetworkInterface.getByInetAddress( socket.getLocalAddress() );
         byte[] address = nic == null ? null : nic.getHardwareAddress();

         if( address != null )
         {
            String mac = "";

            for( int k = 0; k < address.length; k ++ )
            {
               String macValue = String.format("%02X", address[k] );
               mac += macValue;
            }

            return mac;
         }
      }
      catch( SocketException e )
      {
         // error getting network interfaces, return null
      }

      return null;
   }

    /**
     * Enable TLS encryption by creating a TLS socket over the
     * existing TCP/IP network socket.
     *
     * @param ssl the SSL URL property value
     * @throws IOException if an I/O error occurs
     */
    void enableEncryption(String ssl) throws IOException {
        Logger.println("Enabling TLS encryption");
        SocketFactory sf = SocketFactories.getSocketFactory(ssl, socket);
        sslSocket = sf.createSocket(getHost(), getPort());
        setOut(new DataOutputStream(sslSocket.getOutputStream()));
        setIn(new DataInputStream(sslSocket.getInputStream()));
    }

    /**
     * Disable TLS encryption and switch back to raw TCP/IP socket.
     *
     * @throws IOException if an I/O error occurs
     */
    void disableEncryption() throws IOException {
        Logger.println("Disabling TLS encryption");
        sslSocket.close();
        sslSocket = null;
        setOut(new DataOutputStream(socket.getOutputStream()));
        setIn(new DataInputStream(socket.getInputStream()));
    }

    /**
     * Set the character set descriptor to be used to translate byte arrays to
     * or from Strings.
     *
     * @param charsetInfo the character set descriptor
     */
    void setCharsetInfo(CharsetInfo charsetInfo) {
        this.charsetInfo = charsetInfo;
    }

    /**
     * Retrieve the character set descriptor used to translate byte arrays to
     * or from Strings.
     */
    CharsetInfo getCharsetInfo() {
        return charsetInfo;
    }

    /**
     * Retrieve the character set name used to translate byte arrays to
     * or from Strings.
     *
     * @return the character set name as a <code>String</code>
     */
    String getCharset() {
        return charsetInfo.getCharset();
    }

   /**
    * Obtain an instance of a server request stream for this socket.
    *
    * @param bufferSize
    *    the initial buffer size to be used by the <code>RequestStream</code>
    *
    * @param maxPrecision
    *    the maximum precision for numeric/decimal types
    *
    * @return
    *    the server request stream as a <code>RequestStream</code>
    */
   RequestStream getRequestStream( int bufferSize, int maxPrecision )
   {
      int           id;
      VirtualSocket vsock;

      do
      {
         id    = _LastID.incrementAndGet();
         vsock = new VirtualSocket( id );
      }
      // safety net, ID might have already been assigned before integer overflow
      while( _VirtualSockets.putIfAbsent( id, vsock ) != null );

      return new RequestStream( this, vsock, bufferSize, maxPrecision );
   }

    /**
     * Obtain an instance of a server response stream for this socket.
     * NB. getRequestStream() must be used first to obtain the RequestStream
     * needed as a parameter for this method.
     *
     * @param requestStream an existing server request stream object obtained
     *                      from this <code>SharedSocket</code>
     * @param bufferSize    the initial buffer size to be used by the
     *                      <code>RequestStream</code>
     * @return the server response stream as a <code>ResponseStream</code>
     */
    ResponseStream getResponseStream(RequestStream requestStream, int bufferSize) {
        return new ResponseStream(this, requestStream.getVirtualSocket(), bufferSize);
    }

    /**
     * Retrieve the TDS version that is active on the connection
     * supported by this socket.
     *
     * @return the TDS version as an <code>int</code>
     */
    int getTdsVersion() {
        return tdsVersion;
    }

    /**
     * Set the TDS version field.
     *
     * @param tdsVersion the TDS version as an <code>int</code>
     */
    protected void setTdsVersion(int tdsVersion) {
        this.tdsVersion = tdsVersion;
    }

    /**
     * Set the global buffer memory limit for all instances of this driver.
     *
     * @param memoryBudget the global memory budget
     */
    static void setMemoryBudget(int memoryBudget) {
        SharedSocket.memoryBudget = memoryBudget;
    }

    /**
     * Get the global buffer memory limit for all instancs of this driver.
     *
     * @return the memory limit as an <code>int</code>
     */
    static int getMemoryBudget() {
        return SharedSocket.memoryBudget;
    }

    /**
     * Set the minimum number of packets to cache in memory before
     * writing to disk.
     *
     * @param minMemPkts the minimum number of packets to cache
     */
    static void setMinMemPkts(int minMemPkts) {
        SharedSocket.minMemPkts = minMemPkts;
    }

    /**
     * Get the minimum number of memory cached packets.
     *
     * @return minimum memory packets as an <code>int</code>
     */
    static int getMinMemPkts() {
        return SharedSocket.minMemPkts;
    }

    /**
     * Get the connected status of this socket.
     *
     * @return <code>true</code> if the underlying socket is connected
     */
    boolean isConnected() {
        return socket != null;
    }

    /**
     * Send a TDS cancel packet to the server.
     *
     * @param vsock
     *    the {@link VirtualSocket} used by the request to be canceled
     *
     * @return
     *    {@code true} if a cancel is actually issued by this method call
     */
    boolean cancel( VirtualSocket vsock ) {
        //
        // Need to synchronize packet send to avoid race conditions on
        // responsOwner and cancelPending
        //
        synchronized (cancelMonitor) {
            //
            // Only send if response pending for the caller.
            // Caller must have acquired connection mutex first.
            // NB. This method will not work with local named pipes
            // as this thread will be blocked in the write until the
            // reading thread has returned from the read.
            //
            if (responseOwner == vsock && !cancelPending) {
                try {
                    //
                    // Send a cancel packet.
                    //
                    cancelPending = true;
                    doneBufferFrag = 0;
                    byte[] cancel = new byte[TDS_HDR_LEN];
                    cancel[0] = TdsCore.CANCEL_PKT;
                    cancel[1] = 1;
                    cancel[2] = 0;
                    cancel[3] = 8;
                    cancel[4] = 0;
                    cancel[5] = 0;
                    cancel[6] = (tdsVersion >= Driver.TDS70) ? (byte) 1 : 0;
                    cancel[7] = 0;
                    getOut().write(cancel, 0, TDS_HDR_LEN);
                    getOut().flush();
                    if (Logger.isActive()) {
                        Logger.logPacket(vsock.id, false, cancel);
                    }
                    return true;
                } catch (IOException e) {
                    // Ignore error as network is probably dead anyway
                }
            }
        }
        return false;
    }

   /**
    * Close the socket and release all resources.
    *
    * @throws IOException
    *    if the socket close fails
    */
   void close()
      throws IOException
   {
      if( Logger.isActive() )
      {
         Logger.println( "TdsSocket: Max buffer memory used = " + (peakMemUsage / 1024) + "KB" );
      }

      // see if any temporary files need deleting
      for( VirtualSocket vsock : _VirtualSockets.values() )
      {
         if( vsock != null && vsock.diskQueue != null )
         {
            try
            {
               vsock.diskQueue.close();
               vsock.queueFile.delete();
            }
            catch( IOException ioe )
            {
               // ignore errors
            }
         }
      }

      _VirtualSockets.clear();

      try
      {
         if( sslSocket != null )
         {
            sslSocket.close();
            sslSocket = null;
         }
      }
      finally
      {
         // close physical socket
         if( socket != null )
         {
            socket.close();
         }
      }
   }

    /**
     * Force close the socket causing any pending reads/writes to fail.
     * <p>
     * Used by the login timer to abort a login attempt.
     */
    void forceClose() {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException ioe) {
                // Ignore
            } finally {
                sslSocket = null;
                socket = null;
            }
        }
    }

   /**
    * Deallocate a stream linked to this socket.
    *
    * @param vsock
    *    the {@link VirtualSocket} to close
    */
   void closeStream( VirtualSocket vsock )
   {
      // unregister virtual socket
      _VirtualSockets.remove( vsock.id );

      if( vsock.diskQueue != null )
      {
         try
         {
            vsock.diskQueue.close();
            vsock.queueFile.delete();
         }
         catch( IOException ioe )
         {
            // ignore errors
         }
      }
   }

    /**
     * Send a network packet. If output for another virtual socket is
     * in progress this packet will be sent later.
     *
     * @param vsock
     *    {@link VirtualSocket} of the originating {@link RequestStream}
     *
     * @param buffer
     *    the data to send
     *
     * @return
     *    the same buffer received if emptied or another buffer w/ the same size
     *    if the incoming buffer is cached (to avoid copying)
     *
     * @throws
     *    IOException if an I/O error occurs
     */
    byte[] sendNetPacket(VirtualSocket vsock, byte buffer[])
            throws IOException {
        synchronized (_VirtualSockets) {

            while (vsock.inputPkts > 0) {
                //
                // There is unread data in the input buffers.
                // As we are sending another packet we can just discard it now.
                //
                if (Logger.isActive()) {
                    Logger.println("TdsSocket: Unread data in input packet queue");
                }
                dequeueInput(vsock);
            }

            if (responseOwner != null) {
                //
                // Complex case there is another stream's data in the network pipe
                // or we had our own incomplete request to discard first
                // Read and store other stream's data or flush our own.
                //
                byte[] tmpBuf = null;
                boolean ourData = (responseOwner == vsock);
                final VirtualSocket tmpSock = responseOwner;
                do {
                    // Reuse the buffer if it's our data; we don't need it
                    tmpBuf = readPacket(ourData ? tmpBuf : null);

                    if (!ourData) {
                        // We need to save this input as it belongs to
                        // Another thread.
                        enqueueInput(tmpSock, tmpBuf);
                    }   // Any of our input is discarded.
                } while (tmpBuf[1] == 0); // Read all data to complete TDS packet
            }
            //
            // At this point we know that we are able to send the first
            // or subsequent packet of a new request.
            //
            getOut().write(buffer, 0, getPktLen(buffer));

            if (buffer[1] != 0) {
                getOut().flush();
                // We are the response owner now
                responseOwner = vsock;
            }

            return buffer;
        }
    }

    /**
     * Get a network packet. This may be read from the network directly or from
     * previously cached buffers.
     *
     * @param vsock
     *    {@link VirtualSocket} the originating ResponseStream object
     *
     * @param buffer
     *    the data buffer to receive the object (may be replaced)
     *
     * @return
     *    the data in a <code>byte[]</code> buffer
     *
     * @throws IOException
     *    if an I/O error occurs
     */
    byte[] getNetPacket(VirtualSocket vsock, byte buffer[]) throws IOException {
        synchronized (_VirtualSockets) {

            // Return any cached input
            if (vsock.inputPkts > 0) {
                return dequeueInput(vsock);
            }

            // Nothing cached see if we are expecting network data
            if (responseOwner == null)
                throw new IOException( "Stream " + vsock.id + " attempting to read when no request has been sent" );

            // OK There should be data, check that it is for this stream and we are not trying to read another thread's request.
            if (responseOwner != vsock)
                throw new IOException("Stream " + vsock.id + " is trying to read data that belongs to stream " + responseOwner.id );

            // Simple case we are reading our input directly from the server
            return readPacket(buffer);
        }
    }

    /**
     * Save a packet buffer in a memory queue or to a disk queue if the global
     * memory limit for the driver has been exceeded.
     *
     * @param vsock  the virtual socket owning this data
     * @param buffer the data to queue
     */
    private void enqueueInput(VirtualSocket vsock, byte[] buffer)
            throws IOException {
        //
        // Check to see if we should start caching to disk
        //
        if (globalMemUsage + buffer.length > memoryBudget &&
                vsock.pktQueue.size() >= minMemPkts &&
                !securityViolation &&
                vsock.diskQueue == null) {
            // Try to create a disk file for the queue
            try {
                vsock.queueFile = File.createTempFile("jtds", ".tmp", bufferDir);
                // vsock.queueFile.deleteOnExit(); memory leak, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6664633
                vsock.diskQueue = new RandomAccessFile(vsock.queueFile, "rw");

                // Write current cache contents to disk and free memory
                byte[] tmpBuf;

                while (vsock.pktQueue.size() > 0) {
                    tmpBuf = (byte[]) vsock.pktQueue.removeFirst();
                    vsock.diskQueue.write(tmpBuf, 0, getPktLen(tmpBuf));
                    vsock.pktsOnDisk++;
                }
            } catch (java.lang.SecurityException se) {
                // Not allowed to cache to disk so carry on in memory
                securityViolation = true;
                vsock.queueFile = null;
                vsock.diskQueue = null;
            }
        }

        if (vsock.diskQueue != null) {
            // Cache file exists so append buffer to it
            vsock.diskQueue.write(buffer, 0, getPktLen(buffer));
            vsock.pktsOnDisk++;
        } else {
            // Will cache in memory
            vsock.pktQueue.addLast(buffer);
            globalMemUsage += buffer.length;

            if (globalMemUsage > peakMemUsage) {
                peakMemUsage = globalMemUsage;
            }
        }

        vsock.inputPkts++;
    }

    /**
     * Read a cached packet from the in memory queue or from a disk based queue.
     *
     * @param vsock the virtual socket owning this data
     * @return a buffer containing the packet
     */
    private byte[] dequeueInput(VirtualSocket vsock)
            throws IOException {
        byte[] buffer = null;

        if (vsock.pktsOnDisk > 0) {
            // Data is cached on disk
            if (vsock.diskQueue.getFilePointer() == vsock.diskQueue.length()) {
                // First read so rewind() file
                vsock.diskQueue.seek(0L);
            }

            vsock.diskQueue.readFully(hdrBuf, 0, TDS_HDR_LEN);

            int len = getPktLen(hdrBuf);

            buffer = new byte[len];
            System.arraycopy(hdrBuf, 0, buffer, 0, TDS_HDR_LEN);
            vsock.diskQueue.readFully(buffer, TDS_HDR_LEN, len - TDS_HDR_LEN);
            vsock.pktsOnDisk--;

            if (vsock.pktsOnDisk < 1) {
                // File now empty so close and delete it
                try {
                    vsock.diskQueue.close();
                    vsock.queueFile.delete();
                } finally {
                    vsock.queueFile = null;
                    vsock.diskQueue = null;
                }
            }
        } else if (vsock.pktQueue.size() > 0) {
            buffer = (byte[]) vsock.pktQueue.removeFirst();
            globalMemUsage -= buffer.length;
        }

        if (buffer != null) {
            vsock.inputPkts--;
        }

        return buffer;
    }

    /**
     * Read a physical TDS packet from the network.
     *
     * @param buffer a buffer to read the data into (if it fits) or null
     * @return either the incoming buffer if it was large enough or a newly
     *         allocated buffer with the read packet
     */
    private byte[] readPacket(byte buffer[])
            throws IOException {
        //
        // Read rest of header
        try {
            getIn().readFully(hdrBuf);
        } catch (EOFException e) {
            throw new IOException("DB server closed connection.");
        }

        byte packetType = hdrBuf[0];

        if (packetType != TdsCore.LOGIN_PKT
                && packetType != TdsCore.QUERY_PKT
                && packetType != TdsCore.SYBQUERY_PKT // required to connect IBM/Netcool Omnibus, see patch [1844846]
                && packetType != TdsCore.REPLY_PKT) {
            throw new IOException("Unknown packet type 0x" +
                                    Integer.toHexString(packetType & 0xFF));
        }

        // figure out how many bytes are remaining in this packet.
        int len = getPktLen(hdrBuf);

        if (len < TDS_HDR_LEN || len > 65536) {
            throw new IOException("Invalid network packet length " + len);
        }

        if (buffer == null || len > buffer.length) {
            // Create or expand the buffer as required
            buffer = new byte[len];

            if (len > maxBufSize) {
                maxBufSize = len;
            }
        }

        // Preserve the packet header in the buffer
        System.arraycopy(hdrBuf, 0, buffer, 0, TDS_HDR_LEN);

        try {
            getIn().readFully(buffer, TDS_HDR_LEN, len - TDS_HDR_LEN);
        } catch (EOFException e) {
            throw new IOException("DB server closed connection.");
        }

        //
        // SQL Server 2000 < SP3 does not set the last packet
        // flag in the NT challenge packet.
        // If this is the first packet and the length is correct
        // force the last packet flag on.
        //
        if (++packetCount == 1 && serverType == Driver.SQLSERVER
                && "NTLMSSP".equals(new String(buffer, 11, 7))) {
            buffer[1] = 1;
        }

        synchronized (cancelMonitor) {
            //
            // If a cancel request is outstanding check that the last TDS packet
            // is a TDS_DONE with the "cancek ACK" flag set. If it isn't set the
            // "more packets" flag; this will ensure that the stream keeps
            // processing until the "cancel ACK" is processed.
            //
            if (cancelPending) {
                //
                // Move what we assume to be the TDS_DONE packet into doneBuffer
                // Reassembly might be required if packet is too short and TDS_DONE record was split
                // over multiple packets.
                //
                int frag = Math.min(TDS_DONE_LEN, len - TDS_HDR_LEN);
                int keep = TDS_DONE_LEN - frag;
                System.arraycopy(doneBuffer, frag, doneBuffer, 0, keep); // original portion to keep
                System.arraycopy(buffer, len - frag, doneBuffer, keep, frag); // new fragment tail
                doneBufferFrag = Math.min(TDS_DONE_LEN, doneBufferFrag + frag);
                //
                // If doneBuffer has not yet been fully filled then this cannot be the last packet.
                if (doneBufferFrag < TDS_DONE_LEN) {
                   buffer[1] = 0;
                }
                //
                // If this is the last packet and there is a cancel pending see
                // if the last packet contains a TDS_DONE token with the cancel
                // ACK set. If not reset the last packet flag so that the dedicated
                // cancel packet is also read and processed.
                //
                if (buffer[1] == 1) {
                    if ((doneBuffer[0] & 0xFF) < TDS_DONE_TOKEN) {
                        throw new IOException("Expecting a TDS_DONE or TDS_DONEPROC.");
                    }

                    if ((doneBuffer[1] & TdsCore.DONE_CANCEL) != 0) {
                        // OK have a cancel ACK packet
                        cancelPending = false;
                    } else {
                        // Must be in next packet so
                        // force client to read next packet
                        buffer[1] = 0;
                    }
                }
            }

            if (buffer[1] != 0) {
                // End of response; connection now free
                responseOwner = null;
            }
        }

        return buffer;
    }

    /**
     * Convert two bytes (in network byte order) in a byte array into a Java
     * short integer.
     *
     * @param buf    array of data
     * @return the 16 bit unsigned value as an <code>int</code>
     */
    static int getPktLen(byte buf[]) {
        int lo = (buf[3] & 0xff);
        int hi = ((buf[2] & 0xff) << 8);

        return hi | lo;
    }

    /**
     * Set the socket timeout.
     *
     * @param timeout the timeout value in milliseconds
     */
    protected void setTimeout(int timeout) throws SocketException {
        socket.setSoTimeout(timeout);
    }

    /**
     * Set the socket keep alive.
     *
     * @param keepAlive <code>true</code> to turn on socket keep alive
     */
    protected void setKeepAlive(boolean keepAlive) throws SocketException {
        socket.setKeepAlive(keepAlive);
    }

    /**
     * Getter for {@link SharedSocket#in} field.
     *
     * @return {@link InputStream} used for communication
     */
    protected DataInputStream getIn() {
        return in;
    }

    /**
     * Setter for {@link SharedSocket#in} field.
     *
     * @param in the {@link InputStream} to be used for communication
     */
    protected void setIn(DataInputStream in) {
        this.in = in;
    }

    /**
     * Getter for {@link SharedSocket#out} field.
     *
     * @return {@link OutputStream} used for communication
     */
    protected DataOutputStream getOut() {
        return out;
    }

    /**
     * Setter for {@link SharedSocket#out} field.
     *
     * @param out the {@link OutputStream} to be used for communication
     */
    protected void setOut(DataOutputStream out) {
        this.out = out;
    }

    /**
     * Get the server host name.
     *
     * @return the host name as a <code>String</code>
     */
    protected String getHost() {
        return host;
    }

    /**
     * Get the server port number.
     *
     * @return the host port as an <code>int</code>
     */
    protected int getPort() {
        return port;
    }

   /**
    * Ensure all resources are released.
    */
   protected void finalize()
      throws Throwable
   {
      try
      {
         close();
      }
      finally
      {
         super.finalize();
      }
   }

}