Rebase the StatefulFrameDecoder and write response codes for invalid received data.

This commit is contained in:
Ryley Kimmel
2015-03-01 15:06:17 -05:00
parent bb2ef1435f
commit 1899a92915
3 changed files with 41 additions and 63 deletions
@@ -4,7 +4,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import java.io.IOException;
import java.util.List;
import net.burtleburtle.bob.rand.IsaacRandom;
@@ -61,7 +60,7 @@ public final class GamePacketDecoder extends StatefulFrameDecoder<GameDecoderSta
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, GameDecoderState state) throws IOException {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, GameDecoderState state) {
switch (state) {
case GAME_OPCODE:
decodeOpcode(in, out);
@@ -96,9 +95,8 @@ public final class GamePacketDecoder extends StatefulFrameDecoder<GameDecoderSta
*
* @param buffer The buffer.
* @param out The {@link List} of objects to be passed along the pipeline.
* @throws IOException If a received opcode or packet type is illegal.
*/
private void decodeOpcode(ByteBuf buffer, List<Object> out) throws IOException {
private void decodeOpcode(ByteBuf buffer, List<Object> out) {
if (buffer.isReadable()) {
int encryptedOpcode = buffer.readUnsignedByte();
opcode = encryptedOpcode - random.nextInt() & 0xFF;
@@ -121,7 +119,7 @@ public final class GamePacketDecoder extends StatefulFrameDecoder<GameDecoderSta
setState(GameDecoderState.GAME_LENGTH);
break;
default:
throw new IOException("Illegal packet type: " + type + ".");
throw new IllegalStateException("Illegal packet type: " + type + ".");
}
}
}
@@ -2,9 +2,9 @@ package org.apollo.net.codec.login;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.List;
@@ -28,7 +28,7 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
/**
* The secure random number generator.
*/
private static final SecureRandom random = new SecureRandom();
private static final SecureRandom RANDOM = new SecureRandom();
/**
* The login packet length.
@@ -54,11 +54,11 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
* Creates the login decoder with the default initial state.
*/
public LoginDecoder() {
super(LoginDecoderState.LOGIN_HANDSHAKE, true);
super(LoginDecoderState.LOGIN_HANDSHAKE);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, LoginDecoderState state) throws Exception {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, LoginDecoderState state) {
switch (state) {
case LOGIN_HANDSHAKE:
decodeHandshake(ctx, in, out);
@@ -70,7 +70,7 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
decodePayload(ctx, in, out);
break;
default:
throw new IllegalStateException("Invalid login decoder state.");
throw new IllegalStateException("Invalid login decoder state: " + state);
}
}
@@ -84,7 +84,7 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
private void decodeHandshake(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.isReadable()) {
usernameHash = buffer.readUnsignedByte();
serverSeed = random.nextLong();
serverSeed = RANDOM.nextLong();
ByteBuf response = ctx.alloc().buffer(17);
response.writeByte(LoginConstants.STATUS_EXCHANGE_DATA);
@@ -102,14 +102,14 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
* @param ctx The channel handler context.
* @param buffer The buffer.
* @param out The {@link List} of objects to pass forward through the pipeline.
* @throws IOException If the login type sent by the client is invalid.
*/
private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws IOException {
private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= 2) {
int loginType = buffer.readUnsignedByte();
if (loginType != LoginConstants.TYPE_STANDARD && loginType != LoginConstants.TYPE_RECONNECTION) {
throw new IOException("Invalid login type.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
reconnecting = loginType == LoginConstants.TYPE_RECONNECTION;
@@ -125,9 +125,8 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
* @param ctx The channel handler context.
* @param buffer The buffer.
* @param out The {@link List} of objects to pass forward through the pipeline.
* @throws Exception If an error occurs.
*/
private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= loginLength) {
ByteBuf payload = buffer.readBytes(loginLength);
int clientVersion = 255 - payload.readUnsignedByte();
@@ -136,7 +135,8 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
int lowMemoryFlag = payload.readUnsignedByte();
if (lowMemoryFlag != 0 && lowMemoryFlag != 1) {
throw new Exception("Invalid value for low memory flag.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
boolean lowMemory = lowMemoryFlag == 1;
@@ -148,7 +148,8 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
int securePayloadLength = payload.readUnsignedByte();
if (securePayloadLength != loginLength - 41) {
throw new Exception("Secure payload length mismatch.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
ByteBuf securePayload = payload.readBytes(securePayloadLength);
@@ -160,13 +161,15 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
int secureId = securePayload.readUnsignedByte();
if (secureId != 10) {
throw new Exception("Invalid secure payload id.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
long clientSeed = securePayload.readLong();
long reportedServerSeed = securePayload.readLong();
if (reportedServerSeed != serverSeed) {
throw new Exception("Server seed mismatch.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
int uid = securePayload.readInt();
@@ -174,10 +177,9 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
String username = BufferUtil.readString(securePayload);
String password = BufferUtil.readString(securePayload);
if (password.length() < 6 || password.length() > 20) {
throw new Exception("Invalid password.");
} else if (username.isEmpty() || username.length() > 12) {
throw new Exception("Invalid username.");
if (password.length() < 6 || password.length() > 20 || username.isEmpty() || username.length() > 12) {
writeResponseCode(ctx, LoginConstants.STATUS_INVALID_CREDENTIALS);
return;
}
int[] seed = new int[4];
@@ -196,14 +198,23 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
PlayerCredentials credentials = new PlayerCredentials(username, password, usernameHash, uid);
IsaacRandomPair randomPair = new IsaacRandomPair(encodingRandom, decodingRandom);
LoginRequest request = new LoginRequest(credentials, randomPair, reconnecting, lowMemory, releaseNumber, archiveCrcs,
clientVersion);
LoginRequest request = new LoginRequest(credentials, randomPair, reconnecting, lowMemory, releaseNumber, archiveCrcs, clientVersion);
out.add(request);
if (buffer.isReadable()) {
out.add(buffer.readBytes(buffer.readableBytes()));
}
}
}
/**
* Writes a response code to the client and closes the current channel.
*
* @param ctx The context of the channel handler.
* @param responseCode The response code to write.
*/
private void writeResponseCode(ChannelHandlerContext ctx, int responseCode) {
ByteBuf buffer = ctx.alloc().buffer(1);
buffer.writeByte(responseCode);
ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
}
}
+3 -34
View File
@@ -5,8 +5,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
import com.google.common.base.Preconditions;
import java.util.Objects;
/**
* A stateful implementation of a {@link ByteToMessageDecoder} which may be extended and used by other classes. The
@@ -17,7 +16,7 @@ import com.google.common.base.Preconditions;
* The current state is supplied as a parameter in the {@link StatefulFrameDecoder#decode} and
* {@link StatefulFrameDecoder#decodeLast} methods.
*
* This class is not thread safe: it is recommended that the state is only set in the decode methods overriden.
* This class is not thread safe: it is recommended that the state is only set in the decode methods overridden.
*
* @author Graham
* @param <T> The state enumeration.
@@ -36,17 +35,6 @@ public abstract class StatefulFrameDecoder<T extends Enum<T>> extends ByteToMess
* @throws NullPointerException If the state is {@code null}.
*/
public StatefulFrameDecoder(T state) {
this(state, false);
}
/**
* Creates the stateful frame decoder with the specified initial state and unwrap flag.
*
* @param state The initial state.
* @param unwrap The unwrap flag.
* @throws NullPointerException If the state is {@code null}.
*/
public StatefulFrameDecoder(T state, boolean unwrap) {
setState(state);
}
@@ -66,24 +54,6 @@ public abstract class StatefulFrameDecoder<T extends Enum<T>> extends ByteToMess
*/
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, T state) throws Exception;
@Override
protected final void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
decodeLast(ctx, in, out, state);
}
/**
* Decodes remaining data before the channel is closed into a frame. You may override this method, but it is not
* required. If you do not, remaining data will be discarded!
*
* @param ctx The current context of this handler.
* @param in The cumulative buffer, which may contain zero or more bytes.
* @param out The {@link List} of objects to pass forward through the pipeline.
* @param state The current state. The state may be changed by calling {@link #setState}.
*/
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, T state) {
}
/**
* Sets a new state.
*
@@ -91,8 +61,7 @@ public abstract class StatefulFrameDecoder<T extends Enum<T>> extends ByteToMess
* @throws NullPointerException If the state is {@code null}.
*/
public final void setState(T state) {
Preconditions.checkNotNull(state, "State cannot be null.");
this.state = state;
this.state = Objects.requireNonNull(state, "State cannot be null.");
}
}