Coverage Report - net.sourceforge.rconed.CODRcon
 
Classes in this File Line Coverage Branch Coverage Complexity
CODRcon
0 %
0/55
0 %
0/14
5
CODRconPacket
0 %
0/24
0 %
0/24
5
 
 1  
 package net.sourceforge.rconed;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.net.DatagramPacket;
 5  
 import java.net.DatagramSocket;
 6  
 import java.net.InetAddress;
 7  
 import java.net.SocketTimeoutException;
 8  
 
 9  
 import net.sourceforge.rconed.exception.BadRcon;
 10  
 import net.sourceforge.rconed.exception.UnexpectedDataException;
 11  
 
 12  
 /**
 13  
  * Rcon is a simple Java library for issuing RCON commands to game servers.
 14  
  * <p/>
 15  
  * CODRcon is an extension to the library to allow for Call of Duty based
 16  
  * servers to be connected against. This has currently only been tested with
 17  
  * Call of Duty 2 servers, but the communication protocol should be valid for
 18  
  * all current Call of Duty versions.
 19  
  * <p/>
 20  
  * Example:
 21  
  * <p/>
 22  
  * response = CODRcon.send("127.0.0.1", 28960, rconPassword, "status");
 23  
  * <p/>
 24  
 
 25  
  * Copied bulk of original code from Rcon, making modifications where
 26  
  * necessary for Call of Duty servers
 27  
  * <p/>
 28  
  * @author DeadEd, David Hayes
 29  
  * @version 1.1
 30  
  */
 31  0
 public class CODRcon {
 32  
 
 33  
     private static final int RESPONSE_TIMEOUT = 2000;
 34  
     private static final int MULTIPLE_PACKETS_TIMEOUT = 300;
 35  
 
 36  
     /**
 37  
      * Send the RCON request.  Sends the command to the game server. This version allows for
 38  
      * a local portless request to be made.
 39  
      * <p/>
 40  
      * As the communication travels via an unreliable method (at the choice of the Call of
 41  
      * Duty developers), the order in which the message will be returned is not guaranteed,
 42  
      * nor is the fact that you will receive a complete message. It is more advisable to call
 43  
      * commands that result in shorter returns from the console, than commands that return
 44  
      * longer results.
 45  
      *
 46  
      * @param ipStr     The IP (as a String) of the machine where the RCON command will go.
 47  
      * @param port      The port of the machine where the RCON command will go.
 48  
      * @param password  The RCON password.
 49  
      * @param command   The RCON command (without the rcon prefix).
 50  
      * @return The reponse text from the server after trying the RCON command.
 51  
      * @throws SocketTimeoutException when there is any problem communicating with the server.
 52  
      */
 53  
     public static String send(String ipStr, int port, String password, String command)
 54  
                     throws SocketTimeoutException, BadRcon, UnexpectedDataException {
 55  
                 
 56  0
             return send (-1, ipStr, port, password, command);
 57  
             
 58  
         }
 59  
     
 60  
     /**
 61  
      * Send the RCON request.  Sends the command to the game server.  A port
 62  
      * (localPort) must be opened to send the command through.
 63  
      * <p/>
 64  
      * As the communication travels via an unreliable method (at the choice of the Call of
 65  
      * Duty developers), the order in which the message will be returned is not guaranteed,
 66  
      * nor is the fact that you will receive a complete message. It is more advisable to call
 67  
      * commands that result in shorter returns from the console, than commands that return
 68  
      * longer results.
 69  
      *
 70  
      * @param localPort The port on the local machine where the RCON request can be made from.
 71  
      * @param ipStr     The IP (as a String) of the machine where the RCON command will go.
 72  
      * @param port      The port of the machine where the RCON command will go.
 73  
      * @param password  The RCON password.
 74  
      * @param command   The RCON command (without the rcon prefix).
 75  
      * @return The reponse text from the server after trying the RCON command.
 76  
      * @throws SocketTimeoutException when there is any problem communicating with the server.
 77  
      */
 78  
     public static String send(int localPort, String ipStr, int port, String password, String command)
 79  
             throws SocketTimeoutException, BadRcon, UnexpectedDataException {
 80  
 
 81  0
         CODRconPacket[] requested = sendRequest(localPort, ipStr, port, password, command);
 82  
 
 83  0
         String response = assemblePacket(requested);
 84  
 
 85  0
         if (response.matches("Invalid password.\n")) {
 86  0
                 throw new BadRcon();
 87  
         }
 88  0
         return response;
 89  
     }
 90  
 
 91  
     
 92  
 
 93  
     private static DatagramPacket getDatagramPacket(String request, InetAddress inet, int port) {
 94  0
         byte first = -1;
 95  0
         byte last = 0;
 96  0
         byte[] buffer = request.getBytes();
 97  0
         byte[] commandBytes = new byte[buffer.length + 5];
 98  0
         commandBytes[0] = first;
 99  0
         commandBytes[1] = first;
 100  0
         commandBytes[2] = first;
 101  0
         commandBytes[3] = first;
 102  0
         for (int i = 0; i < buffer.length; i++) {
 103  0
             commandBytes[i + 4] = buffer[i];
 104  
         }
 105  0
         commandBytes[buffer.length + 4] = last;
 106  
 
 107  0
         return new DatagramPacket(commandBytes, commandBytes.length, inet, port);
 108  
     }
 109  
 
 110  
     private static CODRconPacket[] sendRequest(int localPort, String ipStr, int port, String password,
 111  
                                             String command) throws SocketTimeoutException {
 112  
 
 113  0
         DatagramSocket socket = null;
 114  0
         CODRconPacket[] resp = new CODRconPacket[128];
 115  
 
 116  
         try {
 117  0
             if (localPort == -1) {
 118  0
                 socket = new DatagramSocket();
 119  
             } else {
 120  0
                 socket = new DatagramSocket(localPort);
 121  
             }
 122  0
             int packetSize = 1400;
 123  
 
 124  0
             InetAddress address = InetAddress.getByName(ipStr);
 125  0
             byte[] ip = address.getAddress();
 126  0
             InetAddress inet = InetAddress.getByAddress(ip);
 127  
 
 128  0
             String commandStr = "rcon \"" + password + "\" " + command;
 129  0
             DatagramPacket out = getDatagramPacket(commandStr, inet, port);
 130  0
             socket.send(out);
 131  
 
 132  
             // get the response
 133  0
             byte[] data = new byte[packetSize];
 134  0
             DatagramPacket inPacket = new DatagramPacket(data, packetSize);
 135  0
             socket.setSoTimeout(RESPONSE_TIMEOUT);
 136  0
             socket.receive(inPacket);
 137  
 
 138  0
             resp[0] = new CODRconPacket(inPacket);
 139  
             try {
 140  
             // Wait for a possible multiple packets response
 141  0
                 socket.setSoTimeout(MULTIPLE_PACKETS_TIMEOUT);
 142  0
                 int i = 1;
 143  
                 while (true) {
 144  0
                     socket.receive(inPacket);
 145  0
                     resp[i++] = new CODRconPacket(inPacket);
 146  
                 }
 147  0
             } catch (SocketTimeoutException sex) {
 148  
                 // Server didn't send more packets
 149  
             }
 150  
 
 151  0
         } catch (SocketTimeoutException sex) {
 152  0
             throw sex;
 153  0
         } catch (IOException ex) {
 154  0
             ex.printStackTrace();
 155  
         } finally {
 156  0
             if (socket != null) {
 157  0
                 socket.close();
 158  
             }
 159  
         }
 160  
 
 161  0
         return resp;
 162  
     }
 163  
 
 164  
     private static String assemblePacket(CODRconPacket[] respPacket) throws UnexpectedDataException {
 165  
 
 166  0
         String resp = "";
 167  
 
 168  
         // TODO: inspect the headers to decide the correct order
 169  
 
 170  0
         for (int i = 0; i < respPacket.length; i++) {
 171  0
             if (respPacket[i] != null) {
 172  
                     // Verify that the packet was valid
 173  0
                     if (respPacket[i].valid) {
 174  0
                             resp = resp.concat(respPacket[i].data);
 175  
                     } else {
 176  0
                             throw new UnexpectedDataException();
 177  
                     }
 178  
             }
 179  
         }
 180  0
         return resp;
 181  
     }
 182  
 
 183  
 }
 184  
 
 185  
 class CODRconPacket {
 186  
 
 187  
     /**
 188  
      * ASCII representation of the full packet received (header included)
 189  
      */
 190  0
     public String ascii = "";
 191  
 
 192  
     /**
 193  
      * The data included in the packet, header removed
 194  
      */
 195  0
     public String data = "";
 196  
 
 197  
     /**
 198  
      * The full packet received (header included) in bytes
 199  
      */
 200  0
     public byte[] bytes = new byte[1400];
 201  
 
 202  
     /**
 203  
      * Length of the packet
 204  
      */
 205  0
     public int length = 0;
 206  
     
 207  
     /**
 208  
      * An indicator of the validity of the packet.
 209  
      */
 210  0
     public boolean valid = true;
 211  
 
 212  
     /**
 213  
      * Represents a rcon response packet from the game server. A response may be split
 214  
      * into multiple packets, so an array of RconPackets should be used.
 215  
      *
 216  
      * @param packet One DatagramPacket returned by the server
 217  
      */
 218  0
     public CODRconPacket(DatagramPacket packet) {
 219  
 
 220  0
         this.ascii = new String(packet.getData(), 0, packet.getLength());
 221  0
         this.bytes = ascii.getBytes();
 222  0
         this.length = packet.getLength();
 223  
 
 224  
         try {
 225  0
                 this.data = parseResponse(packet.getData());
 226  0
         } catch (UnexpectedDataException e) {
 227  0
                 this.data = "ERROR";
 228  0
                 this.valid = false;
 229  0
         }
 230  0
     }
 231  
 
 232  
     private static String parseResponse(byte[] buf) throws UnexpectedDataException{
 233  0
         String retVal = "";
 234  
 
 235  
         /*
 236  
          * The first four bytes of the return are always -1 (0xFF).
 237  
          * Then follows the characters "print" plus the ascii new line (0x0A)
 238  
          * thus the command below
 239  
          */
 240  0
         if (buf[0] != -1 || buf[1] != -1 || buf[2] != -1 || buf[3] != -1 || buf[4] != 112 || buf[5] != 114 || buf[6] != 105 || buf[7] != 110 || buf[8] != 116 || !(buf[9] == 10 || buf[9] == 13)) {
 241  0
             throw new UnexpectedDataException();
 242  
         }
 243  0
                 int off = 10;
 244  0
                 StringBuffer challenge = new StringBuffer(9995);
 245  0
                 while (buf[off] != 0) {
 246  0
                     challenge.append((char) (buf[off++] & 255));
 247  
                 }
 248  0
                 retVal = challenge.toString();
 249  
 
 250  0
         return retVal;
 251  
     }
 252  
 }