summaryrefslogtreecommitdiff
path: root/main/client
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-05-22 21:46:45 -0700
committerLi Haoyi <haoyi.sg@gmail.com>2018-05-22 21:46:45 -0700
commitc04bfa1c0ee5a51ef5f63ade8e63d1f55f53fa3e (patch)
treeef3a2b179864b91fab91b45f59b787dd1dacfc88 /main/client
parent6e60ce3d921a9b5e4ced628e2014b707ce2bbbee (diff)
downloadmill-c04bfa1c0ee5a51ef5f63ade8e63d1f55f53fa3e.tar.gz
mill-c04bfa1c0ee5a51ef5f63ade8e63d1f55f53fa3e.tar.bz2
mill-c04bfa1c0ee5a51ef5f63ade8e63d1f55f53fa3e.zip
Migrate `ProxyOutputStream` to the `main.client` module, add unit/fuzz tests to make sure it works
Diffstat (limited to 'main/client')
-rw-r--r--main/client/src/mill/main/client/MillClientMain.java52
-rw-r--r--main/client/src/mill/main/client/ProxyOutputStream.java34
-rw-r--r--main/client/src/mill/main/client/ProxyStreamPumper.java60
-rwxr-xr-xmain/client/test/resources/akanon.midbin0 -> 45205 bytes
-rw-r--r--main/client/test/resources/bandung.jpgbin0 -> 196224 bytes
-rw-r--r--main/client/test/resources/gettysburg.txt8
-rw-r--r--main/client/test/resources/pip.tar.gzbin0 -> 95197 bytes
-rw-r--r--main/client/test/src/mill/main/client/ClientTests.java90
8 files changed, 193 insertions, 51 deletions
diff --git a/main/client/src/mill/main/client/MillClientMain.java b/main/client/src/mill/main/client/MillClientMain.java
index 45bd05ef..3ec4f8b0 100644
--- a/main/client/src/mill/main/client/MillClientMain.java
+++ b/main/client/src/mill/main/client/MillClientMain.java
@@ -129,7 +129,7 @@ public class MillClientMain {
InputStream outErr = ioSocket.getInputStream();
OutputStream in = ioSocket.getOutputStream();
- ClientOutputPumper outPump = new ClientOutputPumper(outErr, stdout, stderr);
+ ProxyStreamPumper outPump = new ProxyStreamPumper(outErr, stdout, stderr);
InputPumper inPump = new InputPumper(stdin, in, true);
Thread outThread = new Thread(outPump);
outThread.setDaemon(true);
@@ -149,53 +149,3 @@ public class MillClientMain {
}
}
}
-
-class ClientOutputPumper implements Runnable{
- private InputStream src;
- private OutputStream dest1;
- private OutputStream dest2;
- public ClientOutputPumper(InputStream src, OutputStream dest1, OutputStream dest2){
- this.src = src;
- this.dest1 = dest1;
- this.dest2 = dest2;
- }
-
- public void run() {
- byte[] buffer = new byte[1024];
- boolean running = true;
- boolean first = true;
- while (running) {
- try {
- int quantity0 = (byte)src.read();
- int quantity = Math.abs(quantity0);
- int offset = 0;
- while(offset < quantity){
- int delta = src.read(buffer, offset, quantity - offset);
- if (delta == -1) {
- running = false;
- break;
- }else{
- offset += delta;
- }
- }
- if (quantity0 < 0) dest1.write(buffer, 0, quantity);
- else dest2.write(buffer, 0, quantity);
- } catch (IOException e) {
- // Win32NamedPipeSocket input stream somehow doesn't return -1,
- // instead it throws an IOException whose message contains "ReadFile()".
- // However, if it throws an IOException before ever reading some bytes,
- // it could not connect to the server, so exit.
- if (Util.isWindows && e.getMessage().contains("ReadFile()")) {
- if (first) {
- System.err.println("Failed to connect to server");
- System.exit(1);
- } else running = false;
- } else {
- e.printStackTrace();
- System.exit(1);
- }
- }
- }
- }
-
-}
diff --git a/main/client/src/mill/main/client/ProxyOutputStream.java b/main/client/src/mill/main/client/ProxyOutputStream.java
new file mode 100644
index 00000000..339e0150
--- /dev/null
+++ b/main/client/src/mill/main/client/ProxyOutputStream.java
@@ -0,0 +1,34 @@
+package mill.main.client;
+
+import java.io.IOException;
+
+public class ProxyOutputStream extends java.io.OutputStream {
+ private java.io.OutputStream out;
+ private int key;
+ public ProxyOutputStream(java.io.OutputStream out, int key){
+ this.out = out;
+ this.key = key;
+ }
+ @Override synchronized public void write(int b) throws IOException {
+ out.write(key);
+ out.write(b);
+ }
+ @Override synchronized public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+ @Override synchronized public void write(byte[] b, int off, int len) throws IOException {
+ int i = 0;
+ while(i < len && i + off < b.length){
+ int chunkLength = Math.min(len - i, 127);
+ out.write(chunkLength * key);
+ out.write(b, off + i, Math.min(b.length - off - i, chunkLength));
+ i += chunkLength;
+ }
+ }
+ @Override public void flush() throws IOException {
+ out.flush();
+ }
+ @Override public void close() throws IOException {
+ out.close();
+ }
+}
diff --git a/main/client/src/mill/main/client/ProxyStreamPumper.java b/main/client/src/mill/main/client/ProxyStreamPumper.java
new file mode 100644
index 00000000..977323f3
--- /dev/null
+++ b/main/client/src/mill/main/client/ProxyStreamPumper.java
@@ -0,0 +1,60 @@
+package mill.main.client;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ProxyStreamPumper implements Runnable{
+ private InputStream src;
+ private OutputStream dest1;
+ private OutputStream dest2;
+ public ProxyStreamPumper(InputStream src, OutputStream dest1, OutputStream dest2){
+ this.src = src;
+ this.dest1 = dest1;
+ this.dest2 = dest2;
+ }
+
+ public void run() {
+ byte[] buffer = new byte[1024];
+ boolean running = true;
+ boolean first = true;
+ while (running) {
+ try {
+ int quantity0 = (byte)src.read();
+ int quantity = Math.abs(quantity0);
+ int offset = 0;
+ int delta = -1;
+ while(offset < quantity){
+ delta = src.read(buffer, offset, quantity - offset);
+ if (delta == -1) {
+ running = false;
+ break;
+ }else{
+ offset += delta;
+ }
+ }
+
+ if (delta != -1){
+ if (quantity0 > 0) dest1.write(buffer, 0, offset);
+ else dest2.write(buffer, 0, offset);
+ }
+ } catch (IOException e) {
+ // Win32NamedPipeSocket input stream somehow doesn't return -1,
+ // instead it throws an IOException whose message contains "ReadFile()".
+ // However, if it throws an IOException before ever reading some bytes,
+ // it could not connect to the server, so exit.
+ if (Util.isWindows && e.getMessage().contains("ReadFile()")) {
+ if (first) {
+ System.err.println("Failed to connect to server");
+ System.exit(1);
+ } else running = false;
+ } else {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ }
+ }
+
+}
diff --git a/main/client/test/resources/akanon.mid b/main/client/test/resources/akanon.mid
new file mode 100755
index 00000000..795ae0cb
--- /dev/null
+++ b/main/client/test/resources/akanon.mid
Binary files differ
diff --git a/main/client/test/resources/bandung.jpg b/main/client/test/resources/bandung.jpg
new file mode 100644
index 00000000..55152793
--- /dev/null
+++ b/main/client/test/resources/bandung.jpg
Binary files differ
diff --git a/main/client/test/resources/gettysburg.txt b/main/client/test/resources/gettysburg.txt
new file mode 100644
index 00000000..a7d9e4b2
--- /dev/null
+++ b/main/client/test/resources/gettysburg.txt
@@ -0,0 +1,8 @@
+Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
+
+Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.
+
+But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.
+
+Abraham Lincoln
+November 19, 1863 \ No newline at end of file
diff --git a/main/client/test/resources/pip.tar.gz b/main/client/test/resources/pip.tar.gz
new file mode 100644
index 00000000..57d1dc59
--- /dev/null
+++ b/main/client/test/resources/pip.tar.gz
Binary files differ
diff --git a/main/client/test/src/mill/main/client/ClientTests.java b/main/client/test/src/mill/main/client/ClientTests.java
index 5ae44d3f..f3fcf154 100644
--- a/main/client/test/src/mill/main/client/ClientTests.java
+++ b/main/client/test/src/mill/main/client/ClientTests.java
@@ -1,10 +1,14 @@
package mill.main.client;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.*;
public class ClientTests {
@Test
@@ -57,5 +61,91 @@ public class ClientTests {
assertEquals(i.available(), 0);
}
+ public byte[] readSamples(String ...samples) throws Exception{
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for(String sample: samples) {
+ byte[] bytes = java.nio.file.Files.readAllBytes(
+ java.nio.file.Paths.get(getClass().getResource(sample).getFile())
+ );
+ out.write(bytes);
+ }
+ return out.toByteArray();
+ }
+ @Test
+ public void tinyProxyInputOutputStream() throws Exception{
+ proxyInputOutputStreams(
+ Arrays.copyOf(readSamples("/bandung.jpg"), 30),
+ readSamples(),
+ 10
+ );
+ }
+ @Test
+ public void leftProxyInputOutputStream() throws Exception{
+ proxyInputOutputStreams(
+ readSamples("/bandung.jpg", "/akanon.mid", "/gettysburg.txt", "/pip.tar.gz"),
+ readSamples(),
+ 2950
+ );
+ }
+ @Test
+ public void rightProxyInputOutputStream() throws Exception{
+ proxyInputOutputStreams(
+ readSamples(),
+ readSamples("/bandung.jpg", "/akanon.mid", "/gettysburg.txt", "/pip.tar.gz"),
+ 3000
+ );
+ }
+ @Test
+ public void mixedProxyInputOutputStream() throws Exception{
+ proxyInputOutputStreams(
+ readSamples("/bandung.jpg", "/gettysburg.txt"),
+ readSamples("/akanon.mid", "/pip.tar.gz"),
+ 3050
+ );
+ }
+
+ /**
+ * Make sure that when we shove data through both ProxyOutputStreams in
+ * variously sized chunks, we get the exact same bytes back out from the
+ * ProxyStreamPumper.
+ */
+ public void proxyInputOutputStreams(byte[] samples1,
+ byte[] samples2,
+ int chunkMax) throws Exception{
+
+ ByteArrayOutputStream pipe = new ByteArrayOutputStream();
+ OutputStream src1 = new ProxyOutputStream(pipe, 1);
+ OutputStream src2 = new ProxyOutputStream(pipe, -1);
+
+ Random random = new Random(31337);
+
+ int i1 = 0;
+ int i2 = 0;
+ while(i1 < samples1.length || i2 < samples2.length){
+ int chunk = random.nextInt(chunkMax);
+ if (random.nextBoolean() && i1 < samples1.length){
+ src1.write(samples1, i1, Math.min(samples1.length-i1, chunk));
+ src1.flush();
+ i1 += chunk;
+ }else if (i2 < samples2.length){
+ src2.write(samples2, i2, Math.min(samples2.length-i2, chunk));
+ src2.flush();
+ i2 += chunk;
+ }
+ }
+
+ byte[] bytes = pipe.toByteArray();
+
+
+ ByteArrayOutputStream dest1 = new ByteArrayOutputStream();
+ ByteArrayOutputStream dest2 = new ByteArrayOutputStream();
+ ProxyStreamPumper pumper = new ProxyStreamPumper(
+ new ByteArrayInputStream(bytes),
+ dest1, dest2
+ );
+ pumper.run();
+ assertTrue(Arrays.equals(samples1, dest1.toByteArray()));
+ assertTrue(Arrays.equals(samples2, dest2.toByteArray()));
+ }
} \ No newline at end of file