package protocol;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import network.Client;
import org.apache.mina.core.buffer.IoBuffer;
import protocol.soe.FragmentedChannelA;
import protocol.soe.MultiProtocol;
import protocol.soe.DataChannelA;
import protocol.soe.NetStatsClient;
import protocol.soe.NetStatsServer;
import protocol.soe.Ping;
import protocol.soe.SessionResponse;
import protocol.soe.SessionResponse.EncryptionType;
import protocol.swg.LoginServerId;
import protocol.swg.LoginServerString;
import utils.Utilities;
public class SoeProtocolHandler implements ProtocolHandler {
public enum DisconnectReason {
None,
ICMPError,
Timeout,
OtherSideTerminated,
ManagerDeleted,
ConnectFail,
Application,
UnreachableConnection,
UnacknowledgedTimeout,
NewConnectionAttempt,
ConnectionRefused,
MutualConnectError,
ConnectToSelf,
ReliableOverflow
}
private MessageCRC messageCRC = new MessageCRC();
private MessageEncryption messageEncryption = new MessageEncryption();
private MessageCompression messageCompression = new MessageCompression();
private Random crcGenerator = new Random();
@Override
public List<IoBuffer> decode(Client client, IoBuffer buffer) {
short opcode = buffer.getShort();
buffer.position(0);
if((opcode == 1 && client.getCrc() != 0) || (opcode != 1 && client.getCrc() == 0)) {
return null;
} else if(opcode != 1 && client.getCrc() != 0) {
int length = Utilities.getActiveLengthOfBuffer(buffer);
byte [] data = new byte[length];
System.arraycopy(buffer.array(), 0, data, 0, length);
data = messageCompression.decompress(
messageEncryption.decrypt(
messageCRC.validate(data, client.getCrc()), client.getCrc()));
buffer.clear();
buffer.setAutoExpand(true);
buffer.position(0);
buffer.put(data);
buffer.flip();
buffer.position(0);
if(!buffer.hasRemaining() || !buffer.hasArray() || Utilities.getActiveLengthOfBuffer(buffer) < 4)
return null;
}
switch(opcode) {
case 1:
handleSessionRequest(client, buffer);
break;
case 3:
return handleMulti(client, buffer);
case 5:
handleDisconnect(client, buffer);
break;
case 6:
handlePing(client, buffer);
break;
case 7:
handleNetStatsClient(client, buffer);
break;
case 9:
return handleDataA(client, buffer);
case 13:
return handleFragmentedA(client, buffer);
case 17:
handleOutOfOrder(client, buffer);
break;
case 21:
handleAcknowledgementA(client, buffer);
break;
default:
break;
}
return null;
}
private void handleAcknowledgementA(Client client, IoBuffer buffer) {
short sequence = buffer.getShort(2);
if(sequence < client.getLastACKSequence())
return;
client.setOutOfOrder(false);
client.setLastACKSequence(sequence);
client.removeSentPackets(sequence);
client.removeResentPackets(sequence);
}
private void handleOutOfOrder(Client client, IoBuffer buffer) {
short sequence = buffer.getShort(2);
short lastACKSequence = client.getLastACKSequence();
System.out.println("OutOfOrder recieved for sequence: " + sequence + " last ACK sequence: " + lastACKSequence);
if(!client.isOutOfOrder()) {
client.setOutOfOrder(true);
client.setOutOfOrderTimestamp(System.currentTimeMillis());
}
Map<Short, IoBuffer> resentPackets = client.getResentPackets();
if(System.currentTimeMillis() - client.getOutOfOrderTimestamp() < 5000)
resentPackets.clear();
client.resendPackets(sequence);
}
private List<IoBuffer> handleFragmentedA(Client client, IoBuffer buffer) {
buffer.skip(2);
short sequence = buffer.getShort();
sendAcknowledgement(client, sequence);
if(!buffer.hasRemaining())
return null;
List<IoBuffer> currentFrags = client.getCurrentFragmentedPackets();
int remainingFragSize = client.getCurrentFragRemainingSize();
if(currentFrags.size() == 0) {
remainingFragSize = buffer.getInt();
client.setCurrentFragTotalSize(remainingFragSize);
}
remainingFragSize -= buffer.remaining();
currentFrags.add(buffer.getSlice(buffer.remaining()));
if(remainingFragSize <= 0) {
int totalSize = client.getCurrentFragTotalSize();
IoBuffer finalData = client.getBufferPool().allocate(totalSize, false);
for(IoBuffer fragment : currentFrags) {
finalData.put(fragment);
}
currentFrags.clear();
client.setCurrentFragRemainingSize(0);
client.setCurrentFragTotalSize(0);
List<IoBuffer> packets = new ArrayList<IoBuffer>();
packets.add(finalData);
return packets;
}
client.setCurrentFragRemainingSize(remainingFragSize);
return null;
}
private void sendAcknowledgement(Client client, short sequence) {
IoBuffer packet = client.getBufferPool().allocate(4, false);
packet.putShort((short) 21);
packet.putShort(sequence);
packet.flip();
client.sendPacket(packet);
}
private List<IoBuffer> handleDataA(Client client, IoBuffer buffer) {
short sequence = buffer.getShort(2);
sendAcknowledgement(client, sequence);
if(buffer.remaining() <= 4)
return null;
List<IoBuffer> packets = new ArrayList<IoBuffer>();
buffer.position(0);
DataChannelA dataA = new DataChannelA(buffer, client.getBufferPool());
for (IoBuffer messageData : dataA.getMessages()) {
if (messageData != null) {
messageData.flip();
packets.add(messageData);
}
}
return packets;
}
private void handleNetStatsClient(Client client, IoBuffer buffer) {
NetStatsClient netStatsClient = new NetStatsClient();
netStatsClient.deserialize(buffer);
NetStatsServer netStatsServer = new NetStatsServer(netStatsClient.getClientTickCount(), 0, netStatsClient.getClientPacketsSent(), netStatsClient.getClientPacketsReceived(), client.getSent(), client.getRecieved());
client.sendPacket(netStatsServer.serialize());
}
private void handlePing(Client client, IoBuffer buffer) {
client.sendPacket(new Ping().serialize());
}
private void handleDisconnect(Client client, IoBuffer buffer) {
buffer.skip(6);
short reasonId = buffer.getShort();
if(reasonId < 0 || reasonId > 13) {
System.err.println("Unknown disconnect reason ID: " + reasonId);
return;
}
System.out.println("Client disconnecting for reason: " + DisconnectReason.values()[reasonId].toString());
client.getDispatch().getServer().removeClient(client);
}
private List<IoBuffer> handleMulti(Client client, IoBuffer buffer) {
MultiProtocol multi = new MultiProtocol(buffer);
List<IoBuffer> packets = new ArrayList<IoBuffer>();
for (IoBuffer data : multi.getMessages()) {
if (data != null && data.hasArray() && data.array().length >= 2) {
// SOE Packet
if ((data.get(0) == 0x00 && data.get(1) > 0x00 && data.get(1) < 0x1E)) {
decode(client, data);
}
// SWG Packet
else {
packets.add(data);
}
}
}
return packets;
}
private void handleSessionRequest(Client client, IoBuffer buffer) {
buffer.skip(2);
int crcLength = buffer.getInt();
int connectionId = buffer.getInt();
int clientUDPSize = buffer.getInt();
client.setConnectionId(connectionId);
client.setCrc(crcGenerator.nextInt());
SessionResponse response = new SessionResponse(connectionId, client.getCrc(), crcLength, true, EncryptionType.XOR, clientUDPSize);
client.sendPacket(response.serialize());
}
@Override
public List<IoBuffer> encode(Client client, List<IoBuffer> packets) {
if(packets == null || packets.isEmpty())
return null;
int crcSeed = client.getCrc();
List<IoBuffer> packed = new ArrayList<IoBuffer>();
for(IoBuffer packet : packets) {
if(Utilities.IsSOETypeMessage(packet.array()))
packed.add(packet);
}
DataChannelA dataChannelA = new DataChannelA(client.getBufferPool());
for(IoBuffer packet : packets) {
if (packet == null || packed.contains(packet)) continue;
if (packet.array().length < 6 || packet.limit() < 6) continue;
int opcode = packet.getInt(2);
if(opcode == 0x1B24F808 || opcode == 0xC867AB5A) // send movement packets as unreliable packets
continue;
if (!dataChannelA.addMessage(packet) && packet.array().length <= 487) {
packed.add(dataChannelA.serialize());
dataChannelA = new DataChannelA(client.getBufferPool());
dataChannelA.addMessage(packet);
} else if(packet.array().length > 487) {
if (dataChannelA.hasMessages()) {
packed.add(dataChannelA.serialize());
dataChannelA = new DataChannelA(client.getBufferPool());
}
FragmentedChannelA fragChanA = new FragmentedChannelA(client.getBufferPool());
for (FragmentedChannelA fragChanASection : fragChanA.create(packet.array())) {
packed.add(fragChanASection.serialize());
}
}
}
if (dataChannelA.hasMessages())
packed.add(dataChannelA.serialize());
MultiProtocol multiProtocol = new MultiProtocol(client.getBufferPool());
for (IoBuffer packet : packets) {
if (packet == null || packed.contains(packet)) continue;
if (packet.array().length < 6 || packet.limit() < 6) continue;
int opcode = packet.getInt(2);
if(opcode != 0x1B24F808 && opcode != 0xC867AB5A)
continue;
if(packet.array().length > 255)
continue;
if(!multiProtocol.addSWGMessage(packet)) {
packed.add(multiProtocol.serialize());
multiProtocol = new MultiProtocol(client.getBufferPool());
multiProtocol.addSWGMessage(packet);
}
}
if (multiProtocol.hasMessages())
packed.add(multiProtocol.serialize());
List<IoBuffer> out = new ArrayList<IoBuffer>(packed.size());
for(IoBuffer packedPacket : packed) {
short opcode = packedPacket.getShort(0);
if(opcode == 2) { // dont encrypt/compress session response
out.add(packedPacket);
continue;
}
if(opcode == 9 || opcode == 13) {
short nextSequence = client.getNextSequence();
packedPacket.putShort(2, nextSequence);
client.setNextSequence((short) (nextSequence + 1));
byte[] packet = messageCRC.append(
messageEncryption.encrypt(
messageCompression.compress(packedPacket.array()), crcSeed), crcSeed);
IoBuffer outPacket = client.getBufferPool().allocate(packet.length, false).put(packet).flip();
out.add(outPacket);
client.addSentPacket(nextSequence, outPacket);
} else {
byte[] packet = null;
packet = messageCRC.append(
messageEncryption.encrypt(
messageCompression.compress(packedPacket.array()), crcSeed), crcSeed);
IoBuffer outPacket = client.getBufferPool().allocate(packet.length, false).put(packet).flip();
out.add(outPacket);
}
}
return out;
}
public MessageCRC getMessageCRC() {
return messageCRC;
}
public void setMessageCRC(MessageCRC messageCRC) {
this.messageCRC = messageCRC;
}
public MessageEncryption getMessageEncryption() {
return messageEncryption;
}
public void setMessageEncryption(MessageEncryption messageEncryption) {
this.messageEncryption = messageEncryption;
}
public MessageCompression getMessageCompression() {
return messageCompression;
}
public void setMessageCompression(MessageCompression messageCompression) {
this.messageCompression = messageCompression;
}
}