├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── pulse │ └── bep │ ├── util │ ├── Bytes.java │ ├── IOFailed.java │ ├── LZ4Compression.java │ ├── Xdr.java │ ├── XdrInputStream.java │ └── XdrOutputStream.java │ └── v1 │ ├── Close.java │ ├── Message.java │ ├── Ping.java │ ├── Pong.java │ ├── Request.java │ └── Response.java └── test └── java └── pulse └── bep ├── util ├── BytesTests.java ├── LZ4CompressionTests.java ├── XdrInputStreamTests.java ├── XdrOutputStreamTests.java └── XdrTests.java └── v1 ├── CloseTests.java ├── MessageTests.java ├── PingTests.java ├── PongTests.java ├── RequestTests.java └── ResponseTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | *.iml 4 | build 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 dapperstout 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to deal 4 | in the Software without restriction, including without limitation the rights 5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 16 | THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pulse-Java 2 | ============== 3 | 4 | [![Build Status](https://travis-ci.org/dapperstout/pulse-java.svg)](https://travis-ci.org/dapperstout/pulse-java) 5 | 6 | A port of the [Syncthing][1] synchronization protocol in Java. 7 | 8 | [1]: http://syncthing.net 9 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | version = '1.0' 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | compile 'net.jpountz.lz4:lz4:1.2.0' 11 | compile 'commons-io:commons-io:2.4' 12 | testCompile 'junit:junit:4.11' 13 | testCompile 'org.mockito:mockito-all:1.9.5' 14 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapperstout/pulse-java/eca51efc17eb4f9f1439b05bc1f9f3f4f7cb6951/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 09 19:43:42 CEST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pulse-java' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/Bytes.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import java.math.BigInteger; 4 | 5 | public class Bytes { 6 | 7 | public static boolean[] bits(byte eightBits) { 8 | boolean[] result = new boolean[8]; 9 | byte mask = (byte) 0b10000000; 10 | for (int i = 0; i < result.length; i++) { 11 | result[i] = (eightBits & mask) != 0; 12 | eightBits <<= 1; 13 | } 14 | return result; 15 | } 16 | 17 | public static byte[] nibbles(byte twoNibbles) { 18 | byte[] result = new byte[2]; 19 | result[0] = (byte) ((twoNibbles >>> 4) & (byte) 0x0F); 20 | result[1] = (byte) (twoNibbles & 0x0F); 21 | return result; 22 | } 23 | 24 | public static byte[] bytes(short twoBytes) { 25 | byte[] result = new byte[2]; 26 | result[0] = (byte) ((twoBytes >>> 8) & 0xFF); 27 | result[1] = (byte) (twoBytes & 0xFF); 28 | return result; 29 | } 30 | 31 | public static byte[] bytes(int fourBytes) { 32 | byte[] result = new byte[4]; 33 | result[0] = (byte) ((fourBytes >>> 24) & 0xFF); 34 | result[1] = (byte) ((fourBytes >>> 16) & 0xFF); 35 | result[2] = (byte) ((fourBytes >>> 8) & 0xFF); 36 | result[3] = (byte) (fourBytes & 0xFF); 37 | return result; 38 | } 39 | 40 | public static byte[] bytes(long eightBytes) { 41 | byte[] result = new byte[8]; 42 | result[0] = (byte) ((eightBytes >>> 56) & 0xFF); 43 | result[1] = (byte) ((eightBytes >>> 48) & 0xFF); 44 | result[2] = (byte) ((eightBytes >>> 40) & 0xFF); 45 | result[3] = (byte) ((eightBytes >>> 32) & 0xFF); 46 | result[4] = (byte) ((eightBytes >>> 24) & 0xFF); 47 | result[5] = (byte) ((eightBytes >>> 16) & 0xFF); 48 | result[6] = (byte) ((eightBytes >>> 8) & 0xFF); 49 | result[7] = (byte) (eightBytes & 0xFF); 50 | return result; 51 | } 52 | 53 | public static byte concatenateBits(boolean... bits) { 54 | byte result = 0; 55 | for (boolean bit : bits) { 56 | result <<= 1; 57 | if (bit) { 58 | result = (byte) (result | 1); 59 | } 60 | } 61 | return result; 62 | } 63 | 64 | public static byte concatenateNibbles(byte leftNibble, byte rightNibble) { 65 | return (byte) (leftNibble << 4 | rightNibble); 66 | } 67 | 68 | public static short concatenateBytes(byte left, byte right) { 69 | return (short) ((unsigned(left) << 8) | unsigned(right)); 70 | } 71 | 72 | public static int concatenateBytes(byte b0, byte b1, byte b2, byte b3) { 73 | return (unsigned(b0) << 24) | (unsigned(b1) << 16) | (unsigned(b2) << 8) | unsigned(b3); 74 | } 75 | 76 | public static long concatenateBytes(byte... bytes) { 77 | long result = 0; 78 | for (byte b : bytes) { 79 | result <<= 8; 80 | result = (result | unsigned(b)); 81 | } 82 | return result; 83 | } 84 | 85 | public static int unsigned(byte b) { 86 | return b & 0xFF; 87 | } 88 | 89 | public static long unsigned(int i) { 90 | return i & 0xFFFFFFFFL; 91 | } 92 | 93 | public static BigInteger unsigned(long l) { 94 | return new BigInteger(1, bytes(l)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/IOFailed.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import java.io.IOException; 4 | 5 | public class IOFailed extends RuntimeException { 6 | public IOFailed(IOException cause) { 7 | super(cause); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/LZ4Compression.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import net.jpountz.lz4.LZ4Compressor; 4 | import net.jpountz.lz4.LZ4Exception; 5 | import net.jpountz.lz4.LZ4Factory; 6 | import net.jpountz.lz4.LZ4FastDecompressor; 7 | 8 | import static java.lang.System.arraycopy; 9 | import static java.util.Arrays.copyOf; 10 | import static pulse.bep.util.Bytes.*; 11 | 12 | public class LZ4Compression { 13 | 14 | private static final int MAX_BUFFER_LENGTH = 8 * 1024 * 1024; 15 | 16 | private static LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); 17 | private static LZ4Compressor compressor = lz4Factory.fastCompressor(); 18 | private static LZ4FastDecompressor decompressor = lz4Factory.fastDecompressor(); 19 | 20 | public static byte[] compress(byte[] data) { 21 | int maxCompressedLength = compressor.maxCompressedLength(data.length); 22 | byte[] compressed = new byte[4 + maxCompressedLength]; 23 | arraycopy(bytes(data.length), 0, compressed, 0, 4); 24 | int compressedLength = compressor.compress(data, 0, data.length, compressed, 4); 25 | return copyOf(compressed, 4 + compressedLength); 26 | } 27 | 28 | public static byte[] decompress(byte[] data) { 29 | long decompressedLength = unsigned(concatenateBytes(data[0], data[1], data[2], data[3])); 30 | if (decompressedLength > MAX_BUFFER_LENGTH) { 31 | throw new DecompressedDataTooLarge(); 32 | } 33 | try { 34 | return decompressor.decompress(data, 4, (int)decompressedLength); 35 | } catch(LZ4Exception exception) { 36 | throw new InvalidLZ4Data(exception); 37 | } 38 | } 39 | 40 | public static class InvalidLZ4Data extends RuntimeException { 41 | public InvalidLZ4Data(Throwable cause) { 42 | super(cause); 43 | } 44 | } 45 | 46 | public static class DecompressedDataTooLarge extends RuntimeException { 47 | public DecompressedDataTooLarge() { 48 | super("Decompressed data exceeds maximum size"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/Xdr.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | 5 | public class Xdr { 6 | 7 | public static byte[] xdr(Object... objects) { 8 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 9 | XdrOutputStream xdr = new XdrOutputStream(out); 10 | for (Object object : objects) { 11 | if (object instanceof String) { 12 | xdr.writeString((String) object); 13 | } else if (object instanceof Long) { 14 | xdr.writeLong((Long) object); 15 | } else if (object instanceof Integer) { 16 | xdr.writeInteger((Integer) object); 17 | } else if (object instanceof byte[]) { 18 | xdr.writeData((byte[]) object); 19 | } else { 20 | throw new UnexpectedObject(object); 21 | } 22 | } 23 | return out.toByteArray(); 24 | } 25 | 26 | public static byte[] xdr(byte[] data) { 27 | return xdr((Object)data); 28 | } 29 | 30 | public static class UnexpectedObject extends RuntimeException { 31 | public UnexpectedObject(Object object) { 32 | super("Unexpected object: " + object); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/XdrInputStream.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.DataInputStream; 5 | import java.io.IOException; 6 | import java.io.UnsupportedEncodingException; 7 | 8 | import static pulse.bep.util.Bytes.concatenateBytes; 9 | 10 | public class XdrInputStream { 11 | 12 | private DataInputStream in; 13 | 14 | public XdrInputStream(byte[] xdrBytes) { 15 | in = new DataInputStream(new ByteArrayInputStream(xdrBytes)); 16 | } 17 | 18 | public String readString() { 19 | try { 20 | return new String(readData(), "UTF-8"); 21 | } catch (UnsupportedEncodingException shouldNeverHappen) { 22 | throw new Error(shouldNeverHappen); 23 | } 24 | } 25 | 26 | public byte[] readData() { 27 | try { 28 | return readDataThrowingIOException(); 29 | } catch(IOException exception) { 30 | throw new IOFailed(exception); 31 | } 32 | } 33 | 34 | private byte[] readDataThrowingIOException() throws IOException { 35 | int length = in.readInt(); 36 | byte[] utf8Bytes = new byte[length]; 37 | in.readFully(utf8Bytes); 38 | int amountOfPadding = 4 - (length % 4); 39 | for (int i = 0; i < amountOfPadding; i++) { 40 | in.readByte(); 41 | } 42 | return utf8Bytes; 43 | } 44 | 45 | public int readInteger() { 46 | try { 47 | return readIntegerThrowingIOException(); 48 | } catch(IOException exception) { 49 | throw new IOFailed(exception); 50 | } 51 | } 52 | 53 | private int readIntegerThrowingIOException() throws IOException { 54 | byte[] fourBytes = new byte[4]; 55 | in.readFully(fourBytes); 56 | return concatenateBytes(fourBytes[0], fourBytes[1], fourBytes[2], fourBytes[3]); 57 | } 58 | 59 | public long readLong() { 60 | try { 61 | return readLongThrowingIOException(); 62 | } catch(IOException exception) { 63 | throw new IOFailed(exception); 64 | } 65 | } 66 | 67 | private long readLongThrowingIOException() throws IOException { 68 | byte[] eightBytes = new byte[8]; 69 | in.readFully(eightBytes); 70 | return concatenateBytes(eightBytes); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/util/XdrOutputStream.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import java.io.DataOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.io.UnsupportedEncodingException; 7 | 8 | import static pulse.bep.util.Bytes.bytes; 9 | 10 | public class XdrOutputStream { 11 | private DataOutputStream out; 12 | 13 | public XdrOutputStream(OutputStream out) { 14 | this.out = new DataOutputStream(out); 15 | } 16 | 17 | public void flush() { 18 | try { 19 | out.flush(); 20 | } catch (IOException exception) { 21 | throw new IOFailed(exception); 22 | } 23 | } 24 | 25 | public void close() { 26 | try { 27 | out.close(); 28 | } catch (IOException exception) { 29 | throw new IOFailed(exception); 30 | } 31 | } 32 | 33 | public void writeString(String string) { 34 | try { 35 | writeData(string.getBytes("UTF-8")); 36 | } catch (UnsupportedEncodingException shouldNeverHappen) { 37 | throw new Error(shouldNeverHappen); 38 | } 39 | } 40 | 41 | public void writeData(byte[] data) { 42 | try { 43 | writeDataThrowingIOException(data); 44 | } catch(IOException exception) { 45 | throw new IOFailed(exception); 46 | } 47 | } 48 | 49 | public void writeDataThrowingIOException(byte[] utf8Bytes) throws IOException { 50 | out.writeInt(utf8Bytes.length); 51 | out.write(utf8Bytes); 52 | int amountOfPadding = 4 - (utf8Bytes.length % 4); 53 | for (int i = 0; i < amountOfPadding; i++) { 54 | out.write(0); 55 | } 56 | } 57 | 58 | public void writeInteger(int i) { 59 | try { 60 | out.write(bytes(i)); 61 | } catch (IOException exception) { 62 | throw new IOFailed(exception); 63 | } 64 | } 65 | 66 | public void writeLong(long l) { 67 | try { 68 | out.write(bytes(l)); 69 | } catch (IOException exception) { 70 | throw new IOFailed(exception); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Close.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import static pulse.bep.util.Xdr.xdr; 4 | 5 | public class Close extends Message { 6 | 7 | public Close(String someReason) { 8 | super((byte) 7, xdr(someReason), true); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Message.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import pulse.bep.util.IOFailed; 4 | 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | 8 | import static pulse.bep.util.Bytes.*; 9 | import static pulse.bep.util.LZ4Compression.compress; 10 | import static pulse.bep.util.LZ4Compression.decompress; 11 | 12 | public class Message { 13 | 14 | private static final byte VERSION = 0; 15 | private short id; 16 | private byte type; 17 | private byte[] contents; 18 | private boolean isCompressed; 19 | 20 | public Message(byte type) { 21 | this(type, new byte[]{}); 22 | } 23 | 24 | public Message(byte type, byte[] contents) { 25 | this(type, contents, true); 26 | } 27 | 28 | public Message(byte type, byte[] contents, boolean compress) { 29 | this(MessageId.getNextId(), type, contents, compress); 30 | } 31 | 32 | public Message(short id, byte type, byte[] contents, boolean compress) { 33 | this.id = id; 34 | this.type = type; 35 | this.contents = compress ? compress(contents) : contents; 36 | this.isCompressed = compress; 37 | } 38 | 39 | public short getId() { 40 | return id; 41 | } 42 | 43 | public byte getType() { 44 | return type; 45 | } 46 | 47 | public byte[] getContents() { 48 | return isCompressed ? decompress(contents) : contents; 49 | } 50 | 51 | public boolean isCompressed() { 52 | return isCompressed; 53 | } 54 | 55 | public void writeTo(OutputStream out) { 56 | try { 57 | writeThrowingIOException(out); 58 | } catch (IOException exception) { 59 | throw new IOFailed(exception); 60 | } 61 | } 62 | 63 | private void writeThrowingIOException(OutputStream out) throws IOException { 64 | byte[] idBytes = bytes(id); 65 | out.write(concatenateNibbles(VERSION, idBytes[0])); 66 | out.write(idBytes[1]); 67 | out.write(type); 68 | out.write(concatenateBits(false, false, false, false, false, false, false, isCompressed)); 69 | out.write(bytes(contents.length)); 70 | out.write(contents); 71 | } 72 | 73 | private static class MessageId { 74 | private static short nextId = 0; 75 | 76 | public static synchronized short getNextId() { 77 | short result = nextId; 78 | nextId = (short) ((nextId + 1) % 4096); 79 | return result; 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Ping.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | public class Ping extends Message { 4 | 5 | public Ping() { 6 | super((byte) 4, new byte[]{}, false); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Pong.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | public class Pong extends Message { 4 | 5 | public Pong(Ping ping) { 6 | super(ping.getId(), (byte) 5, new byte[]{}, false); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Request.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import static pulse.bep.util.Xdr.xdr; 4 | 5 | public class Request extends Message { 6 | 7 | public Request(String repository, String name, long offset, int size) { 8 | super((byte) 2, xdr(repository, name, offset, size)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/pulse/bep/v1/Response.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import static pulse.bep.util.Xdr.xdr; 4 | 5 | public class Response extends Message { 6 | public Response(Request request, byte[] data) { 7 | super(request.getId(), (byte)3, xdr(data), true); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/util/BytesTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.math.BigInteger; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | import static pulse.bep.util.Bytes.*; 11 | 12 | public class BytesTests { 13 | 14 | @Test 15 | public void testDecompositionOfByteIntoBits() { 16 | boolean[] bits = bits((byte) 0b10101010); 17 | assertThat(bits, is(equalTo(new boolean[]{ 18 | true, false, true, false, true, false, true, false 19 | }))); 20 | } 21 | 22 | @Test 23 | public void testDecompositionOfByteIntoNibbles() { 24 | byte twoNibbles = (byte) 0b10100101; 25 | assertThat(nibbles(twoNibbles), is(equalTo(new byte[]{0b1010, 0b0101}))); 26 | } 27 | 28 | @Test 29 | public void testDecompositionOfShortIntoBytes() { 30 | short twoBytes = (short) 0b1010101001010101; 31 | assertThat(bytes(twoBytes), is(equalTo(new byte[]{(byte) 0b10101010, 0b01010101}))); 32 | } 33 | 34 | @Test 35 | public void testDecompositionOfIntIntoBytes() { 36 | assertThat(bytes(0xF00FA00A), is(equalTo(new byte[]{(byte) 0xF0, 0x0F, (byte) 0xA0, 0x0A}))); 37 | } 38 | 39 | @Test 40 | public void testDecompositionOfLongIntoBytes() { 41 | assertThat(bytes(0xF00FA00AB00BC00CL), is(equalTo(new byte[]{ 42 | (byte) 0xF0, 0x0F, (byte) 0xA0, 0x0A, (byte) 0xB0, 0x0B, (byte) 0xC0, 0x0C, 43 | }))); 44 | } 45 | 46 | @Test 47 | public void testConcatenationOfBitsIntoByte() { 48 | boolean[] bits = new boolean[]{true, false, true, false, true, false, true, false}; 49 | assertThat(concatenateBits(bits), is(equalTo((byte) 0b10101010))); 50 | } 51 | 52 | @Test 53 | public void testConcatenationOfNibblesIntoByte() { 54 | assertThat(concatenateNibbles((byte) 0b1010, (byte) 0b0101), is(equalTo((byte) 0b10100101))); 55 | } 56 | 57 | @Test 58 | public void testConcatenationOfBytesIntoShort() { 59 | byte leftByte = (byte) 0b10101010; 60 | byte rightByte = (byte) 0b01010101; 61 | assertThat(concatenateBytes(leftByte, rightByte), is(equalTo((short) 0b1010101001010101))); 62 | } 63 | 64 | @Test 65 | public void testConcatenationOfBytesIntoInt() { 66 | byte b0 = (byte) 0xF0; 67 | byte b1 = 0x0F; 68 | byte b2 = (byte) 0xA0; 69 | byte b3 = 0x0A; 70 | assertThat(concatenateBytes(b0, b1, b2, b3), is(equalTo(0xF00FA00A))); 71 | } 72 | 73 | @Test 74 | public void testConcatenationOfBytesIntoLong() { 75 | byte b0 = (byte) 0xF0; 76 | byte b1 = 0x0F; 77 | byte b2 = (byte) 0xA0; 78 | byte b3 = 0x0A; 79 | byte b4 = (byte) 0xB0; 80 | byte b5 = 0x0B; 81 | byte b6 = (byte) 0xC0; 82 | byte b7 = 0x0C; 83 | assertThat(concatenateBytes(b0, b1, b2, b3, b4, b5, b6, b7), is(equalTo(0xF00FA00AB00BC00CL))); 84 | } 85 | 86 | @Test 87 | public void testUnsignedByte() { 88 | assertThat(unsigned((byte) 0xFF), is(equalTo(255))); 89 | } 90 | 91 | @Test 92 | public void testUnsignedInt() { 93 | assertThat(unsigned(0xFFFFFFFF), is(equalTo(4294967295L))); 94 | } 95 | 96 | @Test 97 | public void testUnsignedLong() { 98 | assertThat(unsigned(0xFFFFFFFFFFFFFFFFL), is(equalTo(new BigInteger("18446744073709551615")))); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/util/LZ4CompressionTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | import static pulse.bep.util.Bytes.concatenateBytes; 9 | import static pulse.bep.util.LZ4Compression.*; 10 | import static pulse.bep.util.LZ4Compression.compress; 11 | import static pulse.bep.util.LZ4Compression.decompress; 12 | 13 | public class LZ4CompressionTests { 14 | 15 | @Test 16 | public void compressedOutputStartsWithUncompressedSize() { 17 | byte[] compressed = compress(SOME_DATA); 18 | int uncompressedSize = concatenateBytes(compressed[0], compressed[1], compressed[2], compressed[3]); 19 | assertThat(uncompressedSize, is(equalTo(SOME_DATA.length))); 20 | } 21 | 22 | @Test 23 | public void canDecompressCompressedData() { 24 | assertThat(decompress(compress(SOME_DATA)), is(equalTo(SOME_DATA))); 25 | } 26 | 27 | @Test 28 | public void canDecompressCompressedEmptyData() { 29 | assertThat(decompress(compress(new byte[]{})), is(equalTo(new byte[]{}))); 30 | } 31 | 32 | @Test(expected = InvalidLZ4Data.class) 33 | public void shouldNotDecompressInvalidData() { 34 | decompress(new byte[]{0, 0, 0, 4, 12, 23, 56, 78}); 35 | } 36 | 37 | @Test(expected = DecompressedDataTooLarge.class) 38 | public void shouldNotDecompressInvalidLength() { 39 | decompress(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, 12, 23, 56, 78}); 40 | } 41 | 42 | private static final byte[] SOME_DATA = new byte[]{12, 34, 56, 78}; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/util/XdrInputStreamTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | import static pulse.bep.util.Xdr.xdr; 9 | 10 | public class XdrInputStreamTests { 11 | 12 | @Test 13 | public void readsStrings() { 14 | byte[] xdrBytes = xdr("String"); 15 | XdrInputStream in = new XdrInputStream(xdrBytes); 16 | 17 | assertThat(in.readString(), is(equalTo("String"))); 18 | } 19 | 20 | @Test 21 | public void readsIntegers() { 22 | byte[] xdrBytes = xdr(0xF00FA00A); 23 | XdrInputStream in = new XdrInputStream(xdrBytes); 24 | 25 | assertThat(in.readInteger(), is(equalTo(0xF00FA00A))); 26 | } 27 | 28 | @Test 29 | public void readsLongs() { 30 | byte[] xdrBytes = xdr(0xF00FA00AB00BC00CL); 31 | XdrInputStream in = new XdrInputStream(xdrBytes); 32 | 33 | assertThat(in.readLong(), is(equalTo(0xF00FA00AB00BC00CL))); 34 | } 35 | 36 | @Test 37 | public void readsOpaqueData() { 38 | byte[] xdrBytes = xdr(new byte[]{12, 34}); 39 | XdrInputStream in = new XdrInputStream(xdrBytes); 40 | 41 | assertThat(in.readData(), is(equalTo(new byte[]{12, 34}))); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/util/XdrOutputStreamTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.UnsupportedEncodingException; 9 | 10 | import static java.util.Arrays.copyOfRange; 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.junit.Assert.assertThat; 14 | import static org.mockito.Mockito.spy; 15 | import static org.mockito.Mockito.verify; 16 | import static pulse.bep.util.Bytes.bytes; 17 | import static pulse.bep.util.Bytes.concatenateBytes; 18 | 19 | public class XdrOutputStreamTests { 20 | 21 | private static final String SOME_STRING = "String with interesting unicode character \u221E"; 22 | private static final byte[] SOME_DATA = new byte[]{12, 34}; 23 | 24 | private XdrOutputStream out; 25 | private ByteArrayOutputStream wrapped; 26 | 27 | @Before 28 | public void setUp() { 29 | wrapped = spy(new ByteArrayOutputStream()); 30 | out = new XdrOutputStream(wrapped); 31 | } 32 | 33 | @Test 34 | public void writesStringLength() throws UnsupportedEncodingException { 35 | out.writeString(SOME_STRING); 36 | 37 | byte[] xdrBytes = wrapped.toByteArray(); 38 | int xdrStringLength = extractXdrDataLength(xdrBytes); 39 | assertThat(xdrStringLength, is(equalTo(SOME_STRING.getBytes("UTF-8").length))); 40 | } 41 | 42 | @Test 43 | public void writesString() throws UnsupportedEncodingException { 44 | out.writeString(SOME_STRING); 45 | 46 | byte[] xdrBytes = wrapped.toByteArray(); 47 | String xdrString = new String(xdrBytes, 4, extractXdrDataLength(xdrBytes), "UTF-8"); 48 | assertThat(xdrString, is(equalTo(SOME_STRING))); 49 | } 50 | 51 | @Test 52 | public void padsStringToFourByteBoundary() { 53 | out.writeString(SOME_STRING); 54 | 55 | byte[] xdrBytes = wrapped.toByteArray(); 56 | assertThat(xdrBytes.length % 4, is(equalTo(0))); 57 | } 58 | 59 | @Test 60 | public void writesDataLength() { 61 | out.writeData(SOME_DATA); 62 | 63 | byte[] xdrBytes = wrapped.toByteArray(); 64 | int xdrDataLength = extractXdrDataLength(xdrBytes); 65 | assertThat(xdrDataLength, is(equalTo(SOME_DATA.length))); 66 | } 67 | 68 | @Test 69 | public void writesData() { 70 | out.writeData(SOME_DATA); 71 | 72 | byte[] xdrBytes = wrapped.toByteArray(); 73 | assertThat(copyOfRange(xdrBytes, 4, 4 + SOME_DATA.length), is(equalTo(SOME_DATA))); 74 | } 75 | 76 | private int extractXdrDataLength(byte[] bytes) { 77 | return concatenateBytes(bytes[0], bytes[1], bytes[2], bytes[3]); 78 | } 79 | 80 | @Test 81 | public void writesLong() { 82 | out.writeLong(0xF00FA00AB00BC00CL); 83 | 84 | byte[] xdrBytes = wrapped.toByteArray(); 85 | assertThat(bytes(0xF00FA00AB00BC00CL), is(equalTo(xdrBytes))); 86 | } 87 | 88 | @Test 89 | public void writesInteger() { 90 | out.writeInteger(0xF00FA00A); 91 | 92 | byte[] xdrBytes = wrapped.toByteArray(); 93 | assertThat(bytes(0xF00FA00A), is(equalTo(xdrBytes))); 94 | } 95 | 96 | @Test 97 | public void forwardsFlushToWrappedStream() throws IOException { 98 | out.flush(); 99 | 100 | verify(wrapped).flush(); 101 | } 102 | 103 | @Test 104 | public void forwardsCloseToWrappedStream() throws IOException { 105 | out.close(); 106 | 107 | verify(wrapped).close(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/util/XdrTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | import static pulse.bep.util.Xdr.xdr; 9 | 10 | public class XdrTests { 11 | 12 | @Test 13 | public void xdrEncodesIntermingledTypes() { 14 | byte[] bytes = xdr("String", new byte[]{12, 34}, 42L, 3); 15 | XdrInputStream in = new XdrInputStream(bytes); 16 | 17 | assertThat(in.readString(), is(equalTo("String"))); 18 | assertThat(in.readData(), is(equalTo(new byte[]{12, 34}))); 19 | assertThat(in.readLong(), is(equalTo(42L))); 20 | assertThat(in.readInteger(), is(equalTo(3))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/CloseTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import pulse.bep.util.XdrInputStream; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class CloseTests { 12 | 13 | private static final String SOME_REASON = "some reason"; 14 | 15 | private Close message; 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | message = new Close(SOME_REASON); 20 | } 21 | 22 | @Test 23 | public void isType7() { 24 | assertThat(message.getType(), is(equalTo((byte) 7))); 25 | } 26 | 27 | @Test 28 | public void hasXdrEncodedReason() { 29 | String reason = extractReasonFromClose(message); 30 | assertThat(reason, is(equalTo(SOME_REASON))); 31 | } 32 | 33 | private String extractReasonFromClose(Close message) { 34 | XdrInputStream in = new XdrInputStream(message.getContents()); 35 | return in.readString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/MessageTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | import static java.lang.System.arraycopy; 10 | import static org.hamcrest.CoreMatchers.*; 11 | import static org.junit.Assert.assertThat; 12 | import static pulse.bep.util.Bytes.*; 13 | import static pulse.bep.util.LZ4Compression.compress; 14 | 15 | public class MessageTests { 16 | 17 | public static final byte[] SOME_CONTENTS = new byte[]{12, 34, 56, 78}; 18 | 19 | @Test 20 | public void versionIsZero() { 21 | Message message = new SomeMessage(); 22 | byte version = extractVersionFromMessage(message); 23 | assertThat(version, is(equalTo((byte) 0))); 24 | } 25 | 26 | private byte extractVersionFromMessage(Message message) { 27 | byte firstByte = serialize(message)[0]; 28 | return nibbles(firstByte)[0]; 29 | } 30 | 31 | @Test 32 | public void idIsUnique() { 33 | Set previousIds = new HashSet<>(); 34 | for (int i = 0; i < 4096; i++) { 35 | Message message = new SomeMessage(); 36 | short id = extractIdFromMessage(message); 37 | assertThat(previousIds.contains(id), is(false)); 38 | previousIds.add(id); 39 | } 40 | } 41 | 42 | private short extractIdFromMessage(Message message) { 43 | byte[] bytes = serialize(message); 44 | return concatenateBytes(nibbles(bytes[0])[1], bytes[1]); 45 | } 46 | 47 | @Test 48 | public void typeIsEncodedInThirdByte() { 49 | Message message = new Message((byte) 3); 50 | byte type = extractTypeFromMessage(message); 51 | assertThat(type, is(equalTo((byte) 3))); 52 | } 53 | 54 | private byte extractTypeFromMessage(Message message) { 55 | return serialize(message)[2]; 56 | } 57 | 58 | @Test 59 | public void reservedBitsAreZero() { 60 | Message message = new SomeMessage(); 61 | boolean[] bits = bits(serialize(message)[3]); 62 | for (int i = 0; i < 7; i++) { 63 | assertThat(bits[i], is(false)); 64 | } 65 | } 66 | 67 | @Test 68 | public void compressionIsEnabledByDefault() { 69 | boolean isCompressed = extractIsCompressedFromMessage(new SomeMessage()); 70 | assertThat(isCompressed, is(true)); 71 | } 72 | 73 | @Test 74 | public void compressionIsIndicatedInLastBitOfFourthByte() { 75 | Message message = new Message((byte) 0, new byte[]{}, false); 76 | boolean isCompressed = extractIsCompressedFromMessage(message); 77 | assertThat(isCompressed, is(false)); 78 | } 79 | 80 | private boolean extractIsCompressedFromMessage(Message message) { 81 | return bits(serialize(message)[3])[7]; 82 | } 83 | 84 | @Test 85 | public void uncompressedLengthIsIndicatedInBytesFiveThroughEight() { 86 | Message message = new Message((byte) 0, SOME_CONTENTS, false); 87 | int length = extractContentLengthFromMessage(message); 88 | assertThat(length, is(equalTo(SOME_CONTENTS.length))); 89 | } 90 | 91 | @Test 92 | public void compressedLengthIsIndicatedInBytesFiveThroughEight() { 93 | Message message = new Message((byte) 0, SOME_CONTENTS, true); 94 | int length = extractContentLengthFromMessage(message); 95 | assertThat(length, is(equalTo(compress(SOME_CONTENTS).length))); 96 | } 97 | 98 | private int extractContentLengthFromMessage(Message message) { 99 | byte[] bytes = serialize(message); 100 | return concatenateBytes(bytes[4], bytes[5], bytes[6], bytes[7]); 101 | } 102 | 103 | @Test 104 | public void uncompressedContentsIsPresentInBytesNineAndForward() { 105 | Message message = new Message((byte) 0, SOME_CONTENTS, false); 106 | byte[] contents = extractContentsFromMessage(message); 107 | assertThat(contents, is(equalTo(SOME_CONTENTS))); 108 | } 109 | 110 | @Test 111 | public void compressedContentsIsPresentInBytesNineAndForward() { 112 | Message message = new Message((byte) 0, SOME_CONTENTS, true); 113 | byte[] contents = extractContentsFromMessage(message); 114 | assertThat(contents, is(equalTo(compress(SOME_CONTENTS)))); 115 | } 116 | 117 | private byte[] extractContentsFromMessage(Message message) { 118 | byte[] bytes = serialize(message); 119 | byte[] contents = new byte[bytes.length - 8]; 120 | arraycopy(bytes, 8, contents, 0, contents.length); 121 | return contents; 122 | } 123 | 124 | @Test 125 | public void getContentReturnsUncompressedContents() { 126 | Message message = new Message((byte)0, SOME_CONTENTS, true); 127 | assertThat(message.getContents(), is(equalTo(SOME_CONTENTS))); 128 | } 129 | 130 | private class SomeMessage extends Message { 131 | public SomeMessage() { 132 | super((byte) 0); 133 | } 134 | } 135 | 136 | private byte[] serialize(Message message) { 137 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 138 | message.writeTo(out); 139 | return out.toByteArray(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/PingTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.equalTo; 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | public class PingTests { 11 | 12 | private Ping ping; 13 | 14 | @Before 15 | public void setUp() throws Exception { 16 | ping = new Ping(); 17 | } 18 | 19 | @Test 20 | public void hasType4() { 21 | assertThat(ping.getType(), is(equalTo((byte) 4))); 22 | } 23 | 24 | @Test 25 | public void isNotCompressed() { 26 | assertThat(ping.isCompressed(), is(false)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/PongTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.equalTo; 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | public class PongTests { 11 | 12 | private Ping ping; 13 | private Pong pong; 14 | 15 | @Before 16 | public void setUp() throws Exception { 17 | ping = new Ping(); 18 | pong = new Pong(ping); 19 | } 20 | 21 | @Test 22 | public void hasType5() { 23 | assertThat(pong.getType(), is(equalTo((byte) 5))); 24 | } 25 | 26 | @Test 27 | public void isNotCompressed() { 28 | assertThat(pong.isCompressed(), is(false)); 29 | } 30 | 31 | @Test 32 | public void hasSameMessageIdAsPing() { 33 | assertThat(pong.getId(), is(equalTo(ping.getId()))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/RequestTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import pulse.bep.util.XdrInputStream; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class RequestTests { 12 | 13 | private static final String SOME_REPOSITORY = "Some Repository"; 14 | private static final String SOME_NAME = "Some Name"; 15 | private static final long SOME_OFFSET = 42L; 16 | private static final int SOME_SIZE = 3; 17 | 18 | private Request message; 19 | 20 | @Before 21 | public void setUp() { 22 | message = new Request(SOME_REPOSITORY, SOME_NAME, SOME_OFFSET, SOME_SIZE); 23 | } 24 | 25 | @Test 26 | public void hasType2() { 27 | assertThat(message.getType(), is(equalTo((byte) 2))); 28 | } 29 | 30 | @Test 31 | public void hasXdrEncodedFields() { 32 | XdrInputStream in = new XdrInputStream(message.getContents()); 33 | assertThat(in.readString(), is(equalTo(SOME_REPOSITORY))); 34 | assertThat(in.readString(), is(equalTo(SOME_NAME))); 35 | assertThat(in.readLong(), is(equalTo(SOME_OFFSET))); 36 | assertThat(in.readInteger(), is(equalTo(SOME_SIZE))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/pulse/bep/v1/ResponseTests.java: -------------------------------------------------------------------------------- 1 | package pulse.bep.v1; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import pulse.bep.util.XdrInputStream; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class ResponseTests { 12 | 13 | private static final byte[] SOME_DATA = new byte[]{ 1, 2, 3, 4}; 14 | 15 | private Response response; 16 | private Request request; 17 | 18 | @Before 19 | public void setUp() { 20 | request = new Request("", "", 0, 0); 21 | response = new Response(request, SOME_DATA); 22 | } 23 | 24 | @Test 25 | public void hasType3() { 26 | assertThat(response.getType(), is(equalTo((byte)3))); 27 | } 28 | 29 | @Test 30 | public void hasXdrEncodedData() { 31 | XdrInputStream in = new XdrInputStream(response.getContents()); 32 | assertThat(in.readData(), is(equalTo(SOME_DATA))); 33 | } 34 | 35 | @Test 36 | public void hasSameMessageIdAsRequest() { 37 | assertThat(response.getId(), is(equalTo(request.getId()))); 38 | } 39 | } 40 | --------------------------------------------------------------------------------