// 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();
}
}
}