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}