forked from I2P_Developers/i2p.i2p
Compare commits
118 Commits
i2p_0_3_0_
...
i2p_0_3_1_
Author | SHA1 | Date | |
---|---|---|---|
61c97ab940 | |||
b0a1b3b5ca | |||
4c7af01edc | |||
0d431213cd | |||
ad9dd9a2e2 | |||
c7895ed905 | |||
57d7979d51 | |||
61f6871cd1 | |||
406048f7b9 | |||
6dd5b0fe45 | |||
fd4bc5e3cf | |||
3bab2d8957 | |||
af2f5cd2e1 | |||
d4bb32da82 | |||
08aca6ca61 | |||
697b3c6772 | |||
878525ced8 | |||
418531736b | |||
6c175440c6 | |||
723a2f2008 | |||
ea9b9fbf17 | |||
303e257841 | |||
07b6a8ba92 | |||
e216e18368 | |||
e57c5b4bc2 | |||
f772d6ddeb | |||
cd37c301d9 | |||
f5fa26639e | |||
45ec73c115 | |||
13952ebd8b | |||
2df0007a10 | |||
89bc5db3e1 | |||
4021deec7f | |||
a3977f37f7 | |||
766c12242e | |||
a82b951aff | |||
997a94eecc | |||
635535aac2 | |||
25314fd91a | |||
9a06a5758d | |||
e5a2a9644f | |||
e0e7211852 | |||
cdaeb4d176 | |||
07aa2e280d | |||
6c4bc67ff3 | |||
d9f0cc27ef | |||
59aec9d289 | |||
451f4c503d | |||
cd82089d4d | |||
3db8b63cde | |||
6edf5d1e4f | |||
a23fa6fadd | |||
51eb77e409 | |||
691326cea8 | |||
3cac1238ed | |||
b04512a4f6 | |||
3a4d0549aa | |||
d7467f5dc3 | |||
141902b86d | |||
5aa680fc93 | |||
a790117f5a | |||
2a5a52c810 | |||
2156f4c2f3 | |||
2585460286 | |||
1b4af66986 | |||
0324bac044 | |||
2bfbe1ca27 | |||
60584228d9 | |||
44e34f7b11 | |||
7912050647 | |||
2231abd407 | |||
8d17ba4d66 | |||
8244bdb440 | |||
bc3b7ffd86 | |||
e22cb62493 | |||
e923aa1f72 | |||
68a21f1fbb | |||
74209e2607 | |||
1a38271104 | |||
e5ab5d6a5a | |||
2745ff727f | |||
24ea383937 | |||
9cb11d4d5f | |||
7202ea3340 | |||
d234ea01d0 | |||
e246cd37dd | |||
0a4ddedac9 | |||
0e4b80b002 | |||
a460a0dc44 | |||
f7212112b8 | |||
86d55b32a6 | |||
4b0d1aac15 | |||
fb7c06aa01 | |||
5c41be3470 | |||
a78df1a152 | |||
34e8db0fe3 | |||
70faecb8b5 | |||
237f278479 | |||
e766a00a12 | |||
ea03637ec1 | |||
d0f6d47b14 | |||
5bf1658d9a | |||
4ce9fb5b5a | |||
f80f02da73 | |||
52ece833a7 | |||
1ad6dde146 | |||
64bcfd23bd | |||
d659447879 | |||
e73eb55d75 | |||
a52cea29f4 | |||
3d91e59386 | |||
393b1d7674 | |||
c29a6b95ae | |||
567a4e8361 | |||
4d3e4c1a15 | |||
afeecdf4af | |||
4fe7105e2f | |||
d7c3a53f2d |
@ -4,7 +4,6 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
@ -84,8 +83,8 @@ class ClientEngine {
|
||||
/** fire off a new ping */
|
||||
private void doSend() {
|
||||
long now = Clock.getInstance().now();
|
||||
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
|
||||
_data.addPing(now);
|
||||
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
|
||||
}
|
||||
|
||||
/** our actual heartbeat pumper - this drives the test */
|
||||
|
@ -59,7 +59,7 @@ public class PeerDataWriter {
|
||||
String header = getHeader(data);
|
||||
|
||||
out.write(header.getBytes());
|
||||
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\n".getBytes());
|
||||
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\troundTrip\n".getBytes());
|
||||
for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next();
|
||||
String line = getEvent(point);
|
||||
@ -84,12 +84,15 @@ public class PeerDataWriter {
|
||||
buf.append("lifetimeSent \t").append(data.getLifetimeSent()).append('\n');
|
||||
buf.append("lifetimeRecv \t").append(data.getLifetimeReceived()).append('\n');
|
||||
int periods[] = data.getAveragePeriods();
|
||||
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\n");
|
||||
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\troundTrip\n");
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
buf.append("periodAverage\t").append(periods[i]).append('\t');
|
||||
buf.append(getNum(data.getAverageSendTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getAverageReceiveTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getLostMessages(periods[i]))).append('\n');
|
||||
buf.append(getNum(data.getLostMessages(periods[i]))).append('\t');
|
||||
double rtt = data.getAverageSendTime(periods[i])
|
||||
+ data.getAverageReceiveTime(periods[i]);
|
||||
buf.append(getNum(rtt)).append('\n');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
@ -105,6 +108,7 @@ public class PeerDataWriter {
|
||||
if (point.getWasPonged()) {
|
||||
buf.append(point.getPongSent() - point.getPingSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPongSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPingSent()).append('\t');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
|
@ -10,7 +10,6 @@ import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
|
||||
class HeartbeatMonitorGUI extends JFrame {
|
||||
private HeartbeatMonitor _monitor;
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -1,26 +1,23 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.List;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
import org.jfree.data.MovingAverage;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYDotRenderer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.Font;
|
||||
import java.awt.Color;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class);
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
@ -1,21 +1,14 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JLabel;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.heartbeat.PeerDataWriter;
|
||||
import net.i2p.util.Log;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
|
@ -53,7 +53,7 @@ public class HTTPListener extends Thread {
|
||||
private boolean proxyUsed = false;
|
||||
|
||||
/**
|
||||
* Query whether this is the first use of the proxy or not . . .
|
||||
* Query whether this is the first use of the proxy or not
|
||||
* @return Whether this is the first proxy use, no doubt.
|
||||
*/
|
||||
public boolean firstProxyUse() {
|
||||
|
@ -19,6 +19,7 @@ import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handler for browsing Eepsites.
|
||||
@ -26,6 +27,7 @@ import net.i2p.util.Log;
|
||||
public class EepHandler {
|
||||
|
||||
private static final Log _log = new Log(EepHandler.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
protected ErrorHandler errorHandler;
|
||||
|
||||
@ -44,7 +46,7 @@ public class EepHandler {
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
/* boolean fromProxy, */String destination) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = NamingService.getInstance().lookup(destination);
|
||||
Destination dest = _context.namingService().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
|
||||
return;
|
||||
@ -66,8 +68,8 @@ public class EepHandler {
|
||||
* @return boolean, true if something was written, false otherwise.
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean handle(Request req, Filter f, OutputStream out, Destination dest, I2PSocketManager sm)
|
||||
throws IOException {
|
||||
public boolean handle(Request req, Filter f, OutputStream out, Destination dest,
|
||||
I2PSocketManager sm) throws IOException {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
|
@ -12,6 +12,7 @@ import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handler for proxying "normal" HTTP requests.
|
||||
@ -19,6 +20,7 @@ import net.i2p.util.Log;
|
||||
public class ProxyHandler extends EepHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/* package private */ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
@ -31,7 +33,7 @@ public class ProxyHandler extends EepHandler {
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
@ -48,6 +50,6 @@ public class ProxyHandler extends EepHandler {
|
||||
|
||||
private Destination findProxy() {
|
||||
//FIXME!
|
||||
return NamingService.getInstance().lookup("squid.i2p");
|
||||
return _context.namingService().lookup("squid.i2p");
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ public class RootHandler {
|
||||
private static RootHandler instance;
|
||||
|
||||
/**
|
||||
* Singleton stuff . . .
|
||||
* Singleton stuff
|
||||
* @return the one and only instance, yay!
|
||||
*/
|
||||
public static synchronized RootHandler getInstance() {
|
||||
|
@ -49,6 +49,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
@ -63,8 +64,9 @@ import net.i2p.util.EventDispatcherImpl;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnel implements Logging, EventDispatcher {
|
||||
private final static Log _log = new Log(I2PTunnel.class);
|
||||
private final EventDispatcherImpl _event = new EventDispatcherImpl();
|
||||
private Log _log;
|
||||
private EventDispatcherImpl _event;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public static final int PACKET_DELAY = 100;
|
||||
|
||||
@ -74,6 +76,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
public static String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
public static String listenHost = host;
|
||||
|
||||
public static long readTimeout = -1;
|
||||
|
||||
private static final String nocli_args[] = { "-nocli", "-die"};
|
||||
|
||||
private List tasks = new ArrayList();
|
||||
@ -94,6 +98,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
|
||||
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
|
||||
_context = new I2PAppContext();
|
||||
_log = _context.logManager().getLog(I2PTunnel.class);
|
||||
_event = new EventDispatcherImpl();
|
||||
addConnectionEventListener(lsnr);
|
||||
boolean gui = true;
|
||||
boolean checkRunByE = true;
|
||||
@ -201,6 +208,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runConfig(args, l);
|
||||
} else if ("listen_on".equals(cmdname)) {
|
||||
runListenOn(args, l);
|
||||
} else if ("read_timeout".equals(cmdname)) {
|
||||
runReadTimeout(args, l);
|
||||
} else if ("genkeys".equals(cmdname)) {
|
||||
runGenKeys(args, l);
|
||||
} else if ("gentextkeys".equals(cmdname)) {
|
||||
@ -235,6 +244,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("Command list:");
|
||||
l.log("config <i2phost> <i2pport>");
|
||||
l.log("listen_on <ip>");
|
||||
l.log("read_timeout <msecs>");
|
||||
l.log("owndest yes|no");
|
||||
l.log("ping <args>");
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
@ -291,10 +301,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this);
|
||||
addtask(task);
|
||||
notifyEvent("serverTaskId", new Integer(task.getId()));
|
||||
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", new Integer(serv.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
@ -336,10 +347,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
task = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this);
|
||||
addtask(task);
|
||||
notifyEvent("serverTaskId", new Integer(task.getId()));
|
||||
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", new Integer(serv.getId()));
|
||||
} else {
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
|
||||
@ -510,7 +522,31 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
} else {
|
||||
l.log("listen_on <ip>");
|
||||
l.log(" sets the interface to listen for the I2PClient.");
|
||||
notifyEvent("listen_onResult", "ok");
|
||||
notifyEvent("listen_onResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the read timeout going to be used for newly-created I2PSockets
|
||||
*
|
||||
* Sets the event "read_timeoutResult" = "ok" or "error" after the interface has been specified
|
||||
*
|
||||
* @param args {hostname}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runReadTimeout(String args[], Logging l) {
|
||||
if (args.length == 1) {
|
||||
try {
|
||||
readTimeout = Long.parseLong(args[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
notifyEvent("read_timeoutResult", "error");
|
||||
}
|
||||
notifyEvent("read_timeoutResult", "ok");
|
||||
} else {
|
||||
l.log("read_timeout <msecs>");
|
||||
l.log(" sets the read timeout (in milliseconds) for I2P connections\n"
|
||||
+" Negative values will make the connections wait forever");
|
||||
notifyEvent("read_timeoutResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -888,6 +924,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
|
||||
if ((name == null) || (name.trim().length() <= 0)) throw new DataFormatException("Empty destination provided");
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
Log log = ctx.logManager().getLog(I2PTunnel.class);
|
||||
|
||||
if (name.startsWith("file:")) {
|
||||
Destination result = new Destination();
|
||||
byte content[] = null;
|
||||
@ -911,19 +950,21 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
result.fromByteArray(content);
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info("File is not a binary destination - trying base64");
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("File is not a binary destination - trying base64");
|
||||
try {
|
||||
byte decoded[] = Base64.decode(new String(content));
|
||||
result.fromByteArray(decoded);
|
||||
return result;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("File is not a base64 destination either - failing!");
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("File is not a base64 destination either - failing!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ask naming service
|
||||
NamingService inst = NamingService.getInstance();
|
||||
NamingService inst = ctx.namingService();
|
||||
return inst.lookup(name);
|
||||
}
|
||||
}
|
||||
@ -1000,4 +1041,4 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
public Object waitEventValue(String n) {
|
||||
return _event.waitEventValue(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
protected Destination dest;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelClient(int localPort, String destination, Logging l, boolean ownDest, EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender");
|
||||
@ -45,9 +47,13 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
notifyEvent("openClientResult", "ok");
|
||||
}
|
||||
|
||||
public void setReadTimeout(long ms) { readTimeout = ms; }
|
||||
public long getReadTimeout() { return readTimeout; }
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
|
@ -33,6 +33,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
|
||||
private static volatile long __clientId = 0;
|
||||
private long _clientId;
|
||||
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
||||
private I2PSocketManager sockMgr;
|
||||
private List mySockets = new ArrayList();
|
||||
@ -60,9 +62,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName) {
|
||||
super(localPort + " (uninitialized)", notifyThis);
|
||||
_clientId = ++__clientId;
|
||||
this.localPort = localPort;
|
||||
this.l = l;
|
||||
this.handlerName = handlerName;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
@ -75,7 +78,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
l.log("I2P session created");
|
||||
|
||||
Thread t = new I2PThread(this);
|
||||
t.setName("Client");
|
||||
t.setName("Client " + _clientId);
|
||||
listenerReady = false;
|
||||
t.start();
|
||||
open = true;
|
||||
@ -179,8 +182,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
I2PSocket i2ps;
|
||||
|
||||
i2ps = sockMgr.connect(dest, opt);
|
||||
synchronized (sockLock) {
|
||||
i2ps = sockMgr.connect(dest, opt);
|
||||
mySockets.add(i2ps);
|
||||
}
|
||||
|
||||
@ -272,12 +275,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __runnerId = 0;
|
||||
|
||||
public class ClientConnectionRunner extends I2PThread {
|
||||
private Socket s;
|
||||
|
||||
public ClientConnectionRunner(Socket s, String name) {
|
||||
this.s = s;
|
||||
setName(name);
|
||||
setName(name + '.' + (++__runnerId));
|
||||
start();
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,9 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@ -20,6 +23,25 @@ import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Act as a mini HTTP proxy, handling various different types of requests,
|
||||
* forwarding them through I2P appropriately, and displaying the reply. Supported
|
||||
* request formats are: <pre>
|
||||
* $method http://$site[$port]/$path $protocolVersion
|
||||
* or
|
||||
* $method $path $protocolVersion\nHost: $site
|
||||
* or
|
||||
* $method http://i2p/$site/$path $protocolVersion
|
||||
* or
|
||||
* $method /$site/$path $protocolVersion
|
||||
* </pre>
|
||||
*
|
||||
* If the $site resolves with the I2P naming service, then it is directed towards
|
||||
* that eepsite, otherwise it is directed towards this client's outproxy (typically
|
||||
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
|
||||
* and POST have been tested, though other $methods should work.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
|
||||
|
||||
@ -43,7 +65,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"That Desitination was not found. Perhaps you pasted in the wrong "+
|
||||
"BASE64 I2P Destination or the link you are following is bad. "+
|
||||
"The host (or the WWW proxy, if you're using one) could also be "+
|
||||
"temporarily offline. "+
|
||||
"temporarily offline. You may want to <b>retry</b>. "+
|
||||
"Could not find the following Destination:<BR><BR>")
|
||||
.getBytes();
|
||||
|
||||
@ -53,19 +75,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"Cache-control: no-cache\r\n\r\n"+
|
||||
"<html><body><H1>I2P ERROR: TIMEOUT</H1>"+
|
||||
"That Desitination was reachable, but timed out getting a "+
|
||||
"response. This may be a temporary error, so you should simply "+
|
||||
"response. This is likely a temporary error, so you should simply "+
|
||||
"try to refresh, though if the problem persists, the remote "+
|
||||
"destination may have issues. Could not get a response from "+
|
||||
"the following Destination:<BR><BR>")
|
||||
.getBytes();
|
||||
|
||||
// public I2PTunnelHTTPClient(int localPort, Logging l,
|
||||
// boolean ownDest, String wwwProxy) {
|
||||
// this(localPort, l, ownDest, wwwProxy, (EventDispatcher)null);
|
||||
// }
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, boolean ownDest, String wwwProxy, EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId));
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openHTTPClientResult", "error");
|
||||
@ -92,7 +112,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Line=[" + line + "]");
|
||||
|
||||
if (line.startsWith("Connection: ") ||
|
||||
line.startsWith("Keep-Alive: ") ||
|
||||
line.startsWith("Proxy-Connection: "))
|
||||
continue;
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Method is null for [" + line + "]");
|
||||
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
@ -126,6 +157,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
// The request must be forwarded to a WWW proxy
|
||||
destination = wwwProxy;
|
||||
usingWWWProxy = true;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
@ -150,14 +183,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.debug("HOST :" + host + ":");
|
||||
_log.debug("DEST :" + destination + ":");
|
||||
}
|
||||
|
||||
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO)) _log.info("Setting host = " + host);
|
||||
|
||||
} else {
|
||||
if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting host = " + host);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
}
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
if (line.length() == 0) break;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
@ -176,6 +220,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Destination: " + destination);
|
||||
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
@ -211,6 +259,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
|
||||
private static volatile long __timeoutId = 0;
|
||||
|
||||
private class InactivityTimeoutThread extends I2PThread {
|
||||
|
||||
@ -230,7 +279,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_targetRequest = targetRequest;
|
||||
_useWWWProxy = useWWWProxy;
|
||||
_disabled = false;
|
||||
setName("InactivityThread");
|
||||
long timeoutId = ++__timeoutId;
|
||||
setName("InactivityThread " + timeoutId);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
|
@ -6,6 +6,7 @@ package net.i2p.i2ptunnel;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
@ -20,6 +21,8 @@ import net.i2p.util.Log;
|
||||
public class I2PTunnelRunner extends I2PThread {
|
||||
private final static Log _log = new Log(I2PTunnelRunner.class);
|
||||
|
||||
private static volatile long __runnerId;
|
||||
private long _runnerId;
|
||||
/**
|
||||
* max bytes streamed in a packet - smaller ones might be filled
|
||||
* up to this size. Larger ones are not split (at least not on
|
||||
@ -50,7 +53,8 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
lastActivityOn = -1;
|
||||
startedOn = -1;
|
||||
_log.info("I2PTunnelRunner started");
|
||||
setName("I2PTunnelRunner");
|
||||
_runnerId = ++__runnerId;
|
||||
setName("I2PTunnelRunner " + _runnerId);
|
||||
start();
|
||||
}
|
||||
|
||||
@ -116,7 +120,7 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
_log.error("Interrupted", ex);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.error("Error forwarding", ex);
|
||||
_log.debug("Error forwarding", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
@ -128,6 +132,8 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
}
|
||||
}
|
||||
|
||||
private volatile long __forwarderId = 0;
|
||||
|
||||
private class StreamForwarder extends I2PThread {
|
||||
|
||||
InputStream in;
|
||||
@ -136,7 +142,7 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
private StreamForwarder(InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
setName("StreamForwarder");
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
}
|
||||
|
||||
@ -163,11 +169,14 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
} catch (SocketException ex) {
|
||||
// this *will* occur when the other threads closes the socket
|
||||
synchronized (finishLock) {
|
||||
if (!finished)
|
||||
_log.error("Error reading and writing", ex);
|
||||
else
|
||||
_log.warn("You may ignore this", ex);
|
||||
if (!finished) {
|
||||
_log.debug("Socket closed - error reading and writing",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedIOException ex) {
|
||||
_log.warn("Closing connection due to timeout (error: \""
|
||||
+ ex.getMessage() + "\")");
|
||||
} catch (IOException ex) {
|
||||
if (!finished)
|
||||
_log.error("Error forwarding", ex);
|
||||
@ -188,4 +197,4 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private Logging l;
|
||||
|
||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
private long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
@ -57,8 +61,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis) {
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis);
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
@ -78,11 +81,40 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
l.log("Ready!");
|
||||
notifyEvent("openServerResult", "ok");
|
||||
open = true;
|
||||
}
|
||||
|
||||
|
||||
private static volatile long __serverId = 0;
|
||||
|
||||
/**
|
||||
* Start running the I2PTunnelServer.
|
||||
*
|
||||
*/
|
||||
public void startRunning() {
|
||||
Thread t = new I2PThread(this);
|
||||
t.setName("Server");
|
||||
t.setName("Server " + (++__serverId));
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read idle timeout for newly-created connections (in
|
||||
* milliseconds). After this time expires without data being reached from
|
||||
* the I2P network, the connection itself will be closed.
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
readTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the read idle timeout for newly-created connections (in
|
||||
* milliseconds).
|
||||
*
|
||||
* @return The read timeout used for connections
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
if (!open) return true;
|
||||
synchronized (lock) {
|
||||
@ -115,6 +147,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
new I2PTunnelRunner(s, i2ps, slock, null);
|
||||
} catch (SocketException ex) {
|
||||
|
@ -4,71 +4,148 @@ import java.net.ConnectException;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Initial stub implementation for the server socket
|
||||
* Server socket implementation, allowing multiple threads to accept I2PSockets
|
||||
* and pull from a queue populated by various threads (each of whom have their own
|
||||
* timeout)
|
||||
*
|
||||
*/
|
||||
class I2PServerSocketImpl implements I2PServerSocket {
|
||||
private final static Log _log = new Log(I2PServerSocketImpl.class);
|
||||
private I2PSocketManager mgr;
|
||||
private I2PSocket cached = null; // buffer one socket here
|
||||
|
||||
private boolean closing = false; // Are we being closed?
|
||||
|
||||
private Object acceptLock = new Object();
|
||||
|
||||
/** list of sockets waiting for the client to accept them */
|
||||
private List pendingSockets = Collections.synchronizedList(new ArrayList(4));
|
||||
|
||||
/** have we been closed */
|
||||
private volatile boolean closing = false;
|
||||
|
||||
/** lock on this when accepting a pending socket, and wait on it for notification of acceptance */
|
||||
private Object socketAcceptedLock = new Object();
|
||||
/** lock on this when adding a new socket to the pending list, and wait on it accordingly */
|
||||
private Object socketAddedLock = new Object();
|
||||
|
||||
public I2PServerSocketImpl(I2PSocketManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
public synchronized I2PSocket accept() throws I2PException, ConnectException {
|
||||
I2PSocket ret;
|
||||
|
||||
synchronized (acceptLock) {
|
||||
while ((cached == null) && !closing) {
|
||||
myWait();
|
||||
}
|
||||
|
||||
if (closing) {
|
||||
throw new ConnectException("I2PServerSocket closed");
|
||||
}
|
||||
|
||||
ret = cached;
|
||||
cached = null;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
|
||||
_log.debug("TIMING: handed out accept result " + ret.hashCode());
|
||||
|
||||
/**
|
||||
* Waits for the next socket connecting. If a remote user tried to make a
|
||||
* connection and the local application wasn't .accept()ing new connections,
|
||||
* they should get refused (if .accept() doesnt occur in some small period -
|
||||
* currently 5 seconds)
|
||||
*
|
||||
* @return a connected I2PSocket
|
||||
*
|
||||
* @throws I2PException if there is a problem with reading a new socket
|
||||
* from the data available (aka the I2PSession closed, etc)
|
||||
* @throws ConnectException if the I2PServerSocket is closed
|
||||
*/
|
||||
public I2PSocket accept() throws I2PException, ConnectException {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("accept() called, pending: " + pendingSockets.size());
|
||||
|
||||
I2PSocket ret = null;
|
||||
|
||||
while ( (ret == null) && (!closing) ){
|
||||
while (pendingSockets.size() <= 0) {
|
||||
if (closing) throw new ConnectException("I2PServerSocket closed");
|
||||
try {
|
||||
synchronized(socketAddedLock) {
|
||||
socketAddedLock.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
synchronized (pendingSockets) {
|
||||
if (pendingSockets.size() > 0) {
|
||||
ret = (I2PSocket)pendingSockets.remove(0);
|
||||
}
|
||||
}
|
||||
if (ret != null) {
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: handed out accept result " + ret.hashCode());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean getNewSocket(I2PSocket s) {
|
||||
synchronized (acceptLock) {
|
||||
while (cached != null) {
|
||||
myWait();
|
||||
}
|
||||
cached = s;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make the socket available and wait until the client app accepts it, or until
|
||||
* the given timeout elapses. This doesn't have any limits on the queue size -
|
||||
* perhaps it should add some choking (e.g. after 5 waiting for accept, refuse)
|
||||
*
|
||||
* @param timeoutMs how long to wait until accept
|
||||
* @return true if the socket was accepted, false if the timeout expired
|
||||
* or the socket was closed
|
||||
*/
|
||||
public boolean addWaitForAccept(I2PSocket s, long timeoutMs) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("addWaitForAccept [new socket arrived, pending: " + pendingSockets.size());
|
||||
|
||||
if (closing) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already closing the socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
Clock clock = I2PAppContext.getGlobalContext().clock();
|
||||
long start = clock.now();
|
||||
long end = start + timeoutMs;
|
||||
pendingSockets.add(s);
|
||||
synchronized (socketAddedLock) {
|
||||
socketAddedLock.notifyAll();
|
||||
}
|
||||
|
||||
// keep looping until the socket has been grabbed by the accept()
|
||||
// (or the expiration passes, or the socket is closed)
|
||||
while (pendingSockets.contains(s)) {
|
||||
long now = clock.now();
|
||||
if (now >= end) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Expired while waiting for accept (time elapsed =" + (now - start) + "ms");
|
||||
pendingSockets.remove(s);
|
||||
return false;
|
||||
}
|
||||
if (closing) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Server socket closed while waiting for accept");
|
||||
pendingSockets.remove(s);
|
||||
return false;
|
||||
}
|
||||
long remaining = end - now;
|
||||
try {
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.wait(remaining);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
long now = clock.now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.info("Socket accepted after " + (now-start) + "ms");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close() throws I2PException {
|
||||
synchronized (acceptLock) {
|
||||
closing = true;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketManager getManager() {
|
||||
return mgr;
|
||||
}
|
||||
|
||||
private void myWait() {
|
||||
try {
|
||||
acceptLock.wait();
|
||||
} catch (InterruptedException ex) {}
|
||||
|
||||
public void close() {
|
||||
closing = true;
|
||||
// let anyone .accept()ing know to fsck off
|
||||
synchronized (socketAddedLock) {
|
||||
socketAddedLock.notifyAll();
|
||||
}
|
||||
// let anyone addWaitForAccept()ing know to fsck off
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketManager getManager() { return mgr; }
|
||||
}
|
||||
|
@ -32,8 +32,22 @@ public interface I2PSocket {
|
||||
*/
|
||||
public OutputStream getOutputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* How long we will wait blocked on a read() operation.
|
||||
*
|
||||
* @return milliseconds to wait, or -1 if we will wait indefinitely
|
||||
*/
|
||||
public long getReadTimeout();
|
||||
|
||||
/**
|
||||
* Define how long we will wait blocked on a read() operation (-1 will make
|
||||
* the socket wait forever).
|
||||
*
|
||||
*/
|
||||
public void setReadTimeout(long ms);
|
||||
|
||||
/**
|
||||
* Closes the socket if not closed yet
|
||||
*/
|
||||
public void close() throws IOException;
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,32 @@ class I2PSocketImpl implements I2PSocket {
|
||||
private I2POutputStream out;
|
||||
private boolean outgoing;
|
||||
private Object flagLock = new Object();
|
||||
private boolean closed = false, sendClose = true, closed2 = false;
|
||||
|
||||
/**
|
||||
* Whether the I2P socket has already been closed.
|
||||
*/
|
||||
private boolean closed = false;
|
||||
|
||||
/**
|
||||
* Whether to send out a close packet when the socket is
|
||||
* closed. (If the socket is closed because of an incoming close
|
||||
* packet, we need not send one.)
|
||||
*/
|
||||
private boolean sendClose = true;
|
||||
|
||||
/**
|
||||
* Whether the I2P socket has already been closed and all data
|
||||
* (from I2P to the app, dunno whether to call this incoming or
|
||||
* outgoing) has been processed.
|
||||
*/
|
||||
private boolean closed2 = false;
|
||||
|
||||
/**
|
||||
* @param peer who this socket is (or should be) connected to
|
||||
* @param mgr how we talk to the network
|
||||
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
|
||||
* @param localID what is our half of the socket ID?
|
||||
*/
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
|
||||
this.outgoing = outgoing;
|
||||
manager = mgr;
|
||||
@ -45,10 +69,17 @@ class I2PSocketImpl implements I2PSocket {
|
||||
this.localID = localID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our half of the socket's unique ID
|
||||
*
|
||||
*/
|
||||
public String getLocalID() {
|
||||
return localID;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received the other side's half of the socket's unique ID
|
||||
*/
|
||||
public void setRemoteID(String id) {
|
||||
synchronized (remoteIDWaiter) {
|
||||
remoteID = id;
|
||||
@ -56,36 +87,71 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID(boolean wait) throws InterruptedIOException {
|
||||
return getRemoteID(wait, -1);
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it
|
||||
* isn't known yet
|
||||
*
|
||||
* @param wait if true, we should wait until we receive it from the peer, otherwise
|
||||
* return what we know immediately (which may be null)
|
||||
*/
|
||||
public String getRemoteID(boolean wait) {
|
||||
try {
|
||||
return getRemoteID(wait, -1);
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("wtf, we said we didn't want it to time out! you smell", iie);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it isn't
|
||||
* known yet and we were instructed not to wait
|
||||
*
|
||||
* @param wait should we wait for the peer to send us their half of the ID, or
|
||||
* just return immediately?
|
||||
* @param maxWait if we're going to wait, after how long should we timeout and fail?
|
||||
* (if this value is < 0, we wait indefinitely)
|
||||
* @throws InterruptedIOException when the max waiting period has been exceeded
|
||||
*/
|
||||
public String getRemoteID(boolean wait, long maxWait) throws InterruptedIOException {
|
||||
long dieAfter = System.currentTimeMillis() + maxWait;
|
||||
synchronized (remoteIDWaiter) {
|
||||
if (wait) {
|
||||
try {
|
||||
if (maxWait > 0)
|
||||
if (maxWait >= 0)
|
||||
remoteIDWaiter.wait(maxWait);
|
||||
else
|
||||
remoteIDWaiter.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
|
||||
if ((maxWait > 0) && (System.currentTimeMillis() > dieAfter))
|
||||
if ((maxWait >= 0) && (System.currentTimeMillis() >= dieAfter))
|
||||
throw new InterruptedIOException("Timed out waiting for remote ID");
|
||||
|
||||
_log.debug("TIMING: RemoteID set to " + I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: RemoteID set to "
|
||||
+ I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID() throws InterruptedIOException {
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it
|
||||
* isn't known yet. This does not wait
|
||||
*
|
||||
*/
|
||||
public String getRemoteID() {
|
||||
return getRemoteID(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The other side has given us some data, so inject it into our socket's
|
||||
* inputStream
|
||||
*
|
||||
* @param data the data to inject into our local inputStream
|
||||
*/
|
||||
public void queueData(byte[] data) {
|
||||
in.queueData(data);
|
||||
}
|
||||
@ -121,7 +187,8 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket if not closed yet
|
||||
* Closes the socket if not closed yet (from the Application
|
||||
* side).
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized (flagLock) {
|
||||
@ -132,7 +199,10 @@ class I2PSocketImpl implements I2PSocket {
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
public void internalClose() {
|
||||
/**
|
||||
* Close the socket from the I2P side, e. g. by a close packet.
|
||||
*/
|
||||
protected void internalClose() {
|
||||
synchronized (flagLock) {
|
||||
closed = true;
|
||||
closed2 = true;
|
||||
@ -143,14 +213,40 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
private byte getMask(int add) {
|
||||
return (byte) ((outgoing ? (byte) 0xA0 : (byte) 0x50) + (byte) add);
|
||||
if (outgoing)
|
||||
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
|
||||
else
|
||||
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data? If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return in.getReadTimeout();
|
||||
}
|
||||
|
||||
public void setReadTimeout(long ms) {
|
||||
in.setReadTimeout(ms);
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
public class I2PInputStream extends InputStream {
|
||||
private class I2PInputStream extends InputStream {
|
||||
|
||||
private ByteCollector bc = new ByteCollector();
|
||||
|
||||
private long readTimeout = -1;
|
||||
|
||||
public long getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public void setReadTimeout(long ms) {
|
||||
readTimeout = ms;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
int res = read(b);
|
||||
@ -162,30 +258,43 @@ class I2PSocketImpl implements I2PSocket {
|
||||
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
||||
_log.debug("Read called: " + this.hashCode());
|
||||
if (len == 0) return 0;
|
||||
long dieAfter = System.currentTimeMillis() + readTimeout;
|
||||
byte[] read = bc.startToByteArray(len);
|
||||
boolean timedOut = false;
|
||||
|
||||
while (read.length == 0) {
|
||||
synchronized (flagLock) {
|
||||
if (closed) {
|
||||
_log.debug("Closed is set, so closing stream: " + this.hashCode());
|
||||
_log.debug("Closed is set, so closing stream: " + hashCode());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
if (readTimeout >= 0) {
|
||||
wait(readTimeout);
|
||||
} else {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException ex) {}
|
||||
|
||||
if ((readTimeout >= 0)
|
||||
&& (System.currentTimeMillis() >= dieAfter)) {
|
||||
throw new InterruptedIOException("Timeout reading from I2PSocket (" + readTimeout + " msecs)");
|
||||
}
|
||||
|
||||
read = bc.startToByteArray(len);
|
||||
}
|
||||
if (read.length > len) throw new RuntimeException("BUG");
|
||||
System.arraycopy(read, 0, b, off, read.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Read from I2PInputStream " + this.hashCode() + " returned " + read.length + " bytes");
|
||||
_log.debug("Read from I2PInputStream " + hashCode() + " returned "
|
||||
+ read.length + " bytes");
|
||||
}
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// _log.debug("Read from I2PInputStream " + this.hashCode()
|
||||
// + " returned "+read.length+" bytes:\n"
|
||||
// + HexDump.dump(read));
|
||||
// + " returned "+read.length+" bytes:\n"
|
||||
// + HexDump.dump(read));
|
||||
//}
|
||||
return read.length;
|
||||
}
|
||||
@ -199,18 +308,24 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
public synchronized void queueData(byte[] data, int off, int len) {
|
||||
_log.debug("Insert " + len + " bytes into queue: " + this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Insert " + len + " bytes into queue: " + hashCode());
|
||||
bc.append(data, off, len);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized void notifyClosed() {
|
||||
notifyAll();
|
||||
I2PInputStream.this.notifyAll();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
notifyClosed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class I2POutputStream extends OutputStream {
|
||||
private class I2POutputStream extends OutputStream {
|
||||
|
||||
public I2PInputStream sendTo;
|
||||
|
||||
@ -231,79 +346,96 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public class I2PSocketRunner extends I2PThread {
|
||||
private static volatile long __runnerId = 0;
|
||||
private class I2PSocketRunner extends I2PThread {
|
||||
|
||||
public InputStream in;
|
||||
|
||||
public I2PSocketRunner(InputStream in) {
|
||||
_log.debug("Runner's input stream is: " + in.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner's input stream is: " + in.hashCode());
|
||||
this.in = in;
|
||||
setName("SocketRunner from " + I2PSocketImpl.this.remote.calculateHash().toBase64().substring(0, 4));
|
||||
String peer = I2PSocketImpl.this.remote.calculateHash().toBase64();
|
||||
setName("SocketRunner " + (++__runnerId) + " " + peer.substring(0, 4));
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pump some more data
|
||||
*
|
||||
* @return true if we should keep on handling, false otherwise
|
||||
*/
|
||||
private boolean handleNextPacket(ByteCollector bc, byte buffer[])
|
||||
throws IOException, I2PSessionException {
|
||||
int len = in.read(buffer);
|
||||
int bcsize = bc.getCurrentSize();
|
||||
if (len != -1) {
|
||||
bc.append(buffer, len);
|
||||
} else if (bcsize == 0) {
|
||||
// nothing left in the buffer, and read(..) got EOF (-1).
|
||||
// the bart the
|
||||
return false;
|
||||
}
|
||||
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner Point d: " + hashCode());
|
||||
|
||||
try {
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
_log.warn("wtf", e);
|
||||
}
|
||||
}
|
||||
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
|
||||
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
|
||||
if (data.length > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message size is: " + data.length);
|
||||
boolean sent = sendBlock(data);
|
||||
if (!sent) {
|
||||
_log.error("Error sending message to peer. Killing socket runner");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[MAX_PACKET_SIZE];
|
||||
ByteCollector bc = new ByteCollector();
|
||||
boolean sent = true;
|
||||
boolean keepHandling = true;
|
||||
int packetsHandled = 0;
|
||||
try {
|
||||
int len, bcsize;
|
||||
// try {
|
||||
while (true) {
|
||||
len = in.read(buffer);
|
||||
bcsize = bc.getCurrentSize();
|
||||
if (len != -1) {
|
||||
bc.append(buffer, len);
|
||||
} else if (bcsize == 0) {
|
||||
break;
|
||||
}
|
||||
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
|
||||
_log.debug("Runner Point d: " + this.hashCode());
|
||||
|
||||
try {
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
|
||||
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
|
||||
if (data.length > 0) {
|
||||
_log.debug("Message size is: " + data.length);
|
||||
sent = sendBlock(data);
|
||||
if (!sent) {
|
||||
_log.error("Error sending message to peer. Killing socket runner");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// try {
|
||||
while (keepHandling) {
|
||||
keepHandling = handleNextPacket(bc, buffer);
|
||||
packetsHandled++;
|
||||
}
|
||||
if ((bc.getCurrentSize() > 0) && sent) {
|
||||
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: " + in.hashCode() + "; "
|
||||
if ((bc.getCurrentSize() > 0) && (packetsHandled > 1)) {
|
||||
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: "
|
||||
+ in.hashCode() + "; "
|
||||
+ "queue size: " + bc.getCurrentSize() + ")");
|
||||
}
|
||||
synchronized (flagLock) {
|
||||
closed2 = true;
|
||||
}
|
||||
// } catch (IOException ex) {
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("Error reading and writing", ex);
|
||||
// }
|
||||
boolean sc;
|
||||
synchronized (flagLock) {
|
||||
sc = sendClose;
|
||||
} // FIXME: Race here?
|
||||
if (sc) {
|
||||
_log.info("Sending close packet: " + outgoing);
|
||||
byte[] packet = I2PSocketManager.makePacket((byte) (getMask(0x02)), remoteID, new byte[0]);
|
||||
synchronized (manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending close packet: " + outgoing);
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
boolean sent = manager.getSession().sendMessage(remote, packet);
|
||||
if (!sent) {
|
||||
_log.error("Error sending close packet to peer");
|
||||
}
|
||||
}
|
||||
manager.removeSocket(I2PSocketImpl.this);
|
||||
} catch (InterruptedIOException ex) {
|
||||
_log.error("BUG! read() operations should not timeout!", ex);
|
||||
} catch (IOException ex) {
|
||||
// WHOEVER removes this event on inconsistent
|
||||
// state before fixing the inconsistent state (a
|
||||
@ -316,7 +448,8 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
private boolean sendBlock(byte data[]) throws I2PSessionException {
|
||||
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
|
||||
if (remoteID == null) {
|
||||
_log.error("NULL REMOTEID");
|
||||
return false;
|
||||
@ -326,9 +459,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
synchronized (flagLock) {
|
||||
if (closed2) return false;
|
||||
}
|
||||
synchronized (manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
return sent;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -40,11 +41,27 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManager() {
|
||||
_session = null;
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
@ -55,15 +72,25 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.error("Disconnected from the session");
|
||||
_log.info("Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error("Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
@ -77,157 +104,276 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = new String(new byte[] { msg[1], msg[2], msg[3]}, "ISO-8859-1");
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
_log.debug("Message read: type = [" + Integer.toHexString(type) + "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: " + payload.length + "]");
|
||||
synchronized (lock) {
|
||||
switch (type) {
|
||||
case 0x51:
|
||||
// ACK outgoing
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s == null) {
|
||||
_log.warn("No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
if (payload.length == 3 && s.getRemoteID(false) == null) {
|
||||
String newID = new String(payload, "ISO-8859-1");
|
||||
s.setRemoteID(newID);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length != 3)
|
||||
_log.warn("Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn("Remote ID already exists? " + s.getRemoteID());
|
||||
return;
|
||||
}
|
||||
case 0x52:
|
||||
// disconnect outgoing
|
||||
_log.debug("*Disconnect outgoing!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug("Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
}
|
||||
case 0x50:
|
||||
// packet send outgoing
|
||||
_log.debug("*Packet send outgoing [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xA1:
|
||||
// SYN incoming
|
||||
_log.debug("*Syn!");
|
||||
String newLocalID = makeID(_inSockets);
|
||||
Destination d = new Destination();
|
||||
d.readBytes(new ByteArrayInputStream(payload));
|
||||
|
||||
if (_serverSocket == null) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) 0x52, id, newLocalID.getBytes("ISO-8859-1"));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message", new Exception("Failed creation"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
if (_serverSocket.getNewSocket(s)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) 0x51, id, newLocalID.getBytes("ISO-8859-1"));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message", new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
}
|
||||
} else {
|
||||
byte[] packet = (" " + id).getBytes("ISO-8859-1");
|
||||
packet[0] = 0x52;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.error("Error sending NACK for session creation");
|
||||
}
|
||||
s.internalClose();
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case 0xA2:
|
||||
// disconnect incoming
|
||||
_log.debug("*Disconnect incoming!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
_inSockets.remove(id);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length > 0) _log.warn("Disconnect packet had " + payload.length + " bytes");
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
case 0xA0:
|
||||
// packet send incoming
|
||||
_log.debug("*Packet send incoming [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xFF:
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
_log.error("\n\n=============== Unknown packet! " + "============" + "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id) + "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.error("Error processing", ise);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing", ioe);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug("Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn("No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
s.setRemoteID(newID);
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn("Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn("Remote ID already exists? " + remoteId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug("*Disconnect outgoing!");
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug("Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("*Packet send outgoing [" + payload.length + "]");
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
_log.debug("*Syn!");
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn("Error sending NACK for session creation");
|
||||
}
|
||||
s.internalClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
_log.debug("*Disconnect incoming!");
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error("Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("*Packet send incoming [" + payload.length + "]");
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error("\n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error("Abuse reported [" + severity + "]");
|
||||
}
|
||||
@ -258,25 +404,24 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(s.getLocalID(), s);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) 0xA1, localID, pubkey.toByteArray());
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
synchronized (_session) {
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
}
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info("Unable to send & receive ack for SYN packet");
|
||||
synchronized (lock) {
|
||||
@ -285,32 +430,51 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
}
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
if (remoteID == null) { throw new ConnectException("Connection refused by peer"); }
|
||||
if ("".equals(remoteID)) { throw new NoRouteToHostException("Unable to reach peer"); }
|
||||
_log.debug("TIMING: s given out for remoteID " + getReadableForm(remoteID));
|
||||
|
||||
if (remoteID == null)
|
||||
throw new ConnectException("Connection refused by peer");
|
||||
if ("".equals(remoteID))
|
||||
throw new NoRouteToHostException("Unable to reach peer");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID));
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
_log.error("Timeout waiting for ack from syn for id " + getReadableForm(lcID), ioe);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Timeout waiting for ack from syn for id "
|
||||
+ getReadableForm(lcID), ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.error("Unhandled error connecting", e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +488,8 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
@ -334,14 +499,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
|
||||
try {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error closing I2PServerSocket", ex);
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
@ -353,8 +513,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
_log.debug("Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
@ -362,8 +523,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
_log.debug("Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
@ -406,7 +568,7 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) 0xFF});
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.error("I2PException:", ex);
|
||||
return false;
|
||||
@ -415,8 +577,8 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
synchronized (lock) {
|
||||
_log.debug("Removing socket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing socket \"" + getReadableForm(sock.getLocalID()) + "\"");
|
||||
_inSockets.remove(sock.getLocalID());
|
||||
_outSockets.remove(sock.getLocalID());
|
||||
lock.notify();
|
||||
@ -424,14 +586,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
}
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
try {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(id.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,22 +596,17 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
public static String makeID(HashMap uniqueIn) {
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
try {
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = new String(nid, "ISO-8859-1");
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -462,17 +614,28 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = id.getBytes("ISO-8859-1");
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error building the packet", ex);
|
||||
return new byte[0];
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.i2p.client.streaming;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
* No options available...
|
||||
*
|
||||
*/
|
||||
public class I2PSocketOptions {
|
||||
@ -21,7 +20,11 @@ public class I2PSocketOptions {
|
||||
return _connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define how long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
*/
|
||||
public void setConnectTimeout(long ms) {
|
||||
_connectTimeout = ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,57 +33,61 @@ class PeerSummaryReader {
|
||||
PeerSummary summary = null;
|
||||
String curDescription = null;
|
||||
List curArgs = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
}
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handling the data", e);
|
||||
throw new IOException("Error parsing the data");
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
|
58
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs
Normal file
58
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
//
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
//
|
||||
[assembly: AssemblyTitle("")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
//
|
||||
// In order to sign your assembly you must specify a key to use. Refer to the
|
||||
// Microsoft .NET Framework documentation for more information on assembly signing.
|
||||
//
|
||||
// Use the attributes below to control which key is used for signing.
|
||||
//
|
||||
// Notes:
|
||||
// (*) If no key is specified, the assembly is not signed.
|
||||
// (*) KeyName refers to a key that has been installed in the Crypto Service
|
||||
// Provider (CSP) on your machine. KeyFile refers to a file which contains
|
||||
// a key.
|
||||
// (*) If the KeyFile and the KeyName values are both specified, the
|
||||
// following processing occurs:
|
||||
// (1) If the KeyName can be found in the CSP, that key is used.
|
||||
// (2) If the KeyName does not exist and the KeyFile does exist, the key
|
||||
// in the KeyFile is installed into the CSP and used.
|
||||
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
|
||||
// When specifying the KeyFile, the location of the KeyFile should be
|
||||
// relative to the project output directory which is
|
||||
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
|
||||
// located in the project directory, you would specify the AssemblyKeyFile
|
||||
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
|
||||
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
|
||||
// documentation for more information on this.
|
||||
//
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("")]
|
||||
[assembly: AssemblyKeyName("")]
|
50
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs
Normal file
50
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
|
||||
namespace SAM.NET
|
||||
{
|
||||
class SAMTester
|
||||
{
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
new SAMTester();
|
||||
}
|
||||
public SAMTester ()
|
||||
{
|
||||
SAMConnection connection1 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
|
||||
SAMSession session1 = new SAMSession(connection1,SAM.NET.SamSocketType.Stream,"alice");
|
||||
|
||||
SAMConnection connection2 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
|
||||
SAMSession session2 = new SAMSession(connection2,SAM.NET.SamSocketType.Stream,"bob");
|
||||
|
||||
SAMStream stream1 = new SAMStream(connection1,session1,233);
|
||||
stream1.Connect(session2.getKey());
|
||||
|
||||
//Wait till we are connected to destination
|
||||
while (!stream1.isConnected)
|
||||
Thread.Sleep(1000);
|
||||
|
||||
//Send some bytes
|
||||
stream1.Write(Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString() + "Hi!!!!!!"));
|
||||
|
||||
//Wait till a stream magically appears on the other side
|
||||
while (session2.getStreams().Count == 0) Thread.Sleep(1000);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
foreach (SAMStream stream in session2.getStreams().Values)
|
||||
{
|
||||
Console.WriteLine("Text received on " + stream.getID() + " at " + DateTime.Now.ToLongTimeString());
|
||||
Console.WriteLine(Encoding.ASCII.GetString(stream.ReadToEnd()));
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
stream1.Close();
|
||||
connection1.Close();
|
||||
connection2.Close();
|
||||
}
|
||||
}
|
||||
}
|
58
apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs
Normal file
58
apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
//
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
//
|
||||
[assembly: AssemblyTitle("")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
//
|
||||
// In order to sign your assembly you must specify a key to use. Refer to the
|
||||
// Microsoft .NET Framework documentation for more information on assembly signing.
|
||||
//
|
||||
// Use the attributes below to control which key is used for signing.
|
||||
//
|
||||
// Notes:
|
||||
// (*) If no key is specified, the assembly is not signed.
|
||||
// (*) KeyName refers to a key that has been installed in the Crypto Service
|
||||
// Provider (CSP) on your machine. KeyFile refers to a file which contains
|
||||
// a key.
|
||||
// (*) If the KeyFile and the KeyName values are both specified, the
|
||||
// following processing occurs:
|
||||
// (1) If the KeyName can be found in the CSP, that key is used.
|
||||
// (2) If the KeyName does not exist and the KeyFile does exist, the key
|
||||
// in the KeyFile is installed into the CSP and used.
|
||||
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
|
||||
// When specifying the KeyFile, the location of the KeyFile should be
|
||||
// relative to the project output directory which is
|
||||
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
|
||||
// located in the project directory, you would specify the AssemblyKeyFile
|
||||
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
|
||||
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
|
||||
// documentation for more information on this.
|
||||
//
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("")]
|
||||
[assembly: AssemblyKeyName("")]
|
271
apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs
Normal file
271
apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs
Normal file
@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
|
||||
namespace SAM.NET
|
||||
{
|
||||
public enum SamSocketType
|
||||
{
|
||||
Stream,
|
||||
Datagram,
|
||||
Raw
|
||||
}
|
||||
|
||||
public class SAMConnection
|
||||
{
|
||||
private const string propertyMinVersion = "1.0";
|
||||
private const string propertyMaxVersion = "1.0";
|
||||
|
||||
private Socket _sock;
|
||||
private NetworkStream _sockStream;
|
||||
private StreamReader _sockStreamIn;
|
||||
private StreamWriter _sockStreamOut;
|
||||
|
||||
public SAMConnection(IPAddress routerIP, int port)
|
||||
{
|
||||
_sock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
|
||||
IPEndPoint rEP = new IPEndPoint(routerIP,port);
|
||||
_sock.Connect(rEP);
|
||||
_sockStream = new NetworkStream(_sock);
|
||||
_sockStreamIn = new StreamReader(_sockStream);
|
||||
_sockStreamOut = new StreamWriter(_sockStream);
|
||||
try
|
||||
{
|
||||
sendVersion(propertyMinVersion,propertyMinVersion);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sock.Close();
|
||||
throw (new Exception("No SAM for you :("));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendVersion(string min, string max)
|
||||
{
|
||||
_sockStreamOut.WriteLine("HELLO VERSION MIN=" + propertyMinVersion + " MAX=" + propertyMaxVersion);
|
||||
_sockStreamOut.Flush();
|
||||
Hashtable response = SAMUtil.parseKeyValues(_sockStreamIn.ReadLine(),2);
|
||||
if (response["RESULT"].ToString() != "OK") throw (new Exception("Version mismatch"));
|
||||
}
|
||||
|
||||
public StreamWriter getOutputStream()
|
||||
{
|
||||
return _sockStreamOut;
|
||||
}
|
||||
|
||||
public StreamReader getInputStream()
|
||||
{
|
||||
return _sockStreamIn;
|
||||
}
|
||||
|
||||
public NetworkStream getStream()
|
||||
{
|
||||
return _sockStream;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_sock.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Creating a SAMSession object will automatically:
|
||||
* 1) create a sesion on SAM
|
||||
* 1) query for the base64key
|
||||
* 2) start a listening thread to catch all stream commands
|
||||
*/
|
||||
public class SAMSession
|
||||
{
|
||||
private Hashtable _streams;
|
||||
private string _sessionKey;
|
||||
|
||||
public SAMSession (SAMConnection connection, SamSocketType type, string destination)
|
||||
{
|
||||
_streams = new Hashtable();
|
||||
StreamWriter writer = connection.getOutputStream();
|
||||
StreamReader reader = connection.getInputStream();
|
||||
writer.WriteLine("SESSION CREATE STYLE=STREAM DESTINATION=" + destination);
|
||||
writer.Flush();
|
||||
Hashtable response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
|
||||
if (response["RESULT"].ToString() != "OK")
|
||||
{
|
||||
throw (new Exception(response["MESSAGE"].ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("NAMING LOOKUP NAME=ME");
|
||||
writer.Flush();
|
||||
response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
|
||||
_sessionKey = response["VALUE"].ToString();
|
||||
SAMSessionListener listener = new SAMSessionListener(connection,this,_streams);
|
||||
new Thread(new ThreadStart(listener.startListening)).Start();
|
||||
}
|
||||
}
|
||||
public void addStream(SAMStream stream)
|
||||
{
|
||||
_streams.Add(stream.getID(),stream);
|
||||
}
|
||||
public string getKey()
|
||||
{
|
||||
return _sessionKey;
|
||||
}
|
||||
public Hashtable getStreams()
|
||||
{
|
||||
return _streams;
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMSessionListener
|
||||
{
|
||||
private Hashtable _streams;
|
||||
private SAMConnection _connection;
|
||||
private SAMSession _session;
|
||||
private bool stayAlive = true;
|
||||
|
||||
public SAMSessionListener(SAMConnection connection,SAMSession session, Hashtable streams)
|
||||
{
|
||||
_streams = streams;
|
||||
_connection = connection;
|
||||
_session = session;
|
||||
}
|
||||
public void startListening()
|
||||
{
|
||||
StreamReader reader = _connection.getInputStream();
|
||||
while (stayAlive)
|
||||
{
|
||||
string response = reader.ReadLine();
|
||||
if (response.StartsWith("STREAM STATUS"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.ReceivedStatus(values);
|
||||
}
|
||||
if (response.StartsWith("STREAM CONNECTED"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.isConnected = true;
|
||||
}
|
||||
if (response.StartsWith("STREAM RECEIVED"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
int streamID = int.Parse(values["ID"].ToString());
|
||||
SAMStream theStream = (SAMStream)_streams[streamID];
|
||||
if (theStream == null) new SAMStream(_connection,_session,streamID);
|
||||
theStream = (SAMStream)_streams[streamID];
|
||||
theStream.ReceivedData(int.Parse(values["SIZE"].ToString()));
|
||||
}
|
||||
if (response.StartsWith("STREAM CLOSE"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.isConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMStream
|
||||
{
|
||||
private int _ID;
|
||||
private byte[] _data;
|
||||
private int _position=0;
|
||||
private int _size=0;
|
||||
private SAMSession _session;
|
||||
private SAMConnection _connection;
|
||||
public bool isConnected=false;
|
||||
|
||||
public SAMStream (SAMConnection connection,SAMSession session, int ID)
|
||||
{
|
||||
_data = new byte[100000]; //FIXME: change to non-static structure for storing stream data
|
||||
_ID = ID;
|
||||
_connection = connection;
|
||||
_session = session;
|
||||
_session.addStream(this);
|
||||
}
|
||||
|
||||
public void Connect(string destination)
|
||||
{
|
||||
StreamWriter writer = _connection.getOutputStream();
|
||||
writer.WriteLine("STREAM CONNECT ID=" + _ID.ToString() + " DESTINATION=" + destination);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
public void ReceivedData(int size) //FIXME: WTF is going on when reading the payload here? All zeros and way too many of them.
|
||||
{
|
||||
NetworkStream stream = _connection.getStream();
|
||||
int bytesRead = stream.Read(_data,_size,size);
|
||||
_size = _size + bytes;
|
||||
}
|
||||
|
||||
public void ReceivedStatus(Hashtable response)
|
||||
{
|
||||
if (response["RESULT"].ToString() != "OK")
|
||||
{
|
||||
throw (new Exception(response["RESULT"].ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
isConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int getID() {return _ID;}
|
||||
|
||||
public bool DataAvailable()
|
||||
{
|
||||
return _position != _size;
|
||||
}
|
||||
|
||||
public void Write(byte[] buf)
|
||||
{
|
||||
NetworkStream stream = _connection.getStream();
|
||||
int sent = 0;
|
||||
while (sent < buf.Length)
|
||||
{
|
||||
int toSend = Math.Min(buf.Length - sent,32768);
|
||||
string header = "STREAM SEND ID=" + _ID.ToString() + " SIZE=" + toSend.ToString() + "\n";
|
||||
byte[] headerbytes = Encoding.ASCII.GetBytes(header);
|
||||
stream.Write(headerbytes,0,headerbytes.Length);
|
||||
stream.Write(buf,sent,toSend);
|
||||
sent = sent + toSend;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ReadToEnd()
|
||||
{
|
||||
byte[] ret = new byte[_size - _position];
|
||||
Array.Copy(_data,_position,ret,0,_size - _position);
|
||||
_position = _size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
StreamWriter writer = _connection.getOutputStream();
|
||||
writer.WriteLine("STREAM CLOSE " + _ID.ToString());
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMUtil
|
||||
{
|
||||
public static Hashtable parseKeyValues(string str, int startingWord)
|
||||
{
|
||||
Hashtable hash = new Hashtable();
|
||||
string strTruncated = string.Join(" ",str.Split(' '),startingWord,str.Split(' ').Length - startingWord);
|
||||
string[] sets = strTruncated.Split('=',' ');
|
||||
for (int i=0; i<sets.Length; i=i+2)
|
||||
{
|
||||
hash.Add(sets[i],sets[i+1]);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,13 +9,28 @@ package net.i2p.sam;
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* SAM bridge implementation.
|
||||
@ -23,15 +38,25 @@ import net.i2p.util.Log;
|
||||
* @author human
|
||||
*/
|
||||
public class SAMBridge implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(SAMBridge.class);
|
||||
private ServerSocket serverSocket;
|
||||
private Properties i2cpProps;
|
||||
/**
|
||||
* filename in which the name to private key mapping should
|
||||
* be stored (and loaded from)
|
||||
*/
|
||||
private String persistFilename;
|
||||
/**
|
||||
* app designated destination name to the base64 of the I2P formatted
|
||||
* destination keys (Destination+PrivateKey+SigningPrivateKey)
|
||||
*/
|
||||
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
|
||||
|
||||
private boolean acceptConnections = true;
|
||||
|
||||
private final static int SAM_LISTENPORT = 7656;
|
||||
|
||||
private static final int SAM_LISTENPORT = 7656;
|
||||
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
|
||||
|
||||
private SAMBridge() {}
|
||||
|
||||
/**
|
||||
@ -40,8 +65,11 @@ public class SAMBridge implements Runnable {
|
||||
* @param listenHost hostname to listen for SAM connections on ("0.0.0.0" for all)
|
||||
* @param listenPort port number to listen for SAM connections on
|
||||
* @param i2cpProps set of I2CP properties for finding and communicating with the router
|
||||
* @param persistFile location to store/load named keys to/from
|
||||
*/
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps) {
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
|
||||
persistFilename = persistFile;
|
||||
loadKeys();
|
||||
try {
|
||||
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
|
||||
serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
|
||||
@ -63,6 +91,97 @@ public class SAMBridge implements Runnable {
|
||||
this.i2cpProps = i2cpProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the destination associated with the given name
|
||||
*
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the I2P private keystream for the given name, formatted
|
||||
* as a base64 string (Destination+PrivateKey+SessionPrivateKey, as I2CP
|
||||
* stores it).
|
||||
*
|
||||
* @return null if the name does not exist, else the stream
|
||||
*/
|
||||
public String getKeystream(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the given keystream should be used for the given name
|
||||
*
|
||||
*/
|
||||
public void addKeystream(String name, String stream) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
storeKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load up the keys from the persistFilename
|
||||
*
|
||||
*/
|
||||
private synchronized void loadKeys() {
|
||||
Map keys = new HashMap(16);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
keys.put(name, privKeys);
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
nameToPrivKeys = Collections.synchronizedMap(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current keys to disk in the location specified on creation
|
||||
*
|
||||
*/
|
||||
private synchronized void storeKeys() {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
|
||||
@ -72,15 +191,17 @@ public class SAMBridge implements Runnable {
|
||||
* depth, etc.
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String keyfile = DEFAULT_SAM_KEYFILE;
|
||||
int port = SAM_LISTENPORT;
|
||||
String host = "0.0.0.0";
|
||||
Properties opts = null;
|
||||
if (args.length > 0) {
|
||||
int portIndex = 0;
|
||||
keyfile = args[0];
|
||||
int portIndex = 1;
|
||||
try {
|
||||
port = Integer.parseInt(args[portIndex]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
host = args[0];
|
||||
host = args[1];
|
||||
portIndex++;
|
||||
try {
|
||||
port = Integer.parseInt(args[portIndex]);
|
||||
@ -91,7 +212,7 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
opts = parseOptions(args, portIndex+1);
|
||||
}
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts);
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
|
||||
I2PThread t = new I2PThread(bridge, "SAMListener");
|
||||
t.start();
|
||||
}
|
||||
@ -114,7 +235,8 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("Usage: SAMBridge [listenHost listenPortNum[ name=val]*]");
|
||||
System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
|
||||
System.err.println(" keyfile: location to persist private keys (default sam.keys)");
|
||||
System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
|
||||
System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
|
||||
System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
|
||||
@ -140,10 +262,18 @@ public class SAMBridge implements Runnable {
|
||||
} catch (IOException e) {}
|
||||
continue;
|
||||
}
|
||||
handler.setBridge(this);
|
||||
handler.startHandling();
|
||||
} catch (SAMException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM error: " + e.getMessage(), e);
|
||||
try {
|
||||
String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
|
||||
s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM Error sending error reply", ioe);
|
||||
}
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import net.i2p.util.Log;
|
||||
public class SAMDatagramSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMDatagramSession.class);
|
||||
public static int DGRAM_SIZE_MAX = 31*1024;
|
||||
|
||||
private SAMDatagramReceiver recv = null;
|
||||
|
||||
@ -43,7 +44,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(String dest, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(dest, props);
|
||||
|
||||
this.recv = recv;
|
||||
@ -58,7 +60,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(InputStream destStream, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(destStream, props);
|
||||
|
||||
this.recv = recv;
|
||||
@ -73,6 +76,9 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > DGRAM_SIZE_MAX)
|
||||
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
|
||||
|
||||
byte[] dgram = dgramMaker.makeI2PDatagram(data);
|
||||
|
||||
return sendBytesThroughMessageSession(dest, dgram);
|
||||
|
@ -30,6 +30,7 @@ public abstract class SAMHandler implements Runnable {
|
||||
private final static Log _log = new Log(SAMHandler.class);
|
||||
|
||||
protected I2PThread thread = null;
|
||||
protected SAMBridge bridge = null;
|
||||
|
||||
private Object socketWLock = new Object(); // Guards writings on socket
|
||||
private Socket socket = null;
|
||||
@ -71,6 +72,8 @@ public abstract class SAMHandler implements Runnable {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setBridge(SAMBridge bridge) { this.bridge = bridge; }
|
||||
|
||||
/**
|
||||
* Actually handle the SAM protocol.
|
||||
*
|
||||
@ -124,7 +127,9 @@ public abstract class SAMHandler implements Runnable {
|
||||
*
|
||||
*/
|
||||
protected final void closeClientSocket() throws IOException {
|
||||
socket.close();
|
||||
if (socket != null)
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,8 +32,8 @@ public class SAMHandlerFactory {
|
||||
*
|
||||
* @param s Socket attached to SAM client
|
||||
* @param i2cpProps config options for our i2cp connection
|
||||
*
|
||||
* @return A SAM protocol handler
|
||||
* @throws SAMException if the connection handshake (HELLO message) was malformed
|
||||
* @return A SAM protocol handler, or null if the client closed before the handshake
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
|
||||
BufferedReader br;
|
||||
@ -66,8 +66,8 @@ public class SAMHandlerFactory {
|
||||
{
|
||||
String opcode;
|
||||
if (!(opcode = tok.nextToken()).equals("VERSION")) {
|
||||
throw new SAMException("Unrecognized HELLO message opcode: \""
|
||||
+ opcode + "\"");
|
||||
throw new SAMException("Unrecognized HELLO message opcode: '"
|
||||
+ opcode + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,22 +88,8 @@ public class SAMHandlerFactory {
|
||||
}
|
||||
|
||||
String ver = chooseBestVersion(minVer, maxVer);
|
||||
if (ver == null) {
|
||||
// Let's answer negatively
|
||||
try {
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO REPLY RESULT=NOVERSION\n".getBytes("ISO-8859-1"));
|
||||
return null;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")");
|
||||
throw new SAMException("Character encoding error: "
|
||||
+ e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new SAMException("Error reading from socket: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
if (ver == null)
|
||||
throw new SAMException("No version specified");
|
||||
|
||||
// Let's answer positively
|
||||
try {
|
||||
@ -135,8 +121,8 @@ public class SAMHandlerFactory {
|
||||
throw new SAMException("BUG! (in handler instantiation)");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException caught during SAM handler instantiation");
|
||||
return null;
|
||||
_log.error("Error creating the v1 handler", e);
|
||||
throw new SAMException("IOException caught during SAM handler instantiation");
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.util.Log;
|
||||
public class SAMRawSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMRawSession.class);
|
||||
public static final int RAW_SIZE_MAX = 32*1024;
|
||||
|
||||
private SAMRawReceiver recv = null;
|
||||
/**
|
||||
@ -64,6 +65,8 @@ public class SAMRawSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > RAW_SIZE_MAX)
|
||||
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
||||
return sendBytesThroughMessageSession(dest, data);
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
@ -100,11 +101,23 @@ public class SAMStreamSession {
|
||||
allprops.putAll(System.getProperties());
|
||||
allprops.putAll(props);
|
||||
|
||||
// FIXME: we should setup I2CP host and port, too
|
||||
String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
int i2cpPort = 7654;
|
||||
String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new SAMException("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
// streams MUST be mode=guaranteed (though i think the socket manager
|
||||
// enforces this anyway...
|
||||
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
|
||||
_log.debug("Creating I2PSocketManager...");
|
||||
socketMgr = I2PSocketManagerFactory.createManager(destStream,
|
||||
"127.0.0.1",
|
||||
7654, allprops);
|
||||
i2cpHost,
|
||||
i2cpPort,
|
||||
allprops);
|
||||
if (socketMgr == null) {
|
||||
throw new SAMException("Error creating I2PSocketManager");
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Miscellaneous utility methods used by SAM protocol handlers.
|
||||
@ -84,7 +85,7 @@ public class SAMUtils {
|
||||
* @return the Destination for the specified hostname, or null if not found
|
||||
*/
|
||||
public static Destination lookupHost(String name, OutputStream pubKey) {
|
||||
NamingService ns = NamingService.getInstance();
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Destination dest = ns.lookup(name);
|
||||
|
||||
if ((pubKey != null) && (dest != null)) {
|
||||
@ -107,9 +108,10 @@ public class SAMUtils {
|
||||
*
|
||||
* @param tok A StringTokenizer pointing to the SAM parameters
|
||||
*
|
||||
* @return Properties with the parsed SAM params, or null if none is found
|
||||
* @throws SAMException if the data was formatted incorrectly
|
||||
* @return Properties with the parsed SAM params
|
||||
*/
|
||||
public static Properties parseParams(StringTokenizer tok) {
|
||||
public static Properties parseParams(StringTokenizer tok) throws SAMException {
|
||||
int pos, nprops = 0, ntoks = tok.countTokens();
|
||||
String token, param, value;
|
||||
Properties props = new Properties();
|
||||
@ -120,7 +122,7 @@ public class SAMUtils {
|
||||
pos = token.indexOf("=");
|
||||
if (pos == -1) {
|
||||
_log.debug("Error in params format");
|
||||
return null;
|
||||
throw new SAMException("Bad formatting for param [" + token + "]");
|
||||
}
|
||||
param = token.substring(0, pos);
|
||||
value = token.substring(pos + 1);
|
||||
@ -133,22 +135,18 @@ public class SAMUtils {
|
||||
_log.debug("Parsed properties: " + dumpProperties(props));
|
||||
}
|
||||
|
||||
if (nprops != 0) {
|
||||
return props;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/* Dump a Properties object in an human-readable form */
|
||||
private static String dumpProperties(Properties props) {
|
||||
Enumeration enum = props.propertyNames();
|
||||
Enumeration names = props.propertyNames();
|
||||
String msg = "";
|
||||
String key, val;
|
||||
boolean firstIter = true;
|
||||
|
||||
while (enum.hasMoreElements()) {
|
||||
key = (String)enum.nextElement();
|
||||
while (names.hasMoreElements()) {
|
||||
key = (String)names.nextElement();
|
||||
val = props.getProperty(key);
|
||||
|
||||
if (!firstIter) {
|
||||
|
@ -108,7 +108,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
|
||||
msg = buf.toString("ISO-8859-1");
|
||||
msg = buf.toString("ISO-8859-1").trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: " + msg);
|
||||
}
|
||||
@ -127,8 +127,6 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
+ "\"; opcode: \"" + opcode + "\")");
|
||||
}
|
||||
props = SAMUtils.parseParams(tok);
|
||||
if (i2cpProps != null)
|
||||
props.putAll(i2cpProps); // make sure we've got the i2cp settings
|
||||
|
||||
if (domain.equals("STREAM")) {
|
||||
canContinue = execStreamMessage(opcode, props);
|
||||
@ -137,6 +135,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
} else if (domain.equals("RAW")) {
|
||||
canContinue = execRawMessage(opcode, props);
|
||||
} else if (domain.equals("SESSION")) {
|
||||
if (i2cpProps != null)
|
||||
props.putAll(i2cpProps); // make sure we've got the i2cp settings
|
||||
canContinue = execSessionMessage(opcode, props);
|
||||
} else if (domain.equals("DEST")) {
|
||||
canContinue = execDestMessage(opcode, props);
|
||||
@ -154,10 +154,10 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (Exception e) {
|
||||
_log.error("Unexpected exception", e);
|
||||
} finally {
|
||||
@ -189,76 +189,91 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
if ((rawSession != null) || (datagramSession != null)
|
||||
|| (streamSession != null)) {
|
||||
_log.debug("Trying to create a session, but one still exists");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
|
||||
}
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in SESSION CREATE message");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
|
||||
}
|
||||
|
||||
dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("SESSION DESTINATION parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
|
||||
String destKeystream = null;
|
||||
|
||||
if (dest.equals("TRANSIENT")) {
|
||||
_log.debug("TRANSIENT destination requested");
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(priv, null);
|
||||
|
||||
dest = Base64.encode(priv.toByteArray());
|
||||
destKeystream = Base64.encode(priv.toByteArray());
|
||||
} else {
|
||||
destKeystream = bridge.getKeystream(dest);
|
||||
if (destKeystream == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(baos, null);
|
||||
destKeystream = Base64.encode(baos.toByteArray());
|
||||
bridge.addKeystream(dest, destKeystream);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] and it is already known");
|
||||
}
|
||||
}
|
||||
|
||||
String style = props.getProperty("STYLE");
|
||||
if (style == null) {
|
||||
_log.debug("SESSION STYLE parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
|
||||
}
|
||||
props.remove("STYLE");
|
||||
|
||||
if (style.equals("RAW")) {
|
||||
rawSession = new SAMRawSession(dest, props, this);
|
||||
rawSession = new SAMRawSession(destKeystream, props, this);
|
||||
} else if (style.equals("DATAGRAM")) {
|
||||
datagramSession = new SAMDatagramSession(dest, props,this);
|
||||
datagramSession = new SAMDatagramSession(destKeystream, props,this);
|
||||
} else if (style.equals("STREAM")) {
|
||||
String dir = props.getProperty("DIRECTION");
|
||||
if (dir == null) {
|
||||
_log.debug("No DIRECTION parameter in STREAM session");
|
||||
return false;
|
||||
_log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH");
|
||||
dir = "BOTH";
|
||||
}
|
||||
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
|
||||
&& !dir.equals("BOTH")) {
|
||||
_log.debug("Unknow DIRECTION parameter value: " + dir);
|
||||
return false;
|
||||
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
|
||||
}
|
||||
props.remove("DIRECTION");
|
||||
|
||||
streamSession = new SAMStreamSession(dest, dir,props,this);
|
||||
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
|
||||
}
|
||||
return writeString("SESSION STATUS RESULT=OK DESTINATION="
|
||||
+ dest + "\n");
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination specified");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.debug("I2P error when instantiating session", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (SAMException e) {
|
||||
_log.error("Unexpected SAM error", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (IOException e) {
|
||||
_log.error("Unexpected IOException", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +281,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
private boolean execDestMessage(String opcode, Properties props) {
|
||||
|
||||
if (opcode.equals("GENERATE")) {
|
||||
if (props != null) {
|
||||
if (props.size() > 0) {
|
||||
_log.debug("Properties specified in DEST GENERATE message");
|
||||
return false;
|
||||
}
|
||||
@ -483,159 +498,171 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
if (opcode.equals("SEND")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND ID specified: " + strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int size;
|
||||
{
|
||||
String strsize = props.getProperty("SIZE");
|
||||
if (strsize == null) {
|
||||
_log.debug("Size not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
size = Integer.parseInt(strsize);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND size specified: "+strsize);
|
||||
return false;
|
||||
}
|
||||
if (!checkSize(size)) {
|
||||
_log.debug("Specified size (" + size
|
||||
+ ") is out of protocol limits");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
return execStreamSend(props);
|
||||
} else if (opcode.equals("CONNECT")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
if (id < 1) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
props.remove("ID");
|
||||
}
|
||||
|
||||
String dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("Destination not specified in RAW SEND message");
|
||||
return false;
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
}
|
||||
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
|
||||
+ id + "\n");
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
|
||||
+ id + "\n");
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
|
||||
+ id + "\n");
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
|
||||
+ id + "\n");
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
|
||||
+ id + "\n");
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
}
|
||||
return execStreamConnect(props);
|
||||
} else if (opcode.equals("CLOSE")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return streamSession.closeConnection(id);
|
||||
return execStreamClose(props);
|
||||
} else {
|
||||
_log.debug("Unrecognized RAW message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamSend(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND ID specified: " + strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int size;
|
||||
{
|
||||
String strsize = props.getProperty("SIZE");
|
||||
if (strsize == null) {
|
||||
_log.debug("Size not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
size = Integer.parseInt(strsize);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND size specified: "+strsize);
|
||||
return false;
|
||||
}
|
||||
if (!checkSize(size)) {
|
||||
_log.debug("Specified size (" + size
|
||||
+ ") is out of protocol limits");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamConnect(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
if (id < 1) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
props.remove("ID");
|
||||
}
|
||||
|
||||
String dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("Destination not specified in RAW SEND message");
|
||||
return false;
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
}
|
||||
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
|
||||
+ id + "\n");
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
|
||||
+ id + "\n");
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
|
||||
+ id + "\n");
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
|
||||
+ id + "\n");
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
|
||||
+ id + "\n");
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamClose(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return streamSession.closeConnection(id);
|
||||
}
|
||||
|
||||
/* Check whether a size is inside the limits allowed by this protocol */
|
||||
private boolean checkSize(int size) {
|
||||
|
@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionDatagram {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 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);
|
||||
}
|
||||
}
|
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionRaw.java
Normal file
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionRaw.java
Normal file
@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionRaw {
|
||||
private static Log _log = new Log(TestCreateSessionRaw.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 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);
|
||||
}
|
||||
}
|
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionStream.java
Normal file
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionStream.java
Normal file
@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionStream {
|
||||
private static Log _log = new Log(TestCreateSessionStream.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 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);
|
||||
}
|
||||
}
|
106
apps/sam/java/test/net/i2p/sam/TestDatagramTransfer.java
Normal file
106
apps/sam/java/test/net/i2p/sam/TestDatagramTransfer.java
Normal file
@ -0,0 +1,106 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.sam.SAMUtils;
|
||||
|
||||
public class TestDatagramTransfer {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransfer(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransfer(String host, int port, String conOptions) {
|
||||
String destName = "TRANSIENT";
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
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);
|
||||
}
|
||||
|
||||
String send = "DATAGRAM SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
line = reader.readLine();
|
||||
tok = new StringTokenizer(line);
|
||||
maj = tok.nextToken();
|
||||
min = tok.nextToken();
|
||||
props = SAMUtils.parseParams(tok);
|
||||
String size = props.getProperty("SIZE");
|
||||
String from = props.getProperty("DESTINATION");
|
||||
if ( (value == null) || (size == null) ||
|
||||
(!from.equals(value)) || (!size.equals("3")) ) {
|
||||
_log.error("Reply of the datagram is incorrect: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[] = new char[3];
|
||||
int read = reader.read(buf);
|
||||
if (read != 3) {
|
||||
_log.error("Unable to read the full datagram");
|
||||
return;
|
||||
}
|
||||
if (new String(buf).equals("Yo!")) {
|
||||
_log.info("Received payload successfully");
|
||||
} else {
|
||||
_log.error("Payload is incorrect! [" + new String(buf) + "]");
|
||||
}
|
||||
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 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);
|
||||
}
|
||||
}
|
62
apps/sam/java/test/net/i2p/sam/TestDest.java
Normal file
62
apps/sam/java/test/net/i2p/sam/TestDest.java
Normal file
@ -0,0 +1,62 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestDest {
|
||||
private static Log _log = new Log(TestDest.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
test(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void test(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting a DEST generate (should come back with 'DEST REPLY PUB=val PRIV=val')\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "DEST GENERATE\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the dest generate: " + line);
|
||||
_log.debug("The abouve should be a DEST REPLY");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0"; // "i2cp.tcp.host=localhost i2cp.tcp.port=7654 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);
|
||||
}
|
||||
}
|
76
apps/sam/java/test/net/i2p/sam/TestHello.java
Normal file
76
apps/sam/java/test/net/i2p/sam/TestHello.java
Normal file
@ -0,0 +1,76 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestHello {
|
||||
private static Log _log = new Log(TestHello.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort) {
|
||||
testValidVersion(samHost, samPort);
|
||||
testInvalidVersion(samHost, samPort);
|
||||
testCorruptLine(samHost, samPort);
|
||||
}
|
||||
|
||||
private static void testValidVersion(String host, int port) {
|
||||
_log.info("\n\nTesting valid version (should come back with an OK)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testInvalidVersion(String host, int port) {
|
||||
_log.info("\n\nTesting invalid version (should come back with an error)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=9.0 MAX=8.3\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for invalid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCorruptLine(String host, int port) {
|
||||
_log.info("\n\nTesting corrupt line (should come back with an error)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO h0 h0 h0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
82
apps/sam/java/test/net/i2p/sam/TestNaming.java
Normal file
82
apps/sam/java/test/net/i2p/sam/TestNaming.java
Normal file
@ -0,0 +1,82 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestNaming {
|
||||
private static Log _log = new Log(TestNaming.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testMe(samHost, samPort, conOptions);
|
||||
testDuck(samHost, samPort, conOptions);
|
||||
testUnknown(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testMe(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "ME");
|
||||
_log.debug("\n\nTest of ME complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testDuck(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "duck.i2p");
|
||||
_log.debug("\n\nTest of duck complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testUnknown(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "www.odci.gov");
|
||||
_log.debug("\n\nTest of unknown host complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testName(String host, int port, String conOptions, String name) {
|
||||
_log.info("\n\nTesting a name lookup (should come back with 'NAMING REPLY RESULT=OK VALUE=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=" + name + "\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for [" + name +"]: " + line);
|
||||
_log.debug("The abouve should be a NAMING REPLY");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 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);
|
||||
}
|
||||
}
|
109
apps/sam/java/test/net/i2p/sam/TestRawTransfer.java
Normal file
109
apps/sam/java/test/net/i2p/sam/TestRawTransfer.java
Normal file
@ -0,0 +1,109 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.sam.SAMUtils;
|
||||
|
||||
public class TestRawTransfer {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransfer(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransfer(String host, int port, String conOptions) {
|
||||
String destName = "TRANSIENT";
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
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);
|
||||
}
|
||||
|
||||
String send = "RAW SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
line = reader.readLine();
|
||||
try {
|
||||
tok = new StringTokenizer(line);
|
||||
maj = tok.nextToken();
|
||||
min = tok.nextToken();
|
||||
props = SAMUtils.parseParams(tok);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error parsing response line: [" + line + "]", e);
|
||||
return;
|
||||
}
|
||||
String size = props.getProperty("SIZE");
|
||||
if ( (size == null) || (!size.equals("3")) ) {
|
||||
_log.error("Reply of the datagram is incorrect: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[] = new char[3];
|
||||
int read = reader.read(buf);
|
||||
if (read != 3) {
|
||||
_log.error("Unable to read the full datagram");
|
||||
return;
|
||||
}
|
||||
if (new String(buf).equals("Yo!")) {
|
||||
_log.info("Rec8eived payload successfully");
|
||||
} else {
|
||||
_log.error("Payload is incorrect! [" + new String(buf) + "]");
|
||||
}
|
||||
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
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";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 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);
|
||||
}
|
||||
}
|
223
apps/sam/java/test/net/i2p/sam/TestStreamTransfer.java
Normal file
223
apps/sam/java/test/net/i2p/sam/TestStreamTransfer.java
Normal file
@ -0,0 +1,223 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* <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 void runTest(String samHost, int samPort, String conOptions) {
|
||||
startAlice(samHost, samPort, conOptions);
|
||||
testBob(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
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("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Alice: " + line);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
private void doRun() throws IOException, SAMException {
|
||||
String line = _reader.readLine();
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
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("Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
// now echo it back
|
||||
String reply = "STREAM SEND ID=" + id +
|
||||
" SIZE=" + payloadSize +
|
||||
"\n" + payload;
|
||||
_out.write(reply.getBytes());
|
||||
_out.flush();
|
||||
} else {
|
||||
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void testBob(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting Bob\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
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=Bob " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Bob: " + line);
|
||||
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to the stream connect from Bob to Alice: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
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!!";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
|
||||
req = "STREAM CLOSE ID=42\n";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(3*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=www.i2p.net i2cp.tcp.port=7765 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
9
apps/sam/java/test/net/i2p/sam/TestUtil.java
Normal file
9
apps/sam/java/test/net/i2p/sam/TestUtil.java
Normal file
@ -0,0 +1,9 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
|
||||
public class TestUtil {
|
||||
public static void startupBridge(int listenPort) {
|
||||
// Usage: SAMBridge [listenHost listenPortNum[ name=val]*]
|
||||
SAMBridge.main(new String[] { "0.0.0.0", listenPort+"" });
|
||||
}
|
||||
}
|
41
apps/time/java/build.xml
Normal file
41
apps/time/java/build.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="time">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" includes="**/*.java" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/timestamper.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.time.Timestamper" />
|
||||
<attribute name="Class-Path" value="i2p.jar timestamper.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P timestamper" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
145
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
145
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
@ -0,0 +1,145 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
|
||||
/**
|
||||
* NtpClient - an NTP client for Java. This program connects to an NTP server
|
||||
* and prints the response to the console.
|
||||
*
|
||||
* The local clock offset calculation is implemented according to the SNTP
|
||||
* algorithm specified in RFC 2030.
|
||||
*
|
||||
* Note that on windows platforms, the curent time-of-day timestamp is limited
|
||||
* to an resolution of 10ms and adversely affects the accuracy of the results.
|
||||
*
|
||||
*
|
||||
* This code is copyright (c) Adam Buckley 2004
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version. A HTML version of the GNU General Public License can be
|
||||
* seen at http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program 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 for
|
||||
* more details.
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* (minor refactoring by jrandom)
|
||||
*/
|
||||
public class NtpClient {
|
||||
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
|
||||
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
private final static int NTP_PORT = 123;
|
||||
|
||||
/**
|
||||
* Query the ntp servers, returning the current time from first one we find
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC)
|
||||
* @throws IllegalArgumentException if none of the servers are reachable
|
||||
*/
|
||||
public static long currentTime(String serverNames[]) {
|
||||
if (serverNames == null)
|
||||
throw new IllegalArgumentException("No NTP servers specified");
|
||||
for (int i = 0; i < serverNames.length; i++) {
|
||||
long now = currentTime(serverNames[i]);
|
||||
if (now > 0)
|
||||
return now;
|
||||
}
|
||||
throw new IllegalArgumentException("No reachable NTP servers specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the given NTP server, returning the current internet time
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
|
||||
*/
|
||||
public static long currentTime(String serverName) {
|
||||
try {
|
||||
// Send request
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
InetAddress address = InetAddress.getByName(serverName);
|
||||
byte[] buf = new NtpMessage().toByteArray();
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
|
||||
|
||||
// Set the transmit timestamp *just* before sending the packet
|
||||
// ToDo: Does this actually improve performance or not?
|
||||
NtpMessage.encodeTimestamp(packet.getData(), 40,
|
||||
(System.currentTimeMillis()/1000.0)
|
||||
+ SECONDS_1900_TO_EPOCH);
|
||||
|
||||
socket.send(packet);
|
||||
|
||||
// Get response
|
||||
packet = new DatagramPacket(buf, buf.length);
|
||||
socket.setSoTimeout(10*1000);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
} catch (InterruptedIOException iie) {
|
||||
socket.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Immediately record the incoming timestamp
|
||||
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
|
||||
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
|
||||
(msg.receiveTimestamp-msg.transmitTimestamp);
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
socket.close();
|
||||
|
||||
//System.out.println("host: " + serverName + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
|
||||
return (long)(System.currentTimeMillis() + localClockOffset*1000);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Process command-line args
|
||||
if(args.length <= 0) {
|
||||
printUsage();
|
||||
return;
|
||||
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
|
||||
}
|
||||
|
||||
long now = currentTime(args);
|
||||
System.out.println("Current time: " + new java.util.Date(now));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prints usage
|
||||
*/
|
||||
static void printUsage() {
|
||||
System.out.println(
|
||||
"NtpClient - an NTP client for Java.\n" +
|
||||
"\n" +
|
||||
"This program connects to an NTP server and prints the current time to the console.\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"Usage: java NtpClient server[ server]*\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"This program is copyright (c) Adam Buckley 2004 and distributed under the terms\n" +
|
||||
"of the GNU General Public License. This program is distributed in the hope\n" +
|
||||
"that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n" +
|
||||
"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
|
||||
"General Public License available at http://www.gnu.org/licenses/gpl.html for\n" +
|
||||
"more details.");
|
||||
|
||||
}
|
||||
}
|
451
apps/time/java/src/net/i2p/time/NtpMessage.java
Normal file
451
apps/time/java/src/net/i2p/time/NtpMessage.java
Normal file
@ -0,0 +1,451 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* This class represents a NTP message, as specified in RFC 2030. The message
|
||||
* format is compatible with all versions of NTP and SNTP.
|
||||
*
|
||||
* This class does not support the optional authentication protocol, and
|
||||
* ignores the key ID and message digest fields.
|
||||
*
|
||||
* For convenience, this class exposes message values as native Java types, not
|
||||
* the NTP-specified data formats. For example, timestamps are
|
||||
* stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
|
||||
* format).
|
||||
*
|
||||
* However, the contructor NtpMessage(byte[]) and the method toByteArray()
|
||||
* allow the import and export of the raw NTP message format.
|
||||
*
|
||||
*
|
||||
* Usage example
|
||||
*
|
||||
* // Send message
|
||||
* DatagramSocket socket = new DatagramSocket();
|
||||
* InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
|
||||
* byte[] buf = new NtpMessage().toByteArray();
|
||||
* DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
|
||||
* socket.send(packet);
|
||||
*
|
||||
* // Get response
|
||||
* socket.receive(packet);
|
||||
* System.out.println(msg.toString());
|
||||
*
|
||||
*
|
||||
* This code is copyright (c) Adam Buckley 2004
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version. A HTML version of the GNU General Public License can be
|
||||
* seen at http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program 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 for
|
||||
* more details.
|
||||
*
|
||||
*
|
||||
* Comments for member variables are taken from RFC2030 by David Mills,
|
||||
* University of Delaware.
|
||||
*
|
||||
* Number format conversion code in NtpMessage(byte[] array) and toByteArray()
|
||||
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
|
||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||
*
|
||||
* @author Adam Buckley
|
||||
*/
|
||||
public class NtpMessage {
|
||||
/**
|
||||
* This is a two-bit code warning of an impending leap second to be
|
||||
* inserted/deleted in the last minute of the current day. It's values
|
||||
* may be as follows:
|
||||
*
|
||||
* Value Meaning
|
||||
* ----- -------
|
||||
* 0 no warning
|
||||
* 1 last minute has 61 seconds
|
||||
* 2 last minute has 59 seconds)
|
||||
* 3 alarm condition (clock not synchronized)
|
||||
*/
|
||||
public byte leapIndicator = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the NTP/SNTP version number. The version number
|
||||
* is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
|
||||
* If necessary to distinguish between IPv4, IPv6 and OSI, the
|
||||
* encapsulating context must be inspected.
|
||||
*/
|
||||
public byte version = 3;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the mode, with values defined as follows:
|
||||
*
|
||||
* Mode Meaning
|
||||
* ---- -------
|
||||
* 0 reserved
|
||||
* 1 symmetric active
|
||||
* 2 symmetric passive
|
||||
* 3 client
|
||||
* 4 server
|
||||
* 5 broadcast
|
||||
* 6 reserved for NTP control message
|
||||
* 7 reserved for private use
|
||||
*
|
||||
* In unicast and anycast modes, the client sets this field to 3 (client)
|
||||
* in the request and the server sets it to 4 (server) in the reply. In
|
||||
* multicast mode, the server sets this field to 5 (broadcast).
|
||||
*/
|
||||
public byte mode = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the stratum level of the local clock, with values
|
||||
* defined as follows:
|
||||
*
|
||||
* Stratum Meaning
|
||||
* ----------------------------------------------
|
||||
* 0 unspecified or unavailable
|
||||
* 1 primary reference (e.g., radio clock)
|
||||
* 2-15 secondary reference (via NTP or SNTP)
|
||||
* 16-255 reserved
|
||||
*/
|
||||
public short stratum = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the maximum interval between successive messages,
|
||||
* in seconds to the nearest power of two. The values that can appear in
|
||||
* this field presently range from 4 (16 s) to 14 (16284 s); however, most
|
||||
* applications use only the sub-range 6 (64 s) to 10 (1024 s).
|
||||
*/
|
||||
public byte pollInterval = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the precision of the local clock, in seconds to
|
||||
* the nearest power of two. The values that normally appear in this field
|
||||
* range from -6 for mains-frequency clocks to -20 for microsecond clocks
|
||||
* found in some workstations.
|
||||
*/
|
||||
public byte precision = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the total roundtrip delay to the primary reference
|
||||
* source, in seconds. Note that this variable can take on both positive
|
||||
* and negative values, depending on the relative time and frequency
|
||||
* offsets. The values that normally appear in this field range from
|
||||
* negative values of a few milliseconds to positive values of several
|
||||
* hundred milliseconds.
|
||||
*/
|
||||
public double rootDelay = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the nominal error relative to the primary reference
|
||||
* source, in seconds. The values that normally appear in this field
|
||||
* range from 0 to several hundred milliseconds.
|
||||
*/
|
||||
public double rootDispersion = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is a 4-byte array identifying the particular reference source.
|
||||
* In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
|
||||
* stratum-1 (primary) servers, this is a four-character ASCII string, left
|
||||
* justified and zero padded to 32 bits. In NTP Version 3 secondary
|
||||
* servers, this is the 32-bit IPv4 address of the reference source. In NTP
|
||||
* Version 4 secondary servers, this is the low order 32 bits of the latest
|
||||
* transmit timestamp of the reference source. NTP primary (stratum 1)
|
||||
* servers should set this field to a code identifying the external
|
||||
* reference source according to the following list. If the external
|
||||
* reference is one of those listed, the associated code should be used.
|
||||
* Codes for sources not listed can be contrived as appropriate.
|
||||
*
|
||||
* Code External Reference Source
|
||||
* ---- -------------------------
|
||||
* LOCL uncalibrated local clock used as a primary reference for
|
||||
* a subnet without external means of synchronization
|
||||
* PPS atomic clock or other pulse-per-second source
|
||||
* individually calibrated to national standards
|
||||
* ACTS NIST dialup modem service
|
||||
* USNO USNO modem service
|
||||
* PTB PTB (Germany) modem service
|
||||
* TDF Allouis (France) Radio 164 kHz
|
||||
* DCF Mainflingen (Germany) Radio 77.5 kHz
|
||||
* MSF Rugby (UK) Radio 60 kHz
|
||||
* WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
|
||||
* WWVB Boulder (US) Radio 60 kHz
|
||||
* WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
|
||||
* CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
|
||||
* LORC LORAN-C radionavigation system
|
||||
* OMEG OMEGA radionavigation system
|
||||
* GPS Global Positioning Service
|
||||
* GOES Geostationary Orbit Environment Satellite
|
||||
*/
|
||||
public byte[] referenceIdentifier = {0, 0, 0, 0};
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the local clock was last set or corrected, in
|
||||
* seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double referenceTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request departed the client for the
|
||||
* server, in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double originateTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request arrived at the server, in seconds
|
||||
* since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double receiveTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the reply departed the server for the client,
|
||||
* in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double transmitTimestamp = 0;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage from an array of bytes.
|
||||
*/
|
||||
public NtpMessage(byte[] array) {
|
||||
// See the packet format diagram in RFC 2030 for details
|
||||
leapIndicator = (byte) ((array[0] >> 6) & 0x3);
|
||||
version = (byte) ((array[0] >> 3) & 0x7);
|
||||
mode = (byte) (array[0] & 0x7);
|
||||
stratum = unsignedByteToShort(array[1]);
|
||||
pollInterval = array[2];
|
||||
precision = array[3];
|
||||
|
||||
rootDelay = (array[4] * 256.0) +
|
||||
unsignedByteToShort(array[5]) +
|
||||
(unsignedByteToShort(array[6]) / 256.0) +
|
||||
(unsignedByteToShort(array[7]) / 65536.0);
|
||||
|
||||
rootDispersion = (unsignedByteToShort(array[8]) * 256.0) +
|
||||
unsignedByteToShort(array[9]) +
|
||||
(unsignedByteToShort(array[10]) / 256.0) +
|
||||
(unsignedByteToShort(array[11]) / 65536.0);
|
||||
|
||||
referenceIdentifier[0] = array[12];
|
||||
referenceIdentifier[1] = array[13];
|
||||
referenceIdentifier[2] = array[14];
|
||||
referenceIdentifier[3] = array[15];
|
||||
|
||||
referenceTimestamp = decodeTimestamp(array, 16);
|
||||
originateTimestamp = decodeTimestamp(array, 24);
|
||||
receiveTimestamp = decodeTimestamp(array, 32);
|
||||
transmitTimestamp = decodeTimestamp(array, 40);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage in client -> server mode, and sets the
|
||||
* transmit timestamp to the current time.
|
||||
*/
|
||||
public NtpMessage() {
|
||||
// Note that all the other member variables are already set with
|
||||
// appropriate default values.
|
||||
this.mode = 3;
|
||||
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method constructs the data bytes of a raw NTP packet.
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
// All bytes are automatically set to 0
|
||||
byte[] p = new byte[48];
|
||||
|
||||
p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
|
||||
p[1] = (byte) stratum;
|
||||
p[2] = (byte) pollInterval;
|
||||
p[3] = (byte) precision;
|
||||
|
||||
// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
|
||||
int l = (int) (rootDelay * 65536.0);
|
||||
p[4] = (byte) ((l >> 24) & 0xFF);
|
||||
p[5] = (byte) ((l >> 16) & 0xFF);
|
||||
p[6] = (byte) ((l >> 8) & 0xFF);
|
||||
p[7] = (byte) (l & 0xFF);
|
||||
|
||||
// root dispersion is an unsigned 16.16-bit FP, in Java there are no
|
||||
// unsigned primitive types, so we use a long which is 64-bits
|
||||
long ul = (long) (rootDispersion * 65536.0);
|
||||
p[8] = (byte) ((ul >> 24) & 0xFF);
|
||||
p[9] = (byte) ((ul >> 16) & 0xFF);
|
||||
p[10] = (byte) ((ul >> 8) & 0xFF);
|
||||
p[11] = (byte) (ul & 0xFF);
|
||||
|
||||
p[12] = referenceIdentifier[0];
|
||||
p[13] = referenceIdentifier[1];
|
||||
p[14] = referenceIdentifier[2];
|
||||
p[15] = referenceIdentifier[3];
|
||||
|
||||
encodeTimestamp(p, 16, referenceTimestamp);
|
||||
encodeTimestamp(p, 24, originateTimestamp);
|
||||
encodeTimestamp(p, 32, receiveTimestamp);
|
||||
encodeTimestamp(p, 40, transmitTimestamp);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a NtpMessage
|
||||
*/
|
||||
public String toString() {
|
||||
String precisionStr = new DecimalFormat("0.#E0").format(Math.pow(2, precision));
|
||||
|
||||
return "Leap indicator: " + leapIndicator + "\n" +
|
||||
"Version: " + version + "\n" +
|
||||
"Mode: " + mode + "\n" +
|
||||
"Stratum: " + stratum + "\n" +
|
||||
"Poll: " + pollInterval + "\n" +
|
||||
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
|
||||
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
|
||||
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
|
||||
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
|
||||
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
|
||||
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
|
||||
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
|
||||
"Transmit timestamp: " + timestampToString(transmitTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts an unsigned byte to a short. By default, Java assumes that
|
||||
* a byte is signed.
|
||||
*/
|
||||
public static short unsignedByteToShort(byte b) {
|
||||
if((b & 0x80)==0x80)
|
||||
return (short) (128 + (b & 0x7f));
|
||||
else
|
||||
return (short) b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will read 8 bytes of a message beginning at <code>pointer</code>
|
||||
* and return it as a double, according to the NTP 64-bit timestamp
|
||||
* format.
|
||||
*/
|
||||
public static double decodeTimestamp(byte[] array, int pointer) {
|
||||
double r = 0.0;
|
||||
|
||||
for(int i=0; i<8; i++) {
|
||||
r += unsignedByteToShort(array[pointer+i]) * Math.pow(2, (3-i)*8);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encodes a timestamp in the specified position in the message
|
||||
*/
|
||||
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
|
||||
// Converts a double into a 64-bit fixed point
|
||||
for(int i=0; i<8; i++) {
|
||||
// 2^24, 2^16, 2^8, .. 2^-32
|
||||
double base = Math.pow(2, (3-i)*8);
|
||||
|
||||
// Capture byte value
|
||||
array[pointer+i] = (byte) (timestamp / base);
|
||||
|
||||
// Subtract captured value from remaining total
|
||||
timestamp = timestamp - (double) (unsignedByteToShort(array[pointer+i]) * base);
|
||||
}
|
||||
|
||||
// From RFC 2030: It is advisable to fill the non-significant
|
||||
// low order bits of the timestamp with a random, unbiased
|
||||
// bitstring, both to avoid systematic roundoff errors and as
|
||||
// a means of loop detection and replay detection.
|
||||
array[7+pointer] = (byte) (Math.random()*255.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
|
||||
* formatted date/time string.
|
||||
*/
|
||||
public static String timestampToString(double timestamp) {
|
||||
if(timestamp==0) return "0";
|
||||
|
||||
// timestamp is relative to 1900, utc is used by Java and is relative
|
||||
// to 1970
|
||||
double utc = timestamp - (2208988800.0);
|
||||
|
||||
// milliseconds
|
||||
long ms = (long) (utc * 1000.0);
|
||||
|
||||
// date/time
|
||||
String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
|
||||
|
||||
// fraction
|
||||
double fraction = timestamp - ((long) timestamp);
|
||||
String fractionSting = new DecimalFormat(".000000").format(fraction);
|
||||
|
||||
return date + fractionSting;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a reference identifier according
|
||||
* to the rules set out in RFC 2030.
|
||||
*/
|
||||
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
|
||||
// From the RFC 2030:
|
||||
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
|
||||
// or stratum-1 (primary) servers, this is a four-character ASCII
|
||||
// string, left justified and zero padded to 32 bits.
|
||||
if(stratum==0 || stratum==1) {
|
||||
return new String(ref);
|
||||
}
|
||||
|
||||
// In NTP Version 3 secondary servers, this is the 32-bit IPv4
|
||||
// address of the reference source.
|
||||
else if(version==3) {
|
||||
return unsignedByteToShort(ref[0]) + "." +
|
||||
unsignedByteToShort(ref[1]) + "." +
|
||||
unsignedByteToShort(ref[2]) + "." +
|
||||
unsignedByteToShort(ref[3]);
|
||||
}
|
||||
|
||||
// In NTP Version 4 secondary servers, this is the low order 32 bits
|
||||
// of the latest transmit timestamp of the reference source.
|
||||
else if(version==4) {
|
||||
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
|
||||
(unsignedByteToShort(ref[1]) / 65536.0) +
|
||||
(unsignedByteToShort(ref[2]) / 16777216.0) +
|
||||
(unsignedByteToShort(ref[3]) / 4294967296.0));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
110
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
110
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
@ -0,0 +1,110 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Periodically query a series of NTP servers and post the offset
|
||||
* to a given URL. It tries the NTP servers in order, contacting them
|
||||
* using UDP port 123, and sends the current date to the URL specified
|
||||
* (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
|
||||
* It does this every 5 minutes, forever.
|
||||
*
|
||||
* Usage: <pre>
|
||||
* Timestamper URL ntpServer1[ ntpServer2]*
|
||||
* </pre>
|
||||
*/
|
||||
public class Timestamper implements Runnable {
|
||||
private static Log _log = new Log(Timestamper.class);
|
||||
private String _targetURL;
|
||||
private String _serverList[];
|
||||
|
||||
private int DELAY_MS = 5*60*1000;
|
||||
|
||||
public Timestamper(String url, String serverNames[]) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating new timestamper pointing at " + url);
|
||||
_targetURL = url;
|
||||
_serverList = serverNames;
|
||||
}
|
||||
|
||||
public void startTimestamper() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting timestamper pointing at " + _targetURL);
|
||||
I2PThread t = new I2PThread(this, "Timestamper");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting up timestamper");
|
||||
try {
|
||||
while (true) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _serverList);
|
||||
long now = NtpClient.currentTime(_serverList);
|
||||
if (now < 0) {
|
||||
_log.error("Unable to contact any of the NTP servers - network disconnect?");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamp time");
|
||||
stampTime(now);
|
||||
}
|
||||
try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HTTP request to a given URL specifying the current time
|
||||
*/
|
||||
private void stampTime(long now) {
|
||||
try {
|
||||
String toRequest = _targetURL + "&now=" + getNow(now);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamping [" + toRequest + "]");
|
||||
URL url = new URL(toRequest);
|
||||
Object o = url.getContent();
|
||||
// ignore the content
|
||||
} catch (MalformedURLException mue) {
|
||||
_log.error("Invalid URL", mue);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error stamping the time", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
|
||||
private String getNow(long now) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(now));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if ( (args == null) || (args.length < 2) ) {
|
||||
usage();
|
||||
return;
|
||||
//args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
|
||||
}
|
||||
String servers[] = new String[args.length-1];
|
||||
System.arraycopy(args, 1, servers, 0, servers.length);
|
||||
Timestamper ts = new Timestamper(args[0], servers);
|
||||
ts.startTimestamper();
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
|
||||
_log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
<ant dir="apps/sam/java/" target="jar" />
|
||||
<ant dir="apps/heartbeat/java/" target="jar" />
|
||||
<ant dir="apps/netmonitor/java/" target="jar" />
|
||||
<ant dir="apps/time/java/" target="jar" />
|
||||
<ant dir="installer/java/" target="jar" />
|
||||
</target>
|
||||
<target name="compile" />
|
||||
@ -36,6 +37,7 @@
|
||||
<copy file="apps/sam/java/build/sam.jar" todir="build/" />
|
||||
<copy file="apps/heartbeat/java/build/heartbeat.jar" todir="build/" />
|
||||
<copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
|
||||
<copy file="apps/time/java/build/timestamper.jar" todir="build/" />
|
||||
<copy file="installer/java/build/install.jar" todir="build/" />
|
||||
<copy file="installer/java/build/guiinstall.jar" todir="build/" />
|
||||
<copy file="installer/java/build/fetchseeds.jar" todir="build/" />
|
||||
@ -64,6 +66,7 @@
|
||||
<ant dir="apps/sam/java/" target="distclean" />
|
||||
<ant dir="apps/heartbeat/java/" target="distclean" />
|
||||
<ant dir="apps/netmonitor/java/" target="distclean" />
|
||||
<ant dir="apps/time/java/" target="distclean" />
|
||||
<ant dir="installer/java/" target="distclean" />
|
||||
<delete>
|
||||
<fileset dir="." includes="**/*.class" />
|
||||
|
@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.2 $ $Date: 2004/04/10 06:40:05 $";
|
||||
public final static String VERSION = "0.3.0.4";
|
||||
public final static String ID = "$Revision: 1.5 $ $Date: 2004/05/07 12:52:49 $";
|
||||
public final static String VERSION = "0.3.1.2";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
449
core/java/src/net/i2p/I2PAppContext.java
Normal file
449
core/java/src/net/i2p/I2PAppContext.java
Normal file
@ -0,0 +1,449 @@
|
||||
package net.i2p;
|
||||
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.PersistentSessionKeyManager;
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.crypto.ElGamalEngine;
|
||||
import net.i2p.crypto.DummyElGamalEngine;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.HMACSHA256Generator;
|
||||
import net.i2p.crypto.AESEngine;
|
||||
import net.i2p.crypto.CryptixAESEngine;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
|
||||
* using the traditional singleton, where any component can access the component
|
||||
* in question directly, all of those I2P related singletons are exposed through
|
||||
* a particular I2PAppContext. This helps not only with understanding their use
|
||||
* and the components I2P exposes, but it also allows multiple isolated
|
||||
* environments to operate concurrently within the same JVM - particularly useful
|
||||
* for stubbing out implementations of the rooted components and simulating the
|
||||
* software's interaction between multiple instances.</p>
|
||||
*
|
||||
* As a simplification, there is also a global context - if some component needs
|
||||
* access to one of the singletons but doesn't have its own context from which
|
||||
* to root itself, it binds to the I2PAppContext's globalAppContext(), which is
|
||||
* the first context that was created within the JVM, or a new one if no context
|
||||
* existed already. This functionality is often used within the I2P core for
|
||||
* logging - e.g. <pre>
|
||||
* private static final Log _log = new Log(someClass.class);
|
||||
* </pre>
|
||||
* It is for this reason that applications that care about working with multiple
|
||||
* contexts should build their own context as soon as possible (within the main(..))
|
||||
* so that any referenced components will latch on to that context instead of
|
||||
* instantiating a new one. However, there are situations in which both can be
|
||||
* relevent.
|
||||
*
|
||||
*/
|
||||
public class I2PAppContext {
|
||||
/** the context that components without explicit root are bound */
|
||||
protected static I2PAppContext _globalAppContext;
|
||||
/**
|
||||
* Determine if the app context been initialized. If this is false
|
||||
* and something asks for the globalAppContext, a new one is created,
|
||||
* otherwise the existing one is used.
|
||||
*
|
||||
*/
|
||||
protected static volatile boolean _globalAppContextInitialized;
|
||||
|
||||
private Properties _overrideProps;
|
||||
|
||||
private StatManager _statManager;
|
||||
private SessionKeyManager _sessionKeyManager;
|
||||
private NamingService _namingService;
|
||||
private ElGamalEngine _elGamalEngine;
|
||||
private ElGamalAESEngine _elGamalAESEngine;
|
||||
private AESEngine _AESEngine;
|
||||
private LogManager _logManager;
|
||||
private HMACSHA256Generator _hmac;
|
||||
private SHA256Generator _sha;
|
||||
private Clock _clock;
|
||||
private DSAEngine _dsa;
|
||||
private RoutingKeyGenerator _routingKeyGenerator;
|
||||
private RandomSource _random;
|
||||
private KeyGenerator _keyGenerator;
|
||||
private volatile boolean _statManagerInitialized;
|
||||
private volatile boolean _sessionKeyManagerInitialized;
|
||||
private volatile boolean _namingServiceInitialized;
|
||||
private volatile boolean _elGamalEngineInitialized;
|
||||
private volatile boolean _elGamalAESEngineInitialized;
|
||||
private volatile boolean _AESEngineInitialized;
|
||||
private volatile boolean _logManagerInitialized;
|
||||
private volatile boolean _hmacInitialized;
|
||||
private volatile boolean _shaInitialized;
|
||||
private volatile boolean _clockInitialized;
|
||||
private volatile boolean _dsaInitialized;
|
||||
private volatile boolean _routingKeyGeneratorInitialized;
|
||||
private volatile boolean _randomInitialized;
|
||||
private volatile boolean _keyGeneratorInitialized;
|
||||
|
||||
/**
|
||||
* Pull the default context, creating a new one if necessary, else using
|
||||
* the first one created.
|
||||
*
|
||||
*/
|
||||
public static I2PAppContext getGlobalContext() {
|
||||
if (!_globalAppContextInitialized) {
|
||||
synchronized (I2PAppContext.class) {
|
||||
System.err.println("*** Building seperate global context!");
|
||||
if (_globalAppContext == null)
|
||||
_globalAppContext = new I2PAppContext(false, null);
|
||||
_globalAppContextInitialized = true;
|
||||
}
|
||||
}
|
||||
return _globalAppContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets root a brand new context
|
||||
*
|
||||
*/
|
||||
public I2PAppContext() {
|
||||
this(true, null);
|
||||
}
|
||||
/**
|
||||
* Lets root a brand new context
|
||||
*
|
||||
*/
|
||||
public I2PAppContext(Properties envProps) {
|
||||
this(true, envProps);
|
||||
}
|
||||
/**
|
||||
* @param doInit should this context be used as the global one (if necessary)?
|
||||
*/
|
||||
private I2PAppContext(boolean doInit, Properties envProps) {
|
||||
//System.out.println("App context created: " + this);
|
||||
if (doInit) {
|
||||
if (!_globalAppContextInitialized) {
|
||||
synchronized (I2PAppContext.class) {
|
||||
if (_globalAppContext == null) {
|
||||
_globalAppContext = this;
|
||||
_globalAppContextInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_overrideProps = envProps;
|
||||
_statManager = null;
|
||||
_sessionKeyManager = null;
|
||||
_namingService = null;
|
||||
_elGamalEngine = null;
|
||||
_elGamalAESEngine = null;
|
||||
_logManager = null;
|
||||
_statManagerInitialized = false;
|
||||
_sessionKeyManagerInitialized = false;
|
||||
_namingServiceInitialized = false;
|
||||
_elGamalEngineInitialized = false;
|
||||
_elGamalAESEngineInitialized = false;
|
||||
_logManagerInitialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the configuration attributes of this context, using properties
|
||||
* provided during the context construction, or falling back on
|
||||
* System.getProperty if no properties were provided during construction
|
||||
* (or the specified prop wasn't included).
|
||||
*
|
||||
*/
|
||||
public String getProperty(String propName) {
|
||||
if (_overrideProps != null) {
|
||||
if (_overrideProps.containsKey(propName))
|
||||
return _overrideProps.getProperty(propName);
|
||||
}
|
||||
return System.getProperty(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the configuration attributes of this context, using properties
|
||||
* provided during the context construction, or falling back on
|
||||
* System.getProperty if no properties were provided during construction
|
||||
* (or the specified prop wasn't included).
|
||||
*
|
||||
*/
|
||||
public String getProperty(String propName, String defaultValue) {
|
||||
if (_overrideProps != null) {
|
||||
if (_overrideProps.containsKey(propName))
|
||||
return _overrideProps.getProperty(propName, defaultValue);
|
||||
}
|
||||
return System.getProperty(propName, defaultValue);
|
||||
}
|
||||
/**
|
||||
* Access the configuration attributes of this context, listing the properties
|
||||
* provided during the context construction, as well as the ones included in
|
||||
* System.getProperties.
|
||||
*
|
||||
* @return set of Strings containing the names of defined system properties
|
||||
*/
|
||||
public Set getPropertyNames() {
|
||||
Set names = new HashSet(System.getProperties().keySet());
|
||||
if (_overrideProps != null)
|
||||
names.addAll(_overrideProps.keySet());
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* The statistics component with which we can track various events
|
||||
* over time.
|
||||
*/
|
||||
public StatManager statManager() {
|
||||
if (!_statManagerInitialized) initializeStatManager();
|
||||
return _statManager;
|
||||
}
|
||||
private void initializeStatManager() {
|
||||
synchronized (this) {
|
||||
if (_statManager == null)
|
||||
_statManager = new StatManager(this);
|
||||
_statManagerInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The session key manager which coordinates the sessionKey / sessionTag
|
||||
* data. This component allows transparent operation of the
|
||||
* ElGamal/AES+SessionTag algorithm, and contains all of the session tags
|
||||
* for one particular application. If you want to seperate multiple apps
|
||||
* to have their own sessionTags and sessionKeys, they should use different
|
||||
* I2PAppContexts, and hence, different sessionKeyManagers.
|
||||
*
|
||||
*/
|
||||
public SessionKeyManager sessionKeyManager() {
|
||||
if (!_sessionKeyManagerInitialized) initializeSessionKeyManager();
|
||||
return _sessionKeyManager;
|
||||
}
|
||||
private void initializeSessionKeyManager() {
|
||||
synchronized (this) {
|
||||
if (_sessionKeyManager == null)
|
||||
_sessionKeyManager = new PersistentSessionKeyManager(this);
|
||||
_sessionKeyManagerInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull up the naming service used in this context. The naming service itself
|
||||
* works by querying the context's properties, so those props should be
|
||||
* specified to customize the naming service exposed.
|
||||
*/
|
||||
public NamingService namingService() {
|
||||
if (!_namingServiceInitialized) initializeNamingService();
|
||||
return _namingService;
|
||||
}
|
||||
private void initializeNamingService() {
|
||||
synchronized (this) {
|
||||
if (_namingService == null) {
|
||||
_namingService = NamingService.createInstance(this);
|
||||
}
|
||||
_namingServiceInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the ElGamal engine used within this context. While it doesn't
|
||||
* really have anything substantial that is context specific (the algorithm
|
||||
* just does the algorithm), it does transparently use the context for logging
|
||||
* its performance and activity. In addition, the engine can be swapped with
|
||||
* the context's properties (though only someone really crazy should mess with
|
||||
* it ;)
|
||||
*/
|
||||
public ElGamalEngine elGamalEngine() {
|
||||
if (!_elGamalEngineInitialized) initializeElGamalEngine();
|
||||
return _elGamalEngine;
|
||||
}
|
||||
private void initializeElGamalEngine() {
|
||||
synchronized (this) {
|
||||
if (_elGamalEngine == null) {
|
||||
if ("off".equals(getProperty("i2p.encryption", "on")))
|
||||
_elGamalEngine = new DummyElGamalEngine(this);
|
||||
else
|
||||
_elGamalEngine = new ElGamalEngine(this);
|
||||
}
|
||||
_elGamalEngineInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the ElGamal/AES+SessionTag engine for this context. The algorithm
|
||||
* makes use of the context's sessionKeyManager to coordinate transparent
|
||||
* access to the sessionKeys and sessionTags, as well as the context's elGamal
|
||||
* engine (which in turn keeps stats, etc).
|
||||
*
|
||||
*/
|
||||
public ElGamalAESEngine elGamalAESEngine() {
|
||||
if (!_elGamalAESEngineInitialized) initializeElGamalAESEngine();
|
||||
return _elGamalAESEngine;
|
||||
}
|
||||
private void initializeElGamalAESEngine() {
|
||||
synchronized (this) {
|
||||
if (_elGamalAESEngine == null)
|
||||
_elGamalAESEngine = new ElGamalAESEngine(this);
|
||||
_elGamalAESEngineInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ok, I'll admit it. there is no good reason for having a context specific
|
||||
* AES engine. We dont really keep stats on it, since its just too fast to
|
||||
* matter. Though for the crazy people out there, we do expose a way to
|
||||
* disable it.
|
||||
*/
|
||||
public AESEngine AESEngine() {
|
||||
if (!_AESEngineInitialized) initializeAESEngine();
|
||||
return _AESEngine;
|
||||
}
|
||||
private void initializeAESEngine() {
|
||||
synchronized (this) {
|
||||
if (_AESEngine == null) {
|
||||
if ("off".equals(getProperty("i2p.encryption", "on")))
|
||||
_AESEngine = new AESEngine(this);
|
||||
else
|
||||
_AESEngine = new CryptixAESEngine(this);
|
||||
}
|
||||
_AESEngineInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the log manager for this context, which may in turn have its own
|
||||
* set of configuration settings (loaded from the context's properties).
|
||||
* Each context's logManager keeps its own isolated set of Log instances with
|
||||
* their own log levels, output locations, and rotation configuration.
|
||||
*/
|
||||
public LogManager logManager() {
|
||||
if (!_logManagerInitialized) initializeLogManager();
|
||||
return _logManager;
|
||||
}
|
||||
private void initializeLogManager() {
|
||||
synchronized (this) {
|
||||
if (_logManager == null)
|
||||
_logManager = new LogManager(this);
|
||||
_logManagerInitialized = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* There is absolutely no good reason to make this context specific,
|
||||
* other than for consistency, and perhaps later we'll want to
|
||||
* include some stats.
|
||||
*/
|
||||
public HMACSHA256Generator hmac() {
|
||||
if (!_hmacInitialized) initializeHMAC();
|
||||
return _hmac;
|
||||
}
|
||||
private void initializeHMAC() {
|
||||
synchronized (this) {
|
||||
if (_hmac == null)
|
||||
_hmac= new HMACSHA256Generator(this);
|
||||
_hmacInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our SHA256 instance (see the hmac discussion for why its context specific)
|
||||
*
|
||||
*/
|
||||
public SHA256Generator sha() {
|
||||
if (!_shaInitialized) initializeSHA();
|
||||
return _sha;
|
||||
}
|
||||
private void initializeSHA() {
|
||||
synchronized (this) {
|
||||
if (_sha == null)
|
||||
_sha= new SHA256Generator(this);
|
||||
_shaInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our DSA engine (see HMAC and SHA above)
|
||||
*
|
||||
*/
|
||||
public DSAEngine dsa() {
|
||||
if (!_dsaInitialized) initializeDSA();
|
||||
return _dsa;
|
||||
}
|
||||
private void initializeDSA() {
|
||||
synchronized (this) {
|
||||
if (_dsa == null)
|
||||
_dsa = new DSAEngine(this);
|
||||
_dsaInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to generate ElGamal, DSA, and Session keys. For why it is in
|
||||
* the appContext, see the DSA, HMAC, and SHA comments above.
|
||||
*/
|
||||
public KeyGenerator keyGenerator() {
|
||||
if (!_keyGeneratorInitialized) initializeKeyGenerator();
|
||||
return _keyGenerator;
|
||||
}
|
||||
private void initializeKeyGenerator() {
|
||||
synchronized (this) {
|
||||
if (_keyGenerator == null)
|
||||
_keyGenerator = new KeyGenerator(this);
|
||||
_keyGeneratorInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The context's synchronized clock, which is kept context specific only to
|
||||
* enable simulators to play with clock skew among different instances.
|
||||
*
|
||||
*/
|
||||
public Clock clock() {
|
||||
if (!_clockInitialized) initializeClock();
|
||||
return _clock;
|
||||
}
|
||||
private void initializeClock() {
|
||||
synchronized (this) {
|
||||
if (_clock == null)
|
||||
_clock = new Clock(this);
|
||||
_clockInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how much do we want to mess with the keys to turn them
|
||||
* into something we can route. This is context specific because we
|
||||
* may want to test out how things react when peers don't agree on
|
||||
* how to skew.
|
||||
*
|
||||
*/
|
||||
public RoutingKeyGenerator routingKeyGenerator() {
|
||||
if (!_routingKeyGeneratorInitialized) initializeRoutingKeyGenerator();
|
||||
return _routingKeyGenerator;
|
||||
}
|
||||
private void initializeRoutingKeyGenerator() {
|
||||
synchronized (this) {
|
||||
if (_routingKeyGenerator == null)
|
||||
_routingKeyGenerator = new RoutingKeyGenerator(this);
|
||||
_routingKeyGeneratorInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [insert snarky comment here]
|
||||
*
|
||||
*/
|
||||
public RandomSource random() {
|
||||
if (!_randomInitialized) initializeRandom();
|
||||
return _random;
|
||||
}
|
||||
private void initializeRandom() {
|
||||
synchronized (this) {
|
||||
if (_random == null)
|
||||
_random = new RandomSource(this);
|
||||
_randomInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* ATalk - anonymous talk, demonstrating a trivial I2P usage scenario.
|
||||
@ -290,6 +291,7 @@ public class ATalk implements I2PSessionListener, Runnable {
|
||||
|
||||
/** driver */
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext context = new I2PAppContext();
|
||||
if (args.length == 2) {
|
||||
String myKeyFile = args[0];
|
||||
String myDestinationFile = args[1];
|
||||
@ -309,9 +311,9 @@ public class ATalk implements I2PSessionListener, Runnable {
|
||||
String peerDestFile = args[1];
|
||||
String shouldLog = args[2];
|
||||
if (Boolean.TRUE.toString().equalsIgnoreCase(shouldLog))
|
||||
LogManager.getInstance().setDisplayOnScreen(true);
|
||||
context.logManager().setDisplayOnScreen(true);
|
||||
else
|
||||
LogManager.getInstance().setDisplayOnScreen(false);
|
||||
context.logManager().setDisplayOnScreen(false);
|
||||
String logFile = args[2];
|
||||
Thread talkThread = new I2PThread(new ATalk(myKeyfile, peerDestFile));
|
||||
talkThread.start();
|
||||
|
@ -11,6 +11,7 @@ package net.i2p.client;
|
||||
|
||||
import net.i2p.data.i2cp.DisconnectMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP disconnect messages from the router
|
||||
@ -18,8 +19,8 @@ import net.i2p.data.i2cp.I2CPMessage;
|
||||
* @author jrandom
|
||||
*/
|
||||
class DisconnectMessageHandler extends HandlerImpl {
|
||||
public DisconnectMessageHandler() {
|
||||
super(DisconnectMessage.MESSAGE_TYPE);
|
||||
public DisconnectMessageHandler(I2PAppContext context) {
|
||||
super(context, DisconnectMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
|
@ -10,6 +10,7 @@ package net.i2p.client;
|
||||
*/
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Base class for handling I2CP messages
|
||||
@ -19,8 +20,10 @@ import net.i2p.util.Log;
|
||||
abstract class HandlerImpl implements I2CPMessageHandler {
|
||||
protected Log _log;
|
||||
private int _type;
|
||||
protected I2PAppContext _context;
|
||||
|
||||
public HandlerImpl(int type) {
|
||||
public HandlerImpl(I2PAppContext context, int type) {
|
||||
_context = context;
|
||||
_type = type;
|
||||
_log = new Log(getClass());
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import net.i2p.data.i2cp.SendMessageMessage;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Produce the various messages the session needs to send to the router.
|
||||
@ -41,7 +42,12 @@ import net.i2p.util.RandomSource;
|
||||
class I2CPMessageProducer {
|
||||
private final static Log _log = new Log(I2CPMessageProducer.class);
|
||||
private final static RandomSource _rand = RandomSource.getInstance();
|
||||
private I2PAppContext _context;
|
||||
|
||||
public I2CPMessageProducer(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all the messages that a client needs to send to a router to establish
|
||||
* a new session.
|
||||
@ -102,7 +108,7 @@ class I2CPMessageProducer {
|
||||
Payload data = new Payload();
|
||||
// randomize padding
|
||||
int size = payload.length + RandomSource.getInstance().nextInt(1024);
|
||||
byte encr[] = ElGamalAESEngine.encrypt(payload, dest.getPublicKey(), key, tags, tag, newKey, size);
|
||||
byte encr[] = _context.elGamalAESEngine().encrypt(payload, dest.getPublicKey(), key, tags, tag, newKey, size);
|
||||
// yes, in an intelligent component, newTags would be queued for confirmation along with key, and
|
||||
// generateNewTags would only generate tags if necessary
|
||||
|
||||
|
@ -22,6 +22,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Base client implementation
|
||||
@ -70,7 +71,13 @@ class I2PClientImpl implements I2PClient {
|
||||
*
|
||||
*/
|
||||
public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
//return new I2PSessionImpl(destKeyStream, options); // not thread safe
|
||||
return new I2PSessionImpl2(destKeyStream, options); // thread safe
|
||||
return createSession(I2PAppContext.getGlobalContext(), destKeyStream, options);
|
||||
}
|
||||
/**
|
||||
* Create a new session (though do not connect it yet)
|
||||
*
|
||||
*/
|
||||
public I2PSession createSession(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
return new I2PSessionImpl2(context, destKeyStream, options); // thread safe
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import net.i2p.data.i2cp.RequestLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Contains a map of message handlers that a session will want to use
|
||||
@ -28,19 +29,19 @@ import net.i2p.util.Log;
|
||||
class I2PClientMessageHandlerMap {
|
||||
private final static Log _log = new Log(I2PClientMessageHandlerMap.class);
|
||||
/** map of message type id --> I2CPMessageHandler */
|
||||
private static Map _handlers;
|
||||
private Map _handlers;
|
||||
|
||||
static {
|
||||
public I2PClientMessageHandlerMap(I2PAppContext context) {
|
||||
_handlers = new HashMap();
|
||||
_handlers.put(new Integer(DisconnectMessage.MESSAGE_TYPE), new DisconnectMessageHandler());
|
||||
_handlers.put(new Integer(SessionStatusMessage.MESSAGE_TYPE), new SessionStatusMessageHandler());
|
||||
_handlers.put(new Integer(RequestLeaseSetMessage.MESSAGE_TYPE), new RequestLeaseSetMessageHandler());
|
||||
_handlers.put(new Integer(MessagePayloadMessage.MESSAGE_TYPE), new MessagePayloadMessageHandler());
|
||||
_handlers.put(new Integer(MessageStatusMessage.MESSAGE_TYPE), new MessageStatusMessageHandler());
|
||||
_handlers.put(new Integer(SetDateMessage.MESSAGE_TYPE), new SetDateMessageHandler());
|
||||
_handlers.put(new Integer(DisconnectMessage.MESSAGE_TYPE), new DisconnectMessageHandler(context));
|
||||
_handlers.put(new Integer(SessionStatusMessage.MESSAGE_TYPE), new SessionStatusMessageHandler(context));
|
||||
_handlers.put(new Integer(RequestLeaseSetMessage.MESSAGE_TYPE), new RequestLeaseSetMessageHandler(context));
|
||||
_handlers.put(new Integer(MessagePayloadMessage.MESSAGE_TYPE), new MessagePayloadMessageHandler(context));
|
||||
_handlers.put(new Integer(MessageStatusMessage.MESSAGE_TYPE), new MessageStatusMessageHandler(context));
|
||||
_handlers.put(new Integer(SetDateMessage.MESSAGE_TYPE), new SetDateMessageHandler(context));
|
||||
}
|
||||
|
||||
public static I2CPMessageHandler getHandler(int messageTypeId) {
|
||||
public I2CPMessageHandler getHandler(int messageTypeId) {
|
||||
I2CPMessageHandler handler = (I2CPMessageHandler) _handlers.get(new Integer(messageTypeId));
|
||||
return handler;
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Implementation of an I2P session running over TCP. This class is NOT thread safe -
|
||||
@ -47,7 +48,7 @@ import net.i2p.util.Log;
|
||||
* @author jrandom
|
||||
*/
|
||||
abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
|
||||
private final static Log _log = new Log(I2PSessionImpl.class);
|
||||
private Log _log;
|
||||
/** who we are */
|
||||
private Destination _myDestination;
|
||||
/** private key for decryption */
|
||||
@ -79,6 +80,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
protected I2CPMessageProducer _producer;
|
||||
/** map of integer --> MessagePayloadMessage */
|
||||
Map _availableMessages;
|
||||
|
||||
protected I2PClientMessageHandlerMap _handlerMap;
|
||||
|
||||
/** used to seperate things out so we can get rid of singletons */
|
||||
protected I2PAppContext _context;
|
||||
|
||||
/** MessageStatusMessage status from the most recent send that hasn't been consumed */
|
||||
private List _receivedStatus;
|
||||
@ -108,9 +114,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
*
|
||||
* @throws I2PSessionException if there is a problem loading the private keys or
|
||||
*/
|
||||
public I2PSessionImpl(InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
public I2PSessionImpl(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(I2PSessionImpl.class);
|
||||
_handlerMap = new I2PClientMessageHandlerMap(context);
|
||||
_closed = true;
|
||||
_producer = new I2CPMessageProducer();
|
||||
_producer = new I2CPMessageProducer(context);
|
||||
_availableMessages = new HashMap();
|
||||
try {
|
||||
readDestination(destKeyStream);
|
||||
@ -139,13 +148,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_portNum = Integer.parseInt(portNum);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid port number specified, defaulting to "
|
||||
+ TestServer.LISTEN_PORT, nfe);
|
||||
_log.warn("Invalid port number specified, defaulting to "
|
||||
+ TestServer.LISTEN_PORT, nfe);
|
||||
_portNum = TestServer.LISTEN_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
private static Properties filter(Properties options) {
|
||||
private Properties filter(Properties options) {
|
||||
Properties rv = new Properties();
|
||||
for (Iterator iter = options.keySet().iterator(); iter.hasNext();) {
|
||||
String key = (String) iter.next();
|
||||
@ -212,7 +221,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
*/
|
||||
public void connect() throws I2PSessionException {
|
||||
_closed = false;
|
||||
long startConnect = Clock.getInstance().now();
|
||||
long startConnect = _context.clock().now();
|
||||
try {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("connect begin to " + _hostname + ":" + _portNum);
|
||||
_socket = new Socket(_hostname, _portNum);
|
||||
@ -251,7 +260,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
}
|
||||
}
|
||||
long connected = Clock.getInstance().now();
|
||||
long connected = _context.clock().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Lease set created with inbound tunnels after "
|
||||
+ (connected - startConnect)
|
||||
@ -309,6 +318,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static volatile long __notifierId = 0;
|
||||
|
||||
/**
|
||||
* Recieve a payload message and let the app know its available
|
||||
*/
|
||||
@ -328,9 +339,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_sessionListener.messageAvailable(I2PSessionImpl.this, id, size);
|
||||
}
|
||||
});
|
||||
notifier.setName("Notifier [" + _sessionId + "/" + id + "]");
|
||||
long nid = ++__notifierId;
|
||||
notifier.setName("Notifier " + nid);
|
||||
notifier.setDaemon(true);
|
||||
notifier.start();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Notifier " + nid + " is for session " + _sessionId + ", message " + id + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +353,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
*
|
||||
*/
|
||||
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
|
||||
I2CPMessageHandler handler = I2PClientMessageHandlerMap.getHandler(message.getType());
|
||||
I2CPMessageHandler handler = _handlerMap.getHandler(message.getType());
|
||||
if (handler == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unknown message or unhandleable message received: type = "
|
||||
@ -438,7 +452,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (_closed) return;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Destroy the session", new Exception("DestroySession()"));
|
||||
_closed = true;
|
||||
if (sendDisconnect) {
|
||||
try {
|
||||
_producer.disconnect(this);
|
||||
@ -446,6 +459,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
propogateError("Error destroying the session", ipe);
|
||||
}
|
||||
}
|
||||
_closed = true;
|
||||
closeSocket();
|
||||
if (_sessionListener != null) _sessionListener.disconnected(this);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Thread safe implementation of an I2P session running over TCP.
|
||||
@ -33,7 +34,7 @@ import net.i2p.util.RandomSource;
|
||||
* @author jrandom
|
||||
*/
|
||||
class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
private final static Log _log = new Log(I2PSessionImpl2.class);
|
||||
private Log _log;
|
||||
|
||||
/** set of MessageState objects, representing all of the messages in the process of being sent */
|
||||
private Set _sendingStates;
|
||||
@ -48,8 +49,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
*
|
||||
* @throws I2PSessionException if there is a problem loading the private keys or
|
||||
*/
|
||||
public I2PSessionImpl2(InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
super(destKeyStream, options);
|
||||
public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
super(ctx, destKeyStream, options);
|
||||
_log = ctx.logManager().getLog(I2PSessionImpl2.class);
|
||||
_sendingStates = new HashSet(32);
|
||||
}
|
||||
|
||||
@ -63,6 +65,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
|
||||
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
|
||||
|
||||
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
|
||||
}
|
||||
|
||||
@ -95,22 +98,22 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
|
||||
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
|
||||
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
Set sentTags = null;
|
||||
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
|
||||
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
sentTags = createNewTags(50);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Tags are almost expired, adding 50 new ones");
|
||||
}
|
||||
SessionKey newKey = null;
|
||||
if (false) // rekey
|
||||
newKey = KeyGenerator.getInstance().generateSessionKey();
|
||||
newKey = _context.keyGenerator().generateSessionKey();
|
||||
|
||||
long nonce = (long) RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
|
||||
long nonce = (long)_context.random().nextInt(Integer.MAX_VALUE);
|
||||
MessageState state = new MessageState(nonce);
|
||||
state.setKey(key);
|
||||
state.setTags(sentTags);
|
||||
@ -137,7 +140,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_log.debug("Adding sending state " + state.getMessageId() + " / "
|
||||
+ state.getNonce());
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, Clock.getInstance().now() + getTimeout());
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + getTimeout());
|
||||
synchronized (_sendingStates) {
|
||||
_sendingStates.remove(state);
|
||||
}
|
||||
@ -163,22 +167,22 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
|
||||
private boolean sendGuaranteed(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
|
||||
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
Set sentTags = null;
|
||||
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
|
||||
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
sentTags = createNewTags(50);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Tags are almost expired, adding 50 new ones");
|
||||
}
|
||||
SessionKey newKey = null;
|
||||
if (false) // rekey
|
||||
newKey = KeyGenerator.getInstance().generateSessionKey();
|
||||
newKey = _context.keyGenerator().generateSessionKey();
|
||||
|
||||
long nonce = (long) RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
|
||||
long nonce = (long)_context.random().nextInt(Integer.MAX_VALUE);
|
||||
MessageState state = new MessageState(nonce);
|
||||
state.setKey(key);
|
||||
state.setTags(sentTags);
|
||||
@ -206,9 +210,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
+ state.getNonce());
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
if (isGuaranteed())
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS, Clock.getInstance().now() + SEND_TIMEOUT);
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS,
|
||||
_context.clock().now() + SEND_TIMEOUT);
|
||||
else
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, Clock.getInstance().now() + SEND_TIMEOUT);
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + SEND_TIMEOUT);
|
||||
synchronized (_sendingStates) {
|
||||
_sendingStates.remove(state);
|
||||
}
|
||||
@ -218,11 +224,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
if ((!accepted) || (state.getMessageId() == null)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("State with nonce " + state.getNonce()
|
||||
+ " was not accepted? (no messageId!!)");
|
||||
+ " was not accepted? (no messageId!! found=" + found
|
||||
+ " msgId=" + state.getMessageId() + ")",
|
||||
new Exception("Race on accept/success status messages, or reconnected?"));
|
||||
nackTags(state);
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
|
||||
disconnect();
|
||||
//if (_log.shouldLog(Log.CRIT))
|
||||
// _log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
|
||||
//disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -250,9 +258,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
+ state.getTags());
|
||||
if ((state.getTags() != null) && (state.getTags().size() > 0)) {
|
||||
if (state.getNewKey() == null)
|
||||
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getKey(), state.getTags());
|
||||
_context.sessionKeyManager().tagsDelivered(state.getTo().getPublicKey(), state.getKey(), state.getTags());
|
||||
else
|
||||
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getNewKey(), state.getTags());
|
||||
_context.sessionKeyManager().tagsDelivered(state.getTo().getPublicKey(), state.getNewKey(), state.getTags());
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +268,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("nack tags for msgId " + state.getMessageId() + " / " + state.getNonce()
|
||||
+ " key = " + state.getKey());
|
||||
SessionKeyManager.getInstance().failTags(state.getTo().getPublicKey());
|
||||
_context.sessionKeyManager().failTags(state.getTo().getPublicKey());
|
||||
}
|
||||
|
||||
public void receiveStatus(int msgId, long nonce, int status) {
|
||||
|
@ -16,6 +16,7 @@ import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP MessagePayloadMessages from the router delivering the contents
|
||||
@ -25,8 +26,8 @@ import net.i2p.data.i2cp.ReceiveMessageEndMessage;
|
||||
* @author jrandom
|
||||
*/
|
||||
class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
public MessagePayloadMessageHandler() {
|
||||
super(MessagePayloadMessage.MESSAGE_TYPE);
|
||||
public MessagePayloadMessageHandler(I2PAppContext context) {
|
||||
super(context, MessagePayloadMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
@ -53,7 +54,7 @@ class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
*/
|
||||
private Payload decryptPayload(MessagePayloadMessage msg, I2PSessionImpl session) throws DataFormatException {
|
||||
Payload payload = msg.getPayload();
|
||||
byte[] data = ElGamalAESEngine.decrypt(payload.getEncryptedData(), session.getDecryptionKey());
|
||||
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
|
||||
if (data == null) {
|
||||
_log
|
||||
.error("Error decrypting the payload to public key "
|
||||
|
@ -12,6 +12,7 @@ package net.i2p.client;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP MessageStatusMessages from the router. This currently only takes
|
||||
@ -21,8 +22,8 @@ import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
|
||||
* @author jrandom
|
||||
*/
|
||||
class MessageStatusMessageHandler extends HandlerImpl {
|
||||
public MessageStatusMessageHandler() {
|
||||
super(MessageStatusMessage.MESSAGE_TYPE);
|
||||
public MessageStatusMessageHandler(I2PAppContext context) {
|
||||
super(context, MessageStatusMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
|
@ -25,6 +25,7 @@ import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.RequestLeaseSetMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases
|
||||
@ -35,8 +36,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
|
||||
private final static Log _log = new Log(RequestLeaseSetMessageHandler.class);
|
||||
private Map _existingLeaseSets;
|
||||
|
||||
public RequestLeaseSetMessageHandler() {
|
||||
super(RequestLeaseSetMessage.MESSAGE_TYPE);
|
||||
public RequestLeaseSetMessageHandler(I2PAppContext context) {
|
||||
super(context, RequestLeaseSetMessage.MESSAGE_TYPE);
|
||||
_existingLeaseSets = new HashMap(32);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ package net.i2p.client;
|
||||
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP SessionStatusMessagese from the router, updating the session as
|
||||
@ -19,8 +20,8 @@ import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
* @author jrandom
|
||||
*/
|
||||
class SessionStatusMessageHandler extends HandlerImpl {
|
||||
public SessionStatusMessageHandler() {
|
||||
super(SessionStatusMessage.MESSAGE_TYPE);
|
||||
public SessionStatusMessageHandler(I2PAppContext context) {
|
||||
super(context, SessionStatusMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
|
@ -12,6 +12,7 @@ package net.i2p.client;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handle I2CP time messages from the router
|
||||
@ -19,8 +20,8 @@ import net.i2p.util.Clock;
|
||||
* @author jrandom
|
||||
*/
|
||||
class SetDateMessageHandler extends HandlerImpl {
|
||||
public SetDateMessageHandler() {
|
||||
super(SetDateMessage.MESSAGE_TYPE);
|
||||
public SetDateMessageHandler(I2PAppContext ctx) {
|
||||
super(ctx, SetDateMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
|
@ -8,11 +8,21 @@
|
||||
package net.i2p.client.naming;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* A Dummy naming service that can only handle base64 destinations.
|
||||
*/
|
||||
class DummyNamingService extends NamingService {
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
protected DummyNamingService(I2PAppContext context) { super(context); }
|
||||
private DummyNamingService() { super(null); }
|
||||
|
||||
public Destination lookup(String hostname) {
|
||||
return lookupBase64(hostname);
|
||||
}
|
||||
|
@ -14,12 +14,22 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* A naming service based on the "hosts.txt" file.
|
||||
*/
|
||||
public class HostsTxtNamingService extends NamingService {
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public HostsTxtNamingService(I2PAppContext context) { super(context); }
|
||||
private HostsTxtNamingService() { super(null); }
|
||||
|
||||
/**
|
||||
* If this system property is specified, the tunnel will read the
|
||||
* given file for hostname=destKey values when resolving names
|
||||
@ -35,7 +45,7 @@ public class HostsTxtNamingService extends NamingService {
|
||||
// Try to look it up in hosts.txt
|
||||
// Reload file each time to catch changes.
|
||||
// (and it's easier :P
|
||||
String hostsfile = System.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
|
||||
String hostsfile = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
|
||||
Properties hosts = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
|
@ -10,6 +10,9 @@ package net.i2p.client.naming;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* Naming services create a subclass of this class.
|
||||
@ -17,10 +20,23 @@ import net.i2p.util.Log;
|
||||
public abstract class NamingService {
|
||||
|
||||
private final static Log _log = new Log(NamingService.class);
|
||||
protected I2PAppContext _context;
|
||||
|
||||
private static final String PROP_IMPL = "i2p.naming.impl";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
protected NamingService(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
private NamingService() {}
|
||||
|
||||
/**
|
||||
* Look up a host name.
|
||||
* @return the Destination for this host name, or
|
||||
@ -52,23 +68,22 @@ public abstract class NamingService {
|
||||
}
|
||||
}
|
||||
|
||||
private static NamingService instance = null;
|
||||
|
||||
/**
|
||||
* Get a naming service instance. This method ensures that there
|
||||
* will be only one naming service instance (singleton) as well as
|
||||
* choose the implementation from the "i2p.naming.impl" system
|
||||
* property.
|
||||
*/
|
||||
public static synchronized NamingService getInstance() {
|
||||
if (instance == null) {
|
||||
String impl = System.getProperty(PROP_IMPL, DEFAULT_IMPL);
|
||||
try {
|
||||
instance = (NamingService) Class.forName(impl).newInstance();
|
||||
} catch (Exception ex) {
|
||||
_log.error("Cannot loadNaming service implementation", ex);
|
||||
instance = new DummyNamingService(); // fallback
|
||||
}
|
||||
public static final synchronized NamingService createInstance(I2PAppContext context) {
|
||||
NamingService instance = null;
|
||||
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
|
||||
try {
|
||||
Class cls = Class.forName(impl);
|
||||
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
|
||||
instance = (NamingService)con.newInstance(new Object[] { context });
|
||||
} catch (Exception ex) {
|
||||
_log.error("Cannot loadNaming service implementation", ex);
|
||||
instance = new DummyNamingService(context); // fallback
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
@ -19,26 +19,22 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Wrapper singleton for AES cypher operation.
|
||||
* Dummy wrapper for AES cipher operation.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class AESEngine {
|
||||
private final static Log _log = new Log(AESEngine.class);
|
||||
private static AESEngine _engine;
|
||||
static {
|
||||
if ("off".equals(System.getProperty("i2p.encryption", "on")))
|
||||
_engine = new AESEngine();
|
||||
else
|
||||
_engine = new CryptixAESEngine();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
public AESEngine(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(AESEngine.class);
|
||||
if (getClass() == AESEngine.class)
|
||||
_log.warn("Warning: AES is disabled");
|
||||
}
|
||||
|
||||
public static AESEngine getInstance() {
|
||||
return _engine;
|
||||
}
|
||||
|
||||
|
||||
/** Encrypt the payload with the session key
|
||||
* @param payload data to be encrypted
|
||||
* @param sessionKey private esession key to encrypt to
|
||||
@ -50,7 +46,6 @@ public class AESEngine {
|
||||
|| (initializationVector.length != 16)) return null;
|
||||
|
||||
byte cyphertext[] = new byte[payload.length + (16 - (payload.length % 16))];
|
||||
_log.warn("Warning: AES is disabled");
|
||||
System.arraycopy(payload, 0, cyphertext, 0, payload.length);
|
||||
return cyphertext;
|
||||
}
|
||||
@ -59,13 +54,13 @@ public class AESEngine {
|
||||
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize + 64);
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
|
||||
Hash h = _context.sha().calculateHash(sessionKey.getData());
|
||||
try {
|
||||
h.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, payload.length);
|
||||
baos.write(payload);
|
||||
byte tv[] = baos.toByteArray();
|
||||
baos.write(ElGamalAESEngine.getPadding(tv.length, paddedSize));
|
||||
baos.write(ElGamalAESEngine.getPadding(_context, tv.length, paddedSize));
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing data", ioe);
|
||||
return null;
|
||||
@ -85,7 +80,7 @@ public class AESEngine {
|
||||
return null;
|
||||
}
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
|
||||
Hash h = _context.sha().calculateHash(sessionKey.getData());
|
||||
try {
|
||||
Hash rh = new Hash();
|
||||
rh.readBytes(bais);
|
||||
@ -127,20 +122,21 @@ public class AESEngine {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
byte iv[] = new byte[16];
|
||||
RandomSource.getInstance().nextBytes(iv);
|
||||
|
||||
byte sbuf[] = new byte[16];
|
||||
RandomSource.getInstance().nextBytes(sbuf);
|
||||
byte se[] = AESEngine.getInstance().encrypt(sbuf, key, iv);
|
||||
byte sd[] = AESEngine.getInstance().decrypt(se, key, iv);
|
||||
_log.debug("Short test: " + DataHelper.eq(sd, sbuf));
|
||||
byte se[] = ctx.AESEngine().encrypt(sbuf, key, iv);
|
||||
byte sd[] = ctx.AESEngine().decrypt(se, key, iv);
|
||||
ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
|
||||
|
||||
byte lbuf[] = new byte[1024];
|
||||
RandomSource.getInstance().nextBytes(sbuf);
|
||||
byte le[] = AESEngine.getInstance().safeEncrypt(lbuf, key, iv, 2048);
|
||||
byte ld[] = AESEngine.getInstance().safeDecrypt(le, key, iv);
|
||||
_log.debug("Long test: " + DataHelper.eq(ld, lbuf));
|
||||
byte le[] = ctx.AESEngine().safeEncrypt(lbuf, key, iv, 2048);
|
||||
byte ld[] = ctx.AESEngine().safeDecrypt(le, key, iv);
|
||||
ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* This reads an underlying stream as written by AESOutputStream - AES256 encrypted
|
||||
@ -36,8 +37,8 @@ import net.i2p.util.RandomSource;
|
||||
*
|
||||
*/
|
||||
public class AESInputStream extends FilterInputStream {
|
||||
private final static Log _log = new Log(AESInputStream.class);
|
||||
private final static CryptixAESEngine _engine = new CryptixAESEngine();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
private SessionKey _key;
|
||||
private byte[] _lastBlock;
|
||||
private boolean _eofFound;
|
||||
@ -52,8 +53,10 @@ public class AESInputStream extends FilterInputStream {
|
||||
private final static int READ_SIZE = BLOCK_SIZE;
|
||||
private final static int DECRYPT_SIZE = BLOCK_SIZE - 1;
|
||||
|
||||
public AESInputStream(InputStream source, SessionKey key, byte iv[]) {
|
||||
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte iv[]) {
|
||||
super(source);
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(AESInputStream.class);
|
||||
_key = key;
|
||||
_lastBlock = new byte[BLOCK_SIZE];
|
||||
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
|
||||
@ -223,8 +226,8 @@ public class AESInputStream extends FilterInputStream {
|
||||
byte block[] = new byte[BLOCK_SIZE];
|
||||
for (int i = 0; i < numBlocks; i++) {
|
||||
System.arraycopy(encrypted, i * BLOCK_SIZE, block, 0, BLOCK_SIZE);
|
||||
byte decrypted[] = _engine.decrypt(block, _key, _lastBlock);
|
||||
byte data[] = CryptixAESEngine.xor(decrypted, _lastBlock);
|
||||
byte decrypted[] = _context.AESEngine().decrypt(block, _key, _lastBlock);
|
||||
byte data[] = DataHelper.xor(decrypted, _lastBlock);
|
||||
int cleaned[] = stripPadding(data);
|
||||
for (int j = 0; j < cleaned.length; j++) {
|
||||
if (((int) cleaned[j]) <= 0) {
|
||||
@ -297,6 +300,8 @@ public class AESInputStream extends FilterInputStream {
|
||||
* Test AESOutputStream/AESInputStream
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
Log log = ctx.logManager().getLog(AESInputStream.class);
|
||||
byte orig[] = new byte[1024 * 32];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
//byte orig[] = "you are my sunshine, my only sunshine".getBytes();
|
||||
@ -304,40 +309,40 @@ public class AESInputStream extends FilterInputStream {
|
||||
byte iv[] = "there once was a".getBytes();
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
runTest(orig, key, iv);
|
||||
runTest(ctx, orig, key, iv);
|
||||
}
|
||||
|
||||
_log.info("Done testing 32KB data");
|
||||
log.info("Done testing 32KB data");
|
||||
|
||||
orig = new byte[20];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
runTest(orig, key, iv);
|
||||
runTest(ctx, orig, key, iv);
|
||||
}
|
||||
|
||||
_log.info("Done testing 20 byte data");
|
||||
log.info("Done testing 20 byte data");
|
||||
|
||||
orig = new byte[3];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
runTest(orig, key, iv);
|
||||
runTest(ctx, orig, key, iv);
|
||||
}
|
||||
|
||||
_log.info("Done testing 3 byte data");
|
||||
log.info("Done testing 3 byte data");
|
||||
|
||||
orig = new byte[0];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
runTest(orig, key, iv);
|
||||
runTest(ctx, orig, key, iv);
|
||||
}
|
||||
|
||||
_log.info("Done testing 0 byte data");
|
||||
log.info("Done testing 0 byte data");
|
||||
|
||||
orig = new byte[32];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
runOffsetTest(orig, key, iv);
|
||||
runOffsetTest(ctx, orig, key, iv);
|
||||
|
||||
_log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
|
||||
log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
|
||||
|
||||
try {
|
||||
Thread.sleep(30 * 1000);
|
||||
@ -345,11 +350,12 @@ public class AESInputStream extends FilterInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
private static void runTest(byte orig[], SessionKey key, byte[] iv) {
|
||||
private static void runTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
|
||||
Log log = ctx.logManager().getLog(AESInputStream.class);
|
||||
try {
|
||||
long start = Clock.getInstance().now();
|
||||
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
|
||||
AESOutputStream out = new AESOutputStream(origStream, key, iv);
|
||||
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
|
||||
out.write(orig);
|
||||
out.close();
|
||||
|
||||
@ -357,7 +363,7 @@ public class AESInputStream extends FilterInputStream {
|
||||
long endE = Clock.getInstance().now();
|
||||
|
||||
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
|
||||
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
|
||||
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
byte buf[] = new byte[1024 * 32];
|
||||
int read = DataHelper.read(in, buf);
|
||||
@ -370,65 +376,66 @@ public class AESInputStream extends FilterInputStream {
|
||||
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
|
||||
boolean eq = origHash.equals(newHash);
|
||||
if (eq)
|
||||
_log.info("Equal hashes. hash: " + origHash);
|
||||
log.info("Equal hashes. hash: " + origHash);
|
||||
else
|
||||
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
boolean ok = DataHelper.eq(orig, fin);
|
||||
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
|
||||
} catch (Throwable t) {
|
||||
_log.error("ERROR transferring", t);
|
||||
log.error("ERROR transferring", t);
|
||||
}
|
||||
//try { Thread.sleep(5000); } catch (Throwable t) {}
|
||||
}
|
||||
|
||||
private static void runOffsetTest(byte orig[], SessionKey key, byte[] iv) {
|
||||
private static void runOffsetTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
|
||||
Log log = ctx.logManager().getLog(AESInputStream.class);
|
||||
try {
|
||||
long start = Clock.getInstance().now();
|
||||
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
|
||||
AESOutputStream out = new AESOutputStream(origStream, key, iv);
|
||||
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
|
||||
out.write(orig);
|
||||
out.close();
|
||||
|
||||
byte encrypted[] = origStream.toByteArray();
|
||||
long endE = Clock.getInstance().now();
|
||||
|
||||
_log.info("Encrypted segment length: " + encrypted.length);
|
||||
log.info("Encrypted segment length: " + encrypted.length);
|
||||
byte encryptedSegment[] = new byte[40];
|
||||
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
|
||||
|
||||
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
|
||||
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
|
||||
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
byte buf[] = new byte[1024 * 32];
|
||||
int read = DataHelper.read(in, buf);
|
||||
int remaining = in.remainingBytes();
|
||||
int readyBytes = in.readyBytes();
|
||||
_log.info("Read: " + read);
|
||||
log.info("Read: " + read);
|
||||
if (read > 0) baos.write(buf, 0, read);
|
||||
in.close();
|
||||
byte fin[] = baos.toByteArray();
|
||||
_log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
|
||||
log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
|
||||
long end = Clock.getInstance().now();
|
||||
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
|
||||
|
||||
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
|
||||
boolean eq = origHash.equals(newHash);
|
||||
if (eq)
|
||||
_log.info("Equal hashes. hash: " + origHash);
|
||||
log.info("Equal hashes. hash: " + origHash);
|
||||
else
|
||||
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
boolean ok = DataHelper.eq(orig, fin);
|
||||
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
|
||||
} catch (Throwable t) {
|
||||
_log.error("ERROR transferring", t);
|
||||
log.error("ERROR transferring", t);
|
||||
}
|
||||
//try { Thread.sleep(5000); } catch (Throwable t) {}
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* This writes everything as CBC with PKCS#5 padding, but each block is padded
|
||||
@ -28,8 +30,8 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class AESOutputStream extends FilterOutputStream {
|
||||
private final static CryptixAESEngine _engine = new CryptixAESEngine();
|
||||
private final static Log _log = new Log(AESOutputStream.class);
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
private SessionKey _key;
|
||||
private byte[] _lastBlock;
|
||||
private ByteArrayOutputStream _inBuf;
|
||||
@ -42,8 +44,10 @@ public class AESOutputStream extends FilterOutputStream {
|
||||
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
|
||||
private final static int MAX_BUF = 256;
|
||||
|
||||
public AESOutputStream(OutputStream source, SessionKey key, byte[] iv) {
|
||||
public AESOutputStream(I2PAppContext context, OutputStream source, SessionKey key, byte[] iv) {
|
||||
super(source);
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(AESOutputStream.class);
|
||||
_key = key;
|
||||
_lastBlock = new byte[BLOCK_SIZE];
|
||||
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
|
||||
@ -104,8 +108,8 @@ public class AESOutputStream extends FilterOutputStream {
|
||||
block[BLOCK_SIZE - 1] = 0x01; // the padding byte for "full" blocks
|
||||
for (int i = 0; i < numBlocks; i++) {
|
||||
System.arraycopy(src, i * 15, block, 0, 15);
|
||||
byte data[] = _engine.xor(block, _lastBlock);
|
||||
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
|
||||
byte data[] = DataHelper.xor(block, _lastBlock);
|
||||
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
|
||||
_cumulativeWritten += encrypted.length;
|
||||
out.write(encrypted);
|
||||
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
|
||||
@ -118,8 +122,8 @@ public class AESOutputStream extends FilterOutputStream {
|
||||
int paddingBytes = BLOCK_SIZE - remainingBytes;
|
||||
System.arraycopy(src, numBlocks * 15, block, 0, remainingBytes);
|
||||
Arrays.fill(block, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
|
||||
byte data[] = _engine.xor(block, _lastBlock);
|
||||
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
|
||||
byte data[] = DataHelper.xor(block, _lastBlock);
|
||||
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
|
||||
out.write(encrypted);
|
||||
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
|
||||
_cumulativePadding += paddingBytes;
|
||||
|
@ -13,6 +13,7 @@ import java.security.InvalidKeyException;
|
||||
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Wrapper for AES cypher operation using Cryptix's Rijndael implementation. Implements
|
||||
@ -23,10 +24,15 @@ import net.i2p.util.Log;
|
||||
* @author jrandom, thecrypto
|
||||
*/
|
||||
public class CryptixAESEngine extends AESEngine {
|
||||
private final static Log _log = new Log(CryptixAESEngine.class);
|
||||
private Log _log;
|
||||
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
|
||||
private final static boolean USE_FAKE_CRYPTO = false;
|
||||
private final static byte FAKE_KEY = 0x2A;
|
||||
|
||||
public CryptixAESEngine(I2PAppContext context) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(CryptixAESEngine.class);
|
||||
}
|
||||
|
||||
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
|
||||
if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|
||||
@ -116,7 +122,7 @@ public class CryptixAESEngine extends AESEngine {
|
||||
* @param sessionKey private esession key to encrypt to
|
||||
* @return encrypted data
|
||||
*/
|
||||
final static byte[] encrypt(byte payload[], SessionKey sessionKey) {
|
||||
final byte[] encrypt(byte payload[], SessionKey sessionKey) {
|
||||
try {
|
||||
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
|
||||
byte rv[] = new byte[payload.length];
|
||||
@ -133,7 +139,7 @@ public class CryptixAESEngine extends AESEngine {
|
||||
* @param sessionKey private session key
|
||||
* @return unencrypted data
|
||||
*/
|
||||
final static byte[] decrypt(byte payload[], SessionKey sessionKey) {
|
||||
final byte[] decrypt(byte payload[], SessionKey sessionKey) {
|
||||
try {
|
||||
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
|
||||
byte rv[] = new byte[payload.length];
|
||||
|
@ -20,6 +20,7 @@ import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Generate a new session key through a diffie hellman exchange. This uses the
|
||||
@ -62,22 +63,23 @@ public class DHSessionKeyBuilder {
|
||||
public final static String DEFAULT_DH_PRECALC_DELAY = "1000";
|
||||
|
||||
static {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
|
||||
MIN_NUM_BUILDERS = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MIN);
|
||||
MIN_NUM_BUILDERS = val;
|
||||
}
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
|
||||
MAX_NUM_BUILDERS = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MAX);
|
||||
MAX_NUM_BUILDERS = val;
|
||||
}
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
|
||||
CALC_DELAY = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_DH_PRECALC_DELAY);
|
||||
@ -266,6 +268,7 @@ public class DHSessionKeyBuilder {
|
||||
Thread.sleep(20 * 1000);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
_log.debug("\n\n\n\nBegin test\n");
|
||||
long negTime = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
@ -289,8 +292,8 @@ public class DHSessionKeyBuilder {
|
||||
byte iv[] = new byte[16];
|
||||
RandomSource.getInstance().nextBytes(iv);
|
||||
String origVal = "1234567890123456"; // 16 bytes max using AESEngine
|
||||
byte enc[] = AESEngine.getInstance().encrypt(origVal.getBytes(), key1, iv);
|
||||
byte dec[] = AESEngine.getInstance().decrypt(enc, key2, iv);
|
||||
byte enc[] = ctx.AESEngine().encrypt(origVal.getBytes(), key1, iv);
|
||||
byte dec[] = ctx.AESEngine().decrypt(enc, key2, iv);
|
||||
String tranVal = new String(dec);
|
||||
if (origVal.equals(tranVal))
|
||||
_log.debug("**Success: D(E(val)) == val");
|
||||
|
@ -39,17 +39,22 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
public class DSAEngine {
|
||||
private final static Log _log = new Log(DSAEngine.class);
|
||||
private static DSAEngine _instance = new DSAEngine();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public static DSAEngine getInstance() {
|
||||
return _instance;
|
||||
public DSAEngine(I2PAppContext context) {
|
||||
_log = context.logManager().getLog(DSAEngine.class);
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public static DSAEngine getInstance() {
|
||||
return I2PAppContext.getGlobalContext().dsa();
|
||||
}
|
||||
|
||||
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
|
||||
long start = Clock.getInstance().now();
|
||||
long start = _context.clock().now();
|
||||
|
||||
byte[] sigbytes = signature.getData();
|
||||
byte rbytes[] = new byte[20];
|
||||
@ -65,22 +70,20 @@ public class DSAEngine {
|
||||
BigInteger r = new NativeBigInteger(1, rbytes);
|
||||
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
|
||||
BigInteger w = s.modInverse(CryptoConstants.dsaq);
|
||||
BigInteger u1 = ((new NativeBigInteger(1, calculateHash(signedData).getData())).multiply(w))
|
||||
.mod(CryptoConstants.dsaq);
|
||||
byte data[] = calculateHash(signedData).getData();
|
||||
NativeBigInteger bi = new NativeBigInteger(1, data);
|
||||
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
|
||||
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
|
||||
BigInteger v = ((CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap))
|
||||
.multiply(y.modPow(u2,
|
||||
CryptoConstants.dsap)))
|
||||
.mod(
|
||||
CryptoConstants.dsap)
|
||||
.mod(
|
||||
CryptoConstants.dsaq);
|
||||
BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
|
||||
BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
|
||||
BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
|
||||
|
||||
boolean ok = v.compareTo(r) == 0;
|
||||
|
||||
long diff = Clock.getInstance().now() - start;
|
||||
long diff = _context.clock().now() - start;
|
||||
if (diff > 1000) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to verify the signature (" + diff + "ms)");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Took too long to verify the signature (" + diff + "ms)");
|
||||
}
|
||||
|
||||
return ok;
|
||||
@ -88,13 +91,13 @@ public class DSAEngine {
|
||||
|
||||
public Signature sign(byte data[], SigningPrivateKey signingKey) {
|
||||
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
|
||||
long start = Clock.getInstance().now();
|
||||
long start = _context.clock().now();
|
||||
|
||||
Signature sig = new Signature();
|
||||
BigInteger k;
|
||||
|
||||
do {
|
||||
k = new BigInteger(160, RandomSource.getInstance());
|
||||
k = new BigInteger(160, _context.random());
|
||||
} while (k.compareTo(CryptoConstants.dsaq) != 1);
|
||||
|
||||
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
|
||||
@ -139,7 +142,7 @@ public class DSAEngine {
|
||||
}
|
||||
sig.setData(out);
|
||||
|
||||
long diff = Clock.getInstance().now() - start;
|
||||
long diff = _context.clock().now() - start;
|
||||
if (diff > 1000) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to sign (" + diff + "ms)");
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Fake ElG E and D, useful for when performance isn't being tested
|
||||
@ -24,13 +25,22 @@ import net.i2p.util.Log;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DummyElGamalEngine extends ElGamalEngine {
|
||||
private final static Log _log = new Log(DummyElGamalEngine.class);
|
||||
private Log _log;
|
||||
|
||||
public DummyElGamalEngine() {
|
||||
/**
|
||||
* The ElGamal engine should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public DummyElGamalEngine(I2PAppContext context) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(DummyElGamalEngine.class);
|
||||
_log.log(Log.CRIT, "Dummy ElGamal engine in use! NO DATA SECURITY. Danger Will Robinson, Danger!",
|
||||
new Exception("I really hope you know what you're doing"));
|
||||
}
|
||||
|
||||
private DummyElGamalEngine() { super(null); }
|
||||
|
||||
/** encrypt the data to the public key
|
||||
* @return encrypted data
|
||||
* @param publicKey public key encrypt to
|
||||
@ -84,11 +94,13 @@ public class DummyElGamalEngine extends ElGamalEngine {
|
||||
}
|
||||
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
|
||||
if (calcHash.equals(hash)) {
|
||||
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
|
||||
return rv;
|
||||
} else {
|
||||
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv),
|
||||
new Exception("Doesn't match"));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv),
|
||||
new Exception("Doesn't match"));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Handles the actual ElGamal+AES encryption and decryption scenarios using the
|
||||
@ -37,28 +38,27 @@ import net.i2p.util.RandomSource;
|
||||
public class ElGamalAESEngine {
|
||||
private final static Log _log = new Log(ElGamalAESEngine.class);
|
||||
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
|
||||
private I2PAppContext _context;
|
||||
|
||||
static {
|
||||
StatManager.getInstance()
|
||||
.createFrequencyStat("crypto.elGamalAES.encryptNewSession",
|
||||
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
StatManager.getInstance()
|
||||
.createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
|
||||
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
StatManager.getInstance()
|
||||
.createFrequencyStat("crypto.elGamalAES.decryptNewSession",
|
||||
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
StatManager.getInstance()
|
||||
.createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
|
||||
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
StatManager.getInstance()
|
||||
.createFrequencyStat("crypto.elGamalAES.decryptFail",
|
||||
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption",
|
||||
new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
private ElGamalAESEngine() {}
|
||||
public ElGamalAESEngine(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
|
||||
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptNewSession",
|
||||
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l});
|
||||
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
|
||||
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptNewSession",
|
||||
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
|
||||
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?",
|
||||
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptFail",
|
||||
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption",
|
||||
new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +66,7 @@ public class ElGamalAESEngine {
|
||||
* ElGamal+AES algorithm in the data structure spec.
|
||||
*
|
||||
*/
|
||||
public static byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
|
||||
public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
|
||||
if (data == null) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Null data being decrypted?");
|
||||
return null;
|
||||
@ -79,7 +79,7 @@ public class ElGamalAESEngine {
|
||||
byte tag[] = new byte[32];
|
||||
System.arraycopy(data, 0, tag, 0, tag.length);
|
||||
SessionTag st = new SessionTag(tag);
|
||||
SessionKey key = SessionKeyManager.getInstance().consumeTag(st);
|
||||
SessionKey key = _context.sessionKeyManager().consumeTag(st);
|
||||
SessionKey foundKey = new SessionKey();
|
||||
foundKey.setData(null);
|
||||
SessionKey usedKey = new SessionKey();
|
||||
@ -90,16 +90,16 @@ public class ElGamalAESEngine {
|
||||
usedKey.setData(key.getData());
|
||||
decrypted = decryptExistingSession(data, key, targetPrivateKey, foundTags, usedKey, foundKey);
|
||||
if (decrypted != null)
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptExistingSession");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.decryptExistingSession");
|
||||
else
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is NOT known for tag " + st);
|
||||
decrypted = decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
|
||||
if (decrypted != null)
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptNewSession");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.decryptNewSession");
|
||||
else
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
|
||||
}
|
||||
|
||||
if ((key == null) && (decrypted == null)) {
|
||||
@ -109,10 +109,10 @@ public class ElGamalAESEngine {
|
||||
if (foundTags.size() > 0) {
|
||||
if (foundKey.getData() != null) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Found key: " + foundKey);
|
||||
SessionKeyManager.getInstance().tagsReceived(foundKey, foundTags);
|
||||
_context.sessionKeyManager().tagsReceived(foundKey, foundTags);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Used key: " + usedKey);
|
||||
SessionKeyManager.getInstance().tagsReceived(usedKey, foundTags);
|
||||
_context.sessionKeyManager().tagsReceived(usedKey, foundTags);
|
||||
}
|
||||
}
|
||||
return decrypted;
|
||||
@ -132,7 +132,7 @@ public class ElGamalAESEngine {
|
||||
*
|
||||
* @return null if decryption fails
|
||||
*/
|
||||
static byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
|
||||
byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
|
||||
SessionKey foundKey) throws DataFormatException {
|
||||
if (data == null) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session");
|
||||
@ -147,7 +147,7 @@ public class ElGamalAESEngine {
|
||||
} else {
|
||||
System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length);
|
||||
}
|
||||
byte elgDecr[] = ElGamalEngine.getInstance().decrypt(elgEncr, targetPrivateKey);
|
||||
byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey);
|
||||
if (elgDecr == null) return null;
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr);
|
||||
@ -170,7 +170,7 @@ public class ElGamalAESEngine {
|
||||
|
||||
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
|
||||
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
|
||||
@ -200,13 +200,13 @@ public class ElGamalAESEngine {
|
||||
* @param foundKey session key which may be filled with a new sessionKey found during decryption
|
||||
*
|
||||
*/
|
||||
static byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
|
||||
byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
|
||||
SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
|
||||
byte preIV[] = new byte[32];
|
||||
System.arraycopy(data, 0, preIV, 0, preIV.length);
|
||||
byte encr[] = new byte[data.length - 32];
|
||||
System.arraycopy(data, 32, encr, 0, encr.length);
|
||||
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
|
||||
@ -246,12 +246,12 @@ public class ElGamalAESEngine {
|
||||
* @param foundTags set which is filled with any sessionTags found during decryption
|
||||
* @param foundKey session key which may be filled with a new sessionKey found during decryption
|
||||
*/
|
||||
static byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags,
|
||||
byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags,
|
||||
SessionKey foundKey) throws DataFormatException {
|
||||
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));
|
||||
//_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
|
||||
byte decrypted[] = AESEngine.getInstance().decrypt(encrypted, key, iv);
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(decrypted);
|
||||
byte decrypted[] = _context.AESEngine().decrypt(encrypted, key, iv);
|
||||
Hash h = _context.sha().calculateHash(decrypted);
|
||||
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
|
||||
try {
|
||||
SessionKey newKey = null;
|
||||
@ -289,7 +289,7 @@ public class ElGamalAESEngine {
|
||||
byte unencrData[] = new byte[(int) len];
|
||||
read = bais.read(unencrData);
|
||||
if (read != unencrData.length) throw new Exception("Invalid size of the data read");
|
||||
Hash calcHash = SHA256Generator.getInstance().calculateHash(unencrData);
|
||||
Hash calcHash = _context.sha().calculateHash(unencrData);
|
||||
if (calcHash.equals(readHash)) {
|
||||
// everything matches. w00t.
|
||||
foundTags.addAll(tags);
|
||||
@ -317,17 +317,17 @@ public class ElGamalAESEngine {
|
||||
* @param paddedSize minimum size in bytes of the body after padding it (if less than the
|
||||
* body's real size, no bytes are appended but the body is not truncated)
|
||||
*/
|
||||
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
SessionTag currentTag, SessionKey newKey, long paddedSize) {
|
||||
if (currentTag == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Current tag is null, encrypting as new session", new Exception("encrypt new"));
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptNewSession");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.encryptNewSession");
|
||||
return encryptNewSession(data, target, key, tagsForDelivery, newKey, paddedSize);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Current tag is NOT null, encrypting as existing session", new Exception("encrypt existing"));
|
||||
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptExistingSession");
|
||||
_context.statManager().updateFrequency("crypto.elGamalAES.encryptExistingSession");
|
||||
return encryptExistingSession(data, target, key, tagsForDelivery, currentTag, newKey, paddedSize);
|
||||
}
|
||||
}
|
||||
@ -335,7 +335,7 @@ public class ElGamalAESEngine {
|
||||
/**
|
||||
* Encrypt the data to the target using the given key and deliver the specified tags
|
||||
*/
|
||||
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
SessionTag currentTag, long paddedSize) {
|
||||
return encrypt(data, target, key, tagsForDelivery, currentTag, null, paddedSize);
|
||||
}
|
||||
@ -343,14 +343,14 @@ public class ElGamalAESEngine {
|
||||
/**
|
||||
* Encrypt the data to the target using the given key and deliver the specified tags
|
||||
*/
|
||||
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
|
||||
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
|
||||
return encrypt(data, target, key, tagsForDelivery, null, null, paddedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the data to the target using the given key delivering no tags
|
||||
*/
|
||||
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
|
||||
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
|
||||
return encrypt(data, target, key, null, null, null, paddedSize);
|
||||
}
|
||||
|
||||
@ -370,25 +370,25 @@ public class ElGamalAESEngine {
|
||||
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
|
||||
*
|
||||
*/
|
||||
static byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
SessionKey newKey, long paddedSize) {
|
||||
//_log.debug("Encrypting to a NEW session");
|
||||
try {
|
||||
ByteArrayOutputStream elgSrc = new ByteArrayOutputStream(64);
|
||||
key.writeBytes(elgSrc);
|
||||
byte preIV[] = new byte[32];
|
||||
RandomSource.getInstance().nextBytes(preIV);
|
||||
_context.random().nextBytes(preIV);
|
||||
elgSrc.write(preIV);
|
||||
byte rnd[] = new byte[158];
|
||||
RandomSource.getInstance().nextBytes(rnd);
|
||||
_context.random().nextBytes(rnd);
|
||||
elgSrc.write(rnd);
|
||||
elgSrc.flush();
|
||||
|
||||
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
|
||||
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
long before = Clock.getInstance().now();
|
||||
byte elgEncr[] = ElGamalEngine.getInstance().encrypt(elgSrc.toByteArray(), target);
|
||||
long after = Clock.getInstance().now();
|
||||
long before = _context.clock().now();
|
||||
byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrc.toByteArray(), target);
|
||||
long after = _context.clock().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms");
|
||||
if (elgEncr.length < 514) {
|
||||
@ -400,7 +400,7 @@ public class ElGamalAESEngine {
|
||||
}
|
||||
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
|
||||
|
||||
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
|
||||
@ -410,7 +410,7 @@ public class ElGamalAESEngine {
|
||||
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
|
||||
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
|
||||
//_log.debug("Return length: " + rv.length);
|
||||
long finish = Clock.getInstance().now();
|
||||
long finish = _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
|
||||
return rv;
|
||||
@ -436,14 +436,14 @@ public class ElGamalAESEngine {
|
||||
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
|
||||
*
|
||||
*/
|
||||
static byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
|
||||
SessionTag currentTag, SessionKey newKey, long paddedSize) {
|
||||
//_log.debug("Encrypting to an EXISTING session");
|
||||
byte rawTag[] = currentTag.getData();
|
||||
|
||||
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
|
||||
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
Hash ivHash = SHA256Generator.getInstance().calculateHash(rawTag);
|
||||
Hash ivHash = _context.sha().calculateHash(rawTag);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
|
||||
@ -469,7 +469,7 @@ public class ElGamalAESEngine {
|
||||
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
|
||||
*
|
||||
*/
|
||||
final static byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
|
||||
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
|
||||
long paddedSize) {
|
||||
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
|
||||
//_log.debug("Encrypting AES");
|
||||
@ -484,7 +484,7 @@ public class ElGamalAESEngine {
|
||||
//_log.debug("# tags created, registered, and written: " + tags.size());
|
||||
DataHelper.writeLong(aesSrc, 4, data.length);
|
||||
//_log.debug("data length: " + data.length);
|
||||
Hash hash = SHA256Generator.getInstance().calculateHash(data);
|
||||
Hash hash = _context.sha().calculateHash(data);
|
||||
hash.writeBytes(aesSrc);
|
||||
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
|
||||
if (newKey == null) {
|
||||
@ -499,14 +499,14 @@ public class ElGamalAESEngine {
|
||||
aesSrc.write(data);
|
||||
int len = aesSrc.toByteArray().length;
|
||||
//_log.debug("raw data written: " + len);
|
||||
byte padding[] = getPadding(len, paddedSize);
|
||||
byte padding[] = getPadding(_context, len, paddedSize);
|
||||
//_log.debug("padding length: " + padding.length);
|
||||
aesSrc.write(padding);
|
||||
|
||||
byte aesUnencr[] = aesSrc.toByteArray();
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(aesUnencr);
|
||||
Hash h = _context.sha().calculateHash(aesUnencr);
|
||||
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
|
||||
byte aesEncr[] = AESEngine.getInstance().encrypt(aesUnencr, key, iv);
|
||||
byte aesEncr[] = _context.AESEngine().encrypt(aesUnencr, key, iv);
|
||||
//_log.debug("Encrypted length: " + aesEncr.length);
|
||||
return aesEncr;
|
||||
} catch (IOException ioe) {
|
||||
@ -523,7 +523,7 @@ public class ElGamalAESEngine {
|
||||
* at least minPaddedSize
|
||||
*
|
||||
*/
|
||||
final static byte[] getPadding(int curSize, long minPaddedSize) {
|
||||
final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) {
|
||||
int diff = 0;
|
||||
if (curSize < minPaddedSize) {
|
||||
diff = (int) minPaddedSize - curSize;
|
||||
@ -532,7 +532,7 @@ public class ElGamalAESEngine {
|
||||
int numPadding = diff;
|
||||
if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16));
|
||||
byte rv[] = new byte[numPadding];
|
||||
RandomSource.getInstance().nextBytes(rv);
|
||||
context.random().nextBytes(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Wrapper for ElGamal encryption/signature schemes.
|
||||
@ -56,25 +57,28 @@ import net.i2p.util.RandomSource;
|
||||
*/
|
||||
|
||||
public class ElGamalEngine {
|
||||
private final static Log _log = new Log(ElGamalEngine.class);
|
||||
private static ElGamalEngine _engine;
|
||||
static {
|
||||
if ("off".equals(System.getProperty("i2p.encryption", "on")))
|
||||
_engine = new DummyElGamalEngine();
|
||||
else
|
||||
_engine = new ElGamalEngine();
|
||||
|
||||
StatManager.getInstance().createRateStat("crypto.elGamal.encrypt",
|
||||
"how long does it take to do a full ElGamal encryption", "Encryption",
|
||||
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
|
||||
StatManager.getInstance().createRateStat("crypto.elGamal.decrypt",
|
||||
"how long does it take to do a full ElGamal decryption", "Encryption",
|
||||
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
|
||||
/**
|
||||
* The ElGamal engine should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public ElGamalEngine(I2PAppContext context) {
|
||||
context.statManager().createRateStat("crypto.elGamal.encrypt",
|
||||
"how long does it take to do a full ElGamal encryption", "Encryption",
|
||||
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
|
||||
context.statManager().createRateStat("crypto.elGamal.decrypt",
|
||||
"how long does it take to do a full ElGamal decryption", "Encryption",
|
||||
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(ElGamalEngine.class);
|
||||
}
|
||||
private ElGamalEngine() {}
|
||||
|
||||
public static ElGamalEngine getInstance() {
|
||||
return _engine;
|
||||
}
|
||||
|
||||
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02});
|
||||
|
||||
private BigInteger[] getNextYK() {
|
||||
@ -91,12 +95,12 @@ public class ElGamalEngine {
|
||||
throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
|
||||
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
|
||||
|
||||
long start = Clock.getInstance().now();
|
||||
long start = _context.clock().now();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
|
||||
try {
|
||||
baos.write(0xFF);
|
||||
Hash hash = SHA256Generator.getInstance().calculateHash(data);
|
||||
Hash hash = _context.sha().calculateHash(data);
|
||||
hash.writeBytes(baos);
|
||||
baos.write(data);
|
||||
baos.flush();
|
||||
@ -106,25 +110,25 @@ public class ElGamalEngine {
|
||||
}
|
||||
|
||||
byte d2[] = baos.toByteArray();
|
||||
long t0 = Clock.getInstance().now();
|
||||
long t0 = _context.clock().now();
|
||||
BigInteger m = new NativeBigInteger(1, d2);
|
||||
long t1 = Clock.getInstance().now();
|
||||
long t1 = _context.clock().now();
|
||||
if (m.compareTo(CryptoConstants.elgp) >= 0)
|
||||
throw new IllegalArgumentException("ARGH. Data cannot be larger than the ElGamal prime. FIXME");
|
||||
long t2 = Clock.getInstance().now();
|
||||
long t2 = _context.clock().now();
|
||||
BigInteger aalpha = new NativeBigInteger(1, publicKey.getData());
|
||||
long t3 = Clock.getInstance().now();
|
||||
long t3 = _context.clock().now();
|
||||
BigInteger yk[] = getNextYK();
|
||||
BigInteger k = yk[1];
|
||||
BigInteger y = yk[0];
|
||||
|
||||
long t7 = Clock.getInstance().now();
|
||||
long t7 = _context.clock().now();
|
||||
BigInteger d = aalpha.modPow(k, CryptoConstants.elgp);
|
||||
long t8 = Clock.getInstance().now();
|
||||
long t8 = _context.clock().now();
|
||||
d = d.multiply(m);
|
||||
long t9 = Clock.getInstance().now();
|
||||
long t9 = _context.clock().now();
|
||||
d = d.mod(CryptoConstants.elgp);
|
||||
long t10 = Clock.getInstance().now();
|
||||
long t10 = _context.clock().now();
|
||||
|
||||
byte[] ybytes = y.toByteArray();
|
||||
byte[] dbytes = d.toByteArray();
|
||||
@ -146,14 +150,14 @@ public class ElGamalEngine {
|
||||
buf.append("8-9: ").append(t9 - t8).append('\n');
|
||||
buf.append("9-10: ").append(t10 - t9).append('\n');
|
||||
//_log.debug(buf.toString());
|
||||
long end = Clock.getInstance().now();
|
||||
long end = _context.clock().now();
|
||||
|
||||
long diff = end - start;
|
||||
if (diff > 1000) {
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to encrypt ElGamal block (" + diff + "ms)");
|
||||
}
|
||||
|
||||
StatManager.getInstance().addRateData("crypto.elGamal.encrypt", diff, diff);
|
||||
_context.statManager().addRateData("crypto.elGamal.encrypt", diff, diff);
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -165,7 +169,7 @@ public class ElGamalEngine {
|
||||
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
|
||||
if ((encrypted == null) || (encrypted.length > 514))
|
||||
throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
|
||||
long start = Clock.getInstance().now();
|
||||
long start = _context.clock().now();
|
||||
|
||||
byte[] ybytes = new byte[257];
|
||||
byte[] dbytes = new byte[257];
|
||||
@ -196,10 +200,10 @@ public class ElGamalEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
|
||||
Hash calcHash = _context.sha().calculateHash(rv);
|
||||
boolean ok = calcHash.equals(hash);
|
||||
|
||||
long end = Clock.getInstance().now();
|
||||
long end = _context.clock().now();
|
||||
|
||||
long diff = end - start;
|
||||
if (diff > 1000) {
|
||||
@ -207,7 +211,7 @@ public class ElGamalEngine {
|
||||
_log.warn("Took too long to decrypt and verify ElGamal block (" + diff + "ms)");
|
||||
}
|
||||
|
||||
StatManager.getInstance().addRateData("crypto.elGamal.decrypt", diff, diff);
|
||||
_context.statManager().addRateData("crypto.elGamal.decrypt", diff, diff);
|
||||
|
||||
if (ok) {
|
||||
//_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
|
||||
@ -236,6 +240,7 @@ public class ElGamalEngine {
|
||||
}
|
||||
|
||||
RandomSource.getInstance().nextBoolean();
|
||||
I2PAppContext context = new I2PAppContext();
|
||||
|
||||
System.out.println("Running " + numRuns + " times");
|
||||
|
||||
@ -249,9 +254,9 @@ public class ElGamalEngine {
|
||||
byte buf[] = new byte[128];
|
||||
RandomSource.getInstance().nextBytes(buf);
|
||||
long startE = Clock.getInstance().now();
|
||||
byte encr[] = ElGamalEngine.getInstance().encrypt(buf, pubkey);
|
||||
byte encr[] = context.elGamalEngine().encrypt(buf, pubkey);
|
||||
long endE = Clock.getInstance().now();
|
||||
byte decr[] = ElGamalEngine.getInstance().decrypt(encr, privkey);
|
||||
byte decr[] = context.elGamalEngine().decrypt(encr, privkey);
|
||||
long endD = Clock.getInstance().now();
|
||||
eTime += endE - startE;
|
||||
dTime += endD - endE;
|
||||
@ -259,8 +264,7 @@ public class ElGamalEngine {
|
||||
|
||||
if (!DataHelper.eq(decr, buf)) {
|
||||
System.out.println("PublicKey : " + DataHelper.toString(pubkey.getData(), pubkey.getData().length));
|
||||
System.out.println("PrivateKey : "
|
||||
+ DataHelper.toString(privkey.getData(), privkey.getData().length));
|
||||
System.out.println("PrivateKey : " + DataHelper.toString(privkey.getData(), privkey.getData().length));
|
||||
System.out.println("orig : " + DataHelper.toString(buf, buf.length));
|
||||
System.out.println("d(e(orig) : " + DataHelper.toString(decr, decr.length));
|
||||
System.out.println("orig.len : " + buf.length);
|
||||
|
@ -3,28 +3,23 @@ package net.i2p.crypto;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Calculate the HMAC-SHA256 of a key+message. Currently FAKE - returns a stupid
|
||||
* kludgy hash: H(H(key) XOR H(data)). Fix me!
|
||||
*
|
||||
*/
|
||||
public abstract class HMACSHA256Generator {
|
||||
private static HMACSHA256Generator _generator = new DummyHMACSHA256Generator();
|
||||
|
||||
public class HMACSHA256Generator {
|
||||
public HMACSHA256Generator(I2PAppContext context) {};
|
||||
public static HMACSHA256Generator getInstance() {
|
||||
return _generator;
|
||||
return I2PAppContext.getGlobalContext().hmac();
|
||||
}
|
||||
|
||||
public abstract Hash calculate(SessionKey key, byte data[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* jrandom smells.
|
||||
*
|
||||
*/
|
||||
|
||||
class DummyHMACSHA256Generator extends HMACSHA256Generator {
|
||||
|
||||
/**
|
||||
* This should calculate the HMAC/SHA256, but it DOESNT. Its just a kludge.
|
||||
* Fix me.
|
||||
*/
|
||||
public Hash calculate(SessionKey key, byte data[]) {
|
||||
if ((key == null) || (key.getData() == null) || (data == null))
|
||||
throw new NullPointerException("Null arguments for HMAC");
|
||||
|
@ -22,18 +22,24 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/** Define a way of generating asymetrical key pairs as well as symetrical keys
|
||||
* @author jrandom
|
||||
*/
|
||||
public class KeyGenerator {
|
||||
private final static Log _log = new Log(KeyGenerator.class);
|
||||
private static final RandomSource _random = RandomSource.getInstance();
|
||||
private static KeyGenerator _generator = new KeyGenerator();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public static KeyGenerator getInstance() {
|
||||
return _generator;
|
||||
public KeyGenerator(I2PAppContext context) {
|
||||
_log = context.logManager().getLog(KeyGenerator.class);
|
||||
_context = context;
|
||||
}
|
||||
public static KeyGenerator getInstance() {
|
||||
return I2PAppContext.getGlobalContext().keyGenerator();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Generate a private 256 bit session key
|
||||
* @return session key
|
||||
@ -42,7 +48,7 @@ public class KeyGenerator {
|
||||
// 256bit random # as a session key
|
||||
SessionKey key = new SessionKey();
|
||||
byte data[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
_random.nextBytes(data);
|
||||
_context.random().nextBytes(data);
|
||||
key.setData(data);
|
||||
return key;
|
||||
}
|
||||
@ -52,7 +58,7 @@ public class KeyGenerator {
|
||||
* @return pair of keys
|
||||
*/
|
||||
public Object[] generatePKIKeypair() {
|
||||
BigInteger a = new NativeBigInteger(2048, _random);
|
||||
BigInteger a = new NativeBigInteger(2048, _context.random());
|
||||
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
|
||||
|
||||
Object[] keys = new Object[2];
|
||||
@ -80,7 +86,7 @@ public class KeyGenerator {
|
||||
|
||||
// make sure the random key is less than the DSA q
|
||||
do {
|
||||
x = new NativeBigInteger(160, _random);
|
||||
x = new NativeBigInteger(160, _context.random());
|
||||
} while (x.compareTo(CryptoConstants.dsaq) >= 0);
|
||||
|
||||
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
|
||||
@ -118,13 +124,14 @@ public class KeyGenerator {
|
||||
byte src[] = new byte[200];
|
||||
RandomSource.getInstance().nextBytes(src);
|
||||
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
long time = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
long start = Clock.getInstance().now();
|
||||
Object keys[] = KeyGenerator.getInstance().generatePKIKeypair();
|
||||
long end = Clock.getInstance().now();
|
||||
byte ctext[] = ElGamalEngine.getInstance().encrypt(src, (PublicKey) keys[0]);
|
||||
byte ptext[] = ElGamalEngine.getInstance().decrypt(ctext, (PrivateKey) keys[1]);
|
||||
byte ctext[] = ctx.elGamalEngine().encrypt(src, (PublicKey) keys[0]);
|
||||
byte ptext[] = ctx.elGamalEngine().decrypt(ctext, (PrivateKey) keys[1]);
|
||||
time += end - start;
|
||||
if (DataHelper.eq(ptext, src))
|
||||
log.debug("D(E(data)) == data");
|
||||
|
@ -27,6 +27,7 @@ import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Expose the functionality to allow people to write out and read in the
|
||||
@ -35,10 +36,23 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class PersistentSessionKeyManager extends TransientSessionKeyManager {
|
||||
private final static Log _log = new Log(PersistentSessionKeyManager.class);
|
||||
|
||||
private Log _log;
|
||||
private Object _yk = YKGenerator.class;
|
||||
|
||||
|
||||
/**
|
||||
* The session key manager should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public PersistentSessionKeyManager(I2PAppContext context) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(PersistentSessionKeyManager.class);
|
||||
}
|
||||
private PersistentSessionKeyManager() {
|
||||
this(null);
|
||||
}
|
||||
/**
|
||||
* Write the session key data to the given stream
|
||||
*
|
||||
@ -46,8 +60,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
|
||||
public void saveState(OutputStream out) throws IOException, DataFormatException {
|
||||
Set tagSets = getInboundTagSets();
|
||||
Set sessions = getOutboundSessions();
|
||||
_log.info("Saving state with " + tagSets.size() + " inbound tagSets and " + sessions.size()
|
||||
+ " outbound sessions");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Saving state with " + tagSets.size() + " inbound tagSets and "
|
||||
+ sessions.size() + " outbound sessions");
|
||||
|
||||
DataHelper.writeLong(out, 4, tagSets.size());
|
||||
for (Iterator iter = tagSets.iterator(); iter.hasNext();) {
|
||||
@ -79,8 +94,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
|
||||
sessions.add(sess);
|
||||
}
|
||||
|
||||
_log.info("Loading state with " + tagSets.size() + " inbound tagSets and " + sessions.size()
|
||||
+ " outbound sessions");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Loading state with " + tagSets.size() + " inbound tagSets and "
|
||||
+ sessions.size() + " outbound sessions");
|
||||
setData(tagSets, sessions);
|
||||
}
|
||||
|
||||
@ -146,7 +162,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
PersistentSessionKeyManager mgr = new PersistentSessionKeyManager();
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
Log log = ctx.logManager().getLog(PersistentSessionKeyManager.class);
|
||||
PersistentSessionKeyManager mgr = (PersistentSessionKeyManager)ctx.sessionKeyManager();
|
||||
try {
|
||||
mgr.loadState(new FileInputStream("sessionKeys.dat"));
|
||||
String state = mgr.renderStatusHTML();
|
||||
@ -154,13 +172,13 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
|
||||
fos.write(state.getBytes());
|
||||
fos.close();
|
||||
int expired = mgr.aggressiveExpire();
|
||||
_log.error("Expired: " + expired);
|
||||
log.error("Expired: " + expired);
|
||||
String stateAfter = mgr.renderStatusHTML();
|
||||
FileOutputStream fos2 = new FileOutputStream("sessionKeysAfterExpire.html");
|
||||
fos2.write(stateAfter.getBytes());
|
||||
fos2.close();
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error loading/storing sessionKeys", t);
|
||||
log.error("Error loading/storing sessionKeys", t);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
|
@ -30,6 +30,7 @@ package net.i2p.crypto;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/** Defines a wrapper for SHA-256 operation
|
||||
*
|
||||
@ -38,10 +39,9 @@ import net.i2p.data.Hash;
|
||||
* @author thecrypto,jrandom
|
||||
*/
|
||||
public class SHA256Generator {
|
||||
private static SHA256Generator _generator = new SHA256Generator();
|
||||
|
||||
public SHA256Generator(I2PAppContext context) {};
|
||||
public static SHA256Generator getInstance() {
|
||||
return _generator;
|
||||
return I2PAppContext.getGlobalContext().sha();
|
||||
}
|
||||
|
||||
static int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
|
@ -14,6 +14,7 @@ import java.util.Set;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Manage the session keys and session tags used for encryption and decryption.
|
||||
@ -23,12 +24,11 @@ import net.i2p.data.SessionTag;
|
||||
*
|
||||
*/
|
||||
public class SessionKeyManager {
|
||||
private final static SessionKeyManager _instance = new PersistentSessionKeyManager(); // new TransientSessionKeyManager(); // SessionKeyManager();
|
||||
|
||||
public final static SessionKeyManager getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/** session key managers must be created through an app context */
|
||||
protected SessionKeyManager(I2PAppContext context) {}
|
||||
/** see above */
|
||||
private SessionKeyManager() {}
|
||||
|
||||
/**
|
||||
* Retrieve the session key currently associated with encryption to the target,
|
||||
* or null if a new session key should be generated.
|
||||
|
@ -18,6 +18,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
@ -32,7 +33,7 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
class TransientSessionKeyManager extends SessionKeyManager {
|
||||
private final static Log _log = new Log(TransientSessionKeyManager.class);
|
||||
private Log _log;
|
||||
private Map _outboundSessions; // PublicKey --> OutboundSession
|
||||
private Map _inboundTagSets; // SessionTag --> TagSet
|
||||
|
||||
@ -52,11 +53,19 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
public final static long SESSION_LIFETIME_MAX_MS = SESSION_TAG_DURATION_MS + 5 * 60 * 1000;
|
||||
public final static int MAX_INBOUND_SESSION_TAGS = 100 * 1000; // this will consume at most 3.2M
|
||||
|
||||
public TransientSessionKeyManager() {
|
||||
super();
|
||||
/**
|
||||
* The session key manager should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public TransientSessionKeyManager(I2PAppContext context) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(TransientSessionKeyManager.class);
|
||||
_outboundSessions = new HashMap(64);
|
||||
_inboundTagSets = new HashMap(1024);
|
||||
}
|
||||
private TransientSessionKeyManager() { this(null); }
|
||||
|
||||
/** TagSet */
|
||||
protected Set getInboundTagSets() {
|
||||
@ -73,8 +82,9 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
}
|
||||
|
||||
protected void setData(Set inboundTagSets, Set outboundSessions) {
|
||||
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and " + outboundSessions.size()
|
||||
+ " outbound sessions");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and "
|
||||
+ outboundSessions.size() + " outbound sessions");
|
||||
Map tagSets = new HashMap(inboundTagSets.size());
|
||||
for (Iterator iter = inboundTagSets.iterator(); iter.hasNext();) {
|
||||
TagSet ts = (TagSet) iter.next();
|
||||
@ -108,8 +118,10 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
if (sess == null) return null;
|
||||
long now = Clock.getInstance().now();
|
||||
if (sess.getEstablishedDate() < now - SESSION_LIFETIME_MAX_MS) {
|
||||
_log.info("Expiring old session key established on " + new Date(sess.getEstablishedDate())
|
||||
+ " with target " + target);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Expiring old session key established on "
|
||||
+ new Date(sess.getEstablishedDate())
|
||||
+ " with target " + target);
|
||||
return null;
|
||||
} else {
|
||||
return sess.getCurrentKey();
|
||||
@ -137,15 +149,18 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
|
||||
OutboundSession sess = getSession(target);
|
||||
if (sess == null) {
|
||||
_log.debug("No session for " + target);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No session for " + target);
|
||||
return null;
|
||||
}
|
||||
if (sess.getCurrentKey().equals(key)) {
|
||||
SessionTag nxt = sess.consumeNext();
|
||||
_log.debug("Tag consumed: " + nxt);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tag consumed: " + nxt);
|
||||
return nxt;
|
||||
} else {
|
||||
_log.debug("Key does not match existing key, no tag");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Key does not match existing key, no tag");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -194,7 +209,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
sess.setCurrentKey(key);
|
||||
TagSet set = new TagSet(sessionTags, key);
|
||||
sess.addTags(set);
|
||||
_log.debug("Tags delivered to set " + set + " on session " + sess);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tags delivered to set " + set + " on session " + sess);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,7 +231,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
TagSet tagSet = new TagSet(sessionTags, key);
|
||||
for (Iterator iter = sessionTags.iterator(); iter.hasNext();) {
|
||||
SessionTag tag = (SessionTag) iter.next();
|
||||
_log.debug("Receiving tag " + tag + " for key " + key);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receiving tag " + tag + " for key " + key);
|
||||
synchronized (_inboundTagSets) {
|
||||
_inboundTagSets.put(tag, tagSet);
|
||||
}
|
||||
@ -224,7 +241,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
// todo: make this limit the tags by sessionKey and actually enforce the limit!
|
||||
int overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS;
|
||||
if (overage > 0) {
|
||||
_log.error("TOO MANY SESSION TAGS! " + (_inboundTagSets.size()));
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("TOO MANY SESSION TAGS! " + (_inboundTagSets.size()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,13 +260,15 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
synchronized (_inboundTagSets) {
|
||||
TagSet tagSet = (TagSet) _inboundTagSets.remove(tag);
|
||||
if (tagSet == null) {
|
||||
_log.debug("Cannot consume tag " + tag + " as it is not known");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cannot consume tag " + tag + " as it is not known");
|
||||
return null;
|
||||
} else {
|
||||
tagSet.consume(tag);
|
||||
}
|
||||
SessionKey key = tagSet.getAssociatedKey();
|
||||
_log.debug("Consuming tag " + tag + " for sessionKey " + key);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Consuming tag " + tag + " for sessionKey " + key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@ -362,7 +382,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
static class OutboundSession {
|
||||
class OutboundSession {
|
||||
private PublicKey _target;
|
||||
private SessionKey _currentKey;
|
||||
private long _established;
|
||||
@ -406,7 +426,9 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
TagSet set = (TagSet) sets.get(i);
|
||||
dropped += set.getTags().size();
|
||||
}
|
||||
_log.info("Rekeyed from " + _currentKey + " to " + key + ": dropping " + dropped + " session tags");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Rekeyed from " + _currentKey + " to " + key
|
||||
+ ": dropping " + dropped + " session tags");
|
||||
}
|
||||
}
|
||||
_currentKey = key;
|
||||
@ -448,7 +470,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
SessionTag tag = set.consumeNext();
|
||||
if (tag != null) return tag;
|
||||
} else {
|
||||
_log.info("TagSet from " + new Date(set.getDate()) + " expired");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("TagSet from " + new Date(set.getDate()) + " expired");
|
||||
}
|
||||
_tagSets.remove(0);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Precalculate the Y and K for ElGamal encryption operations.
|
||||
@ -56,22 +57,23 @@ class YKGenerator {
|
||||
private final static long CHECK_DELAY = 30 * 1000;
|
||||
|
||||
static {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN));
|
||||
MIN_NUM_BUILDERS = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MIN);
|
||||
MIN_NUM_BUILDERS = val;
|
||||
}
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX));
|
||||
MAX_NUM_BUILDERS = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MAX);
|
||||
MAX_NUM_BUILDERS = val;
|
||||
}
|
||||
try {
|
||||
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY));
|
||||
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY));
|
||||
CALC_DELAY = val;
|
||||
} catch (Throwable t) {
|
||||
int val = Integer.parseInt(DEFAULT_YK_PRECALC_DELAY);
|
||||
|
@ -35,7 +35,6 @@ import net.i2p.util.OrderedProperties;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DataHelper {
|
||||
private final static Log _log = new Log(DataHelper.class);
|
||||
private final static String _equal = "="; // in UTF-8
|
||||
private final static String _semicolon = ";"; // in UTF-8
|
||||
|
||||
@ -56,7 +55,8 @@ public class DataHelper {
|
||||
* @throws IOException if there is a problem reading the data
|
||||
* @return mapping
|
||||
*/
|
||||
public static Properties readProperties(InputStream rawStream) throws DataFormatException, IOException {
|
||||
public static Properties readProperties(InputStream rawStream)
|
||||
throws DataFormatException, IOException {
|
||||
Properties props = new OrderedProperties();
|
||||
long size = readLong(rawStream, 2);
|
||||
byte data[] = new byte[(int) size];
|
||||
@ -65,24 +65,18 @@ public class DataHelper {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
byte eqBuf[] = _equal.getBytes();
|
||||
byte semiBuf[] = _semicolon.getBytes();
|
||||
try {
|
||||
while (in.available() > 0) {
|
||||
String key = readString(in);
|
||||
read = read(in, eqBuf);
|
||||
if ((read != eqBuf.length) || (!eq(new String(eqBuf), _equal))) {
|
||||
_log.debug("Failed eqtest [" + new String(eqBuf) + "]");
|
||||
break;
|
||||
}
|
||||
String val = readString(in);
|
||||
read = read(in, semiBuf);
|
||||
if ((read != semiBuf.length) || (!eq(new String(semiBuf), _semicolon))) {
|
||||
_log.debug("Failed semitest [" + new String(semiBuf) + "]");
|
||||
break;
|
||||
}
|
||||
props.put(key, val);
|
||||
while (in.available() > 0) {
|
||||
String key = readString(in);
|
||||
read = read(in, eqBuf);
|
||||
if ((read != eqBuf.length) || (!eq(new String(eqBuf), _equal))) {
|
||||
break;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error reading properties", ioe);
|
||||
String val = readString(in);
|
||||
read = read(in, semiBuf);
|
||||
if ((read != semiBuf.length) || (!eq(new String(semiBuf), _semicolon))) {
|
||||
break;
|
||||
}
|
||||
props.put(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
@ -96,8 +90,8 @@ public class DataHelper {
|
||||
* @throws DataFormatException if there is not enough valid data to write out
|
||||
* @throws IOException if there is an IO error writing out the data
|
||||
*/
|
||||
public static void writeProperties(OutputStream rawStream, Properties props) throws DataFormatException,
|
||||
IOException {
|
||||
public static void writeProperties(OutputStream rawStream, Properties props)
|
||||
throws DataFormatException, IOException {
|
||||
OrderedProperties p = new OrderedProperties();
|
||||
if (props != null) p.putAll(props);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32);
|
||||
@ -204,10 +198,10 @@ public class DataHelper {
|
||||
* @throws IOException if there is an IO error reading the number
|
||||
* @return number
|
||||
*/
|
||||
public static long readLong(InputStream rawStream, int numBytes) throws DataFormatException, IOException {
|
||||
public static long readLong(InputStream rawStream, int numBytes)
|
||||
throws DataFormatException, IOException {
|
||||
if (numBytes > 8)
|
||||
throw new DataFormatException(
|
||||
"readLong doesn't currently support reading numbers > 8 bytes [as thats bigger than java's long]");
|
||||
throw new DataFormatException("readLong doesn't currently support reading numbers > 8 bytes [as thats bigger than java's long]");
|
||||
byte data[] = new byte[numBytes];
|
||||
int num = read(rawStream, data);
|
||||
if (num != numBytes)
|
||||
@ -225,10 +219,14 @@ public class DataHelper {
|
||||
* @throws DataFormatException if the stream doesn't contain a validly formatted number of that many bytes
|
||||
* @throws IOException if there is an IO error writing to the stream
|
||||
*/
|
||||
public static void writeLong(OutputStream rawStream, int numBytes, long value) throws DataFormatException,
|
||||
IOException {
|
||||
UnsignedInteger i = new UnsignedInteger(value);
|
||||
rawStream.write(i.getBytes(numBytes));
|
||||
public static void writeLong(OutputStream rawStream, int numBytes, long value)
|
||||
throws DataFormatException, IOException {
|
||||
try {
|
||||
UnsignedInteger i = new UnsignedInteger(value);
|
||||
rawStream.write(i.getBytes(numBytes));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new DataFormatException("Invalid value (must be positive)", iae);
|
||||
}
|
||||
}
|
||||
|
||||
/** Read in a date from the stream as specified by the I2P data structure spec.
|
||||
@ -254,7 +252,8 @@ public class DataHelper {
|
||||
* @throws DataFormatException if the date is not valid
|
||||
* @throws IOException if there is an IO error writing the date
|
||||
*/
|
||||
public static void writeDate(OutputStream out, Date date) throws DataFormatException, IOException {
|
||||
public static void writeDate(OutputStream out, Date date)
|
||||
throws DataFormatException, IOException {
|
||||
if (date == null)
|
||||
writeLong(out, 8, 0L);
|
||||
else
|
||||
@ -286,7 +285,8 @@ public class DataHelper {
|
||||
* @throws DataFormatException if the string is not valid
|
||||
* @throws IOException if there is an IO error writing the string
|
||||
*/
|
||||
public static void writeString(OutputStream out, String string) throws DataFormatException, IOException {
|
||||
public static void writeString(OutputStream out, String string)
|
||||
throws DataFormatException, IOException {
|
||||
if (string == null) {
|
||||
writeLong(out, 1, 0);
|
||||
} else {
|
||||
@ -328,7 +328,8 @@ public class DataHelper {
|
||||
* @throws DataFormatException if the boolean is not valid
|
||||
* @throws IOException if there is an IO error writing the boolean
|
||||
*/
|
||||
public static void writeBoolean(OutputStream out, Boolean bool) throws DataFormatException, IOException {
|
||||
public static void writeBoolean(OutputStream out, Boolean bool)
|
||||
throws DataFormatException, IOException {
|
||||
if (bool == null)
|
||||
writeLong(out, 1, 2);
|
||||
else if (Boolean.TRUE.equals(bool))
|
||||
@ -353,7 +354,6 @@ public class DataHelper {
|
||||
boolean eq = (((lhs == null) && (rhs == null)) || ((lhs != null) && (lhs.equals(rhs))));
|
||||
return eq;
|
||||
} catch (ClassCastException cce) {
|
||||
_log.warn("Error comparing [" + lhs + "] with [" + rhs + "]", cce);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -542,12 +542,12 @@ public class DataHelper {
|
||||
out.finish();
|
||||
out.flush();
|
||||
byte rv[] = baos.toByteArray();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Compression of " + orig.length + " into " + rv.length + " (or " + 100.0d
|
||||
* (((double) orig.length) / ((double) rv.length)) + "% savings)");
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Compression of " + orig.length + " into " + rv.length + " (or " + 100.0d
|
||||
// * (((double) orig.length) / ((double) rv.length)) + "% savings)");
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error compressing?!", ioe);
|
||||
//_log.error("Error compressing?!", ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -565,12 +565,12 @@ public class DataHelper {
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
byte rv[] = baos.toByteArray();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d
|
||||
* (((double) rv.length) / ((double) orig.length)) + "% savings)");
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d
|
||||
// * (((double) rv.length) / ((double) orig.length)) + "% savings)");
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error decompressing?", ioe);
|
||||
//_log.error("Error decompressing?", ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ public class Hash extends DataStructureImpl {
|
||||
private final static Log _log = new Log(Hash.class);
|
||||
private byte[] _data;
|
||||
private volatile String _stringified;
|
||||
private volatile String _base64ed;
|
||||
|
||||
public final static int HASH_LENGTH = 32;
|
||||
public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]);
|
||||
@ -44,11 +45,13 @@ public class Hash extends DataStructureImpl {
|
||||
public void setData(byte[] data) {
|
||||
_data = data;
|
||||
_stringified = null;
|
||||
_base64ed = null;
|
||||
}
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_data = new byte[HASH_LENGTH];
|
||||
_stringified = null;
|
||||
_base64ed = null;
|
||||
int read = read(in, _data);
|
||||
if (read != HASH_LENGTH) throw new DataFormatException("Not enough bytes to read the hash");
|
||||
}
|
||||
@ -82,4 +85,11 @@ public class Hash extends DataStructureImpl {
|
||||
}
|
||||
return _stringified;
|
||||
}
|
||||
|
||||
public String toBase64() {
|
||||
if (_base64ed == null) {
|
||||
_base64ed = super.toBase64();
|
||||
}
|
||||
return _base64ed;
|
||||
}
|
||||
}
|
@ -46,6 +46,8 @@ public class RouterInfo extends DataStructureImpl {
|
||||
private volatile boolean _isValid;
|
||||
private volatile String _stringified;
|
||||
private volatile byte _byteified[];
|
||||
private volatile int _hashCode;
|
||||
private volatile boolean _hashCodeInitialized;
|
||||
|
||||
public RouterInfo() {
|
||||
setIdentity(null);
|
||||
@ -59,6 +61,7 @@ public class RouterInfo extends DataStructureImpl {
|
||||
_currentRoutingKey = null;
|
||||
_stringified = null;
|
||||
_byteified = null;
|
||||
_hashCodeInitialized = false;
|
||||
}
|
||||
|
||||
public RouterInfo(RouterInfo old) {
|
||||
@ -74,6 +77,7 @@ public class RouterInfo extends DataStructureImpl {
|
||||
private void resetCache() {
|
||||
_stringified = null;
|
||||
_byteified = null;
|
||||
_hashCodeInitialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,6 +115,7 @@ public class RouterInfo extends DataStructureImpl {
|
||||
*/
|
||||
public void setPublished(long published) {
|
||||
_published = published;
|
||||
resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,16 +226,20 @@ public class RouterInfo extends DataStructureImpl {
|
||||
//_log.debug("verify ok? " + DSAEngine.getInstance().verifySignature(sig, bytes, getIdentity().getSigningPublicKey()));
|
||||
//_log.debug("Signed data: \n" + Base64.encode(bytes));
|
||||
//_log.debug("Signature: " + getSignature());
|
||||
|
||||
resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the raw payload of the routerInfo, excluding the signature. This
|
||||
* caches the data in memory if possible.
|
||||
*
|
||||
* @throws DataFormatException if the data is somehow b0rked (missing props, etc)
|
||||
*/
|
||||
private byte[] getBytes() throws DataFormatException {
|
||||
if (_byteified != null) return _byteified;
|
||||
if (_identity == null) throw new IllegalStateException("Router identity isn't set? wtf!");
|
||||
if (_addresses == null) throw new IllegalStateException("Router addressess isn't set? wtf!");
|
||||
if (_peers == null) throw new IllegalStateException("Router peers isn't set? wtf!");
|
||||
if (_options == null) throw new IllegalStateException("Router options isn't set? wtf!");
|
||||
if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!");
|
||||
if (_addresses == null) throw new DataFormatException("Router addressess isn't set? wtf!");
|
||||
if (_peers == null) throw new DataFormatException("Router peers isn't set? wtf!");
|
||||
if (_options == null) throw new DataFormatException("Router options isn't set? wtf!");
|
||||
|
||||
long before = Clock.getInstance().now();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@ -339,10 +348,14 @@ public class RouterInfo extends DataStructureImpl {
|
||||
}
|
||||
_isValid = DSAEngine.getInstance().verifySignature(_signature, data, _identity.getSigningPublicKey());
|
||||
if (!_isValid) {
|
||||
_log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
|
||||
+ "] w/ signing key: " + _identity.getSigningPublicKey(), new Exception("Signature failed"));
|
||||
_log.debug("Failed data: \n" + Base64.encode(data));
|
||||
_log.debug("Signature: " + getSignature());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
|
||||
+ "] w/ signing key: " + _identity.getSigningPublicKey(),
|
||||
new Exception("Signature failed"));
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Failed data: \n" + Base64.encode(data));
|
||||
_log.debug("Signature: " + getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,19 +391,7 @@ public class RouterInfo extends DataStructureImpl {
|
||||
//if (!isValid())
|
||||
// throw new DataFormatException("Data is not valid");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
_identity.writeBytes(baos);
|
||||
DataHelper.writeDate(baos, new Date(_published));
|
||||
DataHelper.writeLong(baos, 1, _addresses.size());
|
||||
for (Iterator iter = _addresses.iterator(); iter.hasNext();) {
|
||||
RouterAddress addr = (RouterAddress) iter.next();
|
||||
addr.writeBytes(baos);
|
||||
}
|
||||
DataHelper.writeLong(baos, 1, _peers.size());
|
||||
for (Iterator iter = _peers.iterator(); iter.hasNext();) {
|
||||
Hash peerHash = (Hash) iter.next();
|
||||
peerHash.writeBytes(baos);
|
||||
}
|
||||
DataHelper.writeProperties(baos, _options);
|
||||
baos.write(getBytes());
|
||||
_signature.writeBytes(baos);
|
||||
|
||||
byte data[] = baos.toByteArray();
|
||||
@ -401,16 +402,22 @@ public class RouterInfo extends DataStructureImpl {
|
||||
public boolean equals(Object object) {
|
||||
if ((object == null) || !(object instanceof RouterInfo)) return false;
|
||||
RouterInfo info = (RouterInfo) object;
|
||||
return DataHelper.eq(getAddresses(), info.getAddresses()) && DataHelper.eq(getIdentity(), info.getIdentity())
|
||||
&& DataHelper.eq(getOptions(), info.getOptions()) && DataHelper.eq(getPeers(), info.getPeers())
|
||||
&& DataHelper.eq(getSignature(), info.getSignature())
|
||||
return DataHelper.eq(_addresses, info.getAddresses())
|
||||
&& DataHelper.eq(_identity, info.getIdentity())
|
||||
&& DataHelper.eq(_options, info.getOptions())
|
||||
&& DataHelper.eq(_peers, info.getPeers())
|
||||
&& DataHelper.eq(_signature, info.getSignature())
|
||||
&& DataHelper.eq(getPublished(), info.getPublished());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getAddresses()) + DataHelper.hashCode(getIdentity())
|
||||
+ DataHelper.hashCode(getOptions()) + DataHelper.hashCode(getPeers())
|
||||
+ DataHelper.hashCode(getSignature()) + (int) getPublished();
|
||||
if (!_hashCodeInitialized) {
|
||||
_hashCode = DataHelper.hashCode(_addresses) + DataHelper.hashCode(_identity)
|
||||
+ DataHelper.hashCode(_options) + DataHelper.hashCode(_peers)
|
||||
+ DataHelper.hashCode(_signature) + (int) getPublished();
|
||||
_hashCodeInitialized = true;
|
||||
}
|
||||
return _hashCode;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@ -420,17 +427,19 @@ public class RouterInfo extends DataStructureImpl {
|
||||
buf.append("\n\tIdentity: ").append(getIdentity());
|
||||
buf.append("\n\tSignature: ").append(getSignature());
|
||||
buf.append("\n\tPublished on: ").append(new Date(getPublished()));
|
||||
buf.append("\n\tAddresses: #: ").append(getAddresses().size());
|
||||
for (Iterator iter = getAddresses().iterator(); iter.hasNext();) {
|
||||
Set addresses = _addresses; // getAddresses()
|
||||
buf.append("\n\tAddresses: #: ").append(addresses.size());
|
||||
for (Iterator iter = addresses.iterator(); iter.hasNext();) {
|
||||
RouterAddress addr = (RouterAddress) iter.next();
|
||||
buf.append("\n\t\tAddress: ").append(addr);
|
||||
}
|
||||
buf.append("\n\tPeers: #: ").append(getPeers().size());
|
||||
for (Iterator iter = getPeers().iterator(); iter.hasNext();) {
|
||||
Set peers = _peers; // getPeers()
|
||||
buf.append("\n\tPeers: #: ").append(peers.size());
|
||||
for (Iterator iter = peers.iterator(); iter.hasNext();) {
|
||||
Hash hash = (Hash) iter.next();
|
||||
buf.append("\n\t\tPeer hash: ").append(hash);
|
||||
}
|
||||
Properties options = getOptions();
|
||||
Properties options = _options; // getOptions();
|
||||
buf.append("\n\tOptions: #: ").append(options.size());
|
||||
for (Iterator iter = options.keySet().iterator(); iter.hasNext();) {
|
||||
String key = (String) iter.next();
|
||||
|
@ -19,6 +19,7 @@ import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Component to manage the munging of hashes into routing keys - given a hash,
|
||||
@ -40,12 +41,17 @@ import net.i2p.util.RandomSource;
|
||||
*
|
||||
*/
|
||||
public class RoutingKeyGenerator {
|
||||
private final static RoutingKeyGenerator _instance = new RoutingKeyGenerator();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public static RoutingKeyGenerator getInstance() {
|
||||
return _instance;
|
||||
public RoutingKeyGenerator(I2PAppContext context) {
|
||||
_log = context.logManager().getLog(RoutingKeyGenerator.class);
|
||||
_context = context;
|
||||
}
|
||||
private final static Log _log = new Log(RoutingKeyGenerator.class);
|
||||
public static RoutingKeyGenerator getInstance() {
|
||||
return I2PAppContext.getGlobalContext().routingKeyGenerator();
|
||||
}
|
||||
|
||||
private byte _currentModData[];
|
||||
|
||||
private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
@ -67,7 +73,7 @@ public class RoutingKeyGenerator {
|
||||
public void generateDateBasedModData() {
|
||||
Date today = null;
|
||||
synchronized (_cal) {
|
||||
_cal.setTime(new Date(Clock.getInstance().now()));
|
||||
_cal.setTime(new Date(_context.clock().now()));
|
||||
_cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
_cal.set(Calendar.MINUTE, 0);
|
||||
_cal.set(Calendar.SECOND, 0);
|
||||
|
@ -57,8 +57,9 @@ public class UnsignedInteger {
|
||||
* available immediately.
|
||||
*
|
||||
* @param value number to represent
|
||||
* @throws IllegalArgumentException if the value is negative
|
||||
*/
|
||||
public UnsignedInteger(long value) {
|
||||
public UnsignedInteger(long value) throws IllegalArgumentException {
|
||||
_value = value;
|
||||
_data = calculateBytes(value);
|
||||
}
|
||||
@ -86,9 +87,42 @@ public class UnsignedInteger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the bytes as an unsigned integer with the most significant bit and byte in the first position
|
||||
* Calculate the bytes as an unsigned integer with the most significant
|
||||
* bit and byte in the first position. The return value always has at least
|
||||
* one byte in it.
|
||||
*
|
||||
* @throws IllegalArgumentException if the value is negative
|
||||
*/
|
||||
private static byte[] calculateBytes(long value) {
|
||||
private static byte[] calculateBytes(long value) throws IllegalArgumentException {
|
||||
if (value < 0)
|
||||
throw new IllegalArgumentException("unsigned integer, and you want a negative? " + value);
|
||||
byte val[] = new byte[8];
|
||||
val[0] = (byte)(value >>> 56);
|
||||
val[1] = (byte)(value >>> 48);
|
||||
val[2] = (byte)(value >>> 40);
|
||||
val[3] = (byte)(value >>> 32);
|
||||
val[4] = (byte)(value >>> 24);
|
||||
val[5] = (byte)(value >>> 16);
|
||||
val[6] = (byte)(value >>> 8);
|
||||
val[7] = (byte)value;
|
||||
|
||||
int firstNonZero = -1;
|
||||
for (int i = 0; i < val.length; i++) {
|
||||
if (val[i] != 0x00) {
|
||||
firstNonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstNonZero == 0)
|
||||
return val;
|
||||
if (firstNonZero == -1)
|
||||
return new byte[1]; // initialized as 0
|
||||
|
||||
byte rv[] = new byte[8-firstNonZero];
|
||||
System.arraycopy(val, firstNonZero, rv, 0, rv.length);
|
||||
return rv;
|
||||
/*
|
||||
BigInteger bi = new BigInteger("" + value);
|
||||
byte buf[] = bi.toByteArray();
|
||||
if ((buf == null) || (buf.length <= 0))
|
||||
@ -99,6 +133,7 @@ public class UnsignedInteger {
|
||||
byte rv[] = new byte[buf.length - trim];
|
||||
System.arraycopy(buf, trim, rv, 0, rv.length);
|
||||
return rv;
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,6 +155,8 @@ public class UnsignedInteger {
|
||||
throw new IllegalArgumentException("Value (" + _value + ") is greater than the requested number of bytes ("
|
||||
+ numBytes + ")");
|
||||
|
||||
if (numBytes == _data.length) return _data;
|
||||
|
||||
byte[] data = new byte[numBytes];
|
||||
System.arraycopy(_data, 0, data, numBytes - _data.length, _data.length);
|
||||
return data;
|
||||
@ -189,5 +226,9 @@ public class UnsignedInteger {
|
||||
_log.debug(num + " turned into a byte array and back again: " + val2 + " (" + val2.getLong() + "/"
|
||||
+ toString(val2.getBytes()) + ")");
|
||||
_log.debug(num + " As an 8 byte array: " + toString(val2.getBytes(8)));
|
||||
BigInteger bi = new BigInteger(num+"");
|
||||
_log.debug(num + " As a bigInteger: 0x" + bi.toString(16));
|
||||
BigInteger tbi = new BigInteger(1, calculateBytes(num));
|
||||
_log.debug(num + " As a shifted : 0x" + tbi.toString(16));
|
||||
}
|
||||
}
|
@ -30,6 +30,8 @@ public class I2CPMessageReader {
|
||||
private I2CPMessageEventListener _listener;
|
||||
private I2CPMessageReaderRunner _reader;
|
||||
private Thread _readerThread;
|
||||
|
||||
private static volatile long __readerId = 0;
|
||||
|
||||
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
|
||||
_stream = stream;
|
||||
@ -37,7 +39,7 @@ public class I2CPMessageReader {
|
||||
_reader = new I2CPMessageReaderRunner();
|
||||
_readerThread = new I2PThread(_reader);
|
||||
_readerThread.setDaemon(true);
|
||||
_readerThread.setName("I2CP Reader");
|
||||
_readerThread.setName("I2CP Reader " + (++__readerId));
|
||||
}
|
||||
|
||||
public void setListener(I2CPMessageEventListener lsnr) {
|
||||
|
@ -30,7 +30,7 @@ class PersistenceHelper {
|
||||
_log.error("Error formatting " + val + " into a long", nfe);
|
||||
}
|
||||
} else {
|
||||
_log.error("Key " + prefix + name + " does not exist");
|
||||
_log.warn("Key " + prefix + name + " does not exist");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -6,24 +6,25 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
public class SimpleStatDumper {
|
||||
private final static Log _log = new Log(SimpleStatDumper.class);
|
||||
|
||||
public static void dumpStats(int logLevel) {
|
||||
public static void dumpStats(I2PAppContext context, int logLevel) {
|
||||
if (!_log.shouldLog(logLevel)) return;
|
||||
|
||||
StringBuffer buf = new StringBuffer(4 * 1024);
|
||||
dumpFrequencies(buf);
|
||||
dumpRates(buf);
|
||||
dumpFrequencies(context, buf);
|
||||
dumpRates(context, buf);
|
||||
_log.log(logLevel, buf.toString());
|
||||
}
|
||||
|
||||
private static void dumpFrequencies(StringBuffer buf) {
|
||||
Set frequencies = new TreeSet(StatManager.getInstance().getFrequencyNames());
|
||||
private static void dumpFrequencies(I2PAppContext ctx, StringBuffer buf) {
|
||||
Set frequencies = new TreeSet(ctx.statManager().getFrequencyNames());
|
||||
for (Iterator iter = frequencies.iterator(); iter.hasNext();) {
|
||||
String name = (String) iter.next();
|
||||
FrequencyStat freq = StatManager.getInstance().getFrequency(name);
|
||||
FrequencyStat freq = ctx.statManager().getFrequency(name);
|
||||
buf.append('\n');
|
||||
buf.append(freq.getGroupName()).append('.').append(freq.getName()).append(": ")
|
||||
.append(freq.getDescription()).append('\n');
|
||||
@ -39,11 +40,11 @@ public class SimpleStatDumper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpRates(StringBuffer buf) {
|
||||
Set rates = new TreeSet(StatManager.getInstance().getRateNames());
|
||||
private static void dumpRates(I2PAppContext ctx, StringBuffer buf) {
|
||||
Set rates = new TreeSet(ctx.statManager().getRateNames());
|
||||
for (Iterator iter = rates.iterator(); iter.hasNext();) {
|
||||
String name = (String) iter.next();
|
||||
RateStat rate = StatManager.getInstance().getRate(name);
|
||||
RateStat rate = ctx.statManager().getRate(name);
|
||||
buf.append('\n');
|
||||
buf.append(rate.getGroupName()).append('.').append(rate.getName()).append(": ")
|
||||
.append(rate.getDescription()).append('\n');
|
||||
|
@ -10,6 +10,7 @@ import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Coordinate the management of various frequencies and rates within I2P components,
|
||||
@ -19,18 +20,23 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
public class StatManager {
|
||||
private final static Log _log = new Log(StatManager.class);
|
||||
private final static StatManager _instance = new StatManager();
|
||||
private Log _log;
|
||||
private I2PAppContext _context;
|
||||
|
||||
public final static StatManager getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
/** stat name to FrequencyStat */
|
||||
private Map _frequencyStats;
|
||||
/** stat name to RateStat */
|
||||
private Map _rateStats;
|
||||
|
||||
private StatManager() {
|
||||
/**
|
||||
* The stat manager should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public StatManager(I2PAppContext context) {
|
||||
_log = context.logManager().getLog(StatManager.class);
|
||||
_context = context;
|
||||
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = Collections.synchronizedMap(new HashMap(128));
|
||||
}
|
||||
@ -44,6 +50,7 @@ public class StatManager {
|
||||
* @param periods array of period lengths (in milliseconds)
|
||||
*/
|
||||
public void createFrequencyStat(String name, String description, String group, long periods[]) {
|
||||
if (_frequencyStats.containsKey(name)) return;
|
||||
_frequencyStats.put(name, new FrequencyStat(name, description, group, periods));
|
||||
}
|
||||
|
||||
@ -56,6 +63,7 @@ public class StatManager {
|
||||
* @param periods array of period lengths (in milliseconds)
|
||||
*/
|
||||
public void createRateStat(String name, String description, String group, long periods[]) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
_rateStats.put(name, new RateStat(name, description, group, periods));
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Alternate location for determining the time which takes into account an offset.
|
||||
* This offset will ideally be periodically updated so as to serve as the difference
|
||||
@ -12,12 +14,20 @@ import java.util.Set;
|
||||
*
|
||||
*/
|
||||
public class Clock {
|
||||
private final static Log _log = new Log(Clock.class);
|
||||
private final static Clock _instance = new Clock();
|
||||
|
||||
public final static Clock getInstance() {
|
||||
return _instance;
|
||||
private I2PAppContext _context;
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
_offset = 0;
|
||||
_alreadyChanged = false;
|
||||
_listeners = new HashSet(64);
|
||||
}
|
||||
public static Clock getInstance() {
|
||||
return I2PAppContext.getGlobalContext().clock();
|
||||
}
|
||||
|
||||
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||
private Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
|
||||
private volatile long _offset;
|
||||
private boolean _alreadyChanged;
|
||||
private Set _listeners;
|
||||
@ -25,13 +35,7 @@ public class Clock {
|
||||
/** if the clock is skewed by 3+ days, fuck 'em */
|
||||
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
|
||||
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
|
||||
public final static long MIN_OFFSET_CHANGE = 30 * 1000;
|
||||
|
||||
private Clock() {
|
||||
_offset = 0;
|
||||
_alreadyChanged = false;
|
||||
_listeners = new HashSet(64);
|
||||
}
|
||||
public final static long MIN_OFFSET_CHANGE = 10 * 1000;
|
||||
|
||||
/**
|
||||
* Specify how far away from the "correct" time the computer is - a positive
|
||||
@ -40,18 +44,18 @@ public class Clock {
|
||||
*/
|
||||
public void setOffset(long offsetMs) {
|
||||
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
|
||||
_log.error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
|
||||
getLog().error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
|
||||
return;
|
||||
}
|
||||
long delta = offsetMs - _offset;
|
||||
if ((delta < MIN_OFFSET_CHANGE) && (delta > 0 - MIN_OFFSET_CHANGE)) {
|
||||
_log.debug("Not changing offset since it is only " + delta + "ms");
|
||||
getLog().debug("Not changing offset since it is only " + delta + "ms");
|
||||
return;
|
||||
}
|
||||
if (_alreadyChanged)
|
||||
_log.log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
getLog().log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
else
|
||||
_log.log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
getLog().log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
_alreadyChanged = true;
|
||||
_offset = offsetMs;
|
||||
fireOffsetChanged(delta);
|
||||
|
@ -9,6 +9,11 @@ package net.i2p.util;
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* In case its useful later...
|
||||
* (e.g. w/ native programatic thread dumping, etc)
|
||||
@ -16,7 +21,7 @@ package net.i2p.util;
|
||||
*/
|
||||
public class I2PThread extends Thread {
|
||||
private static Log _log;
|
||||
private static OOMEventListener _lsnr;
|
||||
private static Set _listeners = new HashSet(4);
|
||||
|
||||
public I2PThread() {
|
||||
super();
|
||||
@ -38,19 +43,29 @@ public class I2PThread extends Thread {
|
||||
try {
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
if ((t instanceof OutOfMemoryError) && (_lsnr != null)) _lsnr.outOfMemory((OutOfMemoryError) t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
fireOOM((OutOfMemoryError)t);
|
||||
// we cant assume log is created
|
||||
if (_log == null) _log = new Log(I2PThread.class);
|
||||
_log.log(Log.CRIT, "Killing thread " + getName(), t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setOOMEventListener(OOMEventListener lsnr) {
|
||||
_lsnr = lsnr;
|
||||
|
||||
private void fireOOM(OutOfMemoryError oom) {
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
OOMEventListener listener = (OOMEventListener)iter.next();
|
||||
listener.outOfMemory(oom);
|
||||
}
|
||||
}
|
||||
|
||||
public static OOMEventListener getOOMEventListener() {
|
||||
return _lsnr;
|
||||
/** register a new component that wants notification of OOM events */
|
||||
public static void addOOMEventListener(OOMEventListener lsnr) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
|
||||
/** unregister a component that wants notification of OOM events */
|
||||
public static void removeOOMEventListener(OOMEventListener lsnr) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
|
||||
public interface OOMEventListener {
|
||||
|
@ -9,6 +9,9 @@ package net.i2p.util;
|
||||
*
|
||||
*/
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Wrapper class for whatever logging system I2P uses. This class should be
|
||||
* instantiated and kept as a variable for each class it is used by, ala:
|
||||
@ -24,6 +27,8 @@ public class Log {
|
||||
private Class _class;
|
||||
private String _name;
|
||||
private int _minPriority;
|
||||
private LogScope _scope;
|
||||
private LogManager _manager;
|
||||
|
||||
public final static int DEBUG = 10;
|
||||
public final static int INFO = 20;
|
||||
@ -65,33 +70,46 @@ public class Log {
|
||||
}
|
||||
|
||||
public Log(Class cls) {
|
||||
this(cls, null);
|
||||
this(I2PAppContext.getGlobalContext().logManager(), cls, null);
|
||||
_manager.addLog(this);
|
||||
}
|
||||
|
||||
public Log(String name) {
|
||||
this(null, name);
|
||||
this(I2PAppContext.getGlobalContext().logManager(), null, name);
|
||||
_manager.addLog(this);
|
||||
}
|
||||
|
||||
public Log(Class cls, String name) {
|
||||
Log(LogManager manager, Class cls) {
|
||||
this(manager, cls, null);
|
||||
}
|
||||
|
||||
Log(LogManager manager, String name) {
|
||||
this(manager, null, name);
|
||||
}
|
||||
|
||||
Log(LogManager manager, Class cls, String name) {
|
||||
_manager = manager;
|
||||
_class = cls;
|
||||
_name = name;
|
||||
_minPriority = DEBUG;
|
||||
LogManager.getInstance().registerLog(this);
|
||||
_scope = new LogScope(name, cls);
|
||||
//_manager.addRecord(new LogRecord(Log.class, null, Thread.currentThread().getName(), Log.DEBUG,
|
||||
// "Log created with manager " + manager + " for class " + cls, null));
|
||||
}
|
||||
|
||||
public void log(int priority, String msg) {
|
||||
if (priority >= _minPriority) {
|
||||
LogManager.getInstance().addRecord(
|
||||
new LogRecord(_class, _name, Thread.currentThread().getName(), priority,
|
||||
msg, null));
|
||||
_manager.addRecord(new LogRecord(_class, _name,
|
||||
Thread.currentThread().getName(), priority,
|
||||
msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
public void log(int priority, String msg, Throwable t) {
|
||||
if (priority >= _minPriority) {
|
||||
LogManager.getInstance().addRecord(
|
||||
new LogRecord(_class, _name, Thread.currentThread().getName(), priority,
|
||||
msg, t));
|
||||
_manager.addRecord(new LogRecord(_class, _name,
|
||||
Thread.currentThread().getName(), priority,
|
||||
msg, t));
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +151,9 @@ public class Log {
|
||||
|
||||
public void setMinimumPriority(int priority) {
|
||||
_minPriority = priority;
|
||||
//_manager.addRecord(new LogRecord(Log.class, null, Thread.currentThread().getName(), Log.DEBUG,
|
||||
// "Log with manager " + _manager + " for class " + _class
|
||||
// + " new priority " + toLevelString(priority), null));
|
||||
}
|
||||
|
||||
public boolean shouldLog(int priority) {
|
||||
@ -145,5 +166,32 @@ public class Log {
|
||||
else
|
||||
return _name;
|
||||
}
|
||||
|
||||
|
||||
public Object getScope() { return _scope; }
|
||||
private static final class LogScope {
|
||||
private String _scopeName;
|
||||
private Class _scopeClass;
|
||||
public LogScope(String name, Class cls) {
|
||||
_scopeName = name;
|
||||
_scopeClass = cls;
|
||||
}
|
||||
public int hashCode() {
|
||||
if (_scopeClass != null)
|
||||
return _scopeClass.hashCode();
|
||||
else if (_scopeName != null)
|
||||
return _scopeName.hashCode();
|
||||
else
|
||||
return 42;
|
||||
}
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) throw new NullPointerException("Null object scope?");
|
||||
if (obj instanceof LogScope) {
|
||||
LogScope s = (LogScope)obj;
|
||||
return DataHelper.eq(s._scopeName, _scopeName) &&
|
||||
DataHelper.eq(s._scopeClass, _scopeClass);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +1,24 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Offer a glimpse into the last few console messages generated
|
||||
*
|
||||
*/
|
||||
public class LogConsoleBuffer {
|
||||
private final static LogConsoleBuffer _instance = new LogConsoleBuffer();
|
||||
|
||||
public final static LogConsoleBuffer getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
private I2PAppContext _context;
|
||||
private List _buffer;
|
||||
|
||||
private LogConsoleBuffer() {
|
||||
_buffer = new LinkedList();
|
||||
public LogConsoleBuffer(I2PAppContext context) {
|
||||
_context = context;
|
||||
_buffer = new ArrayList();
|
||||
}
|
||||
|
||||
void add(String msg) {
|
||||
int lim = LogManager.getInstance().getConsoleBufferSize();
|
||||
int lim = _context.logManager().getConsoleBufferSize();
|
||||
synchronized (_buffer) {
|
||||
while (_buffer.size() >= lim)
|
||||
_buffer.remove(0);
|
||||
@ -36,7 +34,7 @@ public class LogConsoleBuffer {
|
||||
*/
|
||||
public List getMostRecentMessages() {
|
||||
synchronized (_buffer) {
|
||||
return new LinkedList(_buffer);
|
||||
return new ArrayList(_buffer);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Manages the logging system, loading (and reloading) the configuration file,
|
||||
@ -31,14 +35,6 @@ public class LogManager {
|
||||
public final static String CONFIG_LOCATION_PROP = "loggerConfigLocation";
|
||||
public final static String FILENAME_OVERRIDE_PROP = "loggerFilenameOverride";
|
||||
public final static String CONFIG_LOCATION_DEFAULT = "logger.config";
|
||||
|
||||
public static final LogManager getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
private static final LogManager _instance = new LogManager(System.getProperty(CONFIG_LOCATION_PROP,
|
||||
CONFIG_LOCATION_DEFAULT));
|
||||
private static final Log _log = new Log(LogManager.class);
|
||||
|
||||
/**
|
||||
* These define the characters in the format line of the config file
|
||||
*/
|
||||
@ -65,12 +61,15 @@ public class LogManager {
|
||||
public final static String DEFAULT_DEFALTLEVEL = Log.STR_DEBUG;
|
||||
public final static String DEFAULT_ONSCREENLEVEL = Log.STR_DEBUG;
|
||||
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
private long _configLastRead;
|
||||
|
||||
private String _location;
|
||||
private List _records;
|
||||
private Set _limits;
|
||||
private Set _logs;
|
||||
private Map _logs;
|
||||
private LogWriter _writer;
|
||||
|
||||
private int _defaultLimit;
|
||||
@ -83,7 +82,63 @@ public class LogManager {
|
||||
|
||||
private boolean _displayOnScreen;
|
||||
private int _consoleBufferSize;
|
||||
|
||||
private LogConsoleBuffer _consoleBuffer;
|
||||
|
||||
public LogManager(I2PAppContext context) {
|
||||
_displayOnScreen = true;
|
||||
_records = new ArrayList();
|
||||
_limits = new HashSet();
|
||||
_logs = new HashMap(128);
|
||||
_defaultLimit = Log.DEBUG;
|
||||
_configLastRead = 0;
|
||||
_location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
|
||||
_context = context;
|
||||
_log = getLog(LogManager.class);
|
||||
_consoleBuffer = new LogConsoleBuffer(context);
|
||||
loadConfig();
|
||||
_writer = new LogWriter(this);
|
||||
Thread t = new I2PThread(_writer);
|
||||
t.setName("LogWriter");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
try {
|
||||
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
|
||||
} catch (IllegalStateException ise) {
|
||||
// shutdown in progress, fsck it
|
||||
}
|
||||
//System.out.println("Created logManager " + this + " with context: " + context);
|
||||
}
|
||||
private LogManager() {}
|
||||
|
||||
public Log getLog(Class cls) { return getLog(cls, null); }
|
||||
public Log getLog(String name) { return getLog(null, name); }
|
||||
public Log getLog(Class cls, String name) {
|
||||
Log rv = null;
|
||||
synchronized (_logs) {
|
||||
Log newLog = new Log(this, cls, name);
|
||||
if (_logs.containsKey(newLog.getScope())) {
|
||||
Log oldLog = (Log)_logs.get(newLog.getScope());
|
||||
rv = oldLog;
|
||||
//_log.error("Duplicate log creation for " + cls);
|
||||
} else {
|
||||
_logs.put(newLog.getScope(), newLog);
|
||||
rv = newLog;
|
||||
}
|
||||
}
|
||||
updateLimit(rv);
|
||||
return rv;
|
||||
}
|
||||
void addLog(Log log) {
|
||||
synchronized (_logs) {
|
||||
if (!_logs.containsKey(log.getScope()))
|
||||
_logs.put(log.getScope(), log);
|
||||
}
|
||||
updateLimit(log);
|
||||
}
|
||||
|
||||
public LogConsoleBuffer getBuffer() { return _consoleBuffer; }
|
||||
|
||||
public void setDisplayOnScreen(boolean yes) {
|
||||
_displayOnScreen = yes;
|
||||
}
|
||||
@ -123,18 +178,7 @@ public class LogManager {
|
||||
_records.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during Log construction
|
||||
*
|
||||
*/
|
||||
void registerLog(Log log) {
|
||||
synchronized (_logs) {
|
||||
_logs.add(log);
|
||||
}
|
||||
updateLimit(log);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called periodically by the log writer's thread
|
||||
*
|
||||
@ -148,23 +192,6 @@ public class LogManager {
|
||||
///
|
||||
///
|
||||
|
||||
private LogManager(String location) {
|
||||
_displayOnScreen = true;
|
||||
_location = location;
|
||||
_records = new ArrayList();
|
||||
_limits = new HashSet();
|
||||
_logs = new HashSet();
|
||||
_defaultLimit = Log.DEBUG;
|
||||
_configLastRead = 0;
|
||||
loadConfig();
|
||||
_writer = new LogWriter();
|
||||
Thread t = new I2PThread(_writer);
|
||||
t.setName("LogWriter");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@ -172,9 +199,13 @@ public class LogManager {
|
||||
private void loadConfig() {
|
||||
Properties p = new Properties();
|
||||
File cfgFile = new File(_location);
|
||||
if ((_configLastRead > 0) && (_configLastRead > cfgFile.lastModified())) {
|
||||
_log.debug("Short circuiting config read");
|
||||
if ((_configLastRead > 0) && (_configLastRead >= cfgFile.lastModified())) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Short circuiting config read");
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loading config from " + _location);
|
||||
}
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@ -212,7 +243,7 @@ public class LogManager {
|
||||
_displayOnScreen = false;
|
||||
}
|
||||
|
||||
String filenameOverride = System.getProperty(FILENAME_OVERRIDE_PROP);
|
||||
String filenameOverride = _context.getProperty(FILENAME_OVERRIDE_PROP);
|
||||
if (filenameOverride != null)
|
||||
_baseLogfilename = filenameOverride;
|
||||
else
|
||||
@ -244,6 +275,9 @@ public class LogManager {
|
||||
_consoleBufferSize = DEFAULT_CONSOLEBUFFERSIZE;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Log set to use the base log file as " + _baseLogfilename);
|
||||
|
||||
parseLimits(config);
|
||||
}
|
||||
|
||||
@ -297,11 +331,11 @@ public class LogManager {
|
||||
}
|
||||
|
||||
private void updateLimits() {
|
||||
Set logs = new HashSet();
|
||||
Map logs = null;
|
||||
synchronized (_logs) {
|
||||
logs.addAll(_logs);
|
||||
logs = new HashMap(_logs);
|
||||
}
|
||||
for (Iterator iter = logs.iterator(); iter.hasNext();) {
|
||||
for (Iterator iter = logs.values().iterator(); iter.hasNext();) {
|
||||
Log log = (Log) iter.next();
|
||||
updateLimit(log);
|
||||
}
|
||||
@ -322,10 +356,13 @@ public class LogManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max != null)
|
||||
if (max != null) {
|
||||
log.setMinimumPriority(max.getLimit());
|
||||
else
|
||||
} else {
|
||||
//if (_log != null)
|
||||
// _log.debug("The log for " + log.getClass() + " has no matching limits");
|
||||
log.setMinimumPriority(_defaultLimit);
|
||||
}
|
||||
}
|
||||
|
||||
private List getLimits(Log log) {
|
||||
@ -373,10 +410,11 @@ public class LogManager {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
Log l1 = new Log("test.1");
|
||||
Log l2 = new Log("test.2");
|
||||
Log l21 = new Log("test.2.1");
|
||||
Log l = new Log("test");
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
Log l1 = ctx.logManager().getLog("test.1");
|
||||
Log l2 = ctx.logManager().getLog("test.2");
|
||||
Log l21 = ctx.logManager().getLog("test.2.1");
|
||||
Log l = ctx.logManager().getLog("test");
|
||||
l.debug("this should fail");
|
||||
l.info("this should pass");
|
||||
l1.warn("this should pass");
|
||||
|
@ -26,13 +26,13 @@ class LogRecordFormatter {
|
||||
private final static int MAX_THREAD_LENGTH = 12;
|
||||
private final static int MAX_PRIORITY_LENGTH = 5;
|
||||
|
||||
public static String formatRecord(LogRecord rec) {
|
||||
public static String formatRecord(LogManager manager, LogRecord rec) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
char format[] = LogManager.getInstance()._getFormat();
|
||||
char format[] = manager._getFormat();
|
||||
for (int i = 0; i < format.length; ++i) {
|
||||
switch ((int) format[i]) {
|
||||
case (int) LogManager.DATE:
|
||||
buf.append(getWhen(rec));
|
||||
buf.append(getWhen(manager, rec));
|
||||
break;
|
||||
case (int) LogManager.CLASS:
|
||||
buf.append(getWhere(rec));
|
||||
@ -71,8 +71,8 @@ class LogRecordFormatter {
|
||||
return toString(logRecord.getThreadName(), MAX_THREAD_LENGTH);
|
||||
}
|
||||
|
||||
private static String getWhen(LogRecord logRecord) {
|
||||
return LogManager.getInstance()._getDateFormat().format(new Date(logRecord.getDate()));
|
||||
private static String getWhen(LogManager manager, LogRecord logRecord) {
|
||||
return manager._getDateFormat().format(new Date(logRecord.getDate()));
|
||||
}
|
||||
|
||||
private static String getPriority(LogRecord rec) {
|
||||
|
@ -29,8 +29,14 @@ class LogWriter implements Runnable {
|
||||
private int _rotationNum = -1;
|
||||
private String _logFilenamePattern;
|
||||
private File _currentFile;
|
||||
private LogManager _manager;
|
||||
|
||||
private boolean _write;
|
||||
|
||||
private LogWriter() {}
|
||||
public LogWriter(LogManager manager) {
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public void stopWriting() {
|
||||
_write = false;
|
||||
@ -46,7 +52,7 @@ class LogWriter implements Runnable {
|
||||
|
||||
public void flushRecords() {
|
||||
try {
|
||||
List records = LogManager.getInstance()._removeAll();
|
||||
List records = _manager._removeAll();
|
||||
for (int i = 0; i < records.size(); i++) {
|
||||
LogRecord rec = (LogRecord) records.get(i);
|
||||
writeRecord(rec);
|
||||
@ -68,19 +74,19 @@ class LogWriter implements Runnable {
|
||||
}
|
||||
long now = Clock.getInstance().now();
|
||||
if (now - _lastReadConfig > CONFIG_READ_ITERVAL) {
|
||||
LogManager.getInstance().rereadConfig();
|
||||
_manager.rereadConfig();
|
||||
_lastReadConfig = now;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRecord(LogRecord rec) {
|
||||
String val = LogRecordFormatter.formatRecord(rec);
|
||||
String val = LogRecordFormatter.formatRecord(_manager, rec);
|
||||
writeRecord(val);
|
||||
|
||||
if (LogManager.getInstance().getDisplayOnScreenLevel() <= rec.getPriority()) {
|
||||
if (_manager.getDisplayOnScreenLevel() <= rec.getPriority()) {
|
||||
// we always add to the console buffer, but only sometimes write to stdout
|
||||
LogConsoleBuffer.getInstance().add(val);
|
||||
if (LogManager.getInstance().displayOnScreen()) {
|
||||
_manager.getBuffer().add(val);
|
||||
if (_manager.displayOnScreen()) {
|
||||
System.out.print(val);
|
||||
}
|
||||
}
|
||||
@ -98,7 +104,7 @@ class LogWriter implements Runnable {
|
||||
System.err.println("Error writing record, disk full?");
|
||||
t.printStackTrace();
|
||||
}
|
||||
if (_numBytesInCurrentFile >= LogManager.getInstance()._getFileSize()) {
|
||||
if (_numBytesInCurrentFile >= _manager._getFileSize()) {
|
||||
rotateFile();
|
||||
}
|
||||
}
|
||||
@ -108,7 +114,7 @@ class LogWriter implements Runnable {
|
||||
*
|
||||
*/
|
||||
private void rotateFile() {
|
||||
String pattern = LogManager.getInstance()._getBaseLogfilename();
|
||||
String pattern = _manager._getBaseLogfilename();
|
||||
File f = getNextFile(pattern);
|
||||
_currentFile = f;
|
||||
_numBytesInCurrentFile = 0;
|
||||
@ -129,7 +135,7 @@ class LogWriter implements Runnable {
|
||||
if (pattern.indexOf('#') < 0) {
|
||||
return new File(pattern);
|
||||
} else {
|
||||
int max = LogManager.getInstance()._getRotationLimit();
|
||||
int max = _manager._getRotationLimit();
|
||||
if (_rotationNum == -1) {
|
||||
return getFirstFile(pattern, max);
|
||||
} else {
|
||||
|
@ -24,7 +24,8 @@ public class NativeBigInteger extends BigInteger {
|
||||
_log.info("Native BigInteger library jbigi loaded");
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
_nativeOk = false;
|
||||
_log.warn("Native BigInteger library jbigi not loaded - using pure java", ule);
|
||||
_log.log(Log.CRIT, "Native BigInteger library jbigi not loaded - using pure java");
|
||||
_log.warn("jbigi not loaded", ule);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user