package net.i2p.sam;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* <ol>
* <li>start up SAM</li>
* <li>Alice connects as 'Alice', gets her destination, stashes it away, and
* listens for any streams, echoing back whatever she receives.</li>
* <li>Bob connects as 'Bob', establishes a stream to the destination Alice
* stashed away, sends a few bundles of data, and closes the stream.</li>
* <li>Alice and Bob disconnect from SAM</li>
* <li>SAM bridge taken down</li>
* </ol>
*/
public class TestStreamTransfer {
private static Log _log = new Log(TestStreamTransfer.class);
private static String _alice = null;
private static boolean _dead = false;
private static Object _counterLock = new Object();
private static int _recvCounter = 0, _closeCounter = 0;
private static void runTest(String samHost, int samPort, String conOptions) {
int nTests = 20;
startAlice(samHost, samPort, conOptions);
/* Start up nTests different test threads. */
for (int i = 0; i < nTests; i++) {
testBob("bob" + i, samHost, samPort, conOptions);
if (i % 2 == 1)
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
/* Wait until the correct number of messages have been received
by Alices and the correct number of streams have been closed
by Bobs. */
while (true) {
synchronized (_counterLock) {
if (_recvCounter == nTests * 2 && _closeCounter == nTests) {
break;
}
}
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
_log.info("Receive counter is: " + _recvCounter + " Close counter is: " + _closeCounter);
}
/* Return, assuming the test has passed. */
_log.info("Unit test passed.");
}
private static void startAlice(String host, int port, String conOptions) {
_log.info("\n\nStarting up Alice");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Alice " + conOptions + "\n";
out.write(DataHelper.getASCII(req));
line = reader.readLine();
_log.info("Response to creating the session with destination Alice: " + line);
req = "NAMING LOOKUP NAME=ME\n";
out.write(DataHelper.getASCII(req));
line = reader.readLine();
Properties props = SAMUtils.parseParams(line);
String value = props.getProperty("VALUE");
if (value == null) {
_log.error("No value for ME found! [" + line + "]");
return;
} else {
_log.info("Alice is located at " + value);
}
_alice = value;
I2PThread aliceThread = new I2PThread(new AliceRunner(reader, out, s));
aliceThread.setName("Alice");
aliceThread.start();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static class AliceRunner implements Runnable {
private BufferedReader _reader;
private OutputStream _out;
private Socket _s;
/** ID (string) to base64 destination */
private Map _streams;
public AliceRunner(BufferedReader reader, OutputStream out, Socket s) {
_reader = reader;
_out = out;
_s = s;
_streams = Collections.synchronizedMap(new HashMap(4));
}
public void run() {
while (!_dead) {
try {
doRun();
} catch (Exception e) {
_log.error("Error running alice", e);
try { _reader.close(); } catch (IOException ioe) {}
try { _out.close(); } catch (IOException ioe) {}
try { _s.close(); } catch (IOException ioe) {}
_streams.clear();
_dead = true;
}
}
}
private void doRun() throws IOException, SAMException {
String line = _reader.readLine();
_log.debug("Read: " + line);
Properties props = SAMUtils.parseParams(line);
String maj = props.getProperty(SAMUtils.COMMAND);
String min = props.getProperty(SAMUtils.OPCODE);
if ( ("STREAM".equals(maj)) && ("CONNECTED".equals(min)) ) {
String dest = props.getProperty("DESTINATION");
String id = props.getProperty("ID");
if ( (dest == null) || (id == null) ) {
_log.error("Invalid STREAM CONNECTED line: [" + line + "]");
return;
}
dest = dest.trim();
id = id.trim();
_streams.put(id, dest);
} else if ( ("STREAM".equals(maj)) && ("CLOSED".equals(min)) ) {
String id = props.getProperty("ID");
if (id == null) {
_log.error("Invalid STREAM CLOSED line: [" + line + "]");
return;
}
_streams.remove(id);
} else if ( ("STREAM".equals(maj)) && ("RECEIVED".equals(min)) ) {
String id = props.getProperty("ID");
String size = props.getProperty("SIZE");
if ( (id == null) || (size == null) ) {
_log.error("Invalid STREAM RECEIVED line: [" + line + "]");
return;
}
id = id.trim();
size = size.trim();
int payloadSize = -1;
try {
payloadSize = Integer.parseInt(size);
} catch (NumberFormatException nfe) {
_log.error("Invalid SIZE in message [" + size + "]");
return;
}
// i know, its bytes, but this test uses chars
char payload[] = new char[payloadSize];
int read = _reader.read(payload);
if (read != payloadSize) {
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
return;
}
_log.info("\n== Received from the stream " + id + ": [" + new String(payload) + "]");
synchronized (_counterLock) {
_recvCounter++;
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
/*
// now echo it back
String reply = "STREAM SEND ID=" + id +
" SIZE=" + payloadSize +
"\n" + new String(payload);
_out.write(reply.getBytes());
_out.flush();
_log.info("Reply sent back [" + new String(reply.getBytes()) + "]");
*/
} else {
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
return;
}
}
}
private static void testBob(String sessionName, String host, int port, String conOptions) {
I2PThread t = new I2PThread(new TestBob(sessionName, host, port, conOptions), sessionName);
t.start();
}
private static class TestBob implements Runnable {
private String _sessionName;
private String _host;
private int _port;
private String _opts;
public TestBob(String name, String host, int port, String opts) {
_sessionName = name;
_host = host;
_port = port;
_opts = opts;
}
public void run() {
doTestBob(_sessionName, _host, _port, _opts);
}
}
private static void doTestBob(String sessionName, String host, int port, String conOptions) {
_log.info("\n\nTesting " + sessionName + "\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + sessionName + " " + conOptions + "\n";
out.write(DataHelper.getASCII(req));
line = reader.readLine();
_log.info("Response to creating the session with destination "+ sessionName+": " + line);
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
out.write(DataHelper.getASCII(req));
line = reader.readLine();
_log.info("Response to the stream connect from "+sessionName+" to Alice: " + line);
Properties props = SAMUtils.parseParams(line);
_log.info("props = " + props);
String result = props.getProperty("RESULT");
if (!("OK".equals(result))) {
_log.error("Unable to connect!");
//_dead = true;
return;
}
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
_log.info("\n** Sending BlahBlah!!");
out.write(DataHelper.getASCII(req));
out.flush();
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nFooBarBaz!";
_log.info("\n** Sending FooBarBaz!");
out.write(DataHelper.getASCII(req));
out.flush();
/* Don't delay here, so we can test whether all data is
sent even if we do a STREAM CLOSE immediately. */
_log.info("Sending close");
req = "STREAM CLOSE ID=42\n";
out.write(DataHelper.getASCII(req));
out.flush();
synchronized (_counterLock) {
_closeCounter++;
}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
//_dead = true;
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=10001 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
//try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
//System.exit(0);
}
}