forked from I2P_Developers/i2p.i2p
Compare commits
24 Commits
i2p_0_6_0_
...
i2p_0_6_0_
Author | SHA1 | Date | |
---|---|---|---|
8f2a5b403c | |||
ea41a90eae | |||
b1dd29e64d | |||
46e47c47ac | |||
b7bf431f0d | |||
7f432122d9 | |||
e7be8c6097 | |||
adf56a16e1 | |||
11204b8a2b | |||
cade27dceb | |||
5597d28e59 | |||
0502fec432 | |||
a6714fc2de | |||
1219dadbd5 | |||
77b995f5ed | |||
2f53b9ff68 | |||
d84d045849 | |||
d8e72dfe48 | |||
88b9f7a74c | |||
6a19501214 | |||
ba30b56c5f | |||
a375e4b2ce | |||
44fd71e17f | |||
b41c378de9 |
@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
|
@ -27,6 +27,7 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _guessRequested;
|
||||
private boolean _reseedRequested;
|
||||
private boolean _saveRequested;
|
||||
private boolean _recheckReachabilityRequested;
|
||||
private boolean _timeSyncEnabled;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
@ -44,6 +45,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
reseed();
|
||||
} else if (_saveRequested) {
|
||||
saveChanges();
|
||||
} else if (_recheckReachabilityRequested) {
|
||||
recheckReachability();
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
@ -53,6 +56,7 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setReseed(String moo) { _reseedRequested = true; }
|
||||
public void setSave(String moo) { _saveRequested = true; }
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
_hostname = (hostname != null ? hostname.trim() : null);
|
||||
@ -195,6 +199,11 @@ public class ConfigNetHandler extends FormHandler {
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void recheckReachability() {
|
||||
_context.commSystem().recheckReachability();
|
||||
addFormNotice("Rechecking router reachability...");
|
||||
}
|
||||
|
||||
/**
|
||||
* The user made changes to the network config and wants to save them, so
|
||||
* lets go ahead and do so.
|
||||
|
@ -12,6 +12,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
@ -97,6 +98,23 @@ public class SummaryHelper {
|
||||
return (_context.netDb().getKnownRouters() < 10);
|
||||
}
|
||||
|
||||
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
|
||||
|
||||
public String getReachability() {
|
||||
int status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
return "OK";
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
return "ERR-SymmetricNAT";
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
return "ERR-Reject";
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve amount of used memory.
|
||||
*
|
||||
@ -189,6 +207,7 @@ public class SummaryHelper {
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
@ -206,6 +225,7 @@ public class SummaryHelper {
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
@ -224,6 +244,7 @@ public class SummaryHelper {
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(5*60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
@ -242,6 +263,7 @@ public class SummaryHelper {
|
||||
return "0.0";
|
||||
|
||||
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
|
||||
if (receiveRate == null) return "0.0";
|
||||
Rate rate = receiveRate.getRate(5*60*1000);
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
@ -35,6 +35,8 @@ this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, b
|
||||
TCP port: <input name="tcpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="tcpPort" />" /> <br />
|
||||
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
|
||||
connections on it (this requirement will be removed in i2p 0.6.1, but is necessary now)</b>
|
||||
<br />
|
||||
<input type="submit" name="recheckReachability" value="Check network reachability..." />
|
||||
<hr />
|
||||
|
||||
<b>Bandwidth limiter</b><br />
|
||||
|
@ -14,7 +14,8 @@
|
||||
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
|
||||
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
|
||||
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br /><%
|
||||
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><br /><%
|
||||
if (helper.updateAvailable()) {
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
|
||||
out.print(update.getStatus());
|
||||
@ -39,7 +40,8 @@
|
||||
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
|
||||
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
|
||||
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
|
||||
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /><%
|
||||
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
|
||||
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 10*1000;
|
||||
public static final long MAX_RESEND_DELAY = 5*1000;
|
||||
public static final long MIN_RESEND_DELAY = 3*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
@ -272,10 +272,13 @@ public class Connection {
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
}
|
||||
|
||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||
|
||||
_lastSendTime = _context.clock().now();
|
||||
_outboundQueue.enqueue(packet);
|
||||
resetActivityTimer();
|
||||
|
||||
/*
|
||||
if (ackOnly) {
|
||||
// ACK only, don't schedule this packet for retries
|
||||
// however, if we are running low on sessionTags we want to send
|
||||
@ -286,6 +289,7 @@ public class Connection {
|
||||
_connectionManager.ping(_remotePeer, _options.getRTT()*2, false, packet.getKeyUsed(), packet.getTagsSent(), new PingNotifier());
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private class PingNotifier implements ConnectionManager.PingNotifier {
|
||||
|
@ -13,6 +13,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
private int _receiveWindow;
|
||||
private int _profile;
|
||||
private int _rtt;
|
||||
private int _trend[];
|
||||
private int _resendDelay;
|
||||
private int _sendAckDelay;
|
||||
private int _maxMessageSize;
|
||||
@ -50,6 +51,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
public static final String PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR = "i2p.streaming.congestionAvoidanceGrowthRateFactor";
|
||||
public static final String PROP_SLOW_START_GROWTH_RATE_FACTOR = "i2p.streaming.slowStartGrowthRateFactor";
|
||||
|
||||
private static final int TREND_COUNT = 3;
|
||||
|
||||
public ConnectionOptions() {
|
||||
super();
|
||||
}
|
||||
@ -85,6 +88,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
|
||||
protected void init(Properties opts) {
|
||||
super.init(opts);
|
||||
_trend = new int[TREND_COUNT];
|
||||
|
||||
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
|
||||
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
|
||||
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
|
||||
@ -93,7 +98,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
|
||||
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 500));
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 10));
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
@ -125,7 +130,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
if (opts.containsKey(PROP_INITIAL_WINDOW_SIZE))
|
||||
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
|
||||
if (opts.containsKey(PROP_MAX_RESENDS))
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 10));
|
||||
if (opts.containsKey(PROP_WRITE_TIMEOUT))
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
if (opts.containsKey(PROP_INACTIVITY_TIMEOUT))
|
||||
@ -186,11 +191,36 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
*/
|
||||
public int getRTT() { return _rtt; }
|
||||
public void setRTT(int ms) {
|
||||
synchronized (_trend) {
|
||||
_trend[0] = _trend[1];
|
||||
_trend[1] = _trend[2];
|
||||
if (ms > _rtt)
|
||||
_trend[2] = 1;
|
||||
else if (ms < _rtt)
|
||||
_trend[2] = -1;
|
||||
else
|
||||
_trend[2] = 0;
|
||||
}
|
||||
_rtt = ms;
|
||||
if (_rtt > 60*1000)
|
||||
_rtt = 60*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have
|
||||
* 3 consecutive rtt decreases, we are trending downwards (-1), else we're stable.
|
||||
*
|
||||
*/
|
||||
public int getRTTTrend() {
|
||||
synchronized (_trend) {
|
||||
for (int i = 0; i < TREND_COUNT - 1; i++) {
|
||||
if (_trend[i] != _trend[i+1])
|
||||
return 0;
|
||||
}
|
||||
return _trend[0];
|
||||
}
|
||||
}
|
||||
|
||||
/** rtt = rtt*RTT_DAMPENING + (1-RTT_DAMPENING)*currentPacketRTT */
|
||||
private static final double RTT_DAMPENING = 0.9;
|
||||
|
||||
|
@ -26,6 +26,7 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.resetReceived", "How many messages had we sent successfully before receiving a RESET?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.trend", "What direction the RTT is trending in (with period = windowsize)", "Stream", new long[] { 60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
/** distribute a packet to the connection specified */
|
||||
@ -61,7 +62,7 @@ public class ConnectionPacketHandler {
|
||||
con.getOutputStream().setBufferSize(packet.getOptionalMaxSize());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
con.packetReceived();
|
||||
|
||||
boolean choke = false;
|
||||
@ -91,7 +92,20 @@ public class ConnectionPacketHandler {
|
||||
|
||||
_context.statManager().addRateData("stream.con.receiveMessageSize", packet.getPayloadSize(), 0);
|
||||
|
||||
boolean isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
|
||||
boolean isNew = false;
|
||||
boolean allowAck = true;
|
||||
|
||||
if ( (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) &&
|
||||
( (packet.getSendStreamId() == null) ||
|
||||
(packet.getReceiveStreamId() == null) ||
|
||||
(DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) ||
|
||||
(DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) ) )
|
||||
allowAck = false;
|
||||
|
||||
if (allowAck)
|
||||
isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
|
||||
else
|
||||
isNew = con.getInputStream().messageReceived(con.getInputStream().getHighestReadyBockId(), null);
|
||||
|
||||
if ( (packet.getSequenceNum() == 0) && (packet.getPayloadSize() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -177,7 +191,21 @@ public class ConnectionPacketHandler {
|
||||
// con.getOptions().setRTT(con.getOptions().getRTT() + nacks.length*1000);
|
||||
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(ackThrough, nacks);
|
||||
List acked = null;
|
||||
// if we don't know the streamIds for both sides of the connection, there's no way we
|
||||
// could actually be acking data (this fixes the buggered up ack of packet 0 problem).
|
||||
// this is called after packet verification, which places the stream IDs as necessary if
|
||||
// the SYN verifies (so if we're acking w/out stream IDs, no SYN has been received yet)
|
||||
if ( (packet != null) && (packet.getSendStreamId() != null) && (packet.getReceiveStreamId() != null) &&
|
||||
(con != null) && (con.getSendStreamId() != null) && (con.getReceiveStreamId() != null) &&
|
||||
(!DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
||||
(!DataHelper.eq(con.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) )
|
||||
acked = con.ackPackets(ackThrough, nacks);
|
||||
else
|
||||
return false;
|
||||
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
@ -247,8 +275,13 @@ public class ConnectionPacketHandler {
|
||||
int oldWindow = con.getOptions().getWindowSize();
|
||||
int newWindowSize = oldWindow;
|
||||
|
||||
int trend = con.getOptions().getRTTTrend();
|
||||
|
||||
_context.statManager().addRateData("stream.trend", trend, newWindowSize);
|
||||
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
if ( (newWindowSize > con.getLastCongestionSeenAt() / 2) ||
|
||||
(trend > 0) ) { // tcp vegas: avoidance if rtt is increasing, even if we arent at ssthresh/2 yet
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
|
@ -202,7 +202,7 @@ public class MessageInputStream extends InputStream {
|
||||
public boolean messageReceived(long messageId, ByteArray payload) {
|
||||
synchronized (_dataLock) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("received " + messageId + " with " + payload.getValid());
|
||||
_log.debug("received " + messageId + " with " + (payload != null ? payload.getValid()+"" : "no payload"));
|
||||
if (messageId <= _highestReadyBlockId) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("ignoring dup message " + messageId);
|
||||
|
@ -578,7 +578,7 @@ public class Packet {
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static final String toId(byte id[]) {
|
||||
static final String toId(byte id[]) {
|
||||
if (id == null)
|
||||
return Base64.encode(STREAM_ID_UNKNOWN);
|
||||
else
|
||||
|
31
apps/syndie/doc/intro.sml
Normal file
31
apps/syndie/doc/intro.sml
Normal file
@ -0,0 +1,31 @@
|
||||
Syndie is a new effort to build a user friendly secure blogging tool, exploiting the capabilities offered by anonymity and security systems such as [link schema="web" location="http://www.i2p.net/"]I2P[/link], [link schema="web" location="http://tor.eff.org/"]TOR[/link], [link schema="web" location="http://www.freenetproject.org/"]Freenet[/link], [link schema="web" location="http://www.mnetproject.org/"]MNet[/link], and others. Abstracting away the content distribution side, Syndie allows people to [b]build content and communities[/b] that span technologies rather than tying oneself down to the ups and downs of any particular network.
|
||||
|
||||
[cut][/cut]Syndie is working to take the technologies of the security, anonymity, and cryptography worlds and merge them with the simplicity and user focus of the blogging world. From the user's standpoint, you could perhaps view Syndie as a distributed [link schema="web" location="http://www.livejournal.com"]LiveJournal[/link], while technically Syndie is much, much simpler.
|
||||
|
||||
[b]How Syndie works[/b][hr][/hr]The [i]magic[/i] behind Syndie's abstraction is to ignore any content distribution issues and merely assume data moves around as necessary. Each Syndie instance runs agains the filesystem, verifying and indexing blogs and offering up what it knows to the user through a web interface. The core idea in Syndie, therefore, is the [b]archive[/b]- a collection of blogs categorized and ready for consumption.
|
||||
|
||||
Whenever someone reads or posts to a Syndie instance, it is working with the [b]local archive[/b]. However, as Syndie's development progresses, people will be able to read [b]remote archives[/b] - pulling the archive summary from an I2P [i]eepsite[/i], TOR [i]hosted service[/i], Freenet [i]Freesite[/i], MNet [i]key[/i], or (with a little less glamor) usenet, filesharing apps, or the web. The first thing Syndie needs to use a remote archive is the archive's index - a plain text file summarizing what the archive contains ([attachment id="0"]an example[/attachment]). From that, Syndie will let the user browse through the blogs, pulling the individual blog posts into the local archive when necessary.
|
||||
|
||||
[b]Posting[/b][hr][/hr]Creating and posting to blogs with Syndie is trivial - simply log in to Syndie, click on the [i]Post[/i] button, and fill out the form offered. Syndie handles all of the encryption and formatting details - packaging up the post with any attached files into a single signed, compressed, and potentially encrypted bundle, storing it in the local archive and capable of being shared with other Syndie users. Every blog is identified by its public key behind the scenes, so there is no need for a central authority to require that your blogs are all named uniquely or any other such thing.
|
||||
|
||||
While each blog is run by a single author, they can in turn allow other authors to post to the blog while still letting readers know that the post is authorized (though created by a different author). Of course, if multiple people wanted to run a single blog and make it look like only one person wrote it, they could share the blog's private keys.
|
||||
|
||||
[b]Tags[/b][hr][/hr]Following the lessons from the last few years, every Syndie entry has any number of tags associated with it by the author, allowing trivial categorization and filtering.
|
||||
|
||||
[b]Hosting[/b][hr][/hr]While in many scenarios it is best for people to run Syndie locally on their machine, Syndie is a fully multiuser system so anyone can be a Syndie hosting provider by simply exposing the web interface to the public. The Syndie host's operator can password protect the blog registration interface so only authorized people can create a blog, and the operator can technically go through and delete blog posts or even entire blogs from their local archive. A public Syndie host can be a general purpose blog repository, letting anyone sign up (following the blogger and geocities path), be a more community oriented blog repository, requiring people to introduce you to the host to sign up (following the livejournal/orkut path), be a more focused blog repository, requiring posts to stay within certain guidelines (following the indymedia path), or even to fit specialized needs by picking and choosing among the best blogs and posts out there, offering the operator's editorial flare into a comprehensive collection.
|
||||
|
||||
[b]Syndication[/b][hr][/hr]By itself, Syndie is a nice blogging community system, but its real strength as a tool for individual and community empowerment comes when blogs are shared. While Syndie does not aim to be a content distribution network, it does want to exploit them to allow those who require their message to get out to do so. By design, syndicating Syndie can be done with some of the most basic tools - simply pass around the self authenticating files written to the archive and you're done. The archive itself is organized so that you can expose it as an indexed directory in some webserver and let people wget against it, picking to pull individual posts, all posts within a blog, all posts since a given date, or all posts in all blogs. With a very small shell script, you could parse the plain text archive summary to pull posts by size and tag as well. People could offer up their archives as rsync repositories or package up tarballs/zipfiles of blogs or entries - simply grabbing them and extracting them into your local Syndie archive would instantly give you access to all of the content therein.
|
||||
|
||||
Of course, manual syndication as described above has... limits. When appropriate, Syndie will tie in to content syndication systems such as [link schema="eep" location="http://feedspace.i2p/"]Feedspace[/link] (or even good ol' Usenet) to automatically import (and export) posts. Integration with content distribution networks like Freenet and MNet will allow the user to periodically grab a published archive index and pull down blogs as necessary. Posting archives and blogs to those networks will be done trivially as well, though they do still depend upon a polling paradigm.
|
||||
|
||||
[b]SML[/b][hr][/hr]Syndie is meant to work securely with any browser regardless of the browser's security. Blog entries are written in [b]SML[/b] [i](Syndie or Secure Markup Language)[/i] with a bbcode-linke syntax, extended to exploit some of Syndie's capabilities and context. In addition to the SML content in a blog entry, there can be any number of attachments, references to other blogs/posts/tags, nym<->public key mappings (useful for I2P host distribution), references to archives of blogs (on eepsites, freesites, etc), links to various resources, and more.
|
||||
|
||||
[b]Future[/b][hr][/hr]Down the road, there are lots of things to improve with Syndie. The interface, of course, is critical, as are tools for SML authoring and improvements to SML itself to offer a more engaging user experience. Integration with a search engine like Lucene would allow full text search through entire archives, and Atom/RSS interfaces would allow trivial import and export to existing clients. Even further, blogs could be transparently encrypted, allowing only authorized users (those with the key) to read entries posted to them (or even know what attachments are included). Integration with existing blogging services (such as [link schema="web" location="http://www.anonyblog.com"]anonyblog[/link], [link schema="web" location="http://blo.gs"]blo.gs[/link], and [link schema="web" location="http://livejournal.com"]livejournal[/link]) may also be explored. Of course, bundling with I2P and other anonymity, security, and community systems will be pursued.
|
||||
|
||||
[b]Who/where/when/why[/b][hr][/hr]The base Syndie system was written in a few days by [blog name="jrandom" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" archive0="eep://dev.i2p/~jrandom" archive1="http://dev.i2p.net/~jrandom" archive2="mailto://jrandom@i2p.net"][/blog], though comes out of discussions with [link schema="eep" location="http://frosk.i2p"]Frosk[/link] and many others in the I2P community. Yes, this is an incarnation of [b]MyI2P[/b] (or for those who remember jrand0m's flog, [b]Flogger[/b]).
|
||||
|
||||
All of the Syndie code is of course open source and released into the public domain (the [i]real[/i] "free as in freedom"), though it does use some BSD licensed cryptographic routines and an Apache licensed file upload component. Contributions of code are very much welcome - the source is located within the [link schema="web" location="http://www.i2p.net/cvs"]I2P codebase[/link]. Of course, those who cannot or choose not to contribute code are encouraged to [b]use[/b] Syndie - create a blog, create some content, read some content! For those who really want to though, financial contributions to the Syndie development effort can be channeled through the [link schema="web" location="http://www.i2p.net/donate"]I2P fund[/link] (donations for Syndie are distributed to Syndie developers from time to time).
|
||||
|
||||
The "why" of Syndie is a much bigger question, though is hopefully self-evident. We need kickass anonymity-aware client applications so that we can get better anonymity (since without kickass clients, we don't have many users). We also need kickass tools for safe blogging, since there are limits to the strength offered by low latency anonymity systems like I2P and TOR - Syndie goes beyond them to offer an interface to mid and high latency anonymous systems while exploiting their capabilities for fast and efficient syndication.
|
||||
|
||||
Oh, and jrandom also lost his blog's private key, so needed something to blog with again.
|
27
apps/syndie/doc/readme.txt
Normal file
27
apps/syndie/doc/readme.txt
Normal file
@ -0,0 +1,27 @@
|
||||
To install this base instance:
|
||||
|
||||
mkdir lib
|
||||
cp ../lib/i2p.jar lib/
|
||||
cp ../lib/commons-el.jar lib/
|
||||
cp ../lib/commons-logging.jar lib/
|
||||
cp ../lib/jasper-compiler.jar lib/
|
||||
cp ../lib/jasper-runtime.jar lib/
|
||||
cp ../lib/javax.servlet.jar lib/
|
||||
cp ../lib/jbigi.jar lib/
|
||||
cp ../lib/org.mortbay.jetty.jar lib/
|
||||
cp ../lib/xercesImpl.jar lib/
|
||||
|
||||
To run it:
|
||||
sh run.sh
|
||||
firefox http://localhost:7653/syndie/
|
||||
|
||||
You can share your archive at http://localhost:7653/ so
|
||||
that people can syndicate off you via
|
||||
cd archive ; wget -m -nH http://yourmachine:7653/
|
||||
|
||||
You may want to add a password on the registration form
|
||||
so that you have control over who can create blogs via /syndie/.
|
||||
To do so, set the password in the run.sh script.
|
||||
|
||||
Windows users:
|
||||
write your own instructions. We're alpha, here ;)
|
41
apps/syndie/doc/sml.sml
Normal file
41
apps/syndie/doc/sml.sml
Normal file
@ -0,0 +1,41 @@
|
||||
[cut]A brief glance at SML[/cut]
|
||||
[b]General rules[/b]
|
||||
|
||||
Newlines are newlines are newlines. If you include a newline in your SML, you'll get a newline in the rendered HTML.
|
||||
|
||||
All < and > characters are replaced by their HTML entity counterparts.
|
||||
|
||||
All SML tags are enclosed with [[ and ]] (e.g. [[b]]bold stuff[[/b]]). ([[ and ]] characters are quoted by [[[[ and ]]]], respectively)
|
||||
|
||||
Nesting SML tags is [b]not[/b] currently supported (though will be at a later date).
|
||||
|
||||
All SML tags must have a beginning and end tag (even for ones without any 'body', such as [[hr]][[/hr]]). This restriction may be removed later.
|
||||
|
||||
Simple formatting tags behave as expected: [[b]], [[i]], [[u]], [[h1]] through [[h5]], [[hr]], [[pre]].
|
||||
[hr][/hr]
|
||||
[b]Tag details[/b]
|
||||
|
||||
* To cut an entry so that the summary is before while the details are afterwards:
|
||||
[[cut]]more inside...[[/cut]]
|
||||
|
||||
* To load an attachment as an image with "syndie's logo" as the alternate text:
|
||||
[[img attachment="0"]]syndie's logo[[/img]]
|
||||
|
||||
* To add a download link to an attachment:
|
||||
[[attachment id="0"]]anchor text[[/img]]
|
||||
|
||||
* To quote someone:
|
||||
[[quote author="who you are quoting" location="blog://ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1234567890"]]stuff they said[[/quote]]
|
||||
|
||||
* To sample some code:
|
||||
[[code location="eep://dev.i2p/cgi-bin/cvsweb.cgi/i2p/index.html"]]<html>[[/code]]
|
||||
|
||||
* To link to a [blog name="jrandom" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" blogentry="1124402137773" archive0="eep://dev.i2p/~jrandom/archive" archive1="irc2p://jrandom@irc.postman.i2p/#i2p"]bitchin' blog[/blog]:
|
||||
[[blog name="the blogs name" bloghash="ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" blogtag="tag" blogentry="123456789" archive0="eep://dev.i2p/~jrandom/archive/" archive1="freenet://SSK@blah/archive//"]]description of the blog[[/blog]]. blogentry and blogtag are optional and there can be any number of archiveN locations specified.
|
||||
|
||||
* To link to an [link schema="eep" location="http://dev.i2p/"]external resource[/link]:
|
||||
[[link schema="eep" location="http://dev.i2p/"]]link to it[[/link]].
|
||||
[i]The schema should be a network selection tool, such as "eep" for an eepsite, "tor" for a tor hidden service, "web" for a normal website, "freenet" for a freenet key, etc. The local user's Syndie configuration should include information necessary for the user to access the content referenced through the given schemas.[/i]
|
||||
|
||||
* To pass an [address name="dev.i2p" schema="eep" location="NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA"]addressbook entry[/address]:
|
||||
[[address name="dev.i2p" schema="eep" location="NF2...AAAA"]]add it[[/address]].
|
99
apps/syndie/java/build.xml
Normal file
99
apps/syndie/java/build.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="syndie">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/syndie.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.syndie.CLI" />
|
||||
<attribute name="Class-Path" value="i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<ant target="war" />
|
||||
</target>
|
||||
<target name="war" depends="builddep, compile, precompilejsp">
|
||||
<war destfile="../syndie.war" webxml="../jsp/web-out.xml">
|
||||
<fileset dir="../jsp/" includes="**/*" excludes=".nbintdb, web.xml, web-out.xml, web-fragment.xml, **/*.java, **/*.jsp" />
|
||||
<classes dir="./build/obj" />
|
||||
</war>
|
||||
</target>
|
||||
<target name="precompilejsp">
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
<delete file="../jsp/web-fragment.xml" />
|
||||
<delete file="../jsp/web-out.xml" />
|
||||
<mkdir dir="../jsp/WEB-INF/" />
|
||||
<mkdir dir="../jsp/WEB-INF/classes" />
|
||||
<!-- there are various jspc ant tasks, but they all seem a bit flakey -->
|
||||
<java classname="org.apache.jasper.JspC" fork="true" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/jasper-compiler.jar" />
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/ant.jar" />
|
||||
<pathelement location="build/obj" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
<arg value="-d" />
|
||||
<arg value="../jsp/WEB-INF/classes" />
|
||||
<arg value="-p" />
|
||||
<arg value="net.i2p.syndie.jsp" />
|
||||
<arg value="-webinc" />
|
||||
<arg value="../jsp/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="../jsp/" />
|
||||
</java>
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="**/*.java" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="build/obj" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
<copy file="../jsp/web.xml" tofile="../jsp/web-out.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="../jsp/web-fragment.xml" />
|
||||
<replace file="../jsp/web-out.xml">
|
||||
<replacefilter token="<!-- precompiled servlets -->" value="${jspc.web.fragment}" />
|
||||
</replace>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="syndie" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
374
apps/syndie/java/src/net/i2p/syndie/Archive.java
Normal file
374
apps/syndie/java/src/net/i2p/syndie/Archive.java
Normal file
@ -0,0 +1,374 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.text.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Store blog info in the local filesystem.
|
||||
*
|
||||
* Entries are stored under:
|
||||
* $rootDir/$h(blogKey)/$entryId.snd (the index lists them as YYYYMMDD_n_jKB)
|
||||
* Blog info is stored under:
|
||||
* $rootDir/$h(blogKey)/meta.snm
|
||||
* Archive summary is stored under
|
||||
* $rootDir/archive.txt
|
||||
* Any key=value pairs in
|
||||
* $rootDir/archiveHeaders.txt
|
||||
* are injected into the archive.txt on regeneration.
|
||||
*
|
||||
* When entries are loaded for extraction/verification/etc, their contents are written to
|
||||
* $cacheDir/$h(blogKey)/$entryId/ (e.g. $cacheDir/$h(blogKey)/$entryId/entry.sml)
|
||||
*/
|
||||
public class Archive {
|
||||
private I2PAppContext _context;
|
||||
private File _rootDir;
|
||||
private File _cacheDir;
|
||||
private Map _blogInfo;
|
||||
private ArchiveIndex _index;
|
||||
private EntryExtractor _extractor;
|
||||
|
||||
public static final String METADATA_FILE = "meta.snm";
|
||||
public static final String INDEX_FILE = "archive.txt";
|
||||
public static final String HEADER_FILE = "archiveHeaders.txt";
|
||||
|
||||
private static final FilenameFilter _entryFilenameFilter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) { return name.endsWith(".snd"); }
|
||||
};
|
||||
|
||||
public Archive(I2PAppContext ctx, String rootDir, String cacheDir) {
|
||||
_context = ctx;
|
||||
_rootDir = new File(rootDir);
|
||||
if (!_rootDir.exists())
|
||||
_rootDir.mkdirs();
|
||||
_cacheDir = new File(cacheDir);
|
||||
if (!_cacheDir.exists())
|
||||
_cacheDir.mkdirs();
|
||||
_blogInfo = new HashMap();
|
||||
_index = null;
|
||||
_extractor = new EntryExtractor(ctx);
|
||||
reloadInfo();
|
||||
}
|
||||
|
||||
public void reloadInfo() {
|
||||
File f[] = _rootDir.listFiles();
|
||||
List info = new ArrayList();
|
||||
for (int i = 0; i < f.length; i++) {
|
||||
if (f[i].isDirectory()) {
|
||||
File meta = new File(f[i], METADATA_FILE);
|
||||
if (meta.exists()) {
|
||||
BlogInfo bi = new BlogInfo();
|
||||
try {
|
||||
bi.load(new FileInputStream(meta));
|
||||
info.add(bi);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (_blogInfo) {
|
||||
_blogInfo.clear();
|
||||
for (int i = 0; i < info.size(); i++) {
|
||||
BlogInfo bi = (BlogInfo)info.get(i);
|
||||
_blogInfo.put(bi.getKey().calculateHash(), bi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlogInfo getBlogInfo(BlogURI uri) {
|
||||
synchronized (_blogInfo) {
|
||||
return (BlogInfo)_blogInfo.get(uri.getKeyHash());
|
||||
}
|
||||
}
|
||||
public BlogInfo getBlogInfo(Hash key) {
|
||||
synchronized (_blogInfo) {
|
||||
return (BlogInfo)_blogInfo.get(key);
|
||||
}
|
||||
}
|
||||
public void storeBlogInfo(BlogInfo info) {
|
||||
if (!info.verify(_context)) {
|
||||
System.err.println("Not storing the invalid blog " + info);
|
||||
return;
|
||||
}
|
||||
synchronized (_blogInfo) {
|
||||
_blogInfo.put(info.getKey().calculateHash(), info);
|
||||
}
|
||||
try {
|
||||
File blogDir = new File(_rootDir, info.getKey().calculateHash().toBase64());
|
||||
blogDir.mkdirs();
|
||||
File blogFile = new File(blogDir, "meta.snm");
|
||||
FileOutputStream out = new FileOutputStream(blogFile);
|
||||
info.write(out);
|
||||
out.close();
|
||||
System.out.println("Blog info written to " + blogFile.getPath());
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public List listBlogs() {
|
||||
synchronized (_blogInfo) {
|
||||
return new ArrayList(_blogInfo.values());
|
||||
}
|
||||
}
|
||||
|
||||
private File getEntryDir(File entryFile) {
|
||||
String name = entryFile.getName();
|
||||
if (!name.endsWith(".snd")) throw new RuntimeException("hmm, why are we trying to get an entry dir for " + entryFile.getAbsolutePath());
|
||||
String blog = entryFile.getParentFile().getName();
|
||||
File blogDir = new File(_cacheDir, blog);
|
||||
return new File(blogDir, name.substring(0, name.length()-4));
|
||||
//return new File(entryFile.getParentFile(), "." + name.substring(0, name.length()-4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expensive operation, reading all entries within the blog and parsing out the tags.
|
||||
* Whenever possible, query the index instead of the archive
|
||||
*
|
||||
*/
|
||||
public List listTags(Hash blogKeyHash) {
|
||||
List rv = new ArrayList();
|
||||
BlogInfo info = getBlogInfo(blogKeyHash);
|
||||
if (info == null)
|
||||
return rv;
|
||||
|
||||
File blogDir = new File(_rootDir, Base64.encode(blogKeyHash.getData()));
|
||||
File entries[] = blogDir.listFiles(_entryFilenameFilter);
|
||||
for (int j = 0; j < entries.length; j++) {
|
||||
try {
|
||||
File entryDir = getEntryDir(entries[j]);
|
||||
if (!entryDir.exists()) {
|
||||
if (!extractEntry(entries[j], entryDir, info)) {
|
||||
System.err.println("Entry " + entries[j].getPath() + " is not valid");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EntryContainer entry = getCachedEntry(entryDir);
|
||||
String tags[] = entry.getTags();
|
||||
for (int t = 0; t < tags.length; t++) {
|
||||
if (!rv.contains(tags[t])) {
|
||||
System.out.println("Found a new tag in cached " + entry.getURI() + ": " + tags[t]);
|
||||
rv.add(tags[t]);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} // end iterating over the entries
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the entry to the given dir, returning true if it was verified properly
|
||||
*
|
||||
*/
|
||||
private boolean extractEntry(File entryFile, File entryDir, BlogInfo info) throws IOException {
|
||||
if (!entryDir.exists())
|
||||
entryDir.mkdirs();
|
||||
|
||||
boolean ok = _extractor.extract(entryFile, entryDir, null, info);
|
||||
if (!ok) {
|
||||
File files[] = entryDir.listFiles();
|
||||
for (int i = 0; i < files.length; i++)
|
||||
files[i].delete();
|
||||
entryDir.delete();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private EntryContainer getCachedEntry(File entryDir) {
|
||||
return new CachedEntry(entryDir);
|
||||
}
|
||||
|
||||
public EntryContainer getEntry(BlogURI uri) { return getEntry(uri, null); }
|
||||
public EntryContainer getEntry(BlogURI uri, SessionKey blogKey) {
|
||||
List entries = listEntries(uri, null, blogKey);
|
||||
if (entries.size() > 0)
|
||||
return (EntryContainer)entries.get(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public List listEntries(BlogURI uri, String tag, SessionKey blogKey) {
|
||||
return listEntries(uri.getKeyHash(), uri.getEntryId(), tag, blogKey);
|
||||
}
|
||||
public List listEntries(Hash blog, long entryId, String tag, SessionKey blogKey) {
|
||||
List rv = new ArrayList();
|
||||
BlogInfo info = getBlogInfo(blog);
|
||||
if (info == null)
|
||||
return rv;
|
||||
|
||||
File blogDir = new File(_rootDir, blog.toBase64());
|
||||
File entries[] = blogDir.listFiles(_entryFilenameFilter);
|
||||
if (entries == null)
|
||||
return rv;
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
try {
|
||||
EntryContainer entry = null;
|
||||
if (blogKey == null) {
|
||||
// no key, cache.
|
||||
File entryDir = getEntryDir(entries[i]);
|
||||
if (!entryDir.exists()) {
|
||||
if (!extractEntry(entries[i], entryDir, info)) {
|
||||
System.err.println("Entry " + entries[i].getPath() + " is not valid");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
entry = getCachedEntry(entryDir);
|
||||
} else {
|
||||
// we have an explicit key - no caching
|
||||
entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entries[i]));
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
System.err.println("Keyed entry " + entries[i].getPath() + " is not valid");
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.parseRawData(_context, blogKey);
|
||||
|
||||
entry.setCompleteSize((int)entries[i].length());
|
||||
}
|
||||
|
||||
if (entryId >= 0) {
|
||||
if (entry.getURI().getEntryId() == entryId) {
|
||||
rv.add(entry);
|
||||
return rv;
|
||||
}
|
||||
} else if (tag != null) {
|
||||
String tags[] = entry.getTags();
|
||||
for (int j = 0; j < tags.length; j++) {
|
||||
if (tags[j].equals(tag)) {
|
||||
rv.add(entry);
|
||||
System.out.println("cached entry matched requested tag [" + tag + "]: " + entry.getURI());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("cached entry is ok and no id or tag was requested: " + entry.getURI());
|
||||
rv.add(entry);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean storeEntry(EntryContainer container) {
|
||||
BlogURI uri = container.getURI();
|
||||
BlogInfo info = getBlogInfo(uri);
|
||||
if (info == null) {
|
||||
System.out.println("no blog metadata for the uri " + uri);
|
||||
return false;
|
||||
}
|
||||
if (!container.verifySignature(_context, info)) {
|
||||
System.out.println("Not storing the invalid blog entry at " + uri);
|
||||
return false;
|
||||
} else {
|
||||
//System.out.println("Signature is valid: " + container.getSignature() + " for info " + info);
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
container.write(baos, true);
|
||||
File blogDir = new File(_rootDir, uri.getKeyHash().toBase64());
|
||||
blogDir.mkdirs();
|
||||
byte data[] = baos.toByteArray();
|
||||
File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId()));
|
||||
FileOutputStream out = new FileOutputStream(entryFile);
|
||||
out.write(data);
|
||||
out.close();
|
||||
container.setCompleteSize(data.length);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getEntryFilename(long entryId) { return entryId + ".snd"; }
|
||||
|
||||
private static SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd");
|
||||
public static String getIndexName(long entryId, int numBytes) {
|
||||
try {
|
||||
synchronized (_dateFmt) {
|
||||
String yy = _dateFmt.format(new Date(entryId));
|
||||
long begin = _dateFmt.parse(yy).getTime();
|
||||
long n = entryId - begin;
|
||||
int kb = numBytes / 1024;
|
||||
return yy + '_' + n + '_' + kb + "KB";
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return "UNKNOWN";
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
public static long getEntryIdFromIndexName(String entryIndexName) {
|
||||
if (entryIndexName == null) return -1;
|
||||
if (entryIndexName.endsWith(".snd"))
|
||||
entryIndexName = entryIndexName.substring(0, entryIndexName.length() - 4);
|
||||
int endYY = entryIndexName.indexOf('_');
|
||||
if (endYY <= 0) return -1;
|
||||
int endN = entryIndexName.indexOf('_', endYY+1);
|
||||
if (endN <= 0) return -1;
|
||||
String yy = entryIndexName.substring(0, endYY);
|
||||
String n = entryIndexName.substring(endYY+1, endN);
|
||||
try {
|
||||
synchronized (_dateFmt) {
|
||||
long dayBegin = _dateFmt.parse(yy).getTime();
|
||||
long dayEntry = Long.parseLong(n);
|
||||
return dayBegin + dayEntry;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public static int getSizeFromIndexName(String entryIndexName) {
|
||||
if (entryIndexName == null) return -1;
|
||||
if (entryIndexName.endsWith(".snd"))
|
||||
entryIndexName = entryIndexName.substring(0, entryIndexName.length() - 4);
|
||||
int beginSize = entryIndexName.lastIndexOf('_');
|
||||
if ( (beginSize <= 0) || (beginSize >= entryIndexName.length()-3) )
|
||||
return -1;
|
||||
try {
|
||||
String sz = entryIndexName.substring(beginSize+1, entryIndexName.length()-2);
|
||||
return Integer.parseInt(sz);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ArchiveIndex getIndex() {
|
||||
if (_index == null)
|
||||
regenerateIndex();
|
||||
return _index;
|
||||
}
|
||||
|
||||
public File getArchiveDir() { return _rootDir; }
|
||||
public File getIndexFile() { return new File(_rootDir, INDEX_FILE); }
|
||||
public void regenerateIndex() {
|
||||
reloadInfo();
|
||||
_index = ArchiveIndexer.index(_context, this);
|
||||
try {
|
||||
PrintWriter out = new PrintWriter(new FileWriter(new File(_rootDir, INDEX_FILE)));
|
||||
out.println(_index.toString());
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
137
apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
Normal file
137
apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
Normal file
@ -0,0 +1,137 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Dig through the archive to build an index
|
||||
*/
|
||||
class ArchiveIndexer {
|
||||
private static final int RECENT_BLOG_COUNT = 10;
|
||||
private static final int RECENT_ENTRY_COUNT = 10;
|
||||
|
||||
public static ArchiveIndex index(I2PAppContext ctx, Archive source) {
|
||||
LocalArchiveIndex rv = new LocalArchiveIndex();
|
||||
rv.setGeneratedOn(ctx.clock().now());
|
||||
|
||||
File rootDir = source.getArchiveDir();
|
||||
|
||||
File headerFile = new File(rootDir, Archive.HEADER_FILE);
|
||||
if (headerFile.exists()) {
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile)));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
StringTokenizer tok = new StringTokenizer(line, ":");
|
||||
if (tok.countTokens() == 2)
|
||||
rv.setHeader(tok.nextToken(), tok.nextToken());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// things are new if we just received them in the last day
|
||||
long newSince = ctx.clock().now() - 24*60*60*1000;
|
||||
|
||||
rv.setVersion(Version.INDEX_VERSION);
|
||||
|
||||
/** 0-lowestEntryId --> blog Hash */
|
||||
Map blogsByAge = new TreeMap();
|
||||
/** 0-entryId --> BlogURI */
|
||||
Map entriesByAge = new TreeMap();
|
||||
List blogs = source.listBlogs();
|
||||
rv.setAllBlogs(blogs.size());
|
||||
|
||||
int newEntries = 0;
|
||||
int allEntries = 0;
|
||||
long newSize = 0;
|
||||
long totalSize = 0;
|
||||
int newBlogs = 0;
|
||||
|
||||
for (int i = 0; i < blogs.size(); i++) {
|
||||
BlogInfo cur = (BlogInfo)blogs.get(i);
|
||||
Hash key = cur.getKey().calculateHash();
|
||||
String keyStr = Base64.encode(key.getData());
|
||||
File blogDir = new File(rootDir, Base64.encode(key.getData()));
|
||||
|
||||
File metaFile = new File(blogDir, Archive.METADATA_FILE);
|
||||
long metadate = metaFile.lastModified();
|
||||
|
||||
List entries = source.listEntries(key, -1, null, null);
|
||||
System.out.println("Entries under " + key + ": " + entries);
|
||||
/** tag name --> ordered map of entryId to EntryContainer */
|
||||
Map tags = new TreeMap();
|
||||
|
||||
for (int j = 0; j < entries.size(); j++) {
|
||||
EntryContainer entry = (EntryContainer)entries.get(j);
|
||||
entriesByAge.put(new Long(0-entry.getURI().getEntryId()), entry.getURI());
|
||||
allEntries++;
|
||||
totalSize += entry.getCompleteSize();
|
||||
String entryTags[] = entry.getTags();
|
||||
for (int t = 0; t < entryTags.length; t++) {
|
||||
if (!tags.containsKey(entryTags[t])) {
|
||||
tags.put(entryTags[t], new TreeMap());
|
||||
}
|
||||
Map entriesByTag = (Map)tags.get(entryTags[t]);
|
||||
entriesByTag.put(new Long(0-entry.getURI().getEntryId()), entry);
|
||||
System.out.println("Entries under tag " + entryTags[t] + ":" + entriesByTag.values());
|
||||
}
|
||||
|
||||
if (entry.getURI().getEntryId() >= newSince) {
|
||||
newEntries++;
|
||||
newSize += entry.getCompleteSize();
|
||||
}
|
||||
}
|
||||
|
||||
long lowestEntryId = -1;
|
||||
for (Iterator iter = tags.keySet().iterator(); iter.hasNext(); ) {
|
||||
String tagName = (String)iter.next();
|
||||
Map tagEntries = (Map)tags.get(tagName);
|
||||
long highestId = -1;
|
||||
if (tagEntries.size() <= 0) break;
|
||||
Long id = (Long)tagEntries.keySet().iterator().next();
|
||||
highestId = 0 - id.longValue();
|
||||
|
||||
rv.addBlog(key, tagName, highestId);
|
||||
for (Iterator entryIter = tagEntries.values().iterator(); entryIter.hasNext(); ) {
|
||||
EntryContainer entry = (EntryContainer)entryIter.next();
|
||||
String indexName = Archive.getIndexName(entry.getURI().getEntryId(), entry.getCompleteSize());
|
||||
rv.addBlogEntry(key, tagName, indexName);
|
||||
if (!entryIter.hasNext())
|
||||
lowestEntryId = entry.getURI().getEntryId();
|
||||
}
|
||||
}
|
||||
|
||||
if (lowestEntryId > newSince)
|
||||
newBlogs++;
|
||||
|
||||
blogsByAge.put(new Long(0-lowestEntryId), key);
|
||||
}
|
||||
|
||||
rv.setAllEntries(allEntries);
|
||||
rv.setNewBlogs(newBlogs);
|
||||
rv.setNewEntries(newEntries);
|
||||
rv.setTotalSize(totalSize);
|
||||
rv.setNewSize(newSize);
|
||||
|
||||
int i = 0;
|
||||
for (Iterator iter = blogsByAge.keySet().iterator(); iter.hasNext() && i < RECENT_BLOG_COUNT; i++) {
|
||||
Long when = (Long)iter.next();
|
||||
Hash key = (Hash)blogsByAge.get(when);
|
||||
rv.addNewestBlog(key);
|
||||
}
|
||||
i = 0;
|
||||
for (Iterator iter = entriesByAge.keySet().iterator(); iter.hasNext() && i < RECENT_ENTRY_COUNT; i++) {
|
||||
Long when = (Long)iter.next();
|
||||
BlogURI uri = (BlogURI)entriesByAge.get(when);
|
||||
rv.addNewestEntry(uri);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
432
apps/syndie/java/src/net/i2p/syndie/BlogManager.java
Normal file
432
apps/syndie/java/src/net/i2p/syndie/BlogManager.java
Normal file
@ -0,0 +1,432 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BlogManager {
|
||||
private I2PAppContext _context;
|
||||
private static BlogManager _instance;
|
||||
private File _blogKeyDir;
|
||||
private File _privKeyDir;
|
||||
private File _archiveDir;
|
||||
private File _userDir;
|
||||
private File _cacheDir;
|
||||
private Archive _archive;
|
||||
|
||||
static {
|
||||
String rootDir = I2PAppContext.getGlobalContext().getProperty("syndie.rootDir");
|
||||
if (rootDir == null)
|
||||
rootDir = System.getProperty("user.home");
|
||||
rootDir = rootDir + File.separatorChar + ".syndie";
|
||||
_instance = new BlogManager(I2PAppContext.getGlobalContext(), rootDir);
|
||||
}
|
||||
public static BlogManager instance() { return _instance; }
|
||||
|
||||
public BlogManager(I2PAppContext ctx, String rootDir) {
|
||||
_context = ctx;
|
||||
File root = new File(rootDir);
|
||||
root.mkdirs();
|
||||
_blogKeyDir = new File(root, "blogkeys");
|
||||
_privKeyDir = new File(root, "privkeys");
|
||||
String archiveDir = _context.getProperty("syndie.archiveDir");
|
||||
if (archiveDir != null)
|
||||
_archiveDir = new File(archiveDir);
|
||||
else
|
||||
_archiveDir = new File(root, "archive");
|
||||
_userDir = new File(root, "users");
|
||||
_cacheDir = new File(root, "cache");
|
||||
_blogKeyDir.mkdirs();
|
||||
_privKeyDir.mkdirs();
|
||||
_archiveDir.mkdirs();
|
||||
_cacheDir.mkdirs();
|
||||
_userDir.mkdirs();
|
||||
_archive = new Archive(ctx, _archiveDir.getAbsolutePath(), _cacheDir.getAbsolutePath());
|
||||
_archive.regenerateIndex();
|
||||
}
|
||||
|
||||
public BlogInfo createBlog(String name, String description, String contactURL, String archives[]) {
|
||||
return createBlog(name, null, description, contactURL, archives);
|
||||
}
|
||||
public BlogInfo createBlog(String name, SigningPublicKey posters[], String description, String contactURL, String archives[]) {
|
||||
Object keys[] = _context.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey pub = (SigningPublicKey)keys[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
|
||||
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(new File(_privKeyDir, Base64.encode(pub.calculateHash().getData()) + ".priv"));
|
||||
pub.writeBytes(out);
|
||||
priv.writeBytes(out);
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return createInfo(pub, priv, name, posters, description, contactURL, archives, 0);
|
||||
}
|
||||
|
||||
public BlogInfo createInfo(SigningPublicKey pub, SigningPrivateKey priv, String name, SigningPublicKey posters[],
|
||||
String description, String contactURL, String archives[], int edition) {
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("Name", name);
|
||||
opts.setProperty("Description", description);
|
||||
opts.setProperty("Edition", Integer.toString(edition));
|
||||
opts.setProperty("ContactURL", contactURL);
|
||||
for (int i = 0; archives != null && i < archives.length; i++)
|
||||
opts.setProperty("Archive." + i, archives[i]);
|
||||
|
||||
BlogInfo info = new BlogInfo(pub, posters, opts);
|
||||
info.sign(_context, priv);
|
||||
|
||||
_archive.storeBlogInfo(info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public Archive getArchive() { return _archive; }
|
||||
|
||||
public List listMyBlogs() {
|
||||
File files[] = _privKeyDir.listFiles();
|
||||
List rv = new ArrayList();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isFile() && !files[i].isHidden()) {
|
||||
try {
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(new FileInputStream(files[i]));
|
||||
BlogInfo info = _archive.getBlogInfo(pub.calculateHash());
|
||||
if (info != null)
|
||||
rv.add(info);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public SigningPrivateKey getMyPrivateKey(BlogInfo blog) {
|
||||
if (blog == null) return null;
|
||||
File keyFile = new File(_privKeyDir, Base64.encode(blog.getKey().calculateHash().getData()) + ".priv");
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(keyFile);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(in);
|
||||
SigningPrivateKey priv = new SigningPrivateKey();
|
||||
priv.readBytes(in);
|
||||
return priv;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String login(User user, String login, String pass) {
|
||||
File userFile = new File(_userDir, Base64.encode(_context.sha().calculateHash(login.getBytes()).getData()));
|
||||
System.out.println("Attempting to login to " + login + " w/ pass = " + pass
|
||||
+ ": file = " + userFile.getAbsolutePath() + " passHash = "
|
||||
+ Base64.encode(_context.sha().calculateHash(pass.getBytes()).getData()));
|
||||
if (userFile.exists()) {
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
BufferedReader in = new BufferedReader(new FileReader(userFile));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf('=');
|
||||
if (split <= 0) continue;
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
props.setProperty(key.trim(), val.trim());
|
||||
}
|
||||
return user.login(login, pass, props);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "Error logging in - corrupt userfile";
|
||||
}
|
||||
} else {
|
||||
return "User does not exist";
|
||||
}
|
||||
}
|
||||
|
||||
/** hash of the password required to register and create a new blog (null means no password required) */
|
||||
public String getRegistrationPassword() {
|
||||
String pass = _context.getProperty("syndie.registrationPassword");
|
||||
if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
|
||||
return pass;
|
||||
}
|
||||
|
||||
public void saveUser(User user) {
|
||||
if (!user.getAuthenticated()) return;
|
||||
String userHash = Base64.encode(_context.sha().calculateHash(user.getUsername().getBytes()).getData());
|
||||
File userFile = new File(_userDir, userHash);
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(userFile);
|
||||
out.write("password=" + user.getHashedPassword() + "\n");
|
||||
out.write("blog=" + user.getBlog().toBase64() + "\n");
|
||||
out.write("lastid=" + user.getMostRecentEntry() + "\n");
|
||||
out.write("lastmetaedition=" + user.getLastMetaEntry() + "\n");
|
||||
out.write("lastlogin=" + user.getLastLogin() + "\n");
|
||||
out.write("addressbook=" + user.getAddressbookLocation() + "\n");
|
||||
out.write("showimages=" + user.getShowImages() + "\n");
|
||||
out.write("showexpanded=" + user.getShowExpanded() + "\n");
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("groups=");
|
||||
Map groups = user.getBlogGroups();
|
||||
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
List selectors = (List)groups.get(name);
|
||||
buf.append(name).append(':');
|
||||
for (int i = 0; i < selectors.size(); i++) {
|
||||
buf.append(selectors.get(i));
|
||||
if (i + 1 < selectors.size())
|
||||
buf.append(",");
|
||||
}
|
||||
if (iter.hasNext())
|
||||
buf.append(' ');
|
||||
}
|
||||
buf.append('\n');
|
||||
out.write(buf.toString());
|
||||
// shitlist=hash,hash,hash
|
||||
List shitlistedBlogs = user.getShitlistedBlogs();
|
||||
if (shitlistedBlogs.size() > 0) {
|
||||
buf.setLength(0);
|
||||
buf.append("shitlistedblogs=");
|
||||
for (int i = 0; i < shitlistedBlogs.size(); i++) {
|
||||
Hash blog = (Hash)shitlistedBlogs.get(i);
|
||||
buf.append(blog.toBase64());
|
||||
if (i + 1 < shitlistedBlogs.size())
|
||||
buf.append(',');
|
||||
}
|
||||
buf.append('\n');
|
||||
out.write(buf.toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe){}
|
||||
}
|
||||
}
|
||||
public String register(User user, String login, String password, String registrationPassword, String blogName, String blogDescription, String contactURL) {
|
||||
String hashedRegistrationPassword = getRegistrationPassword();
|
||||
if (hashedRegistrationPassword != null) {
|
||||
if (!hashedRegistrationPassword.equals(Base64.encode(_context.sha().calculateHash(registrationPassword.getBytes()).getData())))
|
||||
return "Invalid registration password";
|
||||
}
|
||||
String userHash = Base64.encode(_context.sha().calculateHash(login.getBytes()).getData());
|
||||
File userFile = new File(_userDir, userHash);
|
||||
if (userFile.exists()) {
|
||||
return "Cannot register the login " + login + ": it already exists";
|
||||
} else {
|
||||
BlogInfo info = createBlog(blogName, blogDescription, contactURL, null);
|
||||
String hashedPassword = Base64.encode(_context.sha().calculateHash(password.getBytes()).getData());
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(userFile);
|
||||
out.write("password=" + hashedPassword + "\n");
|
||||
out.write("blog=" + Base64.encode(info.getKey().calculateHash().getData()) + "\n");
|
||||
out.write("lastid=-1\n");
|
||||
out.write("lastmetaedition=0\n");
|
||||
out.write("addressbook=userhosts-"+userHash + ".txt\n");
|
||||
out.write("showimages=false\n");
|
||||
out.write("showexpanded=false\n");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "Internal error registering - " + ioe.getMessage();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
String loginResult = login(user, login, password);
|
||||
_archive.regenerateIndex();
|
||||
return loginResult;
|
||||
}
|
||||
}
|
||||
|
||||
public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml) {
|
||||
return createBlogEntry(user, subject, tags, entryHeaders, sml, null, null, null);
|
||||
}
|
||||
public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml, List fileNames, List fileStreams, List fileTypes) {
|
||||
if (!user.getAuthenticated()) return null;
|
||||
BlogInfo info = getArchive().getBlogInfo(user.getBlog());
|
||||
if (info == null) return null;
|
||||
SigningPrivateKey privkey = getMyPrivateKey(info);
|
||||
if (privkey == null) return null;
|
||||
|
||||
long entryId = -1;
|
||||
long now = _context.clock().now();
|
||||
long dayBegin = getDayBegin(now);
|
||||
if (user.getMostRecentEntry() >= dayBegin)
|
||||
entryId = user.getMostRecentEntry() + 1;
|
||||
else
|
||||
entryId = dayBegin;
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(tags, " ,\n\t");
|
||||
String tagList[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < tagList.length; i++)
|
||||
tagList[i] = tok.nextToken().trim();
|
||||
|
||||
BlogURI uri = new BlogURI(user.getBlog(), entryId);
|
||||
|
||||
try {
|
||||
StringBuffer raw = new StringBuffer(sml.length() + 128);
|
||||
raw.append("Subject: ").append(subject).append('\n');
|
||||
raw.append("Tags: ");
|
||||
for (int i = 0; i < tagList.length; i++)
|
||||
raw.append(tagList[i]).append('\t');
|
||||
raw.append('\n');
|
||||
if ( (entryHeaders != null) && (entryHeaders.trim().length() > 0) ) {
|
||||
System.out.println("Entry headers: " + entryHeaders);
|
||||
BufferedReader userHeaders = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(entryHeaders.getBytes())));
|
||||
String line = null;
|
||||
while ( (line = userHeaders.readLine()) != null) {
|
||||
line = line.trim();
|
||||
System.out.println("Line: " + line);
|
||||
if (line.length() <= 0) continue;
|
||||
int split = line.indexOf('=');
|
||||
int split2 = line.indexOf(':');
|
||||
if ( (split < 0) || ( (split2 > 0) && (split2 < split) ) ) split = split2;
|
||||
String key = line.substring(0,split).trim();
|
||||
String val = line.substring(split+1).trim();
|
||||
raw.append(key).append(": ").append(val).append('\n');
|
||||
}
|
||||
}
|
||||
raw.append('\n');
|
||||
raw.append(sml);
|
||||
|
||||
EntryContainer c = new EntryContainer(uri, tagList, raw.toString().getBytes());
|
||||
if ((fileNames != null) && (fileStreams != null) && (fileNames.size() == fileStreams.size()) ) {
|
||||
for (int i = 0; i < fileNames.size(); i++) {
|
||||
String name = (String)fileNames.get(i);
|
||||
InputStream in = (InputStream)fileStreams.get(i);
|
||||
String fileType = (fileTypes != null ? (String)fileTypes.get(i) : "application/octet-stream");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1) break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
byte att[] = baos.toByteArray();
|
||||
if ( (att != null) && (att.length > 0) )
|
||||
c.addAttachment(att, new File(name).getName(), null, fileType);
|
||||
}
|
||||
}
|
||||
//for (int i = 7; i < args.length; i++) {
|
||||
// c.addAttachment(read(args[i]), new File(args[i]).getName(),
|
||||
// "Attached file", "application/octet-stream");
|
||||
//}
|
||||
SessionKey entryKey = null;
|
||||
//if (!"NONE".equals(args[5]))
|
||||
// entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
c.seal(_context, privkey, null);
|
||||
boolean ok = getArchive().storeEntry(c);
|
||||
if (ok) {
|
||||
getArchive().regenerateIndex();
|
||||
user.setMostRecentEntry(entryId);
|
||||
saveUser(user);
|
||||
return uri;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String addAddress(User user, String name, String location, String schema) {
|
||||
if (!user.getAuthenticated()) return "Not logged in";
|
||||
boolean ok = validateAddressName(name);
|
||||
if (!ok) return "Invalid name: " + HTMLRenderer.sanitizeString(name);
|
||||
ok = validateAddressLocation(location);
|
||||
if (!ok) return "Invalid location: " + HTMLRenderer.sanitizeString(location);
|
||||
if (!validateAddressSchema(schema)) return "Unsupported schema: " + HTMLRenderer.sanitizeString(schema);
|
||||
// no need to quote user/location further, as they've been sanitized
|
||||
|
||||
FileWriter out = null;
|
||||
try {
|
||||
File userHostsFile = new File(user.getAddressbookLocation());
|
||||
Properties knownHosts = getKnownHosts(user, true);
|
||||
if (knownHosts.containsKey(name)) return "Name is already in use";
|
||||
|
||||
out = new FileWriter(userHostsFile, true);
|
||||
out.write(name + "=" + location + '\n');
|
||||
return "Address " + name + " written to your hosts file (" + userHostsFile.getName() + ")";
|
||||
} catch (IOException ioe) {
|
||||
return "Error writing out host entry: " + ioe.getMessage();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getKnownHosts(User user, boolean includePublic) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
if ( (user != null) && (user.getAuthenticated()) ) {
|
||||
File userHostsFile = new File(user.getAddressbookLocation());
|
||||
rv.putAll(getKnownHosts(userHostsFile));
|
||||
}
|
||||
if (includePublic) {
|
||||
rv.putAll(getKnownHosts(new File("hosts.txt")));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
private Properties getKnownHosts(File filename) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
if (filename.exists()) {
|
||||
rv.load(new FileInputStream(filename));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private boolean validateAddressName(String name) {
|
||||
if ( (name == null) || (name.trim().length() <= 0) || (!name.endsWith(".i2p")) ) return false;
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!Character.isLetterOrDigit(c) && ('.' != c) && ('-' != c) && ('_' != c) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateAddressLocation(String location) {
|
||||
if ( (location == null) || (location.trim().length() <= 0) ) return false;
|
||||
try {
|
||||
Destination d = new Destination(location);
|
||||
return (d.getPublicKey() != null);
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateAddressSchema(String schema) {
|
||||
if ( (schema == null) || (schema.trim().length() <= 0) ) return false;
|
||||
return "eep".equals(schema) || "i2p".equals(schema);
|
||||
}
|
||||
|
||||
private final GregorianCalendar _cal = new GregorianCalendar();
|
||||
private long getDayBegin(long now) {
|
||||
synchronized (_cal) {
|
||||
_cal.setTimeInMillis(now);
|
||||
_cal.set(Calendar.MILLISECOND, 0);
|
||||
_cal.set(Calendar.SECOND, 0);
|
||||
_cal.set(Calendar.MINUTE, 0);
|
||||
_cal.set(Calendar.HOUR, 0);
|
||||
_cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
return _cal.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
}
|
188
apps/syndie/java/src/net/i2p/syndie/CLI.java
Normal file
188
apps/syndie/java/src/net/i2p/syndie/CLI.java
Normal file
@ -0,0 +1,188 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CLI {
|
||||
public static final String USAGE = "Usage: \n" +
|
||||
"rootDir regenerateIndex\n" +
|
||||
"rootDir createBlog name description contactURL[ archiveURL]*\n" +
|
||||
"rootDir createEntry blogPublicKeyHash tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
|
||||
"rootDir listMyBlogs\n" +
|
||||
"rootDir listTags blogPublicKeyHash\n" +
|
||||
"rootDir listEntries blogPublicKeyHash blogTag\n" +
|
||||
"rootDir renderEntry blogPublicKeyHash entryId (NONE|entryKeyBase64) summaryOnly includeImages\n";
|
||||
|
||||
public static void main(String args[]) {
|
||||
//args = new String[] { "~/.syndie/", "listEntries", "9qXCJUyUBCCaiIShURo02ckxjrMvrtiDYENv2ATL3-Y=", "/" };
|
||||
//args = new String[] { "~/.syndie/", "renderEntry", "Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=", "/", "20050811001", "NONE", "true", "false" };
|
||||
if (args.length < 2) {
|
||||
System.err.print(USAGE);
|
||||
return;
|
||||
}
|
||||
String command = args[1];
|
||||
if ("createBlog".equals(command))
|
||||
createBlog(args);
|
||||
else if ("listMyBlogs".equals(command))
|
||||
listMyBlogs(args);
|
||||
else if ("createEntry".equals(command))
|
||||
createEntry(args);
|
||||
else if ("listTags".equals(command))
|
||||
listPaths(args);
|
||||
else if ("listEntries".equals(command))
|
||||
listEntries(args);
|
||||
else if ("regenerateIndex".equals(command))
|
||||
regenerateIndex(args);
|
||||
else if ("renderEntry".equals(command))
|
||||
renderEntry(args);
|
||||
else
|
||||
System.out.print(USAGE);
|
||||
}
|
||||
|
||||
private static void createBlog(String args[]) {
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
String archives[] = new String[args.length - 5];
|
||||
System.arraycopy(args, 5, archives, 0, archives.length);
|
||||
BlogInfo info = mgr.createBlog(args[2], args[3], args[4], archives);
|
||||
System.out.println("Blog created: " + info);
|
||||
mgr.getArchive().regenerateIndex();
|
||||
}
|
||||
private static void listMyBlogs(String args[]) {
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List info = mgr.listMyBlogs();
|
||||
for (int i = 0; i < info.size(); i++)
|
||||
System.out.println(info.get(i).toString());
|
||||
}
|
||||
|
||||
private static void listPaths(String args[]) {
|
||||
// "rootDir listTags blogPublicKeyHash\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List tags = mgr.getArchive().listTags(new Hash(Base64.decode(args[2])));
|
||||
System.out.println("tag count: " + tags.size());
|
||||
for (int i = 0; i < tags.size(); i++)
|
||||
System.out.println("Tag " + i + ": " + tags.get(i).toString());
|
||||
}
|
||||
|
||||
private static void regenerateIndex(String args[]) {
|
||||
// "rootDir regenerateIndex\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
mgr.getArchive().regenerateIndex();
|
||||
System.out.println("Index regenerated");
|
||||
}
|
||||
|
||||
private static void listEntries(String args[]) {
|
||||
// "rootDir listEntries blogPublicKeyHash tag\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
List entries = mgr.getArchive().listEntries(new Hash(Base64.decode(args[2])), -1, args[3], null);
|
||||
System.out.println("Entry count: " + entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
EntryContainer entry = (EntryContainer)entries.get(i);
|
||||
System.out.println("***************************************************");
|
||||
System.out.println("Entry " + i + ": " + entry.getURI().toString());
|
||||
System.out.println("===================================================");
|
||||
System.out.println(entry.getEntry().getText());
|
||||
System.out.println("===================================================");
|
||||
Attachment attachments[] = entry.getAttachments();
|
||||
for (int j = 0; j < attachments.length; j++) {
|
||||
System.out.println("Attachment " + j + ": " + attachments[j]);
|
||||
}
|
||||
System.out.println("===================================================");
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderEntry(String args[]) {
|
||||
//"rootDir renderEntry blogPublicKeyHash entryId (NONE|entryKeyBase64) summaryOnly includeImages\n";
|
||||
BlogManager mgr = new BlogManager(I2PAppContext.getGlobalContext(), args[0]);
|
||||
long id = -1;
|
||||
try {
|
||||
id = Long.parseLong(args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return;
|
||||
}
|
||||
SessionKey entryKey = null;
|
||||
if (!("NONE".equals(args[4])))
|
||||
entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
EntryContainer entry = mgr.getArchive().getEntry(new BlogURI(new Hash(Base64.decode(args[2])), id), entryKey);
|
||||
if (entry != null) {
|
||||
HTMLRenderer renderer = new HTMLRenderer();
|
||||
boolean summaryOnly = "true".equalsIgnoreCase(args[5]);
|
||||
boolean showImages = "true".equalsIgnoreCase(args[6]);
|
||||
try {
|
||||
File f = File.createTempFile("syndie", ".html");
|
||||
Writer out = new FileWriter(f);
|
||||
renderer.render(null, mgr.getArchive(), entry, out, summaryOnly, showImages);
|
||||
out.flush();
|
||||
out.close();
|
||||
System.out.println("Rendered to " + f.getAbsolutePath() + ": " + f.length());
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.err.println("Entry does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private static void createEntry(String args[]) {
|
||||
// "rootDir createEntry blogPublicKey tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
BlogManager mgr = new BlogManager(ctx, args[0]);
|
||||
long entryId = -1;
|
||||
if ("NOW".equals(args[4])) {
|
||||
entryId = ctx.clock().now();
|
||||
} else {
|
||||
try {
|
||||
entryId = Long.parseLong(args[4]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringTokenizer tok = new StringTokenizer(args[3], ",");
|
||||
String tags[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < tags.length; i++)
|
||||
tags[i] = tok.nextToken();
|
||||
BlogURI uri = new BlogURI(new Hash(Base64.decode(args[2])), entryId);
|
||||
BlogInfo blog = mgr.getArchive().getBlogInfo(uri);
|
||||
if (blog == null) {
|
||||
System.err.println("Blog does not exist: " + uri);
|
||||
return;
|
||||
}
|
||||
SigningPrivateKey key = mgr.getMyPrivateKey(blog);
|
||||
|
||||
try {
|
||||
byte smlData[] = read(args[6]);
|
||||
EntryContainer c = new EntryContainer(uri, tags, smlData);
|
||||
for (int i = 7; i < args.length; i++) {
|
||||
c.addAttachment(read(args[i]), new File(args[i]).getName(),
|
||||
"Attached file", "application/octet-stream");
|
||||
}
|
||||
SessionKey entryKey = null;
|
||||
if (!"NONE".equals(args[5]))
|
||||
entryKey = new SessionKey(Base64.decode(args[5]));
|
||||
c.seal(ctx, key, entryKey);
|
||||
boolean ok = mgr.getArchive().storeEntry(c);
|
||||
System.out.println("Blog entry created: " + c+ "? " + ok);
|
||||
if (ok)
|
||||
mgr.getArchive().regenerateIndex();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] read(String file) throws IOException {
|
||||
File f = new File(file);
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
byte rv[] = new byte[(int)f.length()];
|
||||
if (rv.length != DataHelper.read(in, rv))
|
||||
throw new IOException("File not read completely");
|
||||
return rv;
|
||||
}
|
||||
}
|
237
apps/syndie/java/src/net/i2p/syndie/CachedEntry.java
Normal file
237
apps/syndie/java/src/net/i2p/syndie/CachedEntry.java
Normal file
@ -0,0 +1,237 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Lazy loading wrapper for an entry, pulling data out of a cached & extracted dir,
|
||||
* rather than dealing with the crypto, zip, etc.
|
||||
*
|
||||
*/
|
||||
class CachedEntry extends EntryContainer {
|
||||
private File _entryDir;
|
||||
|
||||
private int _format;
|
||||
private int _size;
|
||||
private BlogURI _blog;
|
||||
private Properties _headers;
|
||||
private Entry _entry;
|
||||
private Attachment _attachments[];
|
||||
|
||||
public CachedEntry(File entryDir) {
|
||||
_entryDir = entryDir;
|
||||
importMeta();
|
||||
_entry = new CachedEntryDetails();
|
||||
_attachments = null;
|
||||
}
|
||||
|
||||
// always available, loaded from meta
|
||||
public int getFormat() { return _format; }
|
||||
public BlogURI getURI() { return _blog; }
|
||||
public int getCompleteSize() { return _size; }
|
||||
|
||||
// dont need to override it, as it works off getHeader
|
||||
//public String[] getTags() { return super.getTags(); }
|
||||
|
||||
public Entry getEntry() { return _entry; }
|
||||
public Attachment[] getAttachments() {
|
||||
importAttachments();
|
||||
return _attachments;
|
||||
}
|
||||
public String getHeader(String key) {
|
||||
importHeaders();
|
||||
return _headers.getProperty(key);
|
||||
}
|
||||
|
||||
public String toString() { return getURI().toString(); }
|
||||
public boolean verifySignature(I2PAppContext ctx, BlogInfo info) { return true; }
|
||||
|
||||
// not supported...
|
||||
public void parseRawData(I2PAppContext ctx) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void parseRawData(I2PAppContext ctx, SessionKey zipKey) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void setHeader(String name, String val) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void addAttachment(byte data[], String name, String description, String mimeType) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public void write(OutputStream out, boolean includeRealSignature) throws IOException {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public Signature getSignature() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
// now the actual lazy loading code
|
||||
private void importMeta() {
|
||||
Properties meta = readProps(new File(_entryDir, EntryExtractor.META));
|
||||
_format = getInt(meta, "format");
|
||||
_size = getInt(meta, "size");
|
||||
_blog = new BlogURI(new Hash(Base64.decode(meta.getProperty("blog"))), getLong(meta, "entry"));
|
||||
}
|
||||
|
||||
private Properties importHeaders() {
|
||||
if (_headers == null)
|
||||
_headers = readProps(new File(_entryDir, EntryExtractor.HEADERS));
|
||||
return _headers;
|
||||
}
|
||||
|
||||
private void importAttachments() {
|
||||
if (_attachments == null) {
|
||||
List attachments = new ArrayList();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
File meta = new File(_entryDir, EntryExtractor.ATTACHMENT_PREFIX + i + EntryExtractor.ATTACHMENT_META_SUFFIX);
|
||||
if (meta.exists())
|
||||
attachments.add(new CachedAttachment(i, meta));
|
||||
else
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
Attachment a[] = new Attachment[attachments.size()];
|
||||
for (i = 0; i < a.length; i++)
|
||||
a[i] = (Attachment)attachments.get(i);
|
||||
_attachments = a;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static Properties readProps(File propsFile) {
|
||||
Properties rv = new Properties();
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new FileReader(propsFile));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf('=');
|
||||
if ( (split <= 0) || (split >= line.length()) ) continue;
|
||||
rv.setProperty(line.substring(0, split).trim(), line.substring(split+1).trim());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final int getInt(Properties props, String key) {
|
||||
String val = props.getProperty(key);
|
||||
try { return Integer.parseInt(val); } catch (NumberFormatException nfe) {}
|
||||
return -1;
|
||||
}
|
||||
private static final long getLong(Properties props, String key) {
|
||||
String val = props.getProperty(key);
|
||||
try { return Long.parseLong(val); } catch (NumberFormatException nfe) {}
|
||||
return -1l;
|
||||
}
|
||||
|
||||
|
||||
private class CachedEntryDetails extends Entry {
|
||||
private String _text;
|
||||
public CachedEntryDetails() {
|
||||
super(null);
|
||||
}
|
||||
public String getText() {
|
||||
importText();
|
||||
return _text;
|
||||
}
|
||||
private void importText() {
|
||||
if (_text == null) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
File f = new File(_entryDir, EntryExtractor.ENTRY);
|
||||
byte buf[] = new byte[(int)f.length()]; // hmm
|
||||
in = new FileInputStream(f);
|
||||
int read = DataHelper.read(in, buf);
|
||||
if (read != buf.length) throw new IOException("read: " + read + " file size: " + buf.length + " for " + f.getPath());
|
||||
_text = new String(buf);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CachedAttachment extends Attachment {
|
||||
private int _attachmentId;
|
||||
private File _metaFile;
|
||||
private Properties _attachmentHeaders;
|
||||
private int _dataSize;
|
||||
|
||||
public CachedAttachment(int id, File meta) {
|
||||
super(null, null);
|
||||
_attachmentId = id;
|
||||
_metaFile = meta;
|
||||
_attachmentHeaders = null;
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
importAttachmentHeaders();
|
||||
return _dataSize;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
public InputStream getDataStream() throws IOException {
|
||||
String name = EntryExtractor.ATTACHMENT_PREFIX + _attachmentId + EntryExtractor.ATTACHMENT_DATA_SUFFIX;
|
||||
File f = new File(_entryDir, name);
|
||||
return new FileInputStream(f);
|
||||
}
|
||||
|
||||
public byte[] getRawMetadata() {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
public String getMeta(String key) {
|
||||
importAttachmentHeaders();
|
||||
return _attachmentHeaders.getProperty(key);
|
||||
}
|
||||
|
||||
//public String getName() { return getMeta(NAME); }
|
||||
//public String getDescription() { return getMeta(DESCRIPTION); }
|
||||
//public String getMimeType() { return getMeta(MIMETYPE); }
|
||||
|
||||
public void setMeta(String key, String val) {
|
||||
throw new IllegalStateException("Not supported on cached entries");
|
||||
}
|
||||
|
||||
public Map getMeta() {
|
||||
importAttachmentHeaders();
|
||||
return _attachmentHeaders;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
importAttachmentHeaders();
|
||||
int len = _dataSize;
|
||||
return getName()
|
||||
+ (getDescription() != null ? ": " + getDescription() : "")
|
||||
+ (getMimeType() != null ? ", type: " + getMimeType() : "")
|
||||
+ ", size: " + len;
|
||||
}
|
||||
|
||||
private void importAttachmentHeaders() {
|
||||
if (_attachmentHeaders == null) {
|
||||
Properties props = readProps(_metaFile);
|
||||
String sz = (String)props.remove(EntryExtractor.ATTACHMENT_DATA_SIZE);
|
||||
if (sz != null) {
|
||||
try {
|
||||
_dataSize = Integer.parseInt(sz);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
_attachmentHeaders = props;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
132
apps/syndie/java/src/net/i2p/syndie/EntryExtractor.java
Normal file
132
apps/syndie/java/src/net/i2p/syndie/EntryExtractor.java
Normal file
@ -0,0 +1,132 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* To cut down on unnecessary IO/cpu load, extract entries onto the disk for
|
||||
* faster access later. Individual entries are stored in subdirectories based on
|
||||
* their name - $archiveDir/$blogDir/$entryId.snd extracts its files into various
|
||||
* files under $cacheDir/$blogDir/$entryId/:
|
||||
* headers.txt: name=value pairs for attributes of the entry container itself
|
||||
* info.txt: name=value pairs for implicit attributes of the container (blog, id, format, size)
|
||||
* entry.sml: raw sml file
|
||||
* attachmentN_data.dat: raw binary data for attachment N
|
||||
* attachmentN_meta.dat: name=value pairs for attributes of attachment N
|
||||
*
|
||||
*/
|
||||
public class EntryExtractor {
|
||||
private I2PAppContext _context;
|
||||
|
||||
static final String HEADERS = "headers.txt";
|
||||
static final String META = "meta.txt";
|
||||
static final String ENTRY = "entry.sml";
|
||||
static final String ATTACHMENT_PREFIX = "attachment";
|
||||
static final String ATTACHMENT_DATA_SUFFIX = "_data.dat";
|
||||
static final String ATTACHMENT_META_SUFFIX = "_meta.txt";
|
||||
static final String ATTACHMENT_DATA_SIZE = "EntryExtractor__dataSize";
|
||||
|
||||
public EntryExtractor(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public boolean extract(File entryFile, File entryDir, SessionKey entryKey, BlogInfo info) throws IOException {
|
||||
EntryContainer entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entryFile));
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
return false;
|
||||
} else {
|
||||
entry.setCompleteSize((int)entryFile.length());
|
||||
if (entryKey != null)
|
||||
entry.parseRawData(_context, entryKey);
|
||||
else
|
||||
entry.parseRawData(_context);
|
||||
extract(entry, entryDir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void extract(EntryContainer entry, File entryDir) throws IOException {
|
||||
extractHeaders(entry, entryDir);
|
||||
extractMeta(entry, entryDir);
|
||||
extractEntry(entry, entryDir);
|
||||
Attachment attachments[] = entry.getAttachments();
|
||||
if (attachments != null) {
|
||||
for (int i = 0; i < attachments.length; i++) {
|
||||
extractAttachmentData(i, attachments[i], entryDir);
|
||||
extractAttachmentMetadata(i, attachments[i], entryDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void extractHeaders(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(new File(entryDir, HEADERS));
|
||||
Map headers = entry.getHeaders();
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = (String)headers.get(k);
|
||||
out.write(k.trim() + '=' + v.trim() + '\n');
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractMeta(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(new File(entryDir, META));
|
||||
out.write("format=" + entry.getFormat() + '\n');
|
||||
out.write("size=" + entry.getCompleteSize() + '\n');
|
||||
out.write("blog=" + entry.getURI().getKeyHash().toBase64() + '\n');
|
||||
out.write("entry=" + entry.getURI().getEntryId() + '\n');
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractEntry(EntryContainer entry, File entryDir) throws IOException {
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(new File(entryDir, ENTRY));
|
||||
out.write(entry.getEntry().getText());
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractAttachmentData(int num, Attachment attachment, File entryDir) throws IOException {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_DATA_SUFFIX));
|
||||
//out.write(attachment.getData());
|
||||
InputStream data = attachment.getDataStream();
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = data.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
data.close();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
private void extractAttachmentMetadata(int num, Attachment attachment, File entryDir) throws IOException {
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(new File(entryDir, ATTACHMENT_PREFIX + num + ATTACHMENT_META_SUFFIX));
|
||||
Map meta = attachment.getMeta();
|
||||
for (Iterator iter = meta.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = (String)meta.get(k);
|
||||
out.write(k + '=' + v + '\n');
|
||||
}
|
||||
out.write(ATTACHMENT_DATA_SIZE + '=' + attachment.getDataLength());
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
145
apps/syndie/java/src/net/i2p/syndie/User.java
Normal file
145
apps/syndie/java/src/net/i2p/syndie/User.java
Normal file
@ -0,0 +1,145 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* User session state and preferences.
|
||||
*
|
||||
*/
|
||||
public class User {
|
||||
private I2PAppContext _context;
|
||||
private String _username;
|
||||
private String _hashedPassword;
|
||||
private Hash _blog;
|
||||
private long _mostRecentEntry;
|
||||
/** Group name to List of blog selectors, where the selectors are of the form
|
||||
* blog://$key, entry://$key/$entryId, blogtag://$key/$tag, tag://$tag
|
||||
*/
|
||||
private Map _blogGroups;
|
||||
/** list of blogs (Hash) we never want to see entries from */
|
||||
private List _shitlistedBlogs;
|
||||
/** where our userhosts.txt is */
|
||||
private String _addressbookLocation;
|
||||
private boolean _showImagesByDefault;
|
||||
private boolean _showExpandedByDefault;
|
||||
private long _lastLogin;
|
||||
private long _lastMetaEntry;
|
||||
private boolean _authenticated;
|
||||
|
||||
public User() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
init();
|
||||
}
|
||||
private void init() {
|
||||
_authenticated = false;
|
||||
_username = null;
|
||||
_hashedPassword = null;
|
||||
_blog = null;
|
||||
_mostRecentEntry = -1;
|
||||
_blogGroups = new HashMap();
|
||||
_shitlistedBlogs = new ArrayList();
|
||||
_addressbookLocation = "userhosts.txt";
|
||||
_showImagesByDefault = false;
|
||||
_showExpandedByDefault = false;
|
||||
_lastLogin = -1;
|
||||
_lastMetaEntry = 0;
|
||||
}
|
||||
|
||||
public boolean getAuthenticated() { return _authenticated; }
|
||||
public String getUsername() { return _username; }
|
||||
public Hash getBlog() { return _blog; }
|
||||
public String getBlogStr() { return Base64.encode(_blog.getData()); }
|
||||
public long getMostRecentEntry() { return _mostRecentEntry; }
|
||||
public Map getBlogGroups() { return _blogGroups; }
|
||||
public List getShitlistedBlogs() { return _shitlistedBlogs; }
|
||||
public String getAddressbookLocation() { return _addressbookLocation; }
|
||||
public boolean getShowImages() { return _showImagesByDefault; }
|
||||
public boolean getShowExpanded() { return _showExpandedByDefault; }
|
||||
public long getLastLogin() { return _lastLogin; }
|
||||
public String getHashedPassword() { return _hashedPassword; }
|
||||
public long getLastMetaEntry() { return _lastMetaEntry; }
|
||||
|
||||
public void setMostRecentEntry(long id) { _mostRecentEntry = id; }
|
||||
public void setLastMetaEntry(long id) { _lastMetaEntry = id; }
|
||||
|
||||
public void invalidate() {
|
||||
BlogManager.instance().saveUser(this);
|
||||
init();
|
||||
}
|
||||
|
||||
public String login(String login, String pass, Properties props) {
|
||||
String expectedPass = props.getProperty("password");
|
||||
String hpass = Base64.encode(_context.sha().calculateHash(pass.getBytes()).getData());
|
||||
if (!hpass.equals(expectedPass)) {
|
||||
_authenticated = false;
|
||||
return "Incorrect password";
|
||||
}
|
||||
|
||||
_username = login;
|
||||
_hashedPassword = expectedPass;
|
||||
|
||||
// blog=luS9d3uaf....HwAE=
|
||||
String b = props.getProperty("blog");
|
||||
if (b != null) _blog = new Hash(Base64.decode(b));
|
||||
// lastid=12345
|
||||
String id = props.getProperty("lastid");
|
||||
if (id != null) try { _mostRecentEntry = Long.parseLong(id); } catch (NumberFormatException nfe) {}
|
||||
// lastmetaedition=12345
|
||||
id = props.getProperty("lastmetaedition");
|
||||
if (id != null) try { _lastMetaEntry = Long.parseLong(id); } catch (NumberFormatException nfe) {}
|
||||
// groups=abc:selector,selector,selector,selector def:selector,selector,selector
|
||||
StringTokenizer tok = new StringTokenizer(props.getProperty("groups", ""), " ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String group = tok.nextToken();
|
||||
int endName = group.indexOf(':');
|
||||
if (endName <= 0)
|
||||
continue;
|
||||
String groupName = group.substring(0, endName);
|
||||
String sel = group.substring(endName+1);
|
||||
List selectors = new ArrayList();
|
||||
while ( (sel != null) && (sel.length() > 0) ) {
|
||||
int end = sel.indexOf(',');
|
||||
if (end < 0) {
|
||||
selectors.add(sel);
|
||||
sel = null;
|
||||
} else {
|
||||
if (end + 1 >= sel.length()) {
|
||||
selectors.add(sel.substring(0,end));
|
||||
sel = null;
|
||||
} else if (end == 0) {
|
||||
sel = sel.substring(1);
|
||||
} else {
|
||||
selectors.add(sel.substring(0, end));
|
||||
sel = sel.substring(end+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_blogGroups.put(groupName.trim(), selectors);
|
||||
}
|
||||
// shitlist=hash,hash,hash
|
||||
tok = new StringTokenizer(props.getProperty("shitlistedblogs", ""), ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String blog = tok.nextToken();
|
||||
byte bl[] = Base64.decode(blog);
|
||||
if ( (bl != null) && (bl.length == Hash.HASH_LENGTH) )
|
||||
_shitlistedBlogs.add(new Hash(bl));
|
||||
}
|
||||
|
||||
String addr = props.getProperty("addressbook", "userhosts.txt");
|
||||
if (addr != null)
|
||||
_addressbookLocation = addr;
|
||||
|
||||
String show = props.getProperty("showimages", "false");
|
||||
_showImagesByDefault = (show != null) && (show.equals("true"));
|
||||
show = props.getProperty("showexpanded", "false");
|
||||
_showExpandedByDefault = (show != null) && (show.equals("true"));
|
||||
|
||||
_lastLogin = _context.clock().now();
|
||||
_authenticated = true;
|
||||
return LOGIN_OK;
|
||||
}
|
||||
|
||||
public static final String LOGIN_OK = "Logged in";
|
||||
}
|
11
apps/syndie/java/src/net/i2p/syndie/Version.java
Normal file
11
apps/syndie/java/src/net/i2p/syndie/Version.java
Normal file
@ -0,0 +1,11 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Version {
|
||||
public static final String VERSION = "0-alpha";
|
||||
public static final String BUILD = "0";
|
||||
public static final String INDEX_VERSION = "1.0";
|
||||
public static final String ID = "$Id$";
|
||||
}
|
374
apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java
Normal file
374
apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java
Normal file
@ -0,0 +1,374 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.Archive;
|
||||
import net.i2p.syndie.BlogManager;
|
||||
|
||||
/**
|
||||
* Simple read-only summary of an archive
|
||||
*/
|
||||
public class ArchiveIndex {
|
||||
protected String _version;
|
||||
protected long _generatedOn;
|
||||
protected int _allBlogs;
|
||||
protected int _newBlogs;
|
||||
protected int _allEntries;
|
||||
protected int _newEntries;
|
||||
protected long _totalSize;
|
||||
protected long _newSize;
|
||||
/** list of BlogSummary objects */
|
||||
protected List _blogs;
|
||||
/** list of Hash objects */
|
||||
protected List _newestBlogs;
|
||||
/** list of BlogURI objects */
|
||||
protected List _newestEntries;
|
||||
protected Properties _headers;
|
||||
|
||||
public ArchiveIndex() {
|
||||
this(false); //true);
|
||||
}
|
||||
public ArchiveIndex(boolean shouldLoad) {
|
||||
_blogs = new ArrayList();
|
||||
_newestBlogs = new ArrayList();
|
||||
_newestEntries = new ArrayList();
|
||||
_headers = new Properties();
|
||||
_generatedOn = -1;
|
||||
if (shouldLoad)
|
||||
setIsLocal("true");
|
||||
}
|
||||
|
||||
public String getVersion() { return _version; }
|
||||
public Properties getHeaders() { return _headers; }
|
||||
public int getAllBlogs() { return _allBlogs; }
|
||||
public int getNewBlogs() { return _newBlogs; }
|
||||
public int getAllEntries() { return _allEntries; }
|
||||
public int getNewEntries() { return _newEntries; }
|
||||
public long getTotalSize() { return _totalSize; }
|
||||
public long getNewSize() { return _newSize; }
|
||||
public long getGeneratedOn() { return _generatedOn; }
|
||||
|
||||
public String getNewSizeStr() {
|
||||
if (_newSize < 1024) return _newSize + "";
|
||||
if (_newSize < 1024*1024) return _newSize/1024 + "KB";
|
||||
else return _newSize/(1024*1024) + "MB";
|
||||
}
|
||||
public String getTotalSizeStr() {
|
||||
if (_totalSize < 1024) return _totalSize + "";
|
||||
if (_totalSize < 1024*1024) return _totalSize/1024 + "KB";
|
||||
else return _totalSize/(1024*1024) + "MB";
|
||||
}
|
||||
|
||||
/** how many blogs/tags are indexed */
|
||||
public int getIndexBlogs() { return _blogs.size(); }
|
||||
/** get the blog used for the given blog/tag pair */
|
||||
public Hash getBlog(int index) { return ((BlogSummary)_blogs.get(index)).blog; }
|
||||
/** get the tag used for the given blog/tag pair */
|
||||
public String getBlogTag(int index) { return ((BlogSummary)_blogs.get(index)).tag; }
|
||||
/** get the highest entry ID for the given blog/tag pair */
|
||||
public long getBlogLastUpdated(int index) { return ((BlogSummary)_blogs.get(index)).lastUpdated; }
|
||||
/** get the entry count for the given blog/tag pair */
|
||||
public int getBlogEntryCount(int index) { return ((BlogSummary)_blogs.get(index)).entries.size(); }
|
||||
/** get the entry from the given blog/tag pair */
|
||||
public BlogURI getBlogEntry(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).entry; }
|
||||
/** get the raw entry size (including attachments) from the given blog/tag pair */
|
||||
public long getBlogEntrySizeKB(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).size; }
|
||||
|
||||
/** how many 'new' blogs are listed */
|
||||
public int getNewestBlogCount() { return _newestBlogs.size(); }
|
||||
public Hash getNewestBlog(int index) { return (Hash)_newestBlogs.get(index); }
|
||||
/** how many 'new' entries are listed */
|
||||
public int getNewestBlogEntryCount() { return _newestEntries.size(); }
|
||||
public BlogURI getNewestBlogEntry(int index) { return (BlogURI)_newestEntries.get(index); }
|
||||
|
||||
/** list of locally known tags (String) under the given blog */
|
||||
public List getBlogTags(Hash blog) {
|
||||
List rv = new ArrayList();
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
if (getBlog(i).equals(blog))
|
||||
rv.add(getBlogTag(i));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
/** list of unique blogs locally known (set of Hash) */
|
||||
public Set getUniqueBlogs() {
|
||||
Set rv = new HashSet();
|
||||
for (int i = 0; i < _blogs.size(); i++)
|
||||
rv.add(getBlog(i));
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
try {
|
||||
File l = new File(location);
|
||||
if (l.exists())
|
||||
load(l);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
public void setIsLocal(String val) {
|
||||
if ("true".equals(val)) {
|
||||
try {
|
||||
File dir = BlogManager.instance().getArchive().getArchiveDir();
|
||||
load(new File(dir, Archive.INDEX_FILE));
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File location) throws IOException {
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(location);
|
||||
load(in);
|
||||
} finally {
|
||||
if (in != null)
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** load up the index from an archive.txt */
|
||||
public void load(InputStream index) throws IOException {
|
||||
_allBlogs = 0;
|
||||
_allEntries = 0;
|
||||
_newBlogs = 0;
|
||||
_newEntries = 0;
|
||||
_newSize = 0;
|
||||
_totalSize = 0;
|
||||
_version = null;
|
||||
_blogs = new ArrayList();
|
||||
_newestBlogs = new ArrayList();
|
||||
_newestEntries = new ArrayList();
|
||||
_headers = new Properties();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(index));
|
||||
String line = null;
|
||||
line = in.readLine();
|
||||
if (line == null)
|
||||
return;
|
||||
if (!line.startsWith("SyndieVersion:"))
|
||||
throw new IOException("Index is invalid - it starts with " + line);
|
||||
_version = line.substring("SyndieVersion:".length()).trim();
|
||||
if (!_version.startsWith("1."))
|
||||
throw new IOException("Index is not supported, we only handle versions 1.*, but it is " + _version);
|
||||
while ( (line = in.readLine()) != null) {
|
||||
if (line.length() <= 0)
|
||||
break;
|
||||
if (line.startsWith("Blog:")) break;
|
||||
int split = line.indexOf(':');
|
||||
if (split <= 0) continue;
|
||||
if (split >= line.length()-1) continue;
|
||||
_headers.setProperty(line.substring(0, split), line.substring(split+1));
|
||||
}
|
||||
if (line != null) {
|
||||
do {
|
||||
if (!line.startsWith("Blog:"))
|
||||
break;
|
||||
loadBlog(line);
|
||||
} while ( (line = in.readLine()) != null);
|
||||
}
|
||||
|
||||
// ignore the first line that doesnt start with blog - its blank
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf(':');
|
||||
if (split <= 0) continue;
|
||||
if (split >= line.length()-1) continue;
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
if (key.equals("AllBlogs"))
|
||||
_allBlogs = getInt(val);
|
||||
else if (key.equals("NewBlogs"))
|
||||
_newBlogs = getInt(val);
|
||||
else if (key.equals("AllEntries"))
|
||||
_allEntries = getInt(val);
|
||||
else if (key.equals("NewEntries"))
|
||||
_newEntries = getInt(val);
|
||||
else if (key.equals("TotalSize"))
|
||||
_totalSize = getInt(val);
|
||||
else if (key.equals("NewSize"))
|
||||
_newSize = getInt(val);
|
||||
else if (key.equals("NewestBlogs"))
|
||||
_newestBlogs = parseNewestBlogs(val);
|
||||
else if (key.equals("NewestEntries"))
|
||||
_newestEntries = parseNewestEntries(val);
|
||||
else
|
||||
System.err.println("Key: " + key + " val: " + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dig through the index for BlogURIs matching the given criteria, ordering the results by
|
||||
* their own entryIds.
|
||||
*
|
||||
* @param out where to store the matches
|
||||
* @param blog if set, what blog key must the entries be under
|
||||
* @param tag if set, what tag must the entry be in
|
||||
*
|
||||
*/
|
||||
public void selectMatchesOrderByEntryId(List out, Hash blog, String tag) {
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (blog != null) {
|
||||
if (!blog.equals(summary.blog))
|
||||
continue;
|
||||
}
|
||||
if (tag != null) {
|
||||
if (!tag.equals(summary.tag)) {
|
||||
System.out.println("Tag [" + summary.tag + "] does not match the requested [" + tag + "]");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
ordered.put(new Long(0-entry.entry.getEntryId()), entry.entry);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) {
|
||||
BlogURI entry = (BlogURI)iter.next();
|
||||
if (!out.contains(entry))
|
||||
out.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int getInt(String val) {
|
||||
try {
|
||||
return Integer.parseInt(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private List parseNewestBlogs(String vals) {
|
||||
List rv = new ArrayList();
|
||||
StringTokenizer tok = new StringTokenizer(vals, " \t\n");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(new Hash(Base64.decode(tok.nextToken())));
|
||||
return rv;
|
||||
}
|
||||
private List parseNewestEntries(String vals) {
|
||||
List rv = new ArrayList();
|
||||
StringTokenizer tok = new StringTokenizer(vals, " \t\n");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(new BlogURI(tok.nextToken()));
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void loadBlog(String line) throws IOException {
|
||||
// Blog: hash YYYYMMDD tag\t[ yyyymmdd_n_sizeKB]*
|
||||
StringTokenizer tok = new StringTokenizer(line.trim(), " \n\t");
|
||||
if (tok.countTokens() < 4)
|
||||
return;
|
||||
tok.nextToken();
|
||||
Hash keyHash = new Hash(Base64.decode(tok.nextToken()));
|
||||
long when = getIndexDate(tok.nextToken());
|
||||
String tag = tok.nextToken();
|
||||
BlogSummary summary = new BlogSummary();
|
||||
summary.blog = keyHash;
|
||||
summary.tag = tag.trim();
|
||||
summary.lastUpdated = when;
|
||||
summary.entries = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String entry = tok.nextToken();
|
||||
long id = Archive.getEntryIdFromIndexName(entry);
|
||||
int kb = Archive.getSizeFromIndexName(entry);
|
||||
summary.entries.add(new EntrySummary(new BlogURI(keyHash, id), kb));
|
||||
}
|
||||
_blogs.add(summary);
|
||||
}
|
||||
|
||||
private SimpleDateFormat _dateFmt = new SimpleDateFormat("yyyyMMdd");
|
||||
private long getIndexDate(String yyyymmdd) {
|
||||
synchronized (_dateFmt) {
|
||||
try {
|
||||
return _dateFmt.parse(yyyymmdd).getTime();
|
||||
} catch (ParseException pe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
private String getIndexDate(long when) {
|
||||
synchronized (_dateFmt) {
|
||||
return _dateFmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
protected class BlogSummary {
|
||||
Hash blog;
|
||||
String tag;
|
||||
long lastUpdated;
|
||||
/** list of EntrySummary objects */
|
||||
List entries;
|
||||
|
||||
public BlogSummary() {
|
||||
entries = new ArrayList();
|
||||
}
|
||||
}
|
||||
protected class EntrySummary {
|
||||
BlogURI entry;
|
||||
long size;
|
||||
public EntrySummary(BlogURI uri, long kb) {
|
||||
size = kb;
|
||||
entry = uri;
|
||||
}
|
||||
}
|
||||
|
||||
/** export the index into an archive.txt */
|
||||
public String toString() {
|
||||
StringBuffer rv = new StringBuffer(1024);
|
||||
rv.append("SyndieVersion: ").append(_version).append('\n');
|
||||
for (Iterator iter = _headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = _headers.getProperty(key);
|
||||
rv.append(key).append(": ").append(val).append('\n');
|
||||
}
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
rv.append("Blog: ");
|
||||
Hash blog = getBlog(i);
|
||||
String tag = getBlogTag(i);
|
||||
rv.append(Base64.encode(blog.getData())).append(' ');
|
||||
rv.append(getIndexDate(getBlogLastUpdated(i))).append(' ');
|
||||
rv.append(tag).append('\t');
|
||||
int entries = getBlogEntryCount(i);
|
||||
for (int j = 0; j < entries; j++) {
|
||||
BlogURI entry = getBlogEntry(i, j);
|
||||
long kb = getBlogEntrySizeKB(i, j);
|
||||
rv.append(Archive.getIndexName(entry.getEntryId(), (int)kb*1024)).append(' ');
|
||||
}
|
||||
rv.append('\n');
|
||||
}
|
||||
|
||||
rv.append('\n');
|
||||
rv.append("AllBlogs: ").append(_allBlogs).append('\n');
|
||||
rv.append("NewBlogs: ").append(_newBlogs).append('\n');
|
||||
rv.append("AllEntries: ").append(_allEntries).append('\n');
|
||||
rv.append("NewEntries: ").append(_newEntries).append('\n');
|
||||
rv.append("TotalSize: ").append(_totalSize).append('\n');
|
||||
rv.append("NewSize: ").append(_newSize).append('\n');
|
||||
|
||||
rv.append("NewestBlogs: ");
|
||||
for (int i = 0; i < _newestBlogs.size(); i++)
|
||||
rv.append(((Hash)(_newestBlogs.get(i))).toBase64()).append(' ');
|
||||
rv.append('\n');
|
||||
|
||||
rv.append("NewestEntries: ");
|
||||
for (int i = 0; i < _newestEntries.size(); i++)
|
||||
rv.append(((BlogURI)_newestEntries.get(i)).toString()).append(' ');
|
||||
rv.append('\n');
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
|
||||
/** Usage: ArchiveIndex archive.txt */
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
ArchiveIndex i = new ArchiveIndex();
|
||||
i.load(new File(args[0]));
|
||||
System.out.println(i.toString());
|
||||
} catch (IOException ioe) { ioe.printStackTrace(); }
|
||||
}
|
||||
}
|
121
apps/syndie/java/src/net/i2p/syndie/data/Attachment.java
Normal file
121
apps/syndie/java/src/net/i2p/syndie/data/Attachment.java
Normal file
@ -0,0 +1,121 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Attachment {
|
||||
private byte _data[];
|
||||
private byte _rawMetadata[];
|
||||
private List _keys;
|
||||
private List _values;
|
||||
|
||||
public Attachment(byte data[], byte metadata[]) {
|
||||
_data = data;
|
||||
_rawMetadata = metadata;
|
||||
_keys = new ArrayList();
|
||||
_values = new ArrayList();
|
||||
parseMeta();
|
||||
}
|
||||
|
||||
public static final String NAME = "Name";
|
||||
public static final String DESCRIPTION = "Description";
|
||||
public static final String MIMETYPE = "MimeType";
|
||||
|
||||
public Attachment(byte data[], String name, String description, String mimeType) {
|
||||
_data = data;
|
||||
_keys = new ArrayList();
|
||||
_values = new ArrayList();
|
||||
_keys.add(NAME);
|
||||
_values.add(name);
|
||||
if ( (description != null) && (description.trim().length() > 0) ) {
|
||||
_keys.add(DESCRIPTION);
|
||||
_values.add(description);
|
||||
}
|
||||
if ( (mimeType != null) && (mimeType.trim().length() > 0) ) {
|
||||
_keys.add(MIMETYPE);
|
||||
_values.add(mimeType);
|
||||
}
|
||||
createMeta();
|
||||
}
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public int getDataLength() { return _data.length; }
|
||||
public byte[] getRawMetadata() { return _rawMetadata; }
|
||||
|
||||
public InputStream getDataStream() throws IOException { return new ByteArrayInputStream(_data); }
|
||||
|
||||
public String getMeta(String key) {
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
if (key.equals(_keys.get(i)))
|
||||
return (String)_values.get(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() { return getMeta(NAME); }
|
||||
public String getDescription() { return getMeta(DESCRIPTION); }
|
||||
public String getMimeType() { return getMeta(MIMETYPE); }
|
||||
|
||||
public void setMeta(String key, String val) {
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
if (key.equals(_keys.get(i))) {
|
||||
_values.set(i, val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_keys.add(key);
|
||||
_values.add(val);
|
||||
}
|
||||
|
||||
public Map getMeta() {
|
||||
Map rv = new HashMap(_keys.size());
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
String k = (String)_keys.get(i);
|
||||
String v = (String)_values.get(i);
|
||||
rv.put(k,v);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void createMeta() {
|
||||
StringBuffer meta = new StringBuffer(64);
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
meta.append(_keys.get(i)).append(':').append(_values.get(i)).append('\n');
|
||||
}
|
||||
_rawMetadata = meta.toString().getBytes();
|
||||
}
|
||||
|
||||
private void parseMeta() {
|
||||
if (_rawMetadata == null) return;
|
||||
String key = null;
|
||||
String val = null;
|
||||
int keyBegin = 0;
|
||||
int valBegin = -1;
|
||||
for (int i = 0; i < _rawMetadata.length; i++) {
|
||||
if (_rawMetadata[i] == ':') {
|
||||
key = new String(_rawMetadata, keyBegin, i - keyBegin);
|
||||
valBegin = i + 1;
|
||||
} else if (_rawMetadata[i] == '\n') {
|
||||
val = new String(_rawMetadata, valBegin, i - valBegin);
|
||||
_keys.add(key);
|
||||
_values.add(val);
|
||||
keyBegin = i + 1;
|
||||
key = null;
|
||||
val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
int len = 0;
|
||||
if (_data != null)
|
||||
len = _data.length;
|
||||
return getName()
|
||||
+ (getDescription() != null ? ": " + getDescription() : "")
|
||||
+ (getMimeType() != null ? ", type: " + getMimeType() : "")
|
||||
+ ", size: " + len;
|
||||
}
|
||||
}
|
195
apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java
Normal file
195
apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java
Normal file
@ -0,0 +1,195 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Blog metadata. Formatted as: <pre>
|
||||
* [key:val\n]*
|
||||
* </pre>
|
||||
*
|
||||
* Required keys:
|
||||
* Owner: base64 of their signing public key
|
||||
* Signature: base64 of the DSA signature of the rest of the ordered metadata
|
||||
*
|
||||
* Optional keys:
|
||||
* Posters: comma delimited list of base64 signing public keys that
|
||||
* can post to the blog
|
||||
* Name: name of the blog
|
||||
* Description: brief description of the blog
|
||||
*
|
||||
*/
|
||||
public class BlogInfo {
|
||||
private SigningPublicKey _key;
|
||||
private SigningPublicKey _posters[];
|
||||
private String _optionNames[];
|
||||
private String _optionValues[];
|
||||
private Signature _signature;
|
||||
|
||||
public BlogInfo() {}
|
||||
|
||||
public BlogInfo(SigningPublicKey key, SigningPublicKey posters[], Properties opts) {
|
||||
_optionNames = new String[0];
|
||||
_optionValues = new String[0];
|
||||
setKey(key);
|
||||
setPosters(posters);
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = opts.getProperty(k);
|
||||
setProperty(k.trim(), v.trim());
|
||||
}
|
||||
}
|
||||
|
||||
public SigningPublicKey getKey() { return _key; }
|
||||
public void setKey(SigningPublicKey key) {
|
||||
_key = key;
|
||||
setProperty(OWNER_KEY, Base64.encode(key.getData()));
|
||||
}
|
||||
|
||||
public static final String OWNER_KEY = "Owner";
|
||||
public static final String POSTERS = "Posters";
|
||||
public static final String SIGNATURE = "Signature";
|
||||
public static final String NAME = "Name";
|
||||
public static final String DESCRIPTION = "Description";
|
||||
|
||||
public void load(InputStream in) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
List names = new ArrayList();
|
||||
List vals = new ArrayList();
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
int len = line.length();
|
||||
int split = line.indexOf(':');
|
||||
if ( (len <= 0) || (split <= 0) || (split >= len - 2) )
|
||||
continue;
|
||||
|
||||
String key = line.substring(0, split).trim();
|
||||
String val = line.substring(split+1).trim();
|
||||
names.add(key);
|
||||
vals.add(val);
|
||||
}
|
||||
_optionNames = new String[names.size()];
|
||||
_optionValues = new String[names.size()];
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
_optionNames[i] = (String)names.get(i);
|
||||
_optionValues[i] = (String)vals.get(i);
|
||||
}
|
||||
|
||||
String keyStr = getProperty(OWNER_KEY);
|
||||
if (keyStr == null) throw new IOException("Owner not found");
|
||||
_key = new SigningPublicKey(Base64.decode(keyStr));
|
||||
|
||||
String postersStr = getProperty(POSTERS);
|
||||
if (postersStr != null) {
|
||||
StringTokenizer tok = new StringTokenizer(postersStr, ", \t");
|
||||
_posters = new SigningPublicKey[tok.countTokens()];
|
||||
for (int i = 0; tok.hasMoreTokens(); i++)
|
||||
_posters[i] = new SigningPublicKey(Base64.decode(tok.nextToken()));
|
||||
}
|
||||
|
||||
String sigStr = getProperty(SIGNATURE);
|
||||
if (sigStr == null) throw new IOException("Signature not found");
|
||||
_signature = new Signature(Base64.decode(sigStr));
|
||||
}
|
||||
|
||||
public void write(OutputStream out) throws IOException { write(out, true); }
|
||||
public void write(OutputStream out, boolean includeRealSignature) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if ( (includeRealSignature) || (!SIGNATURE.equals(_optionNames[i])) )
|
||||
buf.append(_optionNames[i]).append(':').append(_optionValues[i]).append('\n');
|
||||
}
|
||||
out.write(buf.toString().getBytes());
|
||||
}
|
||||
|
||||
public String getProperty(String name) {
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if (_optionNames[i].equals(name))
|
||||
return _optionValues[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setProperty(String name, String val) {
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if (_optionNames[i].equals(name)) {
|
||||
_optionValues[i] = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String names[] = new String[_optionNames.length + 1];
|
||||
String values[] = new String[_optionValues.length + 1];
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
names[i] = _optionNames[i];
|
||||
values[i] = _optionValues[i];
|
||||
}
|
||||
names[names.length-1] = name;
|
||||
values[values.length-1] = val;
|
||||
_optionNames = names;
|
||||
_optionValues = values;
|
||||
}
|
||||
|
||||
public String[] getProperties() { return _optionNames; }
|
||||
|
||||
public SigningPublicKey[] getPosters() { return _posters; }
|
||||
public void setPosters(SigningPublicKey posters[]) {
|
||||
_posters = posters;
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; posters != null && i < posters.length; i++) {
|
||||
buf.append(Base64.encode(posters[i].getData()));
|
||||
if (i + 1 < posters.length)
|
||||
buf.append(',');
|
||||
}
|
||||
setProperty(POSTERS, buf.toString());
|
||||
}
|
||||
|
||||
public boolean verify(I2PAppContext ctx) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
write(out, false);
|
||||
return ctx.dsa().verifySignature(_signature, out.toByteArray(), _key);
|
||||
} catch (IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void sign(I2PAppContext ctx, SigningPrivateKey priv) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
write(out, false);
|
||||
byte data[] = out.toByteArray();
|
||||
Signature sig = ctx.dsa().sign(data, priv);
|
||||
if (sig == null)
|
||||
throw new IOException("wtf, why is the signature null? data.len = " + data.length + " priv: " + priv);
|
||||
setProperty(SIGNATURE, Base64.encode(sig.getData()));
|
||||
_signature = sig;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("Blog ").append(getKey().calculateHash().toBase64());
|
||||
for (int i = 0; i < _optionNames.length; i++) {
|
||||
if ( (!SIGNATURE.equals(_optionNames[i])) &&
|
||||
(!OWNER_KEY.equals(_optionNames[i])) &&
|
||||
(!SIGNATURE.equals(_optionNames[i])) )
|
||||
buf.append(' ').append(_optionNames[i]).append(": ").append(_optionValues[i]);
|
||||
}
|
||||
|
||||
if ( (_posters != null) && (_posters.length > 0) ) {
|
||||
buf.append(" additional posts by");
|
||||
for (int i = 0; i < _posters.length; i++) {
|
||||
buf.append(' ').append(_posters[i].calculateHash().toBase64());
|
||||
if (i + 1 < _posters.length)
|
||||
buf.append(',');
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
78
apps/syndie/java/src/net/i2p/syndie/data/BlogURI.java
Normal file
78
apps/syndie/java/src/net/i2p/syndie/data/BlogURI.java
Normal file
@ -0,0 +1,78 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BlogURI {
|
||||
private Hash _blogHash;
|
||||
private long _entryId;
|
||||
|
||||
public BlogURI() {
|
||||
this(null, -1);
|
||||
}
|
||||
public BlogURI(Hash blogHash, long entryId) {
|
||||
_blogHash = blogHash;
|
||||
_entryId = entryId;
|
||||
}
|
||||
public BlogURI(String uri) {
|
||||
if (uri.startsWith("blog://")) {
|
||||
int off = "blog://".length();
|
||||
_blogHash = new Hash(Base64.decode(uri.substring(off, off+44))); // 44 chars == base64(32 bytes)
|
||||
int entryStart = uri.indexOf('/', off+1);
|
||||
if (entryStart < 0) {
|
||||
_entryId = -1;
|
||||
} else {
|
||||
try {
|
||||
_entryId = Long.parseLong(uri.substring(entryStart+1).trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
_entryId = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_blogHash = null;
|
||||
_entryId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public Hash getKeyHash() { return _blogHash; }
|
||||
public long getEntryId() { return _entryId; }
|
||||
|
||||
public void setKeyHash(Hash hash) { _blogHash = hash; }
|
||||
public void setEntryId(long id) { _entryId = id; }
|
||||
|
||||
public String toString() {
|
||||
if ( (_blogHash == null) || (_blogHash.getData() == null) )
|
||||
return "";
|
||||
StringBuffer rv = new StringBuffer(64);
|
||||
rv.append("blog://").append(Base64.encode(_blogHash.getData()));
|
||||
rv.append('/');
|
||||
if (_entryId >= 0)
|
||||
rv.append(_entryId);
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj.getClass() != getClass()) return false;
|
||||
return DataHelper.eq(_entryId, ((BlogURI)obj)._entryId) &&
|
||||
DataHelper.eq(_blogHash, ((BlogURI)obj)._blogHash);
|
||||
}
|
||||
public int hashCode() {
|
||||
return (int)_entryId;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
test("http://asdf/");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/");
|
||||
test("blog://Vq~AlW-r7OM763okVUFIDvVFzxOjpNNsAx0rFb2yaE8=/123456789");
|
||||
}
|
||||
private static void test(String uri) {
|
||||
BlogURI u = new BlogURI(uri);
|
||||
if (!u.toString().equals(uri))
|
||||
System.err.println("Not a match: [" + uri + "] != [" + u.toString() + "]");
|
||||
}
|
||||
}
|
14
apps/syndie/java/src/net/i2p/syndie/data/Entry.java
Normal file
14
apps/syndie/java/src/net/i2p/syndie/data/Entry.java
Normal file
@ -0,0 +1,14 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Entry {
|
||||
private String _text;
|
||||
|
||||
public Entry(String raw) {
|
||||
_text = raw;
|
||||
}
|
||||
|
||||
public String getText() { return _text; }
|
||||
}
|
385
apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java
Normal file
385
apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java
Normal file
@ -0,0 +1,385 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Securely wrap up an entry and any attachments. Container format:<pre>
|
||||
* $format\n
|
||||
* [$key: $val\n]*
|
||||
* \n
|
||||
* Signature: $base64(DSA signature)\n
|
||||
* Size: sizeof(data)\n
|
||||
* [data bytes]
|
||||
* </pre>
|
||||
*
|
||||
* Required keys:
|
||||
* BlogKey: base64 of the SHA256 of the blog's public key
|
||||
* BlogTags: tab delimited list of tags under which this entry should be organized
|
||||
* BlogEntryId: base10 unique identifier of this entry within the key/path. Typically starts
|
||||
* as the current day (in unix time, milliseconds) plus further milliseconds for
|
||||
* each entry within the day.
|
||||
*
|
||||
* The data bytes contains zip file, either in the clear or encrypted. If the format
|
||||
* is encrypted, the BlogPath key will (likely) be encrypted as well.
|
||||
*
|
||||
*/
|
||||
public class EntryContainer {
|
||||
private List _rawKeys;
|
||||
private List _rawValues;
|
||||
private Signature _signature;
|
||||
private byte _rawData[];
|
||||
|
||||
private BlogURI _entryURI;
|
||||
private int _format;
|
||||
private Entry _entryData;
|
||||
private Attachment _attachments[];
|
||||
private int _completeSize;
|
||||
|
||||
public static final int FORMAT_ZIP_UNENCRYPTED = 0;
|
||||
public static final int FORMAT_ZIP_ENCRYPTED = 1;
|
||||
public static final String FORMAT_ZIP_UNENCRYPTED_STR = "syndie.entry.zip-unencrypted";
|
||||
public static final String FORMAT_ZIP_ENCRYPTED_STR = "syndie.entry.zip-encrypted";
|
||||
|
||||
public static final String HEADER_BLOGKEY = "BlogKey";
|
||||
public static final String HEADER_BLOGTAGS = "BlogTags";
|
||||
public static final String HEADER_ENTRYID = "BlogEntryId";
|
||||
|
||||
public EntryContainer() {
|
||||
_rawKeys = new ArrayList();
|
||||
_rawValues = new ArrayList();
|
||||
_completeSize = -1;
|
||||
}
|
||||
|
||||
public EntryContainer(BlogURI uri, String tags[], byte smlData[]) {
|
||||
this();
|
||||
_entryURI = uri;
|
||||
_entryData = new Entry(new String(smlData));
|
||||
setHeader(HEADER_BLOGKEY, Base64.encode(uri.getKeyHash().getData()));
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; tags != null && i < tags.length; i++)
|
||||
buf.append(tags[i]).append('\t');
|
||||
setHeader(HEADER_BLOGTAGS, buf.toString());
|
||||
if (uri.getEntryId() < 0)
|
||||
uri.setEntryId(System.currentTimeMillis());
|
||||
setHeader(HEADER_ENTRYID, Long.toString(uri.getEntryId()));
|
||||
}
|
||||
|
||||
public int getFormat() { return _format; }
|
||||
|
||||
public void load(InputStream source) throws IOException {
|
||||
String fmt = DataHelper.readLine(source).trim();
|
||||
if (FORMAT_ZIP_UNENCRYPTED_STR.equals(fmt)) {
|
||||
_format = FORMAT_ZIP_UNENCRYPTED;
|
||||
} else if (FORMAT_ZIP_ENCRYPTED_STR.equals(fmt)) {
|
||||
_format = FORMAT_ZIP_ENCRYPTED;
|
||||
} else {
|
||||
throw new IOException("Unsupported entry format: " + fmt);
|
||||
}
|
||||
|
||||
String line = null;
|
||||
while ( (line = DataHelper.readLine(source)) != null) {
|
||||
line = line.trim();
|
||||
int len = line.length();
|
||||
if (len <= 0)
|
||||
break;
|
||||
int split = line.indexOf(':');
|
||||
if ( (split <= 0) || (split >= len - 2) )
|
||||
throw new IOException("Invalid format of the syndie entry: line=" + line);
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
_rawKeys.add(key);
|
||||
_rawValues.add(val);
|
||||
}
|
||||
|
||||
parseHeaders();
|
||||
|
||||
String sigStr = DataHelper.readLine(source);
|
||||
sigStr = sigStr.substring("Signature:".length()+1).trim();
|
||||
|
||||
_signature = new Signature(Base64.decode(sigStr));
|
||||
//System.out.println("Sig: " + _signature.toBase64());
|
||||
|
||||
line = DataHelper.readLine(source).trim();
|
||||
int dataSize = -1;
|
||||
try {
|
||||
int index = line.indexOf("Size:");
|
||||
if (index == 0)
|
||||
dataSize = Integer.parseInt(line.substring("Size:".length()+1).trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Invalid entry size: " + line);
|
||||
}
|
||||
|
||||
byte data[] = new byte[dataSize];
|
||||
int read = DataHelper.read(source, data);
|
||||
if (read != dataSize)
|
||||
throw new IOException("Incomplete entry: read " + read + " expected " + dataSize);
|
||||
|
||||
_rawData = data;
|
||||
}
|
||||
|
||||
public void seal(I2PAppContext ctx, SigningPrivateKey signingKey, SessionKey entryKey) throws IOException {
|
||||
System.out.println("Sealing " + _entryURI);
|
||||
if (entryKey == null)
|
||||
_format = FORMAT_ZIP_UNENCRYPTED;
|
||||
else
|
||||
_format = FORMAT_ZIP_ENCRYPTED;
|
||||
setHeader(HEADER_BLOGKEY, Base64.encode(_entryURI.getKeyHash().getData()));
|
||||
if (_entryURI.getEntryId() < 0)
|
||||
_entryURI.setEntryId(ctx.clock().now());
|
||||
setHeader(HEADER_ENTRYID, Long.toString(_entryURI.getEntryId()));
|
||||
_rawData = createRawData(ctx, entryKey);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
write(baos, false);
|
||||
byte data[] = baos.toByteArray();
|
||||
_signature = ctx.dsa().sign(data, signingKey);
|
||||
}
|
||||
|
||||
private byte[] createRawData(I2PAppContext ctx, SessionKey entryKey) throws IOException {
|
||||
byte raw[] = createRawData();
|
||||
if (entryKey != null) {
|
||||
byte iv[] = new byte[16];
|
||||
ctx.random().nextBytes(iv);
|
||||
byte rv[] = new byte[raw.length + iv.length];
|
||||
ctx.aes().encrypt(raw, 0, rv, iv.length, entryKey, iv, raw.length);
|
||||
System.arraycopy(iv, 0, rv, 0, iv.length);
|
||||
return rv;
|
||||
} else {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createRawData() throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ZipOutputStream out = new ZipOutputStream(baos);
|
||||
ZipEntry ze = new ZipEntry(ZIP_ENTRY);
|
||||
byte data[] = _entryData.getText().getBytes();
|
||||
ze.setTime(0);
|
||||
out.putNextEntry(ze);
|
||||
out.write(data);
|
||||
out.closeEntry();
|
||||
for (int i = 0; (_attachments != null) && (i < _attachments.length); i++) {
|
||||
ze = new ZipEntry(ZIP_ATTACHMENT_PREFIX + i + ZIP_ATTACHMENT_SUFFIX);
|
||||
data = _attachments[i].getData();
|
||||
out.putNextEntry(ze);
|
||||
out.write(data);
|
||||
out.closeEntry();
|
||||
ze = new ZipEntry(ZIP_ATTACHMENT_META_PREFIX + i + ZIP_ATTACHMENT_META_SUFFIX);
|
||||
data = _attachments[i].getRawMetadata();
|
||||
out.putNextEntry(ze);
|
||||
out.write(data);
|
||||
out.closeEntry();
|
||||
}
|
||||
out.finish();
|
||||
out.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static final String ZIP_ENTRY = "entry.sml";
|
||||
public static final String ZIP_ATTACHMENT_PREFIX = "attachmentdata";
|
||||
public static final String ZIP_ATTACHMENT_SUFFIX = ".szd";
|
||||
public static final String ZIP_ATTACHMENT_META_PREFIX = "attachmentmeta";
|
||||
public static final String ZIP_ATTACHMENT_META_SUFFIX = ".szm";
|
||||
|
||||
public void parseRawData(I2PAppContext ctx) throws IOException { parseRawData(ctx, null); }
|
||||
public void parseRawData(I2PAppContext ctx, SessionKey zipKey) throws IOException {
|
||||
int dataOffset = 0;
|
||||
if (zipKey != null) {
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(_rawData, 0, iv, 0, iv.length);
|
||||
ctx.aes().decrypt(_rawData, iv.length, _rawData, iv.length, zipKey, iv, _rawData.length - iv.length);
|
||||
dataOffset = iv.length;
|
||||
}
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(_rawData, dataOffset, _rawData.length - dataOffset);
|
||||
ZipInputStream zi = new ZipInputStream(in);
|
||||
Map attachments = new HashMap();
|
||||
Map attachmentMeta = new HashMap();
|
||||
while (true) {
|
||||
ZipEntry entry = zi.getNextEntry();
|
||||
if (entry == null)
|
||||
break;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
byte buf[] = new byte[1024];
|
||||
int read = -1;
|
||||
while ( (read = zi.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
byte entryData[] = out.toByteArray();
|
||||
|
||||
String name = entry.getName();
|
||||
if (ZIP_ENTRY.equals(name)) {
|
||||
_entryData = new Entry(new String(entryData));
|
||||
} else if (name.startsWith(ZIP_ATTACHMENT_PREFIX)) {
|
||||
attachments.put(name, (Object)entryData);
|
||||
} else if (name.startsWith(ZIP_ATTACHMENT_META_PREFIX)) {
|
||||
attachmentMeta.put(name, (Object)entryData);
|
||||
}
|
||||
|
||||
//System.out.println("Read entry [" + name + "] with size=" + entryData.length);
|
||||
}
|
||||
|
||||
_attachments = new Attachment[attachments.size()];
|
||||
|
||||
for (int i = 0; i < attachments.size(); i++) {
|
||||
byte data[] = (byte[])attachments.get(ZIP_ATTACHMENT_PREFIX + i + ZIP_ATTACHMENT_SUFFIX);
|
||||
byte metadata[] = (byte[])attachmentMeta.get(ZIP_ATTACHMENT_META_PREFIX + i + ZIP_ATTACHMENT_META_SUFFIX);
|
||||
if ( (data != null) && (metadata != null) )
|
||||
_attachments[i] = new Attachment(data, metadata);
|
||||
else
|
||||
System.out.println("Unable to get " + i + ": " + data + "/" + metadata);
|
||||
}
|
||||
|
||||
//System.out.println("Attachments: " + _attachments.length + "/" + attachments.size() + ": " + attachments);
|
||||
}
|
||||
|
||||
public BlogURI getURI() { return _entryURI; }
|
||||
private static final String NO_TAGS[] = new String[0];
|
||||
public String[] getTags() {
|
||||
String tags = getHeader(HEADER_BLOGTAGS);
|
||||
if ( (tags == null) || (tags.trim().length() <= 0) ) {
|
||||
return NO_TAGS;
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(tags, "\t");
|
||||
String rv[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < rv.length; i++)
|
||||
rv[i] = tok.nextToken().trim();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
public Signature getSignature() { return _signature; }
|
||||
public Entry getEntry() { return _entryData; }
|
||||
public Attachment[] getAttachments() { return _attachments; }
|
||||
|
||||
public void setCompleteSize(int bytes) { _completeSize = bytes; }
|
||||
public int getCompleteSize() { return _completeSize; }
|
||||
|
||||
public String getHeader(String key) {
|
||||
for (int i = 0; i < _rawKeys.size(); i++) {
|
||||
String k = (String)_rawKeys.get(i);
|
||||
if (k.equals(key))
|
||||
return (String)_rawValues.get(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map getHeaders() {
|
||||
Map rv = new HashMap(_rawKeys.size());
|
||||
for (int i = 0; i < _rawKeys.size(); i++) {
|
||||
String k = (String)_rawKeys.get(i);
|
||||
String v = (String)_rawValues.get(i);
|
||||
rv.put(k,v);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void setHeader(String name, String val) {
|
||||
int index = _rawKeys.indexOf(name);
|
||||
if (index < 0) {
|
||||
_rawKeys.add(name);
|
||||
_rawValues.add(val);
|
||||
} else {
|
||||
_rawValues.set(index, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAttachment(byte data[], String name, String description, String mimeType) {
|
||||
Attachment a = new Attachment(data, name, description, mimeType);
|
||||
int old = (_attachments == null ? 0 : _attachments.length);
|
||||
Attachment nv[] = new Attachment[old+1];
|
||||
if (old > 0)
|
||||
for (int i = 0; i < old; i++)
|
||||
nv[i] = _attachments[i];
|
||||
nv[old] = a;
|
||||
_attachments = nv;
|
||||
}
|
||||
|
||||
private void parseHeaders() throws IOException {
|
||||
String keyHash = getHeader(HEADER_BLOGKEY);
|
||||
String idVal = getHeader(HEADER_ENTRYID);
|
||||
|
||||
if (keyHash == null)
|
||||
throw new IOException("Missing " + HEADER_BLOGKEY + " header");
|
||||
|
||||
long entryId = -1;
|
||||
if ( (idVal != null) && (idVal.length() > 0) ) {
|
||||
try {
|
||||
entryId = Long.parseLong(idVal.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Invalid format of entryId (" + idVal + ")");
|
||||
}
|
||||
}
|
||||
|
||||
_entryURI = new BlogURI(new Hash(Base64.decode(keyHash)), entryId);
|
||||
}
|
||||
|
||||
public boolean verifySignature(I2PAppContext ctx, BlogInfo info) {
|
||||
if (_signature == null) throw new NullPointerException("sig is null");
|
||||
if (info == null) throw new NullPointerException("info is null");
|
||||
if (info.getKey() == null) throw new NullPointerException("info key is null");
|
||||
if (info.getKey().getData() == null) throw new NullPointerException("info key data is null");
|
||||
//System.out.println("Verifying " + _entryURI + " for " + info);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(_rawData.length + 512);
|
||||
try {
|
||||
write(out, false);
|
||||
byte dat[] = out.toByteArray();
|
||||
//System.out.println("Raw data to verify: " + ctx.sha().calculateHash(dat).toBase64() + " sig: " + _signature.toBase64());
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(dat);
|
||||
boolean ok = ctx.dsa().verifySignature(_signature, in, info.getKey());
|
||||
if (!ok && info.getPosters() != null) {
|
||||
for (int i = 0; !ok && i < info.getPosters().length; i++) {
|
||||
in.reset();
|
||||
ok = ctx.dsa().verifySignature(_signature, in, info.getPosters()[i]);
|
||||
}
|
||||
}
|
||||
//System.out.println("Verified ok? " + ok + " key: " + info.getKey().calculateHash().toBase64());
|
||||
//new Exception("verifying").printStackTrace();
|
||||
return ok;
|
||||
} catch (IOException ioe) {
|
||||
//System.out.println("Verification failed! " + ioe.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void write(OutputStream out, boolean includeRealSignature) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
switch (_format) {
|
||||
case FORMAT_ZIP_ENCRYPTED:
|
||||
buf.append(FORMAT_ZIP_ENCRYPTED_STR).append('\n');
|
||||
break;
|
||||
case FORMAT_ZIP_UNENCRYPTED:
|
||||
buf.append(FORMAT_ZIP_UNENCRYPTED_STR).append('\n');
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Invalid format " + _format);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _rawKeys.size(); i++) {
|
||||
String k = (String)_rawKeys.get(i);
|
||||
buf.append(k.trim());
|
||||
buf.append(": ");
|
||||
buf.append(((String)_rawValues.get(i)).trim());
|
||||
buf.append('\n');
|
||||
}
|
||||
|
||||
buf.append('\n');
|
||||
buf.append("Signature: ");
|
||||
if (includeRealSignature)
|
||||
buf.append(Base64.encode(_signature.getData()));
|
||||
buf.append("\n");
|
||||
buf.append("Size: ").append(_rawData.length).append('\n');
|
||||
String str = buf.toString();
|
||||
|
||||
//System.out.println("Writing raw: \n[" + str + "] / " + I2PAppContext.getGlobalContext().sha().calculateHash(str.getBytes()) + ", raw data: " + I2PAppContext.getGlobalContext().sha().calculateHash(_rawData).toBase64() + "\n");
|
||||
out.write(str.getBytes());
|
||||
out.write(_rawData);
|
||||
}
|
||||
|
||||
public String toString() { return _entryURI.toString(); }
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.Archive;
|
||||
|
||||
/**
|
||||
* writable archive index (most are readonly)
|
||||
*/
|
||||
public class LocalArchiveIndex extends ArchiveIndex {
|
||||
|
||||
public LocalArchiveIndex() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
public void setGeneratedOn(long when) { _generatedOn = when; }
|
||||
|
||||
public void setVersion(String v) { _version = v; }
|
||||
public void setHeaders(Properties headers) { _headers = headers; }
|
||||
public void setHeader(String key, String val) { _headers.setProperty(key, val); }
|
||||
public void setAllBlogs(int count) { _allBlogs = count; }
|
||||
public void setNewBlogs(int count) { _newBlogs = count; }
|
||||
public void setAllEntries(int count) { _allEntries = count; }
|
||||
public void setNewEntries(int count) { _newEntries = count; }
|
||||
public void setTotalSize(long bytes) { _totalSize = bytes; }
|
||||
public void setNewSize(long bytes) { _newSize = bytes; }
|
||||
|
||||
public void addBlog(Hash key, String tag, long lastUpdated) {
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary s = (BlogSummary)_blogs.get(i);
|
||||
if ( (s.blog.equals(key)) && (s.tag.equals(tag)) ) {
|
||||
s.lastUpdated = Math.max(s.lastUpdated, lastUpdated);
|
||||
return;
|
||||
}
|
||||
}
|
||||
BlogSummary summary = new ArchiveIndex.BlogSummary();
|
||||
summary.blog = key;
|
||||
summary.tag = tag;
|
||||
summary.lastUpdated = lastUpdated;
|
||||
_blogs.add(summary);
|
||||
}
|
||||
|
||||
public void addBlogEntry(Hash key, String tag, String entry) {
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
if (summary.blog.equals(key) && (summary.tag.equals(tag)) ) {
|
||||
long entryId = Archive.getEntryIdFromIndexName(entry);
|
||||
int kb = Archive.getSizeFromIndexName(entry);
|
||||
System.out.println("Adding entry " + entryId + ", size=" + kb + "KB [" + entry + "]");
|
||||
EntrySummary entrySummary = new EntrySummary(new BlogURI(key, entryId), kb);
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary cur = (EntrySummary)summary.entries.get(j);
|
||||
if (cur.entry.equals(entrySummary.entry))
|
||||
return;
|
||||
}
|
||||
summary.entries.add(entrySummary);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addNewestBlog(Hash key) {
|
||||
if (!_newestBlogs.contains(key))
|
||||
_newestBlogs.add(key);
|
||||
}
|
||||
public void addNewestEntry(BlogURI entry) {
|
||||
if (!_newestEntries.contains(entry))
|
||||
_newestEntries.add(entry);
|
||||
}
|
||||
}
|
32
apps/syndie/java/src/net/i2p/syndie/data/SafeURL.java
Normal file
32
apps/syndie/java/src/net/i2p/syndie/data/SafeURL.java
Normal file
@ -0,0 +1,32 @@
|
||||
package net.i2p.syndie.data;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SafeURL {
|
||||
private String _schema;
|
||||
private String _location;
|
||||
private String _name;
|
||||
private String _description;
|
||||
|
||||
public SafeURL(String raw) {
|
||||
parse(raw);
|
||||
}
|
||||
|
||||
private void parse(String raw) {
|
||||
if (raw != null) {
|
||||
int index = raw.indexOf("://");
|
||||
if ( (index <= 0) || (index + 1 >= raw.length()) )
|
||||
return;
|
||||
_schema = raw.substring(0, index);
|
||||
_location = raw.substring(index+3);
|
||||
_location.replace('>', '_');
|
||||
_location.replace('<', '^');
|
||||
}
|
||||
}
|
||||
|
||||
public String getSchema() { return _schema; }
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
public String toString() { return _schema + "://" + _location; }
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.i2p.syndie.sml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class EventReceiverImpl implements SMLParser.EventReceiver {
|
||||
public void receiveHeader(String header, String value) {
|
||||
System.out.println("Receive header [" + header + "] = [" + value + "]");
|
||||
}
|
||||
public void receiveLink(String schema, String location, String text) {
|
||||
System.out.println("Receive link [" + schema + "]/[" + location+ "]/[" + text + "]");
|
||||
}
|
||||
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId,
|
||||
List blogArchiveLocations, String anchorText) {
|
||||
System.out.println("Receive blog [" + name + "]/[" + blogKeyHash + "]/[" + blogPath
|
||||
+ "]/[" + blogEntryId + "]/[" + blogArchiveLocations + "]/[" + anchorText + "]");
|
||||
}
|
||||
public void receiveArchive(String name, String description, String locationSchema, String location,
|
||||
String postingKey, String anchorText) {
|
||||
System.out.println("Receive archive [" + name + "]/[" + description + "]/[" + locationSchema
|
||||
+ "]/[" + location + "]/[" + postingKey + "]/[" + anchorText + "]");
|
||||
}
|
||||
public void receiveImage(String alternateText, int attachmentId) {
|
||||
System.out.println("Receive image [" + alternateText + "]/[" + attachmentId + "]");
|
||||
}
|
||||
public void receiveAddress(String name, String schema, String location, String anchorText) {
|
||||
System.out.println("Receive address [" + name + "]/[" + schema + "]/[" + location + "]/[" + anchorText+ "]");
|
||||
}
|
||||
public void receiveBold(String text) { System.out.println("Receive bold [" + text+ "]"); }
|
||||
public void receiveItalic(String text) { System.out.println("Receive italic [" + text+ "]"); }
|
||||
public void receiveUnderline(String text) { System.out.println("Receive underline [" + text+ "]"); }
|
||||
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {
|
||||
System.out.println("Receive quote [" + text + "]/[" + whoQuoted + "]/[" + quoteLocationSchema + "]/[" + quoteLocation + "]");
|
||||
}
|
||||
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {
|
||||
System.out.println("Receive code [" + text+ "]/[" + codeLocationSchema + "]/[" + codeLocation + "]");
|
||||
}
|
||||
public void receiveCut(String summaryText) { System.out.println("Receive cut [" + summaryText + "]"); }
|
||||
public void receivePlain(String text) { System.out.println("Receive plain [" + text + "]"); }
|
||||
public void receiveNewline() { System.out.println("Receive NL"); }
|
||||
public void receiveLT() { System.out.println("Receive LT"); }
|
||||
public void receiveGT() { System.out.println("Receive GT"); }
|
||||
public void receiveBegin() { System.out.println("Receive begin"); }
|
||||
public void receiveEnd() { System.out.println("Receive end"); }
|
||||
public void receiveHeaderEnd() { System.out.println("Receive header end"); }
|
||||
public void receiveLeftBracket() { System.out.println("Receive ["); }
|
||||
public void receiveRightBracket() { System.out.println("Receive ]"); }
|
||||
|
||||
public void receiveH1(String text) {}
|
||||
public void receiveH2(String text) {}
|
||||
public void receiveH3(String text) {}
|
||||
public void receiveH4(String text) {}
|
||||
public void receiveH5(String text) {}
|
||||
public void receivePre(String text) {}
|
||||
public void receiveHR() {}
|
||||
public void receiveAttachment(int id, String anchorText) {}
|
||||
}
|
641
apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java
Normal file
641
apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java
Normal file
@ -0,0 +1,641 @@
|
||||
package net.i2p.syndie.sml;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.web.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HTMLRenderer extends EventReceiverImpl {
|
||||
private SMLParser _parser;
|
||||
private Writer _out;
|
||||
private User _user;
|
||||
private Archive _archive;
|
||||
private EntryContainer _entry;
|
||||
private boolean _showImages;
|
||||
private boolean _cutBody;
|
||||
private boolean _cutReached;
|
||||
private int _cutSize;
|
||||
private int _lastNewlineAt;
|
||||
private Map _headers;
|
||||
private List _addresses;
|
||||
private List _links;
|
||||
private List _blogs;
|
||||
private StringBuffer _preBodyBuffer;
|
||||
private StringBuffer _bodyBuffer;
|
||||
private StringBuffer _postBodyBuffer;
|
||||
|
||||
public HTMLRenderer() {
|
||||
_parser = new SMLParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage: HTMLRenderer smlFile outputFile
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("Usage: HTMLRenderer smlFile outputFile");
|
||||
return;
|
||||
}
|
||||
HTMLRenderer renderer = new HTMLRenderer();
|
||||
FileWriter out = null;
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024*512);
|
||||
FileInputStream in = new FileInputStream(args[0]);
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
baos.write(buf, 0, read);
|
||||
out = new FileWriter(args[1]);
|
||||
renderer.render(new User(), BlogManager.instance().getArchive(), null, new String(baos.toByteArray()), out, false, true);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void renderUnknownEntry(User user, Archive archive, BlogURI uri, Writer out) throws IOException {
|
||||
BlogInfo info = archive.getBlogInfo(uri);
|
||||
if (info == null)
|
||||
out.write("<br />The blog " + uri.getKeyHash().toBase64() + " is not known locally. "
|
||||
+ "Please get it from an archive and <a href=\""
|
||||
+ getPageURL(uri.getKeyHash(), null, uri.getEntryId(), -1, -1, user.getShowExpanded(), user.getShowImages())
|
||||
+ "\">try again</a>");
|
||||
else
|
||||
out.write("<br />The blog <a href=\""
|
||||
+ getPageURL(uri.getKeyHash(), null, -1, -1, -1, user.getShowExpanded(), user.getShowImages())
|
||||
+ "\">" + info.getProperty(BlogInfo.NAME) + "</a> is known, but the entry " + uri.getEntryId() + " is not. "
|
||||
+ "Please get it from an archive and <a href=\""
|
||||
+ getPageURL(uri.getKeyHash(), null, uri.getEntryId(), -1, -1, user.getShowExpanded(), user.getShowImages())
|
||||
+ "\">try again</a>");
|
||||
}
|
||||
|
||||
public void render(User user, Archive archive, EntryContainer entry, Writer out, boolean cutBody, boolean showImages) throws IOException {
|
||||
if (entry == null)
|
||||
return;
|
||||
render(user, archive, entry, entry.getEntry().getText(), out, cutBody, showImages);
|
||||
}
|
||||
public void render(User user, Archive archive, EntryContainer entry, String rawSML, Writer out, boolean cutBody, boolean showImages) throws IOException {
|
||||
_user = user;
|
||||
_archive = archive;
|
||||
_entry = entry;
|
||||
_out = out;
|
||||
_headers = new HashMap();
|
||||
_preBodyBuffer = new StringBuffer(1024);
|
||||
_bodyBuffer = new StringBuffer(1024);
|
||||
_postBodyBuffer = new StringBuffer(1024);
|
||||
_addresses = new ArrayList();
|
||||
_links = new ArrayList();
|
||||
_blogs = new ArrayList();
|
||||
_cutBody = cutBody;
|
||||
_showImages = showImages;
|
||||
_cutReached = false;
|
||||
_cutSize = 1024;
|
||||
_parser.parse(rawSML, this);
|
||||
_out.write(_preBodyBuffer.toString());
|
||||
_out.write(_bodyBuffer.toString());
|
||||
_out.write(_postBodyBuffer.toString());
|
||||
//int len = _preBodyBuffer.length() + _bodyBuffer.length() + _postBodyBuffer.length();
|
||||
//System.out.println("Wrote " + len);
|
||||
}
|
||||
|
||||
public void receivePlain(String text) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append(sanitizeString(text));
|
||||
}
|
||||
|
||||
public void receiveBold(String text) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<b>").append(sanitizeString(text)).append("</b>");
|
||||
}
|
||||
public void receiveItalic(String text) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<i>").append(sanitizeString(text)).append("</i>");
|
||||
}
|
||||
public void receiveUnderline(String text) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<u>").append(sanitizeString(text)).append("</u>");
|
||||
}
|
||||
public void receiveHR() {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<hr />");
|
||||
}
|
||||
public void receiveH1(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<h1>").append(sanitizeString(body)).append("</h1>");
|
||||
}
|
||||
public void receiveH2(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<h2>").append(sanitizeString(body)).append("</h2>");
|
||||
}
|
||||
public void receiveH3(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<h3>").append(sanitizeString(body)).append("</h3>");
|
||||
}
|
||||
public void receiveH4(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<h4>").append(sanitizeString(body)).append("</h4>");
|
||||
}
|
||||
public void receiveH5(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<h5>").append(sanitizeString(body)).append("</h5>");
|
||||
}
|
||||
public void receivePre(String body) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<pre>").append(sanitizeString(body)).append("</pre>");
|
||||
}
|
||||
|
||||
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<quote>").append(sanitizeString(text)).append("</quote>");
|
||||
}
|
||||
public void receiveCode(String text, String codeLocationSchema, String codeLocation) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<code>").append(sanitizeString(text)).append("</code>");
|
||||
}
|
||||
public void receiveImage(String alternateText, int attachmentId) {
|
||||
if (!continueBody()) { return; }
|
||||
if (_showImages) {
|
||||
_bodyBuffer.append("<img src=\"").append(getAttachmentURL(attachmentId)).append("\"");
|
||||
if (alternateText != null)
|
||||
_bodyBuffer.append(" alt=\"").append(sanitizeTagParam(alternateText)).append("\"");
|
||||
_bodyBuffer.append(" />");
|
||||
} else {
|
||||
_bodyBuffer.append("[image: attachment ").append(attachmentId);
|
||||
_bodyBuffer.append(": ").append(sanitizeString(alternateText));
|
||||
_bodyBuffer.append(" <a href=\"").append(getEntryURL(true)).append("\">view images</a>]");
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveCut(String summaryText) {
|
||||
if (!continueBody()) { return; }
|
||||
_cutReached = true;
|
||||
if (_cutBody) {
|
||||
_bodyBuffer.append("<a href=\"").append(getEntryURL()).append("\">");
|
||||
if ( (summaryText != null) && (summaryText.length() > 0) )
|
||||
_bodyBuffer.append(sanitizeString(summaryText));
|
||||
else
|
||||
_bodyBuffer.append("more inside...");
|
||||
_bodyBuffer.append("</a>\n");
|
||||
} else {
|
||||
if (summaryText != null)
|
||||
_bodyBuffer.append(sanitizeString(summaryText));
|
||||
}
|
||||
}
|
||||
|
||||
/** are we either before the cut or rendering without cutting? */
|
||||
private boolean continueBody() {
|
||||
boolean rv = ( (!_cutReached) && (_bodyBuffer.length() <= _cutSize) ) || (!_cutBody);
|
||||
//if (!rv)
|
||||
// System.out.println("rv: " + rv + " Cut reached: " + _cutReached + " bodyBufferSize: " + _bodyBuffer.length() + " cutBody? " + _cutBody);
|
||||
if (!rv && !_cutReached) {
|
||||
// exceeded the allowed size
|
||||
_bodyBuffer.append("<a href=\"").append(getEntryURL()).append("\">more inside...</a>");
|
||||
_cutReached = true;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void receiveNewline() {
|
||||
if (!continueBody()) { return; }
|
||||
if (true || (_lastNewlineAt >= _bodyBuffer.length()))
|
||||
_bodyBuffer.append("<br />\n");
|
||||
else
|
||||
_lastNewlineAt = _bodyBuffer.length();
|
||||
}
|
||||
public void receiveLT() {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append("<");
|
||||
}
|
||||
public void receiveGT() {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append(">");
|
||||
}
|
||||
public void receiveBegin() {}
|
||||
public void receiveLeftBracket() {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append('[');
|
||||
}
|
||||
public void receiveRightBracket() {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append(']');
|
||||
}
|
||||
|
||||
private static class Blog {
|
||||
public String name;
|
||||
public String hash;
|
||||
public String tag;
|
||||
public long entryId;
|
||||
public List locations;
|
||||
public int hashCode() { return -1; }
|
||||
public boolean equals(Object o) {
|
||||
Blog b = (Blog)o;
|
||||
return DataHelper.eq(hash, b.hash) && DataHelper.eq(tag, b.tag) && DataHelper.eq(name, b.name)
|
||||
&& DataHelper.eq(entryId, b.entryId) && DataHelper.eq(locations, b.locations);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* when we see a link to a blog, we may want to:
|
||||
* = view the blog entry
|
||||
* = view all entries in that blog
|
||||
* = view all entries in that blog with the given tag
|
||||
* = view the blog's metadata
|
||||
* = [fetch the blog from other locations]
|
||||
* = [add the blog's locations to our list of known locations]
|
||||
* = [shitlist the blog]
|
||||
* = [add the blog to one of our groups]
|
||||
*
|
||||
* [blah] implies *later*.
|
||||
*
|
||||
* Currently renders to:
|
||||
* <a href="$entryURL">$description</a>
|
||||
* [blog: <a href="$blogURL">$name</a> (<a href="$metaURL">meta</a>)
|
||||
* [tag: <a href="$blogTagURL">$tag</a>]
|
||||
* archived at $location*]
|
||||
*
|
||||
*/
|
||||
public void receiveBlog(String name, String hash, String tag, long entryId, List locations, String description) {
|
||||
Blog b = new Blog();
|
||||
b.name = name;
|
||||
b.hash = hash;
|
||||
b.tag = tag;
|
||||
b.entryId = entryId;
|
||||
b.locations = locations;
|
||||
if (!_blogs.contains(b))
|
||||
_blogs.add(b);
|
||||
|
||||
if (!continueBody()) { return; }
|
||||
if (hash == null) return;
|
||||
|
||||
System.out.println("Receiving the blog: " + name + "/" + hash + "/" + tag + "/" + entryId +"/" + locations + ": "+ description);
|
||||
byte blogData[] = Base64.decode(hash);
|
||||
if ( (blogData == null) || (blogData.length != Hash.HASH_LENGTH) )
|
||||
return;
|
||||
Hash blog = new Hash(blogData);
|
||||
if (entryId > 0) {
|
||||
String pageURL = getPageURL(blog, tag, entryId, -1, -1, true, (_user != null ? _user.getShowImages() : false));
|
||||
_bodyBuffer.append("<a href=\"").append(pageURL).append("\">");
|
||||
if ( (description != null) && (description.trim().length() > 0) ) {
|
||||
_bodyBuffer.append(sanitizeString(description));
|
||||
} else if ( (name != null) && (name.trim().length() > 0) ) {
|
||||
_bodyBuffer.append(sanitizeString(name));
|
||||
} else {
|
||||
_bodyBuffer.append("[view entry]");
|
||||
}
|
||||
_bodyBuffer.append("</a>");
|
||||
}
|
||||
|
||||
|
||||
String url = getPageURL(blog, null, -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false));
|
||||
_bodyBuffer.append(" [<a href=\"").append(url);
|
||||
_bodyBuffer.append("\">");
|
||||
if ( (name != null) && (name.trim().length() > 0) )
|
||||
_bodyBuffer.append(sanitizeString(name));
|
||||
else
|
||||
_bodyBuffer.append("view");
|
||||
_bodyBuffer.append("</a> (<a href=\"").append(getMetadataURL(blog)).append("\">meta</a>)");
|
||||
if ( (tag != null) && (tag.trim().length() > 0) ) {
|
||||
url = getPageURL(blog, tag, -1, -1, -1, false, false);
|
||||
_bodyBuffer.append(" <a href=\"").append(url);
|
||||
_bodyBuffer.append("\">Tag: ").append(sanitizeString(tag)).append("</a>");
|
||||
}
|
||||
if ( (locations != null) && (locations.size() > 0) ) {
|
||||
_bodyBuffer.append(" <select name=\"archiveLocation\">");
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
SafeURL surl = (SafeURL)locations.get(i);
|
||||
_bodyBuffer.append("<option value=\"").append(Base64.encode(surl.toString())).append("\">");
|
||||
_bodyBuffer.append(sanitizeString(surl.toString())).append("</option>\n");
|
||||
}
|
||||
_bodyBuffer.append("</select>");
|
||||
}
|
||||
_bodyBuffer.append("] ");
|
||||
}
|
||||
|
||||
private static class Link {
|
||||
public String schema;
|
||||
public String location;
|
||||
public int hashCode() { return -1; }
|
||||
public boolean equals(Object o) {
|
||||
Link l = (Link)o;
|
||||
return DataHelper.eq(schema, l.schema) && DataHelper.eq(location, l.location);
|
||||
}
|
||||
}
|
||||
public void receiveLink(String schema, String location, String text) {
|
||||
Link l = new Link();
|
||||
l.schema = schema;
|
||||
l.location = location;
|
||||
if (!_links.contains(l))
|
||||
_links.add(l);
|
||||
if (!continueBody()) { return; }
|
||||
if ( (schema == null) || (location == null) ) return;
|
||||
_bodyBuffer.append("<a href=\"externallink.jsp?schema=");
|
||||
_bodyBuffer.append(sanitizeURL(schema)).append("&location=");
|
||||
_bodyBuffer.append(sanitizeURL(location)).append("&description=");
|
||||
_bodyBuffer.append(sanitizeURL(text)).append("\">").append(sanitizeString(text)).append("</a>");
|
||||
}
|
||||
|
||||
private static class Address {
|
||||
public String name;
|
||||
public String schema;
|
||||
public String location;
|
||||
public int hashCode() { return -1; }
|
||||
public boolean equals(Object o) {
|
||||
Address a = (Address)o;
|
||||
return DataHelper.eq(schema, a.schema) && DataHelper.eq(location, a.location) && DataHelper.eq(name, a.name);
|
||||
}
|
||||
}
|
||||
public void receiveAddress(String name, String schema, String location, String anchorText) {
|
||||
Address a = new Address();
|
||||
a.name = name;
|
||||
a.schema = schema;
|
||||
a.location = location;
|
||||
if (!_addresses.contains(a))
|
||||
_addresses.add(a);
|
||||
if (!continueBody()) { return; }
|
||||
if ( (schema == null) || (location == null) ) return;
|
||||
_bodyBuffer.append("<a href=\"addaddress.jsp?schema=");
|
||||
_bodyBuffer.append(sanitizeURL(schema)).append("&name=");
|
||||
_bodyBuffer.append(sanitizeURL(name)).append("&location=");
|
||||
_bodyBuffer.append(sanitizeURL(location)).append("\">").append(sanitizeString(anchorText)).append("</a>");
|
||||
}
|
||||
|
||||
public void receiveAttachment(int id, String anchorText) {
|
||||
if (!continueBody()) { return; }
|
||||
Attachment attachments[] = _entry.getAttachments();
|
||||
if ( (id < 0) || (id >= attachments.length)) {
|
||||
_bodyBuffer.append(sanitizeString(anchorText));
|
||||
} else {
|
||||
_bodyBuffer.append("<a href=\"").append(getAttachmentURL(id)).append("\">");
|
||||
_bodyBuffer.append(sanitizeString(anchorText)).append("</a>");
|
||||
_bodyBuffer.append(" (").append(attachments[id].getDataLength()/1024).append("KB, ");
|
||||
_bodyBuffer.append(" \"").append(sanitizeString(attachments[id].getName())).append("\", ");
|
||||
_bodyBuffer.append(sanitizeString(attachments[id].getMimeType())).append(")");
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveEnd() {
|
||||
_postBodyBuffer.append("</td></tr>\n");
|
||||
_postBodyBuffer.append("<tr>\n");
|
||||
_postBodyBuffer.append("<form action=\"").append(getAttachmentURLBase()).append("\">\n");
|
||||
_postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_BLOG);
|
||||
_postBodyBuffer.append("\" value=\"");
|
||||
if (_entry != null)
|
||||
_postBodyBuffer.append(Base64.encode(_entry.getURI().getKeyHash().getData()));
|
||||
else
|
||||
_postBodyBuffer.append("unknown");
|
||||
_postBodyBuffer.append("\" />\n");
|
||||
_postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_ENTRY);
|
||||
_postBodyBuffer.append("\" value=\"");
|
||||
if (_entry != null)
|
||||
_postBodyBuffer.append(_entry.getURI().getEntryId());
|
||||
else
|
||||
_postBodyBuffer.append("unknown");
|
||||
_postBodyBuffer.append("\" />\n");
|
||||
_postBodyBuffer.append("<td valign=\"top\" align=\"left\" style=\"entry.attachments.cell\" bgcolor=\"#77ff77\">\n");
|
||||
|
||||
if ( (_entry != null) && (_entry.getAttachments() != null) && (_entry.getAttachments().length > 0) ) {
|
||||
_postBodyBuffer.append("<b>Attachments:</b> ");
|
||||
_postBodyBuffer.append("<select name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n");
|
||||
for (int i = 0; i < _entry.getAttachments().length; i++) {
|
||||
_postBodyBuffer.append("<option value=\"").append(i).append("\">");
|
||||
Attachment a = _entry.getAttachments()[i];
|
||||
_postBodyBuffer.append(sanitizeString(a.getName()));
|
||||
if ( (a.getDescription() != null) && (a.getDescription().trim().length() > 0) ) {
|
||||
_postBodyBuffer.append(": ");
|
||||
_postBodyBuffer.append(sanitizeString(a.getDescription()));
|
||||
}
|
||||
_postBodyBuffer.append(" (").append(a.getDataLength()/1024).append("KB");
|
||||
_postBodyBuffer.append(", type ").append(sanitizeString(a.getMimeType())).append(")</option>\n");
|
||||
}
|
||||
_postBodyBuffer.append("</select>\n");
|
||||
_postBodyBuffer.append("<input type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n");
|
||||
}
|
||||
|
||||
if (_blogs.size() > 0) {
|
||||
_postBodyBuffer.append("<b>Blog references:</b> ");
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
Blog b = (Blog)_blogs.get(i);
|
||||
_postBodyBuffer.append("<a href=\"").append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false)));
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> ");
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
}
|
||||
|
||||
if (_links.size() > 0) {
|
||||
_postBodyBuffer.append("<b>External links:</b> ");
|
||||
for (int i = 0; i < _links.size(); i++) {
|
||||
Link l = (Link)_links.get(i);
|
||||
_postBodyBuffer.append("<a href=\"externallink.jsp?schema=");
|
||||
_postBodyBuffer.append(sanitizeURL(l.schema)).append("&location=");
|
||||
_postBodyBuffer.append(sanitizeURL(l.location));
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location));
|
||||
_postBodyBuffer.append(" (").append(sanitizeString(l.schema)).append(")</a> ");
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
}
|
||||
|
||||
if (_addresses.size() > 0) {
|
||||
_postBodyBuffer.append("<b>Addresses:</b> ");
|
||||
for (int i = 0; i < _addresses.size(); i++) {
|
||||
Address a = (Address)_addresses.get(i);
|
||||
_postBodyBuffer.append("<a href=\"addaddress.jsp?schema=");
|
||||
_postBodyBuffer.append(sanitizeURL(a.schema)).append("&location=");
|
||||
_postBodyBuffer.append(sanitizeURL(a.location)).append("&name=");
|
||||
_postBodyBuffer.append(sanitizeURL(a.name));
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(a.name));
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
}
|
||||
|
||||
_postBodyBuffer.append("</td>\n</form>\n</tr>\n");
|
||||
_postBodyBuffer.append("</table>\n");
|
||||
}
|
||||
|
||||
public void receiveHeader(String header, String value) {
|
||||
System.err.println("Receive header [" + header + "] = [" + value + "]");
|
||||
_headers.put(header, value);
|
||||
}
|
||||
|
||||
public void receiveHeaderEnd() {
|
||||
renderMetaCell();
|
||||
renderSubjectCell();
|
||||
renderPreBodyCell();
|
||||
}
|
||||
|
||||
public static final String HEADER_SUBJECT = "Subject";
|
||||
public static final String HEADER_BGCOLOR = "bgcolor";
|
||||
public static final String HEADER_IN_REPLY_TO = "InReplyTo";
|
||||
|
||||
private void renderSubjectCell() {
|
||||
_preBodyBuffer.append("<td align=\"left\" valign=\"top\" style=\"entry.subject.cell\" bgcolor=\"#3355ff\">");
|
||||
String subject = (String)_headers.get(HEADER_SUBJECT);
|
||||
if (subject == null)
|
||||
subject = "[no subject]";
|
||||
_preBodyBuffer.append(sanitizeString(subject));
|
||||
_preBodyBuffer.append("</td></tr>\n");
|
||||
}
|
||||
|
||||
private void renderPreBodyCell() {
|
||||
String bgcolor = (String)_headers.get(HEADER_BGCOLOR);
|
||||
if (_cutBody)
|
||||
_preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" style=\"entry.summary.cell\" bgcolor=\"" + (bgcolor == null ? "#33ffff" : sanitizeTagParam(bgcolor)) + "\">");
|
||||
else
|
||||
_preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" style=\"entry.body.cell\" bgcolor=\"" + (bgcolor == null ? "#33ffff" : sanitizeTagParam(bgcolor)) + "\">");
|
||||
}
|
||||
|
||||
private void renderMetaCell() {
|
||||
_preBodyBuffer.append("<table width=\"100%\" border=\"0\">\n");
|
||||
_preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" rowspan=\"3\" style=\"entry.meta.cell\" bgcolor=\"#33ccff\">\n");
|
||||
BlogInfo info = null;
|
||||
if (_entry != null)
|
||||
info = _archive.getBlogInfo(_entry.getURI());
|
||||
if (info != null) {
|
||||
_preBodyBuffer.append("<a href=\"").append(getMetadataURL()).append("\">");
|
||||
String nameStr = info.getProperty("Name");
|
||||
if (nameStr == null)
|
||||
_preBodyBuffer.append("[no name]");
|
||||
else
|
||||
_preBodyBuffer.append(sanitizeString(nameStr));
|
||||
_preBodyBuffer.append("</a>");
|
||||
} else {
|
||||
_preBodyBuffer.append("[unknown blog]");
|
||||
}
|
||||
_preBodyBuffer.append("<br />\n");
|
||||
String tags[] = (_entry != null ? _entry.getTags() : null);
|
||||
_preBodyBuffer.append("<i>");
|
||||
for (int i = 0; tags != null && i < tags.length; i++) {
|
||||
_preBodyBuffer.append("<a href=\"");
|
||||
_preBodyBuffer.append(getPageURL(_entry.getURI().getKeyHash(), tags[i], -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false)));
|
||||
_preBodyBuffer.append("\">");
|
||||
_preBodyBuffer.append(sanitizeString(tags[i]));
|
||||
_preBodyBuffer.append("</a>");
|
||||
if (i + 1 < tags.length)
|
||||
_preBodyBuffer.append(", ");
|
||||
}
|
||||
_preBodyBuffer.append("</i><br /><font size=\"-1\">\n");
|
||||
if (_entry != null)
|
||||
_preBodyBuffer.append(getEntryDate(_entry.getURI().getEntryId()));
|
||||
else
|
||||
_preBodyBuffer.append(getEntryDate(new Date().getTime()));
|
||||
_preBodyBuffer.append("</font><br />");
|
||||
String inReplyTo = (String)_headers.get(HEADER_IN_REPLY_TO);
|
||||
System.err.println("In reply to: [" + inReplyTo + "]");
|
||||
if ( (inReplyTo != null) && (inReplyTo.trim().length() > 0) )
|
||||
_preBodyBuffer.append("<a href=\"").append(getPageURL(sanitizeTagParam(inReplyTo))).append("\">In reply to</a><br />\n");
|
||||
if ( (_user != null) && (_user.getAuthenticated()) )
|
||||
_preBodyBuffer.append("<a href=\"").append(getPostURL(_user.getBlog(), true)).append("\">Reply</a><br />\n");
|
||||
_preBodyBuffer.append("\n</td>\n");
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd");
|
||||
private final String getEntryDate(long when) {
|
||||
synchronized (_dateFormat) {
|
||||
try {
|
||||
String str = _dateFormat.format(new Date(when));
|
||||
long dayBegin = _dateFormat.parse(str).getTime();
|
||||
return str + "<br />" + (when - dayBegin);
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
// wtf
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final String sanitizeString(String str) {
|
||||
if (str == null) return null;
|
||||
if ( (str.indexOf('<') < 0) && (str.indexOf('>') < 0) )
|
||||
return str;
|
||||
str = str.replace('<', '_');
|
||||
str = str.replace('>', '-');
|
||||
return str;
|
||||
}
|
||||
|
||||
public static final String sanitizeURL(String str) { return Base64.encode(str); }
|
||||
public static final String sanitizeTagParam(String str) {
|
||||
if (str.indexOf('\"') < 0)
|
||||
return sanitizeString(str);
|
||||
str = str.replace('\"', '\'');
|
||||
return sanitizeString(str);
|
||||
}
|
||||
|
||||
private String getEntryURL() { return getEntryURL(_user != null ? _user.getShowImages() : false); }
|
||||
private String getEntryURL(boolean showImages) {
|
||||
if (_entry == null) return "unknown";
|
||||
return "index.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" +
|
||||
Base64.encode(_entry.getURI().getKeyHash().getData()) +
|
||||
"&" + ArchiveViewerBean.PARAM_ENTRY + "=" + _entry.getURI().getEntryId() +
|
||||
"&" + ArchiveViewerBean.PARAM_SHOW_IMAGES + (showImages ? "=true" : "=false") +
|
||||
"&" + ArchiveViewerBean.PARAM_EXPAND_ENTRIES + "=true";
|
||||
}
|
||||
|
||||
private String getAttachmentURLBase() { return "viewattachment.jsp"; }
|
||||
private String getAttachmentURL(int id) {
|
||||
if (_entry == null) return "unknown";
|
||||
return getAttachmentURLBase() + "?" +
|
||||
ArchiveViewerBean.PARAM_BLOG + "=" +
|
||||
Base64.encode(_entry.getURI().getKeyHash().getData()) +
|
||||
"&" + ArchiveViewerBean.PARAM_ENTRY + "=" + _entry.getURI().getEntryId() +
|
||||
"&" + ArchiveViewerBean.PARAM_ATTACHMENT + "=" + id;
|
||||
}
|
||||
|
||||
public String getMetadataURL() {
|
||||
if (_entry == null) return "unknown";
|
||||
return getMetadataURL(_entry.getURI().getKeyHash());
|
||||
}
|
||||
public static String getMetadataURL(Hash blog) {
|
||||
return "viewmetadata.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" +
|
||||
Base64.encode(blog.getData());
|
||||
}
|
||||
|
||||
public static String getPostURL(Hash blog) {
|
||||
return "post.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" + Base64.encode(blog.getData());
|
||||
}
|
||||
public String getPostURL(Hash blog, boolean asReply) {
|
||||
if (asReply && _entry != null) {
|
||||
return "post.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" + Base64.encode(blog.getData())
|
||||
+ "&" + ArchiveViewerBean.PARAM_IN_REPLY_TO + '='
|
||||
+ Base64.encode("entry://" + _entry.getURI().getKeyHash().toBase64() + "/" + _entry.getURI().getEntryId());
|
||||
} else {
|
||||
return getPostURL(blog);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPageURL(String selector) {
|
||||
return getPageURL(_user, selector);
|
||||
}
|
||||
|
||||
public static String getPageURL(User user, String selector) {
|
||||
ArchiveViewerBean.Selector sel = new ArchiveViewerBean.Selector(selector);
|
||||
return getPageURL(sel.blog, sel.tag, sel.entry, sel.group, -1, -1, user.getShowExpanded(), user.getShowImages());
|
||||
}
|
||||
|
||||
public static String getPageURL(Hash blog, String tag, long entryId, int numPerPage, int pageNum, boolean expandEntries, boolean showImages) {
|
||||
return getPageURL(blog, tag, entryId, null, numPerPage, pageNum, expandEntries, showImages);
|
||||
}
|
||||
public static String getPageURL(Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum, boolean expandEntries, boolean showImages) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("index.jsp?");
|
||||
if (blog != null)
|
||||
buf.append(ArchiveViewerBean.PARAM_BLOG).append('=').append(Base64.encode(blog.getData())).append('&');
|
||||
if (tag != null)
|
||||
buf.append(ArchiveViewerBean.PARAM_TAG).append('=').append(Base64.encode(tag)).append('&');
|
||||
if (entryId >= 0)
|
||||
buf.append(ArchiveViewerBean.PARAM_ENTRY).append('=').append(entryId).append('&');
|
||||
if (group != null)
|
||||
buf.append(ArchiveViewerBean.PARAM_GROUP).append('=').append(Base64.encode(group)).append('&');
|
||||
if ( (pageNum >= 0) && (numPerPage > 0) ) {
|
||||
buf.append(ArchiveViewerBean.PARAM_PAGE_NUMBER).append('=').append(pageNum).append('&');
|
||||
buf.append(ArchiveViewerBean.PARAM_NUM_PER_PAGE).append('=').append(numPerPage).append('&');
|
||||
}
|
||||
buf.append(ArchiveViewerBean.PARAM_EXPAND_ENTRIES).append('=').append(expandEntries).append('&');
|
||||
buf.append(ArchiveViewerBean.PARAM_SHOW_IMAGES).append('=').append(showImages).append('&');
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
432
apps/syndie/java/src/net/i2p/syndie/sml/SMLParser.java
Normal file
432
apps/syndie/java/src/net/i2p/syndie/sml/SMLParser.java
Normal file
@ -0,0 +1,432 @@
|
||||
package net.i2p.syndie.sml;
|
||||
|
||||
import java.lang.String;
|
||||
import java.util.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/**
|
||||
* Parse out the SML from the text, firing off info to the receiver whenever certain
|
||||
* elements are available. This is a very simple parser, with no support for nested
|
||||
* tags. A simple stack would be good to add, but DTSTTCPW.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SMLParser {
|
||||
private static final char TAG_BEGIN = '[';
|
||||
private static final char TAG_END = ']';
|
||||
private static final char LT = '<';
|
||||
private static final char GT = '>';
|
||||
private static final char EQ = '=';
|
||||
private static final char DQUOTE = '"';
|
||||
private static final char QUOTE = '\'';
|
||||
private static final String WHITESPACE = " \t\n\r";
|
||||
private static final char NL = '\n';
|
||||
private static final char CR = '\n';
|
||||
private static final char LF = '\f';
|
||||
|
||||
public void parse(String rawSML, EventReceiver receiver) {
|
||||
receiver.receiveBegin();
|
||||
int off = 0;
|
||||
off = parseHeaders(rawSML, off, receiver);
|
||||
receiver.receiveHeaderEnd();
|
||||
parseBody(rawSML, off, receiver);
|
||||
receiver.receiveEnd();
|
||||
}
|
||||
|
||||
private int parseHeaders(String rawSML, int off, EventReceiver receiver) {
|
||||
if (rawSML == null) return off;
|
||||
int len = rawSML.length();
|
||||
if (len == off) return off;
|
||||
int keyBegin = off;
|
||||
int valBegin = -1;
|
||||
while (off < len) {
|
||||
char c = rawSML.charAt(off);
|
||||
if ( (c == ':') && (valBegin < 0) ) {
|
||||
// moving on to the value
|
||||
valBegin = off + 1;
|
||||
} else if (c == '\n') {
|
||||
if (valBegin < 0) {
|
||||
// end of the headers
|
||||
off++;
|
||||
break;
|
||||
} else {
|
||||
String key = rawSML.substring(keyBegin, valBegin-1);
|
||||
String val = rawSML.substring(valBegin, off);
|
||||
receiver.receiveHeader(key.trim(), val.trim());
|
||||
valBegin = -1;
|
||||
keyBegin = off + 1;
|
||||
}
|
||||
}
|
||||
off++;
|
||||
}
|
||||
if ( (off >= len) && (valBegin > 0) ) {
|
||||
String key = rawSML.substring(keyBegin, valBegin-1);
|
||||
String val = rawSML.substring(valBegin, len);
|
||||
receiver.receiveHeader(key.trim(), val.trim());
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
private void parseBody(String rawSMLBody, int off, EventReceiver receiver) {
|
||||
if (rawSMLBody == null) return;
|
||||
int begin = off;
|
||||
int len = rawSMLBody.length();
|
||||
if (len <= off) return;
|
||||
int openTagBegin = -1;
|
||||
int openTagEnd = -1;
|
||||
int closeTagBegin = -1;
|
||||
int closeTagEnd = -1;
|
||||
while (off < len) {
|
||||
char c = rawSMLBody.charAt(off);
|
||||
if ( (c == NL) || (c == CR) || (c == LF) ) {
|
||||
if (openTagBegin < 0) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
receiver.receiveNewline();
|
||||
off++;
|
||||
begin = off;
|
||||
continue;
|
||||
} else {
|
||||
// ignore NL inside a tag or between tag blocks
|
||||
}
|
||||
} else if (c == TAG_BEGIN) {
|
||||
if ( (off + 1 < len) && (TAG_BEGIN == rawSMLBody.charAt(off+1))) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
receiver.receiveLeftBracket();
|
||||
off += 2;
|
||||
begin = off;
|
||||
continue;
|
||||
} else if (openTagBegin < 0) {
|
||||
// push everything seen and not accounted for into a plain area
|
||||
if (closeTagEnd < 0) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
} else {
|
||||
if (closeTagEnd + 1 < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(closeTagEnd+1, off));
|
||||
}
|
||||
openTagBegin = off;
|
||||
closeTagBegin = -1;
|
||||
begin = off + 1;
|
||||
} else {
|
||||
// ok, we are at the end of the tag, process it
|
||||
closeTagBegin = off;
|
||||
while ( (c != TAG_END) && (off < len) ) {
|
||||
off++;
|
||||
c = rawSMLBody.charAt(off);
|
||||
}
|
||||
parseTag(rawSMLBody, openTagBegin, openTagEnd, closeTagBegin, off, receiver);
|
||||
begin = off + 1;
|
||||
openTagBegin = -1;
|
||||
openTagEnd = -1;
|
||||
closeTagBegin = -1;
|
||||
closeTagEnd = -1;
|
||||
}
|
||||
} else if (c == TAG_END) {
|
||||
if ( (openTagBegin > 0) && (closeTagBegin < 0) ) {
|
||||
openTagEnd = off;
|
||||
} else if ( (off + 1 < len) && (TAG_END == rawSMLBody.charAt(off+1))) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
receiver.receiveRightBracket();
|
||||
off += 2;
|
||||
begin = off;
|
||||
continue;
|
||||
}
|
||||
} else if (c == LT) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
receiver.receiveLT();
|
||||
off++;
|
||||
begin = off;
|
||||
continue;
|
||||
} else if (c == GT) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
receiver.receiveGT();
|
||||
off++;
|
||||
begin = off;
|
||||
continue;
|
||||
}
|
||||
|
||||
off++;
|
||||
}
|
||||
if ( (off >= len) && (openTagBegin < 0) ) {
|
||||
if (closeTagEnd < 0) {
|
||||
if (begin < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(begin, off));
|
||||
} else {
|
||||
if (closeTagEnd + 1 < off)
|
||||
receiver.receivePlain(rawSMLBody.substring(closeTagEnd+1, off));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseTag(String source, int openTagBegin, int openTagEnd, int closeTagBegin, int closeTagEnd, EventReceiver receiver) {
|
||||
String tagName = getTagName(source, openTagBegin+1);
|
||||
Map attributes = getAttributes(source, openTagBegin+1+tagName.length(), openTagEnd);
|
||||
String body = null;
|
||||
if (openTagEnd + 1 >= closeTagBegin)
|
||||
body = "";
|
||||
else
|
||||
body = source.substring(openTagEnd+1, closeTagBegin);
|
||||
|
||||
//System.out.println("Receiving tag [" + tagName + "] w/ open [" + source.substring(openTagBegin+1, openTagEnd)
|
||||
// + "], close [" + source.substring(closeTagBegin+1, closeTagEnd) + "] body ["
|
||||
// + body + "] attributes: " + attributes);
|
||||
parseTag(tagName, attributes, body, receiver);
|
||||
}
|
||||
|
||||
private static final String T_BOLD = "b";
|
||||
private static final String T_ITALIC = "i";
|
||||
private static final String T_UNDERLINE = "u";
|
||||
private static final String T_CUT = "cut";
|
||||
private static final String T_IMAGE = "img";
|
||||
private static final String T_QUOTE = "quote";
|
||||
private static final String T_CODE = "code";
|
||||
private static final String T_BLOG = "blog";
|
||||
private static final String T_LINK = "link";
|
||||
private static final String T_ADDRESS = "address";
|
||||
private static final String T_H1 = "h1";
|
||||
private static final String T_H2 = "h2";
|
||||
private static final String T_H3 = "h3";
|
||||
private static final String T_H4 = "h4";
|
||||
private static final String T_H5 = "h5";
|
||||
private static final String T_HR = "hr";
|
||||
private static final String T_PRE = "pre";
|
||||
private static final String T_ATTACHMENT = "attachment";
|
||||
|
||||
private static final String P_ATTACHMENT = "attachment";
|
||||
private static final String P_WHO_QUOTED = "author";
|
||||
private static final String P_QUOTE_LOCATION = "location";
|
||||
private static final String P_CODE_LOCATION = "location";
|
||||
private static final String P_BLOG_NAME = "name";
|
||||
private static final String P_BLOG_HASH = "bloghash";
|
||||
private static final String P_BLOG_TAG = "blogtag";
|
||||
private static final String P_BLOG_ENTRY = "blogentry";
|
||||
private static final String P_LINK_LOCATION = "location";
|
||||
private static final String P_LINK_SCHEMA = "schema";
|
||||
private static final String P_ADDRESS_NAME = "name";
|
||||
private static final String P_ADDRESS_LOCATION = "location";
|
||||
private static final String P_ADDRESS_SCHEMA = "schema";
|
||||
private static final String P_ATTACHMENT_ID = "id";
|
||||
|
||||
private void parseTag(String tagName, Map attr, String body, EventReceiver receiver) {
|
||||
tagName = tagName.toLowerCase();
|
||||
if (T_BOLD.equals(tagName)) {
|
||||
receiver.receiveBold(body);
|
||||
} else if (T_ITALIC.equals(tagName)) {
|
||||
receiver.receiveItalic(body);
|
||||
} else if (T_UNDERLINE.equals(tagName)) {
|
||||
receiver.receiveUnderline(body);
|
||||
} else if (T_CUT.equals(tagName)) {
|
||||
receiver.receiveCut(body);
|
||||
} else if (T_IMAGE.equals(tagName)) {
|
||||
receiver.receiveImage(body, getInt(P_ATTACHMENT, attr));
|
||||
} else if (T_QUOTE.equals(tagName)) {
|
||||
receiver.receiveQuote(body, getString(P_WHO_QUOTED, attr), getSchema(P_QUOTE_LOCATION, attr), getLocation(P_QUOTE_LOCATION, attr));
|
||||
} else if (T_CODE.equals(tagName)) {
|
||||
receiver.receiveCode(body, getSchema(P_CODE_LOCATION, attr), getLocation(P_CODE_LOCATION, attr));
|
||||
} else if (T_BLOG.equals(tagName)) {
|
||||
List locations = new ArrayList();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String s = getString("archive" + i, attr);
|
||||
if (s != null)
|
||||
locations.add(new SafeURL(s));
|
||||
else
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
receiver.receiveBlog(getString(P_BLOG_NAME, attr), getString(P_BLOG_HASH, attr), getString(P_BLOG_TAG, attr),
|
||||
getLong(P_BLOG_ENTRY, attr), locations, body);
|
||||
} else if (T_LINK.equals(tagName)) {
|
||||
receiver.receiveLink(getString(P_LINK_SCHEMA, attr), getString(P_LINK_LOCATION, attr), body);
|
||||
} else if (T_ADDRESS.equals(tagName)) {
|
||||
receiver.receiveAddress(getString(P_ADDRESS_NAME, attr), getString(P_ADDRESS_SCHEMA, attr), getString(P_ADDRESS_LOCATION, attr), body);
|
||||
} else if (T_H1.equals(tagName)) {
|
||||
receiver.receiveH1(body);
|
||||
} else if (T_H2.equals(tagName)) {
|
||||
receiver.receiveH2(body);
|
||||
} else if (T_H3.equals(tagName)) {
|
||||
receiver.receiveH3(body);
|
||||
} else if (T_H4.equals(tagName)) {
|
||||
receiver.receiveH4(body);
|
||||
} else if (T_H5.equals(tagName)) {
|
||||
receiver.receiveH5(body);
|
||||
} else if (T_HR.equals(tagName)) {
|
||||
receiver.receiveHR();
|
||||
} else if (T_PRE.equals(tagName)) {
|
||||
receiver.receivePre(body);
|
||||
} else if (T_ATTACHMENT.equals(tagName)) {
|
||||
receiver.receiveAttachment((int)getLong(P_ATTACHMENT_ID, attr), body);
|
||||
} else {
|
||||
System.out.println("need to learn how to parse the tag [" + tagName + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private String getString(String param, Map attributes) { return (String)attributes.get(param); }
|
||||
private String getSchema(String param, Map attributes) {
|
||||
String url = getString(param, attributes);
|
||||
if (url != null) {
|
||||
SafeURL u = new SafeURL(url);
|
||||
return u.getSchema();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getLocation(String param, Map attributes) {
|
||||
String url = getString(param, attributes);
|
||||
if (url != null) {
|
||||
SafeURL u = new SafeURL(url);
|
||||
return u.getLocation();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getInt(String attributeName, Map attributes) {
|
||||
String val = (String)attributes.get(attributeName.toLowerCase());
|
||||
if (val != null) {
|
||||
try {
|
||||
return Integer.parseInt(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private long getLong(String attributeName, Map attributes) {
|
||||
String val = (String)attributes.get(attributeName.toLowerCase());
|
||||
if (val != null) {
|
||||
try {
|
||||
return Long.parseLong(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private String getTagName(String source, int nameStart) {
|
||||
int off = nameStart;
|
||||
while (true) {
|
||||
char c = source.charAt(off);
|
||||
if ( (c == TAG_END) || (WHITESPACE.indexOf(c) >= 0) )
|
||||
return source.substring(nameStart, off);
|
||||
off++;
|
||||
}
|
||||
}
|
||||
private Map getAttributes(String source, int attributesStart, int openTagEnd) {
|
||||
Map rv = new HashMap();
|
||||
int off = attributesStart;
|
||||
int nameStart = -1;
|
||||
int nameEnd = -1;
|
||||
int valStart = -1;
|
||||
int valEnd = -1;
|
||||
while (true) {
|
||||
char c = source.charAt(off);
|
||||
if ( (c == TAG_END) || (off >= openTagEnd) )
|
||||
break;
|
||||
if (WHITESPACE.indexOf(c) < 0) {
|
||||
if (nameStart < 0) {
|
||||
nameStart = off;
|
||||
} else if (c == EQ) {
|
||||
if (nameEnd < 0)
|
||||
nameEnd = off;
|
||||
} else if ( (c == QUOTE) || (c == DQUOTE) ) {
|
||||
if (valStart < 0) {
|
||||
valStart = off;
|
||||
} else {
|
||||
valEnd = off;
|
||||
|
||||
String name = source.substring(nameStart, nameEnd);
|
||||
String val = source.substring(valStart+1, valEnd);
|
||||
rv.put(name.trim(), val.trim());
|
||||
nameStart = -1;
|
||||
nameEnd = -1;
|
||||
valStart = -1;
|
||||
valEnd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
off++;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public interface EventReceiver {
|
||||
public void receiveHeader(String header, String value);
|
||||
public void receiveLink(String schema, String location, String text);
|
||||
/** @param blogArchiveLocations list of SafeURL */
|
||||
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId,
|
||||
List blogArchiveLocations, String anchorText);
|
||||
public void receiveArchive(String name, String description, String locationSchema, String location,
|
||||
String postingKey, String anchorText);
|
||||
public void receiveImage(String alternateText, int attachmentId);
|
||||
public void receiveAddress(String name, String schema, String location, String anchorText);
|
||||
public void receiveAttachment(int id, String anchorText);
|
||||
public void receiveBold(String text);
|
||||
public void receiveItalic(String text);
|
||||
public void receiveUnderline(String text);
|
||||
public void receiveH1(String text);
|
||||
public void receiveH2(String text);
|
||||
public void receiveH3(String text);
|
||||
public void receiveH4(String text);
|
||||
public void receiveH5(String text);
|
||||
public void receivePre(String text);
|
||||
public void receiveHR();
|
||||
public void receiveQuote(String text, String whoQuoted, String quoteLocationSchema, String quoteLocation);
|
||||
public void receiveCode(String text, String codeLocationSchema, String codeLocation);
|
||||
public void receiveCut(String summaryText);
|
||||
public void receivePlain(String text);
|
||||
public void receiveNewline();
|
||||
public void receiveLT();
|
||||
public void receiveGT();
|
||||
public void receiveLeftBracket();
|
||||
public void receiveRightBracket();
|
||||
public void receiveBegin();
|
||||
public void receiveEnd();
|
||||
public void receiveHeaderEnd();
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
test(null);
|
||||
test("");
|
||||
test("A: B");
|
||||
test("A: B\n");
|
||||
test("A: B\nC: D");
|
||||
test("A: B\nC: D\n");
|
||||
test("A: B\nC: D\n\n");
|
||||
|
||||
test("A: B\nC: D\n\nblah");
|
||||
test("A: B\nC: D\n\nblah[[");
|
||||
test("A: B\nC: D\n\nblah]]");
|
||||
test("A: B\nC: D\n\nblah]]blah");
|
||||
test("A: B\nC: D\n\nfoo[a]b[/a]bar");
|
||||
test("A: B\nC: D\n\nfoo[a]b[/a]bar[b][/b]");
|
||||
test("A: B\nC: D\n\nfoo[a]b[/a]bar[b][/b]baz");
|
||||
|
||||
test("A: B\nC: D\n\n<a href=\"http://odci.gov\">hi</a>");
|
||||
|
||||
test("A: B\n\n[a b='c']d[/a]");
|
||||
test("A: B\n\n[a b='c' d='e' f='g']h[/a]");
|
||||
test("A: B\n\n[a b='c' d='e' f='g']h[/a][a b='c' d='e' f='g']h[/a][a b='c' d='e' f='g']h[/a]");
|
||||
|
||||
test("A: B\n\n[a b='c' ]d[/a]");
|
||||
test("A: B\n\n[a b=\"c\" ]d[/a]");
|
||||
|
||||
test("A: B\n\n[b]This[/b] is [i]special[/i][cut]why?[/cut][u]because I say so[/u].\neven if you dont care");
|
||||
}
|
||||
private static void test(String rawSML) {
|
||||
SMLParser parser = new SMLParser();
|
||||
parser.parse(rawSML, new EventReceiverImpl());
|
||||
}
|
||||
}
|
496
apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java
Normal file
496
apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java
Normal file
@ -0,0 +1,496 @@
|
||||
package net.i2p.syndie.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ArchiveViewerBean {
|
||||
public static String getBlogName(String keyHash) {
|
||||
BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(new Hash(Base64.decode(keyHash)));
|
||||
if (info == null)
|
||||
return HTMLRenderer.sanitizeString(keyHash);
|
||||
else
|
||||
return HTMLRenderer.sanitizeString(info.getProperty("Name"));
|
||||
}
|
||||
public static String getEntryTitle(String keyHash, long entryId) {
|
||||
String name = getBlogName(keyHash);
|
||||
return getEntryTitleDate(name, entryId);
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd");
|
||||
private static final String getEntryTitleDate(String blogName, long when) {
|
||||
synchronized (_dateFormat) {
|
||||
try {
|
||||
String str = _dateFormat.format(new Date(when));
|
||||
long dayBegin = _dateFormat.parse(str).getTime();
|
||||
return blogName + ":<br /> <i>" + str + "-" + (when - dayBegin) + "</i>";
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
// wtf
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** base64 encoded hash of the blog's public key, or null for no filtering by blog */
|
||||
public static final String PARAM_BLOG = "blog";
|
||||
/** base64 encoded tag to filter by, or blank for no filtering by tags */
|
||||
public static final String PARAM_TAG = "tag";
|
||||
/** entry id within the blog if we only want to see that one */
|
||||
public static final String PARAM_ENTRY = "entry";
|
||||
/** base64 encoded group within the user's filters */
|
||||
public static final String PARAM_GROUP = "group";
|
||||
/** how many entries per page to show at once */
|
||||
public static final String PARAM_NUM_PER_PAGE = "pageSize";
|
||||
/** which page of entries to render */
|
||||
public static final String PARAM_PAGE_NUMBER = "pageNum";
|
||||
/** should we expand each entry to show the full contents */
|
||||
public static final String PARAM_EXPAND_ENTRIES = "expand";
|
||||
/** should entries be rendered with the images shown inline */
|
||||
public static final String PARAM_SHOW_IMAGES = "images";
|
||||
/** should we regenerate an index to the archive before rendering */
|
||||
public static final String PARAM_REGENERATE_INDEX = "regenerateIndex";
|
||||
/** which attachment should we serve up raw */
|
||||
public static final String PARAM_ATTACHMENT = "attachment";
|
||||
/** we are replying to a particular blog/tag/entry/whatever (value == base64 encoded selector) */
|
||||
public static final String PARAM_IN_REPLY_TO = "inReplyTo";
|
||||
|
||||
/**
|
||||
* Drop down multichooser:
|
||||
* blog://base64(key)
|
||||
* tag://base64(tag)
|
||||
* blogtag://base64(key)/base64(tag)
|
||||
* entry://base64(key)/entryId
|
||||
* group://base64(groupName)
|
||||
* ALL
|
||||
*/
|
||||
public static final String PARAM_SELECTOR = "selector";
|
||||
public static final String SEL_ALL = "ALL";
|
||||
public static final String SEL_BLOG = "blog://";
|
||||
public static final String SEL_TAG = "tag://";
|
||||
public static final String SEL_BLOGTAG = "blogtag://";
|
||||
public static final String SEL_ENTRY = "entry://";
|
||||
public static final String SEL_GROUP = "group://";
|
||||
|
||||
public static void renderBlogSelector(User user, Map parameters, Writer out) throws IOException {
|
||||
out.write("<select name=\"");
|
||||
out.write(PARAM_SELECTOR);
|
||||
out.write("\">");
|
||||
out.write("<option value=\"");
|
||||
out.write(SEL_ALL);
|
||||
out.write("\">All posts from all blogs</option>\n");
|
||||
|
||||
Map groups = null;
|
||||
if (user != null)
|
||||
groups = user.getBlogGroups();
|
||||
if (groups != null) {
|
||||
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
out.write("<option value=\"group://" + Base64.encode(name.getBytes()) + "\">" +
|
||||
"Group: " + HTMLRenderer.sanitizeString(name) + "</option>\n");
|
||||
}
|
||||
}
|
||||
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
ArchiveIndex index = archive.getIndex();
|
||||
List allTags = new ArrayList();
|
||||
// perhaps sort this by name (even though it isnt unique...)
|
||||
Set blogs = index.getUniqueBlogs();
|
||||
for (Iterator iter = blogs.iterator(); iter.hasNext(); ) {
|
||||
Hash cur = (Hash)iter.next();
|
||||
String blog = Base64.encode(cur.getData());
|
||||
out.write("<option value=\"blog://");
|
||||
out.write(blog);
|
||||
out.write("\">");
|
||||
BlogInfo info = archive.getBlogInfo(cur);
|
||||
String name = info.getProperty(BlogInfo.NAME);
|
||||
if (name != null)
|
||||
name = HTMLRenderer.sanitizeString(name);
|
||||
else
|
||||
name = Base64.encode(cur.getData());
|
||||
out.write(name);
|
||||
out.write("- all posts</option>\n");
|
||||
|
||||
List tags = index.getBlogTags(cur);
|
||||
for (int j = 0; j < tags.size(); j++) {
|
||||
String tag = (String)tags.get(j);
|
||||
if (!allTags.contains(tag))
|
||||
allTags.add(tag);
|
||||
out.write("<option value=\"blogtag://");
|
||||
out.write(blog);
|
||||
out.write("/");
|
||||
out.write(Base64.encode(tag));
|
||||
out.write("\">");
|
||||
out.write(name);
|
||||
out.write("- posts with the tag "");
|
||||
out.write(tag);
|
||||
out.write(""</option>\n");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < allTags.size(); i++) {
|
||||
String tag = (String)allTags.get(i);
|
||||
out.write("<option value=\"tag://");
|
||||
out.write(Base64.encode(tag));
|
||||
out.write("\">Posts in any blog with the tag "");
|
||||
out.write(tag);
|
||||
out.write(""</option>\n");
|
||||
}
|
||||
out.write("</select>");
|
||||
|
||||
int numPerPage = getInt(parameters, PARAM_NUM_PER_PAGE, 5);
|
||||
int pageNum = getInt(parameters, PARAM_PAGE_NUMBER, 0);
|
||||
boolean expandEntries = getBool(parameters, PARAM_EXPAND_ENTRIES, (user != null ? user.getShowExpanded() : false));
|
||||
boolean showImages = getBool(parameters, PARAM_SHOW_IMAGES, (user != null ? user.getShowImages() : false));
|
||||
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NUM_PER_PAGE+ "\" value=\"" + numPerPage+ "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PAGE_NUMBER+ "\" value=\"" + pageNum+ "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_EXPAND_ENTRIES+ "\" value=\"" + expandEntries+ "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_SHOW_IMAGES + "\" value=\"" + showImages + "\" />");
|
||||
|
||||
}
|
||||
|
||||
public static void renderBlogs(User user, Map parameters, Writer out) throws IOException {
|
||||
String blogStr = getString(parameters, PARAM_BLOG);
|
||||
Hash blog = null;
|
||||
if (blogStr != null) blog = new Hash(Base64.decode(blogStr));
|
||||
String tag = getString(parameters, PARAM_TAG);
|
||||
if (tag != null) tag = new String(Base64.decode(tag));
|
||||
long entryId = -1;
|
||||
if (blogStr != null) {
|
||||
String entryIdStr = getString(parameters, PARAM_ENTRY);
|
||||
try {
|
||||
entryId = Long.parseLong(entryIdStr);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
String group = getString(parameters, PARAM_GROUP);
|
||||
if (group != null) group = new String(Base64.decode(group));
|
||||
|
||||
String sel = getString(parameters, PARAM_SELECTOR);
|
||||
if (sel != null) {
|
||||
Selector s = new Selector(sel);
|
||||
blog = s.blog;
|
||||
tag = s.tag;
|
||||
entryId = s.entry;
|
||||
group = s.group;
|
||||
}
|
||||
|
||||
int numPerPage = getInt(parameters, PARAM_NUM_PER_PAGE, 5);
|
||||
int pageNum = getInt(parameters, PARAM_PAGE_NUMBER, 0);
|
||||
boolean expandEntries = getBool(parameters, PARAM_EXPAND_ENTRIES, (user != null ? user.getShowExpanded() : false));
|
||||
boolean showImages = getBool(parameters, PARAM_SHOW_IMAGES, (user != null ? user.getShowImages() : false));
|
||||
boolean regenerateIndex = getBool(parameters, PARAM_REGENERATE_INDEX, false);
|
||||
try {
|
||||
renderBlogs(user, blog, tag, entryId, group, numPerPage, pageNum, expandEntries, showImages, regenerateIndex, out);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
throw ioe;
|
||||
} catch (RuntimeException re) {
|
||||
re.printStackTrace();
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Selector {
|
||||
public Hash blog;
|
||||
public String tag;
|
||||
public long entry;
|
||||
public String group;
|
||||
public Selector(String selector) {
|
||||
entry = -1;
|
||||
blog = null;
|
||||
tag = null;
|
||||
if (selector != null) {
|
||||
if (selector.startsWith(SEL_BLOG)) {
|
||||
String blogStr = selector.substring(SEL_BLOG.length());
|
||||
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "]");
|
||||
blog = new Hash(Base64.decode(blogStr));
|
||||
} else if (selector.startsWith(SEL_BLOGTAG)) {
|
||||
int tagStart = selector.lastIndexOf('/');
|
||||
String blogStr = selector.substring(SEL_BLOGTAG.length(), tagStart);
|
||||
blog = new Hash(Base64.decode(blogStr));
|
||||
tag = selector.substring(tagStart+1);
|
||||
if (tag != null) tag = new String(Base64.decode(tag));
|
||||
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] tag: [" + tag + "]");
|
||||
} else if (selector.startsWith(SEL_TAG)) {
|
||||
tag = selector.substring(SEL_TAG.length());
|
||||
if (tag != null) tag = new String(Base64.decode(tag));
|
||||
System.out.println("Selector [" + selector + "] tag: [" + tag + "]");
|
||||
} else if (selector.startsWith(SEL_ENTRY)) {
|
||||
int entryStart = selector.lastIndexOf('/');
|
||||
String blogStr = selector.substring(SEL_ENTRY.length(), entryStart);
|
||||
String entryStr = selector.substring(entryStart+1);
|
||||
try {
|
||||
entry = Long.parseLong(entryStr);
|
||||
blog = new Hash(Base64.decode(blogStr));
|
||||
System.out.println("Selector [" + selector + "] blogString: [" + blogStr + "] entry: [" + entry + "]");
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (selector.startsWith(SEL_GROUP)) {
|
||||
group = new String(Base64.decode(selector.substring(SEL_GROUP.length())));
|
||||
System.out.println("Selector [" + selector + "] group: [" + group + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderBlogs(User user, Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum,
|
||||
boolean expandEntries, boolean showImages, boolean regenerateIndex, Writer out) throws IOException {
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
if (regenerateIndex)
|
||||
archive.regenerateIndex();
|
||||
ArchiveIndex index = archive.getIndex();
|
||||
List entries = pickEntryURIs(user, index, blog, tag, entryId, group);
|
||||
System.out.println("Searching for " + blog + "/" + tag + "/" + entryId + "/" + pageNum + "/" + numPerPage + "/" + group);
|
||||
System.out.println("Entry URIs: " + entries);
|
||||
|
||||
HTMLRenderer renderer = new HTMLRenderer();
|
||||
int start = pageNum * numPerPage;
|
||||
int end = start + numPerPage;
|
||||
int pages = 1;
|
||||
if (entries.size() <= 1) {
|
||||
// just one, so no pagination, etc
|
||||
start = 0;
|
||||
end = 1;
|
||||
} else {
|
||||
if (end >= entries.size())
|
||||
end = entries.size();
|
||||
if ( (pageNum < 0) || (numPerPage <= 0) ) {
|
||||
start = 0;
|
||||
end = entries.size() - 1;
|
||||
} else {
|
||||
pages = entries.size() / numPerPage;
|
||||
if (numPerPage * pages < entries.size())
|
||||
pages++;
|
||||
out.write("<i>");
|
||||
if (pageNum > 0) {
|
||||
String prevURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum-1, expandEntries, showImages);
|
||||
System.out.println("prevURL: " + prevURL);
|
||||
out.write(" <a href=\"" + prevURL + "\"><<</a>");
|
||||
} else {
|
||||
out.write(" << ");
|
||||
}
|
||||
out.write("Page " + (pageNum+1) + " of " + pages);
|
||||
if (pageNum + 1 < pages) {
|
||||
String nextURL = HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum+1, expandEntries, showImages);
|
||||
System.out.println("nextURL: " + nextURL);
|
||||
out.write(" <a href=\"" + nextURL + "\">>></a>");
|
||||
} else {
|
||||
out.write(" >>");
|
||||
}
|
||||
out.write("</i>");
|
||||
}
|
||||
}
|
||||
|
||||
out.write(" <i>");
|
||||
|
||||
if (showImages)
|
||||
out.write("<a href=\"" + HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum, expandEntries, false) +
|
||||
"\">Hide images</a>");
|
||||
else
|
||||
out.write("<a href=\"" + HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum, expandEntries, true) +
|
||||
"\">Show images</a>");
|
||||
|
||||
if (expandEntries)
|
||||
out.write(" <a href=\"" + HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum, false, showImages) +
|
||||
"\">Hide details</a>");
|
||||
else
|
||||
out.write(" <a href=\"" + HTMLRenderer.getPageURL(blog, tag, entryId, group, numPerPage, pageNum, true, showImages) +
|
||||
"\">Expand details</a>");
|
||||
|
||||
out.write("</i>");
|
||||
|
||||
if (entries.size() <= 0) end = -1;
|
||||
System.out.println("Entries.size: " + entries.size() + " start=" + start + " end=" + end);
|
||||
for (int i = start; i < end; i++) {
|
||||
BlogURI uri = (BlogURI)entries.get(i);
|
||||
EntryContainer c = archive.getEntry(uri);
|
||||
try {
|
||||
if (c == null)
|
||||
renderer.renderUnknownEntry(user, archive, uri, out);
|
||||
else
|
||||
renderer.render(user, archive, c, out, !expandEntries, showImages);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List pickEntryURIs(User user, ArchiveIndex index, Hash blog, String tag, long entryId, String group) {
|
||||
List rv = new ArrayList(16);
|
||||
if ( (blog != null) && (entryId >= 0) ) {
|
||||
rv.add(new BlogURI(blog, entryId));
|
||||
return rv;
|
||||
}
|
||||
|
||||
if ( (group != null) && (user != null) ) {
|
||||
List selectors = (List)user.getBlogGroups().get(group);
|
||||
if (selectors != null) {
|
||||
System.out.println("Selectors for group " + group + ": " + selectors);
|
||||
for (int i = 0; i < selectors.size(); i++) {
|
||||
String sel = (String)selectors.get(i);
|
||||
Selector s = new Selector(sel);
|
||||
if ( (s.entry >= 0) && (s.blog != null) && (s.group == null) && (s.tag == null) )
|
||||
rv.add(new BlogURI(s.blog, s.entry));
|
||||
else
|
||||
index.selectMatchesOrderByEntryId(rv, s.blog, s.tag);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
index.selectMatchesOrderByEntryId(rv, blog, tag);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final String getString(Map parameters, String param) {
|
||||
if ( (parameters == null) || (parameters.get(param) == null) )
|
||||
return null;
|
||||
Object vals = parameters.get(param);
|
||||
if (vals.getClass().isArray()) {
|
||||
String v[] = (String[])vals;
|
||||
if (v.length > 0)
|
||||
return ((String[])vals)[0];
|
||||
else
|
||||
return null;
|
||||
} else if (vals instanceof Collection) {
|
||||
Collection c = (Collection)vals;
|
||||
if (c.size() > 0)
|
||||
return (String)c.iterator().next();
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int getInt(Map param, String key, int defaultVal) {
|
||||
String val = getString(param, key);
|
||||
if (val != null) {
|
||||
try { return Integer.parseInt(val); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
private static final boolean getBool(Map param, String key, boolean defaultVal) {
|
||||
String val = getString(param, key);
|
||||
if (val != null) {
|
||||
return ("true".equals(val) || "yes".equals(val));
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public static void renderAttachment(Map parameters, OutputStream out) throws IOException {
|
||||
Attachment a = getAttachment(parameters);
|
||||
if (a == null) {
|
||||
renderInvalidAttachment(parameters, out);
|
||||
} else {
|
||||
InputStream data = a.getDataStream();
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = data.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
data.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String getAttachmentContentType(Map parameters) {
|
||||
Attachment a = getAttachment(parameters);
|
||||
if (a == null)
|
||||
return "text/html";
|
||||
String mime = a.getMimeType();
|
||||
if ( (mime != null) && ((mime.startsWith("image/") || mime.startsWith("text/plain"))) )
|
||||
return mime;
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
public static final int getAttachmentContentLength(Map parameters) {
|
||||
Attachment a = getAttachment(parameters);
|
||||
if (a != null)
|
||||
return a.getDataLength();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static final Attachment getAttachment(Map parameters) {
|
||||
String blogStr = getString(parameters, PARAM_BLOG);
|
||||
Hash blog = null;
|
||||
if (blogStr != null) blog = new Hash(Base64.decode(blogStr));
|
||||
long entryId = -1;
|
||||
if (blogStr != null) {
|
||||
String entryIdStr = getString(parameters, PARAM_ENTRY);
|
||||
try {
|
||||
entryId = Long.parseLong(entryIdStr);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
int attachment = getInt(parameters, PARAM_ATTACHMENT, -1);
|
||||
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
EntryContainer entry = archive.getEntry(new BlogURI(blog, entryId));
|
||||
if ( (entry != null) && (attachment >= 0) && (attachment < entry.getAttachments().length) ) {
|
||||
return entry.getAttachments()[attachment];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void renderInvalidAttachment(Map parameters, OutputStream out) throws IOException {
|
||||
out.write("<b>No such entry, or no such attachment</b>".getBytes());
|
||||
}
|
||||
|
||||
public static void renderMetadata(Map parameters, Writer out) throws IOException {
|
||||
String blogStr = getString(parameters, PARAM_BLOG);
|
||||
if (blogStr != null) {
|
||||
Hash blog = new Hash(Base64.decode(blogStr));
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
BlogInfo info = archive.getBlogInfo(blog);
|
||||
if (info == null) {
|
||||
out.write("Blog " + blog.toBase64() + " does not exist");
|
||||
return;
|
||||
}
|
||||
String props[] = info.getProperties();
|
||||
out.write("<table border=\"0\">");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (props[i].equals(BlogInfo.OWNER_KEY)) {
|
||||
out.write("<tr><td><b>Blog:</b></td><td>");
|
||||
String blogURL = HTMLRenderer.getPageURL(blog, null, -1, -1, -1, false, false);
|
||||
out.write("<a href=\"" + blogURL + "\">" + Base64.encode(blog.getData()) + "</td></tr>\n");
|
||||
} else if (props[i].equals(BlogInfo.SIGNATURE)) {
|
||||
continue;
|
||||
} else if (props[i].equals(BlogInfo.POSTERS)) {
|
||||
SigningPublicKey keys[] = info.getPosters();
|
||||
if ( (keys != null) && (keys.length > 0) ) {
|
||||
out.write("<tr><td><b>Allowed authors:</b></td><td>");
|
||||
for (int j = 0; j < keys.length; j++) {
|
||||
out.write(keys[j].calculateHash().toBase64());
|
||||
if (j + 1 < keys.length)
|
||||
out.write("<br />\n");
|
||||
}
|
||||
out.write("</td></tr>\n");
|
||||
}
|
||||
} else {
|
||||
out.write("<tr><td>" + HTMLRenderer.sanitizeString(props[i]) + ":</td><td>" +
|
||||
HTMLRenderer.sanitizeString(info.getProperty(props[i])) + "</td></tr>\n");
|
||||
}
|
||||
}
|
||||
List tags = BlogManager.instance().getArchive().getIndex().getBlogTags(blog);
|
||||
if ( (tags != null) && (tags.size() > 0) ) {
|
||||
out.write("<tr><td>Known tags:</td><td>");
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
String tag = (String)tags.get(i);
|
||||
out.write("<a href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, false, false) + "\">" +
|
||||
HTMLRenderer.sanitizeString(tag) + "</a> ");
|
||||
}
|
||||
out.write("</td></tr>");
|
||||
}
|
||||
out.write("</table>");
|
||||
} else {
|
||||
out.write("Blog not specified");
|
||||
}
|
||||
}
|
||||
}
|
8
apps/syndie/jsp/_bodyindex.jsp
Normal file
8
apps/syndie/jsp/_bodyindex.jsp
Normal file
@ -0,0 +1,8 @@
|
||||
<%@page import="net.i2p.syndie.web.ArchiveViewerBean, net.i2p.syndie.*" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<form action="index.jsp">
|
||||
<b>Blogs:</b> <%ArchiveViewerBean.renderBlogSelector(user, request.getParameterMap(), out);%>
|
||||
<input type="submit" value="Refresh" /></form>
|
||||
<hr />
|
||||
|
||||
<%ArchiveViewerBean.renderBlogs(user, request.getParameterMap(), out); out.flush(); %>
|
28
apps/syndie/jsp/_leftnav.jsp
Normal file
28
apps/syndie/jsp/_leftnav.jsp
Normal file
@ -0,0 +1,28 @@
|
||||
<%@page import="net.i2p.syndie.web.ArchiveViewerBean, net.i2p.syndie.*, net.i2p.data.Base64" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.data.ArchiveIndex" id="archive" />
|
||||
<%if (request.getRequestURI().indexOf("register.jsp") == -1) {%>
|
||||
<jsp:include page="_leftnavprotected.jsp" />
|
||||
<hr />
|
||||
<% } %>
|
||||
<u>Local archive:</u><br />
|
||||
<b>Posts:</b> <jsp:getProperty name="archive" property="newEntries" />/<jsp:getProperty name="archive" property="allEntries" /><br />
|
||||
<b>Blogs:</b> <jsp:getProperty name="archive" property="newBlogs" />/<jsp:getProperty name="archive" property="allBlogs" /><br />
|
||||
<b>Size:</b> <jsp:getProperty name="archive" property="newSizeStr" />/<jsp:getProperty name="archive" property="totalSizeStr" /><br />
|
||||
<i>(new/total)</i>
|
||||
<hr />
|
||||
<u>Latest blogs:</u><br />
|
||||
<%!int i = 0; %>
|
||||
<%for (i = 0; i < archive.getNewestBlogCount(); i++) {
|
||||
String keyHash = Base64.encode(archive.getNewestBlog(i).getData());
|
||||
%><a href="viewmetadata.jsp?<%=ArchiveViewerBean.PARAM_BLOG%>=<%=keyHash%>">
|
||||
<%=ArchiveViewerBean.getBlogName(keyHash)%></a><br />
|
||||
<% } %>
|
||||
<hr />
|
||||
<u>Latest posts:</u><br />
|
||||
<%for (i = 0; i < archive.getNewestBlogEntryCount(); i++) {
|
||||
String keyHash = Base64.encode(archive.getNewestBlogEntry(i).getKeyHash().getData());
|
||||
long entryId = archive.getNewestBlogEntry(i).getEntryId();
|
||||
%><a href="index.jsp?<%=ArchiveViewerBean.PARAM_BLOG%>=<%=keyHash%>&<%=ArchiveViewerBean.PARAM_ENTRY%>=<%=entryId%>&<%=ArchiveViewerBean.PARAM_EXPAND_ENTRIES%>=true">
|
||||
<%=ArchiveViewerBean.getEntryTitle(keyHash, entryId)%></a><br />
|
||||
<% } %>
|
41
apps/syndie/jsp/_leftnavprotected.jsp
Normal file
41
apps/syndie/jsp/_leftnavprotected.jsp
Normal file
@ -0,0 +1,41 @@
|
||||
<%@page import="net.i2p.syndie.*, net.i2p.syndie.sml.*" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.data.ArchiveIndex" id="archive" />
|
||||
<%
|
||||
if ("true".equals(request.getParameter("logout"))) {
|
||||
user.invalidate();
|
||||
}
|
||||
String login = request.getParameter("login");
|
||||
String pass = request.getParameter("password");
|
||||
String loginSubmit = request.getParameter("Login");
|
||||
if ( (login != null) && (pass != null) && (loginSubmit != null) && (loginSubmit.equals("Login")) ) {
|
||||
String loginResult = BlogManager.instance().login(user, login, pass);
|
||||
out.write("<b>" + loginResult + "</b>");
|
||||
}
|
||||
|
||||
if (user.getAuthenticated()) {%>
|
||||
<b><u><jsp:getProperty property="username" name="user" />:</u></b><br />
|
||||
<a href="<%=HTMLRenderer.getPageURL(user.getBlog(), null, -1, -1, -1, user.getShowExpanded(), user.getShowImages())%>">My blog</a><br />
|
||||
<a href="<%=HTMLRenderer.getPageURL(user.getBlog(), null, user.getMostRecentEntry(), -1, -1, true, user.getShowImages())%>">Last post</a><br />
|
||||
<a href="<%=HTMLRenderer.getPostURL(user.getBlog())%>">Post</a><br />
|
||||
<a href="<%=HTMLRenderer.getMetadataURL(user.getBlog())%>">Metadata</a><br />
|
||||
<a href="index.jsp?logout=true">Logout</a><br />
|
||||
<!--
|
||||
<hr />
|
||||
<u>Remote Archives:</u><br />
|
||||
<a href="viewarchive.jsp?url=eep://politics.i2p/archive/">politics.i2p</a><br />
|
||||
<a href="viewarchive.jsp?url=freenet://SSK@.../TFE/archive/">TFE</a><br />
|
||||
-->
|
||||
<%} else {%>
|
||||
<form action="<%=request.getRequestURI() + "?" + (request.getQueryString() != null ? request.getQueryString() : "")%>">
|
||||
<b>Login:</b> <input type="text" name="login" size="8" /><br />
|
||||
<b>Pass:</b> <input type="password" name="password" size="8" /><br /><%
|
||||
java.util.Enumeration params = request.getParameterNames();
|
||||
while (params.hasMoreElements()) {
|
||||
String p = (String)params.nextElement();
|
||||
String val = request.getParameter(p);
|
||||
%><input type="hidden" name="<%=p%>" value="<%=val%>" /><%
|
||||
}%>
|
||||
<input type="submit" name="Login" value="Login" /><br /></form>
|
||||
<a href="register.jsp">Register</a><br />
|
||||
<% } %>
|
1
apps/syndie/jsp/_rightnav.jsp
Normal file
1
apps/syndie/jsp/_rightnav.jsp
Normal file
@ -0,0 +1 @@
|
||||
<!-- nada -->
|
7
apps/syndie/jsp/_toplogo.jsp
Normal file
7
apps/syndie/jsp/_toplogo.jsp
Normal file
@ -0,0 +1,7 @@
|
||||
<%@page import="net.i2p.syndie.BlogManager" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.data.ArchiveIndex" id="archive" />
|
||||
<% session.setAttribute("archive", BlogManager.instance().getArchive().getIndex()); %>
|
||||
<!--
|
||||
<center>[syndiemedia]</center>
|
||||
-->
|
3
apps/syndie/jsp/_topnav.jsp
Normal file
3
apps/syndie/jsp/_topnav.jsp
Normal file
@ -0,0 +1,3 @@
|
||||
<td valign="top" align="left" bgcolor="#cccc88" height="10"><a href="index.jsp">Blogs</a></td>
|
||||
<td valign="top" align="left" bgcolor="#cccc88" height="10">Remote archives</td>
|
||||
<td valign="top" align="left" bgcolor="#cccc88" height="10">Manage</td>
|
45
apps/syndie/jsp/addaddress.jsp
Normal file
45
apps/syndie/jsp/addaddress.jsp
Normal file
@ -0,0 +1,45 @@
|
||||
<%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.*" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3"><%
|
||||
String nameStr = request.getParameter("name");
|
||||
String locStr = request.getParameter("location");
|
||||
String schemaStr = request.getParameter("schema");
|
||||
String name = null;
|
||||
String location = null;
|
||||
String schema = null;
|
||||
try {
|
||||
name = new String(Base64.decode(nameStr));
|
||||
location = new String(Base64.decode(locStr));
|
||||
schema = new String(Base64.decode(schemaStr));
|
||||
} catch (NullPointerException npe) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if ( (name == null) || (location == null) || (schema == null) ) {
|
||||
out.write("<b>No location specified</b>");
|
||||
} else if (user.getAuthenticated() && ("Add".equals(request.getParameter("action"))) ) {
|
||||
out.write("<b>" + BlogManager.instance().addAddress(user, name, location, schema) + "</b>");
|
||||
} else { %>Are you sure you really want to add the
|
||||
addressbook mapping of <%=HTMLRenderer.sanitizeString(name)%> to
|
||||
<input type="text" size="20" value="<%=HTMLRenderer.sanitizeString(location)%>" />, applicable within the
|
||||
schema <%=HTMLRenderer.sanitizeString(schema)%>?
|
||||
<% if (!user.getAuthenticated()) { %>
|
||||
<p />If so, add the line
|
||||
<input type="text" size="20" value="<%=HTMLRenderer.sanitizeString(name)%>=<%=HTMLRenderer.sanitizeString(location)%>" />
|
||||
to your <code>userhosts.txt</code>.
|
||||
<% } else { %><br />
|
||||
<a href="addaddress.jsp?name=<%=HTMLRenderer.sanitizeURL(name)%>&location=<%=HTMLRenderer.sanitizeURL(location)%>&schema=<%=HTMLRenderer.sanitizeURL(schema)%>&action=Add">Yes, add it</a>.
|
||||
<% }
|
||||
} %></td></tr>
|
||||
</table>
|
||||
</body>
|
32
apps/syndie/jsp/externallink.jsp
Normal file
32
apps/syndie/jsp/externallink.jsp
Normal file
@ -0,0 +1,32 @@
|
||||
<%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3">Are you sure you really want to go to
|
||||
<%
|
||||
String loc = request.getParameter("location");
|
||||
String schema = request.getParameter("schema");
|
||||
String desc = request.getParameter("description");
|
||||
if (loc != null) loc = HTMLRenderer.sanitizeString(new String(Base64.decode(loc)));
|
||||
if (schema != null) schema = HTMLRenderer.sanitizeString(new String(Base64.decode(schema)));
|
||||
if (desc != null) desc = HTMLRenderer.sanitizeString(new String(Base64.decode(desc)));
|
||||
|
||||
if ( (loc != null) && (schema != null) ) {
|
||||
out.write(loc + " (" + schema + ")");
|
||||
if (desc != null)
|
||||
out.write(": " + desc);
|
||||
out.write("? ");
|
||||
out.write("<a href=\"" + loc + "\">yes</a>");
|
||||
} else {
|
||||
out.write("(some unspecified location...)");
|
||||
}
|
||||
%></td></tr>
|
||||
</table>
|
||||
</body>
|
14
apps/syndie/jsp/index.jsp
Normal file
14
apps/syndie/jsp/index.jsp
Normal file
@ -0,0 +1,14 @@
|
||||
<%@page contentType="text/html" import="net.i2p.syndie.web.*" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3"><jsp:include page="_bodyindex.jsp" /></td></tr>
|
||||
</table>
|
||||
</body>
|
112
apps/syndie/jsp/post.jsp
Normal file
112
apps/syndie/jsp/post.jsp
Normal file
@ -0,0 +1,112 @@
|
||||
<%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.data.*, net.i2p.syndie.*, org.mortbay.servlet.MultiPartRequest, java.util.*" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3"><%
|
||||
|
||||
String contentType = request.getContentType();
|
||||
if ((contentType != null) && (contentType.indexOf("boundary=") != -1) ) {
|
||||
if (!user.getAuthenticated()) { %>You must be logged in to post<%
|
||||
} else {
|
||||
MultiPartRequest req = new MultiPartRequest(request);
|
||||
String entrySubject = req.getString("entrysubject");
|
||||
String entryTags = req.getString("entrytags");
|
||||
String entryText = req.getString("entrytext");
|
||||
String entryHeaders = req.getString("entryheaders");
|
||||
String replyTo = req.getString(ArchiveViewerBean.PARAM_IN_REPLY_TO);
|
||||
if ( (replyTo != null) && (replyTo.trim().length() > 0) ) {
|
||||
byte r[] = Base64.decode(replyTo);
|
||||
if (r != null) {
|
||||
if (entryHeaders == null) entryHeaders = HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r);
|
||||
else entryHeaders = entryHeaders + '\n' + HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r);
|
||||
} else {
|
||||
replyTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
List fileStreams = new ArrayList();
|
||||
List fileNames = new ArrayList();
|
||||
List fileTypes = new ArrayList();
|
||||
for (int i = 0; i < 32; i++) {
|
||||
String filename = req.getFilename("entryfile" + i);
|
||||
if ( (filename != null) && (filename.trim().length() > 0) ) {
|
||||
fileNames.add(filename.trim());
|
||||
fileStreams.add(req.getInputStream("entryfile" + i));
|
||||
Hashtable params = req.getParams("entryfile" + i);
|
||||
String type = "application/octet-stream";
|
||||
for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) {
|
||||
String cur = (String)iter.next();
|
||||
if ("content-type".equalsIgnoreCase(cur)) {
|
||||
type = (String)params.get(cur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fileTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
BlogURI entry = BlogManager.instance().createBlogEntry(user, entrySubject, entryTags, entryHeaders, entryText, fileNames, fileStreams, fileTypes);
|
||||
if (entry != null) {
|
||||
// it has been rebuilt...
|
||||
request.setAttribute("index", BlogManager.instance().getArchive().getIndex());
|
||||
%>
|
||||
Blog entry <a href="<%=HTMLRenderer.getPageURL(user.getBlog(), null, entry.getEntryId(), -1, -1, user.getShowExpanded(), user.getShowImages())%>">posted</a>!
|
||||
<% } else { %>
|
||||
There was an error posting... dunno what it was...
|
||||
<% }
|
||||
}
|
||||
} else { %><form action="post.jsp" method="POST" enctype="multipart/form-data">
|
||||
Post subject: <input type="text" size="80" name="entrysubject" /><br />
|
||||
Post tags: <input type="text" size="20" name="entrytags" /><br />
|
||||
Post content (in raw SML, no headers):<br />
|
||||
<textarea rows="6" cols="80" name="entrytext"></textarea><br />
|
||||
<b>SML cheatsheet:</b><br /><textarea rows="6" cols="80" readonly="true">
|
||||
* newlines are newlines are newlines.
|
||||
* all < and > are replaced with their &symbol;
|
||||
* [b][/b] = <b>bold</b>
|
||||
* [i][/i] = <i>italics</i>
|
||||
* [u][/u] = <i>underline</i>
|
||||
* [cut]more inside[/cut] = [<a href="#">more inside...</a>]
|
||||
* [img attachment="1"]alt[/img] = use attachment 1 as an image with 'alt' as the alt text
|
||||
* [blog name="name" bloghash="base64hash"]description[/blog] = link to all posts in the blog
|
||||
* [blog name="name" bloghash="base64hash" blogentry="1234"]description[/blog] = link to the specified post in the blog
|
||||
* [blog name="name" bloghash="base64hash" blogtag="tag"]description[/blog] = link to all posts in the blog with the specified tag
|
||||
* [blog name="name" blogtag="tag"]description[/blog] = link to all posts in all blogs with the specified tag
|
||||
* [link schema="eep" location="http://forum.i2p"]text[/link] = offer a link to an external resource (accessible with the given schema)
|
||||
|
||||
SML headers are newline delimited key=value pairs. Example keys are:
|
||||
* bgcolor = background color of the post (e.g. bgcolor=#ffccaa or bgcolor=red)
|
||||
* bgimage = attachment number to place as the background image for the post (only shown if images are enabled) (e.g. bgimage=1)
|
||||
* textfont = font to put most text into
|
||||
</textarea><br />
|
||||
SML post headers:<br />
|
||||
<textarea rows="3" cols="80" name="entryheaders"></textarea><br /><%
|
||||
String s = request.getParameter(ArchiveViewerBean.PARAM_IN_REPLY_TO);
|
||||
if ( (s != null) && (s.trim().length() > 0) ) {%>
|
||||
<input type="hidden" name="<%=ArchiveViewerBean.PARAM_IN_REPLY_TO%>" value="<%=request.getParameter(ArchiveViewerBean.PARAM_IN_REPLY_TO)%>" />
|
||||
<% } %>
|
||||
|
||||
Attachment 0: <input type="file" name="entryfile0" /><br />
|
||||
Attachment 1: <input type="file" name="entryfile1" /><br />
|
||||
Attachment 2: <input type="file" name="entryfile2" /><br />
|
||||
Attachment 3: <input type="file" name="entryfile3" /><br />
|
||||
Attachment 4: <input type="file" name="entryfile4" /><br />
|
||||
Attachment 5: <input type="file" name="entryfile5" /><br />
|
||||
Attachment 6: <input type="file" name="entryfile6" /><br />
|
||||
Attachment 7: <input type="file" name="entryfile7" /><br />
|
||||
Attachment 8: <input type="file" name="entryfile8" /><br />
|
||||
Attachment 9: <input type="file" name="entryfile9" /><br />
|
||||
<hr />
|
||||
<input type="submit" name="Post" value="Post entry" /> <input type="reset" value="Cancel" />
|
||||
<% } %>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body>
|
48
apps/syndie/jsp/register.jsp
Normal file
48
apps/syndie/jsp/register.jsp
Normal file
@ -0,0 +1,48 @@
|
||||
<%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.*" %>
|
||||
<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" />
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3"><%
|
||||
|
||||
String regLogin = request.getParameter("login");
|
||||
boolean showForm = true;
|
||||
if ( (regLogin != null) && ("Register".equals(request.getParameter("Register"))) ) {
|
||||
String regUserPass = request.getParameter("password");
|
||||
String regPass = request.getParameter("registrationpassword");
|
||||
String blogName = request.getParameter("blogname");
|
||||
String desc = request.getParameter("description");
|
||||
String url = request.getParameter("contacturl");
|
||||
String regResult = BlogManager.instance().register(user, regLogin, regUserPass, regPass, blogName, desc, url);
|
||||
if (User.LOGIN_OK.equals(regResult)) {
|
||||
out.print("<b>Registration successful.</b> <a href=\"index.jsp\">Continue...</a>\n");
|
||||
showForm = false;
|
||||
} else {
|
||||
out.print("<b>" + regResult + "</b>");
|
||||
}
|
||||
}
|
||||
if (showForm) {%><form action="register.jsp" method="POST">
|
||||
<p>To create a new blog (and Syndie user account), please fill out the following form.
|
||||
You may need to enter a registration password given to you by this Syndie instance's
|
||||
operator, or there may be no registration password in place (in which case you can
|
||||
leave that field blank).</p>
|
||||
<p>
|
||||
<b>Syndie login:</b> <input type="text" size="8" name="login" /><br />
|
||||
<b>New password:</b> <input type="password" size="8" name="password" /><br />
|
||||
<b>Registration password:</b> <input type="password" size="8" name="registrationpassword" /><br />
|
||||
<b>Blog name:</b> <input type="text" size="32" name="blogname" /><br />
|
||||
<b>Brief description:</b> <input type="text" size="60" name="description" /><br />
|
||||
<b>Contact URL:</b> <input type="text" size="20" name="contacturl" /> <i>(e.g. mailto://user@mail.i2p, http://foo.i2p/, etc)</i><br />
|
||||
<input type="submit" name="Register" value="Register" />
|
||||
</p>
|
||||
</form><% } %>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body>
|
8
apps/syndie/jsp/viewattachment.jsp
Normal file
8
apps/syndie/jsp/viewattachment.jsp
Normal file
@ -0,0 +1,8 @@
|
||||
<%
|
||||
java.util.Map params = request.getParameterMap();
|
||||
response.setContentType(net.i2p.syndie.web.ArchiveViewerBean.getAttachmentContentType(params));
|
||||
int len = net.i2p.syndie.web.ArchiveViewerBean.getAttachmentContentLength(params);
|
||||
if (len >= 0)
|
||||
response.setContentLength(len);
|
||||
net.i2p.syndie.web.ArchiveViewerBean.renderAttachment(params, response.getOutputStream());
|
||||
%>
|
16
apps/syndie/jsp/viewmetadata.jsp
Normal file
16
apps/syndie/jsp/viewmetadata.jsp
Normal file
@ -0,0 +1,16 @@
|
||||
<%@page contentType="text/html" import="net.i2p.syndie.web.*" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>SyndieMedia</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td>
|
||||
<jsp:include page="_topnav.jsp" />
|
||||
<td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr>
|
||||
<tr><td valign="top" align="left" colspan="3"><%
|
||||
ArchiveViewerBean.renderMetadata(request.getParameterMap(), out);
|
||||
%></td></tr>
|
||||
</table>
|
||||
</body>
|
22
apps/syndie/jsp/web.xml
Normal file
22
apps/syndie/jsp/web.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<!-- precompiled servlets -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.syndie.jsp.index_jsp</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
</session-timeout>
|
||||
</session-config>
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2p">
|
||||
|
||||
|
||||
<target name="all" >
|
||||
<echo message="Useful targets: " />
|
||||
<echo message=" dist: distclean then package everything up (installer, clean tarball, update tarball)" />
|
||||
@ -28,6 +28,7 @@
|
||||
<ant dir="apps/routerconsole/java/" target="jar" />
|
||||
<ant dir="apps/addressbook/" target="war" />
|
||||
<ant dir="apps/susimail/" target="war" />
|
||||
<ant dir="apps/syndie/java/" target="jar" /> <!-- not pushed in the update... yet -->
|
||||
</target>
|
||||
<target name="buildWEB">
|
||||
<ant dir="apps/jetty" target="fetchJettylib" />
|
||||
@ -58,6 +59,8 @@
|
||||
<copy file="installer/lib/jbigi/jbigi.jar" todir="build" />
|
||||
<copy file="apps/addressbook/dist/addressbook.war" todir="build/" />
|
||||
<copy file="apps/susimail/susimail.war" todir="build/" />
|
||||
<copy file="apps/syndie/java/build/syndie.jar" todir="build/" />
|
||||
<copy file="apps/syndie/syndie.war" todir="build/" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
|
@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.36 $ $Date: 2005/07/27 14:04:49 $";
|
||||
public final static String VERSION = "0.6.0.1";
|
||||
public final static String ID = "$Revision: 1.38 $ $Date: 2005/08/08 15:35:50 $";
|
||||
public final static String VERSION = "0.6.0.3";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
@ -44,19 +44,19 @@ public class Base64 {
|
||||
|
||||
/** added by aum */
|
||||
public static String encode(String source) {
|
||||
return encode(source.getBytes());
|
||||
return (source != null ? encode(source.getBytes()) : "");
|
||||
}
|
||||
public static String encode(byte[] source) {
|
||||
return encode(source, 0, (source != null ? source.length : 0));
|
||||
return (source != null ? encode(source, 0, (source != null ? source.length : 0)) : "");
|
||||
}
|
||||
public static String encode(byte[] source, int off, int len) {
|
||||
return encode(source, off, len, false);
|
||||
return (source != null ? encode(source, off, len, false) : "");
|
||||
}
|
||||
public static String encode(byte[] source, boolean useStandardAlphabet) {
|
||||
return encode(source, 0, (source != null ? source.length : 0), useStandardAlphabet);
|
||||
return (source != null ? encode(source, 0, (source != null ? source.length : 0), useStandardAlphabet) : "");
|
||||
}
|
||||
public static String encode(byte[] source, int off, int len, boolean useStandardAlphabet) {
|
||||
return safeEncode(source, off, len, useStandardAlphabet);
|
||||
return (source != null ? safeEncode(source, off, len, useStandardAlphabet) : "");
|
||||
}
|
||||
|
||||
public static byte[] decode(String s) {
|
||||
@ -142,7 +142,7 @@ public class Base64 {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test();
|
||||
//test();
|
||||
if (args.length == 0) {
|
||||
help();
|
||||
return;
|
||||
@ -399,6 +399,7 @@ public class Base64 {
|
||||
* replacing / with ~, and + with -
|
||||
*/
|
||||
private static byte[] safeDecode(String source, boolean useStandardAlphabet) {
|
||||
if (source == null) return null;
|
||||
String toDecode = null;
|
||||
if (useStandardAlphabet) {
|
||||
toDecode = source;
|
||||
|
@ -642,7 +642,7 @@ public class LogManager {
|
||||
|
||||
public void shutdown() {
|
||||
_log.log(Log.WARN, "Shutting down logger");
|
||||
_writer.flushRecords();
|
||||
_writer.flushRecords(false);
|
||||
}
|
||||
|
||||
private static int __id = 0;
|
||||
|
@ -59,7 +59,8 @@ class LogWriter implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public void flushRecords() {
|
||||
public void flushRecords() { flushRecords(true); }
|
||||
public void flushRecords(boolean shouldWait) {
|
||||
try {
|
||||
List records = _manager._removeAll();
|
||||
if (records == null) return;
|
||||
@ -77,11 +78,13 @@ class LogWriter implements Runnable {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
synchronized (this) {
|
||||
this.wait(10*1000);
|
||||
if (shouldWait) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
this.wait(10*1000);
|
||||
}
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
history.txt
67
history.txt
@ -1,4 +1,69 @@
|
||||
$Id: history.txt,v 1.221 2005/08/01 22:26:51 duck Exp $
|
||||
$Id: history.txt,v 1.227 2005/08/17 15:05:03 jrandom Exp $
|
||||
|
||||
* 2005-08-21 0.6.0.3 released
|
||||
|
||||
2005-08-21 jrandom
|
||||
* If we already have an established SSU session with the Charlie helping
|
||||
test us, cancel the test with the status of "unknown".
|
||||
|
||||
2005-08-17 jrandom
|
||||
* Revise the SSU peer testing protocol so that Bob verifies Charlie's
|
||||
viability before agreeing to Alice's request. This doesn't work with
|
||||
older SSU peer test builds, but is backwards compatible (older nodes
|
||||
won't ask newer nodes to participate in tests, and newer nodes won't
|
||||
ask older nodes to either).
|
||||
|
||||
2005-08-12 jrandom
|
||||
* Keep detailed stats on the peer testing, publishing the results in the
|
||||
netDb.
|
||||
* Don't overwrite the status with 'unknown' unless we haven't had a valid
|
||||
status in a while.
|
||||
* Make sure to avoid shitlisted peers for peer testing.
|
||||
* When we get an unknown result to a peer test, try again soon afterwards.
|
||||
* When a peer tells us that our address is different from what we expect,
|
||||
if we've done a recent peer test with a result of OK, fire off a peer
|
||||
test to make sure our IP/port is still valid. If our test is old or the
|
||||
result was not OK, accept their suggestion, but queue up a peer test for
|
||||
later.
|
||||
* Don't try to do a netDb store to a shitlisted peer, and adjust the way
|
||||
we monitor netDb store progress (to clear up the high netDb.storePeers
|
||||
stat)
|
||||
|
||||
2005-08-10 jrandom
|
||||
* Deployed the peer testing implementation to be run every few minutes on
|
||||
each router, as well as any time the user requests a test manually. The
|
||||
tests do not reconfigure the ports at the moment, merely determine under
|
||||
what conditions the local router is reachable. The status shown in the
|
||||
top left will be "ERR-SymmetricNAT" if the user's IP and port show up
|
||||
differently for different peers, "ERR-Reject" if the router cannot
|
||||
receive unsolicited packets or the peer helping test could not find a
|
||||
collaborator, "Unknown" if the test has not been run or the test
|
||||
participants were unreachable, or "OK" if the router can receive
|
||||
unsolicited connections and those connections use the same IP and port.
|
||||
|
||||
* 2005-08-08 0.6.0.2 released
|
||||
|
||||
2005-08-08 jrandom
|
||||
* Add a configurable throttle to the number of concurrent outbound SSU
|
||||
connection negotiations (via i2np.udp.maxConcurrentEstablish=4). This
|
||||
may help those with slow connections to get integrated at the start.
|
||||
* Further fixlets to the streaming lib
|
||||
|
||||
2005-08-07 Complication
|
||||
* Display the average clock skew for both SSU and TCP connections
|
||||
|
||||
2005-08-07 jrandom
|
||||
* Fixed the long standing streaming lib bug where we could lose the first
|
||||
packet on retransmission.
|
||||
* Avoid an NPE when a message expires on the SSU queue.
|
||||
* Adjust the streaming lib's window growth factor with an additional
|
||||
Vegas-esque congestion detection algorithm.
|
||||
* Removed an unnecessary SSU session drop
|
||||
* Reduced the MTU (until we get a working PMTU lib)
|
||||
* Deferr tunnel acceptance until we know how to reach the next hop,
|
||||
rejecting it if we can't find them in time.
|
||||
* If our netDb store of our leaseSet fails, give it a few seconds before
|
||||
republishing.
|
||||
|
||||
* 2005-08-03 0.6.0.1 released
|
||||
|
||||
|
21
hosts.txt
21
hosts.txt
@ -1,6 +1,13 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.149 2005/07/11 18:23:22 jrandom Exp $
|
||||
; $Id: hosts.txt,v 1.156 2005/08/19 20:19:51 cervantes Exp $
|
||||
; changelog:
|
||||
; (1.178) added syndie.i2p and syndiemedia.i2p
|
||||
; (1.177) added irc.freshcoffee.i2p
|
||||
; (1.176) added surrender.adab.i2p
|
||||
; (1.175) added terror.i2p
|
||||
; (1.174) added irc.arcturus.i2p
|
||||
; (1.173) added tracker.postman.i2p, hq.postman.i2p
|
||||
; (1.172) added i2p-bt.postman.i2p
|
||||
; (1.171) added luckypunk.i2p
|
||||
; (1.170) added bash.i2p, stats.i2p
|
||||
; (1.169) added archive.i2p, www.fr.i2p, romster.i2p, marshmallow.i2p, openforums.i2p
|
||||
@ -404,4 +411,14 @@ marshmallow.i2p=99AaeKrGXmKPUaX256IeRqjvjOLmN4xZnUemTEwFAdanLDa0eMI2UitjrSoq6gy~
|
||||
openforums.i2p=9fTKl1CC1o2WrxBbE3fvr4xOUGse~qMjmhfn0lpDGXZ0Ohk55ocODIg0AFlPnQd4AchS0VFk0UyWqoZ7B93tyKfOypuVqHl7stucR6PSYMda9QKRlJugX9vwOKKMFOLnu5Xf5T7s7cKqSX8qUDdqeUjDPXyme~xYGhFyn4GftWURF0EesuhY4qP-GTQPEQef2f4DQmTKB1G62~WrRLd6AbvRBmZ4l0qSaKMd7FRjPhwmiXI9JhS~3gsKZ2DUP1Y7th4Lw77lnIdYVejxZ5ZU-PIYd2xnJK~WsazvRp2QOBPa8Vn8X6302IyAbR52zZEB50jucwK4oQ7FbP-Ws4bLA0WoTe1ZxYtDYLo1R5BUqie0cGP~fdc14oR-0NhAEz8VBnTQ4m9CMoz-oaT2R6xWaG4q5MH4671vULudbFeixz2NaGYjuiTO2rCcsvliCibn8sAA~SfRIk~hZF0ZkjyG0SAysVjO~qwjzDBVirSz5YOPp~ZdntbI5hQzjzMIhgxFAAAA
|
||||
bash.i2p=MsTeG5WIguhorKQ~9xUHVAMHOpia7Y09oid-7vCKS0titHeArGGoal48tkxVD30sXZjxTVBu4lkH9j4oPiQV30wpO0fkSMh1ryiIU1uOTLaAcbNtKCdCixpDchjjJKmxvnTa7LOc1Zft~XkS0ydsxwxsXa9uPU2G3H7WBacJ2v0zxLWyjjJnYIQoJCEHFZrzr~nJ58csCJP-TbJDRegz-0J5LrhZsjbNBFckGrLFtqbRmPwJkS2RU1Kx7SQ0hfN4eFvO7NvratDIo9j3q-OuXyHQm-1RS5XzL5ZxJFHYeaPWgKcLouWHp0MX1OKBnGitGGe1EPzmqf3LwgU8RIohLoFi3CaSc4eO3hYbKLShwz0fzNWFYUeQhLrtLPYA0~fq~lflXpeaXIPOHcJLST0GJd3uWGleS7iV4eXG4LvVlVEEDSYyNNoACmovQRtZOo-DHdUN~iRWy~skNBddX5-hTdAaQyk0ZsfLIF0-pBoWnFW8bFjOcXTF0nNOOgRzXSz9AAAA
|
||||
stats.i2p=Okd5sN9hFWx-sr0HH8EFaxkeIMi6PC5eGTcjM1KB7uQ0ffCUJ2nVKzcsKZFHQc7pLONjOs2LmG5H-2SheVH504EfLZnoB7vxoamhOMENnDABkIRGGoRisc5AcJXQ759LraLRdiGSR0WTHQ0O1TU0hAz7vAv3SOaDp9OwNDr9u902qFzzTKjUTG5vMTayjTkLo2kOwi6NVchDeEj9M7mjj5ySgySbD48QpzBgcqw1R27oIoHQmjgbtbmV2sBL-2Tpyh3lRe1Vip0-K0Sf4D-Zv78MzSh8ibdxNcZACmZiVODpgMj2ejWJHxAEz41RsfBpazPV0d38Mfg4wzaS95R5hBBo6SdAM4h5vcZ5ESRiheLxJbW0vBpLRd4mNvtKOrcEtyCvtvsP3FpA-6IKVswyZpHgr3wn6ndDHiVCiLAQZws4MsIUE1nkfxKpKtAnFZtPrrB8eh7QO9CkH2JBhj7bG0ED6mV5~X5iqi52UpsZ8gnjZTgyG5pOF8RcFrk86kHxAAAA
|
||||
luckypunk.i2p=cWFSoPjjQbtISkSzz2-dZf~ZlN1ZwTqG1DDEpcS3pOW8VzrOR6pQaUIQpzdx6MWa8tHplPlFiw~Y-KX6UlXfr557X7L9s-mld9hlQwAy4FGc6eGAe3XmtwKLLCHhmsTozea3nRE9nOIsPwntz7n~f1yu8PrkehzrMJfeqOIPA5teyyfxmeKwa3Jnr4YC8qc2EWSACgh-tAORnjVpMrByuFRaW-J~6cFBJ7gobC4aZfhIlcDFIt96VT4caUlsqKQjgQQ9k8oPrlbFsQHTmNB7331aapstQZ8WJsRIqkSLOUIGrE1XOzUFGthmk5urwcNcsOZmunlMtMRS2RN4rJp2aDFfr~wPGkh5QtueRAT1mSbn116qqRRo6PQvARZQk0oqtm97bqfqdb5SbELHJpbqpJLoAiNQywIgXwgFLP8LYJnQO64EreVIvOjhevZUav5kbQZVE41NjJcT5ZmDtQLHVA~gVsHiYe0KxTRoAOwrlsx~Z3vExq3I1Yd-vhS6ATA5AAAA
|
||||
luckypunk.i2p=cWFSoPjjQbtISkSzz2-dZf~ZlN1ZwTqG1DDEpcS3pOW8VzrOR6pQaUIQpzdx6MWa8tHplPlFiw~Y-KX6UlXfr557X7L9s-mld9hlQwAy4FGc6eGAe3XmtwKLLCHhmsTozea3nRE9nOIsPwntz7n~f1yu8PrkehzrMJfeqOIPA5teyyfxmeKwa3Jnr4YC8qc2EWSACgh-tAORnjVpMrByuFRaW-J~6cFBJ7gobC4aZfhIlcDFIt96VT4caUlsqKQjgQQ9k8oPrlbFsQHTmNB7331aapstQZ8WJsRIqkSLOUIGrE1XOzUFGthmk5urwcNcsOZmunlMtMRS2RN4rJp2aDFfr~wPGkh5QtueRAT1mSbn116qqRRo6PQvARZQk0oqtm97bqfqdb5SbELHJpbqpJLoAiNQywIgXwgFLP8LYJnQO64EreVIvOjhevZUav5kbQZVE41NjJcT5ZmDtQLHVA~gVsHiYe0KxTRoAOwrlsx~Z3vExq3I1Yd-vhS6ATA5AAAA
|
||||
i2p-bt.postman.i2p=pL4Xjp3RFu4trp35Z9kLrsOX~pqizwk6x6iydmx1JAjNdhzppPxUvix8NRLEu~n-LURObrsMX6mTF~VHWEDTBBCaxUw6y9NGvKeTRRtCirK6NXFQQIAo-STGIA3z6DPN4G5IGtVsOOKx0ucjh7rkHH0k7p4g4rxnbQ0S3XWSAOqeIpnK-pNQDCr~p9rd2PAGCQVsWLSlOJzUITTrQe2~w2by-eysIJXcO59iiXloj9O3JLY3Yzy3fjjjXHnXH5WeP5kbWOuufXBUF2A9mrEzQo1a0hZNb0bnVFevtdNLMuiB4cNxWE6UfBLZXmqwW2JNwFs799rxgpVEjUwJiYttv2tZoMdESOivlc3IZmYESWa~2LsO-23nOYx55X5nfpJ~HBqipgecTgS37F4GNa5m-d9FaMeAcBy23X-nBDFxCob3PMxBAQ9adpTZj-IE04RvSbara28tu4~cQ9KpzYeRQ7Mt-PPuKo4gzxMZUo4IL1aGXRBUb1U~Ph7Lh4r8LZc4AAAA
|
||||
tracker.postman.i2p=YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA
|
||||
hq.postman.i2p=P0T4cDiNYzHWcroPCZrH9Co9kvvHeZlLJ0jNn4qwO5rEDTZbNDzAHTuU2GdgUDd9yi~vLTiPC7T6BUMQz2y-Spg-L0D8qiGCBcFZ5ZPWKTDi-Am3WJIbeM~9psndc2X-sG09LrKb7Da3K1hd7WFA37tYXpeiavo5C1LbRZU0H0X2BrGI5fVt~NmUH0D1Vih2P0zY1vx9pb4pjobPyzL~34u5sO~YsLZjfWHm0dK8MUaypFm9bRDNOOKLO5Q32qHPzrDJ9jx6OLZf4g2UkDrJF3F3lzcdO84oFEP0xsYyre5UoMWvtxTQNWc-NdSq5cvvNJeudys3gudhDJft31ypwRPDmI3Z8Jx1~qS1KOQosCtbd3vsArz-ghC9hGFQsbw~leN0XgCZhqHrakNCQ5d7jMXarBtgFYCd51DDS8NbXkli7~C2Yn7-UGMGTlbHgmPB8dsQXT1PfcGVOw3tpHvliMZVUzHX0wjgfWyDzLQzIRKkB1G28e-oDqrA88vEdzz6AAAA
|
||||
irc.arcturus.i2p=8d5AIidKU7JudLbGi94VCS7Gu~SrSb4rxBp6ADbc9o0MiOp0vrKGVTdrWiDDLmlneRLqVTPIRmBkxB9PAtRZYmdvstiae9zat5twU7T~xA-mf95t1HZXPTMEnxOB6ZX~fSlxLWR3robKDq7L1DOF9aBXT5fw5KcdZmAw2pDeslobozNkB~38siDW3VEKQrNK6SlgkbcQ2ob5fQjUEN3IPnLbyhP4HAap0CppUfX3ix0YU1EP86XHig8ZUgmq2YQ8LpBOGZ21yHQ3pGDHuvgHHGl7bQqz2TV5MnyUaHkCAHu6d~agqdJc4ooORJZMUkWnFIbL4ioJbzZ9zPIPDam81Qw04MuTY5vPEz1Hx9egdWzJX-kCjFv~3-SPX0QVGYAY-cg~fIJfxH0G3jrjXOfGO6NelDiuyTGhvhCR1Y2O6jTqFyVyUc-WZAHAs2qRacfR-TtEpP2-s7fY191aWwxycD3tbXx1F0FG6AYJcnFhUFFp2uoUCryrY7HA6NA5lIDfAAAA
|
||||
terror.i2p=WJ23KjbgLR19NidOl-0TU4kZDfIljbWoTTen07d9Wi-ZFZhaJpF6Kxid-60vhBmEjzoFId1IKuaCZtLK~42whQ7yvsYD0fMda9gZE-FmpWGGwneRHQxtYRU0Tx8sraSipCZ3Sq9i~cjTbyHpFt9YtObvhWOOi1T2AjWRewvTGWKKcxRL1Bfny2ZpN84ADnqZwJp-gmbhgDPB0CFYi67BzIRTYnRseVILepokUHzPWLiIaJM9GYVgyNp18XpFxkjGZ8NYP6phdz2oJVmsHate6W-3LIAyo5mfIw95nhr5PHezQz1W-AeCubvl2gQ04knFY28DAMuxMyWeyIfDROxuPgcwzUMdw6q8QnEPJKr9vag-dnPh8C8U4Ur~DlS5BgHoJvv5saFkU~I1hvEK9YdPxNx4IOSnHsZZdjCXA8d4zAd7X-kAZ56cV4EABkGzEx9SXwyroA--BT2GeywNGf2HvWTV-ElQ~QkqD-rFgHZspjI-q8iKrcXp4Oz-8CJzKkzjAAAA
|
||||
surrender.adab.i2p=ReLukCcBIuGvQwm0~oXMYDjIjRSsQXHdRf7ANaJpUoCx5qRPg340308vKWW0DiijdnRNINf-C4SoMxK7mjx5itttBP~AwAK2wJc9ZFX-9qJ9Gg8EBcvxSRn6taasjw5zQCvEukmsPdVwCBQPN~5NGotDv~OZ5TsRtTmAT3ovAnfFFDx~Cu1QsLW3u~K9TwR989H-iVvZyGFV-V~L5V3WISRqx-qnjZ3-fOWfhj6aNT0q3fWMptMKQAqYjt6rCjAwzt0ppDFfIEVrBDc3ik2nziefRiFKv~79tHNjZmMEDrKEiLu~bXn5HNy0hF24dsCtqmoGZ5Dfzst1XriIkiVLvF-cxw6HqdNhsGi9AdoAeNhpVVaYPKYoEiobT31VAGvTMoodbgajT-W3gYMWs74enUlHXJ7eJd31OISV5XWDH-NgsbhpcG1HK8GPjlBeYsGUs4V-lcpig8k8Pibe7Y~Q8eyqjWHGSlcTW7TehRrxZRMskp3lAG-oFLCapHaMVKMYAAAA
|
||||
irc.freshcoffee.i2p=VM42PrZcVJyDFV6Eqt1WkqZ6G260D72u6wsU8Bxt0oSDd5UtCkcrduYMl-~9bgc6AZJpJ1absO5-opUaSqA~0ypGox6gdlvKVCHqHhxR89VLy8nO-kS-cVBXb8TeUq5MdH3djZIQ8zKv9YL5Q9lGW2Nd1od~re~w2F5-AWM25y9P91Pu6wymokYlZoaIffG3O8aXA~0jBweqnE7epuSK5e~kZ~5omDBnfYlNC2MoNxgEiNbuyQdfXbLf5QjHzEIlIv1-BSym-OY9fCwqRuJc4eCKaXrMg6hy2U-HEkMz80Gq-2gEI-uxqTJHnNG4h276rU7ej1FpCPrsnXS0zSj7ppbksOBCrkqlNEYhy1wrCjoREfbBN9A1kHDTfT9cR73Ym8S2-incCzoQrcyJds-2KmXa5vfr5Pvt2v3SYXkrTKJzZXMhXotLP7CAzItVh~SXYMOQtiVd4NKXTgSmXVarewsHcbxnZuQUr0qimjAsTEJZPZMppQFNkfPAAqIoqz0wAAAA
|
||||
syndie.i2p=n-9clS0jBJTzYl7QStNR5A34sN2Xq-HmIjDE~MRLpUkMtw-0KSnqYb11S581~~hx0xyby5v9dSETx84Hbkv2g7h3SdjswMlLe-8Z0~S3ssR-bp5Y5L3cpg~fHlgxjEqAC~SGZFCWR~ZyYlRbdd~tnOJ5Fij57acIpuTRRnRjImDWJeboFfKeXqDHeMhcyMi8JTxcCCo3vwAZ6G1qffpGbAgnktriYRnMn97H7BBK~KGCFjqwDo172kUORXpDNHUstCwT-Oi5oCb7EN6vPEwYbyiHVuApOFGzyu0IyBZ5SZvfifrQLUbtXj2enmgrcJByq1XIUy7-E3pqMnNgzdT2~34RzNX4aGVFBe0Rb~zEA3cdb9koLyVgLXYUbWqOmn~blbFDHTPAIyNDiFL1vM~8ZorBYEVTdeD8bvMvQgoPHZRcBIDFrtAmjaT0XXzaHL9T8sr6FYt2j1giLUrWllpWbjYb5eWdMwkPa0ke2PHCOO0lF1JKDjK9G3YiYM9z2MyUAAAA
|
||||
syndiemedia.i2p=Rj9ZbkNHJEbbzrsuWGVR7jaBrpMvgw5D92WMtkbBDoDLyX8co17tJYJ58YpJ~cMaC9h8tnpwg15ws05U4Bb4lYvFyamnc9wfBSNwwdz2hgbIGHtBRMXh8Lb5NQZJLZdbFQlmd7jVSjpJc8vNVa-8lWGgy4ExryXa0Ps~HQLckIy6Fr0Fc18DS~G7aZk-6kKpvLQheN0ZRyzcMBDoniSG2z5MJlh6MMA0I5ZLAuUT9Ugg-cy9y05eTeIL6uVILc1LG5Dqc-5xbAGdIPi5d0~Ij2nqOA80PIyRR6zUKt4zbFmlO5bm3mndKYmoeL-XcKkw3BvEvXUBK4q6L43pjgH9PQHszGy-2rJXC7h41rxTxTo7Sn3NV04UI0Ixt2zm2ozaCiZdsLcbfrzwm3IER~2Jvsr4QJFCfuJhQKWEayPqKeRU5gRDnxtph3afYvYJ6Syp~OFTRIFEhkHxfg6WorKfuwVJZ~GbY8~Ptt4yMSa2u7RPsjEnarBru-tfIbDTpPt3AAAA
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
<i2p.news date="$Date: 2005/07/27 15:16:44 $">
|
||||
<i2p.release version="0.6.0.1" date="2005/07/27" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2005/08/03 13:58:14 $">
|
||||
<i2p.release version="0.6.0.2" date="2005/08/08" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000824.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/2005-July/000824.html" />
|
||||
<i2p.notes date="2005/07/26"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000823.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/2005-July/000823.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting138"
|
||||
publiclogs="http://www.i2p.net/meeting138" />
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000834.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/2005-July/000834.html" />
|
||||
<i2p.notes date="2005/08/02"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000826.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/2005-July/000826.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting141"
|
||||
publiclogs="http://www.i2p.net/meeting141" />
|
||||
<h1>Congratulations on getting I2P installed!</h1>
|
||||
</i2p.news>
|
||||
|
||||
|
@ -20,7 +20,7 @@ tunnel.1.type=client
|
||||
tunnel.1.sharedClient=true
|
||||
tunnel.1.interface=127.0.0.1
|
||||
tunnel.1.listenPort=6668
|
||||
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p,irc.postman.i2p
|
||||
tunnel.1.targetDestination=irc.postman.i2p,irc.freshcoffee.i2p,irc.arcturus.i2p
|
||||
tunnel.1.i2cpHost=127.0.0.1
|
||||
tunnel.1.i2cpPort=7654
|
||||
tunnel.1.option.inbound.nickname=shared clients
|
||||
|
18
news.xml
18
news.xml
@ -1,14 +1,14 @@
|
||||
<i2p.news date="$Date: 2005/07/28 15:33:27 $">
|
||||
<i2p.release version="0.6.0.1" date="2005/07/27" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2005/08/03 13:58:14 $">
|
||||
<i2p.release version="0.6.0.2" date="2005/08/08" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-April/000824.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/2005-July/000824.html" />
|
||||
<i2p.notes date="2005/07/27"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000823.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/2005-July/000823.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting139"
|
||||
publiclogs="http://www.i2p.net/meeting138" />
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-April/000834.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/2005-July/000834.html" />
|
||||
<i2p.notes date="2005/08/08"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-July/000826.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/2005-July/000826.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting141"
|
||||
publiclogs="http://www.i2p.net/meeting141" />
|
||||
Welcome to the new 0.6 series of releases, using the new SSU transport!
|
||||
<br />
|
||||
</i2p.news>
|
||||
|
10
readme.html
10
readme.html
@ -5,9 +5,8 @@ the number of "Active: " peers rise, and you should see some local "destinations
|
||||
listed (if not, <a href="#trouble">see below</a>). Once those are up, you can:</p>
|
||||
<ul>
|
||||
<li><b>chat anonymously</b> - fire up your own IRC client and connect to the
|
||||
server at <b>localhost port 6668</b>. This points at one of two anonymously hosted
|
||||
IRC servers (irc.duck.i2p and irc.baffled.i2p), but neither you nor they know
|
||||
where the other is.</li>
|
||||
server at <b>localhost port 6668</b>. This points at one of three anonymously hosted
|
||||
IRC servers, but neither you nor they know where the other is.</li>
|
||||
<li><b>browse "eepsites"</b> - on I2P there are anonymously hosted websites -
|
||||
tell your browser to use the <b>HTTP proxy at localhost port 4444</b>, then
|
||||
browse to an eepsite -
|
||||
@ -65,8 +64,7 @@ hand side of the page will show up to help you when necessary).</p>
|
||||
<p>If you are still having problems, you may want to review the information on the
|
||||
<a href="http://www.i2p.net/">I2P website</a>, post up messages to the
|
||||
<a href="http://forum.i2p.net/">I2P discussion forum</a>, or swing by #i2p or
|
||||
#i2p-chat on IRC at <a href="irc://irc.freenode.net/#i2p">irc.freenode.net</a>,
|
||||
<a href="http://www.invisiblechat.com/">invisiblechat/IIP</a>, or irc.duck.i2p (they're all
|
||||
linked together).</p>
|
||||
#i2p-chat on IRC at <a href="irc://irc.freenode.net/#i2p">irc.freenode.net</a>, irc.postman.i2p, irc.freshcoffee.i2p or
|
||||
irc.arcturus.i2p (they're all linked together).</p>
|
||||
|
||||
<p><b>As a note, you can change this page by editing the file "docs/readme.html"</b></p>
|
@ -1,4 +1,4 @@
|
||||
<code>$Id: udp.html,v 1.14 2005/07/27 14:04:07 jrandom Exp $</code>
|
||||
<code>$Id: udp.html,v 1.15 2005/08/03 13:58:13 jrandom Exp $</code>
|
||||
|
||||
<h1>Secure Semireliable UDP (SSU)</h1>
|
||||
<b>DRAFT</b>
|
||||
@ -573,8 +573,10 @@ quite simple:</p>
|
||||
|
||||
<pre>
|
||||
Alice Bob Charlie
|
||||
PeerTest ------------------>
|
||||
<-------------PeerTest PeerTest------------->
|
||||
PeerTest ------------------->
|
||||
PeerTest-------------------->
|
||||
<-------------------PeerTest
|
||||
<-------------------PeerTest
|
||||
<------------------------------------------PeerTest
|
||||
PeerTest------------------------------------------>
|
||||
<------------------------------------------PeerTest
|
||||
@ -592,7 +594,8 @@ that may be reached are as follows:</p>
|
||||
up to a certain number of times, but if no response ever arrives,
|
||||
she will know that her firewall or NAT is somehow misconfigured,
|
||||
rejecting all inbound UDP packets even in direct response to an
|
||||
outbound packet. Alternately, Bob may be down.</li>
|
||||
outbound packet. Alternately, Bob may be down or unable to get
|
||||
Charlie to reply.</li>
|
||||
|
||||
<li>If Alice doesn't receive a PeerTest message with the
|
||||
expected nonce from a third party (Charlie), she will retransmit
|
||||
@ -713,4 +716,4 @@ with either Bob or Charlie, but it is not required.</p>
|
||||
<dd>If the peer address contains the 'B' capability, that means
|
||||
they are willing and able to serve as an introducer - serving
|
||||
as a Bob for an otherwise unreachable Alice.</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
|
@ -30,6 +30,34 @@ public abstract class CommSystemFacade implements Service {
|
||||
|
||||
public int countActivePeers() { return 0; }
|
||||
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
|
||||
|
||||
/**
|
||||
* Determine under what conditions we are remotely reachable.
|
||||
*
|
||||
*/
|
||||
public short getReachabilityStatus() { return STATUS_OK; }
|
||||
public void recheckReachability() {}
|
||||
|
||||
/**
|
||||
* We are able to receive unsolicited connections
|
||||
*/
|
||||
public static final short STATUS_OK = 0;
|
||||
/**
|
||||
* We are behind a symmetric NAT which will make our 'from' address look
|
||||
* differently when we talk to multiple people
|
||||
*
|
||||
*/
|
||||
public static final short STATUS_DIFFERENT = 1;
|
||||
/**
|
||||
* We are able to talk to peers that we initiate communication with, but
|
||||
* cannot receive unsolicited connections
|
||||
*/
|
||||
public static final short STATUS_REJECT_UNSOLICITED = 2;
|
||||
/**
|
||||
* Our reachability is unknown
|
||||
*/
|
||||
public static final short STATUS_UNKNOWN = 3;
|
||||
|
||||
}
|
||||
|
||||
class DummyCommSystemFacade extends CommSystemFacade {
|
||||
|
@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.210 $ $Date: 2005/07/31 16:35:27 $";
|
||||
public final static String VERSION = "0.6.0.1";
|
||||
public final static String ID = "$Revision: 1.216 $ $Date: 2005/08/17 15:05:03 $";
|
||||
public final static String VERSION = "0.6.0.3";
|
||||
public final static long BUILD = 0;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
|
@ -102,7 +102,7 @@ public class StatisticsManager implements Service {
|
||||
stats.putAll(_context.profileManager().summarizePeers(_publishedStats));
|
||||
|
||||
includeThroughput(stats);
|
||||
//includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000 });
|
||||
includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000 });
|
||||
includeRate("router.duplicateMessageId", stats, new long[] { 24*60*60*1000 });
|
||||
//includeRate("tunnel.duplicateIV", stats, new long[] { 24*60*60*1000 });
|
||||
includeRate("tunnel.fragmentedDropped", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
@ -122,6 +122,14 @@ public class StatisticsManager implements Service {
|
||||
//includeRate("router.throttleTunnelProcessingTime1m", stats, new long[] { 60*60*1000 });
|
||||
|
||||
includeRate("router.fastPeers", stats, new long[] { 60*60*1000 });
|
||||
|
||||
includeRate("udp.statusOK", stats, new long[] { 20*60*1000 });
|
||||
includeRate("udp.statusDifferent", stats, new long[] { 20*60*1000 });
|
||||
includeRate("udp.statusReject", stats, new long[] { 20*60*1000 });
|
||||
includeRate("udp.statusUnknown", stats, new long[] { 20*60*1000 });
|
||||
includeRate("udp.statusKnownharlie", stats, new long[] { 1*60*1000, 10*60*1000 });
|
||||
includeRate("udp.addressUpdated", stats, new long[] { 1*60*1000 });
|
||||
includeRate("udp.addressTestInsteadOfUpdate", stats, new long[] { 1*60*1000 });
|
||||
|
||||
includeRate("clock.skew", stats, new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*1000 });
|
||||
|
||||
|
@ -110,7 +110,7 @@ public class GarlicMessageBuilder {
|
||||
msg.setMessageExpiration(config.getExpiration());
|
||||
|
||||
long timeFromNow = config.getExpiration() - ctx.clock().now();
|
||||
if (timeFromNow < 10*1000)
|
||||
if (timeFromNow < 1*1000)
|
||||
log.error("Building a message expiring in " + timeFromNow + "ms: " + config, new Exception("created by"));
|
||||
|
||||
if (log.shouldLog(Log.WARN))
|
||||
|
@ -57,7 +57,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
private boolean _initialized;
|
||||
/** Clock independent time of when we started up */
|
||||
private long _started;
|
||||
private int _knownRouters;
|
||||
private StartExplorersJob _exploreJob;
|
||||
private HarvesterJob _harvestJob;
|
||||
/** when was the last time an exploration found something new? */
|
||||
@ -128,7 +127,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_peerSelector = new PeerSelector(_context);
|
||||
_publishingLeaseSets = new HashMap(8);
|
||||
_lastExploreNew = 0;
|
||||
_knownRouters = 0;
|
||||
_activeRequests = new HashMap(8);
|
||||
_enforceNetId = DEFAULT_ENFORCE_NETID;
|
||||
}
|
||||
@ -359,7 +357,21 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public int getKnownRouters() { return _knownRouters; }
|
||||
public int getKnownRouters() {
|
||||
CountRouters count = new CountRouters();
|
||||
_kb.getAll(count);
|
||||
return count.size();
|
||||
}
|
||||
|
||||
private class CountRouters implements SelectionCollector {
|
||||
private int _count;
|
||||
public int size() { return _count; }
|
||||
public void add(Hash entry) {
|
||||
Object o = _ds.get(entry);
|
||||
if (o instanceof RouterInfo)
|
||||
_count++;
|
||||
}
|
||||
}
|
||||
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
if (!_initialized) return;
|
||||
@ -650,7 +662,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
+ routerInfo.getOptions().size() + " options on "
|
||||
+ new Date(routerInfo.getPublished()));
|
||||
|
||||
_knownRouters++;
|
||||
_ds.put(key, routerInfo);
|
||||
synchronized (_lastSent) {
|
||||
if (!_lastSent.containsKey(key))
|
||||
@ -712,8 +723,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
synchronized (_passiveSendKeys) {
|
||||
_passiveSendKeys.remove(dbEntry);
|
||||
}
|
||||
if (isRouterInfo)
|
||||
_knownRouters--;
|
||||
}
|
||||
|
||||
public void unpublish(LeaseSet localLeaseSet) {
|
||||
|
@ -84,7 +84,7 @@ public class RepublishLeaseSetJob extends JobImpl {
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("FAILED publishing of the leaseSet for " + _dest.toBase64());
|
||||
RepublishLeaseSetJob.this.requeue(5*1000);
|
||||
RepublishLeaseSetJob.this.requeue(30*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,16 +334,19 @@ class SearchJob extends JobImpl {
|
||||
}
|
||||
TunnelId inTunnelId = inTunnel.getReceiveTunnelId(0);
|
||||
|
||||
RouterInfo inGateway = getContext().netDb().lookupRouterInfoLocally(inTunnel.getPeer(0));
|
||||
if (inGateway == null) {
|
||||
_log.error("We can't find the gateway to our inbound tunnel?! wtf");
|
||||
getContext().jobQueue().addJob(new FailedJob(getContext(), router));
|
||||
return;
|
||||
}
|
||||
// this will fail if we've shitlisted our inbound gateway, but the gw may not necessarily
|
||||
// be shitlisted by whomever needs to contact them, so we don't need to check this
|
||||
|
||||
//RouterInfo inGateway = getContext().netDb().lookupRouterInfoLocally(inTunnel.getPeer(0));
|
||||
//if (inGateway == null) {
|
||||
// _log.error("We can't find the gateway to our inbound tunnel?! wtf");
|
||||
// getContext().jobQueue().addJob(new FailedJob(getContext(), router));
|
||||
// return;
|
||||
//}
|
||||
|
||||
long expiration = getContext().clock().now() + getPerPeerTimeoutMs();
|
||||
|
||||
DatabaseLookupMessage msg = buildMessage(inTunnelId, inGateway, expiration);
|
||||
DatabaseLookupMessage msg = buildMessage(inTunnelId, inTunnel.getPeer(0), expiration);
|
||||
|
||||
TunnelInfo outTunnel = getOutboundTunnelId();
|
||||
if (outTunnel == null) {
|
||||
@ -409,10 +412,11 @@ class SearchJob extends JobImpl {
|
||||
* @param replyGateway gateway for the reply tunnel
|
||||
* @param expiration when the search should stop
|
||||
*/
|
||||
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, RouterInfo replyGateway, long expiration) {
|
||||
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration) {
|
||||
DatabaseLookupMessage msg = new DatabaseLookupMessage(getContext(), true);
|
||||
msg.setSearchKey(_state.getTarget());
|
||||
msg.setFrom(replyGateway.getIdentity().getHash());
|
||||
//msg.setFrom(replyGateway.getIdentity().getHash());
|
||||
msg.setFrom(replyGateway);
|
||||
msg.setDontIncludePeers(_state.getClosestAttempted(MAX_CLOSEST));
|
||||
msg.setMessageExpiration(expiration);
|
||||
msg.setReplyTunnel(replyTunnelId);
|
||||
@ -504,6 +508,8 @@ class SearchJob extends JobImpl {
|
||||
|
||||
boolean sendsBadInfo = getContext().profileOrganizer().peerSendsBadReplies(_peer);
|
||||
if (!sendsBadInfo) {
|
||||
// we don't need to search for everthing we're given here - only ones that
|
||||
// are next in our search path...
|
||||
getContext().netDb().lookupRouterInfo(peer, new ReplyVerifiedJob(getContext(), peer), new ReplyNotVerifiedJob(getContext(), peer), _timeoutMs);
|
||||
_repliesPendingVerification++;
|
||||
} else {
|
||||
|
@ -38,8 +38,8 @@ class StoreJob extends JobImpl {
|
||||
private long _expiration;
|
||||
private PeerSelector _peerSelector;
|
||||
|
||||
private final static int PARALLELIZATION = 3; // how many sent at a time
|
||||
private final static int REDUNDANCY = 10; // we want the data sent to 10 peers
|
||||
private final static int PARALLELIZATION = 6; // how many sent at a time
|
||||
private final static int REDUNDANCY = 6; // we want the data sent to 6 peers
|
||||
/**
|
||||
* additionally send to 1 outlier(s), in case all of the routers chosen in our
|
||||
* REDUNDANCY set are attacking us by accepting DbStore messages but dropping
|
||||
@ -146,7 +146,7 @@ class StoreJob extends JobImpl {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_state.addPending(closestHashes);
|
||||
//_state.addPending(closestHashes);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Continue sending key " + _state.getTarget() + " after " + _state.getAttempted().size() + " tries to " + closestHashes);
|
||||
for (Iterator iter = closestHashes.iterator(); iter.hasNext(); ) {
|
||||
@ -155,8 +155,14 @@ class StoreJob extends JobImpl {
|
||||
if ( (ds == null) || !(ds instanceof RouterInfo) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Error selecting closest hash that wasnt a router! " + peer + " : " + ds);
|
||||
_state.addSkipped(peer);
|
||||
} else {
|
||||
sendStore((RouterInfo)ds);
|
||||
if (getContext().shitlist().isShitlisted(((RouterInfo)ds).getIdentity().calculateHash())) {
|
||||
_state.addSkipped(peer);
|
||||
} else {
|
||||
_state.addPending(peer);
|
||||
sendStore((RouterInfo)ds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,12 @@ class StoreState {
|
||||
_attemptedPeers.addAll(pending);
|
||||
}
|
||||
}
|
||||
/** we aren't even going to try to contact this peer */
|
||||
public void addSkipped(Hash peer) {
|
||||
synchronized (_attemptedPeers) {
|
||||
_attemptedPeers.add(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public long confirmed(Hash peer) {
|
||||
long rv = -1;
|
||||
|
@ -147,7 +147,7 @@ public class PeerTestJob extends JobImpl {
|
||||
|
||||
ReplySelector sel = new ReplySelector(peer.getIdentity().getHash(), nonce, expiration);
|
||||
PeerReplyFoundJob reply = new PeerReplyFoundJob(getContext(), peer, inTunnel, outTunnel);
|
||||
PeerReplyTimeoutJob timeoutJob = new PeerReplyTimeoutJob(getContext(), peer, inTunnel, outTunnel);
|
||||
PeerReplyTimeoutJob timeoutJob = new PeerReplyTimeoutJob(getContext(), peer, inTunnel, outTunnel, sel);
|
||||
|
||||
getContext().messageRegistry().registerPending(sel, reply, timeoutJob, timeoutMs);
|
||||
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnelId, null, peer.getIdentity().getHash());
|
||||
@ -193,10 +193,12 @@ public class PeerTestJob extends JobImpl {
|
||||
private long _expiration;
|
||||
private long _nonce;
|
||||
private Hash _peer;
|
||||
private boolean _matchFound;
|
||||
public ReplySelector(Hash peer, long nonce, long expiration) {
|
||||
_nonce = nonce;
|
||||
_expiration = expiration;
|
||||
_peer = peer;
|
||||
_matchFound = false;
|
||||
}
|
||||
public boolean continueMatching() { return false; }
|
||||
public long getExpiration() { return _expiration; }
|
||||
@ -213,11 +215,13 @@ public class PeerTestJob extends JobImpl {
|
||||
} else {
|
||||
getContext().statManager().addRateData("peer.testOK", getTestTimeout() - timeLeft, 0);
|
||||
}
|
||||
_matchFound = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public boolean matchFound() { return _matchFound; }
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append("Test peer ").append(_peer.toBase64().substring(0,4));
|
||||
@ -268,15 +272,20 @@ public class PeerTestJob extends JobImpl {
|
||||
private RouterInfo _peer;
|
||||
private TunnelInfo _replyTunnel;
|
||||
private TunnelInfo _sendTunnel;
|
||||
public PeerReplyTimeoutJob(RouterContext context, RouterInfo peer, TunnelInfo replyTunnel, TunnelInfo sendTunnel) {
|
||||
private ReplySelector _selector;
|
||||
public PeerReplyTimeoutJob(RouterContext context, RouterInfo peer, TunnelInfo replyTunnel, TunnelInfo sendTunnel, ReplySelector sel) {
|
||||
super(context);
|
||||
_peer = peer;
|
||||
_replyTunnel = replyTunnel;
|
||||
_sendTunnel = sendTunnel;
|
||||
_selector = sel;
|
||||
}
|
||||
public String getName() { return "Peer test failed"; }
|
||||
private boolean getShouldFailPeer() { return true; }
|
||||
public void runJob() {
|
||||
if (_selector.matchFound())
|
||||
return;
|
||||
|
||||
if (getShouldFailPeer())
|
||||
getContext().profileManager().dbLookupFailed(_peer.getIdentity().getHash());
|
||||
|
||||
|
@ -74,7 +74,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
|
||||
public List getMostRecentErrorMessages() {
|
||||
return _manager.getMostRecentErrorMessages();
|
||||
}
|
||||
|
||||
|
||||
public short getReachabilityStatus() { return _manager.getReachabilityStatus(); }
|
||||
public void recheckReachability() { _manager.recheckReachability(); }
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException {
|
||||
_manager.renderStatusHTML(out);
|
||||
}
|
||||
|
@ -41,4 +41,6 @@ public interface Transport {
|
||||
public List getMostRecentErrorMessages();
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException;
|
||||
public short getReachabilityStatus();
|
||||
public void recheckReachability();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@ -351,4 +352,7 @@ public abstract class TransportImpl implements Transport {
|
||||
public void renderStatusHTML(Writer out) throws IOException {}
|
||||
|
||||
public RouterContext getContext() { return _context; }
|
||||
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
|
||||
public void recheckReachability() {}
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ package net.i2p.router.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
@ -22,6 +23,7 @@ import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.tcp.TCPTransport;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
@ -115,6 +117,24 @@ public class TransportManager implements TransportEventListener {
|
||||
return peers;
|
||||
}
|
||||
|
||||
public short getReachabilityStatus() {
|
||||
if (_transports.size() <= 0) return CommSystemFacade.STATUS_UNKNOWN;
|
||||
short status[] = new short[_transports.size()];
|
||||
for (int i = 0; i < _transports.size(); i++) {
|
||||
status[i] = ((Transport)_transports.get(i)).getReachabilityStatus();
|
||||
}
|
||||
// the values for the statuses are increasing for their 'badness'
|
||||
Arrays.sort(status);
|
||||
return status[0];
|
||||
}
|
||||
|
||||
public void recheckReachability() {
|
||||
for (int i = 0; i < _transports.size(); i++)
|
||||
((Transport)_transports.get(i)).recheckReachability();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Map getAddresses() {
|
||||
Map rv = new HashMap(_transports.size());
|
||||
for (int i = 0; i < _transports.size(); i++) {
|
||||
|
@ -802,7 +802,7 @@ public class TCPTransport extends TransportImpl {
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
|
||||
buf.append("<b>Average clock skew: ");
|
||||
buf.append("<b>Average clock skew, TCP peers: ");
|
||||
if (_connectionsByIdent.size() > 0)
|
||||
buf.append(offsetTotal / _connectionsByIdent.size()).append("ms</b><br />\n");
|
||||
else
|
||||
|
@ -22,7 +22,7 @@ public class ACKSender implements Runnable {
|
||||
private boolean _alive;
|
||||
|
||||
/** how frequently do we want to send ACKs to a peer? */
|
||||
static final int ACK_FREQUENCY = 200;
|
||||
static final int ACK_FREQUENCY = 100;
|
||||
|
||||
public ACKSender(RouterContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
@ -31,10 +33,15 @@ public class EstablishmentManager {
|
||||
private Map _inboundStates;
|
||||
/** map of RemoteHostId to OutboundEstablishState */
|
||||
private Map _outboundStates;
|
||||
/** map of RemoteHostId to List of OutNetMessage for messages exceeding capacity */
|
||||
private Map _queuedOutbound;
|
||||
private boolean _alive;
|
||||
private Object _activityLock;
|
||||
private int _activity;
|
||||
|
||||
private static final int DEFAULT_MAX_CONCURRENT_ESTABLISH = 16;
|
||||
public static final String PROP_MAX_CONCURRENT_ESTABLISH = "i2np.udp.maxConcurrentEstablish";
|
||||
|
||||
public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(EstablishmentManager.class);
|
||||
@ -42,6 +49,7 @@ public class EstablishmentManager {
|
||||
_builder = new PacketBuilder(ctx);
|
||||
_inboundStates = new HashMap(32);
|
||||
_outboundStates = new HashMap(32);
|
||||
_queuedOutbound = new HashMap(32);
|
||||
_activityLock = new Object();
|
||||
_context.statManager().createRateStat("udp.inboundEstablishTime", "How long it takes for a new inbound session to be established", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.outboundEstablishTime", "How long it takes for a new outbound session to be established", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
@ -80,6 +88,18 @@ public class EstablishmentManager {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxConcurrentEstablish() {
|
||||
String val = _context.getProperty(PROP_MAX_CONCURRENT_ESTABLISH);
|
||||
if (val != null) {
|
||||
try {
|
||||
return Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return DEFAULT_MAX_CONCURRENT_ESTABLISH;
|
||||
}
|
||||
}
|
||||
return DEFAULT_MAX_CONCURRENT_ESTABLISH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message to its specified recipient by establishing a connection
|
||||
@ -104,12 +124,27 @@ public class EstablishmentManager {
|
||||
synchronized (_outboundStates) {
|
||||
OutboundEstablishState state = (OutboundEstablishState)_outboundStates.get(to);
|
||||
if (state == null) {
|
||||
state = new OutboundEstablishState(_context, remAddr, port,
|
||||
msg.getTarget().getIdentity(),
|
||||
new SessionKey(addr.getIntroKey()));
|
||||
_outboundStates.put(to, state);
|
||||
if (_outboundStates.size() >= getMaxConcurrentEstablish()) {
|
||||
List queued = (List)_queuedOutbound.get(to);
|
||||
if (queued == null) {
|
||||
queued = new ArrayList(1);
|
||||
_queuedOutbound.put(to, queued);
|
||||
}
|
||||
queued.add(msg);
|
||||
} else {
|
||||
state = new OutboundEstablishState(_context, remAddr, port,
|
||||
msg.getTarget().getIdentity(),
|
||||
new SessionKey(addr.getIntroKey()));
|
||||
_outboundStates.put(to, state);
|
||||
}
|
||||
}
|
||||
if (state != null) {
|
||||
state.addMessage(msg);
|
||||
List queued = (List)_queuedOutbound.remove(to);
|
||||
if (queued != null)
|
||||
for (int i = 0; i < queued.size(); i++)
|
||||
state.addMessage((OutNetMessage)queued.get(i));
|
||||
}
|
||||
state.addMessage(msg);
|
||||
}
|
||||
|
||||
notifyActivity();
|
||||
@ -177,9 +212,27 @@ public class EstablishmentManager {
|
||||
*/
|
||||
PeerState receiveData(OutboundEstablishState state) {
|
||||
state.dataReceived();
|
||||
int active = 0;
|
||||
int admitted = 0;
|
||||
int remaining = 0;
|
||||
synchronized (_outboundStates) {
|
||||
active = _outboundStates.size();
|
||||
_outboundStates.remove(state.getRemoteHostId());
|
||||
if (_queuedOutbound.size() > 0) {
|
||||
// there shouldn't have been queued messages for this active state, but just in case...
|
||||
List queued = (List)_queuedOutbound.remove(state.getRemoteHostId());
|
||||
if (queued != null) {
|
||||
for (int i = 0; i < queued.size(); i++)
|
||||
state.addMessage((OutNetMessage)queued.get(i));
|
||||
}
|
||||
|
||||
admitted = locked_admitQueued();
|
||||
}
|
||||
remaining = _queuedOutbound.size();
|
||||
}
|
||||
//if (admitted > 0)
|
||||
// _log.log(Log.CRIT, "Admitted " + admitted + " with " + remaining + " remaining queued and " + active + " active");
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Outbound established completely! yay");
|
||||
PeerState peer = handleCompletelyEstablished(state);
|
||||
@ -187,6 +240,40 @@ public class EstablishmentManager {
|
||||
return peer;
|
||||
}
|
||||
|
||||
private int locked_admitQueued() {
|
||||
int admitted = 0;
|
||||
while ( (_queuedOutbound.size() > 0) && (_outboundStates.size() < getMaxConcurrentEstablish()) ) {
|
||||
// ok, active shrunk, lets let some queued in. duplicate the synchronized
|
||||
// section from the add(
|
||||
|
||||
RemoteHostId to = (RemoteHostId)_queuedOutbound.keySet().iterator().next();
|
||||
List queued = (List)_queuedOutbound.remove(to);
|
||||
|
||||
if (queued.size() <= 0)
|
||||
continue;
|
||||
|
||||
OutNetMessage msg = (OutNetMessage)queued.get(0);
|
||||
RouterAddress ra = msg.getTarget().getTargetAddress(_transport.getStyle());
|
||||
if (ra == null) {
|
||||
for (int i = 0; i < queued.size(); i++)
|
||||
_transport.failed((OutNetMessage)queued.get(i));
|
||||
continue;
|
||||
}
|
||||
UDPAddress addr = new UDPAddress(ra);
|
||||
InetAddress remAddr = addr.getHostAddress();
|
||||
int port = addr.getPort();
|
||||
|
||||
OutboundEstablishState qstate = new OutboundEstablishState(_context, remAddr, port,
|
||||
msg.getTarget().getIdentity(),
|
||||
new SessionKey(addr.getIntroKey()));
|
||||
_outboundStates.put(to, qstate);
|
||||
|
||||
for (int i = 0; i < queued.size(); i++)
|
||||
qstate.addMessage((OutNetMessage)queued.get(i));
|
||||
admitted++;
|
||||
}
|
||||
return admitted;
|
||||
}
|
||||
|
||||
private void notifyActivity() {
|
||||
synchronized (_activityLock) {
|
||||
@ -429,7 +516,11 @@ public class EstablishmentManager {
|
||||
long now = _context.clock().now();
|
||||
long nextSendTime = -1;
|
||||
OutboundEstablishState outboundState = null;
|
||||
int admitted = 0;
|
||||
int remaining = 0;
|
||||
int active = 0;
|
||||
synchronized (_outboundStates) {
|
||||
active = _outboundStates.size();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("# outbound states: " + _outboundStates.size());
|
||||
for (Iterator iter = _outboundStates.values().iterator(); iter.hasNext(); ) {
|
||||
@ -473,8 +564,14 @@ public class EstablishmentManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
admitted = locked_admitQueued();
|
||||
remaining = _queuedOutbound.size();
|
||||
}
|
||||
|
||||
//if (admitted > 0)
|
||||
// _log.log(Log.CRIT, "Admitted " + admitted + " in push with " + remaining + " remaining queued and " + active + " active");
|
||||
|
||||
if (outboundState != null) {
|
||||
if (outboundState.getLifetime() > MAX_ESTABLISH_TIME) {
|
||||
if (outboundState.getState() != OutboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
|
||||
@ -484,7 +581,23 @@ public class EstablishmentManager {
|
||||
break;
|
||||
_transport.failed(msg);
|
||||
}
|
||||
_context.shitlist().shitlistRouter(outboundState.getRemoteIdentity().calculateHash(), "Unable to establish with SSU");
|
||||
String err = null;
|
||||
switch (outboundState.getState()) {
|
||||
case OutboundEstablishState.STATE_CONFIRMED_PARTIALLY:
|
||||
err = "Took too long to establish remote connection (confirmed partially)";
|
||||
break;
|
||||
case OutboundEstablishState.STATE_CREATED_RECEIVED:
|
||||
err = "Took too long to establish remote connection (created received)";
|
||||
break;
|
||||
case OutboundEstablishState.STATE_REQUEST_SENT:
|
||||
err = "Took too long to establish remote connection (request sent)";
|
||||
break;
|
||||
case OutboundEstablishState.STATE_UNKNOWN: // fallthrough
|
||||
default:
|
||||
err = "Took too long to establish remote connection (unknown state)";
|
||||
}
|
||||
|
||||
_context.shitlist().shitlistRouter(outboundState.getRemoteIdentity().calculateHash(), err);
|
||||
} else {
|
||||
while (true) {
|
||||
OutNetMessage msg = outboundState.getNextQueuedMessage();
|
||||
|
@ -319,4 +319,23 @@ public class InboundEstablishState {
|
||||
_lastReceive = _context.clock().now();
|
||||
_nextSend = _lastReceive;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(super.toString());
|
||||
if (_receivedX != null)
|
||||
buf.append(" ReceivedX: ").append(Base64.encode(_receivedX, 0, 4));
|
||||
if (_sentY != null)
|
||||
buf.append(" SentY: ").append(Base64.encode(_sentY, 0, 4));
|
||||
if (_aliceIP != null)
|
||||
buf.append(" AliceIP: ").append(Base64.encode(_aliceIP));
|
||||
buf.append(" AlicePort: ").append(_alicePort);
|
||||
if (_bobIP != null)
|
||||
buf.append(" BobIP: ").append(Base64.encode(_bobIP));
|
||||
buf.append(" BobPort: ").append(_bobPort);
|
||||
buf.append(" RelayTag: ").append(_sentRelayTag);
|
||||
buf.append(" SignedOn: ").append(_sentSignedOnTime);
|
||||
buf.append(" state: ").append(_currentState);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
@ -35,7 +37,7 @@ public class OutboundMessageFragments {
|
||||
/** if we can handle more messages explicitly, set this to true */
|
||||
private boolean _allowExcess;
|
||||
|
||||
private static final int MAX_ACTIVE = 32;
|
||||
private static final int MAX_ACTIVE = 64;
|
||||
// don't send a packet more than 10 times
|
||||
static final int MAX_VOLLEYS = 10;
|
||||
|
||||
@ -83,6 +85,7 @@ public class OutboundMessageFragments {
|
||||
|
||||
long start = _context.clock().now();
|
||||
int numActive = 0;
|
||||
int maxActive = Math.max(_transport.countActivePeers(), MAX_ACTIVE);
|
||||
while (_alive) {
|
||||
finishMessages();
|
||||
try {
|
||||
@ -90,7 +93,7 @@ public class OutboundMessageFragments {
|
||||
numActive = _activeMessages.size();
|
||||
if (!_alive)
|
||||
return false;
|
||||
else if (numActive < MAX_ACTIVE)
|
||||
else if (numActive < maxActive)
|
||||
return true;
|
||||
else if (_allowExcess)
|
||||
return true;
|
||||
@ -108,9 +111,18 @@ public class OutboundMessageFragments {
|
||||
*
|
||||
*/
|
||||
public void add(OutNetMessage msg) {
|
||||
I2NPMessage msgBody = msg.getMessage();
|
||||
RouterInfo target = msg.getTarget();
|
||||
if ( (msgBody == null) || (target == null) ) {
|
||||
synchronized (_activeMessages) {
|
||||
_activeMessages.notifyAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OutboundMessageState state = new OutboundMessageState(_context);
|
||||
boolean ok = state.initialize(msg);
|
||||
state.setPeer(_transport.getPeerState(msg.getTarget().getIdentity().calculateHash()));
|
||||
boolean ok = state.initialize(msg, msgBody);
|
||||
state.setPeer(_transport.getPeerState(target.getIdentity().calculateHash()));
|
||||
finishMessages();
|
||||
int active = 0;
|
||||
synchronized (_activeMessages) {
|
||||
@ -337,7 +349,7 @@ public class OutboundMessageFragments {
|
||||
}
|
||||
|
||||
private UDPPacket[] preparePackets(OutboundMessageState state, PeerState peer) {
|
||||
if (state != null) {
|
||||
if ( (state != null) && (peer != null) ) {
|
||||
int fragments = state.getFragmentCount();
|
||||
if (fragments < 0)
|
||||
return null;
|
||||
@ -420,14 +432,16 @@ public class OutboundMessageFragments {
|
||||
_context.statManager().addRateData("udp.sendConfirmVolley", numSends, state.getFragmentCount());
|
||||
_transport.succeeded(state.getMessage());
|
||||
int numFragments = state.getFragmentCount();
|
||||
if (state.getPeer() != null) {
|
||||
PeerState peer = state.getPeer();
|
||||
if (peer != null) {
|
||||
// this adjusts the rtt/rto/window/etc
|
||||
state.getPeer().messageACKed(numFragments*state.getFragmentSize(), state.getLifetime(), state.getMaxSends());
|
||||
peer.messageACKed(numFragments*state.getFragmentSize(), state.getLifetime(), state.getMaxSends());
|
||||
if (peer.getSendWindowBytesRemaining() > 0)
|
||||
_throttle.unchoke(peer.getRemotePeer());
|
||||
} else {
|
||||
_log.warn("message acked, but no peer attacked: " + state);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("message acked, but no peer attacked: " + state);
|
||||
}
|
||||
if (state.getPeer().getSendWindowBytesRemaining() > 0)
|
||||
_throttle.unchoke(state.getPeer().getRemotePeer());
|
||||
state.releaseResources();
|
||||
return numFragments;
|
||||
} else {
|
||||
|
@ -45,6 +45,7 @@ public class OutboundMessageState {
|
||||
}
|
||||
|
||||
public boolean initialize(OutNetMessage msg) {
|
||||
if (msg == null) return false;
|
||||
try {
|
||||
initialize(msg, msg.getMessage(), null);
|
||||
return true;
|
||||
@ -57,6 +58,9 @@ public class OutboundMessageState {
|
||||
}
|
||||
|
||||
public boolean initialize(I2NPMessage msg, PeerState peer) {
|
||||
if (msg == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
initialize(null, msg, peer);
|
||||
return true;
|
||||
@ -68,6 +72,21 @@ public class OutboundMessageState {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean initialize(OutNetMessage m, I2NPMessage msg) {
|
||||
if ( (m == null) || (msg == null) )
|
||||
return false;
|
||||
|
||||
try {
|
||||
initialize(m, msg, null);
|
||||
return true;
|
||||
} catch (OutOfMemoryError oom) {
|
||||
throw oom;
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error initializing " + msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize(OutNetMessage m, I2NPMessage msg, PeerState peer) {
|
||||
_message = m;
|
||||
_peer = peer;
|
||||
@ -200,7 +219,7 @@ public class OutboundMessageState {
|
||||
public void fragment(int fragmentSize) {
|
||||
int totalSize = _messageBuf.getValid();
|
||||
int numFragments = totalSize / fragmentSize;
|
||||
if (numFragments * fragmentSize != totalSize)
|
||||
if (numFragments * fragmentSize < totalSize)
|
||||
numFragments++;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
@ -456,6 +456,9 @@ public class PacketBuilder {
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*/
|
||||
public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toIntroKey, long nonce, SessionKey aliceIntroKey) {
|
||||
return buildPeerTestFromAlice(toIP, toPort, toIntroKey, toIntroKey, nonce, aliceIntroKey);
|
||||
}
|
||||
public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toCipherKey, SessionKey toMACKey, long nonce, SessionKey aliceIntroKey) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
@ -486,7 +489,7 @@ public class PacketBuilder {
|
||||
if ( (off % 16) != 0)
|
||||
off += 16 - (off % 16);
|
||||
packet.getPacket().setLength(off);
|
||||
authenticate(packet, toIntroKey, toIntroKey);
|
||||
authenticate(packet, toCipherKey, toMACKey);
|
||||
setTo(packet, toIP, toPort);
|
||||
return packet;
|
||||
}
|
||||
@ -580,7 +583,51 @@ public class PacketBuilder {
|
||||
setTo(packet, charlieIP, charliePort);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a packet as if we are Charlie sending Bob a packet verifying that we will help test Alice.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*/
|
||||
public UDPPacket buildPeerTestToBob(InetAddress bobIP, int bobPort, InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, long nonce, SessionKey bobCipherKey, SessionKey bobMACKey) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
|
||||
|
||||
// header
|
||||
data[off] = PEER_TEST_FLAG_BYTE;
|
||||
off++;
|
||||
long now = _context.clock().now() / 1000;
|
||||
DataHelper.toLong(data, off, 4, now);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending peer test " + nonce + " to Bob with time = " + new Date(now*1000));
|
||||
off += 4;
|
||||
|
||||
// now for the body
|
||||
DataHelper.toLong(data, off, 4, nonce);
|
||||
off += 4;
|
||||
byte ip[] = aliceIP.getAddress();
|
||||
DataHelper.toLong(data, off, 1, ip.length);
|
||||
off++;
|
||||
System.arraycopy(ip, 0, data, off, ip.length);
|
||||
off += ip.length;
|
||||
DataHelper.toLong(data, off, 2, alicePort);
|
||||
off += 2;
|
||||
System.arraycopy(aliceIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
|
||||
off += SessionKey.KEYSIZE_BYTES;
|
||||
|
||||
// we can pad here if we want, maybe randomized?
|
||||
|
||||
// pad up so we're on the encryption boundary
|
||||
if ( (off % 16) != 0)
|
||||
off += 16 - (off % 16);
|
||||
packet.getPacket().setLength(off);
|
||||
authenticate(packet, bobCipherKey, bobMACKey);
|
||||
setTo(packet, bobIP, bobPort);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void setTo(UDPPacket packet, InetAddress ip, int port) {
|
||||
packet.getPacket().setAddress(ip);
|
||||
packet.getPacket().setPort(port);
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.router.Router;
|
||||
@ -29,8 +31,12 @@ public class PacketHandler {
|
||||
private InboundMessageFragments _inbound;
|
||||
private PeerTestManager _testManager;
|
||||
private boolean _keepReading;
|
||||
private List _handlers;
|
||||
|
||||
private static final int NUM_HANDLERS = 3;
|
||||
/** let packets be up to 30s slow */
|
||||
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
|
||||
|
||||
|
||||
public PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, InboundMessageFragments inbound, PeerTestManager testManager) {
|
||||
_context = ctx;
|
||||
@ -40,6 +46,10 @@ public class PacketHandler {
|
||||
_establisher = establisher;
|
||||
_inbound = inbound;
|
||||
_testManager = testManager;
|
||||
_handlers = new ArrayList(NUM_HANDLERS);
|
||||
for (int i = 0; i < NUM_HANDLERS; i++) {
|
||||
_handlers.add(new Handler());
|
||||
}
|
||||
_context.statManager().createRateStat("udp.handleTime", "How long it takes to handle a received packet after its been pulled off the queue", "udp", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.queueTime", "How long after a packet is received can we begin handling it", "udp", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.receivePacketSkew", "How long ago after the packet was sent did we receive it", "udp", new long[] { 10*60*1000, 60*60*1000 });
|
||||
@ -52,8 +62,8 @@ public class PacketHandler {
|
||||
|
||||
public void startup() {
|
||||
_keepReading = true;
|
||||
for (int i = 0; i < NUM_HANDLERS; i++) {
|
||||
I2PThread t = new I2PThread(new Handler(), "Packet handler " + i + ": " + _endpoint.getListenPort());
|
||||
for (int i = 0; i < _handlers.size(); i++) {
|
||||
I2PThread t = new I2PThread((Handler)_handlers.get(i), "Packet handler " + i + ": " + _endpoint.getListenPort());
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
@ -62,30 +72,51 @@ public class PacketHandler {
|
||||
public void shutdown() {
|
||||
_keepReading = false;
|
||||
}
|
||||
|
||||
String getHandlerStatus() {
|
||||
StringBuffer rv = new StringBuffer();
|
||||
int size = _handlers.size();
|
||||
rv.append("Handlers: ").append(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Handler handler = (Handler)_handlers.get(i);
|
||||
rv.append(" handler ").append(i).append(" state: ").append(handler._state);
|
||||
}
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
private class Handler implements Runnable {
|
||||
private UDPPacketReader _reader;
|
||||
public volatile int _state;
|
||||
public Handler() {
|
||||
_reader = new UDPPacketReader(_context);
|
||||
_state = 0;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_state = 1;
|
||||
while (_keepReading) {
|
||||
_state = 2;
|
||||
UDPPacket packet = _endpoint.receive();
|
||||
_state = 3;
|
||||
if (packet == null) continue; // keepReading is probably false...
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received the packet " + packet);
|
||||
_state = 4;
|
||||
long queueTime = packet.getLifetime();
|
||||
long handleStart = _context.clock().now();
|
||||
try {
|
||||
_state = 5;
|
||||
handlePacket(_reader, packet);
|
||||
_state = 6;
|
||||
} catch (Exception e) {
|
||||
_state = 7;
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Crazy error handling a packet: " + packet, e);
|
||||
}
|
||||
long handleTime = _context.clock().now() - handleStart;
|
||||
_context.statManager().addRateData("udp.handleTime", handleTime, packet.getLifetime());
|
||||
_context.statManager().addRateData("udp.queueTime", queueTime, packet.getLifetime());
|
||||
_state = 8;
|
||||
|
||||
if (handleTime > 1000) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -95,244 +126,289 @@ public class PacketHandler {
|
||||
|
||||
// back to the cache with thee!
|
||||
packet.release();
|
||||
_state = 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePacket(UDPPacketReader reader, UDPPacket packet) {
|
||||
if (packet == null) return;
|
||||
|
||||
RemoteHostId rem = packet.getRemoteHost();
|
||||
PeerState state = _transport.getPeerState(rem);
|
||||
if (state == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received is not for a connected peer");
|
||||
InboundEstablishState est = _establisher.getInboundState(rem);
|
||||
if (est != null) {
|
||||
//}
|
||||
|
||||
private void handlePacket(UDPPacketReader reader, UDPPacket packet) {
|
||||
if (packet == null) return;
|
||||
|
||||
_state = 10;
|
||||
|
||||
RemoteHostId rem = packet.getRemoteHost();
|
||||
PeerState state = _transport.getPeerState(rem);
|
||||
if (state == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received IS for an inbound establishment");
|
||||
receivePacket(reader, packet, est);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received is not for an inbound establishment");
|
||||
OutboundEstablishState oest = _establisher.getOutboundState(rem);
|
||||
if (oest != null) {
|
||||
_log.debug("Packet received is not for a connected peer");
|
||||
_state = 11;
|
||||
InboundEstablishState est = _establisher.getInboundState(rem);
|
||||
if (est != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received IS for an outbound establishment");
|
||||
receivePacket(reader, packet, oest);
|
||||
_log.debug("Packet received IS for an inbound establishment");
|
||||
_state = 12;
|
||||
receivePacket(reader, packet, est);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received is not for an inbound or outbound establishment");
|
||||
// ok, not already known establishment, try as a new one
|
||||
receivePacket(reader, packet);
|
||||
_log.debug("Packet received is not for an inbound establishment");
|
||||
_state = 13;
|
||||
OutboundEstablishState oest = _establisher.getOutboundState(rem);
|
||||
if (oest != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received IS for an outbound establishment");
|
||||
_state = 14;
|
||||
receivePacket(reader, packet, oest);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received is not for an inbound or outbound establishment");
|
||||
// ok, not already known establishment, try as a new one
|
||||
_state = 15;
|
||||
receivePacket(reader, packet);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received IS for an existing peer");
|
||||
_state = 16;
|
||||
receivePacket(reader, packet, state);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received IS for an existing peer");
|
||||
receivePacket(reader, packet, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, PeerState state) {
|
||||
boolean isValid = packet.validate(state.getCurrentMACKey());
|
||||
if (!isValid) {
|
||||
if (state.getNextMACKey() != null)
|
||||
isValid = packet.validate(state.getNextMACKey());
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, PeerState state) {
|
||||
_state = 17;
|
||||
boolean isValid = packet.validate(state.getCurrentMACKey());
|
||||
if (!isValid) {
|
||||
_state = 18;
|
||||
if (state.getNextMACKey() != null)
|
||||
isValid = packet.validate(state.getNextMACKey());
|
||||
if (!isValid) {
|
||||
_state = 19;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed validation with existing con, trying as new con: " + packet);
|
||||
|
||||
isValid = packet.validate(_transport.getIntroKey());
|
||||
if (isValid) {
|
||||
_state = 20;
|
||||
// this is a stray packet from an inbound establishment
|
||||
// process, so try our intro key
|
||||
// (after an outbound establishment process, there wouldn't
|
||||
// be any stray packets)
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Validation with existing con failed, but validation as reestablish/stray passed");
|
||||
packet.decrypt(_transport.getIntroKey());
|
||||
} else {
|
||||
_state = 21;
|
||||
InboundEstablishState est = _establisher.getInboundState(packet.getRemoteHost());
|
||||
if (est != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet from an existing peer IS for an inbound establishment");
|
||||
_state = 22;
|
||||
receivePacket(reader, packet, est, false);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Validation with existing con failed, and validation as reestablish failed too. DROP");
|
||||
_context.statManager().addRateData("udp.droppedInvalidReestablish", packet.getLifetime(), packet.getExpiration());
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_state = 23;
|
||||
packet.decrypt(state.getNextCipherKey());
|
||||
}
|
||||
} else {
|
||||
_state = 24;
|
||||
packet.decrypt(state.getCurrentCipherKey());
|
||||
}
|
||||
|
||||
_state = 25;
|
||||
handlePacket(reader, packet, state, null, null);
|
||||
_state = 26;
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet) {
|
||||
_state = 27;
|
||||
boolean isValid = packet.validate(_transport.getIntroKey());
|
||||
if (!isValid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed validation with existing con, trying as new con: " + packet);
|
||||
|
||||
isValid = packet.validate(_transport.getIntroKey());
|
||||
_log.warn("Invalid introduction packet received: " + packet, new Exception("path"));
|
||||
_context.statManager().addRateData("udp.droppedInvalidEstablish", packet.getLifetime(), packet.getExpiration());
|
||||
_state = 28;
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Valid introduction packet received: " + packet);
|
||||
}
|
||||
|
||||
_state = 29;
|
||||
packet.decrypt(_transport.getIntroKey());
|
||||
handlePacket(reader, packet, null, null, null);
|
||||
_state = 30;
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state) {
|
||||
receivePacket(reader, packet, state, true);
|
||||
}
|
||||
/**
|
||||
* @param allowFallback if it isn't valid for this establishment state, try as a non-establishment packet
|
||||
*/
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state, boolean allowFallback) {
|
||||
_state = 31;
|
||||
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Attempting to receive a packet on a known inbound state: ");
|
||||
buf.append(state);
|
||||
buf.append(" MAC key: ").append(state.getMACKey());
|
||||
buf.append(" intro key: ").append(_transport.getIntroKey());
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
boolean isValid = false;
|
||||
if (state.getMACKey() != null) {
|
||||
isValid = packet.validate(state.getMACKey());
|
||||
if (isValid) {
|
||||
// this is a stray packet from an inbound establishment
|
||||
// process, so try our intro key
|
||||
// (after an outbound establishment process, there wouldn't
|
||||
// be any stray packets)
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Validation with existing con failed, but validation as reestablish/stray passed");
|
||||
packet.decrypt(_transport.getIntroKey());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Valid introduction packet received for inbound con: " + packet);
|
||||
|
||||
_state = 32;
|
||||
packet.decrypt(state.getCipherKey());
|
||||
handlePacket(reader, packet, null, null, null);
|
||||
return;
|
||||
} else {
|
||||
InboundEstablishState est = _establisher.getInboundState(packet.getRemoteHost());
|
||||
if (est != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet from an existing peer IS for an inbound establishment");
|
||||
receivePacket(reader, packet, est, false);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Validation with existing con failed, and validation as reestablish failed too. DROP");
|
||||
_context.statManager().addRateData("udp.droppedInvalidReestablish", packet.getLifetime(), packet.getExpiration());
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid introduction packet received for inbound con, falling back: " + packet);
|
||||
|
||||
_state = 33;
|
||||
}
|
||||
}
|
||||
if (allowFallback) {
|
||||
// ok, we couldn't handle it with the established stuff, so fall back
|
||||
// on earlier state packets
|
||||
_state = 34;
|
||||
receivePacket(reader, packet);
|
||||
} else {
|
||||
_context.statManager().addRateData("udp.droppedInvalidInboundEstablish", packet.getLifetime(), packet.getExpiration());
|
||||
}
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, OutboundEstablishState state) {
|
||||
_state = 35;
|
||||
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Attempting to receive a packet on a known outbound state: ");
|
||||
buf.append(state);
|
||||
buf.append(" MAC key: ").append(state.getMACKey());
|
||||
buf.append(" intro key: ").append(state.getIntroKey());
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
boolean isValid = false;
|
||||
if (state.getMACKey() != null) {
|
||||
_state = 36;
|
||||
isValid = packet.validate(state.getMACKey());
|
||||
if (isValid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Valid introduction packet received for outbound established con: " + packet);
|
||||
|
||||
_state = 37;
|
||||
packet.decrypt(state.getCipherKey());
|
||||
handlePacket(reader, packet, null, state, null);
|
||||
_state = 38;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
packet.decrypt(state.getNextCipherKey());
|
||||
}
|
||||
} else {
|
||||
packet.decrypt(state.getCurrentCipherKey());
|
||||
}
|
||||
|
||||
handlePacket(reader, packet, state, null, null);
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet) {
|
||||
boolean isValid = packet.validate(_transport.getIntroKey());
|
||||
if (!isValid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid introduction packet received: " + packet, new Exception("path"));
|
||||
_context.statManager().addRateData("udp.droppedInvalidEstablish", packet.getLifetime(), packet.getExpiration());
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Valid introduction packet received: " + packet);
|
||||
}
|
||||
|
||||
packet.decrypt(_transport.getIntroKey());
|
||||
handlePacket(reader, packet, null, null, null);
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state) {
|
||||
receivePacket(reader, packet, state, true);
|
||||
}
|
||||
/**
|
||||
* @param allowFallback if it isn't valid for this establishment state, try as a non-establishment packet
|
||||
*/
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, InboundEstablishState state, boolean allowFallback) {
|
||||
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Attempting to receive a packet on a known inbound state: ");
|
||||
buf.append(state);
|
||||
buf.append(" MAC key: ").append(state.getMACKey());
|
||||
buf.append(" intro key: ").append(_transport.getIntroKey());
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
boolean isValid = false;
|
||||
if (state.getMACKey() != null) {
|
||||
isValid = packet.validate(state.getMACKey());
|
||||
// keys not yet exchanged, lets try it with the peer's intro key
|
||||
isValid = packet.validate(state.getIntroKey());
|
||||
if (isValid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Valid introduction packet received for inbound con: " + packet);
|
||||
|
||||
packet.decrypt(state.getCipherKey());
|
||||
handlePacket(reader, packet, null, null, null);
|
||||
_log.warn("Valid introduction packet received for outbound established con with old intro key: " + packet);
|
||||
_state = 39;
|
||||
packet.decrypt(state.getIntroKey());
|
||||
handlePacket(reader, packet, null, state, null);
|
||||
_state = 40;
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid introduction packet received for inbound con, falling back: " + packet);
|
||||
|
||||
_log.warn("Invalid introduction packet received for outbound established con with old intro key, falling back: " + packet);
|
||||
}
|
||||
}
|
||||
if (allowFallback) {
|
||||
|
||||
// ok, we couldn't handle it with the established stuff, so fall back
|
||||
// on earlier state packets
|
||||
_state = 41;
|
||||
receivePacket(reader, packet);
|
||||
} else {
|
||||
_context.statManager().addRateData("udp.droppedInvalidInboundEstablish", packet.getLifetime(), packet.getExpiration());
|
||||
_state = 42;
|
||||
}
|
||||
}
|
||||
|
||||
private void receivePacket(UDPPacketReader reader, UDPPacket packet, OutboundEstablishState state) {
|
||||
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Attempting to receive a packet on a known outbound state: ");
|
||||
buf.append(state);
|
||||
buf.append(" MAC key: ").append(state.getMACKey());
|
||||
buf.append(" intro key: ").append(state.getIntroKey());
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
boolean isValid = false;
|
||||
if (state.getMACKey() != null) {
|
||||
isValid = packet.validate(state.getMACKey());
|
||||
if (isValid) {
|
||||
/**
|
||||
* Parse out the interesting bits and honor what it says
|
||||
*/
|
||||
private void handlePacket(UDPPacketReader reader, UDPPacket packet, PeerState state, OutboundEstablishState outState, InboundEstablishState inState) {
|
||||
_state = 43;
|
||||
reader.initialize(packet);
|
||||
_state = 44;
|
||||
long recvOn = packet.getBegin();
|
||||
long sendOn = reader.readTimestamp() * 1000;
|
||||
long skew = recvOn - sendOn;
|
||||
if (skew > GRACE_PERIOD) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Valid introduction packet received for outbound established con: " + packet);
|
||||
|
||||
packet.decrypt(state.getCipherKey());
|
||||
handlePacket(reader, packet, null, state, null);
|
||||
_log.warn("Packet too far in the future: " + new Date(sendOn/1000) + ": " + packet);
|
||||
_context.statManager().addRateData("udp.droppedInvalidSkew", skew, packet.getExpiration());
|
||||
return;
|
||||
} else if (skew < 0 - GRACE_PERIOD) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Packet too far in the past: " + new Date(sendOn/1000) + ": " + packet);
|
||||
_context.statManager().addRateData("udp.droppedInvalidSkew", 0-skew, packet.getExpiration());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// keys not yet exchanged, lets try it with the peer's intro key
|
||||
isValid = packet.validate(state.getIntroKey());
|
||||
if (isValid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Valid introduction packet received for outbound established con with old intro key: " + packet);
|
||||
packet.decrypt(state.getIntroKey());
|
||||
handlePacket(reader, packet, null, state, null);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid introduction packet received for outbound established con with old intro key, falling back: " + packet);
|
||||
}
|
||||
|
||||
// ok, we couldn't handle it with the established stuff, so fall back
|
||||
// on earlier state packets
|
||||
receivePacket(reader, packet);
|
||||
}
|
||||
|
||||
/** let packets be up to 30s slow */
|
||||
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
|
||||
|
||||
/**
|
||||
* Parse out the interesting bits and honor what it says
|
||||
*/
|
||||
private void handlePacket(UDPPacketReader reader, UDPPacket packet, PeerState state, OutboundEstablishState outState, InboundEstablishState inState) {
|
||||
reader.initialize(packet);
|
||||
long recvOn = packet.getBegin();
|
||||
long sendOn = reader.readTimestamp() * 1000;
|
||||
long skew = recvOn - sendOn;
|
||||
if (skew > GRACE_PERIOD) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Packet too far in the future: " + new Date(sendOn/1000) + ": " + packet);
|
||||
_context.statManager().addRateData("udp.droppedInvalidSkew", skew, packet.getExpiration());
|
||||
return;
|
||||
} else if (skew < 0 - GRACE_PERIOD) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Packet too far in the past: " + new Date(sendOn/1000) + ": " + packet);
|
||||
_context.statManager().addRateData("udp.droppedInvalidSkew", 0-skew, packet.getExpiration());
|
||||
return;
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received packet from " + state.getRemoteHostId().toString() + " with skew " + skew);
|
||||
state.adjustClockSkew((short)skew);
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("udp.receivePacketSkew", skew, packet.getLifetime());
|
||||
|
||||
//InetAddress fromHost = packet.getPacket().getAddress();
|
||||
//int fromPort = packet.getPacket().getPort();
|
||||
//RemoteHostId from = new RemoteHostId(fromHost.getAddress(), fromPort);
|
||||
RemoteHostId from = packet.getRemoteHost();
|
||||
|
||||
switch (reader.readPayloadType()) {
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
|
||||
_establisher.receiveSessionRequest(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED:
|
||||
_establisher.receiveSessionConfirmed(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_CREATED:
|
||||
_establisher.receiveSessionCreated(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_DATA:
|
||||
if (outState != null)
|
||||
state = _establisher.receiveData(outState);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received new DATA packet from " + state + ": " + packet);
|
||||
_inbound.receiveData(state, reader.getDataReader());
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_TEST:
|
||||
_testManager.receiveTest(from, reader);
|
||||
break;
|
||||
default:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unknown payload type: " + reader.readPayloadType());
|
||||
_context.statManager().addRateData("udp.droppedInvalidUnknown", packet.getLifetime(), packet.getExpiration());
|
||||
return;
|
||||
if (state != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received packet from " + state.getRemoteHostId().toString() + " with skew " + skew);
|
||||
state.adjustClockSkew((short)skew);
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("udp.receivePacketSkew", skew, packet.getLifetime());
|
||||
|
||||
//InetAddress fromHost = packet.getPacket().getAddress();
|
||||
//int fromPort = packet.getPacket().getPort();
|
||||
//RemoteHostId from = new RemoteHostId(fromHost.getAddress(), fromPort);
|
||||
_state = 45;
|
||||
RemoteHostId from = packet.getRemoteHost();
|
||||
_state = 46;
|
||||
|
||||
switch (reader.readPayloadType()) {
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
|
||||
_state = 47;
|
||||
_establisher.receiveSessionRequest(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED:
|
||||
_state = 48;
|
||||
_establisher.receiveSessionConfirmed(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_CREATED:
|
||||
_state = 49;
|
||||
_establisher.receiveSessionCreated(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_DATA:
|
||||
_state = 50;
|
||||
if (outState != null)
|
||||
state = _establisher.receiveData(outState);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received new DATA packet from " + state + ": " + packet);
|
||||
_inbound.receiveData(state, reader.getDataReader());
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_TEST:
|
||||
_state = 51;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received test packet: " + reader + " from " + from);
|
||||
_testManager.receiveTest(from, reader);
|
||||
break;
|
||||
default:
|
||||
_state = 52;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unknown payload type: " + reader.readPayloadType());
|
||||
_context.statManager().addRateData("udp.droppedInvalidUnknown", packet.getLifetime(), packet.getExpiration());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,13 +155,16 @@ public class PeerState {
|
||||
private static final int MINIMUM_WINDOW_BYTES = DEFAULT_SEND_WINDOW_BYTES;
|
||||
private static final int MAX_SEND_WINDOW_BYTES = 1024*1024;
|
||||
/*
|
||||
* 576 gives us 568 IP byes, 548 UDP bytes, and with an SSU data message,
|
||||
* 502 fragment bytes, which is enough to send a tunnel data message in 2
|
||||
* packets.
|
||||
* 596 gives us 588 IP byes, 568 UDP bytes, and with an SSU data message,
|
||||
* 522 fragment bytes, which is enough to send a tunnel data message in 2
|
||||
* packets. A tunnel data message sent over the wire is 1044 bytes, meaning
|
||||
* we need 522 fragment bytes to fit it in 2 packets - add 46 for SSU, 20
|
||||
* for UDP, and 8 for IP, giving us 596. round up to mod 16, giving a total
|
||||
* of 608
|
||||
*/
|
||||
private static final int DEFAULT_MTU = 1500;
|
||||
private static final int MIN_RTO = 500 + ACKSender.ACK_FREQUENCY;
|
||||
private static final int MAX_RTO = 2000; // 5000;
|
||||
private static final int DEFAULT_MTU = 608;//600; //1500;
|
||||
private static final int MIN_RTO = 1000 + ACKSender.ACK_FREQUENCY;
|
||||
private static final int MAX_RTO = 3000; // 5000;
|
||||
|
||||
public PeerState(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
|
@ -4,7 +4,14 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
@ -19,58 +26,47 @@ class PeerTestManager {
|
||||
private Log _log;
|
||||
private UDPTransport _transport;
|
||||
private PacketBuilder _packetBuilder;
|
||||
/**
|
||||
* circular list of nonces which we have received as if we were 'Charlie'
|
||||
* (meaning if we see it again, we aren't Bob and shouldn't find our own Charlie).
|
||||
* Synchronize against this when updating it
|
||||
*/
|
||||
private long _receiveAsCharlie[];
|
||||
/** index into _receiveAsCharlie which we should next write to */
|
||||
private int _receiveAsCharlieIndex;
|
||||
/** nonce we are currently running our own test as, or -1 */
|
||||
private long _currentTestNonce;
|
||||
private InetAddress _bobIP;
|
||||
private int _bobPort;
|
||||
private SessionKey _bobIntroKey;
|
||||
private long _testBeginTime;
|
||||
private long _lastSendTime;
|
||||
private long _receiveBobReplyTime;
|
||||
private long _receiveCharlieReplyTime;
|
||||
private InetAddress _charlieIP;
|
||||
private int _charliePort;
|
||||
private SessionKey _charlieIntroKey;
|
||||
private int _receiveBobReplyPort;
|
||||
private int _receiveCharlieReplyPort;
|
||||
/** map of Long(nonce) to PeerTestState for tests currently in progress */
|
||||
private Map _activeTests;
|
||||
/** current test we are running, or null */
|
||||
private PeerTestState _currentTest;
|
||||
private List _recentTests;
|
||||
|
||||
/** longest we will keep track of a Charlie nonce for */
|
||||
private static final int MAX_CHARLIE_LIFETIME = 10*1000;
|
||||
|
||||
|
||||
public PeerTestManager(RouterContext context, UDPTransport transport) {
|
||||
_context = context;
|
||||
_transport = transport;
|
||||
_log = context.logManager().getLog(PeerTestManager.class);
|
||||
_receiveAsCharlie = new long[64];
|
||||
_activeTests = new HashMap(64);
|
||||
_recentTests = Collections.synchronizedList(new ArrayList(16));
|
||||
_packetBuilder = new PacketBuilder(context);
|
||||
_currentTestNonce = -1;
|
||||
_currentTest = null;
|
||||
_context.statManager().createRateStat("udp.statusKnownCharlie", "How often the bob we pick passes us to a charlie we already have a session with?", "udp", new long[] { 60*1000, 20*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
private static final int RESEND_TIMEOUT = 5*1000;
|
||||
private static final int MAX_TEST_TIME = 30*1000;
|
||||
private static final long MAX_NONCE = (1l << 32) - 1l;
|
||||
public void runTest(InetAddress bobIP, int bobPort, SessionKey bobIntroKey) {
|
||||
_currentTestNonce = _context.random().nextLong(MAX_NONCE);
|
||||
_bobIP = bobIP;
|
||||
_bobPort = bobPort;
|
||||
_bobIntroKey = bobIntroKey;
|
||||
_charlieIP = null;
|
||||
_charliePort = -1;
|
||||
_charlieIntroKey = null;
|
||||
_testBeginTime = _context.clock().now();
|
||||
_lastSendTime = _testBeginTime;
|
||||
_receiveBobReplyTime = -1;
|
||||
_receiveCharlieReplyTime = -1;
|
||||
_receiveBobReplyPort = -1;
|
||||
_receiveCharlieReplyPort = -1;
|
||||
//public void runTest(InetAddress bobIP, int bobPort, SessionKey bobIntroKey) {
|
||||
public void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
|
||||
PeerTestState test = new PeerTestState();
|
||||
test.setNonce(_context.random().nextLong(MAX_NONCE));
|
||||
test.setBobIP(bobIP);
|
||||
test.setBobPort(bobPort);
|
||||
test.setBobCipherKey(bobCipherKey);
|
||||
test.setBobMACKey(bobMACKey);
|
||||
test.setBeginTime(_context.clock().now());
|
||||
test.setLastSendTime(test.getBeginTime());
|
||||
test.setOurRole(PeerTestState.ALICE);
|
||||
_currentTest = test;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Running test with bob = " + bobIP + ":" + bobPort + " " + test.getNonce());
|
||||
while (_recentTests.size() > 16)
|
||||
_recentTests.remove(0);
|
||||
_recentTests.add(new Long(test.getNonce()));
|
||||
|
||||
sendTestToBob();
|
||||
|
||||
@ -79,16 +75,17 @@ class PeerTestManager {
|
||||
|
||||
private class ContinueTest implements SimpleTimer.TimedEvent {
|
||||
public void timeReached() {
|
||||
if (_currentTestNonce < 0) {
|
||||
PeerTestState state = _currentTest;
|
||||
if (state == null) {
|
||||
// already completed
|
||||
return;
|
||||
} else if (expired()) {
|
||||
testComplete();
|
||||
} else {
|
||||
if (_receiveBobReplyTime < 0) {
|
||||
} else if (_context.clock().now() - state.getLastSendTime() >= RESEND_TIMEOUT) {
|
||||
if (state.getReceiveBobTime() <= 0) {
|
||||
// no message from Bob yet, send it again
|
||||
sendTestToBob();
|
||||
} else if (_receiveCharlieReplyTime < 0) {
|
||||
} else if (state.getReceiveCharlieTime() <= 0) {
|
||||
// received from Bob, but no reply from Charlie. send it to
|
||||
// Bob again so he pokes Charlie
|
||||
sendTestToBob();
|
||||
@ -100,16 +97,32 @@ class PeerTestManager {
|
||||
SimpleTimer.getInstance().addEvent(ContinueTest.this, RESEND_TIMEOUT);
|
||||
}
|
||||
}
|
||||
private boolean expired() { return _testBeginTime + MAX_TEST_TIME < _context.clock().now(); }
|
||||
private boolean expired() {
|
||||
PeerTestState state = _currentTest;
|
||||
if (state != null)
|
||||
return _currentTest.getBeginTime() + MAX_TEST_TIME < _context.clock().now();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTestToBob() {
|
||||
_transport.send(_packetBuilder.buildPeerTestFromAlice(_bobIP, _bobPort, _bobIntroKey,
|
||||
_currentTestNonce, _transport.getIntroKey()));
|
||||
PeerTestState test = _currentTest;
|
||||
if (test != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending test to bob: " + test.getBobIP() + ":" + test.getBobPort());
|
||||
_transport.send(_packetBuilder.buildPeerTestFromAlice(test.getBobIP(), test.getBobPort(), test.getBobCipherKey(), test.getBobMACKey(), //_bobIntroKey,
|
||||
test.getNonce(), _transport.getIntroKey()));
|
||||
}
|
||||
}
|
||||
private void sendTestToCharlie() {
|
||||
_transport.send(_packetBuilder.buildPeerTestFromAlice(_charlieIP, _charliePort, _charlieIntroKey,
|
||||
_currentTestNonce, _transport.getIntroKey()));
|
||||
PeerTestState test = _currentTest;
|
||||
if (test != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending test to charlie: " + test.getCharlieIP() + ":" + test.getCharliePort());
|
||||
_transport.send(_packetBuilder.buildPeerTestFromAlice(test.getCharlieIP(), test.getCharliePort(), test.getCharlieIntroKey(),
|
||||
test.getNonce(), _transport.getIntroKey()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -118,20 +131,64 @@ class PeerTestManager {
|
||||
* test
|
||||
*/
|
||||
private void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) {
|
||||
if (DataHelper.eq(from.getIP(), _bobIP.getAddress())) {
|
||||
_receiveBobReplyTime = _context.clock().now();
|
||||
_receiveBobReplyPort = testInfo.readPort();
|
||||
PeerTestState test = _currentTest;
|
||||
if ( (DataHelper.eq(from.getIP(), test.getBobIP().getAddress())) && (from.getPort() == test.getBobPort()) ) {
|
||||
byte ip[] = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(ip, 0);
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByAddress(ip);
|
||||
test.setAliceIP(addr);
|
||||
test.setReceiveBobTime(_context.clock().now());
|
||||
test.setAlicePort(testInfo.readPort());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive test reply from bob @ " + from.getIP() + " via our " + test.getAlicePort() + "/" + test.getAlicePortFromCharlie());
|
||||
if (test.getAlicePortFromCharlie() > 0)
|
||||
testComplete();
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to get our IP from bob's reply: " + from + ", " + testInfo, uhe);
|
||||
}
|
||||
} else {
|
||||
if (_receiveCharlieReplyTime > 0) {
|
||||
PeerState charlieSession = _transport.getPeerState(from);
|
||||
if (charlieSession != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bob chose a charlie we already have a session to, cancelling the test and rerunning (bob: "
|
||||
+ _currentTest + ", charlie: " + from + ")");
|
||||
_currentTest = null;
|
||||
_context.statManager().addRateData("udp.statusKnownCharlie", 1, 0);
|
||||
honorStatus(CommSystemFacade.STATUS_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (test.getReceiveCharlieTime() > 0) {
|
||||
// this is our second charlie, yay!
|
||||
_receiveCharlieReplyPort = testInfo.readPort();
|
||||
testComplete();
|
||||
test.setAlicePortFromCharlie(testInfo.readPort());
|
||||
byte ip[] = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(ip, 0);
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByAddress(ip);
|
||||
test.setAliceIPFromCharlie(addr);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive test reply from charlie @ " + test.getCharlieIP() + " via our "
|
||||
+ test.getAlicePort() + "/" + test.getAlicePortFromCharlie());
|
||||
if (test.getReceiveBobTime() > 0)
|
||||
testComplete();
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Charlie @ " + from + " said we were an invalid IP address: " + uhe.getMessage(), uhe);
|
||||
}
|
||||
} else {
|
||||
// ok, first charlie. send 'em a packet
|
||||
_receiveCharlieReplyTime = _context.clock().now();
|
||||
_charliePort = from.getPort();
|
||||
test.setReceiveCharlieTime(_context.clock().now());
|
||||
SessionKey charlieIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
testInfo.readIntroKey(charlieIntroKey.getData(), 0);
|
||||
test.setCharlieIntroKey(charlieIntroKey);
|
||||
try {
|
||||
_charlieIP = InetAddress.getByAddress(from.getIP());
|
||||
test.setCharlieIP(InetAddress.getByAddress(from.getIP()));
|
||||
test.setCharliePort(from.getPort());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive test from charlie @ " + from);
|
||||
sendTestToCharlie();
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -141,12 +198,6 @@ class PeerTestManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static final short STATUS_REACHABLE_OK = 0;
|
||||
private static final short STATUS_REACHABLE_DIFFERENT = 1;
|
||||
private static final short STATUS_CHARLIE_DIED = 2;
|
||||
private static final short STATUS_REJECT_UNSOLICITED = 3;
|
||||
private static final short STATUS_BOB_SUCKS = 4;
|
||||
|
||||
/**
|
||||
* Evaluate the info we have and act accordingly, since the test has either timed out or
|
||||
* we have successfully received the second PeerTest from a Charlie.
|
||||
@ -154,40 +205,34 @@ class PeerTestManager {
|
||||
*/
|
||||
private void testComplete() {
|
||||
short status = -1;
|
||||
if (_receiveCharlieReplyPort > 0) {
|
||||
PeerTestState test = _currentTest;
|
||||
if (test == null) return;
|
||||
if (test.getAlicePortFromCharlie() > 0) {
|
||||
// we received a second message from charlie
|
||||
if (_receiveBobReplyPort == _receiveCharlieReplyPort) {
|
||||
status = STATUS_REACHABLE_OK;
|
||||
if ( (test.getAlicePort() == test.getAlicePortFromCharlie()) &&
|
||||
(test.getAliceIP() != null) && (test.getAliceIPFromCharlie() != null) &&
|
||||
(test.getAliceIP().equals(test.getAliceIPFromCharlie())) ) {
|
||||
status = CommSystemFacade.STATUS_OK;
|
||||
} else {
|
||||
status = STATUS_REACHABLE_DIFFERENT;
|
||||
status = CommSystemFacade.STATUS_DIFFERENT;
|
||||
}
|
||||
} else if (_receiveCharlieReplyTime > 0) {
|
||||
} else if (test.getReceiveCharlieTime() > 0) {
|
||||
// we received only one message from charlie
|
||||
status = STATUS_CHARLIE_DIED;
|
||||
} else if (_receiveBobReplyTime > 0) {
|
||||
status = CommSystemFacade.STATUS_UNKNOWN;
|
||||
} else if (test.getReceiveBobTime() > 0) {
|
||||
// we received a message from bob but no messages from charlie
|
||||
status = STATUS_REJECT_UNSOLICITED;
|
||||
status = CommSystemFacade.STATUS_REJECT_UNSOLICITED;
|
||||
} else {
|
||||
// we never received anything from bob - he is either down or ignoring us
|
||||
status = STATUS_BOB_SUCKS;
|
||||
// we never received anything from bob - he is either down,
|
||||
// ignoring us, or unable to get a Charlie to respond
|
||||
status = CommSystemFacade.STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
honorStatus(status);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Test complete: " + test);
|
||||
|
||||
// now zero everything out
|
||||
_currentTestNonce = -1;
|
||||
_bobIP = null;
|
||||
_bobPort = -1;
|
||||
_bobIntroKey = null;
|
||||
_charlieIP = null;
|
||||
_charliePort = -1;
|
||||
_charlieIntroKey = null;
|
||||
_testBeginTime = -1;
|
||||
_lastSendTime = -1;
|
||||
_receiveBobReplyTime = -1;
|
||||
_receiveCharlieReplyTime = -1;
|
||||
_receiveBobReplyPort = -1;
|
||||
_receiveCharlieReplyPort = -1;
|
||||
honorStatus(status);
|
||||
_currentTest = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,15 +241,9 @@ class PeerTestManager {
|
||||
*
|
||||
*/
|
||||
private void honorStatus(short status) {
|
||||
switch (status) {
|
||||
case STATUS_REACHABLE_OK:
|
||||
case STATUS_REACHABLE_DIFFERENT:
|
||||
case STATUS_CHARLIE_DIED:
|
||||
case STATUS_REJECT_UNSOLICITED:
|
||||
case STATUS_BOB_SUCKS:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Test results: status = " + status);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Test results: status = " + status);
|
||||
_transport.setReachabilityStatus(status);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,32 +254,62 @@ class PeerTestManager {
|
||||
*/
|
||||
public void receiveTest(RemoteHostId from, UDPPacketReader reader) {
|
||||
UDPPacketReader.PeerTestReader testInfo = reader.getPeerTestReader();
|
||||
byte fromIP[] = null;
|
||||
int fromPort = testInfo.readPort();
|
||||
byte testIP[] = null;
|
||||
int testPort = testInfo.readPort();
|
||||
long nonce = testInfo.readNonce();
|
||||
if (nonce == _currentTestNonce) {
|
||||
PeerTestState test = _currentTest;
|
||||
if ( (test != null) && (test.getNonce() == nonce) ) {
|
||||
receiveTestReply(from, testInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (testInfo.readIPSize() > 0) && (fromPort > 0) ) {
|
||||
fromIP = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(fromIP, 0);
|
||||
if ( (testInfo.readIPSize() > 0) && (testPort > 0) ) {
|
||||
testIP = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(testIP, 0);
|
||||
}
|
||||
|
||||
if ( ( (fromIP == null) && (fromPort <= 0) ) || // info is unknown or...
|
||||
(DataHelper.eq(fromIP, from.getIP()) && (fromPort == from.getPort())) ) { // info matches sender
|
||||
boolean weAreCharlie = false;
|
||||
synchronized (_receiveAsCharlie) {
|
||||
weAreCharlie = (Arrays.binarySearch(_receiveAsCharlie, nonce) != -1);
|
||||
}
|
||||
if (weAreCharlie) {
|
||||
receiveFromAliceAsCharlie(from, testInfo, nonce);
|
||||
PeerTestState state = null;
|
||||
synchronized (_activeTests) {
|
||||
state = (PeerTestState)_activeTests.get(new Long(nonce));
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
if ( (testIP == null) || (testPort <= 0) ) {
|
||||
// we are bob, since we haven't seen this nonce before AND its coming from alice
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("test IP/port are blank coming from " + from + ", assuming we are Bob and they are alice");
|
||||
receiveFromAliceAsBob(from, testInfo, nonce, null);
|
||||
} else {
|
||||
receiveFromAliceAsBob(from, testInfo, nonce);
|
||||
if (_recentTests.contains(new Long(nonce))) {
|
||||
// ignore the packet, as its a holdover from a recently completed locally
|
||||
// initiated test
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We are charlie, as te testIP/port is " + testIP + ":" + testPort + " and the state is unknown for " + nonce);
|
||||
// we are charlie, since alice never sends us her IP and port, only bob does (and,
|
||||
// erm, we're not alice, since it isn't our nonce)
|
||||
receiveFromBobAsCharlie(from, testInfo, nonce, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
receiveFromBobAsCharlie(from, fromIP, fromPort, nonce, testInfo);
|
||||
if (state.getOurRole() == PeerTestState.BOB) {
|
||||
if (DataHelper.eq(from.getIP(), state.getAliceIP().getAddress()) &&
|
||||
(from.getPort() == state.getAlicePort()) ) {
|
||||
receiveFromAliceAsBob(from, testInfo, nonce, state);
|
||||
} else if (DataHelper.eq(from.getIP(), state.getCharlieIP().getAddress()) &&
|
||||
(from.getPort() == state.getCharliePort()) ) {
|
||||
receiveFromCharlieAsBob(from, state);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received from a fourth party as bob! alice: " + state.getAliceIP() + ", charlie: " + state.getCharlieIP() + ", dave: " + from);
|
||||
}
|
||||
} else if (state.getOurRole() == PeerTestState.CHARLIE) {
|
||||
if ( (testIP == null) || (testPort <= 0) ) {
|
||||
receiveFromAliceAsCharlie(from, testInfo, nonce);
|
||||
} else {
|
||||
receiveFromBobAsCharlie(from, testInfo, nonce, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,34 +318,65 @@ class PeerTestManager {
|
||||
* so we must be Charlie receiving a PeerTest from Bob.
|
||||
*
|
||||
*/
|
||||
private void receiveFromBobAsCharlie(RemoteHostId from, byte fromIP[], int fromPort, long nonce, UDPPacketReader.PeerTestReader testInfo) {
|
||||
if (fromIP == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("From address received from Bob (we are Charlie) is invalid: " + from + ": " + testInfo);
|
||||
return;
|
||||
private void receiveFromBobAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
|
||||
boolean isNew = false;
|
||||
if (state == null) {
|
||||
isNew = true;
|
||||
state = new PeerTestState();
|
||||
state.setOurRole(PeerTestState.CHARLIE);
|
||||
}
|
||||
if (fromPort <= 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("From port received from Bob (we are Charlie) is invalid: " + fromPort + ": " + testInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
synchronized (_receiveAsCharlie) {
|
||||
index = _receiveAsCharlieIndex;
|
||||
_receiveAsCharlie[index] = nonce;
|
||||
_receiveAsCharlieIndex = (index + 1) % _receiveAsCharlie.length;
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new RemoveCharlie(nonce, index), MAX_CHARLIE_LIFETIME);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive test as charlie nonce " + nonce);
|
||||
|
||||
int sz = testInfo.readIPSize();
|
||||
byte aliceIPData[] = new byte[sz];
|
||||
try {
|
||||
InetAddress aliceIP = InetAddress.getByAddress(fromIP);
|
||||
SessionKey aliceIntroKey = new SessionKey();
|
||||
testInfo.readIP(aliceIPData, 0);
|
||||
int alicePort = testInfo.readPort();
|
||||
InetAddress aliceIP = InetAddress.getByAddress(aliceIPData);
|
||||
InetAddress bobIP = InetAddress.getByAddress(from.getIP());
|
||||
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, fromPort, aliceIntroKey, _transport.getIntroKey(), nonce);
|
||||
|
||||
state.setAliceIP(aliceIP);
|
||||
state.setAlicePort(alicePort);
|
||||
state.setAliceIntroKey(aliceIntroKey);
|
||||
state.setNonce(nonce);
|
||||
state.setBobIP(bobIP);
|
||||
state.setBobPort(from.getPort());
|
||||
state.setLastSendTime(_context.clock().now());
|
||||
state.setOurRole(PeerTestState.CHARLIE);
|
||||
state.setReceiveBobTime(_context.clock().now());
|
||||
|
||||
PeerState bob = _transport.getPeerState(from);
|
||||
if (bob == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received from bob (" + from + ") who hasn't established a session with us, refusing to help him test " + aliceIP +":" + alicePort);
|
||||
return;
|
||||
} else {
|
||||
state.setBobCipherKey(bob.getCurrentCipherKey());
|
||||
state.setBobMACKey(bob.getCurrentMACKey());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive from bob (" + from + ") as charlie, sending back to bob and sending to alice @ " + aliceIP + ":" + alicePort);
|
||||
|
||||
if (isNew) {
|
||||
synchronized (_activeTests) {
|
||||
_activeTests.put(new Long(nonce), state);
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
|
||||
}
|
||||
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToBob(bobIP, from.getPort(), aliceIP, alicePort, aliceIntroKey, nonce, state.getBobCipherKey(), state.getBobMACKey());
|
||||
_transport.send(packet);
|
||||
|
||||
packet = _packetBuilder.buildPeerTestToAlice(aliceIP, alicePort, aliceIntroKey, _transport.getIntroKey(), nonce);
|
||||
_transport.send(packet);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to build the aliceIP from " + from, uhe);
|
||||
_log.warn("Unable to build the aliceIP from " + from + ", ip size: " + sz + " ip val: " + Base64.encode(aliceIPData), uhe);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,35 +385,81 @@ class PeerTestManager {
|
||||
* any info in the message), plus we are not acting as Charlie (so we've got to be Bob).
|
||||
*
|
||||
*/
|
||||
private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce) {
|
||||
// we are Bob, so send Alice her PeerTest, pick a Charlie, and
|
||||
// send Charlie Alice's info
|
||||
PeerState charlie = _transport.getPeerState(UDPAddress.CAPACITY_TESTING);
|
||||
private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
|
||||
// we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info
|
||||
PeerState charlie = null;
|
||||
RouterInfo charlieInfo = null;
|
||||
if (state == null) { // pick a new charlie
|
||||
for (int i = 0; i < 5; i++) {
|
||||
charlie = _transport.getPeerState(UDPAddress.CAPACITY_TESTING);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Picking charlie as " + charlie + " for alice of " + from);
|
||||
if ( (charlie != null) && (!DataHelper.eq(charlie.getRemoteHostId(), from)) ) {
|
||||
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
|
||||
if (charlieInfo != null)
|
||||
break;
|
||||
}
|
||||
charlie = null;
|
||||
}
|
||||
} else {
|
||||
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
|
||||
if (charlie != null)
|
||||
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
|
||||
}
|
||||
|
||||
if (charlie == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to pick a charlie");
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress aliceIP = null;
|
||||
SessionKey aliceIntroKey = null;
|
||||
try {
|
||||
aliceIP = InetAddress.getByAddress(from.getIP());
|
||||
aliceIntroKey = new SessionKey();
|
||||
aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
|
||||
|
||||
RouterInfo info = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
|
||||
if (info == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No info for charlie: " + charlie);
|
||||
return;
|
||||
}
|
||||
|
||||
UDPAddress addr = new UDPAddress(info.getTargetAddress(UDPTransport.STYLE));
|
||||
UDPAddress addr = new UDPAddress(charlieInfo.getTargetAddress(UDPTransport.STYLE));
|
||||
SessionKey charlieIntroKey = new SessionKey(addr.getIntroKey());
|
||||
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, charlieIntroKey, nonce);
|
||||
_transport.send(packet);
|
||||
//UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, charlieIntroKey, nonce);
|
||||
//_transport.send(packet);
|
||||
|
||||
packet = _packetBuilder.buildPeerTestToCharlie(aliceIP, from.getPort(), aliceIntroKey, nonce,
|
||||
charlie.getRemoteIPAddress(),
|
||||
charlie.getRemotePort(),
|
||||
charlie.getCurrentCipherKey(),
|
||||
charlie.getCurrentMACKey());
|
||||
boolean isNew = false;
|
||||
if (state == null) {
|
||||
isNew = true;
|
||||
state = new PeerTestState();
|
||||
state.setBeginTime(_context.clock().now());
|
||||
}
|
||||
state.setAliceIP(aliceIP);
|
||||
state.setAlicePort(from.getPort());
|
||||
state.setAliceIntroKey(aliceIntroKey);
|
||||
state.setNonce(nonce);
|
||||
state.setCharlieIP(charlie.getRemoteIPAddress());
|
||||
state.setCharliePort(charlie.getRemotePort());
|
||||
state.setCharlieIntroKey(charlieIntroKey);
|
||||
state.setLastSendTime(_context.clock().now());
|
||||
state.setOurRole(PeerTestState.BOB);
|
||||
state.setReceiveAliceTime(_context.clock().now());
|
||||
|
||||
if (isNew) {
|
||||
synchronized (_activeTests) {
|
||||
_activeTests.put(new Long(nonce), state);
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
|
||||
}
|
||||
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToCharlie(aliceIP, from.getPort(), aliceIntroKey, nonce,
|
||||
charlie.getRemoteIPAddress(),
|
||||
charlie.getRemotePort(),
|
||||
charlie.getCurrentCipherKey(),
|
||||
charlie.getCurrentMACKey());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive from alice as bob for " + nonce + ", picking charlie @ " + charlie.getRemoteIPAddress() + ":"
|
||||
+ charlie.getRemotePort() + " for alice @ " + aliceIP + ":" + from.getPort());
|
||||
|
||||
_transport.send(packet);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -321,6 +467,23 @@ class PeerTestManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The PeerTest message came from one of the Charlies picked for an existing test, so send Alice the
|
||||
* packet verifying participation.
|
||||
*
|
||||
*/
|
||||
private void receiveFromCharlieAsBob(RemoteHostId from, PeerTestState state) {
|
||||
state.setReceiveCharlieTime(_context.clock().now());
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(state.getAliceIP(), state.getAlicePort(),
|
||||
state.getAliceIntroKey(), state.getCharlieIntroKey(),
|
||||
state.getNonce());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive from charlie @ " + from + " as bob, sending alice back the ok @ " + state.getAliceIP() + ":" + state.getAlicePort());
|
||||
|
||||
_transport.send(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are charlie, so send Alice her PeerTest message
|
||||
*
|
||||
@ -328,9 +491,13 @@ class PeerTestManager {
|
||||
private void receiveFromAliceAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce) {
|
||||
try {
|
||||
InetAddress aliceIP = InetAddress.getByAddress(from.getIP());
|
||||
SessionKey aliceIntroKey = new SessionKey();
|
||||
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
|
||||
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, _transport.getIntroKey(), nonce);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive from alice as charlie, w/ alice @ " + aliceIP + ":" + from.getPort() + " and nonce " + nonce);
|
||||
|
||||
_transport.send(packet);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -341,20 +508,15 @@ class PeerTestManager {
|
||||
/**
|
||||
* forget about charlie's nonce after 60s.
|
||||
*/
|
||||
private class RemoveCharlie implements SimpleTimer.TimedEvent {
|
||||
private class RemoveTest implements SimpleTimer.TimedEvent {
|
||||
private long _nonce;
|
||||
private int _index;
|
||||
public RemoveCharlie(long nonce, int index) {
|
||||
public RemoveTest(long nonce) {
|
||||
_nonce = nonce;
|
||||
_index = index;
|
||||
}
|
||||
public void timeReached() {
|
||||
/** only forget about an entry if we haven't already moved on */
|
||||
synchronized (_receiveAsCharlie) {
|
||||
if (_receiveAsCharlie[_index] == _nonce)
|
||||
_receiveAsCharlie[_index] = -1;
|
||||
synchronized (_activeTests) {
|
||||
_activeTests.remove(new Long(_nonce));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
118
router/java/src/net/i2p/router/transport/udp/PeerTestState.java
Normal file
118
router/java/src/net/i2p/router/transport/udp/PeerTestState.java
Normal file
@ -0,0 +1,118 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PeerTestState {
|
||||
private long _testNonce;
|
||||
private short _ourRole;
|
||||
private InetAddress _aliceIP;
|
||||
private int _alicePort;
|
||||
private InetAddress _bobIP;
|
||||
private int _bobPort;
|
||||
private InetAddress _charlieIP;
|
||||
private int _charliePort;
|
||||
private InetAddress _aliceIPFromCharlie;
|
||||
private int _alicePortFromCharlie;
|
||||
private SessionKey _aliceIntroKey;
|
||||
private SessionKey _charlieIntroKey;
|
||||
private SessionKey _bobCipherKey;
|
||||
private SessionKey _bobMACKey;
|
||||
private long _beginTime;
|
||||
private long _lastSendTime;
|
||||
private long _receiveAliceTime;
|
||||
private long _receiveBobTime;
|
||||
private long _receiveCharlieTime;
|
||||
|
||||
public static final short ALICE = 1;
|
||||
public static final short BOB = 2;
|
||||
public static final short CHARLIE = 3;
|
||||
|
||||
public synchronized long getNonce() { return _testNonce; }
|
||||
public synchronized void setNonce(long nonce) { _testNonce = nonce; }
|
||||
/** who are we? Alice, bob, or charlie? */
|
||||
public synchronized short getOurRole() { return _ourRole; }
|
||||
public synchronized void setOurRole(short role) { _ourRole = role; }
|
||||
/**
|
||||
* If we are Alice, this will contain the IP that Bob says we
|
||||
* can be reached at - the IP Charlie says we can be reached
|
||||
* at is _aliceIPFromCharlie
|
||||
*
|
||||
*/
|
||||
public synchronized InetAddress getAliceIP() { return _aliceIP; }
|
||||
public synchronized void setAliceIP(InetAddress ip) { _aliceIP = ip; }
|
||||
public synchronized InetAddress getBobIP() { return _bobIP; }
|
||||
public synchronized void setBobIP(InetAddress ip) { _bobIP = ip; }
|
||||
public synchronized InetAddress getCharlieIP() { return _charlieIP; }
|
||||
public synchronized void setCharlieIP(InetAddress ip) { _charlieIP = ip; }
|
||||
public synchronized InetAddress getAliceIPFromCharlie() { return _aliceIPFromCharlie; }
|
||||
public synchronized void setAliceIPFromCharlie(InetAddress ip) { _aliceIPFromCharlie = ip; }
|
||||
/**
|
||||
* If we are Alice, this will contain the port that Bob says we
|
||||
* can be reached at - the port Charlie says we can be reached
|
||||
* at is _alicePortFromCharlie
|
||||
*
|
||||
*/
|
||||
public synchronized int getAlicePort() { return _alicePort; }
|
||||
public synchronized void setAlicePort(int alicePort) { _alicePort = alicePort; }
|
||||
public synchronized int getBobPort() { return _bobPort; }
|
||||
public synchronized void setBobPort(int bobPort) { _bobPort = bobPort; }
|
||||
public synchronized int getCharliePort() { return _charliePort; }
|
||||
public synchronized void setCharliePort(int charliePort) { _charliePort = charliePort; }
|
||||
|
||||
public synchronized int getAlicePortFromCharlie() { return _alicePortFromCharlie; }
|
||||
public synchronized void setAlicePortFromCharlie(int alicePortFromCharlie) { _alicePortFromCharlie = alicePortFromCharlie; }
|
||||
|
||||
public synchronized SessionKey getAliceIntroKey() { return _aliceIntroKey; }
|
||||
public synchronized void setAliceIntroKey(SessionKey key) { _aliceIntroKey = key; }
|
||||
public synchronized SessionKey getCharlieIntroKey() { return _charlieIntroKey; }
|
||||
public synchronized void setCharlieIntroKey(SessionKey key) { _charlieIntroKey = key; }
|
||||
public synchronized SessionKey getBobCipherKey() { return _bobCipherKey; }
|
||||
public synchronized void setBobCipherKey(SessionKey key) { _bobCipherKey = key; }
|
||||
public synchronized SessionKey getBobMACKey() { return _bobMACKey; }
|
||||
public synchronized void setBobMACKey(SessionKey key) { _bobMACKey = key; }
|
||||
|
||||
/** when did this test begin? */
|
||||
public synchronized long getBeginTime() { return _beginTime; }
|
||||
public synchronized void setBeginTime(long when) { _beginTime = when; }
|
||||
/** when did we last send out a packet? */
|
||||
public synchronized long getLastSendTime() { return _lastSendTime; }
|
||||
public synchronized void setLastSendTime(long when) { _lastSendTime = when; }
|
||||
/** when did we last hear from alice? */
|
||||
public synchronized long getReceiveAliceTime() { return _receiveAliceTime; }
|
||||
public synchronized void setReceiveAliceTime(long when) { _receiveAliceTime = when; }
|
||||
/** when did we last hear from bob? */
|
||||
public synchronized long getReceiveBobTime() { return _receiveBobTime; }
|
||||
public synchronized void setReceiveBobTime(long when) { _receiveBobTime = when; }
|
||||
/** when did we last hear from charlie? */
|
||||
public synchronized long getReceiveCharlieTime() { return _receiveCharlieTime; }
|
||||
public synchronized void setReceiveCharlieTime(long when) { _receiveCharlieTime = when; }
|
||||
|
||||
public synchronized String toString() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("Role: ");
|
||||
if (_ourRole == ALICE) buf.append("Alice");
|
||||
else if (_ourRole == BOB) buf.append("Bob");
|
||||
else if (_ourRole == CHARLIE) buf.append("Charlie");
|
||||
else buf.append("unkown!");
|
||||
if (_aliceIP != null)
|
||||
buf.append(" alice: ").append(_aliceIP).append(':').append(_alicePort);
|
||||
if (_aliceIPFromCharlie != null)
|
||||
buf.append(" (fromCharlie ").append(_aliceIPFromCharlie).append(':').append(_alicePortFromCharlie).append(')');
|
||||
if (_bobIP != null)
|
||||
buf.append(" bob: ").append(_bobIP).append(':').append(_bobPort);
|
||||
if (_charlieIP != null)
|
||||
buf.append(" charlie: ").append(_charlieIP).append(':').append(_charliePort);
|
||||
buf.append(" last send after ").append(_lastSendTime - _beginTime).append("ms");
|
||||
if (_receiveAliceTime > 0)
|
||||
buf.append(" receive from alice after ").append(_receiveAliceTime - _beginTime).append("ms");
|
||||
if (_receiveBobTime > 0)
|
||||
buf.append(" receive from bob after ").append(_receiveBobTime - _beginTime).append("ms");
|
||||
if (_receiveCharlieTime > 0)
|
||||
buf.append(" receive from charlie after ").append(_receiveCharlieTime - _beginTime).append("ms");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -20,9 +20,9 @@ public class UDPAddress {
|
||||
public static final String PROP_HOST = "host";
|
||||
public static final String PROP_INTRO_KEY = "key";
|
||||
|
||||
public static final String PROP_CAPACITY = "opts";
|
||||
public static final char CAPACITY_TESTING = 'A';
|
||||
public static final char CAPACITY_INTRODUCER = 'B';
|
||||
public static final String PROP_CAPACITY = "caps";
|
||||
public static final char CAPACITY_TESTING = 'B';
|
||||
public static final char CAPACITY_INTRODUCER = 'C';
|
||||
|
||||
public UDPAddress(RouterAddress addr) {
|
||||
parse(addr);
|
||||
|
@ -15,12 +15,14 @@ public class UDPEndpoint {
|
||||
private RouterContext _context;
|
||||
private Log _log;
|
||||
private int _listenPort;
|
||||
private UDPTransport _transport;
|
||||
private UDPSender _sender;
|
||||
private UDPReceiver _receiver;
|
||||
|
||||
public UDPEndpoint(RouterContext ctx, int listenPort) throws SocketException {
|
||||
public UDPEndpoint(RouterContext ctx, UDPTransport transport, int listenPort) throws SocketException {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UDPEndpoint.class);
|
||||
_transport = transport;
|
||||
|
||||
_listenPort = listenPort;
|
||||
}
|
||||
@ -32,7 +34,7 @@ public class UDPEndpoint {
|
||||
try {
|
||||
DatagramSocket socket = new DatagramSocket(_listenPort);
|
||||
_sender = new UDPSender(_context, socket, "UDPSend on " + _listenPort);
|
||||
_receiver = new UDPReceiver(_context, socket, "UDPReceive on " + _listenPort);
|
||||
_receiver = new UDPReceiver(_context, _transport, socket, "UDPReceive on " + _listenPort);
|
||||
_sender.startup();
|
||||
_receiver.startup();
|
||||
} catch (SocketException se) {
|
||||
|
@ -36,7 +36,7 @@ public class UDPEndpointTest {
|
||||
int base = 2000 + _context.random().nextInt(10000);
|
||||
for (int i = 0; i < numPeers; i++) {
|
||||
_log.debug("Building " + i);
|
||||
UDPEndpoint endpoint = new UDPEndpoint(_context, base + i);
|
||||
UDPEndpoint endpoint = new UDPEndpoint(_context, null, base + i);
|
||||
_endpoints[i] = endpoint;
|
||||
endpoint.startup();
|
||||
I2PThread read = new I2PThread(new TestRead(endpoint), "Test read " + i);
|
||||
|
@ -104,6 +104,8 @@ public class UDPPacketReader {
|
||||
return "Session created packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
|
||||
return "Session request packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_TEST:
|
||||
return "Peer test packet";
|
||||
default:
|
||||
return "Other packet type...";
|
||||
}
|
||||
|
@ -28,13 +28,15 @@ public class UDPReceiver {
|
||||
private List _inboundQueue;
|
||||
private boolean _keepRunning;
|
||||
private Runner _runner;
|
||||
private UDPTransport _transport;
|
||||
|
||||
public UDPReceiver(RouterContext ctx, DatagramSocket socket, String name) {
|
||||
public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UDPReceiver.class);
|
||||
_name = name;
|
||||
_inboundQueue = new ArrayList(128);
|
||||
_socket = socket;
|
||||
_transport = transport;
|
||||
_runner = new Runner();
|
||||
_context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.droppedInbound", "How many packet are queued up but not yet received when we drop", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
@ -64,8 +66,8 @@ public class UDPReceiver {
|
||||
return _runner.updateListeningPort(socket, newPort);
|
||||
}
|
||||
|
||||
/** if a packet been sitting in the queue for 2 seconds, drop subsequent packets */
|
||||
private static final long MAX_QUEUE_PERIOD = 2*1000;
|
||||
/** if a packet been sitting in the queue for a full second (meaning the handlers are overwhelmed), drop subsequent packets */
|
||||
private static final long MAX_QUEUE_PERIOD = 1*1000;
|
||||
|
||||
private static final float ARTIFICIAL_DROP_PROBABILITY = 0.0f; // 0.02f; // 0.0f;
|
||||
|
||||
@ -90,22 +92,38 @@ public class UDPReceiver {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received: " + packet);
|
||||
|
||||
boolean rejected = false;
|
||||
int queueSize = 0;
|
||||
long headPeriod = 0;
|
||||
synchronized (_inboundQueue) {
|
||||
int queueSize = _inboundQueue.size();
|
||||
queueSize = _inboundQueue.size();
|
||||
if (queueSize > 0) {
|
||||
long headPeriod = ((UDPPacket)_inboundQueue.get(0)).getLifetime();
|
||||
headPeriod = ((UDPPacket)_inboundQueue.get(0)).getLifetime();
|
||||
if (headPeriod > MAX_QUEUE_PERIOD) {
|
||||
_context.statManager().addRateData("udp.droppedInbound", queueSize, headPeriod);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Dropping inbound packet with " + queueSize + " queued for " + headPeriod);
|
||||
rejected = true;
|
||||
_inboundQueue.notifyAll();
|
||||
return queueSize;
|
||||
}
|
||||
}
|
||||
_inboundQueue.add(packet);
|
||||
_inboundQueue.notifyAll();
|
||||
return queueSize + 1;
|
||||
if (!rejected) {
|
||||
_inboundQueue.add(packet);
|
||||
_inboundQueue.notifyAll();
|
||||
return queueSize + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// rejected
|
||||
_context.statManager().addRateData("udp.droppedInbound", queueSize, headPeriod);
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
StringBuffer msg = new StringBuffer();
|
||||
msg.append("Dropping inbound packet with ");
|
||||
msg.append(queueSize);
|
||||
msg.append(" queued for ");
|
||||
msg.append(headPeriod);
|
||||
if (_transport != null)
|
||||
msg.append(" packet handlers: ").append(_transport.getPacketHandlerStatus());
|
||||
_log.warn(msg.toString());
|
||||
}
|
||||
return queueSize;
|
||||
}
|
||||
|
||||
private class ArtificiallyDelayedReceive implements SimpleTimer.TimedEvent {
|
||||
@ -128,7 +146,7 @@ public class UDPReceiver {
|
||||
_inboundQueue.notifyAll();
|
||||
return rv;
|
||||
} else {
|
||||
_inboundQueue.wait();
|
||||
_inboundQueue.wait(500);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
@ -16,6 +16,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -26,6 +27,7 @@ import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.Transport;
|
||||
@ -63,6 +65,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
private UDPFlooder _flooder;
|
||||
private PeerTestManager _testManager;
|
||||
private ExpirePeerEvent _expireEvent;
|
||||
private PeerTestEvent _testEvent;
|
||||
private short _reachabilityStatus;
|
||||
private long _reachabilityStatusLastUpdated;
|
||||
|
||||
/** list of RelayPeer objects for people who will relay to us */
|
||||
private List _relayPeers;
|
||||
@ -82,7 +87,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
private TransportBid _slowBid;
|
||||
/** shared slow bid for unconnected peers when we want to prefer UDP */
|
||||
private TransportBid _slowPreferredBid;
|
||||
|
||||
|
||||
public static final String STYLE = "SSU";
|
||||
public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
|
||||
|
||||
@ -116,6 +121,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
|
||||
private static final int MAX_CONSECUTIVE_FAILED = 5;
|
||||
|
||||
private static final int TEST_FREQUENCY = 3*60*1000;
|
||||
|
||||
public UDPTransport(RouterContext ctx) {
|
||||
super(ctx);
|
||||
_context = ctx;
|
||||
@ -141,9 +148,18 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_inboundFragments = new InboundMessageFragments(_context, _fragments, this);
|
||||
_flooder = new UDPFlooder(_context, this);
|
||||
_expireEvent = new ExpirePeerEvent();
|
||||
_testEvent = new PeerTestEvent();
|
||||
_reachabilityStatus = CommSystemFacade.STATUS_UNKNOWN;
|
||||
|
||||
_context.statManager().createRateStat("udp.droppedPeer", "How long ago did we receive from a dropped peer (duration == session lifetime", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.droppedPeerInactive", "How long ago did we receive from a dropped peer (duration == session lifetime)", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.peersByCapacity", "How many peers of the given capacity were available to pick between? (duration == (int)capacity)", "udp", new long[] { 1*60*1000, 5*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.statusOK", "How many times the peer test returned OK", "udp", new long[] { 5*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.statusDifferent", "How many times the peer test returned different IP/ports", "udp", new long[] { 5*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.statusReject", "How many times the peer test returned reject unsolicited", "udp", new long[] { 5*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.statusUnknown", "How many times the peer test returned an unknown result", "udp", new long[] { 5*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.addressTestInsteadOfUpdate", "How many times we fire off a peer test of ourselves instead of adjusting our own reachable address?", "udp", new long[] { 1*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.addressUpdated", "How many times we adjust our own reachable IP address", "udp", new long[] { 1*60*1000, 20*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
@ -199,7 +215,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
if (_endpoint == null) {
|
||||
try {
|
||||
_endpoint = new UDPEndpoint(_context, port);
|
||||
_endpoint = new UDPEndpoint(_context, this, port);
|
||||
} catch (SocketException se) {
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Unable to listen on the UDP port (" + port + ")", se);
|
||||
@ -234,6 +250,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_refiller.startup();
|
||||
_flooder.startup();
|
||||
_expireEvent.setIsAlive(true);
|
||||
_testEvent.setIsAlive(true);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@ -254,6 +271,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
if (_inboundFragments != null)
|
||||
_inboundFragments.shutdown();
|
||||
_expireEvent.setIsAlive(false);
|
||||
_testEvent.setIsAlive(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,28 +298,46 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
|
||||
boolean fixedPort = getIsPortFixed();
|
||||
boolean updated = false;
|
||||
boolean fireTest = false;
|
||||
synchronized (this) {
|
||||
if ( (_externalListenHost == null) ||
|
||||
(!eq(_externalListenHost.getAddress(), _externalListenPort, ourIP, ourPort)) ) {
|
||||
try {
|
||||
_externalListenHost = InetAddress.getByAddress(ourIP);
|
||||
if (!fixedPort)
|
||||
_externalListenPort = ourPort;
|
||||
rebuildExternalAddress();
|
||||
replaceAddress(_externalAddress);
|
||||
updated = true;
|
||||
} catch (UnknownHostException uhe) {
|
||||
_externalListenHost = null;
|
||||
if ( (_reachabilityStatus != CommSystemFacade.STATUS_OK) ||
|
||||
(_context.clock().now() - _reachabilityStatusLastUpdated > 2*TEST_FREQUENCY) ) {
|
||||
// they told us something different and our tests are either old or failing
|
||||
try {
|
||||
_externalListenHost = InetAddress.getByAddress(ourIP);
|
||||
if (!fixedPort)
|
||||
_externalListenPort = ourPort;
|
||||
rebuildExternalAddress();
|
||||
replaceAddress(_externalAddress);
|
||||
updated = true;
|
||||
} catch (UnknownHostException uhe) {
|
||||
_externalListenHost = null;
|
||||
}
|
||||
} else {
|
||||
// they told us something different, but our tests are recent and positive,
|
||||
// so lets test again
|
||||
fireTest = true;
|
||||
}
|
||||
} else {
|
||||
// matched what we expect
|
||||
}
|
||||
}
|
||||
|
||||
if (!fixedPort)
|
||||
_context.router().setConfigSetting(PROP_EXTERNAL_PORT, ourPort+"");
|
||||
_context.router().saveConfig();
|
||||
|
||||
if (updated)
|
||||
if (fireTest) {
|
||||
_context.statManager().addRateData("udp.addressTestInsteadOfUpdate", 1, 0);
|
||||
_testEvent.forceRun();
|
||||
SimpleTimer.getInstance().addEvent(_testEvent, 5*1000);
|
||||
} else if (updated) {
|
||||
_context.statManager().addRateData("udp.addressUpdated", 1, 0);
|
||||
if (!fixedPort)
|
||||
_context.router().setConfigSetting(PROP_EXTERNAL_PORT, ourPort+"");
|
||||
_context.router().saveConfig();
|
||||
_context.router().rebuildRouterInfo();
|
||||
_testEvent.forceRun();
|
||||
SimpleTimer.getInstance().addEvent(_testEvent, 5*1000);
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean eq(byte laddr[], int lport, byte raddr[], int rport) {
|
||||
@ -341,19 +377,44 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if we haven't received anything in the last 5 minutes from a peer, don't
|
||||
* trust its known capacities
|
||||
*/
|
||||
private static final int MAX_INACTIVITY_FOR_CAPACITY = 5*60*1000;
|
||||
/** pick a random peer with the given capacity */
|
||||
public PeerState getPeerState(char capacity) {
|
||||
long now = _context.clock().now();
|
||||
int index = _context.random().nextInt(1024);
|
||||
List peers = _peersByCapacity[capacity-'A'];
|
||||
synchronized (peers) {
|
||||
int size = peers.size();
|
||||
if (size <= 0) return null;
|
||||
index = index % size;
|
||||
return (PeerState)peers.get(index);
|
||||
int size = 0;
|
||||
int off = 0;
|
||||
PeerState rv = null;
|
||||
while (rv == null) {
|
||||
synchronized (peers) {
|
||||
size = peers.size();
|
||||
if (size > 0) {
|
||||
index = (index + off) % size;
|
||||
rv = (PeerState)peers.get(index);
|
||||
}
|
||||
}
|
||||
if (rv == null)
|
||||
break;
|
||||
if (_context.shitlist().isShitlisted(rv.getRemotePeer()))
|
||||
rv = null;
|
||||
else if (now - rv.getLastReceiveTime() > MAX_INACTIVITY_FOR_CAPACITY)
|
||||
rv = null;
|
||||
else
|
||||
break;
|
||||
off++;
|
||||
if (off >= size)
|
||||
break;
|
||||
}
|
||||
_context.statManager().addRateData("udp.peersByCapacity", size, capacity);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final int MAX_PEERS_PER_CAPACITY = 16;
|
||||
private static final int MAX_PEERS_PER_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Intercept RouterInfo entries received directly from a peer to inject them into
|
||||
@ -361,29 +422,41 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
*
|
||||
*/
|
||||
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
|
||||
|
||||
if (inMsg instanceof DatabaseStoreMessage) {
|
||||
DatabaseStoreMessage dsm = (DatabaseStoreMessage)inMsg;
|
||||
if (dsm.getType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) {
|
||||
if (dsm.getValueType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) {
|
||||
Hash from = remoteIdentHash;
|
||||
if (from == null)
|
||||
from = remoteIdent.getHash();
|
||||
|
||||
if (from.equals(dsm.getKey())) {
|
||||
// db info received directly from the peer - inject it into the peersByCapacity
|
||||
RouterInfo info = dsm.getRouterInfo();
|
||||
Properties opts = info.getOptions();
|
||||
if ( (opts != null) && (info.isValid()) ) {
|
||||
String capacities = opts.getProperty(UDPAddress.PROP_CAPACITY);
|
||||
if (capacities != null) {
|
||||
PeerState peer = getPeerState(from);
|
||||
for (int i = 0; i < capacities.length(); i++) {
|
||||
char capacity = capacities.charAt(i);
|
||||
List peers = _peersByCapacity[capacity];
|
||||
synchronized (peers) {
|
||||
if ( (peers.size() < MAX_PEERS_PER_CAPACITY) && (!peers.contains(peer)) )
|
||||
peers.add(peer);
|
||||
Set addresses = info.getAddresses();
|
||||
for (Iterator iter = addresses.iterator(); iter.hasNext(); ) {
|
||||
RouterAddress addr = (RouterAddress)iter.next();
|
||||
if (!STYLE.equals(addr.getTransportStyle()))
|
||||
continue;
|
||||
Properties opts = addr.getOptions();
|
||||
if ( (opts != null) && (info.isValid()) ) {
|
||||
String capacities = opts.getProperty(UDPAddress.PROP_CAPACITY);
|
||||
if (capacities != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Intercepting and storing the capacities for " + from.toBase64() + ": " + capacities);
|
||||
PeerState peer = getPeerState(from);
|
||||
for (int i = 0; i < capacities.length(); i++) {
|
||||
char capacity = capacities.charAt(i);
|
||||
List peers = _peersByCapacity[capacity-'A'];
|
||||
synchronized (peers) {
|
||||
if ( (peers.size() < MAX_PEERS_PER_CAPACITY) && (!peers.contains(peer)) )
|
||||
peers.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// this was an SSU address so we're done now
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -450,8 +523,46 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
dropPeer(peer, true);
|
||||
}
|
||||
private void dropPeer(PeerState peer, boolean shouldShitlist) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dropping remote peer: " + peer + " shitlist? " + shouldShitlist, new Exception("Dropped by"));
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
long now = _context.clock().now();
|
||||
StringBuffer buf = new StringBuffer(4096);
|
||||
long timeSinceSend = now - peer.getLastSendTime();
|
||||
long timeSinceRecv = now - peer.getLastReceiveTime();
|
||||
long timeSinceAck = now - peer.getLastACKSend();
|
||||
buf.append("Dropping remote peer: ").append(peer.toString()).append(" shitlist? ").append(shouldShitlist);
|
||||
buf.append(" lifetime: ").append(now - peer.getKeyEstablishedTime());
|
||||
buf.append(" time since send/recv/ack: ").append(timeSinceSend).append(" / ");
|
||||
buf.append(timeSinceRecv).append(" / ").append(timeSinceAck);
|
||||
|
||||
buf.append("Existing peers: \n");
|
||||
synchronized (_peersByIdent) {
|
||||
for (Iterator iter = _peersByIdent.keySet().iterator(); iter.hasNext(); ) {
|
||||
Hash c = (Hash)iter.next();
|
||||
PeerState p = (PeerState)_peersByIdent.get(c);
|
||||
if (c.equals(peer.getRemotePeer())) {
|
||||
if (p != peer) {
|
||||
buf.append(" SAME PEER, DIFFERENT STATE ");
|
||||
} else {
|
||||
buf.append(" same peer, same state ");
|
||||
}
|
||||
} else {
|
||||
buf.append("Peer ").append(p.toString()).append(" ");
|
||||
}
|
||||
|
||||
buf.append(" lifetime: ").append(now - p.getKeyEstablishedTime());
|
||||
|
||||
timeSinceSend = now - p.getLastSendTime();
|
||||
timeSinceRecv = now - p.getLastReceiveTime();
|
||||
timeSinceAck = now - p.getLastACKSend();
|
||||
|
||||
buf.append(" time since send/recv/ack: ").append(timeSinceSend).append(" / ");
|
||||
buf.append(timeSinceRecv).append(" / ").append(timeSinceAck);
|
||||
buf.append("\n");
|
||||
}
|
||||
}
|
||||
_log.info(buf.toString(), new Exception("Dropped by"));
|
||||
}
|
||||
|
||||
if (peer.getRemotePeer() != null) {
|
||||
dropPeerCapacities(peer);
|
||||
|
||||
@ -494,7 +605,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
if (capacities != null) {
|
||||
for (int i = 0; i < capacities.length(); i++) {
|
||||
char capacity = capacities.charAt(i);
|
||||
List peers = _peersByCapacity[capacity];
|
||||
List peers = _peersByCapacity[capacity-'A'];
|
||||
synchronized (peers) {
|
||||
peers.remove(peer);
|
||||
}
|
||||
@ -627,6 +738,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
if ( (_externalListenPort > 0) && (_externalListenHost != null) ) {
|
||||
options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort));
|
||||
options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress());
|
||||
// if we have explicit external addresses, they had better be reachable
|
||||
options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING);
|
||||
} else {
|
||||
// grab 3 relays randomly
|
||||
synchronized (_relayPeers) {
|
||||
@ -659,6 +772,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
replaceAddress(addr);
|
||||
}
|
||||
|
||||
String getPacketHandlerStatus() {
|
||||
PacketHandler handler = _handler;
|
||||
if (handler != null)
|
||||
return handler.getHandlerStatus();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public void failed(OutboundMessageState msg) {
|
||||
if (msg == null) return;
|
||||
int consecutive = 0;
|
||||
@ -682,8 +803,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
public void succeeded(OutNetMessage msg) {
|
||||
if (msg == null) return;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending message succeeded: " + msg);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending message succeeded: " + msg);
|
||||
super.afterSend(msg, true);
|
||||
}
|
||||
|
||||
@ -708,6 +829,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
synchronized (_peersByIdent) {
|
||||
peers = new ArrayList(_peersByIdent.values());
|
||||
}
|
||||
long offsetTotal = 0;
|
||||
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<b>UDP connections: ").append(peers.size()).append("</b><br />\n");
|
||||
@ -748,6 +870,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
buf.append(" [choked]");
|
||||
if (peer.getConsecutiveFailedSends() > 0)
|
||||
buf.append(" [").append(peer.getConsecutiveFailedSends()).append(" failures]");
|
||||
if (_context.shitlist().isShitlisted(peer.getRemotePeer()))
|
||||
buf.append(" [shitlisted]");
|
||||
buf.append("</td>");
|
||||
|
||||
buf.append("<td>");
|
||||
@ -769,6 +893,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
buf.append("<td>");
|
||||
buf.append(peer.getClockSkew()/1000);
|
||||
buf.append("s</td>");
|
||||
offsetTotal = offsetTotal + peer.getClockSkew();
|
||||
|
||||
buf.append("<td>");
|
||||
buf.append(peer.getSendWindowBytes()/1024);
|
||||
@ -815,6 +940,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
|
||||
out.write("</table>\n");
|
||||
|
||||
buf.append("<b>Average clock skew, UDP peers:");
|
||||
if (peers.size() > 0)
|
||||
buf.append(offsetTotal / peers.size()).append("ms</b><br><br>\n");
|
||||
else
|
||||
buf.append("n/a</b><br><br>\n");
|
||||
|
||||
out.write(buf.toString());
|
||||
buf.setLength(0);
|
||||
}
|
||||
|
||||
private static final DecimalFormat _fmt = new DecimalFormat("#,##0.00");
|
||||
@ -839,6 +973,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
public String toString() { return "UDP bid @ " + getLatencyMs(); }
|
||||
}
|
||||
|
||||
private static final int EXPIRE_TIMEOUT = 10*60*1000;
|
||||
|
||||
private class ExpirePeerEvent implements SimpleTimer.TimedEvent {
|
||||
private List _peers;
|
||||
// toAdd and toRemove are kept separate from _peers so that add and
|
||||
@ -853,10 +989,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_toRemove = new ArrayList(4);
|
||||
}
|
||||
public void timeReached() {
|
||||
long inactivityCutoff = _context.clock().now() - 10*60*1000;
|
||||
long inactivityCutoff = _context.clock().now() - EXPIRE_TIMEOUT;
|
||||
for (int i = 0; i < _peers.size(); i++) {
|
||||
PeerState peer = (PeerState)_peers.get(i);
|
||||
if (peer.getLastReceiveTime() < inactivityCutoff) {
|
||||
if ( (peer.getLastReceiveTime() < inactivityCutoff) && (peer.getLastSendTime() < inactivityCutoff) ) {
|
||||
dropPeer(peer, false);
|
||||
_peers.remove(i);
|
||||
i--;
|
||||
@ -865,8 +1001,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
synchronized (_toAdd) {
|
||||
for (int i = 0; i < _toAdd.size(); i++) {
|
||||
PeerState peer = (PeerState)_toAdd.get(i);
|
||||
if (!_peers.contains(peer))
|
||||
_peers.add(peer);
|
||||
_peers.remove(peer); // in case we are switching peers
|
||||
_peers.add(peer);
|
||||
}
|
||||
_toAdd.clear();
|
||||
}
|
||||
@ -905,4 +1041,100 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we haven't had a non-unknown test result in 5 minutes, we really dont know. Otherwise,
|
||||
* when we receive an unknown we should ignore that value and try again (with different peers)
|
||||
*
|
||||
*/
|
||||
private static final long STATUS_GRACE_PERIOD = 5*60*1000;
|
||||
|
||||
void setReachabilityStatus(short status) {
|
||||
long now = _context.clock().now();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
_context.statManager().addRateData("udp.statusOK", 1, 0);
|
||||
_reachabilityStatus = status;
|
||||
_reachabilityStatusLastUpdated = now;
|
||||
break;
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
_context.statManager().addRateData("udp.statusDifferent", 1, 0);
|
||||
_reachabilityStatus = status;
|
||||
_reachabilityStatusLastUpdated = now;
|
||||
break;
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
_context.statManager().addRateData("udp.statusReject", 1, 0);
|
||||
_reachabilityStatus = status;
|
||||
_reachabilityStatusLastUpdated = now;
|
||||
break;
|
||||
case CommSystemFacade.STATUS_UNKNOWN:
|
||||
default:
|
||||
_context.statManager().addRateData("udp.statusUnknown", 1, 0);
|
||||
if (now - _reachabilityStatusLastUpdated < STATUS_GRACE_PERIOD) {
|
||||
_testEvent.forceRun();
|
||||
SimpleTimer.getInstance().addEvent(_testEvent, 5*1000);
|
||||
} else {
|
||||
_reachabilityStatus = status;
|
||||
_reachabilityStatusLastUpdated = now;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public short getReachabilityStatus() { return _reachabilityStatus; }
|
||||
public void recheckReachability() {
|
||||
_testEvent.runTest();
|
||||
}
|
||||
|
||||
private static final String PROP_SHOULD_TEST = "i2np.udp.shouldTest";
|
||||
|
||||
private boolean shouldTest() {
|
||||
if (true) return true;
|
||||
String val = _context.getProperty(PROP_SHOULD_TEST);
|
||||
return ( (val != null) && ("true".equals(val)) );
|
||||
}
|
||||
|
||||
private class PeerTestEvent implements SimpleTimer.TimedEvent {
|
||||
private boolean _alive;
|
||||
/** when did we last test our reachability */
|
||||
private long _lastTested;
|
||||
private boolean _forceRun;
|
||||
|
||||
public void timeReached() {
|
||||
if (shouldTest()) {
|
||||
long now = _context.clock().now();
|
||||
if ( (_forceRun) || (now - _lastTested >= TEST_FREQUENCY) ) {
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
if (_alive) {
|
||||
long delay = _context.random().nextInt(2*TEST_FREQUENCY);
|
||||
SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay);
|
||||
}
|
||||
}
|
||||
|
||||
private void runTest() {
|
||||
PeerState bob = getPeerState(UDPAddress.CAPACITY_TESTING);
|
||||
if (bob != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Running periodic test with bob = " + bob);
|
||||
_testManager.runTest(bob.getRemoteIPAddress(), bob.getRemotePort(), bob.getCurrentCipherKey(), bob.getCurrentMACKey());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to run a periodic test, as there are no peers with the capacity required");
|
||||
}
|
||||
_lastTested = _context.clock().now();
|
||||
}
|
||||
|
||||
private void forceRun() { _forceRun = true; }
|
||||
|
||||
public void setIsAlive(boolean isAlive) {
|
||||
_alive = isAlive;
|
||||
if (isAlive) {
|
||||
long delay = _context.random().nextInt(2*TEST_FREQUENCY);
|
||||
SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay);
|
||||
} else {
|
||||
SimpleTimer.getInstance().removeEvent(PeerTestEvent.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ public class TunnelParticipant {
|
||||
}
|
||||
|
||||
if ( (_config != null) && (_config.getSendTo() != null) ) {
|
||||
_config.incrementProcessedMessages();
|
||||
RouterInfo ri = _nextHopCache;
|
||||
if (ri == null)
|
||||
ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
|
||||
@ -83,6 +82,7 @@ public class TunnelParticipant {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send off to nextHop directly (" + _config.getSendTo().toBase64().substring(0,4)
|
||||
+ " for " + msg);
|
||||
_config.incrementProcessedMessages();
|
||||
send(_config, msg, ri);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@ -3,6 +3,7 @@ package net.i2p.router.tunnel.pool;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
@ -19,6 +20,7 @@ import net.i2p.router.message.GarlicMessageBuilder;
|
||||
import net.i2p.router.message.PayloadGarlicConfig;
|
||||
import net.i2p.router.message.SendMessageDirectJob;
|
||||
import net.i2p.router.tunnel.HopConfig;
|
||||
import net.i2p.router.peermanager.TunnelHistory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -31,6 +33,7 @@ import net.i2p.util.Log;
|
||||
public class HandleTunnelCreateMessageJob extends JobImpl {
|
||||
private Log _log;
|
||||
private TunnelCreateMessage _request;
|
||||
private boolean _alreadySearched;
|
||||
|
||||
/** job builder to redirect all tunnelCreateMessages through this job type */
|
||||
static class Builder implements HandlerJobBuilder {
|
||||
@ -46,14 +49,19 @@ public class HandleTunnelCreateMessageJob extends JobImpl {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(HandleTunnelCreateMessageJob.class);
|
||||
_request = msg;
|
||||
_alreadySearched = false;
|
||||
}
|
||||
|
||||
private static final int STATUS_DEFERRED = 10000;
|
||||
|
||||
public String getName() { return "Handle tunnel join request"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("handle join request: " + _request);
|
||||
int status = shouldAccept();
|
||||
if (status > 0) {
|
||||
if (status == STATUS_DEFERRED) {
|
||||
return;
|
||||
} else if (status > 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("reject(" + status + ") join request: " + _request);
|
||||
sendRejection(status);
|
||||
@ -64,7 +72,34 @@ public class HandleTunnelCreateMessageJob extends JobImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private int shouldAccept() { return getContext().throttle().acceptTunnelRequest(_request); }
|
||||
private int shouldAccept() {
|
||||
Hash nextRouter = _request.getNextRouter();
|
||||
if (nextRouter != null) {
|
||||
RouterInfo ri = getContext().netDb().lookupRouterInfoLocally(nextRouter);
|
||||
if (ri == null) {
|
||||
if (_alreadySearched) // only search once
|
||||
return TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
|
||||
getContext().netDb().lookupRouterInfo(nextRouter, new DeferredAccept(getContext(), true), new DeferredAccept(getContext(), false), 5*1000);
|
||||
_alreadySearched = true;
|
||||
return STATUS_DEFERRED;
|
||||
}
|
||||
}
|
||||
return getContext().throttle().acceptTunnelRequest(_request);
|
||||
}
|
||||
|
||||
private class DeferredAccept extends JobImpl {
|
||||
private boolean _shouldAccept;
|
||||
public DeferredAccept(RouterContext ctx, boolean shouldAccept) {
|
||||
super(ctx);
|
||||
_shouldAccept = shouldAccept;
|
||||
}
|
||||
public void runJob() {
|
||||
HandleTunnelCreateMessageJob.this.runJob();
|
||||
}
|
||||
private static final String NAME_OK = "Deferred tunnel accept";
|
||||
private static final String NAME_REJECT = "Deferred tunnel reject";
|
||||
public String getName() { return _shouldAccept ? NAME_OK : NAME_REJECT; }
|
||||
}
|
||||
|
||||
private void accept() {
|
||||
byte recvId[] = new byte[4];
|
||||
|
@ -163,7 +163,15 @@ public class TunnelPool {
|
||||
* when selecting tunnels, stick with the same one for a brief
|
||||
* period to allow batching if we can.
|
||||
*/
|
||||
private static final long SELECTION_PERIOD = 500;
|
||||
private long curPeriod() {
|
||||
long period = _context.clock().now();
|
||||
long ms = period % 1000;
|
||||
if (ms > 500)
|
||||
period = period - ms + 500;
|
||||
else
|
||||
period = period - ms;
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a random tunnel out of the pool. If there are none available but
|
||||
@ -173,8 +181,7 @@ public class TunnelPool {
|
||||
*/
|
||||
public TunnelInfo selectTunnel() { return selectTunnel(true); }
|
||||
private TunnelInfo selectTunnel(boolean allowRecurseOnFail) {
|
||||
long period = _context.clock().now();
|
||||
period -= period % SELECTION_PERIOD;
|
||||
long period = curPeriod();
|
||||
synchronized (_tunnels) {
|
||||
if (_lastSelectionPeriod == period) {
|
||||
if ( (_lastSelected != null) &&
|
||||
|
Reference in New Issue
Block a user