/** what IP Alice is reachable on */ public void readIP(byte target[], int targetOffset) { int offset = readBodyOffset() + 4; int size = _message[offset] & 0xff; offset++; System.arraycopy(_message, offset, target, targetOffset, size); if (_log.shouldLog(Log.DEBUG)) _log.debug("read alice ip: " + Base64.encode(target, targetOffset, size)); }
/** unused */ public void readChallengeSize(byte target[], int targetOffset) { int offset = readBodyOffset() + 4; offset += _message[offset] & 0xff; offset += 1 + 2; int sz = _message[offset] & 0xff; offset++; System.arraycopy(_message, offset, target, targetOffset, sz); if (_log.shouldLog(Log.DEBUG)) _log.debug("read challenge data: " + Base64.encode(target)); }
public void readAliceIntroKey(byte target[], int targetOffset) { int offset = readBodyOffset() + 4; offset += _message[offset] & 0xff; offset += 1 + 2; int sz = _message[offset] & 0xff; offset++; offset += sz; System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES); if (_log.shouldLog(Log.DEBUG)) _log.debug( "read alice intro key: " + Base64.encode(target, targetOffset, SessionKey.KEYSIZE_BYTES) + " packet size: " + _payloadLength + " off: " + offset + " data: " + Base64.encode(_message)); }
public void toRawString(StringBuilder buf) throws DataFormatException { UDPPacketReader.this.toRawString(buf); buf.append(" payload: "); int off = getFragmentBegin(0); // first fragment off += 4 + 1; // messageId + fragment info int size = ((int) DataHelper.fromLong(_message, off, 2)) & 0x3FFF; off += 2; buf.append(Base64.encode(_message, off, size)); }
public synchronized void receiveSessionCreated(UDPPacketReader.SessionCreatedReader reader) { if (_currentState == OutboundState.OB_STATE_VALIDATION_FAILED) { if (_log.shouldLog(Log.WARN)) _log.warn("Session created already failed"); return; } if (_receivedY != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Session created already received, ignoring"); return; // already received } _receivedY = new byte[UDPPacketReader.SessionCreatedReader.Y_LENGTH]; reader.readY(_receivedY, 0); if (_aliceIP == null) _aliceIP = new byte[reader.readIPSize()]; reader.readIP(_aliceIP, 0); _alicePort = reader.readPort(); _receivedRelayTag = reader.readRelayTag(); _receivedSignedOnTime = reader.readSignedOnTime(); _receivedEncryptedSignature = new byte[Signature.SIGNATURE_BYTES + 8]; reader.readEncryptedSignature(_receivedEncryptedSignature, 0); _receivedIV = new byte[UDPPacket.IV_SIZE]; reader.readIV(_receivedIV, 0); if (_log.shouldLog(Log.DEBUG)) _log.debug( "Receive session created:Sig: " + Base64.encode(_receivedEncryptedSignature) + "receivedIV: " + Base64.encode(_receivedIV) + "AliceIP: " + Addresses.toString(_aliceIP) + " RelayTag: " + _receivedRelayTag + " SignedOn: " + _receivedSignedOnTime + ' ' + this.toString()); if (_currentState == OutboundState.OB_STATE_UNKNOWN || _currentState == OutboundState.OB_STATE_REQUEST_SENT || _currentState == OutboundState.OB_STATE_INTRODUCED || _currentState == OutboundState.OB_STATE_PENDING_INTRO) _currentState = OutboundState.OB_STATE_CREATED_RECEIVED; packetReceived(); }
public long readSignedOnTime() { int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2 + 4; long rv = DataHelper.fromLong(_message, offset, 4); if (_log.shouldLog(Log.DEBUG)) _log.debug( "Signed on time offset: " + offset + " val: " + rv + "\nRawCreated: " + Base64.encode(_message, _payloadBeginOffset, _payloadLength)); return rv; }
private Hash getHash(String name) { String key = name.substring(PREFIX.length()); key = key.substring(0, 44); // Hash h = new Hash(); try { // h.fromBase64(key); byte[] b = Base64.decode(key); if (b == null) return null; Hash h = Hash.create(b); return h; } catch (Exception dfe) { _log.warn("Invalid base64 [" + key + "]", dfe); return null; } }
/* Parse and execute a DEST message*/ protected boolean execDestMessage(String opcode, Properties props) { if (opcode.equals("GENERATE")) { if (!props.isEmpty()) { _log.debug("Properties specified in DEST GENERATE message"); return false; } ByteArrayOutputStream priv = new ByteArrayOutputStream(); ByteArrayOutputStream pub = new ByteArrayOutputStream(); SAMUtils.genRandomKey(priv, pub); return writeString( "DEST REPLY" + " PUB=" + Base64.encode(pub.toByteArray()) + " PRIV=" + Base64.encode(priv.toByteArray()) + "\n"); } else { _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\""); return false; } }
/* Parse and execute a DEST message*/ protected boolean execDestMessage(String opcode, Properties props) { if (opcode.equals("GENERATE")) { String sigTypeStr = props.getProperty("SIGNATURE_TYPE"); SigType sigType; if (sigTypeStr != null) { sigType = SigType.parseSigType(sigTypeStr); if (sigType == null) { writeString( "DEST REPLY RESULT=I2P_ERROR MESSAGE=\"SIGNATURE_TYPE " + sigTypeStr + " unsupported\"\n"); return false; } } else { sigType = SigType.DSA_SHA1; } ByteArrayOutputStream priv = new ByteArrayOutputStream(663); ByteArrayOutputStream pub = new ByteArrayOutputStream(387); SAMUtils.genRandomKey(priv, pub, sigType); return writeString( "DEST REPLY" + " PUB=" + Base64.encode(pub.toByteArray()) + " PRIV=" + Base64.encode(priv.toByteArray()) + "\n"); } else { writeString("DEST REPLY RESULT=I2P_ERROR MESSAGE=\"DEST GENERATE required\""); if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\""); return false; } }
/** * Verify: Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's new relay tag + Bob's * signed on time Caller must synch on this. */ private boolean verifySessionCreated() { byte signed[] = new byte [256 + 256 // X + Y + _aliceIP.length + 2 + _bobIP.length + 2 + 4 // sent relay tag + 4 // signed on time ]; int off = 0; System.arraycopy(_sentX, 0, signed, off, _sentX.length); off += _sentX.length; System.arraycopy(_receivedY, 0, signed, off, _receivedY.length); off += _receivedY.length; System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length); off += _aliceIP.length; DataHelper.toLong(signed, off, 2, _alicePort); off += 2; System.arraycopy(_bobIP, 0, signed, off, _bobIP.length); off += _bobIP.length; DataHelper.toLong(signed, off, 2, _bobPort); off += 2; DataHelper.toLong(signed, off, 4, _receivedRelayTag); off += 4; DataHelper.toLong(signed, off, 4, _receivedSignedOnTime); boolean valid = _context .dsa() .verifySignature(_receivedSignature, signed, _remotePeer.getSigningPublicKey()); if (_log.shouldLog(Log.DEBUG) || (_log.shouldLog(Log.WARN) && !valid)) { StringBuilder buf = new StringBuilder(128); buf.append("Signed sessionCreated:"); buf.append(" Alice: ").append(Addresses.toString(_aliceIP, _alicePort)); buf.append(" Bob: ").append(Addresses.toString(_bobIP, _bobPort)); buf.append(" RelayTag: ").append(_receivedRelayTag); buf.append(" SignedOn: ").append(_receivedSignedOnTime); buf.append(" signature: ").append(Base64.encode(_receivedSignature.getData())); if (valid) _log.debug(buf.toString()); else if (_log.shouldLog(Log.WARN)) _log.warn("INVALID: " + buf.toString()); } return valid; }
/** * decrypt the signature (and subsequent pad bytes) with the additional layer of encryption using * the negotiated key along side the packet's IV Caller must synch on this. */ private void decryptSignature() { if (_receivedEncryptedSignature == null) throw new NullPointerException("encrypted signature is null! this=" + this.toString()); else if (_sessionKey == null) throw new NullPointerException("SessionKey is null!"); else if (_receivedIV == null) throw new NullPointerException("IV is null!"); _context .aes() .decrypt( _receivedEncryptedSignature, 0, _receivedEncryptedSignature, 0, _sessionKey, _receivedIV, _receivedEncryptedSignature.length); byte signatureBytes[] = new byte[Signature.SIGNATURE_BYTES]; System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, Signature.SIGNATURE_BYTES); _receivedSignature = new Signature(signatureBytes); if (_log.shouldLog(Log.DEBUG)) _log.debug("Decrypted received signature: " + Base64.encode(signatureBytes)); }
/** Verify with the "olddest" property's public key using the "oldsig" property */ public boolean hasValidInnerSig() { if (props == null || name == null || dest == null) return false; boolean rv = false; // don't cache result if (true) { StringWriter buf = new StringWriter(1024); String sig = props.getProperty(PROP_OLDSIG); String olddest = props.getProperty(PROP_OLDDEST); if (sig == null || olddest == null) return false; buf.append(name); buf.append(KV_SEPARATOR); buf.append(dest); try { writeProps(buf, true, true); } catch (IOException ioe) { // won't happen return false; } byte[] sdata = Base64.decode(sig); if (sdata == null) return false; Destination d; try { d = new Destination(olddest); } catch (DataFormatException dfe) { return false; } SigningPublicKey spk = d.getSigningPublicKey(); SigType type = spk.getType(); if (type == null) return false; Signature s; try { s = new Signature(type, sdata); } catch (IllegalArgumentException iae) { return false; } rv = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk); } return rv; }
/* Parse and execute a SESSION message */ protected boolean execSessionMessage(String opcode, Properties props) { String dest = "BUG!"; try { if (opcode.equals("CREATE")) { if ((getRawSession() != null) || (getDatagramSession() != null) || (getStreamSession() != null)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Trying to create a session, but one still exists"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); } if (props.isEmpty()) { if (_log.shouldLog(Log.DEBUG)) _log.debug("No parameters specified in SESSION CREATE message"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n"); } dest = props.getProperty("DESTINATION"); if (dest == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION DESTINATION parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n"); } props.remove("DESTINATION"); String destKeystream = null; if (dest.equals("TRANSIENT")) { _log.debug("TRANSIENT destination requested"); ByteArrayOutputStream priv = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(priv, null); destKeystream = Base64.encode(priv.toByteArray()); } else { destKeystream = bridge.getKeystream(dest); if (destKeystream == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug( "Custom destination specified [" + dest + "] but it isn't known, creating a new one"); ByteArrayOutputStream baos = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(baos, null); destKeystream = Base64.encode(baos.toByteArray()); bridge.addKeystream(dest, destKeystream); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Custom destination specified [" + dest + "] and it is already known"); } } String style = props.getProperty("STYLE"); if (style == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION STYLE parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n"); } props.remove("STYLE"); // Unconditionally override what the client may have set // (iMule sets BestEffort) as None is more efficient // and the client has no way to access delivery notifications props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); if (style.equals("RAW")) { rawSession = new SAMRawSession(destKeystream, props, this); } else if (style.equals("DATAGRAM")) { datagramSession = new SAMDatagramSession(destKeystream, props, this); } else if (style.equals("STREAM")) { String dir = props.getProperty("DIRECTION"); if (dir == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH"); dir = "BOTH"; } else if (!dir.equals("CREATE") && !dir.equals("RECEIVE") && !dir.equals("BOTH")) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unknown DIRECTION parameter value: [" + dir + "]"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n"); } else { props.remove("DIRECTION"); } streamSession = newSAMStreamSession(destKeystream, dir, props); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized SESSION STYLE: \"" + style + "\""); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n"); } return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\""); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n"); } } catch (DataFormatException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Invalid destination specified"); return writeString( "SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (I2PSessionException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("I2P error when instantiating session", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (SAMException e) { _log.error("Unexpected SAM error", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (IOException e) { _log.error("Unexpected IOException", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } }
public void toRawString(StringBuilder buf) { if (_message != null) buf.append(Base64.encode(_message, _payloadBeginOffset, _payloadLength)); }
/* Parse and execute a SESSION message */ @Override protected boolean execSessionMessage(String opcode, Properties props) { String dest = "BUG!"; String nick = null; boolean ok = false; try { if (opcode.equals("CREATE")) { if ((this.getRawSession() != null) || (this.getDatagramSession() != null) || (this.getStreamSession() != null)) { _log.debug("Trying to create a session, but one still exists"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); } if (props == null) { _log.debug("No parameters specified in SESSION CREATE message"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n"); } dest = props.getProperty("DESTINATION"); if (dest == null) { _log.debug("SESSION DESTINATION parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n"); } props.remove("DESTINATION"); if (dest.equals("TRANSIENT")) { _log.debug("TRANSIENT destination requested"); ByteArrayOutputStream priv = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(priv, null); dest = Base64.encode(priv.toByteArray()); } else { _log.debug("Custom destination specified [" + dest + "]"); } try { SAMUtils.checkPrivateDestination(dest); } catch (SAMUtils.InvalidDestination e) { return writeString("SESSION STATUS RESULT=INVALID_KEY\n"); } nick = props.getProperty("ID"); if (nick == null) { _log.debug("SESSION ID parameter not specified"); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n"); } props.remove("ID"); String style = props.getProperty("STYLE"); if (style == null) { _log.debug("SESSION STYLE parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n"); } props.remove("STYLE"); // Unconditionally override what the client may have set // (iMule sets BestEffort) as None is more efficient // and the client has no way to access delivery notifications i2cpProps.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); // Record the session in the database sSessionsHash Properties allProps = new Properties(); allProps.putAll(i2cpProps); allProps.putAll(props); try { sSessionsHash.put(nick, new SessionRecord(dest, allProps, this)); } catch (SessionsDB.ExistingId e) { _log.debug("SESSION ID parameter already in use"); return writeString("SESSION STATUS RESULT=DUPLICATED_ID\n"); } catch (SessionsDB.ExistingDest e) { return writeString("SESSION STATUS RESULT=DUPLICATED_DEST\n"); } // Create the session if (style.equals("RAW")) { DatagramServer.getInstance(i2cpProps); rawSession = newSAMRawSession(nick); this.session = rawSession; } else if (style.equals("DATAGRAM")) { DatagramServer.getInstance(i2cpProps); datagramSession = newSAMDatagramSession(nick); this.session = datagramSession; } else if (style.equals("STREAM")) { streamSession = newSAMStreamSession(nick); this.session = streamSession; } else { _log.debug("Unrecognized SESSION STYLE: \"" + style + "\""); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n"); } ok = true; return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n"); } else { _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\""); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n"); } } catch (DataFormatException e) { _log.debug("Invalid destination specified"); return writeString( "SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (I2PSessionException e) { _log.debug("I2P error when instantiating session", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (SAMException e) { _log.info("Funny SAM error", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (IOException e) { _log.error("Unexpected IOException", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } finally { // unregister the session if it has not been created if (!ok && nick != null) { sSessionsHash.del(nick); session = null; } } }
@Override public String toString() { StringBuilder buf = new StringBuilder(512); long msAgo = _context.clock().now() - readTimestamp() * 1000; buf.append("Data packet sent ").append(msAgo).append("ms ago "); buf.append("IV "); buf.append( Base64.encode(_message, _payloadBeginOffset - UDPPacket.IV_SIZE, UDPPacket.IV_SIZE)); buf.append(" "); int off = readBodyOffset() + 1; if (readACKsIncluded()) { int numACKs = _message[off] & 0xff; off++; buf.append("with ACKs for "); for (int i = 0; i < numACKs; i++) { buf.append(DataHelper.fromLong(_message, off, 4)).append(' '); off += 4; } } if (readACKBitfieldsIncluded()) { int numBitfields = _message[off] & 0xff; off++; buf.append("with partial ACKs for "); try { for (int i = 0; i < numBitfields; i++) { PacketACKBitfield bf = new PacketACKBitfield(off); buf.append(bf.getMessageId()).append(' '); off += bf.getByteLength(); } } catch (DataFormatException dfe) { buf.append("CORRUPT"); return buf.toString(); } } if (readExtendedDataIncluded()) { int size = _message[off] & 0xff; off++; buf.append("with extended size of "); buf.append(size); buf.append(' '); off += size; } int numFragments = _message[off] & 0xff; off++; buf.append("with fragmentCount of "); buf.append(numFragments); buf.append(' '); for (int i = 0; i < numFragments; i++) { buf.append("containing messageId "); buf.append(DataHelper.fromLong(_message, off, 4)); off += 4; int fragNum = (_message[off] & 0xFF) >>> 1; boolean isLast = (_message[off] & 1) != 0; off++; buf.append(" frag# ").append(fragNum); buf.append(" isLast? ").append(isLast); buf.append(" info ").append(_message[off - 1]); int size = ((int) DataHelper.fromLong(_message, off, 2)) & 0x3FFF; off += 2; buf.append(" with ").append(size).append(" bytes; "); off += size; } return buf.toString(); }
private void runTest() { I2PAppContext ctx = I2PAppContext.getGlobalContext(); Log log = ctx.logManager().getLog(getClass()); List order = pickOrder(ctx); TunnelCreatorConfig cfg = createConfig(ctx); _replyRouter = new Hash(); byte h[] = new byte[Hash.HASH_LENGTH]; Arrays.fill(h, (byte) 0xFF); _replyRouter.setData(h); _replyTunnel = 42; // populate and encrypt the message BuildMessageGenerator gen = new BuildMessageGenerator(); TunnelBuildMessage msg = new TunnelBuildMessage(ctx); for (int i = 0; i < BuildMessageGenerator.ORDER.length; i++) { int hop = ((Integer) order.get(i)).intValue(); PublicKey key = null; if (hop < _pubKeys.length) key = _pubKeys[hop]; gen.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key); } gen.layeredEncrypt(ctx, msg, cfg, order); log.debug( "\n================================================================" + "\nMessage fully encrypted" + "\n================================================================"); // now msg is fully encrypted, so lets go through the hops, decrypting and replying // as necessary BuildMessageProcessor proc = new BuildMessageProcessor(ctx); for (int i = 0; i < cfg.getLength(); i++) { // this not only decrypts the current hop's record, but encrypts the other records // with the reply key BuildRequestRecord req = proc.decrypt(ctx, msg, _peers[i], _privKeys[i]); if (req == null) { // no records matched the _peers[i], or the decryption failed throw new RuntimeException("foo @ " + i); } long ourId = req.readReceiveTunnelId(); byte replyIV[] = req.readReplyIV(); long nextId = req.readNextTunnelId(); Hash nextPeer = req.readNextIdentity(); boolean isInGW = req.readIsInboundGateway(); boolean isOutEnd = req.readIsOutboundEndpoint(); long time = req.readRequestTime(); long now = (ctx.clock().now() / (60l * 60l * 1000l)) * (60 * 60 * 1000); int ourSlot = -1; BuildResponseRecord resp = new BuildResponseRecord(); byte reply[] = resp.create(ctx, 0, req.readReplyKey(), req.readReplyIV(), -1); for (int j = 0; j < TunnelBuildMessage.RECORD_COUNT; j++) { if (msg.getRecord(j) == null) { ourSlot = j; msg.setRecord(j, new ByteArray(reply)); break; } } log.debug( "Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64() + " receives on " + ourId + " w/ replyIV " + Base64.encode(replyIV) + " sending to " + nextId + " on " + nextPeer.toBase64() + " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now - time)); } log.debug( "\n================================================================" + "\nAll hops traversed and replies gathered" + "\n================================================================"); // now all of the replies are populated, toss 'em into a reply message and handle it TunnelBuildReplyMessage reply = new TunnelBuildReplyMessage(ctx); for (int i = 0; i < TunnelBuildMessage.RECORD_COUNT; i++) reply.setRecord(i, msg.getRecord(i)); BuildReplyHandler handler = new BuildReplyHandler(); int statuses[] = handler.decrypt(ctx, reply, cfg, order); if (statuses == null) throw new RuntimeException("bar"); boolean allAgree = true; for (int i = 0; i < cfg.getLength(); i++) { Hash peer = cfg.getPeer(i); int record = ((Integer) order.get(i)).intValue(); if (statuses[record] != 0) allAgree = false; // else // penalize peer according to the rejection cause } log.debug( "\n================================================================" + "\nAll peers agree? " + allAgree + "\n================================================================"); }