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;
019
020import com.google.common.base.Charsets;
021import com.google.common.base.Preconditions;
022import io.netty.buffer.ByteBuf;
023
024import javax.annotation.concurrent.ThreadSafe;
025import java.math.BigInteger;
026import java.nio.charset.Charset;
027
028/**
029 * Utility class to help decode the bytes from a backed buffer serializer
030 *
031 * @author The TridentSDK Team
032 */
033@ThreadSafe
034public final class Codec {
035    //Current charset used by strings is UTF_8
036    /**
037     * The charset used for Strings
038     */
039    public static final Charset CHARSET = Charsets.UTF_8;
040
041    private Codec() {
042    } // Suppress initialization of utility class
043
044    /**
045     * Read a string from the encoded buffer
046     *
047     * @param buf the buffer to decode the string from
048     * @return the decoded string read from the buffer
049     */
050    public static String readString(ByteBuf buf) {
051        //Reads the length of the string
052        int length = readVarInt32(buf);
053        byte[] bytes = new byte[length];
054        buf.readBytes(bytes);
055
056        return new String(bytes, CHARSET);
057    }
058
059    /**
060     * Gets the byte length of provided integer
061     *
062     * @param integer integer to get the byte length of
063     * @return byte length of the integer
064     */
065    public static int sizeOf(int integer) {
066        return BigInteger.valueOf(integer).toByteArray().length;
067    }
068
069    /**
070     * Writes a string to the buffer
071     *
072     * @param buf the buffer to decode the string from
073     */
074    public static void writeString(ByteBuf buf, String string) {
075        if (string == null) {
076            return;
077        }
078        //Writes the length of the string
079        writeVarInt32(buf, string.length());
080
081        //Writes the bytes of the string
082        byte[] bytes = string.getBytes(CHARSET);
083        buf.writeBytes(bytes);
084    }
085
086    /**
087     * Reads a 32bit VarInt from the encoded buffer
088     *
089     * @param buf the buffer to decode the integer from
090     * @return the decoded integer read from the buffer
091     */
092    public static int readVarInt32(ByteBuf buf) {
093        //The result we will return
094        int result = 0;
095
096        //How much to indent the current bytes
097        int indent = 0;
098        int b = (int) buf.readByte();
099
100        //If below, it means there are more bytes
101        // 0x80 = 128 for those that don't know
102        while ((b & 0x80) == 0x80) {
103            Preconditions.checkArgument(indent < 21, "Too many bytes for a VarInt32.");
104
105            //Adds the byte in the appropriate position (first byte goes last, etc.)
106            result += (b & 0x7f) << indent;
107            indent += 7;
108
109            //Reads the next byte
110            b = (int) buf.readByte();
111        }
112
113        // 0x7f = 127
114        result += (b & 0x7f) << indent;
115        return result;
116    }
117
118    /**
119     * Writes an int value as a VarInt to the buffer.
120     *
121     * @param buf      the buffer to encode into
122     * @param toEncode the integer encode into buf
123     */
124    public static void writeVarInt32(ByteBuf buf, int toEncode) {
125        //Loops through until the currently 'selected' set of 7 bits is the terminating one
126        while ((toEncode & 0xFFFFFF80) != 0L) {
127            /*Writes the selected 7 bits, and adds a 1 at the front
128            signifying that there is another byte*/
129            buf.writeByte(toEncode & 0x7F | 0x80);
130            //Selects the next set of 7 bits
131            toEncode >>>= 7;
132        }
133        //Writes the final terminating byte with a 0 at the front to signify termination
134        buf.writeByte(toEncode & 0x7F);
135    }
136
137    /**
138     * Reads a 64bit VarInt from the encoded buffer
139     *
140     * @param buf the buffer to decode the long from
141     * @return the decoded long read from the buffer
142     */
143    public static long readVarInt64(ByteBuf buf) {
144        //The result we will return
145        long result = 0L;
146
147        //How much to indent the current bytes
148        int indent = 0;
149        long b = (long) buf.readByte();
150
151        //If below, it means there are more bytes
152        while ((b & 0x80L) == 0x80) {
153            Preconditions.checkArgument(indent < 49, "Too many bytes for a VarInt64.");
154
155            //Adds the byte in the appropriate position (first byte goes last, etc.)
156            result += (b & 0x7fL) << indent;
157            indent += 7;
158
159            //Reads the next byte
160            b = (long) buf.readByte();
161        }
162
163        return result += (b & 0x7fL) << indent;
164    }
165
166    /**
167     * Writes a long value as a VarInt to the buffer.
168     *
169     * @param buf      the buffer to encode into
170     * @param toEncode the integer encode into buf
171     */
172    public static void writeVarInt64(ByteBuf buf, long toEncode) {
173        //Loops through until the currently 'selected' set of 7 bits is the terminating one
174        while ((toEncode & 0xFFFFFFFFFFFFFF80L) != 0L) {
175            /*Writes the selected 7 bits, and adds a 1 at the front
176            signifying that there is another byte*/
177            buf.writeByte((int) (toEncode & 0x7FL | 0x80L));
178            //Selects the next set of 7 bits
179            toEncode >>>= 7L;
180        }
181        //Writes the final terminating byte with a 0 at the front to signify termination
182        buf.writeByte((int) (toEncode & 0x7FL));
183    }
184
185    /**
186     * Writes the full contents of a ByteBuf to an array
187     *
188     * @param buf the buffer to get data from
189     * @return bytes the array of bytes
190     */
191    public static byte[] asArray(ByteBuf buf) {
192        return asArray(buf, buf.readableBytes());
193    }
194
195    /**
196     * Writes a certain length of bytes from a ByteBuf to an array
197     *
198     * @param buf    the buffer to get data from
199     * @param length the length to toPacket
200     * @return bytes the array of bytes
201     */
202    public static byte[] asArray(ByteBuf buf, int length) {
203        byte[] bytes = new byte[length];
204        buf.getBytes(buf.readerIndex(), bytes);
205        return bytes;
206    }
207}