Fix parameter decoding for scrape also

Add caching for info hashes and peer ids; move from ByteArray to SDS
Stop cleaner when plugin stops
Move to the ClientApp interface, remove all static refs
Attempt to fix crash after update
This commit is contained in:
zzz
2014-11-12 14:48:51 +00:00
parent 8a9094cd8e
commit a7225a2379
10 changed files with 302 additions and 77 deletions

View File

@ -1,3 +1,11 @@
0.12.0
2014-xx-xx
Fix parameter decoding for scrape also
Add caching for info hashes and peer ids
Stop cleaner when plugin stops
Move to the ClientApp interface, remove all static refs
Attempt to fix crash after update
0.11.0
2014-11-11
Critical fix for announce parameter decoding, triggered by recent Jetty versions

View File

@ -16,22 +16,20 @@ package net.i2p.zzzot;
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
import net.i2p.data.SimpleDataStructure;
/**
* A 20-byte SHA1 info hash
*/
public class InfoHash extends ByteArray {
public class InfoHash extends SimpleDataStructure {
public InfoHash(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public static final int LENGTH = 20;
public InfoHash(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad infohash length: " + data.length);
}
public int length() {
return LENGTH;
}
}

View File

@ -16,22 +16,20 @@ package net.i2p.zzzot;
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
import net.i2p.data.SimpleDataStructure;
/**
* A 20-byte peer ID
*/
public class PID extends ByteArray {
public class PID extends SimpleDataStructure {
public PID(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public static final int LENGTH = 20;
public PID(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + data.length);
}
public int length() {
return LENGTH;
}
}

View File

@ -18,13 +18,26 @@ package net.i2p.zzzot;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.CoreVersion;
import net.i2p.data.DataHelper;
import net.i2p.data.SDSCache;
import net.i2p.util.VersionComparator;
/**
* All the torrents
*/
public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
private static final int CACHE_SIZE = 2048;
private final SDSCache<InfoHash> _hashCache;
private final SDSCache<PID> _pidCache;
public Torrents() {
super();
_hashCache = new SDSCache<InfoHash>(InfoHash.class, InfoHash.LENGTH, CACHE_SIZE);
_pidCache = new SDSCache<PID>(PID.class, PID.LENGTH, CACHE_SIZE);
}
public int countPeers() {
@ -34,4 +47,52 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
}
return rv;
}
/**
* Pull from cache or return new
*
* @throws IllegalArgumentException if data is not the correct number of bytes
* @since 0.12.0
*/
public InfoHash createInfoHash(String data) throws IllegalArgumentException {
byte[] d = DataHelper.getASCII(data);
if (d.length != InfoHash.LENGTH)
throw new IllegalArgumentException("bad infohash length " + d.length);
return _hashCache.get(d);
}
/**
* Pull from cache or return new
*
* @throws IllegalArgumentException if data is not the correct number of bytes
* @since 0.12.0
*/
public PID createPID(String data) throws IllegalArgumentException {
byte[] d = DataHelper.getASCII(data);
if (d.length != PID.LENGTH)
throw new IllegalArgumentException("bad peer id length " + d.length);
return _pidCache.get(d);
}
/**
* @since 0.12.0
*/
@Override
public void clear() {
super.clear();
clearCaches();
}
/**
* @since 0.12.0
*/
private void clearCaches() {
// not available until 0.9.17
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") >= 0) {
try {
_hashCache.clear();
_pidCache.clear();
} catch (Throwable t) {}
}
}
}

View File

@ -18,33 +18,44 @@ package net.i2p.zzzot;
import java.util.Iterator;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
import net.i2p.I2PAppContext;
import net.i2p.util.SimpleTimer2;
/**
* Instantiate this to fire it up
*/
class ZzzOT {
private Torrents _torrents;
private final Torrents _torrents;
private final Cleaner _cleaner;
private static final long CLEAN_TIME = 4*60*1000;
private static final long EXPIRE_TIME = 60*60*1000;
ZzzOT() {
ZzzOT(I2PAppContext ctx) {
_torrents = new Torrents();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
_cleaner = new Cleaner(ctx);
}
Torrents getTorrents() {
return _torrents;
}
void stop() {
_torrents.clear();
// no way to stop the cleaner
void start() {
_cleaner.forceReschedule(CLEAN_TIME);
}
private class Cleaner implements SimpleTimer.TimedEvent {
void stop() {
_cleaner.cancel();
_torrents.clear();
}
private class Cleaner extends SimpleTimer2.TimedEvent {
/** must schedule later */
public Cleaner(I2PAppContext ctx) {
super(ctx.simpleTimer2());
}
public void timeReached() {
long now = System.currentTimeMillis();
@ -61,6 +72,7 @@ class ZzzOT {
if (recent <= 0)
iter.remove();
}
schedule(CLEAN_TIME);
}
}
}

View File

@ -22,18 +22,25 @@ import java.io.IOException;
import java.util.List;
import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.util.VersionComparator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
@ -47,39 +54,64 @@ import org.eclipse.jetty.xml.XmlConfiguration;
*
* @author zzz
*/
public class ZzzOTController {
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ZzzOTController.class);
private static Server _server;
private static TunnelController _tunnel;
private static ZzzOT _zzzot;
private static Object _lock = new Object();
public class ZzzOTController implements ClientApp {
private final I2PAppContext _context;
private final Log _log;
private final String[] _args;
private final ClientAppManager _mgr;
private Server _server;
private TunnelController _tunnel;
private final ZzzOT _zzzot;
/** only for main() */
private static volatile ZzzOTController _controller;
private ClientAppState _state = UNINITIALIZED;
private static final String NAME = "ZzzOT";
private static final String BACKUP_SUFFIX = ".jetty6";
private static final String[] xmlFiles = {
"jetty.xml", "contexts/base-context.xml", "contexts/cgi-context.xml",
"etc/realm.properties", "etc/webdefault.xml" };
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
if ("start".equals(args[2]))
start(args);
else if ("stop".equals(args[2]))
stop();
else
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
/**
* @since 0.12.0
*/
public ZzzOTController(I2PAppContext ctx, ClientAppManager mgr, String args[]) {
_context = ctx;
_log = ctx.logManager().getLog(ZzzOTController.class);
_mgr = mgr;
_args = args;
_zzzot = new ZzzOT(ctx);
_state = INITIALIZED;
}
/**
* No longer supported, as we now need the ClientAppManager for the webapp to find us
*/
public synchronized static void main(String args[]) {
throw new UnsupportedOperationException("Must use ClientApp interface");
}
/**
* @return null if not running
*/
public static Torrents getTorrents() {
synchronized(_lock) {
if (_zzzot == null)
_zzzot = new ZzzOT();
}
return _zzzot.getTorrents();
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
if (mgr == null)
return null;
ClientApp z = mgr.getRegisteredApp(NAME);
if (z == null)
return null;
ZzzOTController ctrlr = (ZzzOTController) z;
return ctrlr._zzzot.getTorrents();
}
private static void start(String args[]) {
File pluginDir = new File(args[1]);
/**
* @param args ignored
*/
private void start(String args[]) {
//File pluginDir = new File(args[1]);
File pluginDir = new File(_context.getAppDir(), "plugins/zzzot");
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
@ -105,11 +137,12 @@ public class ZzzOTController {
}
startJetty(pluginDir, dest);
startI2PTunnel(pluginDir, dest);
_zzzot.start();
// SeedlessAnnouncer.announce(_tunnel);
}
private static void startI2PTunnel(File pluginDir, Destination dest) {
private void startI2PTunnel(File pluginDir, Destination dest) {
File i2ptunnelConfig = new File(pluginDir, "i2ptunnel.config");
Properties i2ptunnelProps = new Properties();
try {
@ -131,15 +164,15 @@ public class ZzzOTController {
_tunnel = tun;
}
private static void startJetty(File pluginDir, Destination dest) {
private void startJetty(File pluginDir, Destination dest) {
if (_server != null)
throw new IllegalArgumentException("Jetty already running!");
migrateJettyXML(pluginDir);
I2PAppContext context = I2PAppContext.getGlobalContext();
File tmpdir = new File(context.getTempDir().getAbsolutePath(), "/zzzot-work");
File tmpdir = new File(_context.getTempDir().getAbsolutePath(), "/zzzot-work");
tmpdir.mkdir();
File jettyXml = new File(pluginDir, "jetty.xml");
try {
Resource.setDefaultUseCaches(false);
XmlConfiguration xmlc = new XmlConfiguration(jettyXml.toURI().toURL());
Server serv = (Server) xmlc.configure();
//HttpContext[] hcs = serv.getContexts();
@ -155,14 +188,13 @@ public class ZzzOTController {
launchHelp(pluginDir, dest);
}
private static void stop() {
private void stop() {
stopI2PTunnel();
stopJetty();
if (_zzzot != null)
_zzzot.stop();
_zzzot.stop();
}
private static void stopI2PTunnel() {
private void stopI2PTunnel() {
if (_tunnel == null)
return;
try {
@ -174,7 +206,7 @@ public class ZzzOTController {
_tunnel = null;
}
private static void stopJetty() {
private void stopJetty() {
if (_server == null)
return;
try {
@ -190,7 +222,7 @@ public class ZzzOTController {
* Migate the jetty configuration files.
* Save old jetty.xml if moving from jetty 5 to jetty 6
*/
private static void migrateJettyXML(File pluginDir) {
private void migrateJettyXML(File pluginDir) {
// contexts dir does not exist in Jetty 5
File file = new File(pluginDir, "contexts");
file.mkdir();
@ -220,7 +252,7 @@ public class ZzzOTController {
* @return success
* @since Jetty 7
*/
private static boolean backupAndMigrateFile(File toDir, String filename) {
private boolean backupAndMigrateFile(File toDir, String filename) {
File to = new File(toDir, filename);
boolean rv = backupFile(to);
boolean rv2 = migrateJettyFile(toDir, filename);
@ -249,7 +281,7 @@ public class ZzzOTController {
/**
* Migate a single jetty config file, replacing $PLUGIN as we copy it.
*/
private static boolean migrateJettyFile(File pluginDir, String name) {
private boolean migrateJettyFile(File pluginDir, String name) {
File templateDir = new File(pluginDir, "templates");
File fileTmpl = new File(templateDir, name);
File outFile = new File(pluginDir, name);
@ -271,7 +303,7 @@ public class ZzzOTController {
}
/** put the directory, base32, and base64 info in the help.html file and launch a browser window to display it */
private static void launchHelp(File pluginDir, Destination dest) {
private void launchHelp(File pluginDir, Destination dest) {
File fileTmpl = new File(pluginDir, "templates/help.html");
File outFile = new File(pluginDir, "eepsite/docroot/help.html");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
@ -298,4 +330,82 @@ public class ZzzOTController {
UrlLauncher.main(new String[] { "http://127.0.0.1:7662/help.html" } );
}
}
/////// ClientApp methods
/** @since 0.12.0 */
public synchronized void startup() {
if (_mgr != null) {
// this is really ugly, but thru 0.9.16,
// stopping a ClientApp plugin with $PLUGIN in the args fails,
// and it tries to start a second one instead.
// Find the first one and stop it.
ClientApp z = _mgr.getRegisteredApp(NAME);
if (z != null) {
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") < 0) {
ZzzOTController ctrlr = (ZzzOTController) z;
ctrlr.shutdown(null);
} else {
_log.error("ZzzOT already running");
}
changeState(START_FAILED);
return;
}
}
if (_state != STOPPED && _state != INITIALIZED && _state != START_FAILED) {
_log.error("Start while state = " + _state);
return;
}
changeState(STARTING);
try {
start(_args);
changeState(RUNNING);
if (_mgr != null)
_mgr.register(this);
} catch (Exception e) {
changeState(START_FAILED, "Start failed", e);
}
}
/** @since 0.12.0 */
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
return;
changeState(STOPPING);
if (_mgr != null)
_mgr.unregister(this);
stop();
changeState(STOPPED);
}
/** @since 0.12.0 */
public ClientAppState getState() {
return _state;
}
/** @since 0.12.0 */
public String getName() {
return NAME;
}
/** @since 0.12.0 */
public String getDisplayName() {
return NAME;
}
/////// end ClientApp methods
/** @since 0.12.0 */
private synchronized void changeState(ClientAppState state) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, null, null);
}
/** @since 0.12.0 */
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, msg, e);
}
}

View File

@ -73,6 +73,11 @@
msg = "no info hash";
}
if (info_hash.length() != 20 && !fail) {
fail = true;
msg = "bad info hash length " + info_hash.length();
}
if (ip == null && !fail) {
fail = true;
msg = "no ip (dest)";
@ -83,11 +88,22 @@
msg = "no peer id";
}
if (peer_id.length() != 20 && !fail) {
fail = true;
msg = "bad peer id length " + peer_id.length();
}
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null && !fail) {
fail = true;
msg = "tracker is down";
}
InfoHash ih = null;
if (!fail) {
try {
ih = new InfoHash(info_hash);
} catch (Exception e) {
ih = torrents.createInfoHash(info_hash);
} catch (IllegalArgumentException e) {
fail = true;
msg = "bad infohash " + e;
}
@ -111,8 +127,8 @@
PID pid = null;
if (!fail) {
try {
pid = new PID(peer_id);
} catch (Exception e) {
pid = torrents.createPID(peer_id);
} catch (IllegalArgumentException e) {
fail = true;
msg = "bad peer id " + e;
}
@ -162,8 +178,7 @@
} catch (NumberFormatException nfe) {};
}
Torrents torrents = ZzzOTController.getTorrents();
Map<String, Object> m = new HashMap();
Map<String, Object> m = new HashMap(8);
if (fail) {
m.put("failure reason", msg);
} else if ("stopped".equals(event)) {

View File

@ -1,4 +1,4 @@
<%@page import="net.i2p.zzzot.ZzzOTController" %>
<%@page import="net.i2p.zzzot.ZzzOTController,net.i2p.zzzot.Torrents" %>
<html>
<head>
<title>ZzzOT</title>
@ -6,9 +6,20 @@
<p>
zzzot
<p>
<%
Torrents torrents = ZzzOTController.getTorrents();
if (torrents != null) {
%>
<table cellspacing="8">
<tr><td>Torrents:<td align="right"><%=ZzzOTController.getTorrents().size()%>
<tr><td>Peers:<td align="right"><%=ZzzOTController.getTorrents().countPeers()%>
<tr><td>Torrents:<td align="right"><%=torrents.size()%>
<tr><td>Peers:<td align="right"><%=torrents.countPeers()%>
</table>
<%
} else {
%>
ZzzOT is not running
<%
}
%>
</body>
</html>

View File

@ -27,6 +27,10 @@
*/
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
// above doesn't work for the query string
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
@ -46,20 +50,24 @@
boolean all = info_hash == null;
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null && !fail) {
fail = true;
msg = "tracker is down";
}
InfoHash ih = null;
if ((!all) && !fail) {
try {
ih = new InfoHash(info_hash);
ih = torrents.createInfoHash(info_hash);
} catch (Exception e) {
fail = true;
msg = "bad infohash " + e;
}
}
Torrents torrents = ZzzOTController.getTorrents();
// build 3-level dictionary
Map<String, Object> m = new HashMap();
Map<String, Object> m = new HashMap(4);
if (fail) {
m.put("failure reason", msg);
} else {

View File

@ -62,6 +62,10 @@
} else if (req.startsWith("locate dG9ycmVud")) { // locate b64(torrent)
// all the peers
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null) {
response.setStatus(503, "Down");
return;
}
for (InfoHash ihash : torrents.keySet()) {
Peers peers = torrents.get(ihash);
if (peers == null)