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.netty.packet; 019 020import io.netty.buffer.ByteBuf; 021import io.netty.channel.ChannelHandlerContext; 022import io.netty.handler.codec.MessageToByteEncoder; 023import net.tridentsdk.server.TridentServer; 024import net.tridentsdk.server.netty.ClientConnection; 025import net.tridentsdk.server.netty.Codec; 026 027import javax.annotation.concurrent.NotThreadSafe; 028import java.io.ByteArrayOutputStream; 029import java.io.IOException; 030import java.util.zip.Deflater; 031 032/** 033 * Used to compress (if needed) outgoing packets from the server 034 * <p>Note that this is not thread safe, if it is to be used in multiple threads, multiple instances should be 035 * created</p> 036 * <p>This is the second and final in the outbound packet pipeline</p> 037 * 038 * @author The TridentSDK Team 039 */ 040@NotThreadSafe 041public class PacketEncoder extends MessageToByteEncoder<ByteBuf> { 042 private final Deflater deflater = new Deflater(Deflater.BEST_SPEED); 043 private final byte[] buffer = new byte[65536]; 044 private final ByteArrayOutputStream compressed = new ByteArrayOutputStream(); 045 046 private ClientConnection connection; 047 048 @Override 049 public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 050 connection = ClientConnection.connection(ctx); 051 } 052 053 @Override 054 protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf msg, ByteBuf out) throws Exception { 055 int threshold = TridentServer.instance().compressionThreshold(); 056 boolean underThreshold = msg.readableBytes() < threshold && threshold != -1; 057 058 if (underThreshold && connection.isCompressionEnabled()) { 059 sendDecompressed(msg, out); 060 } else if (!(underThreshold) && connection.isCompressionEnabled()) { 061 sendCompressed(msg, out); 062 } else { 063 Codec.writeVarInt32(out, msg.readableBytes()); 064 out.writeBytes(msg); 065 } 066 } 067 068 /** 069 * Encodes the packet without checking for size to see if it should be compressed 070 * <p>Still sends a VarInt 0 to indicate that this packet has not been compressed</p> 071 * <p>This method of handling a packet is abnormal and is only used when compression is disabled</p> 072 * FIXME compression needs to be enabled in order to use this method 073 */ 074 private void sendDecompressed(ByteBuf msg, ByteBuf out) { 075 Codec.writeVarInt32(out, msg.readableBytes() + Codec.sizeOf(0)); 076 Codec.writeVarInt32(out, 0); 077 out.writeBytes(msg); 078 } 079 080 /** 081 * Checks a packets size and encodes (writes the size, compressed size, and data) and compressed the information if 082 * necessary 083 */ 084 private void sendCompressed(ByteBuf msg, ByteBuf out) throws IOException { 085 int index = msg.readerIndex(); 086 int length = msg.readableBytes(); 087 088 byte[] decompressed = new byte[length]; 089 090 msg.readBytes(decompressed); 091 deflater.setInput(decompressed); 092 deflater.finish(); 093 094 while (!deflater.finished()) { 095 int bytes = deflater.deflate(buffer); 096 compressed.write(buffer, 0, bytes); 097 } 098 099 int afterCompress = compressed.toByteArray().length; 100 101 // Equals or more than original size 102 if (afterCompress == length || afterCompress > length) { 103 msg.readerIndex(index); 104 sendDecompressed(msg, out); 105 return; 106 } 107 108 deflater.reset(); 109 110 Codec.writeVarInt32(out, afterCompress + Codec.sizeOf(length)); 111 Codec.writeVarInt32(out, length); 112 out.writeBytes(compressed.toByteArray()); 113 114 compressed.reset(); 115 } 116}