/** * 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.gameserver.network.aion; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayDeque; import java.util.Deque; import org.apache.log4j.Logger; import com.aionemu.commons.network.AConnection; import com.aionemu.commons.network.Dispatcher; import com.aionemu.commons.network.PacketProcessor; import com.aionemu.gameserver.model.account.Account; import com.aionemu.gameserver.model.gameobjects.player.Player; import com.aionemu.gameserver.network.Crypt; import com.aionemu.gameserver.network.aion.serverpackets.SM_KEY; import com.aionemu.gameserver.network.loginserver.LoginServer; import com.aionemu.gameserver.services.PlayerService; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; /** * Object representing connection between GameServer 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); /** * Possible states of AionConnection */ public static enum State { /** * client just connect */ CONNECTED, /** * client is authenticated */ AUTHED, /** * client entered world. */ IN_GAME } /** * Server Packet "to send" Queue */ private final Deque<AionServerPacket> sendMsgQueue = new ArrayDeque<AionServerPacket>(); /** * Current state of this connection */ private State state; /** * AionClient is authenticating by passing to GameServer id of account. */ private Account account; /** * Crypt that will encrypt/decrypt packets. */ private final Crypt crypt = new Crypt(); /** * active Player that owner of this connection is playing [entered game] */ private Player activePlayer; private String lastPlayerName = ""; private LoginServer loginServer; private AionPacketHandler aionPacketHandler; private PlayerService playerService; private long lastPingTimeMS; /** * Constructor * * @param sc * @param d * @throws IOException */ @Inject public AionConnection(@Assisted SocketChannel sc, @Assisted Dispatcher d, LoginServer loginServer, AionPacketHandler aionPacketHandler, PlayerService playerService) throws IOException { super(sc, d); this.loginServer = loginServer; this.aionPacketHandler = aionPacketHandler; this.playerService = playerService; state = State.CONNECTED; String ip = getIP(); log.info("connection from: " + ip); /** Send SM_KEY packet */ sendPacket(new SM_KEY()); } /** * Enable crypt key - generate random key that will be used to encrypt second server packet [first one is * unencrypted] and decrypt client packets. This method is called from SM_KEY server packet, that packet sends key * to aion client. * * @return "false key" that should by used by aion client to encrypt/decrypt packets. */ public final int enableCryptKey() { return crypt.enableKey(); } /** * 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) { try { if(!crypt.decrypt(data)) { log.warn("Decrypt fail!"); return false; } } catch(Exception ex) { log.error("Exception caught during decrypt!" + ex.getMessage()); return false; } AionClientPacket pck = aionPacketHandler.handle(data, this); if(state == State.IN_GAME && activePlayer == null) { log.warn("CHECKPOINT: Skipping packet processing of " + pck.getPacketName() + " for player " + lastPlayerName); return false; } /** * 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() { /** * Client starts authentication procedure */ if(getAccount() != null) loginServer.aionClientDisconnected(getAccount().getId()); if(getActivePlayer() != null) { Player player = getActivePlayer(); if(player.getController().isInShutdownProgress()) playerService.playerLoggedOut(player); // prevent ctrl+alt+del / close window exploit else { int delay = 15; playerService.playerLoggedOutDelay(player, (delay * 1000)); } } } /** * {@inheritDoc} */ @Override protected final void onServerClose() { // TODO mb some packet should be send to client before closing? close(/* packet, */true); } /** * Encrypt packet. * * @param buf */ public final void encrypt(ByteBuffer buf) { crypt.encrypt(buf); } /** * 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; 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.debug("sending packet: " + closePacket + " and closing connection after that."); pendingClose = true; isForcedClosing = forced; sendMsgQueue.clear(); sendMsgQueue.addLast(closePacket); enableWriteInterest(); } } /** * Current state of this connection * * @return state */ public final State getState() { return state; } /** * Sets the state of this connection * * @param state * state of this connection */ public void setState(State state) { this.state = state; } /** * Returns account object associated with this connection * * @return account object associated with this connection */ public Account getAccount() { return account; } /** * Sets account object associated with this connection * * @param account * account object associated with this connection */ public void setAccount(Account account) { this.account = account; } /** * Sets Active player to new value. Update connection state to correct value. * * @param player * @return True if active player was set to new value. */ public boolean setActivePlayer(Player player) { if(activePlayer != null && player != null) return false; activePlayer = player; if(activePlayer == null) state = State.AUTHED; else state = State.IN_GAME; if(activePlayer != null) lastPlayerName = player.getName(); return true; } /** * Return active player or null. * * @return active player or null. */ public Player getActivePlayer() { return activePlayer; } /** * @return the lastPingTimeMS */ public long getLastPingTimeMS() { return lastPingTimeMS; } /** * @param lastPingTimeMS the lastPingTimeMS to set */ public void setLastPingTimeMS(long lastPingTimeMS) { this.lastPingTimeMS = lastPingTimeMS; } }