From 352222e0684a3f9d629f76597453452e85b0cc80 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 00:57:09 -0800 Subject: [PATCH 01/24] Initial project --- proxy/.idea/.gitignore | 3 +++ proxy/.idea/description.html | 1 + proxy/.idea/encodings.xml | 6 ++++++ proxy/.idea/misc.xml | 12 ++++++++++++ proxy/.idea/modules.xml | 8 ++++++++ proxy/.idea/project-template.xml | 3 +++ proxy/.idea/vcs.xml | 6 ++++++ proxy/proxy.iml | 11 +++++++++++ proxy/src/com/trimlighthacking/Main.java | 8 ++++++++ 9 files changed, 58 insertions(+) create mode 100644 proxy/.idea/.gitignore create mode 100644 proxy/.idea/description.html create mode 100644 proxy/.idea/encodings.xml create mode 100644 proxy/.idea/misc.xml create mode 100644 proxy/.idea/modules.xml create mode 100644 proxy/.idea/project-template.xml create mode 100644 proxy/.idea/vcs.xml create mode 100644 proxy/proxy.iml create mode 100644 proxy/src/com/trimlighthacking/Main.java diff --git a/proxy/.idea/.gitignore b/proxy/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/proxy/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/proxy/.idea/description.html b/proxy/.idea/description.html new file mode 100644 index 0000000..db5f129 --- /dev/null +++ b/proxy/.idea/description.html @@ -0,0 +1 @@ +Simple Java application that includes a class with main() method \ No newline at end of file diff --git a/proxy/.idea/encodings.xml b/proxy/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/proxy/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/proxy/.idea/misc.xml b/proxy/.idea/misc.xml new file mode 100644 index 0000000..2fc6c34 --- /dev/null +++ b/proxy/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/proxy/.idea/modules.xml b/proxy/.idea/modules.xml new file mode 100644 index 0000000..0ffafee --- /dev/null +++ b/proxy/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/proxy/.idea/project-template.xml b/proxy/.idea/project-template.xml new file mode 100644 index 0000000..1f08b88 --- /dev/null +++ b/proxy/.idea/project-template.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/proxy/.idea/vcs.xml b/proxy/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/proxy/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/proxy/proxy.iml b/proxy/proxy.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/proxy/proxy.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/proxy/src/com/trimlighthacking/Main.java b/proxy/src/com/trimlighthacking/Main.java new file mode 100644 index 0000000..0ffe611 --- /dev/null +++ b/proxy/src/com/trimlighthacking/Main.java @@ -0,0 +1,8 @@ +package com.trimlighthacking; + +public class Main { + + public static void main(String[] args) { + // write your code here + } +} From db999a7446666a80f4b1fa6e8d610377d941382f Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 01:05:09 -0800 Subject: [PATCH 02/24] Ignore out directory --- .gitignore | 1 + proxy/src/com/trimlighthacking/Main.java | 1 + 2 files changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15fbdb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/proxy/out/ diff --git a/proxy/src/com/trimlighthacking/Main.java b/proxy/src/com/trimlighthacking/Main.java index 0ffe611..bec34bb 100644 --- a/proxy/src/com/trimlighthacking/Main.java +++ b/proxy/src/com/trimlighthacking/Main.java @@ -4,5 +4,6 @@ public class Main { public static void main(String[] args) { // write your code here + System.out.println("Hello world"); } } From 28d2adf1cecb8e4d7b22b5a92887e72da1de87b0 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 11:38:33 -0800 Subject: [PATCH 03/24] Initial proxy server implementation from example --- .gitignore | 1 + proxy/src/com/trimlighthacking/Server.java | 556 ++++++++++++++++++ .../TrimlightProxyServer.java | 152 +++++ 3 files changed, 709 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/Server.java create mode 100644 proxy/src/com/trimlighthacking/TrimlightProxyServer.java diff --git a/.gitignore b/.gitignore index 15fbdb6..301c82a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /proxy/out/ +/proxy/.idea diff --git a/proxy/src/com/trimlighthacking/Server.java b/proxy/src/com/trimlighthacking/Server.java new file mode 100644 index 0000000..c725202 --- /dev/null +++ b/proxy/src/com/trimlighthacking/Server.java @@ -0,0 +1,556 @@ +package com.trimlighthacking; + +import java.io.*; +import java.net.*; +import java.util.*; + +public class Server { + // This is the state for the server + Map services; // Hashtable mapping ports to Listeners + Set connections; // The set of current connections + int maxConnections; // The concurrent connection limit + ThreadGroup threadGroup; // The threadgroup for all our threads + PrintWriter logStream; // Where we send our logging output to + + /** + * This is the Server() constructor. It must be passed a stream + * to send log output to (may be null), and the limit on the number of + * concurrent connections. + **/ + public Server(OutputStream logStream, int maxConnections) { + setLogStream(logStream); + log("Starting server"); + threadGroup = new ThreadGroup(Server.class.getName()); + this.maxConnections = maxConnections; + services = new HashMap(); + connections = new HashSet(maxConnections); + } + + /** + * A main() method for running the server as a standalone program. The + * command-line arguments to the program should be pairs of servicenames + * and port numbers. For each pair, the program will dynamically load the + * named Service class, instantiate it, and tell the server to provide + * that Service on the specified port. The special -control argument + * should be followed by a password and port, and will start special + * server control service running on the specified port, protected by the + * specified password. + **/ + public static void main(String[] args) { + try { + if (args.length < 2) { // Check number of arguments + throw new IllegalArgumentException("Must specify a service"); + } + + // Create a Server object that uses standard out as its log and + // has a limit of ten concurrent connections at once. + Server s = new Server(System.out, 10); + + // Parse the argument list + int i = 0; + + while (i < args.length) { + if (args[i].equals("-control")) { // Handle the -control arg + i++; + + String password = args[i++]; + int port = Integer.parseInt(args[i++]); + // add control service + s.addService(new Control(s, password), port); + } else { + // Otherwise start a named service on the specified port. + // Dynamically load and instantiate a Service class + String serviceName = args[i++]; + Class serviceClass = Class.forName(serviceName); + Service service = (Service) serviceClass.newInstance(); + int port = Integer.parseInt(args[i++]); + s.addService(service, port); + } + } + } catch (Exception e) { // Display a message if anything goes wrong + System.err.println("Server: " + e); + System.err.println("Usage: java Server " + + "[-control ] " + + "[ ... ]"); + System.exit(1); + } + } + + /** + * A public method to set the current logging stream. Pass null + * to turn logging off + **/ + public synchronized void setLogStream(OutputStream out) { + if (out != null) { + logStream = new PrintWriter(out); + } else { + logStream = null; + } + } + + /** Write the specified string to the log */ + protected synchronized void log(String s) { + if (logStream != null) { + logStream.println("[" + new Date() + "] " + s); + logStream.flush(); + } + } + + /** Write the specified object to the log */ + protected void log(Object o) { + log(o.toString()); + } + + /** + * This method makes the server start providing a new service. + * It runs the specified Service object on the specified port. + **/ + public synchronized void addService(Service service, int port) + throws IOException { + Integer key = new Integer(port); // the hashtable key + // Check whether a service is already on that port + + if (services.get(key) != null) { + throw new IllegalArgumentException("Port " + port + + " already in use."); + } + + // Create a Listener object to listen for connections on the port + Listener listener = new Listener(threadGroup, port, service); + // Store it in the hashtable + services.put(key, listener); + // Log it + log("Starting service " + service.getClass().getName() + " on port " + + port); + // Start the listener running. + listener.start(); + } + + /** + * This method makes the server stop providing a service on a port. + * It does not terminate any pending connections to that service, merely + * causes the server to stop accepting new connections + **/ + public synchronized void removeService(int port) { + Integer key = new Integer(port); // hashtable key + // Look up the Listener object for the port in the hashtable + + final Listener listener = (Listener) services.get(key); + + if (listener == null) { + return; + } + + // Ask the listener to stop + listener.pleaseStop(); + // Remove it from the hashtable + services.remove(key); + // And log it. + log("Stopping service " + listener.service.getClass().getName() + + " on port " + port); + } + + /** + * This is the method that Listener objects call when they accept a + * connection from a client. It either creates a Connection object + * for the connection and adds it to the list of current connections, + * or, if the limit on connections has been reached, it closes the + * connection. + **/ + protected synchronized void addConnection(Socket s, Service service) { + // If the connection limit has been reached + if (connections.size() >= maxConnections) { + try { + // Then tell the client it is being rejected. + PrintWriter out = new PrintWriter(s.getOutputStream()); + out.print("Connection refused; " + + "the server is busy; please try again later.\n"); + out.flush(); + // And close the connection to the rejected client. + s.close(); + // And log it, of course + log("Connection refused to " + + s.getInetAddress().getHostAddress() + ":" + s.getPort() + + ": max connections reached."); + } catch (IOException e) { + log(e); + } + } else { // Otherwise, if the limit has not been reached + // Create a Connection thread to handle this connection + + Connection c = new Connection(s, service); + // Add it to the list of current connections + connections.add(c); + // Log this new connection + log("Connected to " + s.getInetAddress().getHostAddress() + ":" + + s.getPort() + " on port " + s.getLocalPort() + " for service " + + service.getClass().getName()); + // And start the Connection thread to provide the service + c.start(); + } + } + + /** + * A Connection thread calls this method just before it exits. It removes + * the specified Connection from the set of connections. + **/ + protected synchronized void endConnection(Connection c) { + connections.remove(c); + log("Connection to " + c.client.getInetAddress().getHostAddress() + + ":" + c.client.getPort() + " closed."); + } + + /** Change the current connection limit */ + public synchronized void setMaxConnections(int max) { + maxConnections = max; + } + + /** + * This method displays status information about the server on the + * specified stream. It can be used for debugging, and is used by the + * Control service later in this example. + **/ + public synchronized void displayStatus(PrintWriter out) { + // Display a list of all Services that are being provided + Iterator keys = services.keySet().iterator(); + + while (keys.hasNext()) { + Integer port = (Integer) keys.next(); + Listener listener = (Listener) services.get(port); + out.print("SERVICE " + listener.service.getClass().getName() + + " ON PORT " + port + "\n"); + } + + // Display the current connection limit + out.print("MAX CONNECTIONS: " + maxConnections + "\n"); + + // Display a list of all current connections + Iterator conns = connections.iterator(); + + while (conns.hasNext()) { + Connection c = (Connection) conns.next(); + out.print("CONNECTED TO " + + c.client.getInetAddress().getHostAddress() + ":" + + c.client.getPort() + " ON PORT " + c.client.getLocalPort() + + " FOR SERVICE " + c.service.getClass().getName() + "\n"); + } + } + + /** + * Here is the Service interface that we have seen so much of. It defines + * only a single method which is invoked to provide the service. serve() + * will be passed an input stream and an output stream to the client. It + * should do whatever it wants with them, and should close them before + * returning. + * + * All connections through the same port to this service share a single + * Service object. Thus, any state local to an individual connection must + * be stored in local variables within the serve() method. State that + * should be global to all connections on the same port should be stored + * in instance variables of the Service class. If the same Service is + * running on more than one port, there will typically be different + * Service instances for each port. Data that should be global to all + * connections on any port should be stored in static variables. + * + * Note that implementations of this interface must have a no-argument + * constructor if they are to be dynamically instantiated by the main() + * method of the Server class. + **/ + public interface Service { + public void serve(InputStream in, OutputStream out) + throws IOException; + } + + /** + * This nested Thread subclass is a "listener". It listens for + * connections on a specified port (using a ServerSocket) and when it gets + * a connection request, it calls the servers addConnection() method to + * accept (or reject) the connection. There is one Listener for each + * Service being provided by the Server. + **/ + public class Listener extends Thread { + ServerSocket listen_socket; // The socket to listen for connections + int port; // The port we're listening on + Service service; // The service to provide on that port + volatile boolean stop = false; // Whether we've been asked to stop + + /** + * The Listener constructor creates a thread for itself in the + * threadgroup. It creates a ServerSocket to listen for connections + * on the specified port. It arranges for the ServerSocket to be + * interruptible, so that services can be removed from the server. + **/ + public Listener(ThreadGroup group, int port, Service service) + throws IOException { + super(group, "Listener:" + port); + listen_socket = new ServerSocket(port); + // give it a non-zero timeout so accept() can be interrupted + listen_socket.setSoTimeout(600000); + this.port = port; + this.service = service; + } + + /** + * This is the polite way to get a Listener to stop accepting + * connections + ***/ + public void pleaseStop() { + this.stop = true; // Set the stop flag + this.interrupt(); // Stop blocking in accept() + + try { + listen_socket.close(); + } // Stop listening. + catch (IOException e) { + } + } + + /** + * A Listener is a Thread, and this is its body. + * Wait for connection requests, accept them, and pass the socket on + * to the addConnection method of the server. + **/ + public void run() { + while (!stop) { // loop until we're asked to stop. + + try { + Socket client = listen_socket.accept(); + addConnection(client, service); + } catch (InterruptedIOException e) { + } catch (IOException e) { + log(e); + } + } + } + } + + /** + * This class is a subclass of Thread that handles an individual + * connection between a client and a Service provided by this server. + * Because each such connection has a thread of its own, each Service can + * have multiple connections pending at once. Despite all the other + * threads in use, this is the key feature that makes this a + * multi-threaded server implementation. + **/ + public class Connection extends Thread { + Socket client; // The socket to talk to the client through + Service service; // The service being provided to that client + + /** + * This constructor just saves some state and calls the superclass + * constructor to create a thread to handle the connection. Connection + * objects are created by Listener threads. These threads are part of + * the server's ThreadGroup, so all Connection threads are part of that + * group, too. + **/ + public Connection(Socket client, Service service) { + super("Server.Connection:" + + client.getInetAddress().getHostAddress() + ":" + + client.getPort()); + this.client = client; + this.service = service; + } + + /** + * This is the body of each and every Connection thread. + * All it does is pass the client input and output streams to the + * serve() method of the specified Service object. That method is + * responsible for reading from and writing to those streams to + * provide the actual service. Recall that the Service object has + * been passed from the Server.addService() method to a Listener + * object to the addConnection() method to this Connection object, and + * is now finally being used to provide the service. Note that just + * before this thread exits it always calls the endConnection() method + * to remove itself from the set of connections + **/ + public void run() { + try { + InputStream in = client.getInputStream(); + OutputStream out = client.getOutputStream(); + service.serve(in, out); + } catch (IOException e) { + log(e); + } finally { + endConnection(this); + } + } + } + + /** + * This is a non-trivial service. It implements a command-based protocol + * that gives password-protected runtime control over the operation of the + * server. See the main() method of the Server class to see how this + * service is started. + * + * The recognized commands are: + * password: give password; authorization is required for most commands + * add: dynamically add a named service on a specified port + * remove: dynamically remove the service running on a specified port + * max: change the current maximum connection limit. + * status: display current services, connections, and connection limit + * help: display a help message + * quit: disconnect + * + * This service displays a prompt, and sends all of its output to the user + * in capital letters. Only one client is allowed to connect to this + * service at a time. + **/ + public static class Control implements Service { + Server server; // The server we control + String password; // The password we require + boolean connected = false; // Whether a client is already connected + + /** + * Create a new Control service. It will control the specified Server + * object, and will require the specified password for authorization + * Note that this Service does not have a no argument constructor, + * which means that it cannot be dynamically instantiated and added as + * the other, generic services above can be. + **/ + public Control(Server server, String password) { + this.server = server; + this.password = password; + } + + /** + * This is the serve method that provides the service. It reads a + * line the client, and uses java.util.StringTokenizer to parse it + * into commands and arguments. It does various things depending on + * the command. + **/ + public void serve(InputStream i, OutputStream o) + throws IOException { + // Setup the streams + BufferedReader in = new BufferedReader(new InputStreamReader(i)); + PrintWriter out = new PrintWriter(o); + String line; // For reading client input lines + // Has the user has given the password yet? + + boolean authorized = false; + + // If there is already a client connected to this service, display + // a message to this client and close the connection. We use a + // synchronized block to prevent a race condition. + synchronized (this) { + if (connected) { + out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n"); + out.close(); + + return; + } else { + connected = true; + } + } + + // This is the main loop: read a command, parse it, and handle it + for (;;) { // infinite loop + out.print("> "); // Display a prompt + out.flush(); // Make it appear right away + line = in.readLine(); // Get the user's input + + if (line == null) { + break; // Quit if we get EOF. + } + + try { + // Use a StringTokenizer to parse the user's command + StringTokenizer t = new StringTokenizer(line); + + if (!t.hasMoreTokens()) { + continue; // if input was empty + // Get first word of the input and convert to lower case + } + + String command = t.nextToken().toLowerCase(); + + // Now compare to each of the possible commands, doing the + // appropriate thing for each command + if (command.equals("password")) { // Password command + + String p = t.nextToken(); // Get the next word + + if (p.equals(this.password)) { // Is it the password? + out.print("OK\n"); // Say so + authorized = true; // Grant authorization + } else { + out.print("INVALID PASSWORD\n"); // Otherwise fail + } + } else if (command.equals("add")) { // Add Service command + // Check whether password has been given + + if (!authorized) { + out.print("PASSWORD REQUIRED\n"); + } else { + // Get the name of the service and try to + // dynamically load and instantiate it. + // Exceptions will be handled below + String serviceName = t.nextToken(); + Class serviceClass = Class.forName(serviceName); + Service service; + + try { + service = (Service) serviceClass.newInstance(); + } catch (NoSuchMethodError e) { + throw new IllegalArgumentException( + "Service must have a " + + "no-argument constructor"); + } + + int port = Integer.parseInt(t.nextToken()); + // If no exceptions occurred, add the service + server.addService(service, port); + out.print("SERVICE ADDED\n"); // acknowledge + } + } else if (command.equals("remove")) { // Remove service + + if (!authorized) { + out.print("PASSWORD REQUIRED\n"); + } else { + int port = Integer.parseInt(t.nextToken()); + server.removeService(port); // remove the service + out.print("SERVICE REMOVED\n"); // acknowledge + } + } else if (command.equals("max")) { // Set connection limit + + if (!authorized) { + out.print("PASSWORD REQUIRED\n"); + } else { + int max = Integer.parseInt(t.nextToken()); + server.setMaxConnections(max); + out.print("MAX CONNECTIONS CHANGED\n"); + } + } else if (command.equals("status")) { // Status Display + + if (!authorized) { + out.print("PASSWORD REQUIRED\n"); + } else { + server.displayStatus(out); + } + } else if (command.equals("help")) { // Help command + // Display command syntax. Password not required + out.print("COMMANDS:\n" + "\tpassword \n" + + "\tadd \n" + "\tremove \n" + + "\tmax \n" + "\tstatus\n" + + "\thelp\n" + "\tquit\n"); + } else if (command.equals("quit")) { + break; // Quit command. + } else { + out.print("UNRECOGNIZED COMMAND\n"); // Error + } + } catch (Exception e) { + // If an exception occurred during the command, print an + // error message, then output details of the exception. + out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" + + e + "\n"); + } + } + + // Finally, when the loop command loop ends, close the streams + // and set our connected flag to false so that other clients can + // now connect. + connected = false; + out.close(); + in.close(); + } + } +} \ No newline at end of file diff --git a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java new file mode 100644 index 0000000..ff9c2d6 --- /dev/null +++ b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java @@ -0,0 +1,152 @@ +package com.trimlighthacking;// See https://resources.oreilly.com/examples/9781565923713/blob/master/ProxyServer.java +// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com) +// Copyright (c) 1997 by David Flanagan +// This example is provided WITHOUT ANY WARRANTY either expressed or implied. +// You may study, use, modify, and distribute it for non-commercial purposes. +// For any commercial use, see http://www.davidflanagan.com/javaexamples + +import java.io.*; +import java.net.*; + +/** + * This class uses the Server class to provide a multi-threaded server + * framework for a relatively simple proxy service. The main() method + * starts up the server. The nested Proxy class implements the + * Server.Service interface and provides the proxy service. + **/ +//public class TrimlightProxyServer { + /** + * Create a Server object, and add Proxy service objects to it to provide + * proxy service as specified by the command-line arguments. + **/ +/* public static void main(String[] args) { + try { + // Check number of args. Must be a multiple of 3 and > 0. + if ((args.length == 0) || (args.length % 3 != 0)) + throw new IllegalArgumentException("Wrong number of arguments"); + + // Create the Server object + Server s = new Server(null, 12); // log stream, max connections + + // Loop through the arguments parsing (host, remoteport, localport) + // tuples. Create an appropriate Proxy object, and add it to the server + int i = 0; + while(i < args.length) { + String host = args[i++]; + int remoteport = Integer.parseInt(args[i++]); + int localport = Integer.parseInt(args[i++]); + s.addService(new Proxy(host, remoteport), localport); + } + } + catch (Exception e) { // Print an error message if anything goes wrong. + System.err.println(e); + System.err.println("Usage: java ProxyServer " + + " ..."); + System.exit(1); + } + }/* + + /** + * This is the class that implements the proxy service. The serve() method + * will be called when the client has connected. At that point, it must + * establish a connection to the server, and then transfer bytes back and + * forth between client and server. For symmetry, this class implements + * two very similar threads as anonymous classes. One thread copies bytes + * from client to server, and the other copies them from server to client. + * The thread that invoke the serve() method creates and starts these + * threads, then just sits and waits for them to exit. + **/ + public class TrimlightProxyServer implements Server.Service { + String host; + int port; + + /** Remember the host and port we are a proxy for */ + public TrimlightProxyServer (String host, int port) { + this.host = host; + this.port = port; + } + + /** The server invokes this method when a client connects. */ + public void serve(InputStream in, OutputStream out) { + // These are some sockets we'll use. They are final so they can be used + // by the anonymous classes defined below. + final InputStream from_client = in; + final OutputStream to_client = out; + final InputStream from_server; + final OutputStream to_server; + + // Try to establish a connection to the specified server and port + // and get sockets to talk to it. Tell our client if we fail. + Socket server; + try { + server = new Socket(host, port); + from_server = server.getInputStream(); + to_server = server.getOutputStream(); + } + catch (Exception e) { + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); + pw.println("Proxy server could not connect to " + host + ":" + port); + pw.flush(); + pw.close(); + try { in.close(); } catch (IOException ex) {} + return; + } + + // Create an array to hold two Threads. It is declared final so that + // it can be used by the anonymous classes below. We use an array + // instead of two variables because given the structure of this program + // two variables would not work if declared final. + final Thread[] threads = new Thread[2]; + + // Define and create a thread to transmit bytes from client to server + Thread c2s = new Thread() { + public void run() { + byte[] buffer = new byte[2048]; + int bytes_read; + try { + while((bytes_read = from_client.read(buffer)) != -1) { + to_server.write(buffer, 0, bytes_read); + to_server.flush(); + } + } + catch (IOException e) {} + + // if the client closed its stream to us, we close our stream + // to the server. First, stop the other thread + threads[1].stop(); + try { to_server.close(); } catch (IOException e) {} + } + }; + + // Define and create a thread to copy bytes from server to client. + Thread s2c = new Thread() { + public void run() { + byte[] buffer = new byte[2048]; + int bytes_read; + try { + while((bytes_read = from_server.read(buffer)) != -1) { + to_client.write(buffer, 0, bytes_read); + to_client.flush(); + } + } + catch (IOException e) {} + + // if the server closed its stream to us, we close our stream + // to the client. First, stop the other thread, though. + threads[0].stop(); + try { to_client.close(); } catch (IOException e) {} + } + }; + + // Store the threads into the final threads[] array, so that the + // anonymous classes can refer to each other. + threads[0] = c2s; threads[1] = s2c; + + // start the threads + c2s.start(); s2c.start(); + + // Wait for them to exit + try { c2s.join(); s2c.join(); } catch (InterruptedException e) {} + } + } + From 4bce5e1ff0234f7960c2a8ae2036684e77443247 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 12:04:55 -0800 Subject: [PATCH 04/24] test proxy --- proxy/src/com/trimlighthacking/Main.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proxy/src/com/trimlighthacking/Main.java b/proxy/src/com/trimlighthacking/Main.java index bec34bb..1129008 100644 --- a/proxy/src/com/trimlighthacking/Main.java +++ b/proxy/src/com/trimlighthacking/Main.java @@ -1,9 +1,20 @@ package com.trimlighthacking; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + public class Main { public static void main(String[] args) { // write your code here System.out.println("Hello world"); + + Server s = new Server(System.out, 12); // log stream, max connections + try { + s.addService(new TrimlightProxyServer("192.168.254.51", 22), 20022); + } catch (IOException e) { + e.printStackTrace(); + } } } From f27819796bc651fe831be44fa0aab3e8c283e316 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 12:12:26 -0800 Subject: [PATCH 05/24] Add attribution --- proxy/src/com/trimlighthacking/Server.java | 1 + proxy/src/com/trimlighthacking/TrimlightProxyServer.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/Server.java b/proxy/src/com/trimlighthacking/Server.java index c725202..a95e70d 100644 --- a/proxy/src/com/trimlighthacking/Server.java +++ b/proxy/src/com/trimlighthacking/Server.java @@ -1,3 +1,4 @@ +// See http://technojeeves.com/index.php/48-server-by-david-flanagan package com.trimlighthacking; import java.io.*; diff --git a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java index ff9c2d6..6826dc5 100644 --- a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java +++ b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java @@ -1,4 +1,4 @@ -package com.trimlighthacking;// See https://resources.oreilly.com/examples/9781565923713/blob/master/ProxyServer.java +package com.trimlighthacking; // See https://resources.oreilly.com/examples/9781565923713/blob/master/ProxyServer.java // This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com) // Copyright (c) 1997 by David Flanagan // This example is provided WITHOUT ANY WARRANTY either expressed or implied. From 0d1de199abd292e2c96e90be41935658e1913549 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Wed, 30 Dec 2020 23:09:18 -0800 Subject: [PATCH 06/24] Add simple BytePrinter --- .../trimlighthacking/ByteArrayFormatter.java | 27 +++++++++++++++++++ proxy/src/com/trimlighthacking/Main.java | 3 +-- .../TrimlightProxyServer.java | 9 +++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 proxy/src/com/trimlighthacking/ByteArrayFormatter.java diff --git a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java new file mode 100644 index 0000000..3783a53 --- /dev/null +++ b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java @@ -0,0 +1,27 @@ +package com.trimlighthacking; + +public class ByteArrayFormatter { + public static String format(byte[] bytes, int count) { + StringBuilder out = new StringBuilder(bytes.length * 3); + if(count > bytes.length) { + throw new IllegalArgumentException(String.format("Count %d is greater than buffer length %d", count, bytes.length)); + } + for(int i = 0; i < count; i++) { + byte b = bytes[i]; + if((b & 0xFF) < 32) { + out.append(String.format("^%s ", (char)((b & 0xFF) + 64))); + } else if((b & 0xFF) < 128) { + out.append(String.format(" %s ", (char)(b))); + } + else { + out.append(" "); + } + + } + out.append("\n "); + for(int i = 0; i < count; i++) { + out.append(String.format("%02X ", bytes[i])); + } + return out.toString(); + } +} diff --git a/proxy/src/com/trimlighthacking/Main.java b/proxy/src/com/trimlighthacking/Main.java index 1129008..79c6322 100644 --- a/proxy/src/com/trimlighthacking/Main.java +++ b/proxy/src/com/trimlighthacking/Main.java @@ -7,8 +7,7 @@ public class Main { public static void main(String[] args) { - // write your code here - System.out.println("Hello world"); + System.out.println("TrimLight Proxy v0.000001"); Server s = new Server(System.out, 12); // log stream, max connections try { diff --git a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java index 6826dc5..5f5b6f7 100644 --- a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java +++ b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java @@ -7,6 +7,7 @@ import java.io.*; import java.net.*; +import java.util.Date; /** * This class uses the Server class to provide a multi-threaded server @@ -66,6 +67,10 @@ public TrimlightProxyServer (String host, int port) { this.port = port; } + public void log(String s) { + System.out.printf("[%tc] %s\n", new Date(), s); + } + /** The server invokes this method when a client connects. */ public void serve(InputStream in, OutputStream out) { // These are some sockets we'll use. They are final so they can be used @@ -105,6 +110,8 @@ public void run() { int bytes_read; try { while((bytes_read = from_client.read(buffer)) != -1) { + String formattedByteArray = ByteArrayFormatter.format(buffer, bytes_read); + log(String.format("client->server %s", formattedByteArray)); to_server.write(buffer, 0, bytes_read); to_server.flush(); } @@ -125,6 +132,8 @@ public void run() { int bytes_read; try { while((bytes_read = from_server.read(buffer)) != -1) { + String formattedByteArray = ByteArrayFormatter.format(buffer, bytes_read); + log(String.format("server->client %s", formattedByteArray)); to_client.write(buffer, 0, bytes_read); to_client.flush(); } From 7ff6fceafbf9e0f5a2d384556fa702235ae9fba8 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 00:58:08 -0800 Subject: [PATCH 07/24] Proxy trimlight rather than ssh --- proxy/src/com/trimlighthacking/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/Main.java b/proxy/src/com/trimlighthacking/Main.java index 79c6322..f342199 100644 --- a/proxy/src/com/trimlighthacking/Main.java +++ b/proxy/src/com/trimlighthacking/Main.java @@ -11,7 +11,7 @@ public static void main(String[] args) { Server s = new Server(System.out, 12); // log stream, max connections try { - s.addService(new TrimlightProxyServer("192.168.254.51", 22), 20022); + s.addService(new TrimlightProxyServer("192.168.254.50", 8189), 8189); } catch (IOException e) { e.printStackTrace(); } From 1a3768d9e362d244bcadd77309bdadb32eecb0e3 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 00:58:24 -0800 Subject: [PATCH 08/24] Add interface --- proxy/src/com/trimlighthacking/TrimlightInterpretable.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/TrimlightInterpretable.java diff --git a/proxy/src/com/trimlighthacking/TrimlightInterpretable.java b/proxy/src/com/trimlighthacking/TrimlightInterpretable.java new file mode 100644 index 0000000..f935f88 --- /dev/null +++ b/proxy/src/com/trimlighthacking/TrimlightInterpretable.java @@ -0,0 +1,7 @@ +package com.trimlighthacking; + +public interface TrimlightInterpretable { + String name = null; + byte[] rawBytes = new byte[0]; + String toString(); +} From 42353bedb34a57bc683baae16c74567406364fba Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 00:59:56 -0800 Subject: [PATCH 09/24] Skeleton classes --- proxy/src/com/trimlighthacking/Request.java | 4 ++++ proxy/src/com/trimlighthacking/Response.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/Request.java create mode 100644 proxy/src/com/trimlighthacking/Response.java diff --git a/proxy/src/com/trimlighthacking/Request.java b/proxy/src/com/trimlighthacking/Request.java new file mode 100644 index 0000000..0808c45 --- /dev/null +++ b/proxy/src/com/trimlighthacking/Request.java @@ -0,0 +1,4 @@ +package com.trimlighthacking; + +public abstract class Request implements TrimlightInterpretable { +} diff --git a/proxy/src/com/trimlighthacking/Response.java b/proxy/src/com/trimlighthacking/Response.java new file mode 100644 index 0000000..4e09a6c --- /dev/null +++ b/proxy/src/com/trimlighthacking/Response.java @@ -0,0 +1,4 @@ +package com.trimlighthacking; + +public abstract class Response implements TrimlightInterpretable { +} From f5415c32da0d57c68dd592174814825f8b72dae6 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 01:01:09 -0800 Subject: [PATCH 10/24] Initial factory --- .../MalformedBufferException.java | 20 +++++++++++++++++++ .../RequestResponseFactory.java | 14 +++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/MalformedBufferException.java create mode 100644 proxy/src/com/trimlighthacking/RequestResponseFactory.java diff --git a/proxy/src/com/trimlighthacking/MalformedBufferException.java b/proxy/src/com/trimlighthacking/MalformedBufferException.java new file mode 100644 index 0000000..0daaf5e --- /dev/null +++ b/proxy/src/com/trimlighthacking/MalformedBufferException.java @@ -0,0 +1,20 @@ +package com.trimlighthacking; + +public class MalformedBufferException extends RuntimeException { + public MalformedBufferException(String message, byte[] buffer) { + super(message); + this.buffer = buffer; + } + + public MalformedBufferException(String message, Throwable cause, byte[] buffer) { + super(message, cause); + this.buffer = buffer; + } + + public MalformedBufferException(Throwable cause, byte[] buffer) { + super(cause); + this.buffer = buffer; + } + + public final byte[] buffer; +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java new file mode 100644 index 0000000..3da19b9 --- /dev/null +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -0,0 +1,14 @@ +package com.trimlighthacking; + +public class RequestResponseFactory { + public TrimlightInterpretable Create(byte[] buffer, boolean toServer) { + if(buffer.length < 3) { + throw new MalformedBufferException("Buffer should be more than three characters long", buffer); + } + if(buffer[0] != 0x5A || buffer[buffer.length - 1] != 0xA5) { + throw new MalformedBufferException("Buffer is not bookended by 0x5A and 0xA5", buffer); + } + + return null; + } +} From e172f67fb638b52f8b2646e0792974f27cca23ba Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 02:07:16 -0800 Subject: [PATCH 11/24] Use factory class to generate requests and responses --- proxy/src/com/trimlighthacking/Request.java | 3 +++ .../RequestResponseFactory.java | 25 ++++++++++++++++--- proxy/src/com/trimlighthacking/Response.java | 3 +++ .../TrimlightInterpretable.java | 2 -- .../TrimlightProxyServer.java | 9 ++++--- .../trimlighthacking/UnclassifiedRequest.java | 12 +++++++++ .../UnclassifiedResponse.java | 12 +++++++++ 7 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 proxy/src/com/trimlighthacking/UnclassifiedRequest.java create mode 100644 proxy/src/com/trimlighthacking/UnclassifiedResponse.java diff --git a/proxy/src/com/trimlighthacking/Request.java b/proxy/src/com/trimlighthacking/Request.java index 0808c45..14ae72f 100644 --- a/proxy/src/com/trimlighthacking/Request.java +++ b/proxy/src/com/trimlighthacking/Request.java @@ -1,4 +1,7 @@ package com.trimlighthacking; public abstract class Request implements TrimlightInterpretable { + protected String name = null; + protected byte[] rawBytes; + protected static final String LogPrefix = "client->server"; } diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index 3da19b9..2422444 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -1,14 +1,31 @@ package com.trimlighthacking; +import java.util.Arrays; + public class RequestResponseFactory { - public TrimlightInterpretable Create(byte[] buffer, boolean toServer) { + public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boolean toServer) { if(buffer.length < 3) { throw new MalformedBufferException("Buffer should be more than three characters long", buffer); } - if(buffer[0] != 0x5A || buffer[buffer.length - 1] != 0xA5) { - throw new MalformedBufferException("Buffer is not bookended by 0x5A and 0xA5", buffer); + + byte[] bufferSlice = Arrays.copyOfRange(buffer, 0, bytes_read); + + if((bufferSlice[0] & 0xFF) != 0x5A || (bufferSlice[bufferSlice.length - 1] & 0xFF) != 0xA5) { + throw new MalformedBufferException(String.format("Buffer is not bookended by 0x5A and 0xA5. Instead, it has %02X and %02X", bufferSlice[0], bufferSlice[bufferSlice.length -1]), bufferSlice); } - return null; + byte code = buffer[1]; + + if(toServer) { + switch (code & 0xFF) { + default: + return new UnclassifiedRequest(bufferSlice); + } + } else { + switch (code) { + default: + return new UnclassifiedResponse(bufferSlice); + } + } } } diff --git a/proxy/src/com/trimlighthacking/Response.java b/proxy/src/com/trimlighthacking/Response.java index 4e09a6c..43bd0e2 100644 --- a/proxy/src/com/trimlighthacking/Response.java +++ b/proxy/src/com/trimlighthacking/Response.java @@ -1,4 +1,7 @@ package com.trimlighthacking; public abstract class Response implements TrimlightInterpretable { + protected String name = null; + protected byte[] rawBytes; + protected static final String LogPrefix = "server->client"; } diff --git a/proxy/src/com/trimlighthacking/TrimlightInterpretable.java b/proxy/src/com/trimlighthacking/TrimlightInterpretable.java index f935f88..99d005f 100644 --- a/proxy/src/com/trimlighthacking/TrimlightInterpretable.java +++ b/proxy/src/com/trimlighthacking/TrimlightInterpretable.java @@ -1,7 +1,5 @@ package com.trimlighthacking; public interface TrimlightInterpretable { - String name = null; - byte[] rawBytes = new byte[0]; String toString(); } diff --git a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java index 5f5b6f7..faec632 100644 --- a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java +++ b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java @@ -110,8 +110,8 @@ public void run() { int bytes_read; try { while((bytes_read = from_client.read(buffer)) != -1) { - String formattedByteArray = ByteArrayFormatter.format(buffer, bytes_read); - log(String.format("client->server %s", formattedByteArray)); + TrimlightInterpretable request = RequestResponseFactory.Create(buffer, bytes_read, true); + log(request.toString()); to_server.write(buffer, 0, bytes_read); to_server.flush(); } @@ -132,8 +132,9 @@ public void run() { int bytes_read; try { while((bytes_read = from_server.read(buffer)) != -1) { - String formattedByteArray = ByteArrayFormatter.format(buffer, bytes_read); - log(String.format("server->client %s", formattedByteArray)); + TrimlightInterpretable response = RequestResponseFactory.Create(buffer, bytes_read, true); + log(response.toString()); + to_client.write(buffer, 0, bytes_read); to_client.flush(); } diff --git a/proxy/src/com/trimlighthacking/UnclassifiedRequest.java b/proxy/src/com/trimlighthacking/UnclassifiedRequest.java new file mode 100644 index 0000000..aeaa512 --- /dev/null +++ b/proxy/src/com/trimlighthacking/UnclassifiedRequest.java @@ -0,0 +1,12 @@ +package com.trimlighthacking; + +public class UnclassifiedRequest extends Request { + public String toString() { + String formattedByteArray = ByteArrayFormatter.format(this.rawBytes, this.rawBytes.length); + return String.format("%s %s", this.LogPrefix, formattedByteArray); + } + + public UnclassifiedRequest(byte[] buffer) { + this.rawBytes = buffer; + } +} diff --git a/proxy/src/com/trimlighthacking/UnclassifiedResponse.java b/proxy/src/com/trimlighthacking/UnclassifiedResponse.java new file mode 100644 index 0000000..d345f6e --- /dev/null +++ b/proxy/src/com/trimlighthacking/UnclassifiedResponse.java @@ -0,0 +1,12 @@ +package com.trimlighthacking; + +public class UnclassifiedResponse extends Response { + public String toString() { + String formattedByteArray = ByteArrayFormatter.format(this.rawBytes, this.rawBytes.length); + return String.format("%s %s", this.LogPrefix, formattedByteArray); + } + + public UnclassifiedResponse(byte[] buffer) { + this.rawBytes = buffer; + } +} From 5f7aa02981bb44a067d7efb136dcda771a6e2f52 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 03:02:04 -0800 Subject: [PATCH 12/24] ConnectionRequest --- .../trimlighthacking/ConnectionRequest.java | 49 +++++++++++++++++++ .../RequestResponseFactory.java | 2 + 2 files changed, 51 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/ConnectionRequest.java diff --git a/proxy/src/com/trimlighthacking/ConnectionRequest.java b/proxy/src/com/trimlighthacking/ConnectionRequest.java new file mode 100644 index 0000000..57dfe87 --- /dev/null +++ b/proxy/src/com/trimlighthacking/ConnectionRequest.java @@ -0,0 +1,49 @@ +package com.trimlighthacking; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class ConnectionRequest extends Request { + private final int fixed_unknown1; + private final int fixed_unknown2; + private final int var_unknown3; + private final int var_unknown4; + private final int var_unknown5; + + public ConnectionRequest(byte[] buffer) { + // We don't know what indices 2-6 are used for yet. + int year = buffer[7] & 0xff; + int month = buffer[8] & 0xff; + int day = buffer[9] & 0xff; + int hour = buffer[11] & 0xff; + int minute = buffer[12] & 0xff; + int second = buffer[13] & 0xff; + + this.fixed_unknown1 = buffer[2] & 0xff; + this.fixed_unknown2 = buffer[3] & 0xff; + this.var_unknown3 = buffer[4] & 0xff; + this.var_unknown4 = buffer[5] & 0xff; + this.var_unknown5 = buffer[6] & 0xff; + + Calendar calendar = new GregorianCalendar(); + + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + this.ConnectionStartDate = calendar; + + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public final Calendar ConnectionStartDate; + + @Override + public String toString() { + return String.format("%s Connection Started: %s Unknowns: (Fixed: %02X %02X Var: %02X %02X %02X)", this.LogPrefix, this.ConnectionStartDate.getTime().toString(), fixed_unknown1, fixed_unknown2, var_unknown3, var_unknown4, var_unknown5); + } +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index 2422444..b31886d 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -18,6 +18,8 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole if(toServer) { switch (code & 0xFF) { + case 0x0C: + return new ConnectionRequest(bufferSlice); default: return new UnclassifiedRequest(bufferSlice); } From 658dc83f222a349d3df4ee15eac33f1f587ce5d8 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 03:41:33 -0800 Subject: [PATCH 13/24] Whitespace --- proxy/src/com/trimlighthacking/ByteArrayFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java index 3783a53..7b7940f 100644 --- a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java +++ b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java @@ -18,7 +18,7 @@ public static String format(byte[] bytes, int count) { } } - out.append("\n "); + out.append("\n "); for(int i = 0; i < count; i++) { out.append(String.format("%02X ", bytes[i])); } From 39d9164e9ce2222fc9891037ddda1a740552461b Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 03:42:25 -0800 Subject: [PATCH 14/24] Formatting --- .../trimlighthacking/ConnectionRequest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/proxy/src/com/trimlighthacking/ConnectionRequest.java b/proxy/src/com/trimlighthacking/ConnectionRequest.java index 57dfe87..aa5907f 100644 --- a/proxy/src/com/trimlighthacking/ConnectionRequest.java +++ b/proxy/src/com/trimlighthacking/ConnectionRequest.java @@ -5,11 +5,11 @@ import java.util.GregorianCalendar; public class ConnectionRequest extends Request { - private final int fixed_unknown1; - private final int fixed_unknown2; - private final int var_unknown3; - private final int var_unknown4; - private final int var_unknown5; + private final int fixedUnknown1; + private final int fixedUnknown2; + private final int varUnknown1; + private final int varUnknown2; + private final int varUnknown3; public ConnectionRequest(byte[] buffer) { // We don't know what indices 2-6 are used for yet. @@ -20,11 +20,11 @@ public ConnectionRequest(byte[] buffer) { int minute = buffer[12] & 0xff; int second = buffer[13] & 0xff; - this.fixed_unknown1 = buffer[2] & 0xff; - this.fixed_unknown2 = buffer[3] & 0xff; - this.var_unknown3 = buffer[4] & 0xff; - this.var_unknown4 = buffer[5] & 0xff; - this.var_unknown5 = buffer[6] & 0xff; + this.fixedUnknown1 = buffer[2] & 0xff; + this.fixedUnknown2 = buffer[3] & 0xff; + this.varUnknown1 = buffer[4] & 0xff; + this.varUnknown2 = buffer[5] & 0xff; + this.varUnknown3 = buffer[6] & 0xff; Calendar calendar = new GregorianCalendar(); @@ -44,6 +44,6 @@ public ConnectionRequest(byte[] buffer) { @Override public String toString() { - return String.format("%s Connection Started: %s Unknowns: (Fixed: %02X %02X Var: %02X %02X %02X)", this.LogPrefix, this.ConnectionStartDate.getTime().toString(), fixed_unknown1, fixed_unknown2, var_unknown3, var_unknown4, var_unknown5); + return String.format("%s Connection Started: %s Unknowns: (Fixed: %02X %02X Var: %02X %02X %02X)", this.LogPrefix, this.ConnectionStartDate.getTime().toString(), fixedUnknown1, fixedUnknown2, varUnknown1, varUnknown2, varUnknown3); } } From b5c46ffbfa44e888c1168ded9a42739571987b5d Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 03:42:56 -0800 Subject: [PATCH 15/24] ConnectionResponse --- .../trimlighthacking/ConnectionResponse.java | 37 +++++++++++++++++++ .../RequestResponseFactory.java | 24 ++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 proxy/src/com/trimlighthacking/ConnectionResponse.java diff --git a/proxy/src/com/trimlighthacking/ConnectionResponse.java b/proxy/src/com/trimlighthacking/ConnectionResponse.java new file mode 100644 index 0000000..bf78634 --- /dev/null +++ b/proxy/src/com/trimlighthacking/ConnectionResponse.java @@ -0,0 +1,37 @@ +package com.trimlighthacking; + +import java.util.Arrays; + +public class ConnectionResponse extends Response { + + private final int varUnknown1; + private final int fixedUnknown1; + private final int fixedUnknown2; + private final int fixedUnknown3; + private final int fixedUnknown4; + private final int fixedUnknown5; + + public ConnectionResponse(byte[] buffer) { + + this.varUnknown1 = buffer[1] & 0xff; + this.fixedUnknown1 = buffer[2] & 0xff; + int hostnameLength = buffer[3] & 0xff; + int hostnameEnd = 4 + hostnameLength; + byte[] hostname = Arrays.copyOfRange(buffer, 4, hostnameEnd); + this.HostName = new String(hostname); + this.fixedUnknown2 = buffer[hostnameEnd] & 0xff; + this.fixedUnknown3 = buffer[hostnameEnd + 1] & 0xff; + this.PixelCount = ((buffer[hostnameEnd + 2] & 0xff) << 8 +(buffer[hostnameEnd + 3] & 0xff)); + this.fixedUnknown4 = buffer[hostnameEnd + 4] & 0xff; + this.fixedUnknown5 = buffer[hostnameEnd + 5] & 0xff; + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public String HostName; + public int PixelCount; + @Override + public String toString() { + return String.format("%s HostName: %s Unknowns: (Fixed: %02X %02X %02X %02X %02X Var: %02X)", this.LogPrefix, this.HostName, this.PixelCount, fixedUnknown1, fixedUnknown2, fixedUnknown3, fixedUnknown4, fixedUnknown5, varUnknown1); + } +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index b31886d..dfb38c1 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -3,6 +3,13 @@ import java.util.Arrays; public class RequestResponseFactory { + private static String ExpectedResponse; + private static final Object LOCK = new Object() {}; + private static void setExpectedResponse(String expectedResponse) { + synchronized (LOCK) { + ExpectedResponse = expectedResponse; + } + } public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boolean toServer) { if(buffer.length < 3) { throw new MalformedBufferException("Buffer should be more than three characters long", buffer); @@ -19,14 +26,25 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole if(toServer) { switch (code & 0xFF) { case 0x0C: + setExpectedResponse("ConnectionResponse"); return new ConnectionRequest(bufferSlice); default: + setExpectedResponse(null); return new UnclassifiedRequest(bufferSlice); } } else { - switch (code) { - default: - return new UnclassifiedResponse(bufferSlice); + try { + switch (ExpectedResponse) { + case "ConnectionResponse": + return new ConnectionResponse(bufferSlice); + } + switch (code) { + default: + return new UnclassifiedResponse(bufferSlice); + } + } + finally { + setExpectedResponse(null); } } } From 84fa584abd9120e45fbf7a80e5d2a79f0ccf3ef6 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 03:43:21 -0800 Subject: [PATCH 16/24] Update protocol with new findings --- protocol/protocol.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/protocol/protocol.md b/protocol/protocol.md index 635b8ee..d9f13b2 100644 --- a/protocol/protocol.md +++ b/protocol/protocol.md @@ -25,17 +25,19 @@ commands that are querying information. * Connect * Request * `0c` - Command. - * `00 0a` - ?? fixed. - * `eb 2b 76 14 0c 1b 07 15 35 04` - ?? varying with each connection. - Is `0a` an array length for this? + * `00 0a` - ?? fixed. [Confirmed same across two devices -- App version?] + * `eb 2b 76` ?? varying. + * `14 0c 1b 07 15 35 04` - Current time: Year Month Day Day-of-week? Hour Minute Second + * Response * `c9` - ?? varying. * `01` - ?? varying 00 or 01. - * `09 54 72 69 6d 6c 69 67 68 74` - "Trimlight" (assuming 09 is a string length). + * `09` - String length + * `54 72 69 6d 6c 69 67 68 74` - "Trimlight" Controller name. - * `00 00` - ?? fixed. - * `02 58` - Pixel count? **TODO: Confirm this with a different system.* - * `03 00` - ?? fixed. + * `00 00` - ?? fixed. [RGB order?] + * `02 58` - Pixel count + * `03 00` - ?? fixed. [IC?] * Controller mode change * Request * `0d` - Command. From 5a5a8cb2d4e235015d75563e1448c83abe30a067 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:04:17 -0800 Subject: [PATCH 17/24] Whitespace --- proxy/src/com/trimlighthacking/ByteArrayFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java index 7b7940f..ca637c4 100644 --- a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java +++ b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java @@ -18,7 +18,7 @@ public static String format(byte[] bytes, int count) { } } - out.append("\n "); + out.append("\n "); for(int i = 0; i < count; i++) { out.append(String.format("%02X ", bytes[i])); } From 14c326ef75dbdf2c6d8db1c3bc1cc9a77ec64b95 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:04:38 -0800 Subject: [PATCH 18/24] Output correct pixel count --- proxy/src/com/trimlighthacking/ConnectionResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/com/trimlighthacking/ConnectionResponse.java b/proxy/src/com/trimlighthacking/ConnectionResponse.java index bf78634..da5a602 100644 --- a/proxy/src/com/trimlighthacking/ConnectionResponse.java +++ b/proxy/src/com/trimlighthacking/ConnectionResponse.java @@ -21,7 +21,7 @@ public ConnectionResponse(byte[] buffer) { this.HostName = new String(hostname); this.fixedUnknown2 = buffer[hostnameEnd] & 0xff; this.fixedUnknown3 = buffer[hostnameEnd + 1] & 0xff; - this.PixelCount = ((buffer[hostnameEnd + 2] & 0xff) << 8 +(buffer[hostnameEnd + 3] & 0xff)); + this.PixelCount = (((buffer[hostnameEnd + 2] & 0xff) << 8) + (buffer[hostnameEnd + 3] & 0xff)); this.fixedUnknown4 = buffer[hostnameEnd + 4] & 0xff; this.fixedUnknown5 = buffer[hostnameEnd + 5] & 0xff; // Reduce to interesting slice @@ -32,6 +32,6 @@ public ConnectionResponse(byte[] buffer) { public int PixelCount; @Override public String toString() { - return String.format("%s HostName: %s Unknowns: (Fixed: %02X %02X %02X %02X %02X Var: %02X)", this.LogPrefix, this.HostName, this.PixelCount, fixedUnknown1, fixedUnknown2, fixedUnknown3, fixedUnknown4, fixedUnknown5, varUnknown1); + return String.format("%s HostName: %s PixelCount: %d Unknowns: (Fixed: %02X %02X %02X %02X %02X Var: %02X)", this.LogPrefix, this.HostName, this.PixelCount, fixedUnknown1, fixedUnknown2, fixedUnknown3, fixedUnknown4, fixedUnknown5, varUnknown1); } } From 5512bc101b716d51520b34fe229686ebf6dac836 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:05:27 -0800 Subject: [PATCH 19/24] Simplify --- .../RequestResponseFactory.java | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index dfb38c1..3c0153d 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -3,48 +3,45 @@ import java.util.Arrays; public class RequestResponseFactory { - private static String ExpectedResponse; - private static final Object LOCK = new Object() {}; - private static void setExpectedResponse(String expectedResponse) { + private static byte previousRequestCode; + private static final Object LOCK = new Object() { + }; + + private static void setPreviousRequestCode(byte currentRequestCode) { synchronized (LOCK) { - ExpectedResponse = expectedResponse; + RequestResponseFactory.previousRequestCode = currentRequestCode; } } + public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boolean toServer) { - if(buffer.length < 3) { + if (buffer.length < 3) { throw new MalformedBufferException("Buffer should be more than three characters long", buffer); } byte[] bufferSlice = Arrays.copyOfRange(buffer, 0, bytes_read); - if((bufferSlice[0] & 0xFF) != 0x5A || (bufferSlice[bufferSlice.length - 1] & 0xFF) != 0xA5) { - throw new MalformedBufferException(String.format("Buffer is not bookended by 0x5A and 0xA5. Instead, it has %02X and %02X", bufferSlice[0], bufferSlice[bufferSlice.length -1]), bufferSlice); + if ((bufferSlice[0] & 0xFF) != 0x5A || (bufferSlice[bufferSlice.length - 1] & 0xFF) != 0xA5) { + throw new MalformedBufferException(String.format("Buffer is not bookended by 0x5A and 0xA5. Instead, it has %02X and %02X", bufferSlice[0], bufferSlice[bufferSlice.length - 1]), bufferSlice); } byte code = buffer[1]; - - if(toServer) { + if (toServer) { + setPreviousRequestCode(code); switch (code & 0xFF) { case 0x0C: - setExpectedResponse("ConnectionResponse"); + return new ConnectionRequest(bufferSlice); default: - setExpectedResponse(null); return new UnclassifiedRequest(bufferSlice); } } else { - try { - switch (ExpectedResponse) { - case "ConnectionResponse": - return new ConnectionResponse(bufferSlice); - } - switch (code) { - default: - return new UnclassifiedResponse(bufferSlice); - } + switch (previousRequestCode & 0xFF) { + case 0x0C: + return new ConnectionResponse(bufferSlice); } - finally { - setExpectedResponse(null); + switch (code) { + default: + return new UnclassifiedResponse(bufferSlice); } } } From 27790483857e383cd9b89fe92c97f493686353eb Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:05:41 -0800 Subject: [PATCH 20/24] Fix bug --- proxy/src/com/trimlighthacking/TrimlightProxyServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java index faec632..8889cc5 100644 --- a/proxy/src/com/trimlighthacking/TrimlightProxyServer.java +++ b/proxy/src/com/trimlighthacking/TrimlightProxyServer.java @@ -132,7 +132,7 @@ public void run() { int bytes_read; try { while((bytes_read = from_server.read(buffer)) != -1) { - TrimlightInterpretable response = RequestResponseFactory.Create(buffer, bytes_read, true); + TrimlightInterpretable response = RequestResponseFactory.Create(buffer, bytes_read, false); log(response.toString()); to_client.write(buffer, 0, bytes_read); From 04f02ebed1dc1d59956f8b4c92ad33b29c810edc Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:39:15 -0800 Subject: [PATCH 21/24] Add PatternLibraryQueryRequest --- .../PatternLibraryQueryRequest.java | 21 +++++++++++++++++ .../PatternLibraryQueryResponse.java | 23 +++++++++++++++++++ .../RequestResponseFactory.java | 5 +++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 proxy/src/com/trimlighthacking/PatternLibraryQueryRequest.java create mode 100644 proxy/src/com/trimlighthacking/PatternLibraryQueryResponse.java diff --git a/proxy/src/com/trimlighthacking/PatternLibraryQueryRequest.java b/proxy/src/com/trimlighthacking/PatternLibraryQueryRequest.java new file mode 100644 index 0000000..c50084f --- /dev/null +++ b/proxy/src/com/trimlighthacking/PatternLibraryQueryRequest.java @@ -0,0 +1,21 @@ +package com.trimlighthacking; + +import java.util.Arrays; + +public class PatternLibraryQueryRequest extends Request { + + private final int fixedUnknown1; + private final int fixedUnknown2; + + public PatternLibraryQueryRequest(byte[] buffer) { + + this.fixedUnknown1 = buffer[1] & 0xFF; + this.fixedUnknown2 = buffer[2] & 0xFF; + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public String toString() { + return String.format("%s Unknowns: (Fixed: %02X %02X)", this.LogPrefix, fixedUnknown1, fixedUnknown2); + } +} diff --git a/proxy/src/com/trimlighthacking/PatternLibraryQueryResponse.java b/proxy/src/com/trimlighthacking/PatternLibraryQueryResponse.java new file mode 100644 index 0000000..e682afb --- /dev/null +++ b/proxy/src/com/trimlighthacking/PatternLibraryQueryResponse.java @@ -0,0 +1,23 @@ +package com.trimlighthacking; + +import java.util.Arrays; + +public class PatternLibraryQueryResponse extends Response { + + public PatternLibraryQueryResponse(byte[] buffer) { + + /*this.PatternIndex = buffer[1] & 0xFF; + byte[] patternNameBytes = Arrays.copyOfRange(buffer, 2, 27); + this.PatternName = new String(patternNameBytes);*/ + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public int PatternIndex; + public String PatternName; + + public String toString() { + String formattedByteArray = ByteArrayFormatter.format(this.rawBytes, this.rawBytes.length); + return String.format("%s %s", this.LogPrefix, formattedByteArray); + } +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index 3c0153d..c7b0845 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -28,14 +28,17 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole if (toServer) { setPreviousRequestCode(code); switch (code & 0xFF) { + case 0x02: + return new PatternLibraryQueryRequest(bufferSlice); case 0x0C: - return new ConnectionRequest(bufferSlice); default: return new UnclassifiedRequest(bufferSlice); } } else { switch (previousRequestCode & 0xFF) { + case 0x02: + return new PatternLibraryQueryResponse(bufferSlice); case 0x0C: return new ConnectionResponse(bufferSlice); } From da5d1102f0ac8f87eb9322bf680870e8942241c5 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:48:21 -0800 Subject: [PATCH 22/24] Add BuildInPatternSetRequest --- .../BuiltInPatternSetRequest.java | 29 +++++++++++++++++++ .../RequestResponseFactory.java | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 proxy/src/com/trimlighthacking/BuiltInPatternSetRequest.java diff --git a/proxy/src/com/trimlighthacking/BuiltInPatternSetRequest.java b/proxy/src/com/trimlighthacking/BuiltInPatternSetRequest.java new file mode 100644 index 0000000..fd24237 --- /dev/null +++ b/proxy/src/com/trimlighthacking/BuiltInPatternSetRequest.java @@ -0,0 +1,29 @@ +package com.trimlighthacking; + +import java.util.Arrays; + +public class BuiltInPatternSetRequest extends Request { + private final int fixedUnknown1; + private final int fixedUnknown2; + + public BuiltInPatternSetRequest(byte[] buffer) { + + + this.fixedUnknown1 = buffer[2] & 0xFF; + this.fixedUnknown2 = buffer[3] & 0xFF; + + this.PatternIndex = buffer[4] & 0xFF; + this.Speed = buffer[5] & 0xFF; + this.Brightness = buffer[6] & 0xFF; + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + int PatternIndex; // TODO: Enumerate names + int Speed; + int Brightness; + + public String toString() { + return String.format("%s Index: %s Speed: %d Brightness: %d Unknowns: (Fixed: %02X %02X)", this.LogPrefix, PatternIndex, Speed, Brightness, fixedUnknown1, fixedUnknown2); + } +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index c7b0845..19f498f 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -30,6 +30,8 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole switch (code & 0xFF) { case 0x02: return new PatternLibraryQueryRequest(bufferSlice); + case 0x0A: + return new BuiltInPatternSetRequest(bufferSlice); case 0x0C: return new ConnectionRequest(bufferSlice); default: From 4884b2f4e358e5280533c7c38b055021a8d9d4d5 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Thu, 31 Dec 2020 04:50:08 -0800 Subject: [PATCH 23/24] Formatting --- proxy/src/com/trimlighthacking/ByteArrayFormatter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java index ca637c4..89ca920 100644 --- a/proxy/src/com/trimlighthacking/ByteArrayFormatter.java +++ b/proxy/src/com/trimlighthacking/ByteArrayFormatter.java @@ -3,6 +3,7 @@ public class ByteArrayFormatter { public static String format(byte[] bytes, int count) { StringBuilder out = new StringBuilder(bytes.length * 3); + out.append("\n "); if(count > bytes.length) { throw new IllegalArgumentException(String.format("Count %d is greater than buffer length %d", count, bytes.length)); } @@ -18,7 +19,7 @@ public static String format(byte[] bytes, int count) { } } - out.append("\n "); + out.append("\n "); for(int i = 0; i < count; i++) { out.append(String.format("%02X ", bytes[i])); } From 6c339217f821a52c6f667146d2b66b84775e5873 Mon Sep 17 00:00:00 2001 From: Stephen Horne Date: Fri, 1 Jan 2021 18:53:23 -0800 Subject: [PATCH 24/24] Add PatternEntry Request and Response --- .../trimlighthacking/PatternEntryRequest.java | 23 ++++++++++ .../PatternEntryResponse.java | 44 +++++++++++++++++++ .../RequestResponseFactory.java | 6 ++- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 proxy/src/com/trimlighthacking/PatternEntryRequest.java create mode 100644 proxy/src/com/trimlighthacking/PatternEntryResponse.java diff --git a/proxy/src/com/trimlighthacking/PatternEntryRequest.java b/proxy/src/com/trimlighthacking/PatternEntryRequest.java new file mode 100644 index 0000000..406d221 --- /dev/null +++ b/proxy/src/com/trimlighthacking/PatternEntryRequest.java @@ -0,0 +1,23 @@ +package com.trimlighthacking; + +import java.util.Arrays; + +public class PatternEntryRequest extends Request { + private final int fixedUnknown1; + private final int fixedUnknown2; + + public PatternEntryRequest(byte[] buffer) { + this.fixedUnknown1 = buffer[2] & 0xFF; + this.fixedUnknown2 = buffer[3] & 0xFF; + + this.PatternIndex = buffer[4] & 0xFF; + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public int PatternIndex; + + public String toString() { + return String.format("%s Index: %d Unknowns: (Fixed: %02X %02X)", this.LogPrefix, this.PatternIndex, fixedUnknown1, fixedUnknown2); + } +} diff --git a/proxy/src/com/trimlighthacking/PatternEntryResponse.java b/proxy/src/com/trimlighthacking/PatternEntryResponse.java new file mode 100644 index 0000000..c913c0d --- /dev/null +++ b/proxy/src/com/trimlighthacking/PatternEntryResponse.java @@ -0,0 +1,44 @@ +package com.trimlighthacking; + +import sun.plugin2.util.ColorUtil; + +import java.awt.*; +import java.util.Arrays; + +public class PatternEntryResponse extends Response { + public PatternEntryResponse(byte[] buffer) { + this.PatternIndex = buffer[1] & 0xFF; + byte[] patternNameBytes = Arrays.copyOfRange(buffer, 2, 27); + this.PatternName = new String(patternNameBytes); + for(int i = 0; i < 7; i++) { + Lengths[i] = buffer[31 + i] & 0xFF; + } + + for(int i = 0; i < 7; i++) { + int tripletStart = i * 3 + 38; + int r = buffer[tripletStart + 0] & 0xFF; + int g = buffer[tripletStart + 1] & 0xFF; + int b = buffer[tripletStart + 2] & 0xFF; + Colors[i] = new Color(r,g,b); + } + // Reduce to interesting slice + this.rawBytes = Arrays.copyOfRange(buffer, 2, buffer.length - 1); + } + + public int PatternIndex; + public String PatternName; + public int[] Lengths = new int[7]; + public Color[] Colors = new Color[7]; + + public String toString() { + String formattedByteArray = ByteArrayFormatter.format(this.rawBytes, this.rawBytes.length); + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < 7; i++) { + sb.append(Colors[i].toString().replace("java.awt.Color", "")); + sb.append(" x "); + sb.append(Lengths[i]); + sb.append(" "); + } + return String.format("%s #%d \"%s\" %s", this.LogPrefix, this.PatternIndex, this.PatternName, sb.toString()); + } +} diff --git a/proxy/src/com/trimlighthacking/RequestResponseFactory.java b/proxy/src/com/trimlighthacking/RequestResponseFactory.java index 19f498f..bf36327 100644 --- a/proxy/src/com/trimlighthacking/RequestResponseFactory.java +++ b/proxy/src/com/trimlighthacking/RequestResponseFactory.java @@ -28,6 +28,8 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole if (toServer) { setPreviousRequestCode(code); switch (code & 0xFF) { + case 0x16: + return new PatternEntryRequest(bufferSlice); case 0x02: return new PatternLibraryQueryRequest(bufferSlice); case 0x0A: @@ -39,12 +41,12 @@ public static TrimlightInterpretable Create(byte[] buffer, int bytes_read, boole } } else { switch (previousRequestCode & 0xFF) { + case 0x16: + return new PatternEntryResponse(bufferSlice); case 0x02: return new PatternLibraryQueryResponse(bufferSlice); case 0x0C: return new ConnectionResponse(bufferSlice); - } - switch (code) { default: return new UnclassifiedResponse(bufferSlice); }