gt: 				int[] subKSKUsedExt = new int[prevSubKSK.length + 1];
context: package plugins.CENO.Client.Signaling;
  
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.net.MalformedURLException;
  import java.security.GeneralSecurityException;
  import java.util.concurrent.TimeUnit;
  
  import plugins.CENO.Client.CENOClient;
  import plugins.CENO.Common.Crypto;
  import freenet.client.FetchException;
  import freenet.client.FetchException.FetchExceptionMode;
  import freenet.client.FetchResult;
  import freenet.client.InsertException;
  import freenet.client.InsertException.InsertExceptionMode;
  import freenet.client.async.BaseClientPutter;
  import freenet.client.async.ClientContext;
  import freenet.client.async.ClientPutCallback;
  import freenet.client.async.PersistenceDisabledException;
  import freenet.keys.FreenetURI;
  import freenet.node.FSParseException;
  import freenet.node.RequestClient;
  import freenet.support.IllegalBase64Exception;
  import freenet.support.Logger;
  import freenet.support.SimpleFieldSet;
  import freenet.support.api.Bucket;
  import freenet.support.io.ResumeFailedException;
  
  public class ChannelMaker implements Runnable {
  	private static final int MAX_KSK_POLLS = 20;
  
  	private FreenetURI signalSSK;
  	private FreenetURI signalSSKpub;
  	private boolean channelEstablished = false;
  	private ChannelStatus channelStatus = ChannelStatus.starting;
  	private long lastSynced = 0L;
  
  	public ChannelMaker() {
  		this(null, 0L);
  	}
  
  	public ChannelMaker(String signalSSKString, long lastSynced) {
  		this.lastSynced = lastSynced;
  		try {
  			if (signalSSKString != null) {
  				this.signalSSK = new FreenetURI(signalSSKString);
  				this.signalSSKpub = this.signalSSK.deriveRequestURIFromInsertURI();
  				this.lastSynced = lastSynced;
  				channelStatus = ChannelStatus.publishedKSK;
  				Logger.warning(this, "Retrieved previously established channel from client.properties");
  				if (System.currentTimeMillis() - lastSynced < TimeUnit.DAYS.toMillis(15)) {
  					channelStatus = ChannelStatus.waitingForSyn;
  					Logger.warning(this, "The established channel was synced less than 15 days ago and will start using without re-establishment");
  				}
  				return;
  			}
  		} catch (MalformedURLException e) {
  			Logger.error(this, "signalSSK read from configuration is not a valid Freenet key");
  		}
  		FreenetURI newKeyPair[] = CENOClient.nodeInterface.generateKeyPair();
  		this.signalSSK = newKeyPair[0];
  		this.signalSSKpub = newKeyPair[1];
  		this.channelStatus = ChannelStatus.starting;
  		Logger.warning(this, "Generated keys for establishing a secure channel");
  	}
  
  	@Override
  	public void run() {
  		if (!checkChannelEstablished()) {
  			if(ChannelStatus.isFatalStatus(channelStatus)) {
  				return;
  			}
  			establishChannel();
  		} else if (ChannelStatus.sentPrivUSK(channelStatus)) {
  			waitForSyn();
  		}
  	}
  
  	public String getSignalSSK() {
  		return signalSSK.toString();
  	}
  
  	public long getLastSynced() {
  		return lastSynced;
  	}
  
  	public boolean isFatal() {
  		return ChannelStatus.isFatalStatus(channelStatus);
  	}
  
  	public boolean canSend() {
  		return ChannelStatus.canSend(channelStatus);
  	}
  
  	public boolean sentPrivUSK() {
  		return ChannelStatus.sentPrivUSK(channelStatus);
  	}
  
  	private boolean checkChannelEstablished() {
  		if (System.currentTimeMillis() - lastSynced  > TimeUnit.DAYS.toMillis(30)) {
  			return false;
  		}
  
  		if(ChannelStatus.sentPrivUSK(channelStatus)) {
  			return true;
  		}
  
  		return channelEstablished;
  	}
  
  	private boolean waitForSyn() {
  		FreenetURI synURI = new FreenetURI("USK", "syn", signalSSKpub.getRoutingKey(), signalSSKpub.getCryptoKey(), signalSSKpub.getExtra());
  		FetchResult fetchResult = null;
  		while(!channelEstablished) {
  			try {
  				fetchResult = CENOClient.nodeInterface.fetchURI(synURI);
  			} catch (FetchException e) {
  				if(e.getMode() == FetchExceptionMode.PERMANENT_REDIRECT) {
  					synURI = e.newURI;
  				} else if(e.isDNF() || e.isFatal()) {
  					try {
  						Thread.sleep(TimeUnit.MINUTES.toMillis(5));
  					} catch (InterruptedException e1) {}
  					continue;
  				}
  			}
  			if(fetchResult != null) {
  				try {
  					Long synDate = Long.parseLong(new String(fetchResult.asByteArray()));
  					if (System.currentTimeMillis() - synDate > TimeUnit.DAYS.toMillis(25)) {
  						establishChannel();
  						break;
  					}
  				} catch (IOException e) {
  					channelStatus = ChannelStatus.failedToParseSyn;
  					break;
  				}
  				Logger.normal(this, "Received syn from the bridge - signaling channel established successfully");
  				channelStatus = ChannelStatus.syn;
  				lastSynced = System.currentTimeMillis();
  				channelEstablished = true;
  			}
  		}
  		return channelEstablished;
  	}
  
  	private void establishChannel() {
  		FreenetURI bridgeKey;
  		try {
  			bridgeKey = new FreenetURI(CENOClient.getBridgeKey());
  		} catch (MalformedURLException e1) {
  			Logger.error(this, "Could not calculate the bridge key from the configured SSK: " + e1.getMessage());
  			channelStatus = ChannelStatus.fatal;
  			return;
  		}
  		FreenetURI bridgeSignalerURI = new FreenetURI("USK", "CENO-signaler", bridgeKey.getRoutingKey(), bridgeKey.getCryptoKey(), bridgeKey.getExtra());
  		FetchResult bridgeSignalFreesite = null;
  		while(bridgeSignalFreesite == null) {
  			try {
  				bridgeSignalFreesite = CENOClient.nodeInterface.fetchURI(bridgeSignalerURI);
  			} catch (FetchException e) {
  				if (e.mode == FetchException.FetchExceptionMode.PERMANENT_REDIRECT) {
  					bridgeSignalerURI = e.newURI;
  					continue;
  				}
  				if (e.isFatal()) {
  					channelStatus = ChannelStatus.failedToGetSignalSSK;
  					return;
  				}
  				Logger.warning(this, "Exception while retrieving the bridge's signal page: " + e.getMessage());
  			}
  		}
  		channelStatus = ChannelStatus.gotSignalSSK;
  		SimpleFieldSet sfs = null;
  		String question = null;
  		String pubKey = null;
  		try {
  			sfs = new SimpleFieldSet(new String(bridgeSignalFreesite.asByteArray()), false, true, true);
  			question = sfs.getString("question");
  			pubKey = sfs.getString("pubkey");
  		} catch (IOException e) {
  			Logger.error(this, "IOException while reading the CENO-signaler page");
  		} catch (FSParseException e) {
  			Logger.error(this, "Exception while parsing the SFS of the CENO-signaler");
  		} finally {
  			if (question == null || pubKey == null) {
  				// CENO Client won't be able to signal the bridge
  				//TODO Terminate plugin
  				channelStatus = ChannelStatus.failedToParseSignalSFS;
  				return;
  			}
  		}
  		channelStatus = ChannelStatus.puzzleSolved;
  		byte[] encReply = null;
  		try {
  			encReply = Crypto.encrypt(signalSSK.toString().getBytes("UTF-8"), pubKey);
  		} catch (GeneralSecurityException e1) {
  			Logger.error(this, "General security exception while encrypting response to quiz: " + e1.getMessage());
  		} catch (IllegalBase64Exception e1) {
  			Logger.error(this, "Could not base64 decrypt bridge's public key : " + e1.getMessage());
  		} catch (UnsupportedEncodingException e) {
  			Logger.error(this, "UTF-8 Encoding not supported");
  		} finally {
  			if (encReply == null) {
  				channelStatus = ChannelStatus.failedToEncrypt;
  				return;
  			}
  		}
  
  		insertSubKSK(question, encReply,  new int[0]);
  		waitForSyn();
  	}
  
  	private void insertSubKSK(String question, byte[] encReply, int[] prevSubKSK) {
  		FreenetURI insertedKSK = null;
  		int randSubKSK = (int) (Math.random() * MAX_KSK_POLLS);
  		try {
  			insertedKSK = CENOClient.nodeInterface.insertSingleChunk(new FreenetURI("KSK@" + question + "-" + randSubKSK), encReply, new KSKSolutionPutCallback(question, encReply, new int[]{randSubKSK}));
  		} catch (PersistenceDisabledException e) {
  			Logger.error(this, "Tried to insert KSK reply with persistence, but persistence is disabled: " + e.getMessage());
  			channelStatus = ChannelStatus.failedToPublishKSK;
  		} catch (MalformedURLException e) {
  			Logger.error(this, "Reply KSK is malformed: " + e.getMessage());
  			channelStatus = ChannelStatus.failedToPublishKSK;
  		} catch (InsertException e) {
  			Logger.error(this, "Error while initializing insertion for KSK reply: " + e.getMessage());
  			channelStatus = ChannelStatus.failedToPublishKSK;
  		} catch (UnsupportedEncodingException e) {
  			Logger.error(this, "UTF-8 Encoding not supported");
  			channelStatus = ChannelStatus.failedToPublishKSK;
  		} finally {
  			if(insertedKSK == null) {
  				return;
  			}
  		}
  		channelStatus = ChannelStatus.publishedKSK;
  		Logger.normal(this, "Started publishing to KSK solution to the bridge slot " + randSubKSK);
  		return;
  	}
  
  	private class KSKSolutionPutCallback implements ClientPutCallback {
  		int[] prevSubKSK;
  		byte[] encReply;
  		String question;
  
  		public KSKSolutionPutCallback(String question, byte[] encReply, int[] prevSubKSK) {
  			this.question = question;
  			this.encReply = encReply;
  			this.prevSubKSK = prevSubKSK;
  		}
  
  		@Override
  		public void onResume(ClientContext context) throws ResumeFailedException {}
  
  		@Override
  		public RequestClient getRequestClient() {
  			return CENOClient.nodeInterface.getRequestClient();
  		}
  
  		@Override
  		public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) {}
  
  		@Override
  		public void onGeneratedMetadata(Bucket metadata, BaseClientPutter state) {}
  
  		@Override
  		public void onFetchable(BaseClientPutter state) {}
  
  		@Override
  		public void onSuccess(BaseClientPutter state) {
  			Logger.normal(this,"Inserted private SSK key in the KSK@solution to the puzzle published by the bridge");
  		}
  
  		@Override
  		public void onFailure(InsertException e, BaseClientPutter state) {
  			if (e.getMode() == InsertExceptionMode.COLLISION) {
  				boolean subKSKUsed;
  				int randSubKSK;
  				do {
  					subKSKUsed = false;
  					randSubKSK = (int) (Math.random() * MAX_KSK_POLLS);
  					for (int i = 0; i < prevSubKSK.length ; i++) {
  						if (prevSubKSK[i] == randSubKSK) {
  							subKSKUsed = true;
  							break;
  						}
  					}
  				} while (subKSKUsed);
  