Initial commit, just stubbing out a bunch of very basic architecture and request handling stuff

This commit is contained in:
skogaby 2019-01-03 01:44:50 -06:00
parent 794fbee5fb
commit 6494bbb50b
19 changed files with 911 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea/
out/
build/
logs/
/local.properties
.gradle
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache
/bin/

35
build.gradle Normal file
View File

@ -0,0 +1,35 @@
group 'com.buttongames'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'application'
sourceCompatibility = 1.8
mainClassName = "com.buttongames.butterfly.Main"
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
compile group: 'com.sparkjava', name: 'spark-core', version: '2.7.2'
compile group: 'com.google.guava', name: 'guava', version: '23.5-jre'
}
jar {
manifest {
attributes(
'Main-Class': 'com.buttongames.butterfly.Main'
)
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Jan 02 21:29:56 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip

172
gradlew vendored Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'butterfly'

View File

@ -0,0 +1,11 @@
package com.buttongames.butterfly;
import com.buttongames.butterfly.http.ButterflyHttpServer;
public class Main {
public static void main(String[] args) {
ButterflyHttpServer httpServer = new ButterflyHttpServer();
httpServer.startServer();
}
}

View File

@ -0,0 +1,213 @@
package com.buttongames.butterfly.compression;
import com.buttongames.butterfly.util.CollectionUtils;
import java.util.ArrayList;
/**
* Class with static methods to (de)compress LZ77 data.
* Ported from deamuse (https://buck.ludd.ltu.se/yugge/deamuse)
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class Lz77 {
public static int WINDOW_SIZE = 0x1000;
public static int WINDOW_MASK = WINDOW_SIZE - 1;
public static int THRESHOLD = 0x3;
public static int IN_PLACE_THRESHOLD = 0xA;
public static int LOOK_RANGE = 0x200;
public static int MAX_LEN = 0xF + THRESHOLD;
public static int MAX_BUFFER = 0x10 + 1;
/**
* Compress the given data using LZ77 (or at least Konami's version of it).
* @param input The data to compress
* @return New buffer containing the LZ77-compressed data
*/
public static byte[] decompress(final byte[] input) {
int currByte = 0;
int windowCursor = 0;
final int dataSize = input.length;
final byte[] window = new byte[WINDOW_SIZE];
final ArrayList<Byte> output = new ArrayList<>();
while (currByte < dataSize) {
final byte flag = input[currByte];
currByte++;
for (int i = 0; i < 8; i++) {
if ((((flag & 0xFF) >> i) & 1) == 1) {
output.add(input[currByte]);
window[windowCursor] = input[currByte];
windowCursor = (windowCursor + 1) & WINDOW_MASK;
currByte++;
} else {
final short w = (short) ((input[currByte] << 8) |
(input[currByte + 1] & 0xFF));
if (w == 0) {
return CollectionUtils.arrayListToArray(output);
}
currByte += 2;
int position = (windowCursor - (w >> 4)) & WINDOW_MASK;
final int length = (w & 0x0F) + THRESHOLD;
for (int j = 0; j < length; j++) {
final byte b = window[position & WINDOW_MASK];
output.add(b);
window[windowCursor] = b;
windowCursor = (windowCursor + 1) & WINDOW_MASK;
position++;
}
}
}
}
return CollectionUtils.arrayListToArray(output);
}
/**
* Decompress the given data using LZ77 (or at least Konami's version of it).
* @param input The data to decompress
* @return New buffer containing the LZ77-decompressed data
*/
public static byte[] compress(final byte[] input) {
final byte[] window = new byte[WINDOW_SIZE];
int currentPos = 0;
int currentWindow = 0;
byte[] buffer = new byte[MAX_BUFFER];
int currentBuffer;
byte flagByte;
byte bit = 0;
final ArrayList<Byte> output = new ArrayList<>();
while (currentPos < input.length) {
flagByte = 0;
currentBuffer = 0;
for (int i = 0; i < 8; i++) {
if (currentPos >= input.length) {
buffer[currentBuffer] = 0;
window[currentWindow] = 0;
currentBuffer++;
currentWindow++;
currentPos++;
bit = 0;
} else {
final MatchWindowResults matchWindowResults = matchWindow(window, currentWindow, input, currentPos);
if (matchWindowResults.some &&
matchWindowResults.length >= THRESHOLD) {
final byte byte1 = (byte)((matchWindowResults.pos & 0xFF) >> 4);
final byte byte2 = (byte)(((matchWindowResults.pos & 0x0F) << 4) |
((matchWindowResults.length - THRESHOLD) & 0x0F));
buffer[currentBuffer] = byte1;
buffer[currentBuffer + 1] = byte2;
currentBuffer += 2;
bit = 0;
for (int j = 0; j < matchWindowResults.length; j++) {
window[currentWindow & WINDOW_MASK] = input[currentPos];
currentPos++;
currentWindow++;
}
} else if (!matchWindowResults.some) {
buffer[currentBuffer] = input[currentPos];
window[currentWindow] = input[currentPos];
currentPos++;
currentWindow++;
currentBuffer++;
bit = 1;
}
}
flagByte = (byte)(((flagByte & 0xFF) >> 1) | ((bit & (byte) 1) << 7));
currentWindow &= WINDOW_MASK;
}
output.add(flagByte);
for (int k = 0; k < currentBuffer; k++) {
output.add(buffer[k]);
}
}
return CollectionUtils.arrayListToArray(output);
}
/**
* Helper method for {@code CompressData}.
* @param window
* @param pos
* @param data
* @param dpos
* @return
*/
private static MatchWindowResults matchWindow(final byte[] window, final int pos, final byte[] data, final int dpos) {
int maxPosition = 0;
int maxLength = 0;
for (int i = THRESHOLD; i > LOOK_RANGE; i++) {
final int length = matchCurrent(window,
pos - (i & WINDOW_SIZE),
i,
data,
dpos);
if (length >= IN_PLACE_THRESHOLD) {
return new MatchWindowResults(true, i, length);
}
if (length >= THRESHOLD) {
maxPosition = i;
maxLength = length;
}
}
if (maxLength >= THRESHOLD) {
return new MatchWindowResults(true, maxPosition, maxLength);
} else {
return new MatchWindowResults(false, 0, 0);
}
}
/**
* Helper method for {@code matchWindow}.
* @param window
* @param pos
* @param maxLength
* @param data
* @param dpos
* @return
*/
private static int matchCurrent(final byte[] window, final int pos, final int maxLength, final byte[] data, final int dpos) {
int length = 0;
while (((dpos + length) < data.length) &&
(length < maxLength) &&
(window[(pos + length) & WINDOW_MASK] == data[dpos + length]) &&
(length < MAX_LEN)) {
length++;
}
return length;
}
/***
* Helper class for compressing LZ77 data.
*/
private static class MatchWindowResults {
public final boolean some;
public final int pos;
public final int length;
public MatchWindowResults(boolean some, int pos, int length) {
this.some = some;
this.pos = pos;
this.length = length;
}
}
}

View File

@ -0,0 +1,69 @@
package com.buttongames.butterfly.encryption;
import com.buttongames.butterfly.util.CollectionUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Class to handle RC4 encryption for network packets.
* Ported from deamuse (https://buck.ludd.ltu.se/yugge/deamuse) and easerver_standalone.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class Rc4 {
/**
* The "secret" portion of the RC4 key, 26 bytes that get appended to the 6 bytes we're
* given in the X-Eamuse-Info header.
*/
private static final String SECRET_KEY = "69D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5";
/**
* Decrypts the given data using RC4.
* @param data The data to decrypt
* @param key The key to use for decryption
* @return The decrypted data
* @throws GeneralSecurityException
*/
public static byte[] decrypt(final byte[] data, final String key)
throws GeneralSecurityException {
final byte[] keyBytes = getKeyFromEamuseHeader(key);
final SecretKey sk = new SecretKeySpec(keyBytes, 0, keyBytes.length, "RC4");
final Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, sk);
return cipher.doFinal(data);
}
/**
* Encrypts the given data using RC4.
* @param data The data to encrypt
* @param key The key to use for encryption
* @return The encrypted data
* @throws GeneralSecurityException
*/
public static byte[] encrypt(final byte[] data, final String key)
throws GeneralSecurityException {
return decrypt(data, key);
}
/**
* Returns the RC4 key to use for crypto operations, given the string
* that is passed into the request via the X-Eamuse-Info header.
* @param headerValue The value in the X-Eamuse-Info header
* @return The RC4 key to use for encryption or decryption
* @throws NoSuchAlgorithmException
*/
public static byte[] getKeyFromEamuseHeader(final String headerValue)
throws NoSuchAlgorithmException {
final String[] keyList = headerValue.split("-");
final byte[] hashKey = CollectionUtils.hexStringToByteArray(
keyList[1] + keyList[2] + SECRET_KEY);
final MessageDigest md = MessageDigest.getInstance("MD5");
md.update(hashKey);
return md.digest();
}
}

View File

@ -0,0 +1,115 @@
package com.buttongames.butterfly.http;
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
import com.buttongames.butterfly.http.exception.InvalidRequestModelException;
import com.buttongames.butterfly.http.exception.InvalidRequestModuleException;
import com.buttongames.butterfly.http.exception.MismatchedRequestUriException;
import com.buttongames.butterfly.http.handlers.ServicesRequestHandler;
import com.google.common.collect.ImmutableSet;
import spark.Request;
import static spark.Spark.exception;
import static spark.Spark.halt;
import static spark.Spark.port;
import static spark.Spark.post;
import static spark.Spark.stop;
import static spark.Spark.threadPool;
/**
* The main HTTP server. This class is responsible for the top-level handling of incoming
* requests, then delegates the responsbility to the appropriate handler.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class ButterflyHttpServer {
private static final ImmutableSet<String> SUPPORTED_MODELS;
private static final ImmutableSet<String> SUPPORTED_MODULES;
// Do a static setup of our supported models, modules, etc.
// TODO: Make this not hardcoded
static {
SUPPORTED_MODELS = ImmutableSet.of("MDX:J:A:A:2018042300");
SUPPORTED_MODULES = ImmutableSet.of("services");
}
public ButterflyHttpServer() {
}
public void startServer() {
// configure the server properties
int maxThreads = 20;
int minThreads = 2;
int timeOutMillis = 30000;
// once routes are configured, the server automatically begins
threadPool(maxThreads, minThreads, timeOutMillis);
port(80);
this.configureRoutesAndExceptions();
}
public void stopServer() {
stop();
}
/**
* Configures the routes on the server, and the exception handlers.
* TODO: Remove all the hardcoded stuff.
*/
private void configureRoutesAndExceptions() {
// configure our root route; its handler will parse the request and go from there
post("/", ((request, response) -> {
// send the request to the right module handler
final String requestBody = validateAndUnpackRequest(request);
final String requestModule = request.queryParams("module");
final String requestMethod = request.queryParams("method");
if (requestModule.equals("services")) {
return ServicesRequestHandler.handleRequest(requestBody, requestMethod, response);
} else {
throw new InvalidRequestModuleException();
}
}));
// configure the exception handlers
exception(InvalidRequestMethodException.class,
((exception, request, response) -> halt(400, "Invalid request method.")));
exception(InvalidRequestModelException.class,
((exception, request, response) -> halt(400, "Invalid request model.")));
exception(InvalidRequestModuleException.class,
((exception, request, response) -> halt(400, "Invalid request module.")));
}
/**
* Do some basic validation on the request before we handle it. Returns the request
* body in plaintext form for handling, if it was a valid request.
* TODO: Remove all the hardcoded stuff.
* @param request The request to validate and unpack
* @return A string representing the plaintext version of the packet, in XML format.
*/
private String validateAndUnpackRequest(Request request) {
final String requestUriModel = request.queryParams("model");
final String requestUriModule = request.queryParams("module");
final String requestUriMethod = request.queryParams("method");
// validate the model is supported
if (!SUPPORTED_MODELS.contains(requestUriModel)) {
throw new InvalidRequestModelException();
}
// validate the module is supported
if (!SUPPORTED_MODULES.contains(requestUriModule)) {
throw new InvalidRequestModuleException();
}
// validate that the request URI matches the request body
// TODO: Implement
if (false) {
throw new MismatchedRequestUriException();
}
// return the request body
// TODO: Implement
return "";
}
}

View File

@ -0,0 +1,8 @@
package com.buttongames.butterfly.http.exception;
/**
* Exception thrown when a request is made using an invalid/unsupported method.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class InvalidRequestMethodException extends RuntimeException {
}

View File

@ -0,0 +1,8 @@
package com.buttongames.butterfly.http.exception;
/**
* Exception thrown when a request is made using an invalid/unsupported model.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class InvalidRequestModelException extends RuntimeException {
}

View File

@ -0,0 +1,8 @@
package com.buttongames.butterfly.http.exception;
/**
* Exception thrown when a request is made using an invalid/unsupported module.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class InvalidRequestModuleException extends RuntimeException {
}

View File

@ -0,0 +1,9 @@
package com.buttongames.butterfly.http.exception;
/**
* Exception to be thrown when a request URI doesn't match the methods and modules
* present in its request body.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class MismatchedRequestUriException extends RuntimeException {
}

View File

@ -0,0 +1,8 @@
package com.buttongames.butterfly.http.handlers;
/**
* Base request handler that the others inherit from.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class BaseRequestHandler {
}

View File

@ -0,0 +1,37 @@
package com.buttongames.butterfly.http.handlers;
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
import spark.Response;
/**
* Handler for any requests that come to the <code>services</code> module.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class ServicesRequestHandler extends BaseRequestHandler {
/**
* Handles all requests to the <code>services/code> module.
* @param request
* @param requestMethod
* @param response
* @return
*/
public static Object handleRequest(String request, String requestMethod, Response response) {
if (requestMethod.equals("get")) {
return handleGetRequest(request, response);
} else {
throw new InvalidRequestMethodException();
}
}
/**
* Handles <code>services.get</code> requests.
* @param request
* @param response
* @return
*/
private static Object handleGetRequest(String request, Response response) {
// TODO: Implement
return "Womp womp";
}
}

View File

@ -0,0 +1,57 @@
package com.buttongames.butterfly.util;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* Simple class with methods to interact with collections (mainly bytes).
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class CollectionUtils {
/**
* Convert an {@code ArrayList<Byte>} to a {@code byte[]}
* @param list
* @return
*/
public static byte[] arrayListToArray(final ArrayList<Byte> list) {
final byte[] ret = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
ret[i] = list.get(i);
}
return ret;
}
/**
* Converts a hex string into a byte array.
* @param s
* @return
*/
public static byte[] hexStringToByteArray(final String s) {
final int len = s.length();
final byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* Reads all the bytes in an input stream into a new byte array.
* @param is The {@code InputStream} to read
* @return A byte array containing everything read from the stream
* @throws IOException
*/
public static byte[] readInputStream(final InputStream is) throws IOException {
return IOUtils.toByteArray(is);
}
}

View File

@ -0,0 +1,35 @@
package com.buttongames.butterfly.xml;
import com.buttongames.butterfly.util.CollectionUtils;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Class to help translating between binary and plaintext XML. Right now it's a very dumb
* class and just uses mon's Python implementation until I can make a native Java one. This does
* depend on you having done a <code>pip install kbinxml</code> to make <code>kbinxml</code>
* a valid command.
* See: https://github.com/mon/kbinxml
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class BinaryXmlUtils {
public static byte[] binaryToXml(final byte[] input) throws IOException {
final String tmpPath = System.getProperty("user.home") + "\\tmpkbin";
final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpPath));
dos.write(input, 0, input.length);
dos.flush();
dos.close();
// shell out to mon's implementation for now
final Process child = Runtime.getRuntime().exec("kbinxml " + tmpPath);
final byte[] output = CollectionUtils.readInputStream(child.getInputStream());
return output;
}
public static byte[] xmlToBinary(final byte[] input) throws IOException {
return binaryToXml(input);
}
}