/**
 * This code is free software; you can redistribute it and/or modify it under
 * the terms of the new BSD License.
 *
 * Copyright (c) 2008-2009, Sebastian Staudt
 */

package steamcondenser.steam.sockets;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.concurrent.TimeoutException;

import steamcondenser.PacketFormatException;
import steamcondenser.SteamCondenserException;
import steamcondenser.steam.packets.SteamPacket;
import steamcondenser.steam.packets.SteamPacketFactory;

/**
 * Defines common methods for sockets used to connect to game and master
 * servers.
 * @author Sebastian Staudt
 */
abstract public class SteamSocket
{
    protected ByteBuffer buffer;
    protected SelectableChannel channel;
    protected InetSocketAddress remoteSocket;

    /**
     * Creates a new SteamSocket used to connect to the given IP address and
     * port number
     * @param ipAddress The IP of the server to connect to
     * @param portNumber The port number of the server
     */
    protected SteamSocket(InetAddress ipAddress, int portNumber)
    {
        this.buffer = ByteBuffer.allocate(1400);
        this.buffer.order(ByteOrder.LITTLE_ENDIAN);

        this.remoteSocket = new InetSocketAddress(ipAddress, portNumber);
    }

    /**
     * Reads a single packet from the buffer into a SteamPacket object
     * @return The SteamPacket object created from the data in the buffer
     * @throws PacketFormatException When the SteamPacket could not be created
     *         because of an format error
     */
    protected SteamPacket getPacketFromData()
            throws PacketFormatException
    {
        byte[] packetData = new byte[this.buffer.remaining()];
        this.buffer.get(packetData);

        return SteamPacketFactory.getPacketFromData(packetData);
    }

    /**
     * Subclasses have to implement this method for their individual packet
     * format
     * @return The SteamPacket received
     * @throws IOException
     * @throws Exception
     */
    abstract public SteamPacket getReply()
            throws IOException, TimeoutException, SteamCondenserException;

    /**
     * Reads an UDP packet into an existing or a new buffer
     * @param bufferLength The length of the new buffer to created or 0 to use
     *        the existing buffer
     * @return The number of bytes received
     * @throws IOException
     * @throws TimeoutException
     * TODO select() seems to "miss" some packets (e.g. long RCON responses)
     */
    protected int receivePacket(int bufferLength)
            throws IOException, TimeoutException
    {
        Selector selector = Selector.open();
        this.channel.register(selector, SelectionKey.OP_READ);

        if (selector.select(1000) == 0) {
            selector.close();
            throw new TimeoutException();
        }

        int bytesRead;

        if (bufferLength == 0) {
            this.buffer.clear();
        } else {
            this.buffer = ByteBuffer.allocate(bufferLength);
        }

        bytesRead = ((ReadableByteChannel) this.channel).read(this.buffer);
        if(bytesRead > 0) {
            this.buffer.rewind();
            this.buffer.limit(bytesRead);
        }

        selector.close();

        return bytesRead;
    }

    /**
     * Closes the DatagramChannel
     */
    @Override
    public void finalize()
            throws IOException
    {
        this.channel.close();
    }
}

@@ -143,10 +143,10 @@
 8-20
+1
 0
-9
 , Se
@@ -917,16 +917,56 @@
 ocket%0A%7B%0A
+    private static int timeout = 1000;%0A%0A
     prot
@@ -1078,16 +1078,602 @@
 ocket;%0A%0A
+    /**%0A     * Sets the timeout for socket operations. This usually only affects%0A     * timeouts, i.e. when a server does not respond in time.%0A     *%0A     * Due to the server-side implementation of the RCON protocol, each RCON%0A     * request will also wait this amount of time after execution. So if you%0A     * need RCON requests to execute fast, you should set this to a adequatly%0A     * low value.%0A     *%0A     * @param timeout The amount of milliseconds before a request times out%0A     */%0A    public static void setTimeout(int timeout) %7B%0A        SteamSocket.timeout = timeout;%0A    %7D%0A%0A
     /**%0A
@@ -3620,20 +3620,35 @@
 .select(
-1000
+SteamSocket.timeout
 ) == 0) 

Allow customization of socket timeouts