package plugins.CENO.Bridge.Signaling; import java.io.IOException; import java.net.MalformedURLException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PrivateKey; import java.util.concurrent.TimeUnit; import plugins.CENO.CENOErrCode; import plugins.CENO.CENOException; import plugins.CENO.Bridge.CENOBridge; import plugins.CENO.Common.Crypto; import plugins.CENO.Common.GetSyncCallback; import freenet.client.FetchException; import freenet.client.InsertException; import freenet.client.FetchException.FetchExceptionMode; import freenet.keys.FreenetURI; import freenet.support.Logger; public class PollingPuzzle { static final int MAX_KSK_SLOTS = 20; private Puzzle puzzle; private String bridgeInsertURI; private KeyPair asymKeyPair; private int consumedSlots = 0; private ChannelMakerListener[] channelListeners = new ChannelMakerListener[MAX_KSK_SLOTS]; public PollingPuzzle( String bridgeInsertURI, KeyPair keyPair) { this.bridgeInsertURI = bridgeInsertURI; this.asymKeyPair = keyPair; this.puzzle = new Puzzle(); } public void startPolling() throws IOException, InsertException, CENOException, GeneralSecurityException { ChannelMakerAnnouncer channelMakerAnnouncer = new ChannelMakerAnnouncer(bridgeInsertURI, Crypto.savePublicKey(asymKeyPair.getPublic()), puzzle); channelMakerAnnouncer.doAnnounce(); startSlotListeners(puzzle.getAnswer()); } private void startSlotListeners(String puzzleAnswer) throws CENOException { try { for (int i = 0; i < MAX_KSK_SLOTS; i++) { channelListeners[i] = new ChannelMakerListener(puzzleAnswer, i, asymKeyPair.getPrivate()); Thread listenerThread = new Thread(channelListeners[i]); listenerThread.setName("ChannelListener-" + i); listenerThread.start(); } } catch (MalformedURLException e) { throw new CENOException(CENOErrCode.RR, "Could not start Channel Maker Listener thread."); } } public void stopListeners() { for (ChannelMakerListener channelLister : channelListeners) { channelLister.stopListener(); } } private void shouldOfferNewPuzzle() { if (consumedSlots > MAX_KSK_SLOTS * 0.5) { try { ChannelMaker.getInstance().publishNewPuzzle(); } catch (IOException e) { Logger.error(this, "Could not start channel listener for the given insertURI: " + e.getMessage()); return; } catch (GeneralSecurityException e) { Logger.error(this, "The given public RSA key is invalid"); return; } catch (CENOException e) { Logger.error(this, "Could not start decentralized signaling channel maker: " + e.getMessage()); return; } } } private class ChannelMakerListener implements Runnable { final long KSK_POLLING_PAUSE = TimeUnit.MINUTES.toMillis(5); private String puzzleAnswer; private FreenetURI channelMakingKSK; private PrivateKey asymPrivKey; private volatile boolean continueLoop; public ChannelMakerListener(String puzzleAnswer, int subKsk, PrivateKey asymPrivKey) throws MalformedURLException { this.puzzleAnswer = puzzleAnswer; this.asymPrivKey = asymPrivKey; channelMakingKSK = new FreenetURI("KSK@" + this.puzzleAnswer + "-" + subKsk); continueLoop = true; } @Override public void run() { try { int window = 0; while(continueLoop) { byte[] kskContent = null; GetSyncCallback getSyncCallback = new GetSyncCallback(CENOBridge.nodeInterface.getRequestClient()); try { CENOBridge.nodeInterface.distFetchURI(channelMakingKSK, getSyncCallback); kskContent = getSyncCallback.getResult(10 * KSK_POLLING_PAUSE, TimeUnit.MILLISECONDS); } catch (FetchException e) { // TODO Fine-grain log messages according to FetchException codes if(e.mode == FetchExceptionMode.RECENTLY_FAILED) { window++; } Logger.normal(ChannelMakerListener.class, "Exception while fetching KSK clients use for making channels: " + e.getShortMessage()); } if (kskContent != null) { if (window > 0) { window--; } Logger.minor(this, "Congestion window for fetching KSKs without getting Recently Failed exceptions set to: " + window + " minutes"); String decKskContent = null; try { decKskContent = new String(Crypto.decrypt(kskContent, asymPrivKey), "UTF-8"); } catch (GeneralSecurityException e) { Logger.error(this, "General Security Exception while decrypting KSK reply from client"); continue; } Logger.normal(ChannelMakerListener.class, "A client has posted information for establishing a signaling channel"); ChannelManager.getInstance().addChannel(decKskContent); consumedSlots++; shouldOfferNewPuzzle(); continueLoop = false; } // Pause the looping thread Thread.sleep(KSK_POLLING_PAUSE + TimeUnit.MINUTES.toMillis(window)); } } catch (InterruptedException e) { continueLoop = false; } catch (IOException e) { Logger.warning(this, "IOException while trying to create channel from KSK response"); continueLoop = false; } } public void stopListener() { continueLoop = false; } } class Puzzle { private String question; private String answer; public Puzzle() { this.question = Long.toHexString(Double.doubleToLongBits(Math.random())); this.answer = this.question; } public String getQuestion() { return question; } public String getAnswer() { return answer; } } }