001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.player; 019 020import net.tridentsdk.docs.InternalUseOnly; 021import net.tridentsdk.server.netty.ClientConnection; 022import net.tridentsdk.server.netty.packet.OutPacket; 023import net.tridentsdk.server.netty.packet.Packet; 024import net.tridentsdk.server.netty.packet.PacketHandler; 025import net.tridentsdk.server.netty.protocol.Protocol; 026import net.tridentsdk.server.packets.play.in.PacketPlayInKeepAlive; 027import net.tridentsdk.server.packets.play.out.PacketPlayOutKeepAlive; 028 029import javax.annotation.concurrent.GuardedBy; 030import javax.annotation.concurrent.ThreadSafe; 031import java.net.InetSocketAddress; 032import java.util.concurrent.ThreadLocalRandom; 033 034/** 035 * Represents the connection the player has to the server 036 * 037 * @author The TridentSDK Team 038 */ 039@ThreadSafe 040public class PlayerConnection extends ClientConnection { 041 private final TridentPlayer player; 042 043 @GuardedBy("this") 044 private int keepAliveId = -1; 045 @GuardedBy("this") 046 private int readCounter = 0; 047 @GuardedBy("this") 048 private int writeCounter = 0; 049 050 private PlayerConnection(ClientConnection connection, TridentPlayer player) { 051 // remove old connection, and replace it with this one 052 ClientConnection.clientData.put(connection.address(), connection); 053 054 super.address = connection.address(); 055 super.channel = connection.channel(); 056 super.loginKeyPair = connection.loginKeyPair(); 057 super.sharedSecret = connection.sharedSecret(); 058 super.stage = Protocol.ClientStage.PLAY; // stage must be PLAY to actually create PlayerConnection 059 if (sharedSecret != null) { 060 // TODO in case something goes wrong during keygen, this won't be done even with encryption enabled 061 enableEncryption(sharedSecret.getEncoded()); 062 } 063 super.compressionEnabled = connection.isCompressionEnabled(); 064 065 this.player = player; 066 067 // update the clients packet handler 068 PacketHandler handler = channel.pipeline().get(PacketHandler.class); 069 070 if (handler != null) { // while unlikely, I'll take my chances 071 handler.updateConnection(this); 072 } 073 } 074 075 public static PlayerConnection createPlayerConnection(ClientConnection connection, TridentPlayer player) { 076 return new PlayerConnection(connection, player); 077 } 078 079 public static PlayerConnection connection(InetSocketAddress address) { 080 return (PlayerConnection) ClientConnection.getConnection(address); 081 } 082 083 /** 084 * Gets the player that has this connection 085 * 086 * @return the player that is wrapped 087 */ 088 public TridentPlayer player() { 089 while (player == null) ; // IO thread can afford to busy wait 090 return this.player; 091 } 092 093 @InternalUseOnly 094 public synchronized void sendKeepAlive() { 095 int oldId = keepAliveId; 096 097 if (oldId != -1) 098 return; 099 100 int id = ThreadLocalRandom.current().nextInt(0x230000); 101 102 OutPacket packet = new PacketPlayOutKeepAlive(); 103 104 packet.set("keepAliveId", id); 105 keepAliveId = id; 106 107 sendPacket(packet); 108 } 109 110 @InternalUseOnly 111 public synchronized void handleKeepAlive(PacketPlayInKeepAlive keepAlive) { 112 int currentId = keepAliveId; 113 114 if (keepAlive.id() != currentId) 115 return; 116 117 keepAliveId = -1; 118 readCounter = 0; 119 } 120 121 @InternalUseOnly 122 public synchronized void resetReadCounter() { 123 readCounter = 0; 124 } 125 126 @Override 127 public void sendPacket(Packet packet) { 128 if (!channel.isWritable()) return; 129 super.sendPacket(packet); 130 131 synchronized (this) { 132 writeCounter = 0; // reset write counter 133 } 134 } 135 136 // Entire method is not needed to be synchronized 137 // But release and reacquire from conditions can be expensive 138 // Lock striping can be performed by the JIT anyways 139 synchronized void tick() { 140 ++writeCounter; 141 ++readCounter; 142 143 int read = readCounter; 144 int write = writeCounter; 145 146 if (read >= 300) { 147 if (keepAliveId == -1) { 148 sendKeepAlive(); 149 } else if (read >= 600) { 150 player.kickPlayer("Timed out!"); 151 } 152 } 153 154 if (write >= 300) { 155 sendKeepAlive(); 156 } 157 } 158 159 /** 160 * Checks the hasSent flag to see if the player has sent the keep alive packet 161 * 162 * @return {@code true} to represent the player keep alive has been sent 163 */ 164 public synchronized boolean hasSentKeepAlive() { 165 return keepAliveId == -1; 166 } 167}