--- a/src/java.base/unix/native/libnet/PlainSocketImpl.c Tue Sep 26 20:29:13 2017 +0530
+++ b/src/java.base/unix/native/libnet/PlainSocketImpl.c Tue Sep 26 20:34:10 2017 +0530
@@ -679,14 +679,16 @@
}
/* ECONNABORTED or EWOULDBLOCK error so adjust timeout if there is one. */
- currNanoTime = JVM_NanoTime(env, 0);
- nanoTimeout -= (currNanoTime - prevNanoTime);
- if (nanoTimeout < NET_NSEC_PER_MSEC) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
- "Accept timed out");
- return;
+ if (nanoTimeout >= NET_NSEC_PER_MSEC) {
+ currNanoTime = JVM_NanoTime(env, 0);
+ nanoTimeout -= (currNanoTime - prevNanoTime);
+ if (nanoTimeout < NET_NSEC_PER_MSEC) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+ "Accept timed out");
+ return;
+ }
+ prevNanoTime = currNanoTime;
}
- prevNanoTime = currNanoTime;
}
if (newfd < 0) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/HugeDataTransferTest.java Tue Sep 26 20:34:10 2017 +0530
@@ -0,0 +1,881 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8185072
+ * @summary network006 times out in many configs in JDK10-hs nightly
+ * @run main/othervm/manual HugeDataTransferTest 1
+ */
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Random;
+
+/**
+ * This test makes huge number of data transfers between 2 Java virtual machines
+ * using the TCP/IP protocol, and checks if those data are transfered correctly.
+ * Both client and server VMs run on the same local computer and attach TCP/IP
+ * sockets to the local host, or to the loopback domain
+ * ``<code>localhost</code>'' (having IP address <code>127.0.0.1</code>).
+ *
+ * <p>
+ * In this test, 128 client/server connections are established. Once a
+ * connection is established, client passes a data parcel to server, and server
+ * reads that parcel and checks if it is same as expected (byte-to-byte equality
+ * is desired). Then server passes (some other) parcel to the client, and client
+ * reads and verifies those bytes. This ping-pong game is repeated 128 times;
+ * and after that each pair of sockets checks if there are no extra bytes
+ * accedentally passed through their connection.
+ *
+ * <p>
+ * Parcels lengths and contents are chosen randomly, and average parcel length
+ * is 128 bytes. So totally, each pair of sockets passes ~16Kb of data to each
+ * other, and thus ~32Kb of data are transfered by each sockets pair. Totally,
+ * ~4Mb of data are transfered by all client/server pairs.
+ *
+ * @author vtewari
+ */
+public class HugeDataTransferTest {
+
+ /**
+ * Timeout for TCP/IP sockets (currently set to 1 min).
+ */
+ private static int SO_TIMEOUT;// = 2*60*1000;
+
+ /**
+ * Maximal number of connections this test should open simultaneously.
+ */
+ private final static int MAX_CONNECTIONS = 128;
+
+ /**
+ * Check few more connections to make sure that MAX_CONNECTIONS are safe.
+ */
+ private final static int CONNECTIONS_RESERVE = 10;
+
+ /**
+ * The test used to fail with connection reset by peer set to 50. (and once
+ * in a three if it was set to 10). So now we set it to MAX_CONNECTIONS
+ * (128).
+ */
+ private final static int BACKLOG_QUEUE_LENGTH = MAX_CONNECTIONS;
+
+ /**
+ * Number of parcels to be sent/recieved.
+ */
+ private final static int DATA_PARCELS = 128;
+
+ /**
+ * Maximal length of data parcel to be sent/recieved (it equals to 256 bytes
+ * now).
+ */
+ private final static int MAX_PARCEL = 1 << 8;
+
+ /**
+ * Either actually display optional reports or not.
+ */
+ static private final boolean DEBUG_MODE = false;
+
+ /**
+ * How many IP sockets can we open simultaneously? Check if
+ * <code>MAX_CONNECTIONS</code> connections can be open simultaneously.
+ */
+ private static int detectOSLimitation() {
+ final int CONNECTIONS_TO_TRY = MAX_CONNECTIONS + CONNECTIONS_RESERVE;
+ display("--- Trying to open " + CONNECTIONS_TO_TRY + " connections:");
+
+ InetAddress address;
+ ServerSocket serverSocket;
+ try {
+ address = InetAddress.getLocalHost();
+ int anyPort = 0;
+ int defaultBacklog = BACKLOG_QUEUE_LENGTH;
+ serverSocket = new ServerSocket(anyPort, defaultBacklog, address);
+ } catch (IOException ioe) {
+ throw new Error("FATAL error while loading the test: " + ioe);
+ }
+ display(serverSocket.toString());
+
+ Socket server[] = new Socket[CONNECTIONS_TO_TRY];
+ Socket client[] = new Socket[CONNECTIONS_TO_TRY];
+
+ int i, port = serverSocket.getLocalPort();
+ for (i = 0; i < CONNECTIONS_TO_TRY; i++) {
+ try {
+ client[i] = new Socket(address, port);
+ display(">Open: client[" + i + "] = " + client[i]);
+ server[i] = serverSocket.accept();
+ display(">Open: server[" + i + "] = " + server[i]);
+ } catch (IOException ioe) {
+ display(">OOPS! -- failed to open connection #" + i);
+ break;
+ }
+ }
+ display("> Could open "
+ + (i < CONNECTIONS_TO_TRY ? "only " : "") + i + " connections.");
+ display(">Closing them:");
+ for (int j = 0; j < i; j++) {
+ try {
+ server[j].close();
+ client[j].close();
+ } catch (IOException ioe) {
+ throw new Error("FATAL error while loading the test: " + ioe);
+ }
+ }
+ display(">OK.");
+ int safeConnections = i - CONNECTIONS_RESERVE;
+ if (safeConnections < 1) {
+ safeConnections = 1;
+ }
+ if (safeConnections < MAX_CONNECTIONS) {
+ complain("------------------------- CAUTION: -------------------");
+ complain("While checking the OS limitations, the test found that");
+ complain("only " + i + " TCP/IP socket connections could be safely open");
+ complain("simultaneously. However, possibility to open at least");
+ complain("" + MAX_CONNECTIONS + "+" + CONNECTIONS_RESERVE
+ + " connections were expected.");
+ complain("");
+ complain("So, the test will check only " + safeConnections + " connection"
+ + (safeConnections == 1 ? "" : "s") + " which seem");
+ complain("safe to be open simultaneously.");
+ complain("------------------------------------------------------");
+ }
+ return safeConnections;
+ }
+
+ //----------------------------------------------------------------//
+ /**
+ * Re-calls to the method <code>run(args[],out)</code> actually performing
+ * the test. After <code>run(args[],out)</code> stops, follow JDK-like
+ * convention for exit codes. I.e.: stop with exit status 95 if the test has
+ * passed, or with status 97 if the test has failed.
+ *
+ * @see #run(String[],PrintStream)
+ */
+ public static void main(String args[]) {
+ int exitCode = run(args, System.out);
+ System.exit(exitCode + 95);
+ // JCK-like exit status.
+ }
+
+ public static int run(String args[], PrintStream out) {
+ HugeDataTransferTest.out = out;
+
+ //
+ // Get the Internet address of the local machine.
+ //
+ InetAddress address = null;
+ try {
+ address = InetAddress.getLocalHost();
+ } catch (UnknownHostException exception) {
+ complain(exception.toString());
+ return 2; // FAILED
+ }
+ display("Host: " + address);
+
+ //
+ // Detect if it is safe to open MAX_CONNETIONS simultaneously:
+ //
+ final int CONNECTIONS = detectOSLimitation();
+
+ //
+ // Assign ServerSocket, and start client VM which should open
+ // the prescribed number of CONNECTIONS to that ServerSocket.
+ //
+ ServerSocket serverSocket;
+ try {
+ final int anyPort = 0;
+ final int defaultBacklog = BACKLOG_QUEUE_LENGTH;
+ serverSocket = new ServerSocket(anyPort, defaultBacklog, address);
+ } catch (IOException exception) {
+ complain("Cannot assign a ServerSocket on: " + address);
+ return 2;
+ }
+
+ //
+ // Start the client process on different VM.
+ //
+ String jdkPath = System.getProperty("test.jdk");
+ Path toolName = Paths.get("bin", "java" + (isWindows() ? ".exe" : ""));
+ Path jdkTool = Paths.get(jdkPath, toolName.toString());
+
+ String IPAddress = address.getHostAddress();
+ int localPort = serverSocket.getLocalPort();
+ String arguments = " " + CONNECTIONS + " " + IPAddress + " " + localPort;
+ //String command = args[0] + " " + network006.class.getName() + "$Client " + arguments;
+ String command = jdkTool.toAbsolutePath().toString() + " " + Client.class.getName() + " " + arguments;
+ try {
+ SO_TIMEOUT = Integer.parseInt(args[0]) * 60 * 1000;
+ } catch (NumberFormatException e) {
+ complain("Wrong timeout argument: " + e);
+ return 2;
+ }
+
+ Runtime runtime = Runtime.getRuntime();
+
+ Process client = null;
+ IORedirector redirectOut = null;
+ IORedirector redirectErr = null;
+
+ try {
+ // Start clients on different JVM:
+ client = runtime.exec(command);
+
+ // Provide clients with access to stderr and stdout:
+ InputStream clientOut = client.getInputStream();
+ InputStream clientErr = client.getErrorStream();
+ redirectOut = new IORedirector(clientOut, DEBUG_MODE ? out : null);
+ redirectErr = new IORedirector(clientErr, out);
+ redirectOut.start();
+ redirectErr.start();
+
+ } catch (IOException exception) {
+ complain("Failed to start client: " + exception);
+ return 2;
+ }
+ //
+ // Start the server threads (and let them establish connections):
+ //
+
+ Server server[] = new Server[CONNECTIONS];
+ for (int i = 0; i < CONNECTIONS; i++) {
+ server[i] = new Server(serverSocket);
+ display("Server #" + i + ": " + server[i]);
+ server[i].start();
+ }
+
+ //
+ // Wait for the servers and the clients:
+ //
+ boolean testFailed = false;
+
+ try {
+ client.waitFor();
+ int clientStatus = client.exitValue();
+ display("Client VM exitCode=" + clientStatus);
+
+ // Let I/O redirectors to flush:
+ if (redirectOut.isAlive()) {
+ redirectOut.join();
+ }
+ if (redirectErr.isAlive()) {
+ redirectErr.join();
+ }
+
+ // If client has crashed, also terminate the server (to avoid hangup).
+ if (clientStatus != 95) {
+ complain("Client VM has crashed: exit status=" + clientStatus);
+ testFailed = true;
+ }
+
+ // Client has finished OK; wait for the server.
+ for (int i = 0; i < CONNECTIONS; i++) {
+ display("Server: waiting for #" + i);
+ if (server[i].isAlive()) {
+ display("Server #" + i + ": (joining...)" + server[i]);
+ server[i].join();
+ }
+ if (server[i].exception != null) {
+ if (server[i].message != null) {
+ complain("Server #" + i + "(finished): with message:" + server[i].message);
+ }
+
+ complain("Server #" + i + "(finished): " + server[i].exception);
+ server[i].exception.printStackTrace(out);
+ out.flush();
+// complain("Server #"+i+": "+server[i].exception.getStackTrace());
+ testFailed = true;
+ }
+ }
+
+ } catch (InterruptedException exception) {
+ complain("Test interrupted: " + exception);
+ testFailed = true;
+ }
+
+ if (testFailed) {
+ complain("Test failed.");
+ } else {
+ display("Test passed.");
+ }
+ return testFailed ? 2 : 0;
+ }
+
+ private static boolean isWindows() {
+ return System.getProperty("os.name").toLowerCase().startsWith("win");
+ }
+ //----------------------------------------------------------------//
+ /**
+ * Log stream for error messages and/or (optional) execution trace.
+ */
+ private static PrintStream out;
+
+ /**
+ * Print error message.
+ */
+ private static synchronized void complain(Object message) {
+ out.println("# " + message);
+ out.flush();
+ }
+
+ ;
+
+ /**
+ * Display optional report: comment ca va?
+ */
+ private static synchronized void display(Object report) {
+ if (DEBUG_MODE) {
+ out.println(report.toString());
+ }
+ out.flush(); //todo shouldn't this be inside if??
+ }
+
+ ;
+
+ //----------------------------------------------------------------//
+
+ /**
+ * Server thread should reply to data parcels sent by Client VM.
+ */
+ private static class Server extends Thread {
+
+ /**
+ * The socket is assigned at the Server instantiation.
+ */
+ private ServerSocket serverSocket;
+
+ /**
+ * The socket is assigned at the Server runtime.
+ */
+ private Socket socket;
+
+ /**
+ * Display the server socket.
+ */
+ @Override
+ public String toString() {
+
+ return "ServerSocket: " + serverSocket.toString();
+// + " socket: " + socket.toString();
+ }
+
+ /**
+ * Which port is this socket listening?
+ */
+ int getPort() {
+ return serverSocket.getLocalPort();
+ }
+
+ /**
+ * Find some free port at the given <code>address</code> and attach new
+ * server to hear that port. // lidsten to??
+ */
+ public Server(ServerSocket serverSocket) {
+ this.serverSocket = serverSocket;
+ }
+
+ /**
+ * Exception just arisen while the server was working, or
+ * <code>null</code> if it was OK with the server.
+ */
+ Exception exception = null;
+ String message = null;
+
+ /**
+ * Accept connection, then reply to client's parcels.
+ */
+ @Override
+ public void run() {
+ try {
+ socket = serverSocket.accept();
+ socket.setSoTimeout(SO_TIMEOUT);
+
+ InputStream istream = socket.getInputStream();
+ OutputStream ostream = socket.getOutputStream();
+
+ Random random = new Random(getPort());
+
+ for (int i = 0; i < DATA_PARCELS; i++) {
+ Parcel etalon = new Parcel(random);
+ message = "reading parcel number " + i;
+ Parcel sample = new Parcel(istream); // read
+ if (!sample.equals(etalon)) {
+ complain("Server thread for port #"
+ + getPort() + " got unexpected parcel:\n"
+ + "sample=" + sample + "\n"
+ + "etalon=" + etalon);
+ throw new TestFailure( //received??
+ "server has read unexpected parcel");
+ }
+ message = "sending parcel number " + i;
+ etalon.send(ostream);
+ ostream.flush();
+ }
+
+ int datum = istream.read(); // wait for client close()
+ if (datum >= 0) {
+ throw new TestFailure(
+ "server has read ambigous byte: " + datum);
+ }
+
+ ostream.close(); // implies: socket.close();
+
+ } catch (Exception oops) {
+ exception = oops;
+ }
+ }
+ }
+
+ //----------------------------------------------------------------//
+ /**
+ * Client VM should send data parcels to Server VM and recieve and verify
+ * the server's replies.
+ */
+ private static class Client extends Thread {
+
+ /**
+ * This thread uses the single client socket.
+ */
+ private Socket socket;
+
+ /**
+ * Address and port of this socket.
+ */
+ @Override
+ public String toString() {
+ return socket.toString();
+ }
+
+ /**
+ * Did the thread failed? If yes, what is the failure's reason.
+ */
+ Exception exception = null;
+ String message = null;
+
+ public static java.io.PrintStream complainStream = System.out;
+ public static java.io.PrintStream displayStream = System.err;
+
+ /**
+ * Connect client socket on the given <code>address</code> and
+ * <code>port</code>.
+ */
+ Client(InetAddress address, int port) throws IOException {
+ socket = new Socket(address, port);
+ socket.setSoTimeout(SO_TIMEOUT);
+ }
+
+ /**
+ * What is the port number this socket is listening for?
+ */
+ int getPort() {
+ return socket.getPort();
+ }
+
+ /**
+ * Establish connection, then read/respond <code>DATA_PARCELS</code>
+ * parcels of random data. Set initial seed for pseudo-random numbers
+ * generator to the value of the local port number.
+ *
+ * @see #DATA_PARCELS
+ * @see #getPort()
+ */
+ @Override
+ public void run() {
+ try {
+ InputStream istream = socket.getInputStream();
+ OutputStream ostream = socket.getOutputStream();
+
+ Random random = new Random(getPort());
+ // suggested by Oleg -- to avoid race conditions
+ /* try{
+ Thread.sleep(500);
+ }
+ catch (java.lang.InterruptedException e)
+ {
+ }*/
+
+ for (int i = 0; i < DATA_PARCELS; i++) {
+ Parcel etalon = new Parcel(random);
+ message = "sending parcel number: " + i;
+ etalon.send(ostream);
+ ostream.flush();
+
+ message = "reading parcel number: " + i;
+ Parcel sample = new Parcel(istream); // read
+ if (!sample.equals(etalon)) {
+ complain("Client thread for port #"
+ + getPort() + " got unexpected parcel:\n"
+ + "sample=" + sample + "\n"
+ + "etalon=" + etalon);
+ throw new TestFailure(
+ "parcel context is unexpected to client");
+ }
+ }
+
+ if (istream.available() > 0) {
+ int datum = istream.read();
+ throw new TestFailure(
+ "client has read ambigous byte: " + datum);
+ }
+ ostream.close(); // implies: socket.close()
+
+ } catch (Exception oops) {
+ exception = oops;
+ }
+ }
+
+ /**
+ * Establish lots of connections to server socket, attack servers with
+ * huge data parcels, and check if they reply correctly. The number of
+ * connections to try, the address and port number for the server socket
+ * are passed through <code>args[]</code>, like:
+ * <pre>
+ * java network006$Client connections_to_try address port
+ * </pre>
+ */
+ public static void main(String args[]) {
+ if (DEBUG_MODE) {
+ try {
+ String filename = "Client" + ((args.length == 3) ? args[2] : "new");
+ displayStream = new PrintStream(filename + ".out");
+ complainStream = new PrintStream(filename + ".err");
+ } catch (FileNotFoundException exception) {
+ complain(exception);
+ }
+
+ }
+
+ if (args.length != 3) {
+ complain("Client expects 3 paramenets:");
+ complain(" java " + Client.class.getName() + " connections_to_try address port");
+ exit(1); // FAILED
+ }
+
+ int CONNECTIONS = Integer.parseInt(args[0]);
+ display("Client VM: will try " + CONNECTIONS + " connections.");
+ InetAddress address;
+ try {
+ address = InetAddress.getByName(args[1]);
+ } catch (UnknownHostException exception) {
+ address = null;
+ complain("Client: cannot find host: \"" + args[1] + "\"");
+ exit(4);
+ }
+ display("Client: host to contact: " + address);
+ int port = Integer.parseInt(args[2]);
+ display("Client: port to contact: " + port);
+
+ //
+ // Establish connections, and start client processes:
+ //
+ Client client[] = new Client[CONNECTIONS];
+ for (int i = 0; i < CONNECTIONS; i++) {
+ try {
+ client[i] = new Client(address, port);
+ display("Client #" + i + ": " + client[i]);
+
+ } catch (IOException ioe) {
+ complain("Client #" + i + "(creation): " + ioe);
+ ioe.printStackTrace(complainStream);
+ complainStream.flush();
+// complain("Client #" + i + "(creation): " + ioe.getStackTrace());
+ exit(3);
+ }
+ }
+
+ for (int i = 0; i < CONNECTIONS; i++) {
+ client[i].start();
+ }
+
+ //
+ // Wait until testing is not finished:
+ //
+ int status = 0;
+ for (int i = 0; i < CONNECTIONS; i++) {
+ display("Client: waiting for #" + i);
+ if (client[i].isAlive()) {
+ display("Client #" + i + ": (joining...)" + client[i]);
+
+ try {
+ client[i].join();
+ } catch (InterruptedException ie) {
+ complain("Client #" + i + ": " + ie);
+ status = 3;
+ }
+ }
+ if (client[i].exception != null) {
+ if (client[i].message != null) {
+ complain("Client #" + i + "(finished) with message: " + client[i].message);
+ }
+ complain("Client #" + i + "(finished): " + client[i].exception);
+ client[i].exception.printStackTrace(complainStream);
+ complainStream.flush();
+ if (status == 0) {
+ status = 2;
+ }
+ }
+ }
+
+ exit(status);
+ }
+
+ /**
+ * Print error message.
+ */
+ private static synchronized void complain(Object message) {
+ complainStream.println("# " + message);
+ complainStream.flush();
+ }
+
+ /**
+ * Display execution trace.
+ */
+ private static synchronized void display(Object message) {
+ if (!DEBUG_MODE) {
+ return;
+ }
+ displayStream.println(message.toString());
+ displayStream.flush();
+ }
+
+ /**
+ * Exit with JCK-like status.
+ */
+ private static void exit(int exitCode) {
+ int status = exitCode + 95;
+// display("Client: exiting with code=" + status);
+ System.exit(status);
+ }
+ }
+
+ /**
+ * Two of such threads should redirect <code>out</code> and <code>err</code>
+ * streams of client VM.
+ */
+ private static class IORedirector extends Thread {
+
+ /**
+ * Source stream.
+ */
+ InputStream in;
+ /**
+ * Destination stream.
+ */
+ OutputStream out;
+
+ /**
+ * Redirect <code>in</code> to <code>out</code>.
+ */
+ public IORedirector(InputStream in, OutputStream out) {
+ this.in = in;
+ this.out = out;
+ }
+
+ /**
+ * Read input stream until the EOF, and write everithing to output
+ * stream. If output stream is assigned to <code>null</code>, do not
+ * print anything, but read the input stream anywhere.
+ */
+ @Override
+ public void run() {
+ try {
+ for (;;) {
+ int symbol = in.read();
+ if (symbol < 0) {
+ break; // EOF
+ }
+ if (out != null) {
+ out.write(symbol);
+ }
+ }
+
+ if (out != null) {
+ out.flush();
+ }
+
+ } catch (IOException exception) {
+ throw new TestFailure("IORedirector exception: " + exception);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------//
+ /**
+ * A data parcel to be sent/recieved between Client VM and Server thread.
+ * When data parcel is sent, first 4 bytes are transfered which encode the
+ * <code>int</code> number equal to size of the parcel minus 1. I.e.: if
+ * number of data bytes in the parcel's contents is <code>N</code>, then the
+ * first 4 bytes encode the number <code>N-1</code>. After that, the
+ * parcel's contents bytes are transered.
+ */
+ static class Parcel {
+
+ private final byte[] parcel;
+
+ /**
+ * Display all bytes as integer values from 0 to 255; or return
+ * ``<tt>null</tt>'' if this Parcel is not yet initialized.
+ */
+ @Override
+ public String toString() {
+ if (parcel == null) {
+ return "null";
+ }
+ String s = "{";
+ for (int i = 0; i < parcel.length; i++) {
+ s += (i > 0 ? ", " : "") + ((int) parcel[i] & 0xFF);
+ }
+ return s + "}";
+ }
+
+ /**
+ * Generate new <code>parcel[]</code> array using the given
+ * <code>random</code> numbers generator. Client and Server threads
+ * should use identical <code>random</code> generators, so that those
+ * threads could generate equal data parcels and check the parcel just
+ * transfered.
+ */
+ public Parcel(Random random) {
+ int size = random.nextInt(MAX_PARCEL) + 1;
+ parcel = new byte[size];
+ for (int i = 0; i < size; i++) {
+ parcel[i] = (byte) random.nextInt(256);
+ }
+ }
+
+ ;
+
+ /**
+ * Read exactly <code>size</code> bytes from the <code>istream</code>
+ * if possible, or throw <code>TestFailure</code> if unexpected end of
+ * <code>istream</code> occurs.
+ */
+ private static byte[] readBytes(int size, InputStream istream)
+ throws IOException {
+
+ byte data[] = new byte[size];
+ for (int i = 0; i < size; i++) {
+ int datum = istream.read();
+ if (datum < 0) {
+ throw new TestFailure(
+ "unexpected EOF: have read: " + i + " bytes of " + size);
+ }
+ data[i] = (byte) datum;
+ }
+ return data;
+ }
+
+ /**
+ * Read 4 bytes from <code>istream</code> and threat them to encode size
+ * of data parcel following these 4 bytes.
+ */
+ private static int getSize(InputStream istream) throws IOException {
+ byte data[] = readBytes(4, istream);
+ int data0 = (int) data[0] & 0xFF;
+ int data1 = (int) data[1] & 0xFF;
+ int data2 = (int) data[2] & 0xFF;
+ int data3 = (int) data[3] & 0xFF;
+ int sizeWord = data0 + (data1 << 8) + (data2 << 16) + (data3 << 24);
+ int size = sizeWord + 1;
+ if (size <= 0) {
+ throw new TestFailure("illegal size: " + size);
+ }
+ return size;
+ }
+
+ /**
+ * Send 4 bytes encoding actual size of the parcel just to be
+ * transfered.
+ */
+ private static void putSize(OutputStream ostream, int size)
+ throws IOException {
+
+ if (size <= 0) {
+ throw new TestFailure("illegal size: " + size);
+ }
+
+ int sizeWord = size - 1;
+ byte data[] = new byte[4];
+ data[0] = (byte) sizeWord;
+ data[1] = (byte) (sizeWord >> 8);
+ data[2] = (byte) (sizeWord >> 16);
+ data[3] = (byte) (sizeWord >> 24);
+ ostream.write(data);
+ }
+
+ /**
+ * Recieve data parcel.
+ */
+ public Parcel(InputStream istream) throws IOException {
+ int size = getSize(istream);
+ parcel = readBytes(size, istream);
+ }
+
+ /**
+ * Send <code>this</code> data parcel.
+ */
+ public void send(OutputStream ostream) throws IOException {
+ int size = parcel.length;
+ putSize(ostream, size);
+ ostream.write(parcel);
+ }
+
+ /**
+ * Check byte-to-byte equality between <code>this</code> and the
+ * <code>other</code> parcels.
+ */
+ public boolean equals(Parcel other) {
+ if (this.parcel.length != other.parcel.length) {
+ return false;
+ }
+ int size = parcel.length;
+ for (int i = 0; i < size; i++) {
+ if (this.parcel[i] != other.parcel[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Server or Client may throw this exception to report the test failure.
+ */
+ static class TestFailure extends RuntimeException {
+
+ /**
+ * Report particular <code>purpose</code> of the test failure.
+ */
+ public TestFailure(String purpose) {
+ super(purpose);
+ }
+ }
+}