/** * */ package freenet.node; import java.io.UnsupportedEncodingException; import java.net.UnknownHostException; import freenet.client.InsertContext; import freenet.client.InsertException; import freenet.client.async.BaseClientPutter; import freenet.client.async.ClientContext; import freenet.client.async.ClientPutCallback; import freenet.client.async.ClientPutter; import freenet.client.async.PersistenceDisabledException; import freenet.io.comm.Peer; import freenet.io.comm.PeerParseException; import freenet.keys.FreenetURI; import freenet.keys.InsertableClientSSK; import freenet.support.Logger; import freenet.support.SimpleFieldSet; import freenet.support.SimpleReadOnlyArrayBucket; import freenet.support.Logger.LogLevel; import freenet.support.api.Bucket; import freenet.support.api.RandomAccessBucket; public class NodeARKInserter implements ClientPutCallback, RequestClient { /** * */ private final Node node; private final NodeCrypto crypto; private final String darknetOpennetString; private final NodeIPPortDetector detector; private static boolean logMINOR; private final boolean enabled; /** * @param node * @param old If true, use the old ARK rather than the new ARK */ NodeARKInserter(Node node, NodeCrypto crypto, NodeIPPortDetector detector, boolean enableARKs) { this.node = node; this.crypto = crypto; this.detector = detector; logMINOR = Logger.shouldLog(LogLevel.MINOR, this); if(crypto.isOpennet) darknetOpennetString = "Opennet"; else darknetOpennetString = "Darknet"; this.enabled = enableARKs; } private ClientPutter inserter; private boolean shouldInsert; private Peer[] lastInsertedPeers; private boolean canStart; void start() { if(!enabled) return; canStart = true; innerUpdate(); } public void update() { // Called by detector code, which is critical and convoluted. // Run off-thread, break locks, avoid stalling caller. node.executor.execute(new Runnable() { @Override public void run() { innerUpdate(); } }); } private void innerUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); if(logMINOR) Logger.minor(this, "update()"); if(!checkIPUpdated()) return; // We'll broadcast the new physical.udp entry to our connected peers via a differential node reference // We'll err on the side of caution and not update our peer to an empty physical.udp entry using a differential node reference SimpleFieldSet nfs = crypto.exportPublicFieldSet(false, false, true); String[] entries = nfs.getAll("physical.udp"); if(entries != null) { SimpleFieldSet fs = new SimpleFieldSet(true); fs.putOverwrite("physical.udp", entries); if(logMINOR) Logger.minor(this, darknetOpennetString + " ref's physical.udp is '" + fs.toString() + "'"); node.peers.locallyBroadcastDiffNodeRef(fs, !crypto.isOpennet, crypto.isOpennet); } else { if(logMINOR) Logger.minor(this, darknetOpennetString + " ref's physical.udp is null"); } // Proceed with inserting the ARK if(logMINOR) Logger.minor(this, "Inserting " + darknetOpennetString + " ARK because peers list changed"); if(inserter != null) { // Already inserting. // Re-insert after finished. synchronized(this) { shouldInsert = true; } return; } // Otherwise need to start an insert if(node.noConnectedPeers()) { // Can't start an insert yet synchronized (this) { shouldInsert = true; } return; } startInserter(); } private boolean checkIPUpdated() { Peer[] p = detector.detectPrimaryPeers(); if(p == null) { if(logMINOR) Logger.minor(this, "Not inserting " + darknetOpennetString + " ARK because no IP address"); return false; // no point inserting } synchronized (this) { if(lastInsertedPeers != null) { if(p.length != lastInsertedPeers.length) return true; for(int i=0;i<p.length;i++) if(!p[i].strictEquals(lastInsertedPeers[i])) return true; } else { // we've not inserted an ARK that we know about (ie since startup) return true; } } return false; } private void startInserter() { if(!canStart) { if(logMINOR) Logger.minor(this, darknetOpennetString + " ARK inserter can't start yet"); return; } if(logMINOR) Logger.minor(this, "starting " + darknetOpennetString + " ARK inserter"); SimpleFieldSet fs = crypto.exportPublicFieldSet(false, false, true); // Remove some unnecessary fields that only cause collisions. // Delete entire ark.* field for now. Changing this and automatically moving to the new may be supported in future. fs.removeSubset("ark"); fs.removeValue("location"); fs.removeValue("sig"); //fs.remove("version"); - keep version because of its significance in reconnection String s = fs.toString(); byte[] buf; try { buf = s.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e); } RandomAccessBucket b = new SimpleReadOnlyArrayBucket(buf); long number = crypto.myARKNumber; InsertableClientSSK ark = crypto.myARK; FreenetURI uri = ark.getInsertURI().setKeyType("USK").setSuggestedEdition(number); if(logMINOR) Logger.minor(this, "Inserting " + darknetOpennetString + " ARK: " + uri + " contents:\n" + s); InsertContext ctx = node.clientCore.makeClient((short)0, true, false).getInsertContext(true); inserter = new ClientPutter(this, b, uri, null, // Modern ARKs easily fit inside 1KB so should be pure SSKs => no MIME type; this improves fetchability considerably ctx, RequestStarter.INTERACTIVE_PRIORITY_CLASS, false, null, false, node.clientCore.clientContext, null, -1); try { node.clientCore.clientContext.start(inserter); synchronized (this) { if(fs.get("physical.udp") == null) lastInsertedPeers = null; else { try { String[] all = fs.getAll("physical.udp"); Peer[] peers = new Peer[all.length]; for(int i=0;i<all.length;i++) peers[i] = new Peer(all[i], false); lastInsertedPeers = peers; } catch (PeerParseException e1) { Logger.error(this, "Error parsing own " + darknetOpennetString + " ref: "+e1+" : "+fs.get("physical.udp"), e1); } catch (UnknownHostException e1) { Logger.error(this, "Error parsing own " + darknetOpennetString + " ref: "+e1+" : "+fs.get("physical.udp"), e1); } } } } catch (InsertException e) { onFailure(e, inserter); } catch (PersistenceDisabledException e) { // Impossible } } @Override public void onSuccess(BaseClientPutter state) { FreenetURI uri = state.getURI(); if(logMINOR) Logger.minor(this, darknetOpennetString + " ARK insert succeeded: " + uri); synchronized (this) { inserter = null; if(!shouldInsert) return; shouldInsert = false; } startInserter(); } @Override public void onFailure(InsertException e, BaseClientPutter state) { if(logMINOR) Logger.minor(this, darknetOpennetString + " ARK insert failed: "+e); synchronized(this) { lastInsertedPeers = null; } // :( // Better try again try { Thread.sleep(5000); } catch (InterruptedException e1) { // Ignore } startInserter(); } @Override public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) { if(logMINOR) Logger.minor(this, "Generated URI for " + darknetOpennetString + " ARK: "+uri); long l = uri.getSuggestedEdition(); if(l < crypto.myARKNumber) { Logger.error(this, "Inserted " + darknetOpennetString + " ARK edition # lower than attempted: "+l+" expected "+crypto.myARKNumber); } else if(l > crypto.myARKNumber) { if(logMINOR) Logger.minor(this, darknetOpennetString + " ARK number moving from "+crypto.myARKNumber+" to "+l); crypto.myARKNumber = l; if(crypto.isOpennet) node.writeOpennetFile(); else node.writeNodeFile(); // We'll broadcast the new ARK edition to our connected peers via a differential node reference SimpleFieldSet fs = new SimpleFieldSet(true); fs.put("ark.number", crypto.myARKNumber); node.peers.locallyBroadcastDiffNodeRef(fs, !crypto.isOpennet, crypto.isOpennet); } } public void onConnectedPeer() { if(!checkIPUpdated()) return; synchronized (this) { if(!shouldInsert) return; } // Already inserting. if(inserter != null) return; synchronized (this) { shouldInsert = false; } startInserter(); } @Override public void onFetchable(BaseClientPutter state) { // Ignore, we don't care } @Override public boolean persistent() { return false; } @Override public boolean realTimeFlag() { return false; } @Override public void onGeneratedMetadata(Bucket metadata, BaseClientPutter state) { Logger.error(this, "Bogus onGeneratedMetadata() on "+this+" from "+state, new Exception("error")); metadata.free(); } @Override public void onResume(ClientContext context) { // Not persistent. } @Override public RequestClient getRequestClient() { return this; } }