├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── license-definitions.xml ├── pom.xml └── src ├── main └── java │ └── net │ └── beaconpe │ └── jraklib │ ├── Binary.java │ ├── JRakLib.java │ ├── Logger.java │ ├── client │ ├── ClientHandler.java │ ├── ClientInstance.java │ ├── Connection.java │ ├── ConnectionManager.java │ ├── JRakLibClient.java │ └── UDPClientSocket.java │ ├── protocol │ ├── ACK.java │ ├── ADVERTISE_SYSTEM.java │ ├── AcknowledgePacket.java │ ├── CLIENT_CONNECT_DataPacket.java │ ├── CLIENT_DISCONNECT_DataPacket.java │ ├── CLIENT_HANDSHAKE_DataPacket.java │ ├── DataPacket.java │ ├── DataPackets.java │ ├── EncapsulatedPacket.java │ ├── NACK.java │ ├── OPEN_CONNECTION_REPLY_1.java │ ├── OPEN_CONNECTION_REPLY_2.java │ ├── OPEN_CONNECTION_REQUEST_1.java │ ├── OPEN_CONNECTION_REQUEST_2.java │ ├── PING_DataPacket.java │ ├── PONG_DataPacket.java │ ├── Packet.java │ ├── SERVER_HANDSHAKE_DataPacket.java │ ├── UNCONNECTED_PING.java │ ├── UNCONNECTED_PING_OPEN_CONNECTIONS.java │ └── UNCONNECTED_PONG.java │ └── server │ ├── JRakLibServer.java │ ├── ServerHandler.java │ ├── ServerInstance.java │ ├── Session.java │ ├── SessionManager.java │ └── UDPServerSocket.java └── test └── java └── TestClient.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | sudo: false 6 | 7 | install: echo "No dependencies required :)" 8 | 9 | script: mvn test 10 | 11 | after_success: echo "Build Succeeded! :)" 12 | after_failure: echo "Build failed! :/" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JRakLib [![Travis branch](https://img.shields.io/travis/BlockServerProject/JRakLib/master.svg?style=flat-square)](https://travis-ci.org/BlockServerProject/JRakLib) [![License](https://img.shields.io/badge/license-LGPLv3-blue.svg?style=flat-square)](https://tldrlegal.com/license/gnu-lesser-general-public-license-v3-(lgpl-3)) 2 | 3 | #### NOTICE: A more improved version can be found at: https://github.com/jython234/JRakLibPlus 4 | A port of the PHP MCPE RakNet networking library: RakLib. 5 | -------------------------------------------------------------------------------- /license-definitions.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | /**EOL * JRakLib is not affiliated with Jenkins Software LLC or RakNet.EOL * This software is a port of RakLib https://github.com/PocketMine/RakLib.EOL 24 | * 25 | */ 26 | 27 | 28 | (\s|\t)*/\*.*$ 29 | .*\*/(\s|\t)*$ 30 | false 31 | true 32 | false 33 | 34 | 35 | 36 | 37 | ]]> 38 | 39 | $]]> 40 | 41 | (\s|\t)*$]]> 42 | false 43 | true 44 | false 45 | 46 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 24 | 4.0.0 25 | 26 | net.beaconpe.jraklib 27 | JRakLib 28 | 1.1.1-SNAPSHOT 29 | 30 | 31 | 32 | snapshot-repo 33 | http://repo.blockserver.org/snapshots 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 43 | 1.8 44 | 1.8 45 | 46 | 47 | 48 | com.mycila 49 | license-maven-plugin 50 | 2.11 51 | 52 |
com/mycila/maven/plugin/license/templates/LGPL-3.txt
53 | 54 | license-definitions.xml 55 | 56 | 57 | **/README 58 | **/LICENSE 59 | src/test/resources/** 60 | src/main/resources/** 61 | 62 |
63 | 64 | 65 | 66 | check 67 | 68 | 69 | 70 |
71 |
72 |
73 | 74 |
-------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/Binary.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib; 21 | 22 | 23 | import java.nio.ByteBuffer; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | 28 | /** 29 | * Binary Utility class for writing/reading. 30 | */ 31 | public class Binary { 32 | 33 | /** 34 | * Reads a 3-byte little-endian number 35 | * @param bytes 36 | * @return integer 37 | */ 38 | public static int readLTriad(byte[] bytes){ 39 | return (bytes[0] & 0xFF) | ((bytes[1] & 0xFF) << 8) | ((bytes[2] & 0x0F) << 16); 40 | } 41 | 42 | /** 43 | * Writes a 3-byte little-endian number 44 | * @param triad 45 | * @return triad bytes 46 | */ 47 | public static byte[] writeLTriad(int triad){ 48 | byte b1,b2,b3; 49 | b3 = (byte)(triad & 0xFF); 50 | b2 = (byte)((triad >> 8) & 0xFF); 51 | b1 = (byte)((triad >> 16) & 0xFF); 52 | return new byte[] {b3, b2, b1}; 53 | } 54 | 55 | /** 56 | * Reads a signed byte as a boolean 57 | * @param b The raw byte 58 | * @return The boolean 59 | */ 60 | public static boolean readBool(byte b){ 61 | if(b == 0){ 62 | return true; 63 | } else { 64 | return false; 65 | } 66 | } 67 | 68 | /** 69 | * Writes a signed boolean as a byte 70 | * @param b The boolean 71 | * @return Boolean as a byte 72 | */ 73 | public static byte writeBool(boolean b){ 74 | if(b){ 75 | return 0x01; 76 | } else { 77 | return 0x00; 78 | } 79 | } 80 | 81 | /** 82 | * Reads a signed/unsigned byte 83 | * @param b The raw byte 84 | * @param signed If the byte is signed 85 | * @return Signed/unsigned byte as int. 86 | */ 87 | public static int readByte(byte b, boolean signed){ 88 | if(signed){ 89 | return b; 90 | } else { 91 | return b & 0xFF; 92 | } 93 | } 94 | 95 | /** 96 | * Writes a signed/unsigned byte. 97 | * @param b Raw byte 98 | * @return The byte. 99 | */ 100 | public static byte writeByte(byte b){ 101 | return b; 102 | } 103 | 104 | /** 105 | * Reads an unsigned 16 bit big-endian number. 106 | * @param bytes Raw bytes 107 | * @return The unsigned short. 108 | */ 109 | public static int readShort(byte[] bytes){ 110 | return ((bytes[0] << 8) & 0x0000ff00) | (bytes[1] & 0x000000ff); 111 | } 112 | 113 | /** 114 | * Reads a signed 16 bit big-endian number. 115 | * @param bytes Raw bytes 116 | * @return The signed short. 117 | */ 118 | public static short readSignedShort(byte[] bytes){ 119 | return ByteBuffer.wrap(bytes).getShort(); 120 | } 121 | 122 | /** 123 | * Writes a signed 16 bit big-endian number. 124 | * @param s The short 125 | * @return Short as a byte array 126 | */ 127 | public static byte[] writeShort(short s){ 128 | return ByteBuffer.allocate(2).putShort(s).array(); 129 | } 130 | 131 | /** 132 | * Writes an unsigned 16 bit big-endian number. 133 | * @param s The unsigned short (integer) 134 | * @return Short as a byte array 135 | */ 136 | public static byte[] writeUnsignedShort(int s){ 137 | ByteBuffer bb = ByteBuffer.allocate(2); 138 | bb.put((byte) ((s >> 8) & 0xff)); 139 | bb.put((byte) (s & 0xff)); 140 | return bb.array(); 141 | } 142 | 143 | /** 144 | * Reads a signed 32 bit big-endian number. 145 | * @param bytes Raw bytes 146 | * @return The integer. 147 | */ 148 | public static int readInt(byte[] bytes){ 149 | return ByteBuffer.wrap(bytes).getInt(); 150 | } 151 | 152 | /** 153 | * Writes a signed 32 bit big-endian number. 154 | * @param i The integer. 155 | * @return Integer as a byte array 156 | */ 157 | public static byte[] writeInt(int i){ 158 | return ByteBuffer.allocate(4).putInt(i).array(); 159 | } 160 | 161 | /** 162 | * Reads a signed 32 bit big-endian floating point number. 163 | * @param bytes Raw bytes 164 | * @return The float 165 | */ 166 | public static float readFloat(byte[] bytes){ 167 | return ByteBuffer.wrap(bytes).getFloat(); 168 | } 169 | 170 | /** 171 | * Writes a signed 32 bit big-endian floating point number. 172 | * @param f The float. 173 | * @return The float as a byte array 174 | */ 175 | public static byte[] writeFloat(float f){ 176 | return ByteBuffer.allocate(4).putFloat(f).array(); 177 | } 178 | 179 | /** 180 | * Reads a signed 64 bit big-endian double precision number. 181 | * @param bytes Raw bytes 182 | * @return The double. 183 | */ 184 | public static double readDouble(byte[] bytes){ 185 | return ByteBuffer.wrap(bytes).getFloat(); 186 | } 187 | 188 | /** 189 | * Writes a signed 64 bit big-endian double precision number. 190 | * @param d The double. 191 | * @return The double as a byte array 192 | */ 193 | public static byte[] writeDouble(double d){ 194 | return ByteBuffer.allocate(8).putDouble(d).array(); 195 | } 196 | 197 | /** 198 | * Reads a signed 64 bit big-endian number. 199 | * @param bytes Raw bytes 200 | * @return The long 201 | */ 202 | public static long readLong(byte[] bytes){ 203 | return ByteBuffer.wrap(bytes).getLong(); 204 | } 205 | 206 | /** 207 | * Writes a signed 64 bit big-endian number. 208 | * @param l The long. 209 | * @return The long as a byte array. 210 | */ 211 | public static byte[] writeLong(long l){ 212 | return ByteBuffer.allocate(8).putLong(l).array(); 213 | } 214 | 215 | public static byte[] subbytes(byte[] bytes, int start, int length){ 216 | ByteBuffer bb = ByteBuffer.wrap(bytes); 217 | bb.position(start); 218 | byte[] bytes2 = new byte[length]; 219 | bb.get(bytes2); 220 | return bytes2; 221 | } 222 | 223 | public static byte[] subbytes(byte[] bytes, int start){ 224 | return subbytes(bytes, start, bytes.length - start); 225 | } 226 | 227 | public static byte[][] splitbytes(byte[] bytes, int chunkSize){ 228 | byte[][] splits = new byte[1024][chunkSize]; 229 | int chunks = 0; 230 | for(int i=0;i chunkSize){ 232 | splits[chunks] = Arrays.copyOfRange(bytes, i, i + chunkSize); 233 | } else { 234 | splits[chunks] = Arrays.copyOfRange(bytes, i, bytes.length); 235 | } 236 | chunks++; 237 | } 238 | 239 | splits = Arrays.copyOf(splits, chunks); 240 | 241 | return splits; 242 | } 243 | } -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/JRakLib.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib; 21 | 22 | import java.time.Instant; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * JRakLib Constants Class. 27 | */ 28 | public abstract class JRakLib { 29 | public static final String VERSION = "1.1.1-SNAPSHOT"; 30 | public static final byte PROTOCOL = 7; 31 | public static final byte[] MAGIC = new byte[]{ 32 | 0x00, (byte) 0xff, (byte) 0xff, 0x00, 33 | (byte) 0xfe, (byte) 0xfe, (byte) 0xfe, (byte) 0xfe, 34 | (byte) 0xfd, (byte) 0xfd, (byte) 0xfd, (byte) 0xfd, 35 | 0x12, 0x34, 0x56, 0x78 }; 36 | 37 | public static final byte PRIORITY_NORMAL = 0; 38 | public static final byte PRIORITY_IMMEDIATE = 1; 39 | 40 | public static final byte FLAG_NEED_ACK = 0b00001000; 41 | 42 | /* 43 | * ENCAPSULATED payload: 44 | * byte (identifier length) 45 | * byte[] (identifier) 46 | * byte (flags, last 3 bits, priority) 47 | * payload (binary internal EncapsulatedPacket) 48 | */ 49 | public static final byte PACKET_ENCAPSULATED = 0x01; 50 | /* 51 | * OPEN_SESSION payload: 52 | * byte (identifier length) 53 | * byte[] (identifier) 54 | * byte (address length) 55 | * byte[] (address) 56 | * short (port) 57 | * long (clientID) 58 | */ 59 | public static final byte PACKET_OPEN_SESSION = 0x02; 60 | /* 61 | * CLOSE_SESSION payload: 62 | * byte (identifier length) 63 | * byte[] (identifier) 64 | * string (reason) 65 | */ 66 | public static final byte PACKET_CLOSE_SESSION = 0x03; 67 | /* 68 | * INVALID_SESSION payload: 69 | * byte (identifier length) 70 | * byte[] (identifier) 71 | */ 72 | public static final byte PACKET_INVALID_SESSION = 0x04; 73 | /* SEND_QUEUE payload: 74 | * byte (identifier length) 75 | * byte[] (identifier) 76 | */ 77 | public static final byte PACKET_SEND_QUEUE = 0x05; 78 | /* 79 | * ACK_NOTIFICATION payload: 80 | * byte (identifier length) 81 | * byte[] (identifier) 82 | * int (identifierACK) 83 | */ 84 | public static final byte PACKET_ACK_NOTIFICATION = 0x06; 85 | /* 86 | * SET_OPTION payload: 87 | * byte (option name length) 88 | * byte[] (option name) 89 | * byte[] (option value) 90 | */ 91 | public static final byte PACKET_SET_OPTION = 0x07; 92 | /* 93 | * RAW payload: 94 | * byte (address length) 95 | * byte[] (address from/to) 96 | * short (port) 97 | * byte[] (payload) 98 | */ 99 | public static final byte PACKET_RAW = 0x08; 100 | /* 101 | * RAW payload: 102 | * byte (address length) 103 | * byte[] (address) 104 | * int (timeout) 105 | */ 106 | public static final byte PACKET_BLOCK_ADDRESS = 0x09; 107 | /* 108 | * No payload 109 | * 110 | * Sends the disconnect message, removes sessions correctly, closes sockets. 111 | */ 112 | public static final byte PACKET_SHUTDOWN = 0x7e; 113 | /* 114 | * No payload 115 | * 116 | * Leaves everything as-is and halts, other Threads can be in a post-crash condition. 117 | */ 118 | public static final byte PACKET_EMERGENCY_SHUTDOWN = 0x7f; 119 | 120 | /** 121 | * RAW payload: 122 | * byte (message length) 123 | * byte[] message 124 | * ushort (class length) 125 | * byte[] class message 126 | */ 127 | public static final byte PACKET_EXCEPTION_CAUGHT = 0x7d; 128 | 129 | public static void sleepUntil(long time) { 130 | while (true) { 131 | if (Instant.now().toEpochMilli() >= time) { 132 | break; 133 | } 134 | } 135 | } 136 | 137 | public static String getAddressFromString(String address){ 138 | if(address.contains("/")){ 139 | return address.split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[0]; 140 | } else { 141 | return address.split(Pattern.quote(":"))[0]; 142 | } 143 | } 144 | 145 | public static int getPortFromString(String address){ 146 | if(address.contains("/")){ 147 | return Integer.parseInt(address.split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[1]); 148 | } else { 149 | return Integer.parseInt(address.split(Pattern.quote(":"))[1]); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/Logger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib; 21 | 22 | /** 23 | * Interface for an implementation of a logger wrapper that JRakLib can use. 24 | */ 25 | public interface Logger { 26 | void notice(String message); 27 | void critical(String message); 28 | void emergency(String message); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | import net.beaconpe.jraklib.JRakLib; 24 | import net.beaconpe.jraklib.protocol.EncapsulatedPacket; 25 | 26 | import java.nio.ByteBuffer; 27 | import java.util.Arrays; 28 | 29 | /** 30 | * A handler class for handling the client. 31 | * 32 | * @author jython234 33 | */ 34 | public class ClientHandler { 35 | protected JRakLibClient client; 36 | protected ClientInstance instance; 37 | 38 | public ClientHandler(JRakLibClient client, ClientInstance instance){ 39 | this.client = client; 40 | this.instance = instance; 41 | } 42 | 43 | public void sendEncapsulated(EncapsulatedPacket packet){ 44 | byte flags = JRakLib.PRIORITY_NORMAL; 45 | sendEncapsulated("", packet, flags); 46 | } 47 | 48 | public void sendEncapsulated(String identifier, EncapsulatedPacket packet, byte flags){ 49 | ByteBuffer bb = ByteBuffer.allocate(3+identifier.getBytes().length+packet.getTotalLength(true)); 50 | bb.put(JRakLib.PACKET_ENCAPSULATED).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put(flags).put(packet.toBinary(true)); 51 | client.pushMainToThreadPacket(Arrays.copyOf(bb.array(), bb.position())); 52 | bb = null; 53 | } 54 | 55 | public void sendRaw(byte[] payload){ 56 | sendRaw(client.getServerIP(), (short) client.getServerPort(), payload); 57 | } 58 | 59 | public void sendRaw(String address, short port, byte[] payload){ 60 | ByteBuffer bb = ByteBuffer.allocate(4+address.getBytes().length+payload.length); 61 | bb.put(JRakLib.PACKET_RAW).put((byte) address.getBytes().length).put(address.getBytes()).put(Binary.writeShort(port)).put(payload); 62 | client.pushMainToThreadPacket(bb.array()); 63 | } 64 | 65 | public void sendOption(String name, String value){ 66 | ByteBuffer bb = ByteBuffer.allocate(2+name.getBytes().length+value.getBytes().length); 67 | bb.put(JRakLib.PACKET_SET_OPTION).put((byte) name.getBytes().length).put(name.getBytes()).put(value.getBytes()); 68 | client.pushMainToThreadPacket(bb.array()); 69 | } 70 | 71 | public void disconnectFromServer(){ 72 | shutdown(); 73 | } 74 | 75 | public void shutdown(){ 76 | client.shutdown(); 77 | client.pushMainToThreadPacket(new byte[] {JRakLib.PACKET_SHUTDOWN}); 78 | //TODO: Find a way to kill client after sleep. 79 | } 80 | 81 | public void emergencyShutdown(){ 82 | client.shutdown(); 83 | client.pushMainToThreadPacket(new byte[] {0x7f}); //JRakLib::PACKET_EMERGENCY_SHUTDOWN 84 | } 85 | 86 | public boolean handlePacket(){ 87 | byte[] packet = client.readThreadToMainPacket(); 88 | if(packet == null){ 89 | return false; 90 | } 91 | if(packet.length > 0){ 92 | byte id = packet[0]; 93 | int offset = 1; 94 | if(id == JRakLib.PACKET_ENCAPSULATED){ 95 | int len = packet[offset++]; 96 | String identifier = new String(Binary.subbytes(packet, offset, len)); 97 | offset += len; 98 | byte flags = packet[offset++]; 99 | byte[] buffer = Binary.subbytes(packet, offset); 100 | instance.handleEncapsulated(EncapsulatedPacket.fromBinary(buffer, true), flags); 101 | } else if(id == JRakLib.PACKET_RAW){ 102 | int len = packet[offset++]; 103 | String address = new String(Binary.subbytes(packet, offset, len)); 104 | offset += len; 105 | int port = Binary.readShort(Binary.subbytes(packet, offset, 2)); 106 | offset += 2; 107 | byte[] payload = Binary.subbytes(packet, offset); 108 | instance.handleRaw(payload); 109 | } else if(id == JRakLib.PACKET_SET_OPTION){ 110 | int len = packet[offset++]; 111 | String name = new String(Binary.subbytes(packet, offset, len)); 112 | offset += len; 113 | String value = new String(Binary.subbytes(packet, offset)); 114 | instance.handleOption(name, value); 115 | } else if(id == JRakLib.PACKET_OPEN_SESSION){ 116 | int len = packet[offset++]; 117 | String identifier = new String(Binary.subbytes(packet, offset, len)); 118 | offset += len; 119 | long serverID = Binary.readLong(Binary.subbytes(packet, offset, 8)); 120 | instance.connectionOpened(serverID); 121 | } else if(id == JRakLib.PACKET_CLOSE_SESSION){ 122 | int len = packet[offset++]; 123 | String identifier = new String(Binary.subbytes(packet, offset, len)); 124 | offset += len; 125 | len = packet[offset++]; 126 | String reason = new String(Binary.subbytes(packet, offset, len)); 127 | instance.connectionClosed(reason); 128 | } 129 | return true; 130 | } 131 | return false; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/ClientInstance.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import net.beaconpe.jraklib.protocol.EncapsulatedPacket; 23 | 24 | /** 25 | * An interface for communication with the client implementation 26 | * . 27 | * @author jython234 28 | */ 29 | public interface ClientInstance { 30 | 31 | /** 32 | * Called when the connection is opened to the RakNet server. 33 | * @param serverId The serverId of the RakNet server. 34 | */ 35 | void connectionOpened(long serverId); 36 | 37 | /** 38 | * Called when the connection is closed. 39 | * @param reason The reason for the closure. 40 | */ 41 | void connectionClosed(String reason); 42 | 43 | /** 44 | * Called when an EncapsulatedPacket is received from the RakNet server. 45 | * @param packet The packet received. 46 | * @param flags Flags from the packet. 47 | */ 48 | void handleEncapsulated(EncapsulatedPacket packet, int flags); 49 | 50 | /** 51 | * Called when an unrecognized packet is received from the server. 52 | * @param payload Raw payload (in bytes) of the strange packet. 53 | */ 54 | void handleRaw(byte[] payload); 55 | 56 | /** 57 | * Called when an internal option is updated. Current only supports "bandwith" 58 | * @param option The option name. 59 | * @param value The option value. 60 | */ 61 | void handleOption(String option, String value); 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/Connection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | import net.beaconpe.jraklib.JRakLib; 24 | import net.beaconpe.jraklib.protocol.*; 25 | 26 | import java.io.IOException; 27 | import java.net.InetAddress; 28 | import java.net.InetSocketAddress; 29 | import java.nio.ByteBuffer; 30 | import java.time.Instant; 31 | import java.util.*; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | 34 | /** 35 | * Represents a Connection to a server. 36 | * 37 | * @author jython234 38 | */ 39 | public class Connection { 40 | 41 | public final static int STATE_UNCONNECTED = 0; 42 | public final static int STATE_CONNECTING_1 = 1; 43 | public final static int STATE_CONNECTING_2 = 2; 44 | public final static int STATE_CONNECTING_3 = 3; 45 | public final static int STATE_CONNECTED = 4; 46 | 47 | public static long sessionID = new Random().nextLong(); 48 | public static int WINDOW_SIZE = 2048; 49 | 50 | protected ConnectionManager manager; 51 | 52 | private int state = STATE_UNCONNECTED; 53 | private List preJoinQueue = new ArrayList<>(); 54 | private int mtuSize = 548; //Min size 55 | private long id = 0; 56 | private int splitID = 0; 57 | 58 | private int messageIndex = 0; 59 | private Map channelIndex = new ConcurrentHashMap<>(); 60 | 61 | private int sendSeqNumber = 0; 62 | private int lastSeqNumber = -1; 63 | 64 | private long lastUpdate; 65 | private long startTime; 66 | 67 | private List packetToSend = new ArrayList<>(); 68 | 69 | private boolean isActive; 70 | 71 | private List ACKQueue = new ArrayList<>(); 72 | private List NACKQueue = new ArrayList<>(); 73 | 74 | private Map recoveryQueue = new ConcurrentHashMap<>(); 75 | 76 | private Map> splitPackets = new ConcurrentHashMap<>(); 77 | 78 | private Map> needACK = new ConcurrentHashMap<>(); 79 | 80 | 81 | private DataPacket sendQueue; 82 | 83 | private int windowStart; 84 | private Map receivedWindow = new ConcurrentHashMap<>(); 85 | private int windowEnd; 86 | 87 | private int reliableWindowStart; 88 | private int reliableWindowEnd; 89 | private Map reliableWindow = new ConcurrentHashMap<>(); 90 | private int lastReliableIndex = -1; 91 | 92 | private long lastPing; 93 | private long lastPong = Instant.now().toEpochMilli(); 94 | 95 | public Connection(ConnectionManager manager, int mtuSize){ 96 | this.manager = manager; 97 | this.mtuSize = mtuSize; 98 | sendQueue = new DataPackets.DATA_PACKET_4(); 99 | lastUpdate = Instant.now().toEpochMilli(); 100 | startTime = Instant.now().toEpochMilli(); 101 | isActive = false; 102 | windowStart = -1; 103 | windowEnd = WINDOW_SIZE; 104 | 105 | reliableWindowStart = 0; 106 | reliableWindowEnd = WINDOW_SIZE; 107 | 108 | for(byte i = 0; i < 32; i++){ 109 | channelIndex.put(i, 0); 110 | } 111 | } 112 | 113 | public void update(long time) throws IOException { 114 | isActive = false; 115 | if((Instant.now().toEpochMilli() - lastPong) >= 8000){ 116 | disconnect("connection timed out."); 117 | } 118 | if((Instant.now().toEpochMilli() - lastPing) >= 5000){ 119 | sendPing(); 120 | } 121 | if(!ACKQueue.isEmpty()){ 122 | ACK pk = new ACK(); 123 | pk.packets = ACKQueue.stream().toArray(Integer[]::new); 124 | sendPacket(pk); 125 | ACKQueue.clear(); 126 | } 127 | 128 | if(!NACKQueue.isEmpty()){ 129 | NACK pk = new NACK(); 130 | pk.packets = NACKQueue.stream().toArray(Integer[]::new); 131 | sendPacket(pk); 132 | NACKQueue.clear(); 133 | } 134 | 135 | if(!packetToSend.isEmpty()){ 136 | int limit = 16; 137 | for(int i = 0; i < packetToSend.size(); i++){ 138 | DataPacket pk = packetToSend.get(i); 139 | pk.sendTime = time; 140 | pk.encode(); 141 | recoveryQueue.put(pk.seqNumber, pk); 142 | packetToSend.remove(pk); 143 | sendPacket(pk); 144 | if(limit-- <= 0){ 145 | break; 146 | } 147 | } 148 | } 149 | 150 | if(packetToSend.size() > WINDOW_SIZE){ 151 | packetToSend.clear(); 152 | } 153 | 154 | if(needACK.values().size() > 0){ 155 | for(Integer i : needACK.keySet()){ 156 | Map indexes = needACK.get(i); 157 | if(indexes.values().size() == 0){ 158 | needACK.remove(indexes); 159 | //sessionManager.notifyACK(this, i); 160 | } 161 | } 162 | } 163 | 164 | for(Integer seq : recoveryQueue.keySet()){ 165 | DataPacket pk = recoveryQueue.get(seq); 166 | if(pk.sendTime < Instant.now().toEpochMilli() - 6000){ //If no ACK in 6 seconds, resend :) 167 | packetToSend.add(pk); 168 | recoveryQueue.remove(seq); 169 | } else { 170 | break; 171 | } 172 | } 173 | 174 | for(Integer seq : receivedWindow.keySet()){ 175 | if(seq < windowStart){ 176 | receivedWindow.remove(seq); 177 | } else { 178 | break; 179 | } 180 | } 181 | 182 | try { 183 | sendQueue(); 184 | } catch (IOException e) { 185 | manager.getLogger().notice("Failed to send queue: IOException: "+e.getMessage()); 186 | throw new RuntimeException(e); 187 | } 188 | } 189 | 190 | private void sendPing() throws IOException { 191 | PING_DataPacket ping = new PING_DataPacket(); 192 | ping.pingID = Instant.now().toEpochMilli(); 193 | ping.encode(); 194 | 195 | EncapsulatedPacket pk = new EncapsulatedPacket(); 196 | pk.reliability = 0; 197 | pk.buffer = ping.buffer; 198 | addToQueue(pk); 199 | lastPing = Instant.now().toEpochMilli(); 200 | } 201 | 202 | public void disconnect(String reason){ 203 | manager.streamClose("0.0.0.0:"+manager.getSocket().getSocket().getLocalPort(), reason); 204 | manager.shutdown = true; 205 | } 206 | 207 | public void disconnect() { 208 | disconnect("disconnected by client."); 209 | } 210 | 211 | private void sendPacket(Packet pk) throws IOException { 212 | manager.sendPacket(pk, manager.getClient().getServerIP(), manager.getClient().getServerPort()); 213 | } 214 | 215 | public void sendQueue() throws IOException { 216 | if(!sendQueue.packets.isEmpty()){ 217 | sendQueue.seqNumber = sendSeqNumber++; 218 | sendPacket(sendQueue); 219 | sendQueue.sendTime = Instant.now().toEpochMilli(); 220 | recoveryQueue.put(sendQueue.seqNumber, sendQueue); 221 | sendQueue = new DataPackets.DATA_PACKET_4(); 222 | } 223 | } 224 | 225 | private void addToQueue(EncapsulatedPacket pk) throws IOException { 226 | addToQueue(pk, JRakLib.PRIORITY_NORMAL); 227 | } 228 | 229 | private void addToQueue(EncapsulatedPacket pk, int flags) throws IOException { 230 | int priority = flags & 0b0000111; 231 | if(pk.needACK && pk.messageIndex != -1){ 232 | Map map; 233 | if(needACK.get(pk.needACK) != null){ 234 | map = needACK.get(pk.needACK); 235 | map.put(pk.messageIndex, pk.messageIndex); 236 | } else { 237 | map = new ConcurrentHashMap<>(); 238 | map.put(pk.messageIndex, pk.messageIndex); 239 | } 240 | needACK.put(pk.identifierACK, map); 241 | } 242 | 243 | if(priority == JRakLib.PRIORITY_IMMEDIATE){ //Skip queues 244 | DataPacket packet = new DataPackets.DATA_PACKET_0(); 245 | packet.seqNumber = sendSeqNumber++; 246 | if(pk.needACK){ 247 | packet.packets.add(pk); 248 | pk.needACK = false; 249 | } else { 250 | packet.packets.add(pk.toBinary()); 251 | } 252 | 253 | sendPacket(packet); 254 | packet.sendTime = Instant.now().toEpochMilli(); 255 | recoveryQueue.put(packet.seqNumber, packet); 256 | return; 257 | } 258 | int length = sendQueue.length(); 259 | if(length + pk.getTotalLength() > mtuSize){ 260 | sendQueue(); 261 | } 262 | 263 | if(pk.needACK){ 264 | sendQueue.packets.add(pk); 265 | pk.needACK = false; 266 | } else { 267 | sendQueue.packets.add(pk.toBinary()); 268 | } 269 | } 270 | 271 | public void addEncapsulatedToQueue(EncapsulatedPacket packet) throws IOException { 272 | addEncapsulatedToQueue(packet, JRakLib.PRIORITY_NORMAL); 273 | } 274 | 275 | public void addEncapsulatedToQueue(EncapsulatedPacket packet, byte flags) throws IOException { 276 | if((packet.needACK = (flags & JRakLib.FLAG_NEED_ACK) > 0) == true){ 277 | needACK.put(packet.identifierACK, new ConcurrentHashMap<>()); 278 | } 279 | 280 | if(packet.reliability == 2 || packet.reliability == 3 || packet.reliability == 4 || packet.reliability == 6 || packet.reliability == 7){ 281 | packet.messageIndex = messageIndex++; 282 | 283 | if(packet.reliability == 3){ 284 | channelIndex.put(packet.orderChannel, channelIndex.get(packet.orderChannel) + 1); 285 | packet.orderIndex = channelIndex.get(packet.orderChannel); 286 | } 287 | } 288 | 289 | if(packet.getTotalLength() + 4 > mtuSize){ 290 | byte[][] buffers = Binary.splitbytes(packet.buffer, mtuSize - 34); 291 | int splitID = this.splitID++; 292 | splitID = splitID % 65536; 293 | for(int count = 0; count < buffers.length; count++){ 294 | byte[] buffer = buffers[count]; 295 | EncapsulatedPacket pk = new EncapsulatedPacket(); 296 | pk.splitID = (short) splitID; 297 | pk.hasSplit = true; 298 | pk.splitCount = buffers.length; 299 | pk.reliability = packet.reliability; 300 | pk.splitIndex = count; 301 | pk.buffer = buffer; 302 | if(count > 0){ 303 | pk.messageIndex = messageIndex++; 304 | } else { 305 | pk.messageIndex = packet.messageIndex; 306 | } 307 | if(pk.reliability == 3){ 308 | pk.orderChannel = packet.orderChannel; 309 | pk.orderIndex = packet.orderIndex; 310 | } 311 | addToQueue(pk, flags | JRakLib.PRIORITY_IMMEDIATE); 312 | } 313 | } else { 314 | addToQueue(packet, flags); 315 | } 316 | } 317 | 318 | private void handleSplit(EncapsulatedPacket packet) throws IOException { 319 | if(packet.splitCount >= 128){ 320 | return; 321 | } 322 | 323 | if(!splitPackets.containsKey(packet.splitID)){ 324 | Map map = new ConcurrentHashMap<>(); 325 | map.put(packet.splitIndex, packet); 326 | splitPackets.put(packet.splitID, map); 327 | } else { 328 | Map map = splitPackets.get(packet.splitID); 329 | map.put(packet.splitIndex, packet); 330 | splitPackets.put(packet.splitID, map); 331 | } 332 | 333 | if(splitPackets.get(packet.splitID).values().size() == packet.splitCount){ 334 | EncapsulatedPacket pk = new EncapsulatedPacket(); 335 | ByteBuffer bb = ByteBuffer.allocate(64 * 64 * 64); 336 | for(int i = 0; i < packet.splitCount; i++){ 337 | bb.put(splitPackets.get(packet.splitID).get(i).buffer); 338 | } 339 | pk.buffer = Arrays.copyOf(bb.array(), bb.position()); 340 | bb = null; 341 | 342 | pk.length = pk.buffer.length; 343 | splitPackets.remove(packet.splitID); 344 | 345 | handleEncapsulatedPacketRoute(pk); 346 | } 347 | } 348 | 349 | private void handleEncapsulatedPacket(EncapsulatedPacket packet) throws IOException { 350 | if(packet.messageIndex == -1){ 351 | handleEncapsulatedPacketRoute(packet); 352 | } else { 353 | if(packet.messageIndex < reliableWindowStart || packet.messageIndex > reliableWindowEnd){ 354 | return; 355 | } 356 | 357 | if((packet.messageIndex - lastReliableIndex) == 1){ 358 | lastReliableIndex++; 359 | reliableWindowStart++; 360 | reliableWindowEnd++; 361 | handleEncapsulatedPacketRoute(packet); 362 | 363 | if(!reliableWindow.values().isEmpty()){ 364 | //TODO: Implement ksort() ? 365 | //ksort(reliableWindow.values()); 366 | 367 | for(Integer index : reliableWindow.keySet()){ 368 | EncapsulatedPacket pk = reliableWindow.get(index); 369 | 370 | if((index - lastReliableIndex) != 1){ 371 | break; 372 | } 373 | lastReliableIndex++; 374 | reliableWindowStart++; 375 | reliableWindowEnd++; 376 | handleEncapsulatedPacketRoute(packet); 377 | reliableWindow.remove(index); 378 | } 379 | } 380 | } else { 381 | reliableWindow.put(packet.messageIndex, packet); 382 | } 383 | } 384 | } 385 | 386 | private void handleEncapsulatedPacketRoute(EncapsulatedPacket packet) throws IOException { 387 | if(manager == null){ 388 | return; 389 | } 390 | 391 | if(packet.hasSplit){ 392 | if(state == STATE_CONNECTED){ 393 | handleSplit(packet); 394 | } 395 | return; 396 | } 397 | 398 | byte id = packet.buffer[0]; 399 | if(id < 0x80) { //internal data packet 400 | if (state == STATE_CONNECTING_3) { 401 | if (id == SERVER_HANDSHAKE_DataPacket.ID) { 402 | SERVER_HANDSHAKE_DataPacket pk = new SERVER_HANDSHAKE_DataPacket(); 403 | pk.buffer = packet.buffer; 404 | pk.decode(); 405 | 406 | CLIENT_HANDSHAKE_DataPacket response = new CLIENT_HANDSHAKE_DataPacket(); 407 | response.address = new InetSocketAddress("0.0.0.0", 0); 408 | response.systemAddresses = new InetSocketAddress[] { 409 | new InetSocketAddress("0.0.0.0", 0), 410 | new InetSocketAddress("0.0.0.0", 0), 411 | new InetSocketAddress("0.0.0.0", 0), 412 | new InetSocketAddress("0.0.0.0", 0), 413 | new InetSocketAddress("0.0.0.0", 0), 414 | new InetSocketAddress("0.0.0.0", manager.getSocket().getSocket().getLocalPort()), 415 | new InetSocketAddress("0.0.0.0", 0), 416 | new InetSocketAddress("0.0.0.0", manager.getSocket().getSocket().getLocalPort()), 417 | new InetSocketAddress("0.0.0.0", 0), 418 | new InetSocketAddress("0.0.0.0", manager.getSocket().getSocket().getLocalPort()), 419 | }; 420 | response.sendPing = Instant.now().toEpochMilli(); 421 | response.sendPong = Instant.now().toEpochMilli(); 422 | response.encode(); 423 | 424 | EncapsulatedPacket sendPacket = new EncapsulatedPacket(); 425 | sendPacket.reliability = 0; 426 | sendPacket.buffer = response.buffer; 427 | addToQueue(sendPacket, JRakLib.PRIORITY_IMMEDIATE); 428 | 429 | if(!manager.portChecking){ 430 | state = STATE_CONNECTED; 431 | manager.streamOpen(this.id); 432 | for(EncapsulatedPacket p : preJoinQueue){ 433 | manager.streamEncapsulated(p); 434 | } 435 | preJoinQueue.clear(); 436 | } 437 | 438 | } 439 | } else if (id == CLIENT_DISCONNECT_DataPacket.ID) { 440 | disconnect("disconnected by server."); 441 | } else if (id == PING_DataPacket.ID) { 442 | PING_DataPacket dataPacket = new PING_DataPacket(); 443 | dataPacket.buffer = packet.buffer; 444 | dataPacket.decode(); 445 | 446 | PONG_DataPacket pk = new PONG_DataPacket(); 447 | pk.pingID = dataPacket.pingID; 448 | pk.encode(); 449 | 450 | EncapsulatedPacket sendPacket = new EncapsulatedPacket(); 451 | sendPacket.reliability = 0; 452 | sendPacket.buffer = pk.buffer; 453 | addToQueue(sendPacket); 454 | //TODO: add PING/PONG (0x00/0x03) automatic latency measure 455 | } else if (id == PONG_DataPacket.ID) { 456 | lastPong = Instant.now().toEpochMilli(); 457 | } else if(state == STATE_CONNECTED) { 458 | manager.streamEncapsulated(packet); 459 | //TODO: stream channels 460 | } 461 | } else { 462 | preJoinQueue.add(packet); 463 | } 464 | } 465 | 466 | public void handlePacket(Packet packet) throws IOException{ 467 | isActive = true; 468 | lastUpdate = Instant.now().toEpochMilli(); 469 | if(state == STATE_CONNECTED || state == STATE_CONNECTING_3){ 470 | if(packet.buffer[0] >= 0x80 || packet.buffer[0] <= 0x8f && packet instanceof DataPacket){ 471 | packet.decode(); 472 | 473 | DataPacket dp = (DataPacket) packet; 474 | if(dp.seqNumber < windowStart || dp.seqNumber > windowEnd || receivedWindow.containsKey(dp.seqNumber)){ 475 | return; 476 | } 477 | 478 | int diff = dp.seqNumber - lastSeqNumber; 479 | 480 | NACKQueue.remove(Integer.valueOf(dp.seqNumber)); 481 | ACKQueue.add(dp.seqNumber); 482 | receivedWindow.put(dp.seqNumber, dp.seqNumber); 483 | 484 | if(diff != 1){ 485 | for(int i = lastSeqNumber + 1; i < dp.seqNumber; i++){ 486 | if(!receivedWindow.containsKey(i)){ 487 | NACKQueue.add(i); 488 | } 489 | } 490 | } 491 | 492 | if(diff >= 1){ 493 | lastSeqNumber = dp.seqNumber; 494 | windowStart += diff; 495 | windowEnd += diff; 496 | } 497 | 498 | for(Object pk : dp.packets){ 499 | if(pk instanceof EncapsulatedPacket) { 500 | handleEncapsulatedPacket((EncapsulatedPacket) pk); 501 | } 502 | } 503 | } else { 504 | if(packet instanceof ACK){ 505 | packet.decode(); 506 | for(int seq : ((ACK) packet).packets){ 507 | if(recoveryQueue.containsKey(seq)){ 508 | for(Object pk : recoveryQueue.get(seq).packets){ 509 | if(pk instanceof EncapsulatedPacket && ((EncapsulatedPacket) pk).needACK && ((EncapsulatedPacket) pk).messageIndex != -1){ 510 | if(needACK.containsKey(((EncapsulatedPacket) pk).identifierACK)){ 511 | Map map = needACK.get(((EncapsulatedPacket) pk).identifierACK); 512 | map.remove(((EncapsulatedPacket) pk).messageIndex); 513 | needACK.put(((EncapsulatedPacket) pk).identifierACK, map); 514 | } 515 | } 516 | recoveryQueue.remove(seq); 517 | } 518 | } 519 | } 520 | } else if(packet instanceof NACK){ 521 | packet.decode(); 522 | for(Integer seq : ((NACK) packet).packets){ 523 | if(recoveryQueue.containsKey(seq)){ 524 | DataPacket pk = recoveryQueue.get(seq); 525 | pk.seqNumber = sendSeqNumber++; 526 | packetToSend.add(pk); 527 | recoveryQueue.remove(seq); 528 | } 529 | } 530 | } 531 | } 532 | } else if(packet.buffer[0] > 0x00 || packet.buffer[0] < 0x80){ //Not Data packet :) 533 | packet.decode(); 534 | if(packet instanceof OPEN_CONNECTION_REPLY_1){ 535 | OPEN_CONNECTION_REPLY_1 reply1 = (OPEN_CONNECTION_REPLY_1) packet; 536 | this.id = reply1.serverID; 537 | OPEN_CONNECTION_REQUEST_2 request2 = new OPEN_CONNECTION_REQUEST_2(); 538 | request2.mtuSize = (short) this.mtuSize; 539 | request2.clientID = JRakLibClient.getClientID(); 540 | request2.serverAddress = manager.getClient().getServerEndpoint(); 541 | request2.encode(); 542 | sendPacket(request2); 543 | if(reply1.mtuSize == this.mtuSize){ 544 | state = STATE_CONNECTING_2; 545 | } 546 | } else if(state == STATE_CONNECTING_2 && packet instanceof OPEN_CONNECTION_REPLY_2){ 547 | OPEN_CONNECTION_REPLY_2 reply2 = (OPEN_CONNECTION_REPLY_2) packet; 548 | if(((OPEN_CONNECTION_REPLY_2) packet).clientAddress.getPort() == manager.getSocket().getSocket().getLocalPort() || !manager.portChecking){ 549 | CLIENT_CONNECT_DataPacket connect = new CLIENT_CONNECT_DataPacket(); 550 | connect.clientID = JRakLibClient.getClientID(); 551 | connect.sendPing = sessionID; 552 | connect.encode(); 553 | 554 | EncapsulatedPacket pk = new EncapsulatedPacket(); 555 | pk.reliability = 0; 556 | pk.buffer = connect.buffer; 557 | addToQueue(pk, JRakLib.PRIORITY_IMMEDIATE); 558 | sendPing(); 559 | state = STATE_CONNECTING_3; 560 | } 561 | } 562 | } 563 | } 564 | 565 | protected void onShutdown() { 566 | try { 567 | close(); 568 | } catch (IOException e) { 569 | manager.getClient().getLogger().critical("Could not close connection to server, IOException: "+e.getMessage()); 570 | e.printStackTrace(); 571 | } 572 | } 573 | 574 | public void close() throws IOException { 575 | byte[] data = new byte[] {0x00, 0x00, 0x08, 0x15}; 576 | addEncapsulatedToQueue(EncapsulatedPacket.fromBinary(data), JRakLib.PRIORITY_IMMEDIATE); 577 | sendQueue(); 578 | manager = null; 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/ConnectionManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import static net.beaconpe.jraklib.JRakLib.getAddressFromString; 23 | import static net.beaconpe.jraklib.JRakLib.sleepUntil; 24 | import static net.beaconpe.jraklib.protocol.DataPackets.*; 25 | 26 | import net.beaconpe.jraklib.Binary; 27 | import net.beaconpe.jraklib.JRakLib; 28 | import net.beaconpe.jraklib.Logger; 29 | import net.beaconpe.jraklib.protocol.*; 30 | 31 | import java.io.IOException; 32 | import java.net.DatagramPacket; 33 | import java.net.InetSocketAddress; 34 | import java.net.SocketAddress; 35 | import java.nio.ByteBuffer; 36 | import java.time.Instant; 37 | import java.util.Arrays; 38 | import java.util.ConcurrentModificationException; 39 | import java.util.Map; 40 | import java.util.concurrent.ConcurrentHashMap; 41 | import java.util.regex.Pattern; 42 | 43 | /** 44 | * Manager for handling a connection. The manager handles the internal ticking, and networking. 45 | * 46 | * @author jython234 47 | */ 48 | public class ConnectionManager { 49 | protected Map> packetPool = new ConcurrentHashMap<>(); 50 | 51 | protected JRakLibClient client; 52 | 53 | protected UDPClientSocket socket; 54 | 55 | protected int receiveBytes = 0; 56 | protected int sendBytes = 0; 57 | 58 | protected boolean shutdown = false; 59 | 60 | protected int ticks = 0; 61 | protected long lastMeasure; 62 | 63 | protected Connection connection; 64 | 65 | public boolean portChecking = false; 66 | 67 | public ConnectionManager(JRakLibClient client, UDPClientSocket socket){ 68 | this.client = client; 69 | this.socket = socket; 70 | registerPackets(); 71 | 72 | try { 73 | if(!connect(1447, 4)){ 74 | getLogger().notice("Failed to connect to "+client.getServerEndpoint()+": no response."); 75 | client.pushMainToThreadPacket(new byte[] {JRakLib.PACKET_SHUTDOWN}); 76 | } else { 77 | run(); 78 | } 79 | } catch (IOException e) { 80 | getLogger().emergency("*** FAILED TO CONNECT TO " + client.getServerEndpoint() + ": IOException: " + e.getMessage()); 81 | e.printStackTrace(); 82 | client.pushMainToThreadPacket(new byte[]{JRakLib.PACKET_EMERGENCY_SHUTDOWN}); 83 | } 84 | } 85 | 86 | private boolean connect(int payloadSize, int packets) throws IOException { 87 | for(int i = 0; i < packets; i++){ 88 | OPEN_CONNECTION_REQUEST_1 request1 = new OPEN_CONNECTION_REQUEST_1(); 89 | request1.protocol = JRakLib.PROTOCOL; 90 | request1.mtuSize = (short) payloadSize; 91 | request1.encode(); 92 | socket.writePacket(request1.buffer, client.getServerEndpoint()); 93 | 94 | DatagramPacket response = socket.readPacketBlocking(500); 95 | if(response != null && response.getData()[0] == OPEN_CONNECTION_REPLY_1.ID){ 96 | connection = new Connection(this, payloadSize); 97 | Packet packet = getPacketFromPool(response.getData()[0]); 98 | packet.buffer = response.getData(); 99 | connection.handlePacket(packet); 100 | return true; 101 | } 102 | } 103 | if(payloadSize == 1447){ 104 | return connect(1155, 4); 105 | } else if(payloadSize == 1155){ 106 | return connect(531, 5); 107 | } else { 108 | return false; 109 | } 110 | } 111 | 112 | public Logger getLogger(){ 113 | return client.getLogger(); 114 | } 115 | 116 | public void run(){ 117 | try { 118 | tickProcessor(); 119 | } catch (Exception e) { 120 | throw new RuntimeException(e); 121 | } 122 | } 123 | 124 | private void tickProcessor() throws IOException { 125 | lastMeasure = Instant.now().toEpochMilli(); 126 | 127 | while(!shutdown){ 128 | long start = Instant.now().toEpochMilli(); 129 | int max = 5000; 130 | while(receivePacket()){ 131 | max = max - 1; 132 | } 133 | while(receiveStream()); 134 | long time = Instant.now().toEpochMilli() - start; 135 | if(time < 50){ //20 ticks per second (1000 / 20) 136 | sleepUntil(Instant.now().toEpochMilli()+(50 - time)); 137 | } 138 | tick(); 139 | } 140 | } 141 | 142 | private void tick() throws IOException { 143 | long time = Instant.now().toEpochMilli(); 144 | connection.update(time); 145 | 146 | if((ticks & 0b1111) == 0){ 147 | double diff = Math.max(0.005d, time - lastMeasure); 148 | streamOption("bandwith", "up:"+(sendBytes / diff)+",down:"+(receiveBytes / diff)); //TODO: Fix this stuff 149 | lastMeasure = time; 150 | sendBytes = 0; 151 | receiveBytes = 0; 152 | } 153 | ticks = ticks + 1; 154 | } 155 | 156 | private boolean receivePacket() throws IOException { 157 | DatagramPacket packet = socket.readPacket(); 158 | if(packet == null) { 159 | return false; 160 | } 161 | int len = packet.getLength(); 162 | if(len > 0){ 163 | SocketAddress source = packet.getSocketAddress(); 164 | receiveBytes += len; 165 | 166 | Packet pkt = getPacketFromPool(packet.getData()[0]); 167 | if(pkt != null){ 168 | pkt.buffer = packet.getData(); 169 | connection.handlePacket(pkt); 170 | return true; 171 | } else if (packet.getData() != null){ 172 | streamRaw(source, packet.getData()); 173 | return true; 174 | } else { 175 | getLogger().notice("Dropped packet: "+ Arrays.toString(packet.getData())); 176 | return false; 177 | } 178 | } 179 | return false; 180 | } 181 | 182 | public void streamEncapsulated(EncapsulatedPacket packet){ 183 | streamEncapsulated(packet, JRakLib.PRIORITY_NORMAL); 184 | } 185 | 186 | public void streamEncapsulated(EncapsulatedPacket packet, byte flags){ 187 | String id = client.getServerIP() + ":" + client.getServerPort(); 188 | ByteBuffer bb = ByteBuffer.allocate(3+id.getBytes().length+packet.getTotalLength(true)); 189 | bb.put(JRakLib.PACKET_ENCAPSULATED).put((byte) id.getBytes().length).put(id.getBytes()).put(flags).put(packet.toBinary(true)); 190 | client.pushThreadToMainPacket(bb.array()); 191 | } 192 | 193 | public void streamRaw(SocketAddress address, byte[] payload){ 194 | String dest; 195 | int port; 196 | if(address.toString().contains("/")) { 197 | dest = address.toString().split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[0]; 198 | port = Integer.parseInt(address.toString().split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[1]); 199 | } else { 200 | dest = address.toString().split(Pattern.quote(":"))[0]; 201 | port = Integer.parseInt(address.toString().split(Pattern.quote(":"))[1]); 202 | } 203 | streamRaw(dest, port, payload); 204 | } 205 | 206 | public void streamRaw(String address, int port, byte[] payload){ 207 | ByteBuffer bb = ByteBuffer.allocate(4 + address.getBytes().length + payload.length); 208 | bb.put(JRakLib.PACKET_RAW).put((byte) address.getBytes().length).put(address.getBytes()).put(Binary.writeShort((short) port)).put(payload); 209 | client.pushThreadToMainPacket(bb.array()); 210 | } 211 | 212 | protected void streamClose(String identifier, String reason){ 213 | ByteBuffer bb = ByteBuffer.allocate(3 + identifier.getBytes().length + reason.getBytes().length); 214 | bb.put(JRakLib.PACKET_CLOSE_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put((byte) reason.getBytes().length).put(reason.getBytes()); 215 | client.pushThreadToMainPacket(bb.array()); 216 | } 217 | 218 | protected void streamInvalid(String identifier){ 219 | ByteBuffer bb = ByteBuffer.allocate(2+identifier.getBytes().length); 220 | bb.put(JRakLib.PACKET_INVALID_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()); 221 | client.pushThreadToMainPacket(bb.array()); 222 | } 223 | 224 | protected void streamOpen(long serverId){ 225 | String identifier = client.getServerIP() + ":" + client.getServerPort(); 226 | ByteBuffer bb = ByteBuffer.allocate(10 + identifier.getBytes().length); 227 | bb.put(JRakLib.PACKET_OPEN_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put(Binary.writeLong(serverId)); 228 | client.pushThreadToMainPacket(bb.array()); 229 | } 230 | 231 | protected void streamACK(String identifier, int identifierACK){ 232 | ByteBuffer bb = ByteBuffer.allocate(6+identifier.getBytes().length); 233 | bb.put(JRakLib.PACKET_ACK_NOTIFICATION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put(Binary.writeInt(identifierACK)); 234 | client.pushThreadToMainPacket(bb.array()); 235 | } 236 | 237 | protected void streamOption(String name, String value){ 238 | ByteBuffer bb = ByteBuffer.allocate(2+name.getBytes().length+value.getBytes().length); 239 | bb.put(JRakLib.PACKET_SET_OPTION).put((byte) name.getBytes().length).put(name.getBytes()).put(value.getBytes()); 240 | client.pushThreadToMainPacket(bb.array()); 241 | } 242 | 243 | public void sendPacket(Packet packet, String dest, int port) throws IOException { 244 | packet.encode(); 245 | sendBytes += packet.buffer.length; 246 | socket.writePacket(packet.buffer, new InetSocketAddress(dest, port)); 247 | } 248 | 249 | public boolean receiveStream() throws IOException { 250 | byte[] packet = client.readMainToThreadPacket(); 251 | if(packet == null){ 252 | return false; 253 | } 254 | if(packet.length > 0){ 255 | byte id = packet[0]; 256 | int offset = 1; 257 | if(id == JRakLib.PACKET_ENCAPSULATED){ 258 | int len = packet[offset++]; 259 | String identifier = new String(Binary.subbytes(packet, offset, len)); 260 | offset += len; 261 | byte flags = packet[offset++]; 262 | byte[] buffer = Binary.subbytes(packet, offset); 263 | connection.addEncapsulatedToQueue(EncapsulatedPacket.fromBinary(buffer, true), flags); 264 | } else if(id == JRakLib.PACKET_RAW){ 265 | int len = packet[offset++]; 266 | String address = new String(Binary.subbytes(packet, offset, len)); 267 | offset += len; 268 | int port = Binary.readShort(Binary.subbytes(packet, offset, 2)); 269 | offset += 2; 270 | byte[] payload = Binary.subbytes(packet, offset); 271 | socket.writePacket(payload, new InetSocketAddress(address, port)); 272 | } else if(id == JRakLib.PACKET_CLOSE_SESSION){ 273 | /* 274 | int len = packet[offset++]; 275 | String identifier = new String(Binary.subbytes(packet, offset, len)); 276 | */ 277 | client.pushThreadToMainPacket(packet); 278 | } else if(id == JRakLib.PACKET_SET_OPTION){ 279 | int len = packet[offset++]; 280 | String name = new String(Binary.subbytes(packet, offset, len)); 281 | offset += len; 282 | String value = new String(Binary.subbytes(packet, offset)); 283 | switch(name){ 284 | case "portChecking": 285 | portChecking = Boolean.parseBoolean(value); 286 | break; 287 | } 288 | } else if(id == JRakLib.PACKET_SHUTDOWN){ 289 | connection.onShutdown(); 290 | 291 | socket.close(); 292 | shutdown = true; 293 | } else if(id == JRakLib.PACKET_EMERGENCY_SHUTDOWN){ 294 | shutdown = true; 295 | } else { 296 | return false; 297 | } 298 | return true; 299 | } 300 | return false; 301 | } 302 | 303 | private void registerPacket(byte id, Class clazz){ 304 | packetPool.put(id, clazz); 305 | } 306 | 307 | public Packet getPacketFromPool(byte id){ 308 | if(packetPool.containsKey(id)){ 309 | try { 310 | return packetPool.get(id).newInstance(); 311 | } catch (InstantiationException e) { 312 | e.printStackTrace(); 313 | } catch (IllegalAccessException e) { 314 | e.printStackTrace(); 315 | } 316 | } 317 | return null; 318 | } 319 | 320 | private void registerPackets() { 321 | registerPacket(UNCONNECTED_PING.ID, UNCONNECTED_PING.class); 322 | registerPacket(UNCONNECTED_PING_OPEN_CONNECTIONS.ID, UNCONNECTED_PING_OPEN_CONNECTIONS.class); 323 | registerPacket(OPEN_CONNECTION_REQUEST_1.ID, OPEN_CONNECTION_REQUEST_1.class); 324 | registerPacket(OPEN_CONNECTION_REPLY_1.ID, OPEN_CONNECTION_REPLY_1.class); 325 | registerPacket(OPEN_CONNECTION_REQUEST_2.ID, OPEN_CONNECTION_REQUEST_2.class); 326 | registerPacket(OPEN_CONNECTION_REPLY_2.ID, OPEN_CONNECTION_REPLY_2.class); 327 | registerPacket(UNCONNECTED_PONG.ID, UNCONNECTED_PONG.class); 328 | registerPacket(ADVERTISE_SYSTEM.ID, ADVERTISE_SYSTEM.class); 329 | registerPacket(DATA_PACKET_0.ID, DATA_PACKET_0.class); 330 | registerPacket(DATA_PACKET_1.ID, DATA_PACKET_1.class); 331 | registerPacket(DATA_PACKET_2.ID, DATA_PACKET_2.class); 332 | registerPacket(DATA_PACKET_3.ID, DATA_PACKET_3.class); 333 | registerPacket(DATA_PACKET_4.ID, DATA_PACKET_4.class); 334 | registerPacket(DATA_PACKET_5.ID, DATA_PACKET_5.class); 335 | registerPacket(DATA_PACKET_6.ID, DATA_PACKET_6.class); 336 | registerPacket(DATA_PACKET_7.ID, DATA_PACKET_7.class); 337 | registerPacket(DATA_PACKET_8.ID, DATA_PACKET_8.class); 338 | registerPacket(DATA_PACKET_9.ID, DATA_PACKET_9.class); 339 | registerPacket(DATA_PACKET_A.ID, DATA_PACKET_A.class); 340 | registerPacket(DATA_PACKET_B.ID, DATA_PACKET_B.class); 341 | registerPacket(DATA_PACKET_C.ID, DATA_PACKET_C.class); 342 | registerPacket(DATA_PACKET_D.ID, DATA_PACKET_D.class); 343 | registerPacket(DATA_PACKET_E.ID, DATA_PACKET_E.class); 344 | registerPacket(DATA_PACKET_F.ID, DATA_PACKET_F.class); 345 | registerPacket(NACK.ID, NACK.class); 346 | registerPacket(ACK.ID, ACK.class); 347 | } 348 | 349 | public JRakLibClient getClient() { 350 | return client; 351 | } 352 | 353 | public UDPClientSocket getSocket() { 354 | return socket; 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/JRakLibClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import net.beaconpe.jraklib.Logger; 23 | import net.beaconpe.jraklib.protocol.UNCONNECTED_PING; 24 | import net.beaconpe.jraklib.protocol.UNCONNECTED_PONG; 25 | 26 | import java.io.IOException; 27 | import java.net.DatagramPacket; 28 | import java.net.InetSocketAddress; 29 | import java.util.LinkedList; 30 | import java.util.List; 31 | import java.util.Random; 32 | 33 | /** 34 | * Represents a JRakLib Client. 35 | * @author jython234 36 | */ 37 | public class JRakLibClient extends Thread{ 38 | private static long clientID = new Random(System.currentTimeMillis()).nextLong(); 39 | private static long startTime = -1; 40 | protected InetSocketAddress serverEndpoint; 41 | 42 | protected Logger logger; 43 | protected boolean shutdown = false; 44 | 45 | protected List externalQueue; 46 | protected List internalQueue; 47 | 48 | /** 49 | * Creates a new JRakLibClient and connects right away. 50 | * @param logger Logger implementation for the client. 51 | * @param serverIP The server IP address. 52 | * @param serverPort The server's port. 53 | */ 54 | public JRakLibClient(Logger logger, String serverIP, int serverPort){ 55 | if(serverPort < 1 || serverPort > 65536){ 56 | throw new IllegalArgumentException("Invalid port range."); 57 | } 58 | this.logger = logger; 59 | this.serverEndpoint = new InetSocketAddress(serverIP, serverPort); 60 | 61 | externalQueue = new LinkedList<>(); 62 | internalQueue = new LinkedList<>(); 63 | 64 | start(); 65 | } 66 | 67 | /** 68 | * Pings a RakNet server at the specified serverIP and serverPort. 69 | * @param logger Logger implementation. 70 | * @param serverIP The server's IP Address. 71 | * @param serverPort The server's port. 72 | * @param tries Amount of packets sent with no response before giving up. (tries) 73 | * @param delay Delay between packets sent with no response. (In milliseconds) 74 | * @throws IOException If there is a problem while reading/sending. 75 | * @return The ping response 76 | */ 77 | public static PingResponse pingServer(Logger logger, String serverIP, int serverPort, int tries, int delay) throws IOException { 78 | if(startTime == -1){ 79 | startTime = System.currentTimeMillis(); 80 | } 81 | UDPClientSocket socket = new UDPClientSocket(logger); 82 | for(int i = 0; i < tries; i++){ 83 | UNCONNECTED_PING ping = new UNCONNECTED_PING(); 84 | ping.pingId = System.currentTimeMillis() - startTime; //Amount since start. 85 | ping.encode(); 86 | socket.writePacket(ping.buffer, new InetSocketAddress(serverIP, serverPort)); 87 | 88 | DatagramPacket pkt = socket.readPacketBlocking(delay); 89 | if(pkt != null && pkt.getData()[0] == UNCONNECTED_PONG.ID){ 90 | UNCONNECTED_PONG pong = new UNCONNECTED_PONG(); 91 | pong.buffer = pkt.getData(); 92 | pong.decode(); 93 | return new PingResponse(pong.serverID, pong.pingID, pong.serverName); 94 | } 95 | } 96 | return null; 97 | } 98 | 99 | public boolean isShutdown(){ 100 | return shutdown == true; 101 | } 102 | 103 | public void shutdown(){ 104 | shutdown = true; 105 | } 106 | 107 | public int getServerPort(){ 108 | return serverEndpoint.getPort(); 109 | } 110 | 111 | public String getServerIP(){ 112 | return serverEndpoint.getHostString(); 113 | } 114 | 115 | public InetSocketAddress getServerEndpoint() { 116 | return serverEndpoint; 117 | } 118 | 119 | public Logger getLogger(){ 120 | return logger; 121 | } 122 | 123 | public List getExternalQueue(){ 124 | return externalQueue; 125 | } 126 | 127 | public List getInternalQueue(){ 128 | return internalQueue; 129 | } 130 | 131 | public void pushMainToThreadPacket(byte[] bytes){ 132 | internalQueue.add(0, bytes); 133 | } 134 | 135 | public byte[] readMainToThreadPacket(){ 136 | if(!internalQueue.isEmpty()) { 137 | byte[] data = internalQueue.get(internalQueue.size() - 1); 138 | internalQueue.remove(data); 139 | return data; 140 | } 141 | return null; 142 | } 143 | 144 | public void pushThreadToMainPacket(byte[] bytes){ 145 | externalQueue.add(0, bytes); 146 | } 147 | 148 | public byte[] readThreadToMainPacket(){ 149 | if(!externalQueue.isEmpty()) { 150 | byte[] data = externalQueue.get(externalQueue.size() - 1); 151 | externalQueue.remove(data); 152 | return data; 153 | } 154 | return null; 155 | } 156 | 157 | /** 158 | * Regenerates the static clientID. 159 | */ 160 | public static void regenerateClientID(){ 161 | clientID = new Random().nextLong(); 162 | } 163 | 164 | /** 165 | * Regenerates the static clientID with the specified seed. 166 | * @param seed The seed to generate the clientID. 167 | */ 168 | public static void regenerateClientID(long seed){ 169 | clientID = new Random(seed).nextLong(); 170 | } 171 | 172 | public static long getClientID(){ 173 | return clientID; 174 | } 175 | 176 | public long getTimeSinceStart() { 177 | return startTime; 178 | } 179 | 180 | private class ShutdownHandler extends Thread { 181 | @Override 182 | public void run() { 183 | if (shutdown != true) { 184 | logger.emergency("[JRakLib Thread #" + getId() + "] JRakLib crashed!"); 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Represents a PingResponse from a server. 191 | */ 192 | public static class PingResponse { 193 | /** 194 | * The PingId from the packet. The original amount sent to the server was the time sent start. (in milliseconds) 195 | *
196 | * You can find more information here: http://wiki.vg/Pocket_Minecraft_Protocol#ID_CONNECTED_PING_OPEN_CONNECTIONS_.280x01.29 197 | */ 198 | public final long pingId; 199 | /** 200 | * The serverId from the packet. This is the server's unique identifier generated at startup. (The value changes each runtime, and could be modified in transport) 201 | */ 202 | public final long serverId; 203 | /** 204 | * The server's name or MOTD. 205 | */ 206 | public final String name; 207 | 208 | public PingResponse(long serverId, long pingId, String name){ 209 | this.pingId = pingId; 210 | this.serverId = serverId; 211 | this.name = name; 212 | } 213 | } 214 | 215 | @Override 216 | public void run(){ 217 | setName("JRakLib Client Thread #"+getId()); 218 | Runtime.getRuntime().addShutdownHook(new ShutdownHandler()); 219 | UDPClientSocket socket = new UDPClientSocket(logger); 220 | new ConnectionManager(this, socket); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/client/UDPClientSocket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.client; 21 | 22 | import net.beaconpe.jraklib.Logger; 23 | 24 | import javax.xml.crypto.Data; 25 | import java.io.Closeable; 26 | import java.io.IOException; 27 | import java.net.*; 28 | import java.util.Arrays; 29 | import java.util.ConcurrentModificationException; 30 | 31 | /** 32 | * A UDP Client socket. 33 | * 34 | * @author jython234 35 | */ 36 | public class UDPClientSocket implements Closeable{ 37 | private Logger logger; 38 | private DatagramSocket socket; 39 | 40 | /** 41 | * Create a new UDP Client socket with the specified logger. 42 | * @param logger The logger used in case the socket was unable to be created. 43 | */ 44 | public UDPClientSocket(Logger logger){ 45 | this.logger = logger; 46 | try { 47 | socket = new DatagramSocket(); 48 | socket.setBroadcast(true); 49 | socket.setSendBufferSize(1024 * 1024 * 8); 50 | socket.setReceiveBufferSize(1024 * 1024 * 8); 51 | socket.setSoTimeout(1); 52 | } catch (SocketException e) { 53 | logger.critical("**** FAILED TO CREATE SOCKET!"); 54 | logger.critical("java.net.SocketException: "+e.getMessage()); 55 | System.exit(1); 56 | } 57 | } 58 | 59 | /** 60 | * Returns the socket used by this client socket. 61 | * @return DatagramSocket 62 | */ 63 | public DatagramSocket getSocket(){ 64 | return socket; 65 | } 66 | 67 | /** 68 | * Closes this socket, and the underlying DatagramSocket. 69 | */ 70 | @Override 71 | public void close() { 72 | socket.close(); 73 | } 74 | 75 | /** 76 | * Reads a packet from the socket (non-blocking). If no packet is received, the method will return null. 77 | * @return The DatagramPacket, if received. 78 | * @throws IOException If there is a problem while reading. 79 | */ 80 | public DatagramPacket readPacket() throws IOException { 81 | DatagramPacket dp = new DatagramPacket(new byte[65535], 65535); 82 | try { 83 | socket.receive(dp); 84 | dp.setData(Arrays.copyOf(dp.getData(), dp.getLength())); 85 | return dp; 86 | } catch (SocketTimeoutException e) { 87 | return null; 88 | } 89 | } 90 | 91 | /** 92 | * Reads a packet from the socket (blocking). If no packet is received, the method will return null. 93 | * @param blockFor Block for this amount (milliseconds). If amount is negative, the method will block forever (until a packet is received) 94 | * @return The DatagramPacket, if received. 95 | * @throws IOException If there is a problem while reading. 96 | */ 97 | public DatagramPacket readPacketBlocking(int blockFor) throws IOException{ 98 | DatagramPacket dp = new DatagramPacket(new byte[65535], 65535); 99 | try { 100 | socket.setSoTimeout(blockFor); 101 | socket.receive(dp); 102 | socket.setSoTimeout(1); 103 | dp.setData(Arrays.copyOf(dp.getData(), dp.getLength())); 104 | return dp; 105 | } catch (SocketTimeoutException e) { 106 | return null; 107 | } 108 | } 109 | 110 | /** 111 | * Sends a packet to the specified dest. 112 | * @param buffer Raw payload of the packet. 113 | * @param dest The endpoint where the data will go. 114 | * @throws IOException If there is a problem while sending. 115 | */ 116 | public void writePacket(byte[] buffer, SocketAddress dest) throws IOException { 117 | DatagramPacket dp = new DatagramPacket(buffer, buffer.length, dest); 118 | socket.send(dp); 119 | } 120 | 121 | /** 122 | * Sets this socket's send buffer size. Defaults to 1024 * 1024 * 8. 123 | * @param size Size in bytes. 124 | * @throws SocketException If there is a failure. 125 | */ 126 | public void setSendBuffer(int size) throws SocketException { 127 | socket.setSendBufferSize(size); 128 | } 129 | 130 | /** 131 | * Sets this socket's receive buffer size. Defaults to 1024 * 1024 * 8. 132 | * @param size Size in bytes. 133 | * @throws SocketException If there is a failure. 134 | */ 135 | public void setRecvBuffer(int size) throws SocketException { 136 | socket.setReceiveBufferSize(size); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/ACK.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * Acknowledged. 24 | */ 25 | public class ACK extends AcknowledgePacket{ 26 | public static byte ID = (byte) 0xc0; 27 | 28 | public byte getID(){ 29 | return (byte) 0xc0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/ADVERTISE_SYSTEM.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * Variation of UNCONNECTED_PONG. 24 | */ 25 | public class ADVERTISE_SYSTEM extends UNCONNECTED_PONG{ 26 | public static byte ID = 0x1d; 27 | 28 | public byte getID() { 29 | return 0x1d; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/AcknowledgePacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | 29 | /** 30 | * Base class for ACK/NACK. 31 | */ 32 | public abstract class AcknowledgePacket extends Packet{ 33 | 34 | public Integer[] packets; 35 | 36 | @Override 37 | protected void _encode() { 38 | ByteBuffer payload = ByteBuffer.allocate(1024); 39 | int count = packets.length; 40 | int records = 0; 41 | 42 | if(count > 0){ 43 | int pointer = 0; 44 | int start = packets[0]; 45 | int last = packets[0]; 46 | 47 | while(pointer + 1 < count){ 48 | int current = packets[pointer++]; 49 | int diff = current - last; 50 | if(diff == 1){ 51 | last = current; 52 | } else if(diff > 1){ //Forget about duplicated packets (bad queues?) 53 | if(start == last){ 54 | payload.put((byte) 0x01); 55 | payload.put(Binary.writeLTriad(start)); 56 | start = last = current; 57 | } else { 58 | payload.put((byte) 0x00); 59 | payload.put(Binary.writeLTriad(start)); 60 | payload.put(Binary.writeLTriad(last)); 61 | start = last = current; 62 | } 63 | records = records + 1; 64 | } 65 | } 66 | 67 | if(start == last){ 68 | payload.put((byte) 0x01); 69 | payload.put(Binary.writeLTriad(start)); 70 | } else { 71 | payload.put((byte) 0x00); 72 | payload.put(Binary.writeLTriad(start)); 73 | payload.put(Binary.writeLTriad(last)); 74 | } 75 | records = records + 1; 76 | } 77 | putShort((short) records); 78 | put(Arrays.copyOf(payload.array(), payload.position())); 79 | } 80 | 81 | @Override 82 | protected void _decode() { 83 | int count = getShort(); 84 | List packets = new ArrayList<>(); 85 | int cnt = 0; 86 | for(int i = 0; i < count && !feof() && cnt < 4096; i++){ 87 | if(getByte() == 0){ 88 | int start = getLTriad(); 89 | int end = getLTriad(); 90 | if((end - start) > 512){ 91 | end = start + 512; 92 | } 93 | for(int c = start; c <= end; c++){ 94 | cnt = cnt + 1; 95 | packets.add(c); 96 | } 97 | } else { 98 | packets.add(getLTriad()); 99 | } 100 | } 101 | this.packets = packets.stream().toArray(Integer[]::new); 102 | } 103 | 104 | public void clean(){ 105 | packets = new Integer[] {}; 106 | super.clean(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/CLIENT_CONNECT_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * CLIENT_CONNECT (Encapsulated, 0x09) 24 | */ 25 | public class CLIENT_CONNECT_DataPacket extends Packet{ 26 | public static byte ID = 0x09; 27 | 28 | public long clientID; 29 | public long sendPing; 30 | public boolean useSecurity = false; 31 | 32 | 33 | public byte getID() { 34 | return 0x09; 35 | } 36 | 37 | @Override 38 | protected void _encode() { 39 | putLong(clientID); 40 | putLong(sendPing); 41 | putByte((byte) (useSecurity ? 1 : 0)); 42 | } 43 | 44 | @Override 45 | protected void _decode() { 46 | clientID = getLong(); 47 | sendPing = getLong(); 48 | useSecurity = getByte() > 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/CLIENT_DISCONNECT_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * CLIENT_DISCONNECT (Encapsulated, 0x15) 24 | */ 25 | public class CLIENT_DISCONNECT_DataPacket extends Packet{ 26 | public static byte ID = 0x15; 27 | 28 | public byte getID() { 29 | return 0x15; 30 | } 31 | 32 | @Override 33 | protected void _encode() { 34 | 35 | } 36 | 37 | @Override 38 | protected void _decode() { 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/CLIENT_HANDSHAKE_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import java.net.InetSocketAddress; 23 | 24 | /** 25 | * CLIENT_HANDSHAKE (Encapsulated, 0x13) 26 | */ 27 | public class CLIENT_HANDSHAKE_DataPacket extends Packet{ 28 | public static byte ID = 0x13; 29 | public InetSocketAddress address; 30 | 31 | public InetSocketAddress[] systemAddresses; 32 | 33 | public long sendPing; 34 | public long sendPong; 35 | 36 | 37 | public byte getID() { 38 | return 0x13; 39 | } 40 | 41 | @Override 42 | protected void _encode() { 43 | putAddress(address.getHostString(), address.getPort(), (byte) 4); 44 | for(InetSocketAddress a : systemAddresses){ 45 | putAddress(a.getHostString(), a.getPort(), (byte) 4); 46 | } 47 | putLong(sendPing); 48 | putLong(sendPong); 49 | } 50 | 51 | @Override 52 | protected void _decode() { 53 | address = getAddress(); 54 | systemAddresses = new InetSocketAddress[10]; 55 | for(int i = 0; i < 10; i++){ 56 | systemAddresses[i] = getAddress(); 57 | } 58 | sendPing = getLong(); 59 | sendPong = getLong(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * Represents a RakNet Custom Packet. 29 | */ 30 | public abstract class DataPacket extends Packet{ 31 | public List packets = new ArrayList<>(); 32 | public int seqNumber = -1; 33 | 34 | @Override 35 | protected void _encode() { 36 | putLTriad(seqNumber); 37 | for(Object o : packets){ 38 | if(o instanceof EncapsulatedPacket){ 39 | put(((EncapsulatedPacket) o).toBinary()); 40 | } else { 41 | put((byte[]) o); 42 | } 43 | } 44 | } 45 | 46 | public int length(){ 47 | int len = 4; 48 | for(Object o : packets){ 49 | if(o instanceof EncapsulatedPacket){ 50 | len = len + ((EncapsulatedPacket) o).getTotalLength(); 51 | } else { 52 | len = len + ((byte[]) o).length; 53 | } 54 | } 55 | return len; 56 | } 57 | 58 | @Override 59 | protected void _decode() { 60 | seqNumber = getLTriad(); 61 | 62 | while(!feof()){ 63 | int offset = 0; 64 | byte[] data = Binary.subbytes(buffer, this.offset); 65 | if(data.length < 1){ 66 | break; 67 | } 68 | EncapsulatedPacket packet = EncapsulatedPacket.fromBinary(data, false, offset); 69 | offset = packet.offset; 70 | this.offset = this.offset + packet.offset; 71 | if(packet.buffer.length == 0){ 72 | break; 73 | } 74 | packets.add(packet); 75 | } 76 | } 77 | 78 | public void clean(){ 79 | packets.clear(); 80 | seqNumber = -1; 81 | super.clean(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/DataPackets.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * Big class with lots of DataPackets 24 | */ 25 | public abstract class DataPackets { 26 | public static class DATA_PACKET_0 extends DataPacket{ 27 | public static byte ID = (byte) 0x80; 28 | 29 | public byte getID() { 30 | return (byte) 0x80; 31 | } 32 | } 33 | public static class DATA_PACKET_1 extends DataPacket{ 34 | public static byte ID = (byte) 0x81; 35 | 36 | public byte getID() { 37 | return (byte) 0x81; 38 | } 39 | } 40 | public static class DATA_PACKET_2 extends DataPacket{ 41 | public static byte ID = (byte) 0x82; 42 | 43 | public byte getID() { 44 | return (byte) 0x82; 45 | } 46 | } 47 | public static class DATA_PACKET_3 extends DataPacket{ 48 | public static byte ID = (byte) 0x83; 49 | 50 | public byte getID() { 51 | return (byte) 0x83; 52 | } 53 | } 54 | public static class DATA_PACKET_4 extends DataPacket{ 55 | public static byte ID = (byte) 0x84; 56 | 57 | public byte getID() { 58 | return (byte) 0x84; 59 | } 60 | } 61 | public static class DATA_PACKET_5 extends DataPacket{ 62 | public static byte ID = (byte) 0x85; 63 | 64 | public byte getID() { 65 | return (byte) 0x85; 66 | } 67 | } 68 | public static class DATA_PACKET_6 extends DataPacket{ 69 | public static byte ID = (byte) 0x86; 70 | 71 | public byte getID() { 72 | return (byte) 0x86; 73 | } 74 | } 75 | public static class DATA_PACKET_7 extends DataPacket{ 76 | public static byte ID = (byte) 0x87; 77 | 78 | public byte getID() { 79 | return (byte) 0x87; 80 | } 81 | } 82 | public static class DATA_PACKET_8 extends DataPacket{ 83 | public static byte ID = (byte) 0x88; 84 | 85 | public byte getID() { 86 | return (byte) 0x88; 87 | } 88 | } 89 | public static class DATA_PACKET_9 extends DataPacket{ 90 | public static byte ID = (byte) 0x89; 91 | 92 | public byte getID() { 93 | return (byte) 0x89; 94 | } 95 | } 96 | public static class DATA_PACKET_A extends DataPacket{ 97 | public static byte ID = (byte) 0x8a; 98 | 99 | public byte getID() { 100 | return (byte) 0x8a; 101 | } 102 | } 103 | public static class DATA_PACKET_B extends DataPacket{ 104 | public static byte ID = (byte) 0x8b; 105 | 106 | public byte getID() { 107 | return (byte) 0x8b; 108 | } 109 | } 110 | public static class DATA_PACKET_C extends DataPacket{ 111 | public static byte ID = (byte) 0x8c; 112 | 113 | public byte getID() { 114 | return (byte) 0x8c; 115 | } 116 | } 117 | public static class DATA_PACKET_D extends DataPacket{ 118 | public static byte ID = (byte) 0x8d; 119 | 120 | public byte getID() { 121 | return (byte) 0x8d; 122 | } 123 | } 124 | public static class DATA_PACKET_E extends DataPacket{ 125 | public static byte ID = (byte) 0x8e; 126 | 127 | public byte getID() { 128 | return (byte) 0x8e; 129 | } 130 | } 131 | public static class DATA_PACKET_F extends DataPacket{ 132 | public static byte ID = (byte) 0x8f; 133 | 134 | public byte getID() { 135 | return (byte) 0x8f; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/EncapsulatedPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.util.Arrays; 26 | 27 | /** 28 | * Represents an encapsulated packet inside a CustomPacket 29 | */ 30 | public class EncapsulatedPacket { 31 | public byte reliability; 32 | public boolean hasSplit = false; 33 | public int length = 0; 34 | public int messageIndex = -1; 35 | public int orderIndex = -1; 36 | public byte orderChannel = -1; 37 | public int splitCount = -1; 38 | public short splitID = -1; 39 | public int splitIndex = -1; 40 | public byte[] buffer; 41 | public boolean needACK = false; 42 | public int identifierACK = -1; 43 | 44 | public int bufferLength; 45 | 46 | protected int offset; 47 | 48 | public static EncapsulatedPacket fromBinary(byte[] binary){ 49 | return fromBinary(binary, false); 50 | } 51 | 52 | public static EncapsulatedPacket fromBinary(byte[] binary, boolean internal){ 53 | return fromBinary(binary, internal, -1); 54 | } 55 | 56 | public static EncapsulatedPacket fromBinary(byte[] binary, boolean internal, int offset){ 57 | EncapsulatedPacket packet = new EncapsulatedPacket(); 58 | packet.bufferLength = binary.length; 59 | byte flags = binary[0]; 60 | packet.reliability = (byte) ((flags & 0b11100000) >> 5); 61 | packet.hasSplit = (flags & 0b00010000) > 0; 62 | int length; 63 | if(internal){ 64 | length = Binary.readInt(Binary.subbytes(binary, 1, 4)); 65 | packet.identifierACK = Binary.readInt(Binary.subbytes(binary, 5, 4)); 66 | offset = 9; 67 | } else { 68 | length = (int) Math.ceil(Binary.readShort(Binary.subbytes(binary, 1, 2)) / 8.0); 69 | offset = 3; 70 | packet.identifierACK = -1; 71 | } 72 | 73 | if(packet.reliability > 0){ 74 | if(packet.reliability >= 2 && packet.reliability != 5){ 75 | packet.messageIndex = Binary.readLTriad(Binary.subbytes(binary, offset, 3)); 76 | offset = offset + 3; 77 | } 78 | 79 | if(packet.reliability <= 4 && packet.reliability != 2){ 80 | packet.orderIndex = Binary.readLTriad(Binary.subbytes(binary, offset, 3)); 81 | offset = offset + 3; 82 | packet.orderChannel = binary[offset++]; 83 | } 84 | } 85 | 86 | if(packet.hasSplit){ 87 | packet.splitCount = Binary.readInt(Binary.subbytes(binary, offset, 4)); 88 | offset = offset + 4; 89 | packet.splitID = (short) Binary.readShort(Binary.subbytes(binary, offset, 2)); 90 | offset = offset + 2; 91 | packet.splitIndex = Binary.readInt(Binary.subbytes(binary, offset, 4)); 92 | offset = offset + 4; 93 | } 94 | 95 | packet.buffer = Binary.subbytes(binary, offset, length); 96 | offset = offset + length; 97 | packet.offset = offset; 98 | return packet; 99 | } 100 | 101 | public int getTotalLength(){ 102 | return getTotalLength(false); 103 | } 104 | 105 | public int getTotalLength(boolean internal){ 106 | if(internal) { 107 | return 9 + buffer.length + (messageIndex != -1 ? 3 : 0) + (orderIndex != -1 ? 4 : 0) + (hasSplit ? 10 : 0); 108 | } else { 109 | return 3 + buffer.length + (messageIndex != -1 ? 3 : 0) + (orderIndex != -1 ? 4 : 0) + (hasSplit ? 10 : 0); 110 | } 111 | } 112 | 113 | public byte[] toBinary(boolean internal){ 114 | int offset = 0; 115 | ByteBuffer bb = ByteBuffer.allocate(64 * 64 * 64); 116 | bb.put((byte) ((byte) (reliability << 5) | (hasSplit ? 0b00010000 : 0))); 117 | if(internal){ 118 | bb.put(Binary.writeInt(buffer.length)); 119 | bb.put(Binary.writeInt(identifierACK)); 120 | } else { 121 | bb.put(Binary.writeShort((short) (buffer.length << 3))); 122 | } 123 | 124 | if(reliability > 0){ 125 | if(reliability >= 2 && reliability != 5){ 126 | bb.put(Binary.writeLTriad(messageIndex)); 127 | } 128 | if(reliability <= 4 && reliability != 2){ 129 | bb.put(Binary.writeLTriad(orderIndex)); 130 | bb.put(Binary.writeByte(orderChannel)); 131 | } 132 | } 133 | 134 | if(hasSplit){ 135 | bb.put(Binary.writeInt(splitCount)); 136 | bb.put(Binary.writeShort(splitID)); 137 | bb.put(Binary.writeInt(splitIndex)); 138 | } 139 | 140 | bb.put(buffer); 141 | byte[] data = Arrays.copyOf(bb.array(), bb.position()); 142 | bufferLength = data.length; 143 | bb = null; 144 | return data; 145 | } 146 | 147 | public byte[] toBinary(){ 148 | return toBinary(false); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/NACK.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * Not Acknowledged. 24 | */ 25 | public class NACK extends AcknowledgePacket{ 26 | public static byte ID = (byte) 0xa0; 27 | 28 | public byte getID() { 29 | return (byte) 0xa0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/OPEN_CONNECTION_REPLY_1.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | /** 25 | * OPEN_CONNECTION_REPLY_1 (Not encapsulated, 0x06) 26 | */ 27 | public class OPEN_CONNECTION_REPLY_1 extends Packet{ 28 | public static byte ID = 0x06; 29 | public long serverID; 30 | public short mtuSize; 31 | 32 | 33 | public byte getID() { 34 | return 0x06; 35 | } 36 | 37 | @Override 38 | protected void _encode() { 39 | put(JRakLib.MAGIC); 40 | putLong(serverID); 41 | putByte((byte) 0); //Server security 42 | putShort(mtuSize); 43 | } 44 | 45 | @Override 46 | protected void _decode() { 47 | offset = offset + 16; //Magic 48 | serverID = getLong(); 49 | getByte(); //security 50 | mtuSize = getShort(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/OPEN_CONNECTION_REPLY_2.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | import java.net.InetSocketAddress; 25 | 26 | /** 27 | * OPEN_CONNECTION_REPLY_2 (Not encapsulated, 0x08) 28 | */ 29 | public class OPEN_CONNECTION_REPLY_2 extends Packet{ 30 | public static byte ID = 0x08; 31 | public long serverID; 32 | public InetSocketAddress clientAddress; 33 | public short mtuSize; 34 | 35 | 36 | public byte getID() { 37 | return 0x08; 38 | } 39 | 40 | @Override 41 | protected void _encode() { 42 | put(JRakLib.MAGIC); 43 | putLong(serverID); 44 | putAddress(clientAddress.getHostString(), clientAddress.getPort(), (byte) 4); 45 | putShort(mtuSize); 46 | putByte((byte) 0x00); //server security 47 | } 48 | 49 | @Override 50 | protected void _decode() { 51 | offset = offset + 15; 52 | serverID = getLong(); 53 | clientAddress = getAddress(); 54 | mtuSize = getShort(); 55 | //server security 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/OPEN_CONNECTION_REQUEST_1.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | /** 25 | * OPEN_CONNECTION_REQUEST_1 (Not encapsulated, 0x05) 26 | */ 27 | public class OPEN_CONNECTION_REQUEST_1 extends Packet{ 28 | public static byte ID = 0x05; 29 | public byte protocol = JRakLib.PROTOCOL; 30 | public short mtuSize; 31 | 32 | 33 | public byte getID() { 34 | return 0x05; 35 | } 36 | 37 | @Override 38 | protected void _encode() { 39 | put(JRakLib.MAGIC); 40 | putByte(protocol); 41 | put(new byte[mtuSize - 18]); 42 | } 43 | 44 | @Override 45 | protected void _decode() { 46 | offset = offset + 15; //Magic 47 | protocol = getByte(); 48 | mtuSize = (short) (get().length - 18); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/OPEN_CONNECTION_REQUEST_2.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | import java.net.InetSocketAddress; 25 | 26 | /** 27 | * OPEN_CONNECTION_REQUEST_2 (Not encapsulated, 0x07) 28 | */ 29 | public class OPEN_CONNECTION_REQUEST_2 extends Packet{ 30 | public static byte ID = 0x07; 31 | public long clientID; 32 | public InetSocketAddress serverAddress; 33 | public short mtuSize; 34 | 35 | public byte getID() { 36 | return 0x07; 37 | } 38 | 39 | @Override 40 | protected void _encode() { 41 | put(JRakLib.MAGIC); 42 | putAddress(serverAddress.getHostString(), serverAddress.getPort(), (byte) 4); 43 | putShort(mtuSize); 44 | putLong(clientID); 45 | } 46 | 47 | @Override 48 | protected void _decode() { 49 | offset = offset + 15; //Magic 50 | serverAddress = getAddress(); 51 | mtuSize = getShort(); 52 | clientID = getLong(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/PING_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * PING (Encapsulated, 0x00) 24 | */ 25 | public class PING_DataPacket extends Packet{ 26 | public static byte ID = 0x00; 27 | public long pingID; 28 | 29 | 30 | public byte getID() { 31 | return 0x00; 32 | } 33 | 34 | @Override 35 | protected void _encode() { 36 | putLong(pingID); 37 | } 38 | 39 | @Override 40 | protected void _decode() { 41 | pingID = getLong(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/PONG_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * PONG (Encapsulated, 0x03) 24 | */ 25 | public class PONG_DataPacket extends Packet{ 26 | public static byte ID = 0x03; 27 | public long pingID; 28 | 29 | 30 | public byte getID() { 31 | return 0x03; 32 | } 33 | 34 | @Override 35 | protected void _encode() { 36 | putLong(pingID); 37 | } 38 | 39 | @Override 40 | protected void _decode() { 41 | pingID = getLong(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | 24 | import java.net.InetAddress; 25 | import java.net.InetSocketAddress; 26 | import java.net.UnknownHostException; 27 | import java.nio.ByteBuffer; 28 | import java.util.Arrays; 29 | import java.util.regex.Pattern; 30 | 31 | /** 32 | * Base class for all Packets. 33 | */ 34 | public abstract class Packet { 35 | 36 | protected int offset = 0; 37 | protected int length; 38 | public byte[] buffer; 39 | protected ByteBuffer sendBuffer; 40 | public long sendTime; 41 | 42 | public abstract byte getID(); 43 | 44 | protected byte[] get(int len){ 45 | if(len < 0){ 46 | offset = buffer.length - 1; 47 | return new byte[] {}; 48 | } else { 49 | /* 50 | ByteBuffer bb = ByteBuffer.allocate(len); 51 | while(len > 0 && !feof()){ 52 | offset = offset + 1; 53 | if(buffer.length == offset){ 54 | offset = offset - 1; 55 | } 56 | bb.put(buffer[offset]); 57 | len = len - 1; 58 | } 59 | return bb.array(); 60 | */ 61 | byte[] bytes = Binary.subbytes(buffer, offset, len); 62 | offset = offset + bytes.length; 63 | return bytes; 64 | } 65 | } 66 | 67 | public void setBuffer(byte[] buffer, int offset){ 68 | this.buffer = buffer; 69 | this.offset = offset; 70 | } 71 | 72 | public int getOffset(){ 73 | return offset; 74 | } 75 | 76 | protected byte[] get(){ 77 | return Binary.subbytes(buffer, offset); 78 | } 79 | 80 | protected long getLong(boolean signed){ 81 | if(signed){ 82 | return Binary.readLong(get(8)); 83 | } else { 84 | return Binary.readLong(get(8)) & 0xFF; 85 | } 86 | } 87 | 88 | protected long getLong(){ 89 | return getLong(true); 90 | } 91 | 92 | protected int getInt(){ 93 | return Binary.readInt(get(4)); 94 | } 95 | 96 | protected int getShort(boolean signed){ 97 | if(signed){ 98 | return Binary.readSignedShort(get(2)); 99 | } else { 100 | return Binary.readShort(get(2)); 101 | } 102 | } 103 | 104 | protected short getShort(){ 105 | return (short) getShort(true); 106 | } 107 | 108 | protected int getLTriad(){ 109 | return Binary.readLTriad(get(3)); 110 | } 111 | 112 | protected int getByte(boolean signed){ 113 | int b = Binary.readByte(buffer[offset], signed); 114 | offset = offset + 1; 115 | return b; 116 | } 117 | 118 | protected byte getByte(){ 119 | return (byte) getByte(true); 120 | } 121 | 122 | protected String getString(){ 123 | byte[] d = get(getShort()); 124 | return new String(d); 125 | } 126 | 127 | protected InetSocketAddress getAddress(){ 128 | int version = getByte(); 129 | if(version == 4){ 130 | String address = ((~getByte()) & 0xff) +"."+ ((~getByte()) & 0xff) +"."+ ((~getByte()) & 0xff) +"."+ ((~getByte()) & 0xff); 131 | int port = getShort(false); 132 | return new InetSocketAddress(address, port); 133 | } else { 134 | //TODO: IPv6 135 | return new InetSocketAddress("0.0.0.0", 0); 136 | } 137 | } 138 | 139 | protected boolean feof(){ 140 | try{ 141 | byte d = buffer[offset]; 142 | return false; 143 | } catch (ArrayIndexOutOfBoundsException e){ 144 | return true; 145 | } 146 | } 147 | 148 | protected void put(byte[] bytes){ 149 | sendBuffer.put(bytes); 150 | } 151 | 152 | protected void putLong(long l){ 153 | sendBuffer.put(Binary.writeLong(l)); 154 | } 155 | 156 | protected void putInt(int i){ 157 | sendBuffer.put(Binary.writeInt(i)); 158 | } 159 | 160 | protected void putShort(short s){ 161 | sendBuffer.put(Binary.writeShort(s)); 162 | } 163 | 164 | protected void putLTriad(int t){ 165 | sendBuffer.put(Binary.writeLTriad(t)); 166 | } 167 | 168 | protected void putByte(byte b){ 169 | sendBuffer.put(b); 170 | } 171 | 172 | protected void putString(String s){ 173 | putShort((short) s.getBytes().length); 174 | put(s.getBytes()); 175 | } 176 | 177 | protected void putAddress(String addr, int port, byte version){ 178 | if(!addr.contains(Pattern.quote("."))){ 179 | try { 180 | addr = InetAddress.getByName(addr).getHostAddress(); 181 | } catch (UnknownHostException e) { 182 | e.printStackTrace(); 183 | } 184 | } 185 | putByte(version); 186 | if(version == 4){ 187 | for (String section : addr.split(Pattern.quote("."))){ 188 | putByte((byte) ((byte) ~(Integer.parseInt(section)) & 0xFF)); 189 | } 190 | putShort((short) port); 191 | } 192 | } 193 | 194 | protected abstract void _encode(); 195 | protected abstract void _decode(); 196 | 197 | public void encode(){ 198 | sendBuffer = ByteBuffer.allocate(64 * 64 * 64); 199 | putByte(getID()); 200 | _encode(); 201 | buffer = Arrays.copyOf(sendBuffer.array(), sendBuffer.position()); 202 | } 203 | 204 | public void decode(){ 205 | getByte(); 206 | _decode(); 207 | } 208 | 209 | public void clean(){ 210 | buffer = null; 211 | sendBuffer = null; 212 | offset = 0; 213 | sendTime = -1; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/SERVER_HANDSHAKE_DataPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import java.net.InetSocketAddress; 23 | 24 | /** 25 | * SERVER_HANDSHAKE (Encapsulated, 0x10) 26 | */ 27 | public class SERVER_HANDSHAKE_DataPacket extends Packet{ 28 | public static byte ID = 0x10; 29 | public InetSocketAddress address; 30 | public InetSocketAddress[] systemAddresses = new InetSocketAddress[] { 31 | new InetSocketAddress("127.0.0.1", 0), 32 | new InetSocketAddress("0.0.0.0", 0), 33 | new InetSocketAddress("0.0.0.0", 0), 34 | new InetSocketAddress("0.0.0.0", 0), 35 | new InetSocketAddress("0.0.0.0", 0), 36 | new InetSocketAddress("0.0.0.0", 0), 37 | new InetSocketAddress("0.0.0.0", 0), 38 | new InetSocketAddress("0.0.0.0", 0), 39 | new InetSocketAddress("0.0.0.0", 0), 40 | new InetSocketAddress("0.0.0.0", 0), 41 | }; 42 | 43 | public long sendPing; 44 | public long sendPong; 45 | 46 | 47 | public byte getID() { 48 | return 0x10; 49 | } 50 | 51 | @Override 52 | protected void _encode() { 53 | putAddress(address.getHostString(), address.getPort(), (byte) 4); 54 | putShort((short) 0); 55 | for(InetSocketAddress a : systemAddresses){ 56 | putAddress(a.getHostString(), a.getPort(), (byte) 4); 57 | } 58 | putLong(sendPing); 59 | putLong(sendPong); 60 | } 61 | 62 | @Override 63 | protected void _decode() { 64 | address = getAddress(); 65 | getShort(); 66 | systemAddresses = new InetSocketAddress[10]; 67 | for(int i = 0; i < 10; i++){ 68 | systemAddresses[i] = getAddress(); 69 | } 70 | sendPing = getLong(); 71 | sendPong = getLong(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/UNCONNECTED_PING.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | /** 25 | * UNCONNECTED_PING (Not encapsulated, 0x01) 26 | */ 27 | public class UNCONNECTED_PING extends Packet{ 28 | public static byte ID = 0x01; 29 | public long pingId; 30 | 31 | 32 | public byte getID() { 33 | return 0x01; 34 | } 35 | 36 | @Override 37 | protected void _encode() { 38 | putLong(pingId); 39 | put(JRakLib.MAGIC); 40 | } 41 | 42 | @Override 43 | protected void _decode() { 44 | pingId = getLong(); 45 | //magic 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/UNCONNECTED_PING_OPEN_CONNECTIONS.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | /** 23 | * UNCONNECTED_PING_OPEN_CONNECTIONS (Not encapsulated, 0x02) 24 | */ 25 | public class UNCONNECTED_PING_OPEN_CONNECTIONS extends UNCONNECTED_PING{ 26 | public static byte ID = 0x02; 27 | 28 | public byte getID() { 29 | return 0x02; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/protocol/UNCONNECTED_PONG.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.protocol; 21 | 22 | import net.beaconpe.jraklib.JRakLib; 23 | 24 | /** 25 | * Packet that advertises the server to the client. 26 | */ 27 | public class UNCONNECTED_PONG extends Packet{ 28 | public static byte ID = 0x1c; 29 | 30 | public long pingID; 31 | public long serverID; 32 | public String serverName; 33 | 34 | public byte getID() { 35 | return 0x1c; 36 | } 37 | 38 | @Override 39 | protected void _encode() { 40 | putLong(pingID); 41 | putLong(serverID); 42 | put(JRakLib.MAGIC); 43 | putString(serverName); 44 | } 45 | 46 | @Override 47 | protected void _decode() { 48 | pingID = getLong(); 49 | serverID = getLong(); 50 | offset = offset + 16; //magic 51 | serverName = getString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/JRakLibServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | import net.beaconpe.jraklib.Logger; 23 | 24 | import java.net.InetSocketAddress; 25 | import java.util.*; 26 | import java.util.concurrent.*; 27 | 28 | /** 29 | * JRakLib server. 30 | */ 31 | public class JRakLibServer extends Thread{ 32 | protected InetSocketAddress _interface; 33 | 34 | protected Logger logger; 35 | protected boolean shutdown = false; 36 | 37 | protected Queue externalQueue; 38 | protected Queue internalQueue; 39 | 40 | public JRakLibServer(Logger logger, int port, String _interface){ 41 | if(port < 1 || port > 65536){ 42 | throw new IllegalArgumentException("Invalid port range."); 43 | } 44 | this._interface = new InetSocketAddress(_interface, port); 45 | this.logger = logger; 46 | this.shutdown = false; 47 | 48 | externalQueue = new ConcurrentLinkedQueue<>(); 49 | internalQueue = new ConcurrentLinkedQueue<>(); 50 | 51 | start(); 52 | } 53 | 54 | public boolean isShutdown(){ 55 | return shutdown == true; 56 | } 57 | 58 | public void shutdown(){ 59 | shutdown = true; 60 | } 61 | 62 | public int getPort(){ 63 | return _interface.getPort(); 64 | } 65 | 66 | public String getInterface(){ 67 | return _interface.getHostString(); 68 | } 69 | 70 | public Logger getLogger(){ 71 | return logger; 72 | } 73 | 74 | public Queue getExternalQueue(){ 75 | return externalQueue; 76 | } 77 | 78 | public Queue getInternalQueue(){ 79 | return internalQueue; 80 | } 81 | 82 | public void pushMainToThreadPacket(byte[] bytes){ 83 | internalQueue.add(bytes); 84 | } 85 | 86 | public byte[] readMainToThreadPacket(){ 87 | if(!internalQueue.isEmpty()) { 88 | return internalQueue.remove(); 89 | } 90 | return null; 91 | } 92 | 93 | public void pushThreadToMainPacket(byte[] bytes){ 94 | externalQueue.add(bytes); 95 | } 96 | 97 | public byte[] readThreadToMainPacket(){ 98 | if(!externalQueue.isEmpty()) { 99 | return externalQueue.remove(); 100 | } 101 | return null; 102 | } 103 | 104 | private class ShutdownHandler extends Thread{ 105 | public void run(){ 106 | if(shutdown != true){ 107 | logger.emergency("[RakLib Thread #"+getId()+"] RakLib crashed!"); 108 | } 109 | } 110 | } 111 | 112 | public void run(){ 113 | setName("JRakLib Thread #"+getId()); 114 | Runtime.getRuntime().addShutdownHook(new ShutdownHandler()); 115 | UDPServerSocket socket = new UDPServerSocket(logger, _interface.getPort(), _interface.getHostString()); 116 | new SessionManager(this, socket); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/ServerHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | import net.beaconpe.jraklib.JRakLib; 24 | import net.beaconpe.jraklib.protocol.EncapsulatedPacket; 25 | 26 | import java.nio.ByteBuffer; 27 | import java.util.Arrays; 28 | 29 | /** 30 | * Handler that provides easy communication with the server. 31 | */ 32 | public class ServerHandler { 33 | protected JRakLibServer server; 34 | protected ServerInstance instance; 35 | 36 | public ServerHandler(JRakLibServer server, ServerInstance instance){ 37 | this.server = server; 38 | this.instance = instance; 39 | } 40 | 41 | public void sendEncapsulated(String identifier, EncapsulatedPacket packet){ 42 | byte flags = JRakLib.PRIORITY_NORMAL; 43 | sendEncapsulated(identifier, packet, flags); 44 | } 45 | 46 | public void sendEncapsulated(String identifier, EncapsulatedPacket packet, byte flags){ 47 | ByteBuffer bb = ByteBuffer.allocate(3+identifier.getBytes().length+packet.getTotalLength(true)); 48 | bb.put(JRakLib.PACKET_ENCAPSULATED).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put(flags).put(packet.toBinary(true)); 49 | server.pushMainToThreadPacket(Arrays.copyOf(bb.array(), bb.position())); 50 | bb = null; 51 | } 52 | 53 | public void sendRaw(String address, short port, byte[] payload){ 54 | ByteBuffer bb = ByteBuffer.allocate(4+address.getBytes().length+payload.length); 55 | bb.put(JRakLib.PACKET_RAW).put((byte) address.getBytes().length).put(address.getBytes()).put(Binary.writeShort(port)).put(payload); 56 | server.pushMainToThreadPacket(bb.array()); 57 | } 58 | 59 | public void closeSession(String identifier, String reason){ 60 | ByteBuffer bb = ByteBuffer.allocate(3+identifier.getBytes().length+reason.getBytes().length); 61 | bb.put(JRakLib.PACKET_CLOSE_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put((byte) reason.getBytes().length).put(reason.getBytes()); 62 | server.pushMainToThreadPacket(bb.array()); 63 | } 64 | 65 | public void sendOption(String name, String value){ 66 | ByteBuffer bb = ByteBuffer.allocate(2+name.getBytes().length+value.getBytes().length); 67 | bb.put(JRakLib.PACKET_SET_OPTION).put((byte) name.getBytes().length).put(name.getBytes()).put(value.getBytes()); 68 | server.pushMainToThreadPacket(bb.array()); 69 | } 70 | 71 | public void blockAddress(String address, int timeout){ 72 | ByteBuffer bb = ByteBuffer.allocate(6+address.getBytes().length); 73 | bb.put(JRakLib.PACKET_BLOCK_ADDRESS).put((byte) address.getBytes().length).put(address.getBytes()).put(Binary.writeInt(timeout)); 74 | server.pushMainToThreadPacket(bb.array()); 75 | } 76 | 77 | public void shutdown(){ 78 | server.shutdown(); 79 | server.pushMainToThreadPacket(new byte[] {JRakLib.PACKET_SHUTDOWN}); 80 | //TODO: Find a way to kill server after sleep. 81 | } 82 | 83 | public void emergencyShutdown(){ 84 | server.shutdown(); 85 | server.pushMainToThreadPacket(new byte[] {0x7f}); //JRakLib::PACKET_EMERGENCY_SHUTDOWN 86 | } 87 | 88 | protected void invalidSession(String identifier){ 89 | ByteBuffer bb = ByteBuffer.allocate(2+identifier.getBytes().length); 90 | bb.put(JRakLib.PACKET_INVALID_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()); 91 | server.pushMainToThreadPacket(bb.array()); 92 | } 93 | 94 | public boolean handlePacket(){ 95 | byte[] packet = server.readThreadToMainPacket(); 96 | if(packet == null){ 97 | return false; 98 | } 99 | if(packet.length > 0){ 100 | byte id = packet[0]; 101 | int offset = 1; 102 | if(id == JRakLib.PACKET_ENCAPSULATED){ 103 | int len = packet[offset++]; 104 | String identifier = new String(Binary.subbytes(packet, offset, len)); 105 | offset += len; 106 | byte flags = packet[offset++]; 107 | byte[] buffer = Binary.subbytes(packet, offset); 108 | instance.handleEncapsulated(identifier, EncapsulatedPacket.fromBinary(buffer, true), flags); 109 | } else if(id == JRakLib.PACKET_RAW){ 110 | int len = packet[offset++]; 111 | String address = new String(Binary.subbytes(packet, offset, len)); 112 | offset += len; 113 | int port = Binary.readShort(Binary.subbytes(packet, offset, 2)); 114 | offset += 2; 115 | byte[] payload = Binary.subbytes(packet, offset); 116 | instance.handleRaw(address, port, payload); 117 | } else if(id == JRakLib.PACKET_SET_OPTION){ 118 | int len = packet[offset++]; 119 | String name = new String(Binary.subbytes(packet, offset, len)); 120 | offset += len; 121 | String value = new String(Binary.subbytes(packet, offset)); 122 | instance.handleOption(name, value); 123 | } else if(id == JRakLib.PACKET_OPEN_SESSION){ 124 | int len = packet[offset++]; 125 | String identifier = new String(Binary.subbytes(packet, offset, len)); 126 | offset += len; 127 | len = packet[offset++]; 128 | String address = new String(Binary.subbytes(packet, offset, len)); 129 | offset += len; 130 | int port = Binary.readShort(Binary.subbytes(packet, offset, 2)); 131 | offset += 2; 132 | long clientID = Binary.readLong(Binary.subbytes(packet, offset, 8)); 133 | instance.openSession(identifier, address, port, clientID); 134 | } else if(id == JRakLib.PACKET_CLOSE_SESSION){ 135 | int len = packet[offset++]; 136 | String identifier = new String(Binary.subbytes(packet, offset, len)); 137 | offset += len; 138 | len = packet[offset++]; 139 | String reason = new String(Binary.subbytes(packet, offset, len)); 140 | instance.closeSession(identifier, reason); 141 | } else if(id == JRakLib.PACKET_INVALID_SESSION){ 142 | int len = packet[offset++]; 143 | String identifier = new String(Binary.subbytes(packet, offset, len)); 144 | instance.closeSession(identifier, "Invalid session."); 145 | } else if(id == JRakLib.PACKET_ACK_NOTIFICATION){ 146 | int len = packet[offset++]; 147 | String identifier = new String(Binary.subbytes(packet, offset, len)); 148 | offset += len; 149 | int identifierACK = Binary.readInt(Binary.subbytes(packet, offset, 4)); 150 | instance.notifyACK(identifier, identifierACK); 151 | } else if(id == JRakLib.PACKET_EXCEPTION_CAUGHT){ 152 | int len = packet[offset++]; 153 | String message = new String(Binary.subbytes(packet, offset, len)); 154 | offset += len; 155 | len = Binary.readShort(Binary.subbytes(packet, offset, 2)); 156 | String className = new String(Binary.subbytes(packet, offset, len)); 157 | instance.exceptionCaught(className, message); 158 | } 159 | return true; 160 | } 161 | return false; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/ServerInstance.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | import net.beaconpe.jraklib.protocol.EncapsulatedPacket; 23 | 24 | /** 25 | * An interface for communication with the server implementation. 26 | */ 27 | public interface ServerInstance { 28 | 29 | void openSession(String identifier, String address, int port, long clientID); 30 | 31 | void closeSession(String identifier, String reason); 32 | 33 | void handleEncapsulated(String identifier, EncapsulatedPacket packet, int flags); 34 | 35 | void handleRaw(String address, int port, byte[] payload); 36 | 37 | void notifyACK(String identifier, int identifierACK); 38 | 39 | void exceptionCaught(String clazz, String message); 40 | 41 | void handleOption(String option, String value); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/Session.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | 23 | import net.beaconpe.jraklib.Binary; 24 | import net.beaconpe.jraklib.JRakLib; 25 | import net.beaconpe.jraklib.protocol.*; 26 | 27 | import java.io.IOException; 28 | import java.net.InetSocketAddress; 29 | import java.nio.ByteBuffer; 30 | import java.time.Instant; 31 | import java.util.*; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | 34 | /** 35 | * Represents a networking session. 36 | */ 37 | public class Session { 38 | public final static int STATE_UNCONNECTED = 0; 39 | public final static int STATE_CONNECTING_1 = 1; 40 | public final static int STATE_CONNECTING_2 = 2; 41 | public final static int STATE_CONNECTED = 3; 42 | 43 | public static int WINDOW_SIZE = 2048; 44 | 45 | private int messageIndex = 0; 46 | private Map channelIndex = new ConcurrentHashMap<>(); 47 | 48 | private SessionManager sessionManager; 49 | private String address; 50 | private int port; 51 | private int state = STATE_UNCONNECTED; 52 | private List preJoinQueue = new ArrayList<>(); 53 | private int mtuSize = 548; //Min size 54 | private long id = 0; 55 | private int splitID = 0; 56 | 57 | private int sendSeqNumber = 0; 58 | private int lastSeqNumber = -1; 59 | 60 | private long lastUpdate; 61 | private long startTime; 62 | 63 | private List packetToSend = new ArrayList<>(); 64 | 65 | private boolean isActive; 66 | 67 | private List ACKQueue = new ArrayList<>(); 68 | private List NACKQueue = new ArrayList<>(); 69 | 70 | private Map recoveryQueue = new ConcurrentHashMap<>(); 71 | 72 | private Map> splitPackets = new ConcurrentHashMap<>(); 73 | 74 | private Map> needACK = new ConcurrentHashMap<>(); 75 | 76 | 77 | private DataPacket sendQueue; 78 | 79 | private int windowStart; 80 | private Map receivedWindow = new ConcurrentHashMap<>(); 81 | private int windowEnd; 82 | 83 | private int reliableWindowStart; 84 | private int reliableWindowEnd; 85 | private Map reliableWindow = new ConcurrentHashMap<>(); 86 | private int lastReliableIndex = -1; 87 | 88 | public Session(SessionManager sessionManager, String address, int port) { 89 | this.sessionManager = sessionManager; 90 | this.address = address; 91 | this.port = port; 92 | sendQueue = new DataPackets.DATA_PACKET_4(); 93 | lastUpdate = Instant.now().toEpochMilli(); 94 | startTime = Instant.now().toEpochMilli(); 95 | isActive = false; 96 | windowStart = -1; 97 | windowEnd = WINDOW_SIZE; 98 | 99 | reliableWindowStart = 0; 100 | reliableWindowEnd = WINDOW_SIZE; 101 | 102 | for(byte i = 0; i < 32; i++){ 103 | channelIndex.put(i, 0); 104 | } 105 | } 106 | 107 | public String getAddress() { 108 | return address; 109 | } 110 | 111 | public int getPort() { 112 | return port; 113 | } 114 | 115 | public long getID() { 116 | return id; 117 | } 118 | 119 | public void update(long time) throws IOException { 120 | if(!isActive && (lastUpdate + 10000) < time){ //10 second timeout 121 | disconnect("timeout"); 122 | return; 123 | } 124 | isActive = false; 125 | 126 | if(!ACKQueue.isEmpty()){ 127 | ACK pk = new ACK(); 128 | pk.packets = ACKQueue.stream().toArray(Integer[]::new); 129 | sendPacket(pk); 130 | ACKQueue.clear(); 131 | } 132 | 133 | if(!NACKQueue.isEmpty()){ 134 | NACK pk = new NACK(); 135 | pk.packets = NACKQueue.stream().toArray(Integer[]::new); 136 | sendPacket(pk); 137 | NACKQueue.clear(); 138 | } 139 | 140 | if(!packetToSend.isEmpty()){ 141 | int limit = 16; 142 | for(int i = 0; i < packetToSend.size(); i++){ 143 | DataPacket pk = packetToSend.get(i); 144 | pk.sendTime = time; 145 | pk.encode(); 146 | recoveryQueue.put(pk.seqNumber, pk); 147 | packetToSend.remove(pk); 148 | sendPacket(pk); 149 | if(limit-- <= 0){ 150 | break; 151 | } 152 | } 153 | } 154 | 155 | if(packetToSend.size() > WINDOW_SIZE){ 156 | packetToSend.clear(); 157 | } 158 | 159 | if(needACK.values().size() > 0){ 160 | for(Integer i : needACK.keySet()){ 161 | Map indexes = needACK.get(i); 162 | if(indexes.values().size() == 0){ 163 | needACK.remove(indexes); 164 | sessionManager.notifyACK(this, i); 165 | } 166 | } 167 | } 168 | 169 | for(Integer seq : recoveryQueue.keySet()){ 170 | DataPacket pk = recoveryQueue.get(seq); 171 | if(pk.sendTime < Instant.now().toEpochMilli() - 6000){ //If no ACK in 6 seconds, resend :) 172 | packetToSend.add(pk); 173 | recoveryQueue.remove(seq); 174 | } else { 175 | break; 176 | } 177 | } 178 | 179 | for(Integer seq : receivedWindow.keySet()){ 180 | if(seq < windowStart){ 181 | receivedWindow.remove(seq); 182 | } else { 183 | break; 184 | } 185 | } 186 | 187 | try { 188 | sendQueue(); 189 | } catch (IOException e) { 190 | sessionManager.getLogger().notice("Failed to send queue: IOException: "+e.getMessage()); 191 | throw new RuntimeException(e); 192 | } 193 | } 194 | 195 | public void disconnect() throws IOException { 196 | disconnect("unknown"); 197 | } 198 | 199 | public void disconnect(String reason) throws IOException { 200 | sessionManager.removeSession(this, reason); 201 | } 202 | 203 | private void sendPacket(Packet packet) throws IOException { 204 | sessionManager.sendPacket(packet, address, port); 205 | } 206 | 207 | public void sendQueue() throws IOException { 208 | if(!sendQueue.packets.isEmpty()){ 209 | sendQueue.seqNumber = sendSeqNumber++; 210 | sendPacket(sendQueue); 211 | sendQueue.sendTime = Instant.now().toEpochMilli(); 212 | recoveryQueue.put(sendQueue.seqNumber, sendQueue); 213 | sendQueue = new DataPackets.DATA_PACKET_4(); 214 | } 215 | } 216 | 217 | private void addToQueue(EncapsulatedPacket pk) throws IOException { 218 | addToQueue(pk, JRakLib.PRIORITY_NORMAL); 219 | } 220 | 221 | private void addToQueue(EncapsulatedPacket pk, int flags) throws IOException { 222 | int priority = flags & 0b0000111; 223 | if(pk.needACK && pk.messageIndex != -1){ 224 | Map map; 225 | if(needACK.get(pk.needACK) != null){ 226 | map = needACK.get(pk.needACK); 227 | map.put(pk.messageIndex, pk.messageIndex); 228 | } else { 229 | map = new ConcurrentHashMap<>(); 230 | map.put(pk.messageIndex, pk.messageIndex); 231 | } 232 | needACK.put(pk.identifierACK, map); 233 | } 234 | 235 | if(priority == JRakLib.PRIORITY_IMMEDIATE){ //Skip queues 236 | DataPacket packet = new DataPackets.DATA_PACKET_0(); 237 | packet.seqNumber = sendSeqNumber++; 238 | if(pk.needACK){ 239 | packet.packets.add(pk); 240 | pk.needACK = false; 241 | } else { 242 | packet.packets.add(pk.toBinary()); 243 | } 244 | 245 | sendPacket(packet); 246 | packet.sendTime = Instant.now().toEpochMilli(); 247 | recoveryQueue.put(packet.seqNumber, packet); 248 | return; 249 | } 250 | int length = sendQueue.length(); 251 | if(length + pk.getTotalLength() > mtuSize){ 252 | sendQueue(); 253 | } 254 | 255 | if(pk.needACK){ 256 | sendQueue.packets.add(pk); 257 | pk.needACK = false; 258 | } else { 259 | sendQueue.packets.add(pk.toBinary()); 260 | } 261 | } 262 | 263 | public void addEncapsulatedToQueue(EncapsulatedPacket packet) throws IOException { 264 | addEncapsulatedToQueue(packet, JRakLib.PRIORITY_NORMAL); 265 | } 266 | 267 | public void addEncapsulatedToQueue(EncapsulatedPacket packet, byte flags) throws IOException { 268 | if((packet.needACK = (flags & JRakLib.FLAG_NEED_ACK) > 0) == true){ 269 | needACK.put(packet.identifierACK, new ConcurrentHashMap<>()); 270 | } 271 | 272 | if(packet.reliability == 2 || packet.reliability == 3 || packet.reliability == 4 || packet.reliability == 6 || packet.reliability == 7){ 273 | packet.messageIndex = messageIndex++; 274 | 275 | if(packet.reliability == 3){ 276 | channelIndex.put(packet.orderChannel, channelIndex.get(packet.orderChannel) + 1); 277 | packet.orderIndex = channelIndex.get(packet.orderChannel); 278 | } 279 | } 280 | 281 | if(packet.getTotalLength() + 4 > mtuSize){ 282 | byte[][] buffers = Binary.splitbytes(packet.buffer, mtuSize - 34); 283 | int splitID = this.splitID++; 284 | splitID = splitID % 65536; 285 | for(int count = 0; count < buffers.length; count++){ 286 | byte[] buffer = buffers[count]; 287 | EncapsulatedPacket pk = new EncapsulatedPacket(); 288 | pk.splitID = (short) splitID; 289 | pk.hasSplit = true; 290 | pk.splitCount = buffers.length; 291 | pk.reliability = packet.reliability; 292 | pk.splitIndex = count; 293 | pk.buffer = buffer; 294 | if(count > 0){ 295 | pk.messageIndex = messageIndex++; 296 | } else { 297 | pk.messageIndex = packet.messageIndex; 298 | } 299 | if(pk.reliability == 3){ 300 | pk.orderChannel = packet.orderChannel; 301 | pk.orderIndex = packet.orderIndex; 302 | } 303 | addToQueue(pk, flags | JRakLib.PRIORITY_IMMEDIATE); 304 | } 305 | } else { 306 | addToQueue(packet, flags); 307 | } 308 | } 309 | 310 | private void handleSplit(EncapsulatedPacket packet) throws IOException { 311 | if(packet.splitCount >= 128){ 312 | return; 313 | } 314 | 315 | if(!splitPackets.containsKey(packet.splitID)){ 316 | Map map = new ConcurrentHashMap<>(); 317 | map.put(packet.splitIndex, packet); 318 | splitPackets.put(packet.splitID, map); 319 | } else { 320 | Map map = splitPackets.get(packet.splitID); 321 | map.put(packet.splitIndex, packet); 322 | splitPackets.put(packet.splitID, map); 323 | } 324 | 325 | if(splitPackets.get(packet.splitID).values().size() == packet.splitCount){ 326 | EncapsulatedPacket pk = new EncapsulatedPacket(); 327 | ByteBuffer bb = ByteBuffer.allocate(64 * 64 * 64); 328 | for(int i = 0; i < packet.splitCount; i++){ 329 | bb.put(splitPackets.get(packet.splitID).get(i).buffer); 330 | } 331 | pk.buffer = Arrays.copyOf(bb.array(), bb.position()); 332 | bb = null; 333 | 334 | pk.length = pk.buffer.length; 335 | splitPackets.remove(packet.splitID); 336 | 337 | handleEncapsulatedPacketRoute(pk); 338 | } 339 | } 340 | 341 | private void handleEncapsulatedPacket(EncapsulatedPacket packet) throws IOException { 342 | if(packet.messageIndex == -1){ 343 | handleEncapsulatedPacketRoute(packet); 344 | } else { 345 | if(packet.messageIndex < reliableWindowStart || packet.messageIndex > reliableWindowEnd){ 346 | return; 347 | } 348 | 349 | if((packet.messageIndex - lastReliableIndex) == 1){ 350 | lastReliableIndex++; 351 | reliableWindowStart++; 352 | reliableWindowEnd++; 353 | handleEncapsulatedPacketRoute(packet); 354 | 355 | if(!reliableWindow.values().isEmpty()){ 356 | //TODO: Implement ksort() ? 357 | //ksort(reliableWindow.values()); 358 | 359 | for(Integer index : reliableWindow.keySet()){ 360 | EncapsulatedPacket pk = reliableWindow.get(index); 361 | 362 | if((index - lastReliableIndex) != 1){ 363 | break; 364 | } 365 | lastReliableIndex++; 366 | reliableWindowStart++; 367 | reliableWindowEnd++; 368 | handleEncapsulatedPacketRoute(packet); 369 | reliableWindow.remove(index); 370 | } 371 | } 372 | } else { 373 | reliableWindow.put(packet.messageIndex, packet); 374 | } 375 | } 376 | } 377 | 378 | private void handleEncapsulatedPacketRoute(EncapsulatedPacket packet) throws IOException { 379 | if(sessionManager == null){ 380 | return; 381 | } 382 | 383 | if(packet.hasSplit){ 384 | if(state == STATE_CONNECTED){ 385 | handleSplit(packet); 386 | } 387 | return; 388 | } 389 | 390 | byte id = packet.buffer[0]; 391 | if(id < 0x80) { //internal data packet 392 | if (state == STATE_CONNECTING_2) { 393 | if (id == CLIENT_CONNECT_DataPacket.ID) { 394 | CLIENT_CONNECT_DataPacket dataPacket = new CLIENT_CONNECT_DataPacket(); 395 | dataPacket.buffer = packet.buffer; 396 | dataPacket.decode(); 397 | SERVER_HANDSHAKE_DataPacket pk = new SERVER_HANDSHAKE_DataPacket(); 398 | pk.address = new InetSocketAddress(address, port); 399 | pk.sendPing = dataPacket.sendPing; 400 | pk.sendPong = dataPacket.sendPing + 1000L; 401 | pk.encode(); 402 | 403 | EncapsulatedPacket sendPacket = new EncapsulatedPacket(); 404 | sendPacket.reliability = 0; 405 | sendPacket.buffer = pk.buffer; 406 | addToQueue(sendPacket, JRakLib.PRIORITY_IMMEDIATE); 407 | } else if (id == CLIENT_HANDSHAKE_DataPacket.ID) { 408 | CLIENT_HANDSHAKE_DataPacket dataPacket = new CLIENT_HANDSHAKE_DataPacket(); 409 | dataPacket.buffer = packet.buffer; 410 | dataPacket.decode(); 411 | 412 | if (dataPacket.address.getPort() == sessionManager.getPort() || !sessionManager.portChecking) { 413 | state = STATE_CONNECTED; //FINALLY! 414 | sessionManager.openSession(this); 415 | for (EncapsulatedPacket p : preJoinQueue) { 416 | sessionManager.streamEncapsulated(this, p); 417 | } 418 | preJoinQueue.clear(); 419 | } 420 | } 421 | } else if (id == CLIENT_DISCONNECT_DataPacket.ID) { 422 | disconnect("client disconnect"); 423 | } else if (id == PING_DataPacket.ID) { 424 | PING_DataPacket dataPacket = new PING_DataPacket(); 425 | dataPacket.buffer = packet.buffer; 426 | dataPacket.decode(); 427 | 428 | PONG_DataPacket pk = new PONG_DataPacket(); 429 | pk.pingID = dataPacket.pingID; 430 | pk.encode(); 431 | 432 | EncapsulatedPacket sendPacket = new EncapsulatedPacket(); 433 | sendPacket.reliability = 0; 434 | sendPacket.buffer = pk.buffer; 435 | addToQueue(sendPacket); 436 | //TODO: add PING/PONG (0x00/0x03) automatic latency measure 437 | } else if(state == STATE_CONNECTED) { 438 | sessionManager.streamEncapsulated(this, packet); 439 | //TODO: stream channels 440 | } 441 | } else { 442 | preJoinQueue.add(packet); 443 | } 444 | } 445 | 446 | public void handlePacket(Packet packet) throws IOException { 447 | isActive = true; 448 | lastUpdate = Instant.now().toEpochMilli(); 449 | if(state == STATE_CONNECTED || state == STATE_CONNECTING_2){ 450 | if(packet.buffer[0] >= 0x80 || packet.buffer[0] <= 0x8f && packet instanceof DataPacket){ 451 | packet.decode(); 452 | 453 | DataPacket dp = (DataPacket) packet; 454 | if(dp.seqNumber < windowStart || dp.seqNumber > windowEnd || receivedWindow.containsKey(dp.seqNumber)){ 455 | return; 456 | } 457 | 458 | int diff = dp.seqNumber - lastSeqNumber; 459 | 460 | NACKQueue.remove(Integer.valueOf(dp.seqNumber)); 461 | ACKQueue.add(dp.seqNumber); 462 | receivedWindow.put(dp.seqNumber, dp.seqNumber); 463 | 464 | if(diff != 1){ 465 | for(int i = lastSeqNumber + 1; i < dp.seqNumber; i++){ 466 | if(!receivedWindow.containsKey(i)){ 467 | NACKQueue.add(i); 468 | } 469 | } 470 | } 471 | 472 | if(diff >= 1){ 473 | lastSeqNumber = dp.seqNumber; 474 | windowStart += diff; 475 | windowEnd += diff; 476 | } 477 | 478 | for(Object pk : dp.packets){ 479 | if(pk instanceof EncapsulatedPacket) { 480 | handleEncapsulatedPacket((EncapsulatedPacket) pk); 481 | } 482 | } 483 | } else { 484 | if(packet instanceof ACK){ 485 | packet.decode(); 486 | for(int seq : ((ACK) packet).packets){ 487 | if(recoveryQueue.containsKey(seq)){ 488 | for(Object pk : recoveryQueue.get(seq).packets){ 489 | if(pk instanceof EncapsulatedPacket && ((EncapsulatedPacket) pk).needACK && ((EncapsulatedPacket) pk).messageIndex != -1){ 490 | if(needACK.containsKey(((EncapsulatedPacket) pk).identifierACK)){ 491 | Map map = needACK.get(((EncapsulatedPacket) pk).identifierACK); 492 | map.remove(((EncapsulatedPacket) pk).messageIndex); 493 | needACK.put(((EncapsulatedPacket) pk).identifierACK, map); 494 | } 495 | } 496 | recoveryQueue.remove(seq); 497 | } 498 | } 499 | } 500 | } else if(packet instanceof NACK){ 501 | packet.decode(); 502 | for(Integer seq : ((NACK) packet).packets){ 503 | if(recoveryQueue.containsKey(seq)){ 504 | DataPacket pk = recoveryQueue.get(seq); 505 | pk.seqNumber = sendSeqNumber++; 506 | packetToSend.add(pk); 507 | recoveryQueue.remove(seq); 508 | } 509 | } 510 | } 511 | } 512 | } else if(packet.buffer[0] > 0x00 || packet.buffer[0] < 0x80){ //Not Data packet :) 513 | packet.decode(); 514 | if(packet instanceof UNCONNECTED_PING){ 515 | UNCONNECTED_PONG pk = new UNCONNECTED_PONG(); 516 | pk.serverID = sessionManager.getID(); 517 | pk.pingID = ((UNCONNECTED_PING) packet).pingId; 518 | pk.serverName = sessionManager.getName(); 519 | pk.encode(); 520 | sendPacket(pk); 521 | } else if(packet instanceof OPEN_CONNECTION_REQUEST_1){ 522 | //((OPEN_CONNECTION_REQUEST_1) packet).protocol; //TODO: check protocol number and refuse connections 523 | OPEN_CONNECTION_REPLY_1 pk = new OPEN_CONNECTION_REPLY_1(); 524 | pk.mtuSize = ((OPEN_CONNECTION_REQUEST_1) packet).mtuSize; 525 | pk.serverID = sessionManager.getID(); 526 | pk.encode(); 527 | sendPacket(pk); 528 | state = STATE_CONNECTING_1; 529 | } else if(state == STATE_CONNECTING_1 && packet instanceof OPEN_CONNECTION_REQUEST_2){ 530 | id = ((OPEN_CONNECTION_REQUEST_2) packet).clientID; 531 | if(((OPEN_CONNECTION_REQUEST_2) packet).serverAddress.getPort() == sessionManager.getPort() || !sessionManager.portChecking){ 532 | mtuSize = Math.min(Math.abs(((OPEN_CONNECTION_REQUEST_2) packet).mtuSize), 1464); //Max size, do not allow creating large buffers to fill server memory 533 | OPEN_CONNECTION_REPLY_2 pk = new OPEN_CONNECTION_REPLY_2(); 534 | pk.mtuSize = (short) mtuSize; 535 | pk.serverID = sessionManager.getID(); 536 | pk.clientAddress = new InetSocketAddress(address, port); 537 | pk.encode(); 538 | sendPacket(pk); 539 | state = STATE_CONNECTING_2; 540 | } 541 | } 542 | } 543 | } 544 | 545 | public void close() throws IOException { 546 | byte[] data = new byte[] {0x00, 0x00, 0x08, 0x15}; 547 | addEncapsulatedToQueue(EncapsulatedPacket.fromBinary(data), JRakLib.PRIORITY_IMMEDIATE); 548 | sessionManager = null; 549 | } 550 | } 551 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/SessionManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | import net.beaconpe.jraklib.Binary; 23 | import net.beaconpe.jraklib.JRakLib; 24 | import net.beaconpe.jraklib.Logger; 25 | 26 | import net.beaconpe.jraklib.protocol.*; 27 | import net.beaconpe.jraklib.protocol.DataPackets.*; 28 | 29 | import java.io.IOException; 30 | import java.net.DatagramPacket; 31 | import java.net.InetSocketAddress; 32 | import java.net.SocketAddress; 33 | import java.nio.ByteBuffer; 34 | import java.time.Instant; 35 | import java.util.Arrays; 36 | import java.util.Map; 37 | import java.util.Random; 38 | import java.util.concurrent.ConcurrentHashMap; 39 | import java.util.regex.Pattern; 40 | 41 | import static net.beaconpe.jraklib.JRakLib.*; 42 | 43 | /** 44 | * Manager for managing sessions. 45 | */ 46 | public class SessionManager{ 47 | protected Map> packetPool = new ConcurrentHashMap<>(); 48 | 49 | protected JRakLibServer server; 50 | 51 | protected UDPServerSocket socket; 52 | 53 | protected int receiveBytes = 0; 54 | protected int sendBytes = 0; 55 | 56 | protected Map sessions = new ConcurrentHashMap<>(); 57 | 58 | protected String name = ""; 59 | 60 | protected int packetLimit = 1000; 61 | 62 | protected boolean shutdown = false; 63 | 64 | protected int ticks = 0; 65 | protected long lastMeasure; 66 | 67 | protected Map block = new ConcurrentHashMap<>(); 68 | protected Map ipSec = new ConcurrentHashMap<>(); 69 | 70 | public boolean portChecking = false; 71 | private long serverID; 72 | 73 | public SessionManager(JRakLibServer server, UDPServerSocket socket){ 74 | this.server = server; 75 | this.socket = socket; 76 | registerPackets(); 77 | 78 | serverID = new Random().nextLong(); 79 | 80 | run(); 81 | } 82 | 83 | public int getPort(){ 84 | return server.getPort(); 85 | } 86 | 87 | public Logger getLogger(){ 88 | return server.getLogger(); 89 | } 90 | 91 | public void run(){ 92 | try { 93 | tickProcessor(); 94 | } catch (Exception e) { 95 | streamException(e); 96 | } 97 | } 98 | 99 | private void tickProcessor() throws IOException { 100 | lastMeasure = Instant.now().toEpochMilli(); 101 | 102 | while(!shutdown){ 103 | long start = Instant.now().toEpochMilli(); 104 | int max = 5000; 105 | while(receivePacket()){ 106 | max = max - 1; 107 | } 108 | while(receiveStream()); 109 | long time = Instant.now().toEpochMilli() - start; 110 | if(time < 50){ //20 ticks per second (1000 / 20) 111 | sleepUntil(Instant.now().toEpochMilli()+(50 - time)); 112 | } 113 | tick(); 114 | } 115 | } 116 | 117 | private void tick() throws IOException { 118 | long time = Instant.now().toEpochMilli(); 119 | for(Session session: sessions.values()){ 120 | session.update(time); 121 | } 122 | 123 | for(String address : ipSec.keySet()){ 124 | if(ipSec.get(address) >= packetLimit){ 125 | blockAddress(address); 126 | } 127 | } 128 | ipSec.clear(); 129 | 130 | if((ticks & 0b1111) == 0){ 131 | double diff = Math.max(0.005d, time - lastMeasure); 132 | streamOption("bandwith", "up:"+(sendBytes / diff)+",down:"+(receiveBytes / diff)); //TODO: Fix this stuff 133 | lastMeasure = time; 134 | sendBytes = 0; 135 | receiveBytes = 0; 136 | 137 | if(block.values().size() > 0){ 138 | long now = Instant.now().toEpochMilli(); 139 | for(String address: block.keySet()){ 140 | if(block.get(address) <= now){ 141 | block.remove(address); 142 | } else { 143 | break; 144 | } 145 | } 146 | } 147 | } 148 | ticks = ticks + 1; 149 | } 150 | 151 | private boolean receivePacket() throws IOException { 152 | DatagramPacket packet = socket.readPacket(); 153 | if(packet == null) { 154 | return false; 155 | } 156 | int len = packet.getLength(); 157 | if(len > 0){ 158 | SocketAddress source = packet.getSocketAddress(); 159 | receiveBytes += len; 160 | if(block.containsKey(source.toString())){ 161 | return true; 162 | } 163 | 164 | if(ipSec.containsKey(source.toString())){ 165 | ipSec.put(source.toString(), ipSec.get(source.toString()) + 1); 166 | } else { 167 | ipSec.put(source.toString(), 1); 168 | } 169 | 170 | Packet pkt = getPacketFromPool(packet.getData()[0]); 171 | if(pkt != null){ 172 | pkt.buffer = packet.getData(); 173 | getSession(getAddressFromString(source.toString()), packet.getPort()).handlePacket(pkt); 174 | return true; 175 | } else if (packet.getData() != null){ 176 | streamRaw(source, packet.getData()); 177 | return true; 178 | } else { 179 | getLogger().notice("Dropped packet: "+ Arrays.toString(packet.getData())); 180 | return false; 181 | } 182 | } 183 | return false; 184 | } 185 | 186 | public void sendPacket(Packet packet, String dest, int port) throws IOException { 187 | packet.encode(); 188 | sendBytes += packet.buffer.length; 189 | socket.writePacket(packet.buffer, new InetSocketAddress(dest, port)); 190 | } 191 | 192 | public void streamEncapsulated(Session session, EncapsulatedPacket packet){ 193 | streamEncapsulated(session, packet, JRakLib.PRIORITY_NORMAL); 194 | } 195 | 196 | public void streamEncapsulated(Session session, EncapsulatedPacket packet, byte flags){ 197 | String id = session.getAddress() + ":" + session.getPort(); 198 | ByteBuffer bb = ByteBuffer.allocate(3+id.getBytes().length+packet.getTotalLength(true)); 199 | bb.put(JRakLib.PACKET_ENCAPSULATED).put((byte) id.getBytes().length).put(id.getBytes()).put(flags).put(packet.toBinary(true)); 200 | server.pushThreadToMainPacket(bb.array()); 201 | } 202 | 203 | public void streamRaw(SocketAddress address, byte[] payload){ 204 | String dest; 205 | int port; 206 | if(address.toString().contains("/")) { 207 | dest = address.toString().split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[0]; 208 | port = Integer.parseInt(address.toString().split(Pattern.quote("/"))[1].split(Pattern.quote(":"))[1]); 209 | } else { 210 | dest = address.toString().split(Pattern.quote(":"))[0]; 211 | port = Integer.parseInt(address.toString().split(Pattern.quote(":"))[1]); 212 | } 213 | streamRaw(dest, port, payload); 214 | } 215 | 216 | public void streamRaw(String address, int port, byte[] payload){ 217 | ByteBuffer bb = ByteBuffer.allocate(4 + address.getBytes().length + payload.length); 218 | bb.put(JRakLib.PACKET_RAW).put((byte) address.getBytes().length).put(address.getBytes()).put(Binary.writeShort((short) port)).put(payload); 219 | server.pushThreadToMainPacket(bb.array()); 220 | } 221 | 222 | protected void streamClose(String identifier, String reason){ 223 | ByteBuffer bb = ByteBuffer.allocate(3 + identifier.getBytes().length + reason.getBytes().length); 224 | bb.put(JRakLib.PACKET_CLOSE_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put((byte) reason.getBytes().length).put(reason.getBytes()); 225 | server.pushThreadToMainPacket(bb.array()); 226 | } 227 | 228 | protected void streamInvalid(String identifier){ 229 | ByteBuffer bb = ByteBuffer.allocate(2+identifier.getBytes().length); 230 | bb.put(JRakLib.PACKET_INVALID_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()); 231 | server.pushThreadToMainPacket(bb.array()); 232 | } 233 | 234 | protected void streamOpen(Session session){ 235 | String identifier = session.getAddress() + ":" + session.getPort(); 236 | ByteBuffer bb = ByteBuffer.allocate(13 + identifier.getBytes().length+session.getAddress().getBytes().length); 237 | bb.put(JRakLib.PACKET_OPEN_SESSION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put((byte) session.getAddress().getBytes().length).put(session.getAddress().getBytes()).put(Binary.writeShort((short) session.getPort())).put(Binary.writeLong(session.getID())); 238 | server.pushThreadToMainPacket(bb.array()); 239 | } 240 | 241 | protected void streamACK(String identifier, int identifierACK){ 242 | ByteBuffer bb = ByteBuffer.allocate(6+identifier.getBytes().length); 243 | bb.put(JRakLib.PACKET_ACK_NOTIFICATION).put((byte) identifier.getBytes().length).put(identifier.getBytes()).put(Binary.writeInt(identifierACK)); 244 | server.pushThreadToMainPacket(bb.array()); 245 | } 246 | 247 | protected void streamOption(String name, String value){ 248 | ByteBuffer bb = ByteBuffer.allocate(2+name.getBytes().length+value.getBytes().length); 249 | bb.put(JRakLib.PACKET_SET_OPTION).put((byte) name.getBytes().length).put(name.getBytes()).put(value.getBytes()); 250 | server.pushThreadToMainPacket(bb.array()); 251 | } 252 | 253 | protected void streamException(Exception e) { 254 | ByteBuffer bb = ByteBuffer.allocate(5+e.getMessage().getBytes().length+e.getClass().getName().getBytes().length); 255 | bb.put(JRakLib.PACKET_EXCEPTION_CAUGHT).put((byte) e.getMessage().getBytes().length).put(e.getMessage().getBytes()).put(Binary.writeUnsignedShort(e.getClass().getName().getBytes().length)).put(e.getClass().getName().getBytes()); 256 | server.pushThreadToMainPacket(bb.array()); 257 | } 258 | 259 | public boolean receiveStream() throws IOException { 260 | byte[] packet = server.readMainToThreadPacket(); 261 | if(packet == null){ 262 | return false; 263 | } 264 | if(packet.length > 0){ 265 | byte id = packet[0]; 266 | int offset = 1; 267 | if(id == JRakLib.PACKET_ENCAPSULATED){ 268 | int len = packet[offset++]; 269 | String identifier = new String(Binary.subbytes(packet, offset, len)); 270 | offset += len; 271 | if(sessions.containsKey(identifier)){ 272 | byte flags = packet[offset++]; 273 | byte[] buffer = Binary.subbytes(packet, offset); 274 | sessions.get(identifier).addEncapsulatedToQueue(EncapsulatedPacket.fromBinary(buffer, true), flags); 275 | } else { 276 | streamInvalid(identifier); 277 | } 278 | } else if(id == JRakLib.PACKET_RAW){ 279 | int len = packet[offset++]; 280 | String address = new String(Binary.subbytes(packet, offset, len)); 281 | offset += len; 282 | int port = Binary.readShort(Binary.subbytes(packet, offset, 2)); 283 | offset += 2; 284 | byte[] payload = Binary.subbytes(packet, offset); 285 | socket.writePacket(payload, new InetSocketAddress(address, port)); 286 | } else if(id == JRakLib.PACKET_CLOSE_SESSION){ 287 | int len = packet[offset++]; 288 | String identifier = new String(Binary.subbytes(packet, offset, len)); 289 | if(sessions.containsKey(identifier)){ 290 | removeSession(sessions.get(identifier)); 291 | } else { 292 | streamInvalid(identifier); 293 | } 294 | } else if(id == JRakLib.PACKET_INVALID_SESSION){ 295 | int len = packet[offset++]; 296 | String identifier = new String(Binary.subbytes(packet, offset, len)); 297 | if(sessions.containsKey(identifier)){ 298 | removeSession(sessions.get(identifier)); 299 | } 300 | } else if(id == JRakLib.PACKET_SET_OPTION){ 301 | int len = packet[offset++]; 302 | String name = new String(Binary.subbytes(packet, offset, len)); 303 | offset += len; 304 | String value = new String(Binary.subbytes(packet, offset)); 305 | switch(name){ 306 | case "name": 307 | this.name = value; 308 | break; 309 | case "portChecking": 310 | portChecking = Boolean.parseBoolean(value); 311 | break; 312 | case "packetLimit": 313 | packetLimit = Integer.parseInt(value); 314 | break; 315 | } 316 | } else if(id == JRakLib.PACKET_BLOCK_ADDRESS){ 317 | int len = packet[offset++]; 318 | String address = new String(Binary.subbytes(packet, offset, len)); 319 | offset += len; 320 | int timeout = Binary.readInt(Binary.subbytes(packet, offset, 4)); 321 | blockAddress(address, timeout); 322 | } else if(id == JRakLib.PACKET_SHUTDOWN){ 323 | for(Session session: sessions.values()){ 324 | removeSession(session); 325 | } 326 | 327 | socket.close(); 328 | shutdown = true; 329 | } else if(id == JRakLib.PACKET_EMERGENCY_SHUTDOWN){ 330 | shutdown = true; 331 | } else { 332 | return false; 333 | } 334 | return true; 335 | } 336 | return false; 337 | } 338 | 339 | public void blockAddress(String address){ 340 | blockAddress(address, 30000); 341 | } 342 | 343 | /** 344 | * Block an address (no packets will be handled from this address), for the specified timeout. 345 | * @param address The address of the client in the format of ([IP]:[Port]). 346 | * @param timeout The timeout value in milliseconds. If -1, the address will be blocked forever. 347 | */ 348 | public void blockAddress(String address, int timeout){ 349 | long _final = Instant.now().toEpochMilli() + timeout; 350 | if(!block.containsKey(address) || timeout == -1){ 351 | if(timeout == -1){ 352 | _final = Long.MAX_VALUE; 353 | } else { 354 | getLogger().notice("[JRakLib Thread #"+Thread.currentThread().getId()+"] Blocked "+address+" for "+timeout+" milliseconds"); 355 | } 356 | block.put(address, _final); 357 | } else if(block.get(address) < _final){ 358 | block.put(address, _final); 359 | } 360 | } 361 | 362 | public Session getSession(String ip, int port){ 363 | String id = ip + ":" + port; 364 | if(!sessions.containsKey(id)){ 365 | sessions.put(id, new Session(this, ip, port)); 366 | } 367 | 368 | return sessions.get(id); 369 | } 370 | 371 | public void removeSession(Session session) throws IOException { 372 | removeSession(session, "unknown"); 373 | } 374 | 375 | public void removeSession(Session session, String reason) throws IOException { 376 | String id = session.getAddress() + ":" + session.getPort(); 377 | if(sessions.containsKey(id)){ 378 | sessions.get(id).close(); 379 | sessions.remove(id); 380 | streamClose(id, reason); 381 | } 382 | } 383 | 384 | public void openSession(Session session){ 385 | streamOpen(session); 386 | } 387 | 388 | public void notifyACK(Session session, int identifierACK){ 389 | streamACK(session.getAddress() + ":" + session.getPort(), identifierACK); 390 | } 391 | 392 | public String getName(){ 393 | return name; 394 | } 395 | 396 | public long getID(){ 397 | return serverID; 398 | } 399 | 400 | private void registerPacket(byte id, Class clazz) { 401 | packetPool.put(id, clazz); 402 | } 403 | 404 | public Packet getPacketFromPool(byte id) { 405 | if(packetPool.containsKey(id)){ 406 | Class clazz = packetPool.get(id); 407 | try { 408 | return clazz.newInstance(); 409 | } catch (InstantiationException e) { 410 | e.printStackTrace(); 411 | } catch (IllegalAccessException e) { 412 | e.printStackTrace(); 413 | } 414 | } 415 | return null; 416 | } 417 | 418 | private void registerPackets(){ 419 | registerPacket(UNCONNECTED_PING.ID, UNCONNECTED_PING.class); 420 | registerPacket(UNCONNECTED_PING_OPEN_CONNECTIONS.ID, UNCONNECTED_PING_OPEN_CONNECTIONS.class); 421 | registerPacket(OPEN_CONNECTION_REQUEST_1.ID, OPEN_CONNECTION_REQUEST_1.class); 422 | registerPacket(OPEN_CONNECTION_REPLY_1.ID, OPEN_CONNECTION_REPLY_1.class); 423 | registerPacket(OPEN_CONNECTION_REQUEST_2.ID, OPEN_CONNECTION_REQUEST_2.class); 424 | registerPacket(OPEN_CONNECTION_REPLY_2.ID, OPEN_CONNECTION_REPLY_2.class); 425 | registerPacket(UNCONNECTED_PONG.ID, UNCONNECTED_PONG.class); 426 | registerPacket(ADVERTISE_SYSTEM.ID, ADVERTISE_SYSTEM.class); 427 | registerPacket(DATA_PACKET_0.ID, DATA_PACKET_0.class); 428 | registerPacket(DATA_PACKET_1.ID, DATA_PACKET_1.class); 429 | registerPacket(DATA_PACKET_2.ID, DATA_PACKET_2.class); 430 | registerPacket(DATA_PACKET_3.ID, DATA_PACKET_3.class); 431 | registerPacket(DATA_PACKET_4.ID, DATA_PACKET_4.class); 432 | registerPacket(DATA_PACKET_5.ID, DATA_PACKET_5.class); 433 | registerPacket(DATA_PACKET_6.ID, DATA_PACKET_6.class); 434 | registerPacket(DATA_PACKET_7.ID, DATA_PACKET_7.class); 435 | registerPacket(DATA_PACKET_8.ID, DATA_PACKET_8.class); 436 | registerPacket(DATA_PACKET_9.ID, DATA_PACKET_9.class); 437 | registerPacket(DATA_PACKET_A.ID, DATA_PACKET_A.class); 438 | registerPacket(DATA_PACKET_B.ID, DATA_PACKET_B.class); 439 | registerPacket(DATA_PACKET_C.ID, DATA_PACKET_C.class); 440 | registerPacket(DATA_PACKET_D.ID, DATA_PACKET_D.class); 441 | registerPacket(DATA_PACKET_E.ID, DATA_PACKET_E.class); 442 | registerPacket(DATA_PACKET_F.ID, DATA_PACKET_F.class); 443 | registerPacket(NACK.ID, NACK.class); 444 | registerPacket(ACK.ID, ACK.class); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/main/java/net/beaconpe/jraklib/server/UDPServerSocket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | package net.beaconpe.jraklib.server; 21 | 22 | import net.beaconpe.jraklib.Logger; 23 | 24 | import java.io.Closeable; 25 | import java.io.IOException; 26 | import java.net.*; 27 | import java.util.Arrays; 28 | 29 | /** 30 | * A UDP Server socket. 31 | */ 32 | public class UDPServerSocket implements Closeable{ 33 | protected Logger logger; 34 | protected DatagramSocket socket; 35 | 36 | public UDPServerSocket(Logger logger, int port, String _interface){ 37 | this.logger = logger; 38 | try { 39 | socket = new DatagramSocket(new InetSocketAddress(_interface, port)); 40 | socket.setBroadcast(true); 41 | socket.setSendBufferSize(1024 * 1024 * 8); 42 | socket.setReceiveBufferSize(1024 * 1024 * 8); 43 | socket.setSoTimeout(1); 44 | } catch(SocketException e){ 45 | logger.critical("**** FAILED TO BIND TO "+_interface+":"+port+"!"); 46 | logger.critical("Perhaps a server is already running on that port?"); 47 | System.exit(1); 48 | } 49 | } 50 | 51 | public DatagramSocket getSocket(){ 52 | return socket; 53 | } 54 | 55 | public void close(){ 56 | socket.close(); 57 | } 58 | 59 | public DatagramPacket readPacket() throws IOException { 60 | DatagramPacket dp = new DatagramPacket(new byte[65535], 65535); 61 | try { 62 | socket.receive(dp); 63 | dp.setData(Arrays.copyOf(dp.getData(), dp.getLength())); 64 | return dp; 65 | } catch (SocketTimeoutException e) { 66 | return null; 67 | } 68 | } 69 | 70 | public void writePacket(byte[] buffer, InetSocketAddress dest) throws IOException { 71 | DatagramPacket dp = new DatagramPacket(buffer, buffer.length, dest); 72 | socket.send(dp); 73 | } 74 | 75 | public void setSendBuffer(int size) throws SocketException { 76 | socket.setSendBufferSize(size); 77 | } 78 | 79 | public void setRecvBuffer(int size) throws SocketException { 80 | socket.setReceiveBufferSize(size); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/TestClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JRakLib is not affiliated with Jenkins Software LLC or RakNet. 3 | * This software is a port of RakLib https://github.com/PocketMine/RakLib. 4 | 5 | * This file is part of JRakLib. 6 | * 7 | * JRakLib is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * JRakLib is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with JRakLib. If not, see . 19 | */ 20 | import net.beaconpe.jraklib.client.ClientHandler; 21 | import net.beaconpe.jraklib.client.ClientInstance; 22 | import net.beaconpe.jraklib.client.JRakLibClient; 23 | import net.beaconpe.jraklib.protocol.EncapsulatedPacket; 24 | 25 | import java.io.IOException; 26 | import java.util.Arrays; 27 | 28 | /** 29 | * Random Testing client thing. 30 | * 31 | * @author jython234 32 | */ 33 | public class TestClient { 34 | 35 | public static void main(String[] args){ 36 | try { 37 | JRakLibClient.PingResponse response = JRakLibClient.pingServer(null, "imcpe.com", 19132, 5, 500); 38 | System.out.println("ServerID: "+response.serverId+", Name: "+response.name); 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | JRakLibClient client = new JRakLibClient(null, "imcpe.com", 19132); 43 | ClientHandler handler = new ClientHandler(client, new TestClientInstance()); 44 | while(true){ 45 | handler.handlePacket(); 46 | } 47 | } 48 | 49 | public static class TestClientInstance implements ClientInstance { 50 | 51 | @Override 52 | public void connectionOpened(long serverId) { 53 | System.out.println("Connection opened! ServerID: "+serverId); 54 | } 55 | 56 | @Override 57 | public void connectionClosed(String reason) { 58 | System.out.println("Connection closed, reason: "+reason); 59 | } 60 | 61 | @Override 62 | public void handleEncapsulated(EncapsulatedPacket packet, int flags) { 63 | System.out.println("Encapsulated: "+packet.buffer[0]+", "+flags); 64 | } 65 | 66 | @Override 67 | public void handleRaw(byte[] payload) { 68 | System.out.println("Raw: "+ Arrays.toString(payload)); 69 | } 70 | 71 | @Override 72 | public void handleOption(String option, String value) { 73 | System.out.println("Option: "+option+", "+value); 74 | } 75 | } 76 | 77 | } 78 | --------------------------------------------------------------------------------