/** * This file is part of aion-emu <aion-emu.com>. * * aion-emu is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * aion-emu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with aion-emu. If not, see <http://www.gnu.org/licenses/>. */ package com.aionemu.loginserver.network.aion; import com.aionemu.commons.network.AConnection; import com.aionemu.commons.network.Dispatcher; import com.aionemu.commons.network.PacketProcessor; import com.aionemu.loginserver.controller.AccountController; import com.aionemu.loginserver.controller.AccountTimeController; import com.aionemu.loginserver.model.Account; import com.aionemu.loginserver.network.aion.serverpackets.SM_INIT; import com.aionemu.loginserver.network.ncrypt.CryptEngine; import com.aionemu.loginserver.network.ncrypt.KeyGen; import com.aionemu.loginserver.network.ncrypt.EncryptedRSAKeyPair; import org.apache.log4j.Logger; import javax.crypto.SecretKey; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.interfaces.RSAPrivateKey; import java.util.ArrayDeque; import java.util.Deque; /** * Object representing connection between LoginServer and Aion Client. * * @author -Nemesiss- */ public class AionConnection extends AConnection { /** * Logger for this class. */ private static final Logger log = Logger.getLogger(AionConnection.class); /** * PacketProcessor for executing packets. */ private final static PacketProcessor<AionConnection> processor = new PacketProcessor<AionConnection>(1, 8); /** * Server Packet "to send" Queue */ private final Deque<AionServerPacket> sendMsgQueue = new ArrayDeque<AionServerPacket>(); /** * Unique Session Id of this connection */ private int sessionId = hashCode(); /** * Account object for this connection. if state = AUTHED_LOGIN account cant be null. * */ private Account account; /** * Crypt to encrypt/decrypt packets */ private CryptEngine cryptEngine; /** * True if this user is connecting to GS. */ private boolean joinedGs; /** * Scrambled key pair for RSA */ private EncryptedRSAKeyPair encryptedRSAKeyPair; /** * Session Key for this connection. */ private SessionKey sessionKey; /** * Current state of this connection */ private State state; /** * Possible states of AionConnection */ public static enum State { /** * Means that client just connects */ CONNECTED, /** * Means that clients GameGuard is authenticated */ AUTHED_GG, /** * Means that client is logged in. */ AUTHED_LOGIN } /** * Constructor * * @param sc * @param d * @throws IOException */ public AionConnection(SocketChannel sc, Dispatcher d) throws IOException { super(sc, d); state = State.CONNECTED; String ip = getIP(); log.info("connection from: " + ip); encryptedRSAKeyPair = KeyGen.getEncryptedRSAKeyPair(); SecretKey blowfishKey = KeyGen.generateBlowfishKey(); cryptEngine = new CryptEngine(); cryptEngine.updateKey(blowfishKey.getEncoded()); /** Send Init packet */ sendPacket(new SM_INIT(this, blowfishKey)); } /** * Called by Dispatcher. ByteBuffer data contains one packet that should be processed. * * @param data * @return True if data was processed correctly, False if some error occurred and connection should be closed NOW. */ @Override protected final boolean processData(ByteBuffer data) { if (!decrypt(data)) { return false; } AionClientPacket pck = AionPacketHandler.handle(data, this); log.info("recived packet: " + pck); /** * Execute packet only if packet exist (!= null) and read was ok. */ if ((pck != null) && pck.read()) processor.executePacket(pck); return true; } /** * This method will be called by Dispatcher, and will be repeated till return false. * * @param data * @return True if data was written to buffer, False indicating that there are not any more data to write. */ @Override protected final boolean writeData(ByteBuffer data) { synchronized (guard) { AionServerPacket packet = sendMsgQueue.pollFirst(); if (packet == null) { return false; } packet.write(this, data); return true; } } /** * This method is called by Dispatcher when connection is ready to be closed. * * @return time in ms after witch onDisconnect() method will be called. Always return 0. */ @Override protected final long getDisconnectionDelay() { return 0; } /** * {@inheritDoc} */ @Override protected final void onDisconnect() { /** * Remove account only if not joined GameServer yet. */ if ((account != null) && !joinedGs) { AccountController.removeAccountOnLS(account); AccountTimeController.updateOnLogout(account); } } /** * {@inheritDoc} */ @Override protected final void onServerClose() { // TODO mb some packet should be send to client before closing? close( /* packet, */true); } /** * Decrypt packet. * * @param buf * @return true if success */ private boolean decrypt(ByteBuffer buf) { int size = buf.remaining(); final int offset = buf.arrayOffset() + buf.position(); boolean ret = cryptEngine.decrypt(buf.array(), offset, size); if (!ret) { log.warn("Wrong checksum from client: " + this); } return ret; } /** * Encrypt packet. * * @param buf * @return encrypted packet size. */ public final int encrypt(ByteBuffer buf) { int size = buf.limit() - 2; final int offset = buf.arrayOffset() + buf.position(); size = cryptEngine.encrypt(buf.array(), offset, size); return size; } /** * Sends AionServerPacket to this client. * * @param bp * AionServerPacket to be sent. */ public final void sendPacket(AionServerPacket bp) { synchronized (guard) { /** * Connection is already closed or waiting for last (close packet) to be sent */ if (isWriteDisabled()) { return; } log.debug("sending packet: " + bp); sendMsgQueue.addLast(bp); enableWriteInterest(); } } /** * Its guaranted that closePacket will be sent before closing connection, but all past and future packets wont. * Connection will be closed [by Dispatcher Thread], and onDisconnect() method will be called to clear all other * things. forced means that server shouldn't wait with removing this connection. * * @param closePacket * Packet that will be send before closing. * @param forced * have no effect in this implementation. */ public final void close(AionServerPacket closePacket, boolean forced) { synchronized (guard) { if (isWriteDisabled()) { return; } log.info("sending packet: " + closePacket + " and closing connection after that."); pendingClose = true; isForcedClosing = forced; sendMsgQueue.clear(); sendMsgQueue.addLast(closePacket); enableWriteInterest(); } } /** * Return Scrambled modulus * * @return Scrambled modulus */ public final byte[] getEncryptedModulus() { return encryptedRSAKeyPair.getEncryptedModulus(); } /** * Return RSA private key * * @return rsa private key */ public final RSAPrivateKey getRSAPrivateKey() { return (RSAPrivateKey) encryptedRSAKeyPair.getRSAKeyPair().getPrivate(); } /** * Returns unique sessionId of this connection. * * @return SessionId */ public final int getSessionId() { return sessionId; } /** * Current state of this connection * * @return state */ public final State getState() { return state; } /** * Set current state of this connection * * @param state */ public final void setState(State state) { this.state = state; } /** * Returns Account object that this client logged in or null * * @return Account */ public final Account getAccount() { return account; } /** * Set Account object for this connection. * * @param account */ public final void setAccount(Account account) { this.account = account; } /** * Returns Session Key of this connection * * @return SessionKey */ public final SessionKey getSessionKey() { return sessionKey; } /** * Set Session Key for this connection * * @param sessionKey */ public final void setSessionKey(SessionKey sessionKey) { this.sessionKey = sessionKey; } /** * Set joinedGs value to true */ public final void setJoinedGs() { joinedGs = true; } /** * @return String info about this connection */ @Override public String toString() { return (account != null) ? account + " " + getIP() : "not loged " + getIP(); } /** * This method should no be modified, hashcode in this class is used to ensure that each connection hash unique id * * @return unique identifier */ @Override public int hashCode() { return super.hashCode(); } }