/*******************************************************************************
* =============================================================================
* Filename: RconPacket.java
* Project: Coyote
* Creation Date: 2013-03-10
* Author: Dimitris Zarras (feugatos) <zarras.dim@gmail.com>
* =============================================================================
* =============================================================================
* Copyright 2013 Dimitris Zarras
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
* =============================================================================
* Contributors:
* * Dimitris Zarras (feugatos) <zarras.dim@gmail.com>
* =============================================================================
******************************************************************************/
package net.ceidwarfare.coyote.classes;
import net.ceidwarfare.coyote.exceptions.UnexpectedValueException;
import net.ceidwarfare.helpers.ByteConversions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* This class is used to create the actual packets that will be sent to the server. The static methods
* createPacket(int, int, String) and createPacket(byte[]) can be used to create instances of this class.
*/
public class RconPacket {
/**
* The packet type of an authentication packet, currently 3
*/
public static final int SERVERDATA_AUTH = 3;
/**
* The packet type of an authentication response packet, currently 2
*/
public static final int SERVERDATA_AUTH_RESPONSE = 2;
/**
* The packet type of an exec command packet, currently 2
*/
public static final int SERVERDATA_EXECCOMMAND = 2;
/**
* The packet type of a response packet, currently 0
*/
public static final int SERVERDATA_RESPONSE_VALUE = 0;
/**
* The minimum size of a packet excluding the packetSize variable, currently 10
*/
public static final int MIN_SIZE = 10;
/**
* The maximum size of a packet excluding the packetSize variable, currently 4096
*/
public static final int MAX_SIZE = 4096;
/**
* The minimum size of a packet including the packetSize variable, currently 14
*/
public static final int REAL_MIN_SIZE = 14;
/**
* The maximum size of a packet including the packetSize variable, currently 4100
*/
public static final int REAL_MAX_SIZE = 4100;
/**
* The maximum length for the body string of the packet excluding the '\0' character at the end, currently 4086
*/
public static final int MAX_BODY_SIZE = 4086;
//Private member fields
private int size;
private int id;
private int type;
private String body;
private String emptyBody;
/**
* Constructs a new RconPacket given it's id, type and body. This constructor doesn't do any input validation so all
* data passed to this constructor must be validated beforehand.
*
* @param id the id of the packet
* @param type the type of the packet
* @param body the body of the packet
*/
private RconPacket(int id, int type, String body) {
this.size = 4 //for id
+ 4 // for type
+ body.length() + 1 //the body length plus the null termination char
+ 1; //1 for the null termination char of the emptyBody
this.id = id;
this.type = type;
this.body = body;
this.emptyBody = "";
}
/**
* Returns the packet size (excluding the packetSize variable itself).
*
* @return the size of the packet
*/
public int getSize() {
return size;
}
/**
* Returns the size of the byte[] array that will be sent to the server. This includes the packetSize variable
* as well.
*
* @return the size of the packet including the packetSize variable itself
*/
public int getRealSize() {
return size + 4; //4 more bytes for the size variable itself
}
/**
* Returns the id of the current packet.
*
* @return the packet id
*/
public int getId() {
return id;
}
/**
* Returns the numeric representation of the type of the current packet. Check the class constants
* SERVERDATA_AUTH, SERVERDATA_AUTH_RESPONSE,SERVERDATA_EXECCOMMAND and SERVERDATA_RESPONSE_VALUE as well.
*
* @return the packet type as an integer
*/
public int getType() {
return type;
}
/**
* Returns the body string of the current packet. This is the command to be sent for a SERVERDATA_EXECCOMMAND
* packet, the response from the server for a SERVERDATA_RESPONSE_VALUE packet, the rcon password for a SERVER_DATA_AUTH
* packet and an empty string for a SERVER_DATA_AUTH_RESPONSE packet.
*
* @return the body of the packet
*/
public String getBody() {
return body;
}
/**
* Returns the byte[] array representation of the current packet which can be directly use with OutputStream.write()
* in order to send the packet to the server.
*
* @return the byte[] array representation of the current packet
*/
public byte[] getBytes() {
ByteBuffer buffer = ByteBuffer.allocate(this.getRealSize());
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(size);
buffer.putInt(id);
buffer.putInt(type);
buffer.put((body + '\0').getBytes(Charset.forName("US-ASCII")));
buffer.put((emptyBody + '\0').getBytes(Charset.forName("US-ASCII")));
return buffer.array();
}
/**
* Create a new RconPacket given it's id, type and body. This method is typically used to create an authentication
* packet or a command packet that will be sent to the server.
*
* @param id the id of the packet. must be greater than -1.
* @param type the type of the packet. check SERVERDATA_* class variables for more.
* @param body the body of the packet. can't be larger than MAX_BODY_SIZE characters.
* @return the corresponding RconPacket.
* @throws IllegalArgumentException if id is less that 0 , or if type is not equal to one of the SERVERDATA_* class
* variables or if body exceeds MAX_BODY_SIZE characters.
*/
public static RconPacket createPacket(int id, int type, String body) {
//check if id is less than 0
if (id < 0) {
throw new IllegalArgumentException("the packetId must be an integer greater or equal to 0");
}
//check if type is one of the expected ones
if ((type != SERVERDATA_AUTH) && (type != SERVERDATA_AUTH_RESPONSE) && (type != SERVERDATA_EXECCOMMAND) &&
(type != SERVERDATA_RESPONSE_VALUE)) {
throw new IllegalArgumentException("the packetType must be the value of one of the SERVERDATA_* values");
}
if (body == null) {
body = "";
} else {
//check if the length of the body doesn't exceed MAX_BODY_SIZE
if (body.length() > MAX_BODY_SIZE) {
throw new IllegalArgumentException("The packet body cannot be greater than " + MAX_BODY_SIZE + " bytes");
}
}
//return the newly constructed RconPacket
return new RconPacket(id, type, body);
}
/**
* Create a packet given its little endian byte[] array representation. Typically this method is used to construct
* incoming packets which are read, as a byte[] array, from the server's InputStream using read().
*
* @param bytes the little endian byte[] array to be converted to an RconPacket
* @return the RconPacket constructed from the given bytes
* @throws IllegalArgumentException if bytes is null or bytes.length is not between MIN_SIZE and MAX_SIZE
* @throws UnexpectedValueException if the packetId or packetType don't have an expected value, eg packetId being -19
* or packetType being 144
*/
public static RconPacket createPacket(byte[] bytes) {
//check if bytes is null
if (bytes == null) {
throw new IllegalArgumentException("bytes cannot be a null reference");
}
//check if bytes is at least MIN_SIZE and at most MAX_SIZE bytes long
if ((bytes.length < MIN_SIZE) || (bytes.length > MAX_SIZE)) {
throw new IllegalArgumentException("bytes be at least MIN_SIZE and at most MAX_SIZE bytes long");
}
//check if the converted from bytes id is greater than -1
//-1 is only used if the authentication is not successful in the id field of the SERVERDATA_AUTH_RESPONSE packet
int packetId = ByteConversions.littleEndianToInt(Arrays.copyOfRange(bytes, 0, 4));
if (packetId < -1) {
throw new UnexpectedValueException("the packetId must be an integer greater than -1.");
}
//check if the converted from bytes type is one of the expected.
int packetType = ByteConversions.littleEndianToInt(Arrays.copyOfRange(bytes, 4, 8));
if ((packetType != SERVERDATA_AUTH) && (packetType != SERVERDATA_EXECCOMMAND) &&
(packetType != SERVERDATA_RESPONSE_VALUE) && (packetType != SERVERDATA_AUTH_RESPONSE)) {
throw new UnexpectedValueException("the packetType must be the value of one of the SERVERDATA_* values");
}
//construct the packet body. we exclude the final 2 bytes as we already now they should be '\0'.
String response = new String(Arrays.copyOfRange(bytes, 8, bytes.length - 2));
//return the newly created packet
return new RconPacket(packetId, packetType, response);
}
}