package net.i2p.sam.client;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
/**
* Swiss army knife tester.
* Saves our transient b64 destination to myKeyFile where SAMStreamSend can get it.
* Saves received data to a file (in sinkDir).
*
* Usage: SAMStreamSink [options] myKeyFile sinkDir
*
* See apps/sam/doc/README-test.txt for info on test setup.
* Receives data in one of 7 modes.
* Optionally uses SSL.
* Configurable SAM client version.
*
*/
public class SAMStreamSink {
private final I2PAppContext _context;
private final Log _log;
private final String _samHost;
private final String _samPort;
private final String _destFile;
private final String _sinkDir;
private String _conOptions;
private SAMReader _reader, _reader2;
private boolean _isV3;
private boolean _isV32;
private String _v3ID;
/** Connection id (Integer) to peer (Flooder) */
private final Map<String, Sink> _remotePeers;
private static I2PSSLSocketFactory _sslSocketFactory;
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6, FORWARDSSL=7;
private static final int MASTER=8;
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort]\n" +
" [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
" modes: stream: 0; datagram: 1; v1datagram: 2;\n" +
" raw: 3; v1raw: 4; raw-with-headers: 5;\n" +
" stream-forward: 6; stream-forward-ssl: 7\n" +
" default is stream\n" +
" -s: use SSL to connect to bridge\n" +
" -x: use master session (forces -v 3.3)\n" +
" multiple -o session options are allowed";
private static final int V3FORWARDPORT=9998;
private static final int V3DGPORT=9999;
public static void main(String args[]) {
Getopt g = new Getopt("SAM", args, "sxhb:m:p:u:v:w:");
boolean isSSL = false;
boolean isMaster = false;
int mode = STREAM;
String version = "3.3";
String host = "127.0.0.1";
String port = "7656";
String user = null;
String password = null;
String opts = "inbound.length=0 outbound.length=0";
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
case 's':
isSSL = true;
break;
case 'x':
isMaster = true;
break;
case 'm':
mode = Integer.parseInt(g.getOptarg());
if (mode < 0 || mode > FORWARDSSL) {
System.err.println(USAGE);
return;
}
break;
case 'v':
version = g.getOptarg();
break;
case 'b':
host = g.getOptarg();
break;
case 'o':
opts = opts + ' ' + g.getOptarg();
break;
case 'p':
port = g.getOptarg();
break;
case 'u':
user = g.getOptarg();
break;
case 'w':
password = g.getOptarg();
break;
case 'h':
case '?':
case ':':
default:
System.err.println(USAGE);
return;
} // switch
} // while
int startArgs = g.getOptind();
if (args.length - startArgs != 2) {
System.err.println(USAGE);
return;
}
if (isMaster) {
mode += MASTER;
version = "3.3";
}
if ((user == null && password != null) ||
(user != null && password == null)) {
System.err.println("both user and password or neither");
return;
}
if (user != null && password != null && VersionComparator.comp(version, "3.2") < 0) {
System.err.println("user/password require 3.2");
return;
}
I2PAppContext ctx = I2PAppContext.getGlobalContext();
SAMStreamSink sink = new SAMStreamSink(ctx, host, port,
args[startArgs], args[startArgs + 1]);
sink.startup(version, isSSL, mode, user, password, opts);
}
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
_context = ctx;
_log = ctx.logManager().getLog(SAMStreamSink.class);
_samHost = samHost;
_samPort = samPort;
_destFile = destFile;
_sinkDir = sinkDir;
_conOptions = "";
_remotePeers = new HashMap<String, Sink>();
}
public void startup(String version, boolean isSSL, int mode, String user, String password, String sessionOpts) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Starting up");
try {
Socket sock = connect(isSSL);
OutputStream out = sock.getOutputStream();
SAMEventHandler eventHandler = new SinkEventHandler(_context, out);
_reader = new SAMReader(_context, sock.getInputStream(), eventHandler);
_reader.startReading();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reader created");
String ourDest = handshake(out, version, true, eventHandler, mode, user, password, sessionOpts);
if (mode >= MASTER)
mode -= MASTER;
if (ourDest == null)
throw new IOException("handshake failed");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handshake complete. we are " + ourDest);
if (_isV32) {
_log.debug("Starting pinger");
Thread t = new Pinger(out);
t.start();
}
if (_isV3 && (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)) {
// test multiple acceptors, only works in 3.2
int acceptors = (_isV32 && mode == STREAM) ? 4 : 1;
for (int i = 0; i < acceptors; i++) {
Socket sock2 = connect(isSSL);
out = sock2.getOutputStream();
eventHandler = new SinkEventHandler2(_context, sock2.getInputStream(), out);
_reader2 = new SAMReader(_context, sock2.getInputStream(), eventHandler);
_reader2.startReading();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reader " + (2 + i) + " created");
String ok = handshake(out, version, false, eventHandler, mode, user, password, "");
if (ok == null)
throw new IOException("handshake " + (2 + i) + " failed");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handshake " + (2 + i) + " complete.");
}
if (mode == FORWARD) {
// set up a listening ServerSocket
(new FwdRcvr(false, null)).start();
} else if (mode == FORWARDSSL) {
// set up a listening ServerSocket
String scfile = SSLUtil.DEFAULT_SAMCLIENT_CONFIGFILE;
File file = new File(scfile);
Properties opts = new Properties();
if (file.exists())
DataHelper.loadProps(opts, file);
boolean shouldSave = SSLUtil.verifyKeyStore(opts);
if (shouldSave)
DataHelper.storeProps(opts, file);
(new FwdRcvr(true, opts)).start();
}
} else if (_isV3 && (mode == DG || mode == RAW || mode == RAWHDR)) {
// set up a listening DatagramSocket
(new DGRcvr(mode)).start();
}
writeDest(ourDest);
} catch (IOException e) {
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
}
}
private class DGRcvr extends I2PAppThread {
private final int _mode;
public DGRcvr(int mode) {
super("SAM DG Rcvr");
_mode = mode;
}
public void run() {
byte[] buf = new byte[32768];
try {
Sink sink = new Sink("FAKE", "FAKEFROM");
DatagramSocket dg = new DatagramSocket(V3DGPORT);
while (true) {
DatagramPacket p = new DatagramPacket(buf, 32768);
dg.receive(p);
int len = p.getLength();
int off = p.getOffset();
byte[] data = p.getData();
_log.info("Got datagram length " + len);
if (_mode == DG || _mode == RAWHDR) {
ByteArrayInputStream bais = new ByteArrayInputStream(data, off, len);
String line = DataHelper.readLine(bais);
if (line == null) {
_log.error("DGRcvr no header line");
continue;
}
if (_mode == DG && line.length() < 516) {
_log.error("DGRcvr line too short: \"" + line + '\n');
continue;
}
String[] parts = line.split(" ");
int i = 0;
if (_mode == DG) {
String dest = parts[0];
_log.info("DG is from " + dest);
i++;
}
for ( ; i < parts.length; i++) {
_log.info("Parameter: " + parts[i]);
}
int left = bais.available();
sink.received(data, off + len - left, left);
} else {
sink.received(data, off, len);
}
}
} catch (IOException ioe) {
_log.error("DGRcvr", ioe);
}
}
}
private class FwdRcvr extends I2PAppThread {
private final boolean _isSSL;
// for SSL only
private final Properties _opts;
public FwdRcvr(boolean isSSL, Properties opts) {
super("SAM Fwd Rcvr");
_isSSL = isSSL;
_opts = opts;
}
public void run() {
try {
ServerSocket ss;
if (_isSSL) {
SSLServerSocketFactory fact = SSLUtil.initializeFactory(_opts);
SSLServerSocket sock = (SSLServerSocket) fact.createServerSocket(V3FORWARDPORT);
I2PSSLSocketFactory.setProtocolsAndCiphers(sock);
ss = sock;
} else {
ss = new ServerSocket(V3FORWARDPORT);
}
while (true) {
Socket s = ss.accept();
Sink sink = new Sink("FAKE", "FAKEFROM");
try {
InputStream in = s.getInputStream();
boolean gotDest = false;
byte[] dest = new byte[1024];
int dlen = 0;
byte[] buf = new byte[32768];
int len;
while((len = in.read(buf)) >= 0) {
if (!gotDest) {
// eat the dest line
for (int i = 0; i < len; i++) {
byte b = buf[i];
if (b == (byte) '\n') {
gotDest = true;
if (_log.shouldInfo()) {
try {
_log.info("Got incoming accept from: \"" + new String(dest, 0, dlen, "ISO-8859-1") + '"');
} catch (IOException uee) {}
}
// feed any remaining to the sink
i++;
if (i < len)
sink.received(buf, i, len - i);
break;
} else {
if (dlen < dest.length) {
dest[dlen++] = b;
} else if (dlen == dest.length) {
dlen++;
_log.error("first line overflow on accept");
}
}
}
} else {
sink.received(buf, 0, len);
}
}
sink.closed();
} catch (IOException ioe) {
_log.error("Fwdcvr", ioe);
}
}
} catch (IOException ioe) {
_log.error("Fwdcvr", ioe);
}
}
}
private static class Pinger extends I2PAppThread {
private final OutputStream _out;
public Pinger(OutputStream out) {
super("SAM Sink Pinger");
setDaemon(true);
_out = out;
}
public void run() {
while (true) {
try {
Thread.sleep(127*1000);
synchronized(_out) {
_out.write(DataHelper.getUTF8("PING " + System.currentTimeMillis() + '\n'));
_out.flush();
}
} catch (InterruptedException ie) {
break;
} catch (IOException ioe) {
break;
}
}
}
}
private class SinkEventHandler extends SAMEventHandler {
protected final OutputStream _out;
public SinkEventHandler(I2PAppContext ctx, OutputStream out) {
super(ctx);
_out = out;
}
@Override
public void streamClosedReceived(String result, String id, String message) {
Sink sink;
synchronized (_remotePeers) {
sink = _remotePeers.remove(id);
}
if (sink != null) {
sink.closed();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
} else {
_log.error("not connected to " + id + " but we were just closed?");
}
}
@Override
public void streamDataReceived(String id, byte data[], int offset, int length) {
Sink sink;
synchronized (_remotePeers) {
sink = _remotePeers.get(id);
}
if (sink != null) {
sink.received(data, offset, length);
} else {
_log.error("not connected to " + id + " but we received " + length + "?");
}
}
@Override
public void streamConnectedReceived(String dest, String id) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connection " + id + " received from " + dest);
try {
Sink sink = new Sink(id, dest);
synchronized (_remotePeers) {
_remotePeers.put(id, sink);
}
} catch (IOException ioe) {
_log.error("Error creating a new sink", ioe);
}
}
@Override
public void pingReceived(String data) {
if (_log.shouldInfo())
_log.info("Got PING " + data + ", sending PONG " + data);
synchronized (_out) {
try {
_out.write(("PONG " + data + '\n').getBytes("UTF-8"));
_out.flush();
} catch (IOException ioe) {
_log.error("PONG fail", ioe);
}
}
}
@Override
public void datagramReceived(String dest, byte[] data, int offset, int length, int fromPort, int toPort) {
// just get the first
Sink sink;
synchronized (_remotePeers) {
if (_remotePeers.isEmpty()) {
_log.error("not connected but we received datagram " + length + "?");
return;
}
sink = _remotePeers.values().iterator().next();
}
sink.received(data, offset, length);
}
@Override
public void rawReceived(byte[] data, int offset, int length, int fromPort, int toPort, int protocol) {
// just get the first
Sink sink;
synchronized (_remotePeers) {
if (_remotePeers.isEmpty()) {
_log.error("not connected but we received raw " + length + "?");
return;
}
sink = _remotePeers.values().iterator().next();
}
sink.received(data, offset, length);
}
}
private class SinkEventHandler2 extends SinkEventHandler {
private final InputStream _in;
public SinkEventHandler2(I2PAppContext ctx, InputStream in, OutputStream out) {
super(ctx, out);
_in = in;
}
@Override
public void streamStatusReceived(String result, String id, String message) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("got STREAM STATUS, result=" + result);
super.streamStatusReceived(result, id, message);
Sink sink = null;
try {
String dest = "TODO_if_not_silent";
sink = new Sink(_v3ID, dest);
synchronized (_remotePeers) {
_remotePeers.put(_v3ID, sink);
}
} catch (IOException ioe) {
_log.error("Error creating a new sink", ioe);
try { _in.close(); } catch (IOException ioe2) {}
if (sink != null)
sink.closed();
return;
}
// inline so the reader doesn't grab the data
try {
boolean gotDest = false;
byte[] dest = new byte[1024];
int dlen = 0;
byte buf[] = new byte[4096];
int len;
while((len = _in.read(buf)) >= 0) {
if (!gotDest) {
// eat the dest line
for (int i = 0; i < len; i++) {
byte b = buf[i];
if (b == (byte) '\n') {
gotDest = true;
if (_log.shouldInfo()) {
try {
_log.info("Got incoming accept from: \"" + new String(dest, 0, dlen, "ISO-8859-1") + '"');
} catch (IOException uee) {}
}
// feed any remaining to the sink
i++;
if (i < len)
sink.received(buf, i, len - i);
break;
} else {
if (dlen < dest.length) {
dest[dlen++] = b;
} else if (dlen == dest.length) {
dlen++;
_log.error("first line overflow on accept");
}
}
}
} else {
sink.received(buf, 0, len);
}
}
sink.closed();
} catch (IOException ioe) {
_log.error("Error reading", ioe);
} finally {
try { _in.close(); } catch (IOException ioe) {}
}
}
}
private Socket connect(boolean isSSL) throws IOException {
int port = Integer.parseInt(_samPort);
if (!isSSL)
return new Socket(_samHost, port);
synchronized(SAMStreamSink.class) {
if (_sslSocketFactory == null) {
try {
_sslSocketFactory = new I2PSSLSocketFactory(
_context, true, "certificates/sam");
} catch (GeneralSecurityException gse) {
throw new IOException("SSL error", gse);
}
}
}
SSLSocket sock = (SSLSocket) _sslSocketFactory.createSocket(_samHost, port);
I2PSSLSocketFactory.verifyHostname(_context, sock, _samHost);
return sock;
}
/**
* @param isMaster is this the control socket
* @return our b64 dest or null
*/
private String handshake(OutputStream samOut, String version, boolean isMaster,
SAMEventHandler eventHandler, int mode, String user, String password,
String sopts) {
synchronized (samOut) {
try {
if (user != null && password != null)
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes("UTF-8"));
else
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes("UTF-8"));
samOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Hello sent");
String hisVersion = eventHandler.waitForHelloReply();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Hello reply found: " + hisVersion);
if (hisVersion == null)
throw new IOException("Hello failed");
if (!isMaster) {
// only for v3
//String req = "STREAM ACCEPT SILENT=true ID=" + _v3ID + "\n";
// TO_PORT not supported until 3.2 but 3.0-3.1 will ignore
String req;
if (mode == STREAM)
req = "STREAM ACCEPT SILENT=false TO_PORT=5678 ID=" + _v3ID + "\n";
else if (mode == FORWARD)
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + '\n';
else if (mode == FORWARDSSL)
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + " SSL=true\n";
else
throw new IllegalStateException("mode " + mode);
samOut.write(req.getBytes("UTF-8"));
samOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("STREAM ACCEPT/FORWARD sent");
if (mode == FORWARD || mode == FORWARDSSL) {
// docs were wrong, we do not get a STREAM STATUS if SILENT=true for ACCEPT
boolean ok = eventHandler.waitForStreamStatusReply();
if (!ok)
throw new IOException("Stream status failed");
if (_log.shouldLog(Log.DEBUG))
_log.debug("got STREAM STATUS, awaiting connection");
}
return "OK";
}
_isV3 = VersionComparator.comp(hisVersion, "3") >= 0;
String dest;
if (_isV3) {
_isV32 = VersionComparator.comp(hisVersion, "3.2") >= 0;
// we use the filename as the name in sam.keys
// and read it in ourselves
File keys = new File("sam.keys");
if (keys.exists()) {
Properties opts = new Properties();
DataHelper.loadProps(opts, keys);
String s = opts.getProperty(_destFile);
if (s != null) {
dest = s;
} else {
dest = "TRANSIENT";
(new File(_destFile)).delete();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Requesting new transient destination");
}
} else {
dest = "TRANSIENT";
(new File(_destFile)).delete();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Requesting new transient destination");
}
if (isMaster) {
byte[] id = new byte[5];
_context.random().nextBytes(id);
_v3ID = Base32.encode(id);
_conOptions = "ID=" + _v3ID;
}
} else {
// we use the filename as the name in sam.keys
// and give it to the SAM server
dest = _destFile;
}
boolean masterMode; // are we using v3.3 master session
String command;
if (mode >= MASTER) {
masterMode = true;
command = "ADD";
mode -= MASTER;
} else {
masterMode = false;
command = "CREATE DESTINATION=" + dest;
}
String style;
if (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)
style = "STREAM";
else if (mode == V1DG)
style = "DATAGRAM";
else if (mode == DG)
style = "DATAGRAM PORT=" + V3DGPORT;
else if (mode == V1RAW)
style = "RAW";
else if (mode == RAW)
style = "RAW PORT=" + V3DGPORT;
else
style = "RAW HEADER=true PORT=" + V3DGPORT;
if (masterMode) {
if (mode == V1DG || mode == V1RAW)
throw new IllegalArgumentException("v1 dg/raw incompatible with master session");
String req = "SESSION CREATE DESTINATION=" + dest + " STYLE=MASTER ID=masterSink " + sopts + '\n';
samOut.write(req.getBytes("UTF-8"));
samOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("SESSION CREATE STYLE=MASTER sent");
boolean ok = eventHandler.waitForSessionCreateReply();
if (!ok)
throw new IOException("SESSION CREATE STYLE=MASTER failed");
if (_log.shouldLog(Log.DEBUG))
_log.debug("SESSION CREATE STYLE=MASTER reply found: " + ok);
}
String req = "SESSION " + command + " STYLE=" + style + ' ' + _conOptions + ' ' + sopts + '\n';
samOut.write(req.getBytes("UTF-8"));
samOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("SESSION " + command + " sent");
//if (mode == STREAM) {
boolean ok;
if (masterMode)
ok = eventHandler.waitForSessionAddReply();
else
ok = eventHandler.waitForSessionCreateReply();
if (!ok)
throw new IOException("SESSION " + command + " failed");
if (_log.shouldLog(Log.DEBUG))
_log.debug("SESSION " + command + " reply found: " + ok);
//}
if (masterMode) {
// do a bunch more
req = "SESSION ADD STYLE=STREAM FROM_PORT=99 ID=stream99\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION ADD STYLE=STREAM FROM_PORT=98 ID=stream98\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION ADD STYLE=DATAGRAM PORT=9997 LISTEN_PORT=97 ID=dg97\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION ADD STYLE=DATAGRAM PORT=9996 FROM_PORT=96 ID=dg96\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION ADD STYLE=RAW PORT=9995 LISTEN_PORT=95 ID=raw95\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION ADD STYLE=RAW PORT=9994 FROM_PORT=94 LISTEN_PROTOCOL=222 ID=raw94\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION REMOVE ID=stream99\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION REMOVE ID=raw95\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION REMOVE ID=notfound\n";
samOut.write(req.getBytes("UTF-8"));
req = "SESSION REMOVE ID=masterSink\n"; // shouldn't remove ourselves
samOut.write(req.getBytes("UTF-8"));
samOut.flush();
}
req = "NAMING LOOKUP NAME=ME\n";
samOut.write(req.getBytes("UTF-8"));
samOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Naming lookup sent");
String destination = eventHandler.waitForNamingReply("ME");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Naming lookup reply found: " + destination);
if (destination == null) {
_log.error("No naming lookup reply found!");
return null;
}
if (_log.shouldInfo())
_log.info(_destFile + " is located at " + destination);
if (mode == V1DG || mode == V1RAW) {
// fake it so the sink starts
eventHandler.streamConnectedReceived(destination, "FAKE");
}
return destination;
} catch (IOException e) {
_log.error("Error handshaking", e);
return null;
}
}
}
private boolean writeDest(String dest) {
File f = new File(_destFile);
/*
if (f.exists()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Destination file exists, not overwriting: " + _destFile);
return false;
}
*/
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
fos.write(dest.getBytes("UTF-8"));
if (_log.shouldLog(Log.DEBUG))
_log.debug("My destination written to " + _destFile);
} catch (IOException e) {
_log.error("Error writing to " + _destFile, e);
return false;
} finally {
if(fos != null) try { fos.close(); } catch(IOException ioe) {}
}
return true;
}
private class Sink {
private final String _connectionId;
private final String _remoteDestination;
private volatile boolean _closed;
private final long _started;
private long _lastReceivedOn;
private final OutputStream _out;
public Sink(String conId, String remDest) throws IOException {
_connectionId = conId;
_remoteDestination = remDest;
_closed = false;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("sink." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("sink." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("sink." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
File sinkDir = new File(_sinkDir);
if (!sinkDir.exists())
sinkDir.mkdirs();
File out = File.createTempFile("sink", ".dat", sinkDir);
if (_log.shouldWarn())
_log.warn("outputting to " + out);
_out = new FileOutputStream(out);
_started = _context.clock().now();
}
public String getConnectionId() { return _connectionId; }
public String getDestination() { return _remoteDestination; }
public void closed() {
if (_closed) return;
_closed = true;
long lifetime = _context.clock().now() - _started;
_context.statManager().addRateData("sink." + _connectionId + ".lifetime", lifetime, lifetime);
try {
_out.close();
} catch (IOException ioe) {
_log.info("Error closing", ioe);
}
}
public void received(byte data[], int offset, int len) {
if (_closed) return;
try {
_out.write(data, offset, len);
} catch (IOException ioe) {
_log.error("Error writing received data");
closed();
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received " + len + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
+ "ms with " + _remoteDestination.substring(0,6));
_lastReceivedOn = _context.clock().now();
}
}
}