Author: kryptos
Date: 2007-08-18 18:04:33 +0000 (Sat, 18 Aug 2007)
New Revision: 14790

Modified:
   branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java
Log:
 JFK message structure complete

Modified: branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java
===================================================================
--- branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java 2007-08-18 
17:45:28 UTC (rev 14789)
+++ branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java 2007-08-18 
18:04:33 UTC (rev 14790)
@@ -1,3 +1,4 @@
+
 /* This code is part of Freenet. It is distributed under the GNU General
  * Public License, version 2 (or at your option any later version). See
  * http://www.gnu.org/ for further details of the GPL. */
@@ -5,16 +6,24 @@

 import java.security.MessageDigest;
 import java.util.Arrays;
-
+import java.lang.*;
+import java.util.ArrayList;
+import java.util.*;
 import net.i2p.util.NativeBigInteger;
-
+import freenet.crypt.crypto_Random.*;
 import freenet.crypt.BlockCipher;
+import freenet.crypt.DSAGroup;
+import freenet.crypt.DSAPrivateKey;
+import freenet.crypt.DSAPublicKey;
 import freenet.crypt.DSASignature;
 import freenet.crypt.DiffieHellman;
 import freenet.crypt.DiffieHellmanContext;
+import freenet.crypt.DummyRandomSource;
 import freenet.crypt.EntropySource;
+import freenet.crypt.Global;
 import freenet.crypt.PCFBMode;
 import freenet.crypt.SHA256;
+import freenet.crypt.crypto_Random.*;
 import freenet.io.comm.*;
 import freenet.io.comm.Peer.LocalAddressException;
 import freenet.support.Fields;
@@ -35,12 +44,14 @@
  */
 public class FNPPacketMangler implements OutgoingPacketMangler, 
IncomingPacketFilter {

-    private static boolean logMINOR;
+       private static boolean logMINOR;
     final Node node;
-    final PeerManager pm;
-    final UdpSocketManager usm;
+    final NodeCrypto crypto;
+    final MessageCore usm;
+    final PacketSocketHandler sock;
     final EntropySource fnpTimingSource;
     final EntropySource myPacketDataSource;
+    final nonceGen ng;
     private static final int MAX_PACKETS_IN_FLIGHT = 256; 
     private static final int RANDOM_BYTES_LENGTH = 12;
     private static final int HASH_LENGTH = 32;
@@ -64,17 +75,19 @@
        static public final int HEADERS_LENGTH_ONE_MESSAGE = 
                HEADERS_LENGTH_MINIMUM + 2; // 2 bytes = length of message. 
rest is the same.

-       static public final int FULL_HEADERS_LENGTH_MINIMUM = 
-               HEADERS_LENGTH_MINIMUM + UdpSocketManager.UDP_HEADERS_LENGTH;
-       static public final int FULL_HEADERS_LENGTH_ONE_MESSAGE =
-               HEADERS_LENGTH_ONE_MESSAGE + 
UdpSocketManager.UDP_HEADERS_LENGTH;
-    
-    public FNPPacketMangler(Node node) {
+       final int fullHeadersLengthMinimum;
+       final int fullHeadersLengthOneMessage;
+       
+    public FNPPacketMangler(Node node, NodeCrypto crypt, PacketSocketHandler 
sock) {
         this.node = node;
-        this.pm = node.peers;
+        this.crypto = crypt;
         this.usm = node.usm;
+        this.sock = sock;
         fnpTimingSource = new EntropySource();
         myPacketDataSource = new EntropySource();
+        fullHeadersLengthMinimum = HEADERS_LENGTH_MINIMUM + 
sock.getHeadersLength();
+        fullHeadersLengthOneMessage = HEADERS_LENGTH_ONE_MESSAGE + 
sock.getHeadersLength();
+               logMINOR = Logger.shouldLog(Logger.MINOR, this);
     }

     /**
@@ -109,7 +122,11 @@
          * Otherwise try all of them (on the theory that nodes 
          * occasionally change their IP addresses).
          */
-        PeerNode opn = pm.getByPeer(peer);
+        PeerNode opn = node.peers.getByPeer(peer);
+        if(opn != null && opn.getOutgoingMangler() != this) {
+               Logger.error(this, "Apparently contacted by "+opn+") on "+this);
+               opn = null;
+        }
         PeerNode pn;

         if(opn != null) {
@@ -126,9 +143,10 @@
                 if(tryProcessAuth(buf, offset, length, opn, peer)) return;
             }
         }
+        PeerNode[] peers = crypto.getPeerNodes();
         if(length > HASH_LENGTH + RANDOM_BYTES_LENGTH + 4 + 6) {
-            for(int i=0;i<pm.connectedPeers.length;i++) {
-                pn = pm.myPeers[i];
+            for(int i=0;i<peers.length;i++) {
+                pn = peers[i];
                 if(pn == opn) continue;
                 if(tryProcess(buf, offset, length, pn.getCurrentKeyTracker())) 
{
                     // IP address change
@@ -148,8 +166,8 @@
             }
         }
         if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH + 2) {
-            for(int i=0;i<pm.myPeers.length;i++) {
-                pn = pm.myPeers[i];
+            for(int i=0;i<peers.length;i++) {
+                pn = peers[i];
                 if(pn == opn) continue;
                 if(tryProcessAuth(buf, offset, length, pn, peer)) return;
             }
@@ -249,10 +267,10 @@
             return;
         }

-        if((negType == 0)) {
-            Logger.error(this, "Decrypted auth packet but unknown negotiation 
type "+negType+" from "+replyTo+" possibly from "+pn);
-            return;
-        }else if ( negType == 1){
+        if(negType == 0) {
+               Logger.error(this, "Old ephemeral Diffie-Hellman (negType 0) 
not supported.");
+               return;
+        }else if (negType == 1) {
                // Four stage Diffie-Hellman. 0 = ephemeral, 1 = payload stages 
are signed (not quite STS)
                // FIXME reduce to 3 stages and implement STS properly (we have 
a separate validation mechanism in PeerNode)
                // AFAICS this (with negType=1) is equivalent in security to 
STS; it expands the second phase into a second and a fourth phase.
@@ -310,47 +328,91 @@
                        processSignedDHTwoOrThree(3, payload, pn, replyTo, 
false);
                }
         }
-       else if (negType==2){
-               /*
-                * We implement Just Fast Keying key management protocol with 
active identity                     * protection for the initiator and no 
identity protection for the responder
-                * Refer devNotes for detailed explanation of the protocol
-                */ 
-               if(packetType<0 || packetType>3){
-                       Logger.error(this,"Unknown PacketType" + packetType + 
"from" + replyTo + "from" +pn); 
-                       return ;
-       }
-               else if(packetType==0){
-                     /*
-                      * Initiator- This is a straightforward DiffieHellman 
exponential. The Init                       * iator Nonce serves two 
purposes;it allows the initiator to use the same                        * 
exponentials during different sessions while ensuring that the resulting        
               * session key will be different,can be used to differentiate 
between
-                      * parallel sessions
-                      */
-                       ProcessMessage1(pn,payload,0);                  
-                       
-               }
-               else if(packetType==1){
-                     /*
-                      * Responder replies with a signed copy of his own 
exponential, a random
-                      * nonce and an authenticator calculated from a transient 
hash key private
-                      * to the responder. We slightly deviate JFK here;we do 
not send any public                       * key information as specified in the 
JFK docs
-                      */
-                       ProcessMessage2(pn,payload,1);
-               }
-               else if(packetType==2){
-                     /* Initiator echoes the data sent by the responder.These 
messages are
+        else if (negType==2){
+               /*
+                * We implement Just Fast Keying key management protocol with 
active identity                     * protection for the initiator and no 
identity protection for the responder
+                * Refer devNotes for detailed explanation of the protocol
+                */ 
+               if(packetType<0 || packetType>3){
+                       Logger.error(this,"Unknown PacketType" + packetType + 
"from" + replyTo + "from" +pn); 
+                       return ;
+               }
+               else if(packetType==0){
+                     /*
+                      * Initiator- This is a straightforward DiffieHellman 
exponential.
+                       * The Initiator Nonce serves two purposes;it allows the 
initiator to use the same                        * exponentials during 
different sessions while ensuring that the resulting                       * 
session key will be different,can be used to differentiate between
+                      * parallel sessions
+                      */
+                       ProcessMessage1(pn,payload,0);                  
+                       
+               }
+               else if(packetType==1){
+                     /*
+                      * Responder replies with a signed copy of his own 
exponential, a random
+                      * nonce and an authenticator calculated from a transient 
hash key private
+                      * to the responder. We slightly deviate JFK here;we do 
not send any public
+                       * key information as specified in the JFK docs
+                      */
+                       ProcessMessage2(pn,payload,1);
+               }
+               else if(packetType==2){
+                     /* Initiator echoes the data sent by the responder.These 
messages are
                        * cached by the Responder.Receiving a duplicate message 
simply causes
                        * the responder to Re-transmit the corresponding 
message4
                        */
-                      
-               }
-               else if(packetType==3){
-                     /*
-                      * Encrypted message of the signature on both nonces, 
both exponentials 
-                      * using the same keys as in the previous message
-                      */
-               }
-       }
+                        ProcessMessage3(pn,payload,2);
+               }
+               else if(packetType==3){
+                     /*
+                      * Encrypted message of the signature on both nonces, 
both exponentials 
+                      * using the same keys as in the previous message
+                      */
+                       ProcessMessage4(pn,payload,3);
+               }
+        }
+        else {
+            Logger.error(this, "Decrypted auth packet but unknown negotiation 
type "+negType+" from "+replyTo+" possibly from "+pn);
+            return;
+        }
     }
     /*
+     * Initiator DH Exponential
+     */
+    private byte[] Gi(){
+                DiffieHellmanContext 
dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
+                if(dh==null)
+                {
+                        if(shouldLogErrorInHandshake())
+                                Logger.error(this,"Failed getting 
exponentials");
+                       
+                }
+                return dh.getOurExponential().toByteArray();
+    }
+    /*
+     * Responder DH Exponential
+     */
+    private byte[] Gr(){
+                DiffieHellmanContext 
dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
+                if(dh==null)
+                {
+                        if(shouldLogErrorInHandshake())
+                                Logger.error(this,"Failed getting 
exponentials");
+                       
+                }
+                return dh.getHisExponential().toByteArray();
+    }
+    private byte[] iNonce(){
+                
+                byte[] n=new byte[16];
+                node.random.nextBytes(n);
+                return n;
+    }
+    private byte[] rNonce(){
+                byte[] n=new byte[16];
+                node.random.nextBytes(n);
+                return n;
+    }
+    /*
      * Initiator Method:Message1
      * Process Message1
      * Send the Initiator nonce and DiffieHellman Exponential
@@ -361,30 +423,42 @@
     private void ProcessMessage1(PeerNode pn,byte[] payload,int phase)
     {
                 long t1=System.currentTimeMillis();
-                Ni=nonceGen.getNewNonce();
-                DiffieHellmanContext 
dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
-                if(ctx==null)
-               {
-                        if(shouldLogErrorInHandshake())
-                                Logger.error(this,"Failed getting 
exponentials");
-                       
-                }
-                byte[] gi=ctx.getOurExponential().toByteArray();
-                byte[] message1=new byte[Ni.length + gi.length+1];
-                System.arraycopy(Ni,0,message1,0,Ni.length);
-                System.arraycopy(gi,0,message1,Ni.length+1,gi.length);
-                sendMessage1or3Packet(1,negType,phase,message1,pn,replyTo);
+                byte[] message1=new byte[iNonce().length + Gi().length+1];
+                System.arraycopy(iNonce(),0,message1,0,iNonce().length);
+                
System.arraycopy(Gi(),0,message1,iNonce().length+1,Gi().length);
+                //Send params:Version,negType,phase,data,peernode,peer
+                sendMessage1or2Packet(1,2,0,message1,pn,replyTo);
                 long t2=System.currentTimeMillis();
                 if((t2-t1)>500)
                         Logger.error(this,"Message1 timeout error "+" replyto 
"+pn.getName());

     }
     /*
+     * Authenticator computed over the Responder exponentials and the Nonces
+     * Used by the responder to verify the authenticity of the received data
+     */
+    private void sendMessage2and3Auth(){
+                byte[] authData=new 
byte[iNonce().length+rNonce().length+Gr().length+1];
+                System.arraycopy(iNonce(),0,authData,0,iNonce().length);
+                
System.arraycopy(rNonce(),0,authData,iNonce().length+1,rNonce().length);
+               
System.arraycopy(Gr(),0,authData,iNonce().length+rNonce().length+1,Gr().length);
+               //Calculate the Hash of the Concatenated data(Responder 
exponentials and nonces
+               //using a key that will be private to the responder
+               HKrGenerator trKey=new HKrGenerator(20);
+               hkr=trKey.getNewHKr();
+               HMAC hash=new HMAC(SHA1.getInstance());
+                /*
+                 * Compute the authenticator
+                 * Used by the responder in Message3 to verify the 
authenticity of the message
+                 */
+               return hash.mac(hkr,authData,20);
+    }
+    /*
      * Responder Method:Message2
      * Process Message2
      * Send the Initiator nonce,Responder nonce and DiffieHellman Exponential 
of the responder
-     * and grpInfo in the clear.
-     * Send a signed copy of his own exponential and grpInfo.
+     * in the clear.
+     * Send a signed copy of his own exponential and grpInfo details.
      * Send an authenticator which is a hash of Ni,Nr,g^r calculated over the 
transient key HKr
      * @param The packet phase number
      * @param The peerNode we are talking to
@@ -394,43 +468,30 @@
     private void ProcessMessage2(PeerNode pn,byte[] payload,int phase)
     {
                long t1=System.currentTimeMillis();
-               Nr=nonceGen.getNewNonce();
-               DiffieHellmanContext 
dh=(DIffieHellmanContext)pn.getKeyAgreementSchemeContext();
-               if(ctx==null)
-               {
-                       if(shouldLogErrorInHandshake())
-                               Logger.error(this,"failed getting 
exponentials");
-               }
-               //Get responder Exponentials
-               byte[] DHExpr=dh.getHisExponential().toByteArray();
-               int grpLength=grpInfo.size();
-               byte signData=new byte[grpLength+DHExpr.length+1];
-               //Signature of Initiator Exponentials and GrpInfo
-               DSASignature sig = crypto.sign(unVerifiedData);
-               byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
-               byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
-               Logger.minor(this, " 
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
-               
-               if(r.length > 255 || s.length > 255)
-                       throw new IllegalStateException("R or S is too long: 
r.length="+r.length+" s.length="+s.length);
-
+               byte[] signData=new byte[Gr().length+1];
+               //COmpute the Signature:DSA
+               DSASignature sig = crypto.sign(signData);
+                byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+                byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+                Logger.minor(this, " 
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+                if(r.length > 255 || s.length > 255)
+                    throw new IllegalStateException("R or S is too long: 
r.length="+r.length+" s.length="+s.length);
                //Data sent in the clear
-               byte[] unVerifiedData=new 
byte[Ni.length+Nr.length+DHExpr.length+1];
-                System.arraycopy(Ni,0,unVerifiedData,0,Ni.length);
-                System.arraycopy(Nr,0,unVerifiedData,Ni.length+1,Nr.length);
-               
System.arraycopy(DHExp,0,unVerifiedData,Ni.length+nr.length+1,DHExpr.length);
-               //Calculate the Hash of the Concatenated data(Responder 
exponentials and nonces
-               //using a key that will be private to the responder
-               HKrGenerator message2Key=new HKrGenerator(20);
-               hkr=message2Key.getNewHKr();
-               HMAC s=new HMAC(SHA1.getInstance());
-               byte[] hashedMessage=s.mac(hkr,unVerifiedData,20);
-               byte[] Message2=new 
byte[hashedMessage.length+unVerifiedData.length+s.length+r.length+1];
-               byte[] signedData=new byte[s.length+r.length+1];
+               byte[] unVerifiedData=new 
byte[iNonce().length+rNonce().length+Gr().length+1];
+                System.arraycopy(iNonce(),0,unVerifiedData,0,iNonce().length);
+                
System.arraycopy(rNonce(),0,unVerifiedData,iNonce().length+1,rNonce().length);
+               
System.arraycopy(Gr(),0,unVerifiedData,iNonce().length+rNonce().length+1,Gr().length);
+               /*
+                 * Compute the authenticator
+                 * Used by the responder in Message3 to verify the 
authenticity of the message
+                 */
+               byte[] authenticator=sendMessage2and3Auth();
+               byte[] Message2=new 
byte[authenticator.length+unVerifiedData.length+s.length+r.length+1];
+               byte[] signedData=new byte[s.length+r.length];
                System.arraycopy(signedData,0,Message2,0,signedData.length);
                
System.arraycopy(unVerifiedData,0,Message2,signData.length+1,unVerifiedData.length);
-               
System.arraycopy(hashedMessage,0,Message2,signedData.length+unVerifiedData.length+1,hashedMessage.length);
-               sendMessage2or4Packet(1,negType,phase,message1,pn,replyTo);
+               
System.arraycopy(authenticator,0,Message2,signedData.length+unVerifiedData.length+1,authenticator.length);
+               sendMessage1or2Packet(1,negType,phase,message2,pn,replyTo);
                long t2=System.currentTimeMillis();
                 if((t2-t1)>500)
                         Logger.error(this,"Message2 timeout error "+" replyto 
"+pn.getName());
@@ -448,31 +509,28 @@
     * @param The peerNode we are talking to
     * @param Payload
     */
-    private void ProcessMessage3(PeerNode pn,byte[] payload,int 
phase,BlockCipher cipher)                      
+    private void ProcessMessage3(PeerNode pn,byte[] payload,int phase)         
        
     {
-       PCFBMode pcfb = PCFBMode.create(cipher);
-        int paddingLength = node.random.nextInt(100);
-        byte[] iv = new byte[pcfb.lengthIV()];
-        node.random.nextBytes(iv);
-       //Hash of the Data using the transient hashkey private to the responder
-        HKrGenerator message3Key=new HKrGenerator(20);
-        hkr=message3Key.getNewHKr();
-        HMAC s=new HMAC(SHA1.getInstance());
-        byte[] unVerifiedDataMinusExpi=new 
byte[Ni.length+Nr.length+DHExpr.length+1];
-        System.arraycopy(Ni,0,unVerifiedDataMinusExpi,0,Ni.length);
-        System.arraycopy(Nr,0,unVerifiedDataMinusExpi,Ni.length+1,Nr.length);
-        
System.arraycopy(DHExpr,0,unVerifiedDataMinuxExpi,Ni.length+nr.length+1,DHExpr.length);
-       byte[] hash=s.mac(hkr,unVerifiedDataMinusExpi,20);
-       byte[] unVerifiedData=new 
byte[unVerifiedDataMinusExpi.length+DHExpi.length+1];
-       
System.arraycopy(DHExpi,0,unVerifiedData,unVerifiedDataMinusExpi.length+1,DHExpi.length);
-        if(logMINOR)
-                Logger.minor(this, "Data hash: "+HexUtil.bytesToHex(hash));
-       DSASignature sig = crypto.sign(unVerifiedData);
+       
+       byte[] unVerifiedData=new 
byte[iNonce().length+rNonce().length+Gr().length+Gi().length+1];
+        System.arraycopy(iNonce(),0,unVerifiedData,0,iNonce().length);
+        
System.arraycopy(rNonce(),0,unVerifiedData,iNonce().length+1,rNonce().length);
+       
System.arraycopy(Gi(),0,unVerifiedData,iNonce().length+rNonce().length+1,Gi().length);
+        
System.arraycopy(Gr(),0,unVerifiedData,iNonce().length+rNonce().length+Gi().length+1,Gr().length);
+        DSASignature sig = crypto.sign(unVerifiedData);
         byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
         byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
         Logger.minor(this, " 
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
-        
+        byte[] authenticator=sendMessage2and3Auth();
+        BlockCipher c=pn.outgoingSetupCipher;
+        if(logMINOR)
+            
Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+        PCFBMode pk=PCFBMode.create(c);
+        byte[] iv=new byte[pk.lengthIV()];
         int outputLength = iv.length + r.length + s.length + 2;
+        /*
+         * The signature of the data sent in the clear is encrypted using PCFB 
and rijndael
+         */
         byte[] output = new byte[outputLength];
         System.arraycopy(iv, 0, output, 0, iv.length);
         int count = iv.length;
@@ -484,25 +542,59 @@
         output[count++] = (byte) s.length;
         System.arraycopy(s, 0, output, count, s.length);
         count += s.length;
-        pcfb.blockEncipher(output, 0, output.length);
+        pk.blockEncipher(output, 0, output.length);
+        byte[] message3=new 
byte[output.length+authenticator.length+unVerifiedData.length+1];
+        System.arraycopy(output,0,message3,0,output.length);
+        
System.arraycopy(authenticator,0,message3,output.length+1,authenticator.length);
+       
System.arraycopy(unVerifiedData,0,message3,output.length+authenticator.length+1,unVerifiedData.length);
+        sendMessage3and4Packet(version,negType,phase,message3,pn,replyTo);
     }
     /*
      * Responder Method:Message4
      * Process Message4
-     * Send the Initiator nonce,Responder nonce and DiffieHellman Exponential 
of the responder
-     * and grpInfo in the clear.
-     * Send a signed copy of his own exponential and grpInfo.
-     * Send an authenticator which is a hash of Ni,Nr,g^r calculated over the 
transient key HKr
+     * Encrypted message of the signature on both nonces, both exponentials 
using the same
+     * keys as in the previous message.The Initiator can verify that the 
Responder is present
+     * and participating in the session, by decrypting the message and 
verifying the enclosed
+     * signature.
      * @param The packet phase number
      * @param The peerNode we are talking to
      * @param Payload
      */

-    private void ProcessMessage4(PeerNode pn,byte[] payload,int 
phase,BlockCipher cipher)
+    private void ProcessMessage4(PeerNode pn,byte[] payload,int phase)
     {
        //Responder keeps a copy of recently received message3 and 
corresponding message4
         //Receiving a duplicated message simply causes the responder to 
retransmit the
        //corresponding message4 without creating a new state
+       
+        byte[] unVerifiedData=new 
byte[iNonce().length+rNonce().length+Gr().length+Gi().length+1];
+        System.arraycopy(iNonce(),0,unVerifiedData,0,iNonce().length);
+        
System.arraycopy(rNonce(),0,unVerifiedData,iNonce().length+1,rNonce().length);
+       
System.arraycopy(Gi(),0,unVerifiedData,iNonce().length+rNonce().length+1,Gi().length);
+        
System.arraycopy(Gr(),0,unVerifiedData,iNonce().length+rNonce().length+Gi().length+1,Gr().length);
+        DSASignature sig = crypto.sign(unVerifiedData);
+        byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        Logger.minor(this, " 
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+        BlockCipher c=pn.outgoingSetupCipher;
+        if(logMINOR)
+            
Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+        PCFBMode pk=PCFBMode.create(c);
+        byte[] iv=new byte[pk.lengthIV()];
+        int message4Length = r.length + s.length + 2;
+        byte[] message4 = new byte[message4Length];
+        System.arraycopy(iv, 0, output, 0, iv.length);
+        int count = iv.length;
+        if(r.length > 255 || s.length > 255)
+               throw new IllegalStateException("R or S is too long: 
r.length="+r.length+" s.length="+s.length);
+        message4[count++] = (byte) r.length;
+        System.arraycopy(r, 0, message4, count, r.length);
+        count += r.length;
+        message[count++] = (byte) s.length;
+        System.arraycopy(s, 0, message4, count, s.length);
+        count += s.length;
+        pk.blockEncipher(message4, 0, message4.length);
+        sendMessage3and4Packet(version,negType,phase,message3,pn,replyTo);
     }  

     /*
@@ -514,7 +606,7 @@
      * @param The peerNode we are talking to
      * @param The peer to which we need to send the packet
      */
-    private void sendMessage1or3Packet(int version,int negType,int 
phase,byte[] data,PeerNode pn,Peer replyTo)
+    private void sendMessage1or2Packet(int version,int negType,int 
phase,byte[] data,PeerNode pn,Peer replyTo)
     {
                 long now = System.currentTimeMillis();
                 long delta = now - pn.lastSentPacketTime();
@@ -526,16 +618,14 @@
                 if(logMINOR) Logger.minor(this, "Sending auth packet for 
"+pn.getPeer()+" (phase="+phase+", ver="+version+", nt="+negType+") (last 
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) to "+replyTo+" 
data.length="+data.length);
                try
                {
-                       sendPacket(data,replyTo,pn,0);
+                       sendPacket(output,replyTo,pn,0);
                }catch(LocalAddressException e)
                {
                        Logger.error(this, "Tried to send auth packet to local 
address: "+replyTo+" for "+pn);
                }
-       
-               
-    }
+     }
     /*
-     * Send Message2 packet
+     * Send Message2 or 4 packet
      * @param version
      * @param negType
      * @param The packet phase number
@@ -544,11 +634,13 @@
      * @param The peer to which we need to send the packet
      */

-    private void sendMessage2or4Packet(int version,int negType,int 
phase,byte[] data,PeerNode pn,Peer replyTo)
+    private void sendMessage3and4Packet(int version,int negType,int 
phase,byte[] data,PeerNode pn,Peer replyTo)
     {
                 long now = System.currentTimeMillis();
                 long delta = now - pn.lastSentPacketTime();
                 byte[] output = new byte[data.length+3];
+                if(data.length > node.usm.getMaxPacketSize())
+                    throw new IllegalStateException("Packet length too long");
                 output[0] = (byte) version;
                 output[1] = (byte) negType;
                 output[2] = (byte) phase;
@@ -556,77 +648,1592 @@
                 if(logMINOR) Logger.minor(this, "Sending auth packet for 
"+pn.getPeer()+" (phase="+phase+", ver="+version+", nt="+negType+") (last 
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) to "+replyTo+" 
data.length="+data.length);
                 try
                 {
-                        sendPacket(data,replyTo,pn,0);
+                        sendPacket(output,replyTo,pn,0);
                 }catch(LocalAddressException e)
                 {
                         Logger.error(this, "Tried to send auth packet to local 
address: "+replyTo+" for "+pn);
                 }
+                                  
+    }
+     

+    /**
+     * Send a signed DH completion message.
+     * Format:
+     * IV
+     * Signature on { My exponential, his exponential, data }
+     * Data
+     * @param phase The packet phase number. Either 2 or 3.
+     * @param cipher The negotiated cipher.
+     * @param pn The PeerNode which we are talking to.
+     * @param replyTo The Peer to which to send the packet (not necessarily 
the same
+     * as the one on pn as the IP may have changed).
+     */
+    private void sendSignedDHCompletion(int phase, BlockCipher cipher, 
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) {
+        PCFBMode pcfb = PCFBMode.create(cipher);
+        byte[] iv = new byte[pcfb.lengthIV()];
+        
+        byte[] myRef = crypto.myCompressedSetupRef();
+        byte[] data = new byte[myRef.length + 8];
+        System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0, 8);
+        System.arraycopy(myRef, 0, data, 8, myRef.length);
+        
+        byte[] myExp = ctx.getOurExponential().toByteArray();
+        byte[] hisExp = ctx.getHisExponential().toByteArray();
+        
+        MessageDigest md = SHA256.getMessageDigest();
+        md.update(myExp);
+        md.update(hisExp);
+        md.update(data);
+        byte[] hash = md.digest();
+        
+        DSASignature sig = crypto.sign(hash);
+        
+        byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+        
+        Logger.minor(this, "Sending DH completion: "+pn+" hash 
"+HexUtil.bytesToHex(hash)+" r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+        
+        int outputLength = iv.length + data.length + r.length + s.length + 2;
+        
+        byte[] output = new byte[outputLength];
+        
+        System.arraycopy(iv, 0, output, 0, iv.length);
+        int count = iv.length;
+        if(r.length > 255 || s.length > 255)
+               throw new IllegalStateException("R or S is too long: 
r.length="+r.length+" s.length="+s.length);
+        output[count++] = (byte) r.length;
+        System.arraycopy(r, 0, output, count, r.length);
+        count += r.length;
+        output[count++] = (byte) s.length;
+        System.arraycopy(s, 0, output, count, s.length);
+        count += s.length;
+        System.arraycopy(data, 0, output, count, data.length);
+        
+        pcfb.blockEncipher(output, 0, output.length);
+        
+        sendAuthPacket(1, 1, phase, output, pn, replyTo);
+    }

+    /**
+     * Send a first-half (phase 0 or 1) DH negotiation packet to the node.
+     * @param phase The phase of the message to be sent (0 or 1).
+     * @param negType The negotiation type.
+     * @param integer Our exponential
+     * @param replyTo The peer to reply to
+     */
+    private void sendFirstHalfDHPacket(int phase, int negType, 
NativeBigInteger integer, PeerNode pn, Peer replyTo) {
+        long time1 = System.currentTimeMillis();
+        if(logMINOR) Logger.minor(this, "Sending ("+phase+") 
"+integer.toHexString()+" to "+pn.getPeer());
+        byte[] data = integer.toByteArray();
+        int targetLength = DiffieHellman.modulusLengthInBytes();
+        if(data.length != targetLength) {
+            byte[] newData = new byte[targetLength];
+            if((data.length == targetLength+1) && (data[0] == 0)) {
+                // Sign bit
+                System.arraycopy(data, 1, newData, 0, targetLength);
+            } else if(data.length < targetLength) {
+                System.arraycopy(data, 0, newData, targetLength-data.length, 
data.length);
+            } else {
+                throw new IllegalStateException("Too long!");
+            }
+            data = newData;
+        }
+        if(logMINOR) Logger.minor(this, "Processed: 
"+HexUtil.bytesToHex(data));
+        long time2 = System.currentTimeMillis();
+        if((time2 - time1) > 200) {
+          Logger.error(this, "sendFirstHalfDHPacket: time2 is more than 200ms 
after time1 ("+(time2 - time1)+") working on "+replyTo+" of 
"+pn.userToString());
+        }
+        sendAuthPacket(1, negType, phase, data, pn, replyTo);
+        long time3 = System.currentTimeMillis();
+        if((time3 - time2) > 500) {
+          Logger.error(this, "sendFirstHalfDHPacket:sendAuthPacket() time3 is 
more than half a second after time2 ("+(time3 - time2)+") working on 
"+replyTo+" of "+pn.userToString());
+        }
+        if((time3 - time1) > 500) {
+          Logger.error(this, "sendFirstHalfDHPacket: time3 is more than half a 
second after time1 ("+(time3 - time1)+") working on "+replyTo+" of 
"+pn.userToString());
+        }
     }

-    /*
-     * Signature of the message using DSA
-     * Information on what are the encryption and authentication algorithms 
used is sent in
-     * message2 via grpInfo
-     * @param Concatenation of the data to be signed and verified
-     */ 
-    private void signatureMessage(byte[] data)
-    {
-               DSAGroup g = Global.DSAgroupBigA;
-                DummyRandomSource y = new DummyRandomSource();
-               //DSA Private key:Sign the message
-                DSAPrivateKey pk=new DSAPrivateKey(g, y);
-               //DSA Public key:Verify the message
-                DSAPublicKey pub=new DSAPublicKey(g, pk);
-                while(true)
-               {
-                        long totalRSize = 0;
-                        long totalSSize = 0;
-                        long totalPubKeySize = 0;
-                        long totalPrivKeySize = 0;
-                        int maxPrivKeySize = 0;
-                        int maxPubKeySize = 0;
-                        int maxRSize = 0;
-                        int maxSSize = 0;
-                        int totalRUnsignedBitSize = 0;
-                        int maxRUnsignedBitSize = 0;
-                        Random r = new Random(y.nextLong());
-                       byte[] msg=new byte[32];
-                       for(int i=0;i<1000;i++) {
-                                r.nextBytes(msg);
-                                BigInteger m = new BigInteger(1, msg);
-                               BigInteger d = new BigInteger(1,data);
-                                pk = new DSAPrivateKey(g, r);
-                                int privKeySize = pk.asBytes().length;
-                                totalPrivKeySize += privKeySize;
-                                if(privKeySize > maxPrivKeySize) 
maxPrivKeySize = privKeySize;
-                                pub = new DSAPublicKey(g, pk);
-                                int pubKeySize = pub.asBytes().length;
-                                totalPubKeySize += pubKeySize;
-                                if(pubKeySize > maxPubKeySize) maxPubKeySize = 
pubKeySize;
-                               /*Signature of payload data using the private 
key belonging                                      to the initiator or 
responder*/ 
-                                sig = sign(g,pk,m,d,y);
-                                if(!verify(pub, sig, m, false)) {
-                                        System.err.println("Failed to 
verify!");
-                                }
-                                int rSize = sig.getR().bitLength();
-                                rSize = (rSize / 8) + (rSize % 8 == 0 ? 0 : 1);
-                                totalRSize += rSize;
-                                if(rSize > maxRSize) maxRSize = rSize;
-                                int rUnsignedBitSize = sig.getR().bitLength();
-                                totalRUnsignedBitSize += rUnsignedBitSize;
-                                maxRUnsignedBitSize = 
Math.max(maxRUnsignedBitSize, rUnsignedBitSize);
-                                int sSize = sig.getS().bitLength();
-                                sSize = sSize / 8 +  (sSize % 8 == 0 ? 0 : 1);
-                               totalSSize += sSize;
-                                if(sSize > maxSSize) maxSSize = sSize;
+    /**
+     * Send an auth packet.
+     */
+    private void sendAuthPacket(int version, int negType, int phase, byte[] 
data, PeerNode pn, Peer replyTo) {
+        long now = System.currentTimeMillis();
+        long delta = now - pn.lastSentPacketTime();
+        byte[] output = new byte[data.length+3];
+        output[0] = (byte) version;
+        output[1] = (byte) negType;
+        output[2] = (byte) phase;
+        System.arraycopy(data, 0, output, 3, data.length);
+        if(logMINOR) Logger.minor(this, "Sending auth packet for 
"+pn.getPeer()+" (phase="+phase+", ver="+version+", nt="+negType+") (last 
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) to "+replyTo+" 
data.length="+data.length);
+        sendAuthPacket(output, pn, replyTo);
+    }
+
+    /**
+     * Send an auth packet (we have constructed the payload, now hash it, pad 
it, encrypt it).
+     */
+    private void sendAuthPacket(byte[] output, PeerNode pn, Peer replyTo) {
+        int length = output.length;
+        if(length > sock.getMaxPacketSize()) {
+            throw new IllegalStateException("Cannot send auth packet: too 
long: "+length);
+        }
+        BlockCipher cipher = pn.outgoingSetupCipher;
+        if(logMINOR) Logger.minor(this, "Outgoing cipher: 
"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+        PCFBMode pcfb = PCFBMode.create(cipher);
+        int paddingLength = node.fastWeakRandom.nextInt(100);
+        byte[] iv = new byte[pcfb.lengthIV()];
+        node.random.nextBytes(iv);
+        byte[] hash = SHA256.digest(output);
+        if(logMINOR) Logger.minor(this, "Data hash: 
"+HexUtil.bytesToHex(hash));
+        byte[] data = new byte[iv.length + hash.length + 2 /* length */ + 
output.length + paddingLength];
+        pcfb.reset(iv);
+        System.arraycopy(iv, 0, data, 0, iv.length);
+        pcfb.blockEncipher(hash, 0, hash.length);
+        System.arraycopy(hash, 0, data, iv.length, hash.length);
+        if(logMINOR) Logger.minor(this, "Payload length: "+length);
+        data[hash.length+iv.length] = (byte) pcfb.encipher((byte)(length>>8));
+        data[hash.length+iv.length+1] = (byte) pcfb.encipher((byte)length);
+        pcfb.blockEncipher(output, 0, output.length);
+        System.arraycopy(output, 0, data, hash.length+iv.length+2, 
output.length);
+        byte[] random = new byte[paddingLength];
+        node.fastWeakRandom.nextBytes(random);
+        System.arraycopy(random, 0, data, 
hash.length+iv.length+2+output.length, random.length);
+        try {
+               sendPacket(data, replyTo, pn, 0);
+               } catch (LocalAddressException e) {
+                       Logger.error(this, "Tried to send auth packet to local 
address: "+replyTo+" for "+pn+" - maybe you should set allowLocalAddresses for 
this peer??");
+               }
+               if(logMINOR) Logger.minor(this, "Sending auth packet (long) to 
"+replyTo+" - size "+data.length+" data length: "+output.length);
+     }
+
+    private void sendPacket(byte[] data, Peer replyTo, PeerNode pn, int 
alreadyReportedBytes) throws LocalAddressException {
+       if(pn.isIgnoreSource()) {
+               Peer p = pn.getPeer();
+               if(p != null) replyTo = p;
+       }
+       sock.sendPacket(data, replyTo, pn.allowLocalAddresses());
+       pn.reportOutgoingBytes(data.length);
+       node.outputThrottle.forceGrab(data.length - alreadyReportedBytes);
+       }
+
+    /**
+     * Process a stage 2 or stage 3 auth packet.
+     * Send a signed DH completion message.
+     * Format:
+     * IV
+     * Signature on { My exponential, his exponential, data }
+     * Data
+     * 
+     * May decrypt in place.
+     */
+    private DiffieHellmanContext processSignedDHTwoOrThree(int phase, byte[] 
payload, PeerNode pn, Peer replyTo, boolean sendCompletion) {
+       if(logMINOR) Logger.minor(this, "Handling signed stage "+phase+" auth 
packet");
+       // Get context, cipher, IV
+        DiffieHellmanContext ctx = (DiffieHellmanContext) 
pn.getKeyAgreementSchemeContext();
+        if((ctx == null) || !ctx.canGetCipher()) {
+            if(shouldLogErrorInHandshake()) {
+                Logger.error(this, "Cannot get cipher");
+            }
+            return null;
+        }
+        byte[] encKey = ctx.getKey();
+        BlockCipher cipher = ctx.getCipher();
+        PCFBMode pcfb = PCFBMode.create(cipher);
+        int ivLength = pcfb.lengthIV();
+        if(payload.length-3 < HASH_LENGTH + ivLength + 8) {
+            Logger.error(this, "Too short phase "+phase+" packet from 
"+replyTo+" probably from "+pn);
+            return null;
+        }
+        pcfb.reset(payload, 3); // IV
+        
+        // Decrypt the rest
+        pcfb.blockDecipher(payload, 3, payload.length - 3);
+        
+        int count = 3 + ivLength;
+        
+        // R
+        int rLen = payload[count++] & 0xFF;
+        byte[] rBytes = new byte[rLen];
+        System.arraycopy(payload, count, rBytes, 0, rLen);
+        count += rLen;
+        NativeBigInteger r = new NativeBigInteger(1, rBytes);
+        
+        // S
+        int sLen = payload[count++] & 0xFF;
+        byte[] sBytes = new byte[sLen];
+        System.arraycopy(payload, count, sBytes, 0, sLen);
+        count += sLen;
+        NativeBigInteger s = new NativeBigInteger(1, sBytes);
+        
+        DSASignature sig = new DSASignature(r, s);
+        
+        // Data
+        byte[] data = new byte[payload.length - count];
+        System.arraycopy(payload, count, data, 0, payload.length - count);
+        
+        // Now verify
+        MessageDigest md = SHA256.getMessageDigest();
+        md.update(ctx.getHisExponential().toByteArray());
+        md.update(ctx.getOurExponential().toByteArray());
+        md.update(data);
+        byte[] hash = md.digest();
+        if(!pn.verify(hash, sig)) {
+               Logger.error(this, "Signature verification failed for "+pn+" 
hash "+HexUtil.bytesToHex(hash)+" 
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" 
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+               return null;
+        }
+        
+        // Success!
+        long bootID = Fields.bytesToLong(data);
+        // Send the completion before parsing the data, because this is easiest
+        // Doesn't really matter - if it fails, we get loads of errors 
anyway...
+        // Only downside is that the other side might still think we are 
connected for a while.
+        // But this should be extremely rare.
+        // REDFLAG?
+        // We need to send the completion before the PN sends any packets, 
that's all...
+        if(pn.completedHandshake(bootID, data, 8, data.length-8, cipher, 
encKey, replyTo, phase == 2)) {
+               if(sendCompletion)
+                       sendSignedDHCompletion(3, ctx.getCipher(), pn, replyTo, 
ctx);
+               pn.maybeSendInitialMessages();
+        } else {
+               Logger.error(this, "Handshake not completed");
+        }
+        return ctx;
+    }
+
+    /**
+     * Should we log an error for an event that could easily be
+     * caused by a handshake across a restart boundary?
+     */
+    private boolean shouldLogErrorInHandshake() {
+        long now = System.currentTimeMillis();
+        if(now - node.startupTime < Node.HANDSHAKE_TIMEOUT*2)
+            return false;
+        return true;
+    }
+
+    /**
+     * Process a phase-0 or phase-1 Diffie-Hellman packet.
+     * @return a DiffieHellmanContext if we succeeded, otherwise null.
+     */
+    private DiffieHellmanContext processDHZeroOrOne(int phase, byte[] payload, 
PeerNode pn) {
+        
+        if((phase == 0) && pn.hasLiveHandshake(System.currentTimeMillis())) {
+            if(logMINOR) Logger.minor(this, "Rejecting phase "+phase+" 
handshake on "+pn+" - already running one");
+            return null;
+        }
+        
+        // First, get the BigInteger
+        int length = DiffieHellman.modulusLengthInBytes();
+        if(payload.length < length + 3) {
+            Logger.error(this, "Packet too short: "+payload.length+" after 
decryption in DH("+phase+"), should be "+(length + 3));
+            return null;
+        }
+        byte[] aAsBytes = new byte[length];
+        System.arraycopy(payload, 3, aAsBytes, 0, length);
+        NativeBigInteger a = new NativeBigInteger(1, aAsBytes);
+        DiffieHellmanContext ctx;
+        if(phase == 1) {
+            ctx = (DiffieHellmanContext) pn.getKeyAgreementSchemeContext();
+            if(ctx == null) {
+                if(shouldLogErrorInHandshake())
+                    Logger.error(this, "Could not get context for phase 1 
handshake from "+pn);
+                return null;
+            }
+        } else {
+            ctx = DiffieHellman.generateContext();
+            // Don't calculate the key until we need it
+            pn.setKeyAgreementSchemeContext(ctx);
+        }
+        ctx.setOtherSideExponential(a);
+        if(logMINOR) Logger.minor(this, "His exponential: "+a.toHexString());
+        // REDFLAG: This is of course easily DoS'ed if you know the node.
+        // We will fix this by means of JFKi.
+        return ctx;
+    }
+
+    /**
+     * Try to process an incoming packet with a given PeerNode.
+     * We need to know where the packet has come from in order to
+     * decrypt and authenticate it.
+     */
+    private boolean tryProcess(byte[] buf, int offset, int length, KeyTracker 
tracker) {
+        // Need to be able to call with tracker == null to simplify code above
+        if(tracker == null) {
+            if(Logger.shouldLog(Logger.DEBUG, this)) Logger.debug(this, 
"Tracker == null");
+            return false;
+        }
+        if(logMINOR) Logger.minor(this,"Entering tryProcess: 
"+Fields.hashCode(buf)+ ',' +offset+ ',' +length+ ',' +tracker);
+        /**
+         * E_pcbc_session(H(seq+random+data)) E_pcfb_session(seq+random+data)
+         * 
+         * So first two blocks are the hash, PCBC encoded (meaning the
+         * first one is ECB, and the second one is ECB XORed with the 
+         * ciphertext and plaintext of the first block).
+         */
+        BlockCipher sessionCipher = tracker.sessionCipher;
+        if(sessionCipher == null) {
+               if(logMINOR) Logger.minor(this, "No cipher");
+            return false;
+        }
+        if(logMINOR) Logger.minor(this, "Decrypting with 
"+HexUtil.bytesToHex(tracker.sessionKey));
+        int blockSize = sessionCipher.getBlockSize() >> 3;
+        if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
+            throw new IllegalStateException("Block size must be equal to key 
size");
+        
+        if(HASH_LENGTH != blockSize)
+            throw new IllegalStateException("Block size must be digest 
length!");
+
+        byte[] packetHash = new byte[HASH_LENGTH];
+        System.arraycopy(buf, offset, packetHash, 0, HASH_LENGTH);
+        
+        // Decrypt the sequence number and see if it's plausible
+        // Verify the hash later
+        
+        PCFBMode pcfb;
+        pcfb = PCFBMode.create(sessionCipher);
+        // Set IV to the hash, after it is encrypted
+        pcfb.reset(packetHash);
+        //Logger.minor(this,"IV:\n"+HexUtil.bytesToHex(packetHash));
+        
+        byte[] seqBuf = new byte[4];
+        System.arraycopy(buf, offset+HASH_LENGTH, seqBuf, 0, 4);
+        //Logger.minor(this, "Encypted sequence number: 
"+HexUtil.bytesToHex(seqBuf));
+        pcfb.blockDecipher(seqBuf, 0, 4);
+        //Logger.minor(this, "Decrypted sequence number: 
"+HexUtil.bytesToHex(seqBuf));
+        
+        int seqNumber = ((((((seqBuf[0] & 0xff) << 8)
+                + (seqBuf[1] & 0xff)) << 8) + 
+                (seqBuf[2] & 0xff)) << 8) +
+                (seqBuf[3] & 0xff);
+
+        int targetSeqNumber = tracker.highestReceivedIncomingSeqNumber();
+        if(logMINOR) Logger.minor(this, "Seqno: "+seqNumber+" (highest seen 
"+targetSeqNumber+") receiving packet from "+tracker.pn.getPeer());
+
+        if(seqNumber == -1) {
+            // Ack/resendreq-only packet
+        } else {
+            // Now is it credible?
+            // As long as it's within +/- 256, this is valid.
+            if((targetSeqNumber != -1) && (Math.abs(targetSeqNumber - 
seqNumber) > MAX_PACKETS_IN_FLIGHT))
+                return false;
+        }
+        if(logMINOR) Logger.minor(this, "Sequence number received: 
"+seqNumber);
+        
+        // Plausible, so lets decrypt the rest of the data
+        
+        byte[] plaintext = new byte[length-(4+HASH_LENGTH)];
+        System.arraycopy(buf, offset+HASH_LENGTH+4, plaintext, 0, 
length-(HASH_LENGTH+4));
+        
+        pcfb.blockDecipher(plaintext, 0, length-(HASH_LENGTH+4));
+        
+        //Logger.minor(this, "Plaintext:\n"+HexUtil.bytesToHex(plaintext));
+        
+        MessageDigest md = SHA256.getMessageDigest();
+        md.update(seqBuf);
+        md.update(plaintext);
+        byte[] realHash = md.digest();
+        SHA256.returnMessageDigest(md); md = null;
+
+        // Now decrypt the original hash
+        
+        byte[] temp = new byte[blockSize];
+        System.arraycopy(buf, offset, temp, 0, blockSize);
+        sessionCipher.decipher(temp, temp);
+        System.arraycopy(temp, 0, packetHash, 0, blockSize);
+        
+        // Check the hash
+        if(!Arrays.equals(packetHash, realHash)) {
+            if(logMINOR) Logger.minor(this, "Packet possibly from "+tracker+" 
hash does not match:\npacketHash="+
+                    HexUtil.bytesToHex(packetHash)+"\n  
realHash="+HexUtil.bytesToHex(realHash)+" ("+(length-HASH_LENGTH)+" bytes 
payload)");
+            return false;
+        }
+        
+        // Verify
+        tracker.pn.verified(tracker);
+        
+        for(int i=0;i<HASH_LENGTH;i++) {
+            packetHash[i] ^= buf[offset+i];
+        }
+        if(logMINOR) Logger.minor(this, "Contributing entropy");
+        node.random.acceptEntropyBytes(myPacketDataSource, packetHash, 0, 
HASH_LENGTH, 0.5);
+        if(logMINOR) Logger.minor(this, "Contributed entropy");
+        
+        // Lots more to do yet!
+        processDecryptedData(plaintext, seqNumber, tracker, length - 
plaintext.length);
+        tracker.pn.reportIncomingBytes(length);
+        return true;
+    }
+
+    /**
+     * Process an incoming packet, once it has been decrypted.
+     * @param decrypted The packet's contents.
+     * @param seqNumber The detected sequence number of the packet.
+     * @param tracker The KeyTracker responsible for the key used to encrypt 
the packet.
+     */
+    private void processDecryptedData(byte[] decrypted, int seqNumber, 
KeyTracker tracker, int overhead) {
+        /**
+         * Decoded format:
+         * 1 byte - version number (0)
+         * 1 byte - number of acknowledgements
+         * Acknowledgements:
+         * 1 byte - ack (+ve integer, subtract from seq-1) to get seq# to ack
+         * 
+         * 1 byte - number of explicit retransmit requests
+         * Explicit retransmit requests:
+         * 1 byte - retransmit request (+ve integer, subtract from seq-1) to 
get seq# to resend
+         * 
+         * 1 byte - number of packets forgotten
+         * Forgotten packets:
+         * 1 byte - forgotten packet seq# (+ve integer, subtract from seq-1) 
to get seq# lost
+         * 
+         * 1 byte - number of messages
+         * 2 bytes - message length
+         * first message
+         * 2 bytes - second message length
+         * second message
+         * ...
+         * last message
+         * anything beyond this point is padding, to be ignored
+         */ 
+        
+        // Use ptr to simplify code
+        int ptr = RANDOM_BYTES_LENGTH;
+        
+        int version = decrypted[ptr++];
+        if(ptr > decrypted.length) {
+            Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            return;
+        }
+        if(version != 0) {
+            Logger.error(this,"Packet from "+tracker+" decrypted but invalid 
version: "+version);
+            return;
+        }
+
+        /** Highest sequence number sent - not the same as this packet's seq 
number */
+        int realSeqNumber = seqNumber;
+        
+        if(seqNumber == -1) {
+            if(ptr+4 > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+                return;
+            }
+            realSeqNumber =
+                ((((((decrypted[ptr+0] & 0xff) << 8) + (decrypted[ptr+1] & 
0xff)) << 8) + 
+                        (decrypted[ptr+2] & 0xff)) << 8) + (decrypted[ptr+3] & 
0xff);
+            ptr+=4;
+        } else {
+            if(ptr > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+                return;
+            }
+            realSeqNumber = seqNumber + (decrypted[ptr++] & 0xff);
+        }
+        
+        //Logger.minor(this, "Reference seq number: 
"+HexUtil.bytesToHex(decrypted, ptr, 4));
+        
+        if(ptr+4 > decrypted.length) {
+            Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            return;
+        }
+        int referenceSeqNumber = 
+            ((((((decrypted[ptr+0] & 0xff) << 8) + (decrypted[ptr+1] & 0xff)) 
<< 8) + 
+                    (decrypted[ptr+2] & 0xff)) << 8) + (decrypted[ptr+3] & 
0xff);
+        ptr+=4;
+        
+        if(logMINOR) Logger.minor(this, "Reference sequence number: 
"+referenceSeqNumber);
+
+        int ackCount = decrypted[ptr++] & 0xff;
+        if(logMINOR) Logger.minor(this, "Acks: "+ackCount);
+
+        int[] acks = new int[ackCount];
+        for(int i=0;i<ackCount;i++) {
+            int offset = decrypted[ptr++] & 0xff;
+            if(ptr > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+                return;
+            }
+            acks[i] = referenceSeqNumber - offset;
+        }
+        
+        tracker.acknowledgedPackets(acks);
+        
+        int retransmitCount = decrypted[ptr++] & 0xff;
+        if(logMINOR) Logger.minor(this, "Retransmit requests: 
"+retransmitCount);
+        
+        for(int i=0;i<retransmitCount;i++) {
+            int offset = decrypted[ptr++] & 0xff;
+            if(ptr > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            }
+            int realSeqNo = referenceSeqNumber - offset;
+            if(logMINOR) Logger.minor(this, "RetransmitRequest: "+realSeqNo);
+            tracker.resendPacket(realSeqNo);
+        }
+
+        int ackRequestsCount = decrypted[ptr++] & 0xff;
+        if(logMINOR) Logger.minor(this, "Ack requests: "+ackRequestsCount);
+        
+        // These two are relative to our outgoing packet number
+        // Because they relate to packets we have sent.
+        for(int i=0;i<ackRequestsCount;i++) {
+            int offset = decrypted[ptr++] & 0xff;
+            if(ptr > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            }
+            int realSeqNo = realSeqNumber - offset;
+            if(logMINOR) Logger.minor(this, "AckRequest: "+realSeqNo);
+            tracker.receivedAckRequest(realSeqNo);
+        }
+        
+        int forgottenCount = decrypted[ptr++] & 0xff;
+        if(logMINOR) Logger.minor(this, "Forgotten packets: "+forgottenCount);
+        
+        for(int i=0;i<forgottenCount;i++) {
+            int offset = decrypted[ptr++] & 0xff;
+            if(ptr > decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            }
+            int realSeqNo = realSeqNumber - offset;
+            tracker.destForgotPacket(realSeqNo);
+        }
+
+        if(seqNumber == -1) {
+               if(logMINOR) Logger.minor(this, "Returning because seqno = 
"+seqNumber);
+               return;
+        }
+        // No sequence number == no messages
+
+        if((seqNumber != -1) && tracker.alreadyReceived(seqNumber)) {
+            tracker.queueAck(seqNumber);
+                       tracker.pn.receivedPacket(false);
+            Logger.error(this, "Received packet twice ("+seqNumber+") from 
"+tracker.pn.getPeer()+": "+seqNumber+" ("+TimeUtil.formatTime((long) 
tracker.pn.pingAverage.currentValue(), 2, true)+" ping avg)");
+            return;
+        }
+        
+        tracker.receivedPacket(seqNumber);
+        
+        int messages = decrypted[ptr++] & 0xff;
+        
+        overhead += ptr;
+        
+        for(int i=0;i<messages;i++) {
+            if(ptr+1 >= decrypted.length) {
+                Logger.error(this, "Packet not long enough at byte "+ptr+" on 
"+tracker);
+            }
+            int length = ((decrypted[ptr++] & 0xff) << 8) +
+               (decrypted[ptr++] & 0xff);
+            if(length > decrypted.length - ptr) {
+                Logger.error(this, "Message longer than remaining space: 
"+length);
+                return;
+            }
+            if(logMINOR) Logger.minor(this, "Message "+i+" length "+length+", 
hash code: "+Fields.hashCode(decrypted, ptr, length));
+            Message m = usm.decodeSingleMessage(decrypted, ptr, length, 
tracker.pn, 1 + (overhead / messages));
+            ptr+=length;
+            if(m != null) {
+                //Logger.minor(this, "Dispatching packet: "+m);
+                usm.checkFilters(m, sock);
+            }
+        }
+        if(logMINOR) Logger.minor(this, "Done");
+    }
+
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#processOutgoingOrRequeue(freenet.node.MessageItem[],
 freenet.node.PeerNode, boolean, boolean)
+        */
+    public void processOutgoingOrRequeue(MessageItem[] messages, PeerNode pn, 
boolean neverWaitForPacketNumber, boolean dontRequeue) {
+       String requeueLogString = "";
+       if(!dontRequeue) {
+               requeueLogString = ", requeueing";
+       }
+        if(logMINOR) Logger.minor(this, "processOutgoingOrRequeue 
"+messages.length+" messages for "+pn+" ("+neverWaitForPacketNumber+ ')');
+        byte[][] messageData = new byte[messages.length][];
+        int[] alreadyReported = new int[messages.length];
+        MessageItem[] newMsgs = new MessageItem[messages.length];
+        KeyTracker kt = pn.getCurrentKeyTracker();
+        if(kt == null) {
+               Logger.error(this, "Not connected while sending packets: "+pn);
+               return;
+        }
+        int length = 1;
+        length += kt.countAcks() + kt.countAckRequests() + 
kt.countResendRequests();
+        int callbacksCount = 0;
+        int x = 0;
+               String mi_name = null;
+        for(int i=0;i<messageData.length;i++) {
+            MessageItem mi = messages[i];
+               if(logMINOR) Logger.minor(this, "Handling formatted MessageItem 
"+mi+" : "+mi.getData(pn).length);
+                       mi_name = (mi.msg == null ? "(not a Message)" : 
mi.msg.getSpec().getName());
+            if(mi.formatted) {
+                try {
+                    byte[] buf = mi.getData(pn);
+                    kt = pn.getCurrentKeyTracker();
+                    if(kt == null) {
+                        if(logMINOR) Logger.minor(this, "kt = null");
+                        pn.requeueMessageItems(messages, i, messages.length-i, 
false, "kt = null");
+                        return;
+                    }
+                    int packetNumber = 
kt.allocateOutgoingPacketNumberNeverBlock();
+                    this.processOutgoingPreformatted(buf, 0, buf.length, 
pn.getCurrentKeyTracker(), packetNumber, mi.cb, mi.alreadyReportedBytes);
+                    mi.onSent(buf.length + fullHeadersLengthOneMessage);
+                } catch (NotConnectedException e) {
+                    Logger.normal(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString);
+                    // Requeue
+                    if(!dontRequeue) {
+                       pn.requeueMessageItems(messages, 0, x, false, 
"NotConnectedException(1a)");
+                       pn.requeueMessageItems(messages, i, messages.length-i, 
false, "NotConnectedException(1b)");
+                    }
+                    return;
+                } catch (WouldBlockException e) {
+                    if(logMINOR) Logger.minor(this, "Caught "+e+" while 
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                    // Requeue
+                    if(!dontRequeue) {
+                       pn.requeueMessageItems(messages, 0, x, false, 
"WouldBlockException(1a)");
+                       pn.requeueMessageItems(messages, i, messages.length-i, 
false, "WouldBlockException(1b)");
+                    }
+                    return;
+                } catch (KeyChangedException e) {
+                       if(logMINOR) Logger.minor(this, "Caught "+e+" while 
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                    // Requeue
+                    if(!dontRequeue) {
+                       pn.requeueMessageItems(messages, 0, x, false, 
"KeyChangedException(1a)");
+                       pn.requeueMessageItems(messages, i, messages.length-i, 
false, "KeyChangedException(1b)");
+                    }
+                    return;
+                } catch (Throwable e) {
+                    Logger.error(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                    // Requeue
+                    if(!dontRequeue) {
+                       pn.requeueMessageItems(messages, 0, x, false, 
"Throwable(1)");
+                       pn.requeueMessageItems(messages, i, messages.length-i, 
false, "Throwable(1)");
+                    }
+                    return;
+                }
+            } else {
+                byte[] data = mi.getData(pn);
+                messageData[x] = data;
+                if(data.length > sock.getMaxPacketSize()) {
+                    Logger.error(this, "Message exceeds packet size: 
"+messages[i]+" size "+data.length+" message "+mi.msg);
+                    // Will be handled later
+                }
+                newMsgs[x] = mi;
+                alreadyReported[x] = mi.alreadyReportedBytes;
+                x++;
+                if(mi.cb != null) callbacksCount += mi.cb.length;
+                if(logMINOR) Logger.minor(this, "Sending: "+mi+" length 
"+data.length+" cb "+ StringArray.toString(mi.cb));
+                length += (data.length + 2);
+            }
+        }
+        if(x != messageData.length) {
+            byte[][] newMessageData = new byte[x][];
+            System.arraycopy(messageData, 0, newMessageData, 0, x);
+            messageData = newMessageData;
+            messages = newMsgs;
+            newMsgs = new MessageItem[x];
+            System.arraycopy(messages, 0, newMsgs, 0, x);
+            messages = newMsgs;
+        }
+        AsyncMessageCallback callbacks[] = new 
AsyncMessageCallback[callbacksCount];
+        x=0;
+        int alreadyReportedBytes = 0;
+        for(int i=0;i<messages.length;i++) {
+            if(messages[i].formatted) continue;
+            if(messages[i].cb != null) {
+               alreadyReportedBytes += messages[i].alreadyReportedBytes;
+                System.arraycopy(messages[i].cb, 0, callbacks, x, 
messages[i].cb.length);
+                x += messages[i].cb.length;
+            }
+        }
+        if(x != callbacksCount) throw new IllegalStateException();
+        
+        if((length + HEADERS_LENGTH_MINIMUM < sock.getMaxPacketSize()) &&
+                (messageData.length < 256)) {
+                       mi_name = null;
+            try {
+                innerProcessOutgoing(messageData, 0, messageData.length, 
length, pn, neverWaitForPacketNumber, callbacks, alreadyReportedBytes);
+                for(int i=0;i<messageData.length;i++) {
+                       MessageItem mi = newMsgs[i];
+                                       mi_name = (mi.msg == null ? "(not a 
Message)" : mi.msg.getSpec().getName());
+                                       mi.onSent(messageData[i].length + 2 + 
(fullHeadersLengthMinimum / messageData.length));
+                }
+            } catch (NotConnectedException e) {
+                Logger.normal(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString);
+                // Requeue
+                if(!dontRequeue)
+                       pn.requeueMessageItems(messages, 0, messages.length, 
false, "NotConnectedException(2)");
+                return;
+            } catch (WouldBlockException e) {
+               if(logMINOR) Logger.minor(this, "Caught "+e+" while sending 
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                // Requeue
+                if(!dontRequeue)
+                       pn.requeueMessageItems(messages, 0, messages.length, 
false, "WouldBlockException(2)");
+                return;
+            } catch (Throwable e) {
+                Logger.error(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                // Requeue
+                if(!dontRequeue)
+                       pn.requeueMessageItems(messages, 0, messages.length, 
false, "Throwable(2)");
+                return;
+                
+            }
+        } else {
+                       if(!dontRequeue) {
+                               requeueLogString = ", requeueing remaining 
messages";
+                       }
+            length = 1;
+            length += kt.countAcks() + kt.countAckRequests() + 
kt.countResendRequests();
+            int count = 0;
+            int lastIndex = 0;
+            alreadyReportedBytes = 0;
+            for(int i=0;i<=messageData.length;i++) {
+                int thisLength;
+                if(i == messages.length) thisLength = 0;
+                else thisLength = (messageData[i].length + 2);
+                int newLength = length + thisLength;
+                count++;
+                if((newLength + HEADERS_LENGTH_MINIMUM > 
sock.getMaxPacketSize()) || (count > 255) || (i == messages.length)) {
+                    // lastIndex up to the message right before this one
+                    // e.g. lastIndex = 0, i = 1, we just send message 0
+                    if(lastIndex != i) {
+                                               mi_name = null;
+                        try {
+                            innerProcessOutgoing(messageData, lastIndex, 
i-lastIndex, length, pn, neverWaitForPacketNumber, callbacks, 
alreadyReportedBytes);
+                            for(int j=lastIndex;j<i;j++) {
+                               MessageItem mi = newMsgs[j];
+                                                               mi_name = 
(mi.msg == null ? "(not a Message)" : mi.msg.getSpec().getName());
+                                                               
mi.onSent(messageData[j].length + 2 + (fullHeadersLengthMinimum / 
(i-lastIndex)));
+                            }
+                        } catch (NotConnectedException e) {
+                            Logger.normal(this, "Caught "+e+" while sending 
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString);
+                            // Requeue
+                            if(!dontRequeue)
+                               pn.requeueMessageItems(messages, lastIndex, 
messages.length - lastIndex, false, "NotConnectedException(3)");
+                            return;
+                        } catch (WouldBlockException e) {
+                               if(logMINOR) Logger.minor(this, "Caught "+e+" 
while sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                            // Requeue
+                            if(!dontRequeue)
+                               pn.requeueMessageItems(messages, lastIndex, 
messages.length - lastIndex, false, "WouldBlockException(3)");
+                            return;
+                        } catch (Throwable e) {
+                            Logger.error(this, "Caught "+e+" while sending 
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+                            // Requeue
+                            if(!dontRequeue)
+                               pn.requeueMessageItems(messages, lastIndex, 
messages.length - lastIndex, false, "Throwable(3)");
+                            return;
                         }
-             }
-   }   
+                    }
+                    lastIndex = i;
+                    if(i != messageData.length)
+                        length = 1 + (messageData[i].length + 2);
+                    count = 0;
+                } else {
+                       length = newLength;
+                       alreadyReportedBytes += alreadyReported[i];
+                }
+            }
+        }
+    }
+    
+    /**
+     * Send some messages.
+     * @param messageData An array block of messages.
+     * @param start Index to start reading the array.
+     * @param length Number of messages to read.
+     * @param bufferLength Size of the buffer to write into.
+     * @param pn Node to send the messages to.
+     * @throws PacketSequenceException 
+     */
+    private void innerProcessOutgoing(byte[][] messageData, int start, int 
length, int bufferLength, 
+               PeerNode pn, boolean neverWaitForPacketNumber, 
AsyncMessageCallback[] callbacks, int alreadyReportedBytes) throws 
NotConnectedException, WouldBlockException, PacketSequenceException {
+       if(logMINOR) Logger.minor(this, "innerProcessOutgoing(...,"+start+ ',' 
+length+ ',' +bufferLength+ ')');
+        byte[] buf = new byte[bufferLength];
+        buf[0] = (byte)length;
+        int loc = 1;
+        for(int i=start;i<(start+length);i++) {
+            byte[] data = messageData[i];
+            int len = data.length;
+            buf[loc++] = (byte)(len >> 8);
+            buf[loc++] = (byte)len;
+            System.arraycopy(data, 0, buf, loc, len);
+            loc += len;
+        }
+        processOutgoingPreformatted(buf, 0, loc, pn, neverWaitForPacketNumber, 
callbacks, alreadyReportedBytes);
+    }
+
+    /* (non-Javadoc)
+        * @see freenet.node.OutgoingPacketMangler#processOutgoing(byte[], int, 
int, freenet.node.KeyTracker, int)
+        */
+    public void processOutgoing(byte[] buf, int offset, int length, KeyTracker 
tracker, int alreadyReportedBytes) throws KeyChangedException, 
NotConnectedException, PacketSequenceException, WouldBlockException {
+        byte[] newBuf = preformat(buf, offset, length);
+        processOutgoingPreformatted(newBuf, 0, newBuf.length, tracker, -1, 
null, alreadyReportedBytes);
+    }
+    
+    /**
+     * Send a packet using the current key. Retry if it fails solely because
+     * the key changes.
+     * @throws PacketSequenceException 
+     */
+    void processOutgoingPreformatted(byte[] buf, int offset, int length, 
PeerNode peer, boolean neverWaitForPacketNumber, AsyncMessageCallback[] 
callbacks, int alreadyReportedBytes) throws NotConnectedException, 
WouldBlockException, PacketSequenceException {
+       KeyTracker last = null;
+        while(true) {
+            try {
+               if(!peer.isConnected())
+                       throw new NotConnectedException();
+                KeyTracker tracker = peer.getCurrentKeyTracker();
+                last = tracker;
+                if(tracker == null) {
+                    Logger.normal(this, "Dropping packet: Not connected to 
"+peer.getPeer()+" yet(2)");
+                    throw new NotConnectedException();
+                }
+                int seqNo = neverWaitForPacketNumber ? 
tracker.allocateOutgoingPacketNumberNeverBlock() :
+                    tracker.allocateOutgoingPacketNumber();
+                processOutgoingPreformatted(buf, offset, length, tracker, 
seqNo, callbacks, alreadyReportedBytes);
+                return;
+            } catch (KeyChangedException e) {
+               Logger.normal(this, "Key changed(2) for "+peer.getPeer());
+               if(last == peer.getCurrentKeyTracker()) {
+                       if(peer.isConnected()) {
+                               Logger.error(this, "Peer is connected, yet 
current tracker is deprecated !!: "+e, e);
+                               throw new NotConnectedException("Peer is 
connected, yet current tracker is deprecated !!: "+e);
+                       }
+               }
+                // Go around again
+            }
+        }
+    }
+
+    byte[] preformat(byte[] buf, int offset, int length) {
+        byte[] newBuf;
+        if(buf != null) {
+            newBuf = new byte[length+3];
+            newBuf[0] = 1;
+            newBuf[1] = (byte)(length >> 8);
+            newBuf[2] = (byte)length;
+            System.arraycopy(buf, offset, newBuf, 3, length);
+        } else {
+            newBuf = new byte[1];
+            newBuf[0] = 0;
+        }
+        return newBuf;
+    }
+    
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#processOutgoingPreformatted(byte[], int, 
int, freenet.node.KeyTracker, int, freenet.node.AsyncMessageCallback[], int)
+        */
+    public void processOutgoingPreformatted(byte[] buf, int offset, int 
length, KeyTracker tracker, int packetNumber, AsyncMessageCallback[] callbacks, 
int alreadyReportedBytes) throws KeyChangedException, NotConnectedException, 
PacketSequenceException, WouldBlockException {
+        if(logMINOR) {
+            String log = 
"processOutgoingPreformatted("+Fields.hashCode(buf)+", "+offset+ ',' +length+ 
',' +tracker+ ',' +packetNumber+ ',';
+            if(callbacks == null) log += "null";
+            else log += (""+callbacks.length+(callbacks.length >= 1 ? 
String.valueOf(callbacks[0]) : ""));
+            Logger.minor(this, log);
+        }
+        if((tracker == null) || (!tracker.pn.isConnected())) {
+            throw new NotConnectedException();
+        }
+        
+        // We do not support forgotten packets at present
+
+        int[] acks, resendRequests, ackRequests, forgotPackets;
+       int seqNumber;
+        /* Locking:
+         * Avoid allocating a packet number, then a long pause due to 
+         * overload, during which many other packets are sent, 
+         * resulting in the other side asking us to resend a packet 
+         * which doesn't exist yet.
+         * => grabbing resend reqs, packet no etc must be as
+         * close together as possible.
+         * 
+         * HOWEVER, tracker.allocateOutgoingPacketNumber can block,
+         * so should not be locked.
+         */
+       
+               if(packetNumber > 0)
+                       seqNumber = packetNumber;
+               else {
+                       if(buf.length == 1)
+                               // Ack/resendreq only packet
+                               seqNumber = -1;
+                       else
+                               seqNumber = 
tracker.allocateOutgoingPacketNumberNeverBlock();
+               }
+        
+               if(logMINOR) Logger.minor(this, "Sequence number (sending): 
"+seqNumber+" ("+packetNumber+") to "+tracker.pn.getPeer());
+        
+        /** The last sent sequence number, so that we can refer to packets
+         * sent after this packet was originally sent (it may be a resend) */
+               int realSeqNumber;
+               
+               int otherSideSeqNumber;
+               
+               synchronized(tracker) {
+               acks = tracker.grabAcks();
+               forgotPackets = tracker.grabForgotten();
+               resendRequests = tracker.grabResendRequests();
+               ackRequests = tracker.grabAckRequests();
+            realSeqNumber = tracker.getLastOutgoingSeqNumber();
+            otherSideSeqNumber = tracker.highestReceivedIncomingSeqNumber();
+            if(logMINOR) Logger.minor(this, "Sending packet to 
"+tracker.pn.getPeer()+", other side max seqno: "+otherSideSeqNumber);
+        }
+        
+        int packetLength = 4 + // seq number
+               RANDOM_BYTES_LENGTH + // random junk
+               1 + // version
+               ((packetNumber == -1) ? 4 : 1) + // highest sent seqno - 4 
bytes if seqno = -1
+               4 + // other side's seqno
+               1 + // number of acks
+               acks.length + // acks
+               1 + // number of resend reqs
+               resendRequests.length + // resend requests
+               1 + // number of ack requests
+               ackRequests.length + // ack requests
+               1 + // no forgotten packets
+               length; // the payload !
+        
+        // Padding
+        // This will do an adequate job of disguising the contents, and a poor 
(but not totally
+        // worthless) job of disguising the traffic. FIXME!!!!!
+        // Ideally we'd mimic the size profile - and the session bytes! - of a 
common protocol.
+        
+        int paddedLen = ((packetLength + 63) / 64) * 64;
+        paddedLen += node.fastWeakRandom.nextInt(64);
+        if(packetLength <= 1280 && paddedLen > 1280) paddedLen = 1280;
+
+        byte[] padding = new byte[paddedLen - packetLength];
+        node.fastWeakRandom.nextBytes(padding);
+        
+        packetLength = paddedLen;
+        
+        if(logMINOR) Logger.minor(this, "Packet length: "+packetLength+" 
("+length+")");
+
+        byte[] plaintext = new byte[packetLength];
+        
+        byte[] randomJunk = new byte[RANDOM_BYTES_LENGTH];
+        
+        int ptr = offset;
+
+        plaintext[ptr++] = (byte)(seqNumber >> 24);
+        plaintext[ptr++] = (byte)(seqNumber >> 16);
+        plaintext[ptr++] = (byte)(seqNumber >> 8);
+        plaintext[ptr++] = (byte)seqNumber;
+        
+        if(logMINOR) Logger.minor(this, "Getting random junk");
+        node.random.nextBytes(randomJunk);
+        System.arraycopy(randomJunk, 0, plaintext, ptr, RANDOM_BYTES_LENGTH);
+        ptr += RANDOM_BYTES_LENGTH;
+        
+        plaintext[ptr++] = 0; // version number
+
+        if(seqNumber == -1) {
+            plaintext[ptr++] = (byte)(realSeqNumber >> 24);
+            plaintext[ptr++] = (byte)(realSeqNumber >> 16);
+            plaintext[ptr++] = (byte)(realSeqNumber >> 8);
+            plaintext[ptr++] = (byte)realSeqNumber;
+        } else {
+            plaintext[ptr++] = (byte)(realSeqNumber - seqNumber);
+        }
+        
+        plaintext[ptr++] = (byte)(otherSideSeqNumber >> 24);
+        plaintext[ptr++] = (byte)(otherSideSeqNumber >> 16);
+        plaintext[ptr++] = (byte)(otherSideSeqNumber >> 8);
+        plaintext[ptr++] = (byte)otherSideSeqNumber;
+        
+        plaintext[ptr++] = (byte) acks.length;
+        for(int i=0;i<acks.length;i++) {
+            int ackSeq = acks[i];
+            if(logMINOR) Logger.minor(this, "Acking "+ackSeq);
+            int offsetSeq = otherSideSeqNumber - ackSeq;
+            if((offsetSeq > 255) || (offsetSeq < 0))
+                throw new PacketSequenceException("bad ack offset "+offsetSeq+
+                        " - seqNumber="+otherSideSeqNumber+", 
ackNumber="+ackSeq+" talking to "+tracker.pn.getPeer());
+            plaintext[ptr++] = (byte)offsetSeq;
+        }
+        
+        plaintext[ptr++] = (byte) resendRequests.length;
+        for(int i=0;i<resendRequests.length;i++) {
+            int reqSeq = resendRequests[i];
+            if(logMINOR) Logger.minor(this, "Resend req: "+reqSeq);
+            int offsetSeq = otherSideSeqNumber - reqSeq;
+            if((offsetSeq > 255) || (offsetSeq < 0))
+                throw new PacketSequenceException("bad resend request offset 
"+offsetSeq+
+                        " - reqSeq="+reqSeq+", 
otherSideSeqNumber="+otherSideSeqNumber+" talking to "+tracker.pn.getPeer());
+            plaintext[ptr++] = (byte)offsetSeq;
+        }
+
+        plaintext[ptr++] = (byte) ackRequests.length;
+        if(logMINOR) Logger.minor(this, "Ackrequests: "+ackRequests.length);
+        for(int i=0;i<ackRequests.length;i++) {
+            int ackReqSeq = ackRequests[i];
+            if(logMINOR) Logger.minor(this, "Ack request "+i+": "+ackReqSeq);
+            // Relative to packetNumber - we are asking them to ack
+            // a packet we sent to them.
+            int offsetSeq = realSeqNumber - ackReqSeq;
+            if((offsetSeq > 255) || (offsetSeq < 0))
+                throw new PacketSequenceException("bad ack requests offset: 
"+offsetSeq+
+                        " - ackReqSeq="+ackReqSeq+", 
packetNumber="+realSeqNumber+" talking to "+tracker.pn.getPeer());
+            plaintext[ptr++] = (byte)offsetSeq;
+        }
+        
+        byte[] forgotOffsets = null;
+        int forgotCount = 0;
+        
+        if(forgotPackets.length > 0) {
+               for(int i=0;i<forgotPackets.length;i++) {
+                       int seq = forgotPackets[i];
+                       if(logMINOR) Logger.minor(this, "Forgot packet "+i+": 
"+seq);
+                       int offsetSeq = realSeqNumber - seq;
+                       if((offsetSeq > 255) || (offsetSeq < 0)) {
+                               if(tracker.isDeprecated()) {
+                                       // Oh well
+                                       Logger.error(this, "Dropping 
forgot-packet notification on deprecated tracker: "+seq+" on "+tracker+" - real 
seq="+realSeqNumber);
+                                       // Ignore it
+                                       continue;
+                               } else {
+                                       Logger.error(this, "bad forgot packet 
offset: "+offsetSeq+
+                                " - forgotSeq="+seq+", 
packetNumber="+realSeqNumber+" talking to "+tracker.pn.getPeer(), new 
Exception("error"));
+                               }
+                       } else {
+                               if(forgotOffsets == null)
+                                       forgotOffsets = new 
byte[forgotPackets.length - i];
+                               forgotOffsets[i] = (byte) offsetSeq;
+                               forgotCount++;
+                               if(forgotCount == 256)
+                                       tracker.requeueForgot(forgotPackets, 
forgotCount, forgotPackets.length - forgotCount);
+                       }
+               }
+        }
+        
+        plaintext[ptr++] = (byte) forgotCount;
+        
+        if(forgotOffsets != null) {
+               System.arraycopy(forgotOffsets, 0, plaintext, ptr, forgotCount);
+               ptr += forgotCount;
+        }
+        
+        System.arraycopy(buf, offset, plaintext, ptr, length);
+        ptr += length;
+        
+        System.arraycopy(padding, 0, plaintext, ptr, padding.length);
+        ptr += padding.length;
+        
+        if(ptr != plaintext.length) {
+               Logger.error(this, "Inconsistent length: "+plaintext.length+" 
buffer but "+(ptr)+" actual");
+               byte[] newBuf = new byte[ptr];
+               System.arraycopy(plaintext, 0, newBuf, 0, ptr);
+               plaintext = newBuf;
+        }
+
+        if(seqNumber != -1) {
+            byte[] saveable = new byte[length];
+            System.arraycopy(buf, offset, saveable, 0, length);
+            tracker.sentPacket(saveable, seqNumber, callbacks);
+        }
+        
+        if(logMINOR) Logger.minor(this, "Sending... "+seqNumber);
+
+        processOutgoingFullyFormatted(plaintext, tracker, 
alreadyReportedBytes);
+        if(logMINOR) Logger.minor(this, "Sent packet "+seqNumber);
+    }
+
+    /**
+     * Encrypt and send a packet.
+     * @param plaintext The packet's plaintext, including all formatting,
+     * including acks and resend requests. Is clobbered.
+     */
+    private void processOutgoingFullyFormatted(byte[] plaintext, KeyTracker 
kt, int alreadyReportedBytes) {
+        BlockCipher sessionCipher = kt.sessionCipher;
+        if(logMINOR) Logger.minor(this, "Encrypting with 
"+HexUtil.bytesToHex(kt.sessionKey));
+        if(sessionCipher == null) {
+            Logger.error(this, "Dropping packet send - have not handshaked 
yet");
+            return;
+        }
+        int blockSize = sessionCipher.getBlockSize() >> 3;
+        if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
+            throw new IllegalStateException("Block size must be half key size: 
blockSize="+
+                    sessionCipher.getBlockSize()+", 
keySize="+sessionCipher.getKeySize());
+        
+        MessageDigest md = SHA256.getMessageDigest();
+        
+        int digestLength = md.getDigestLength();
+        
+        if(digestLength != blockSize)
+            throw new IllegalStateException("Block size must be digest 
length!");
+        
+        byte[] output = new byte[plaintext.length + digestLength];
+        System.arraycopy(plaintext, 0, output, digestLength, plaintext.length);
+        
+        md.update(plaintext);
+        
+        //Logger.minor(this, "Plaintext:\n"+HexUtil.bytesToHex(plaintext));
+        
+        byte[] digestTemp;
+        
+        digestTemp = md.digest();
+        
+        SHA256.returnMessageDigest(md); md = null;
+
+        if(logMINOR) Logger.minor(this, "\nHash:      
"+HexUtil.bytesToHex(digestTemp));
+                
+        // Put encrypted digest in output
+        sessionCipher.encipher(digestTemp, digestTemp);
+        
+        // Now copy it back
+        System.arraycopy(digestTemp, 0, output, 0, digestLength);
+        // Yay, we have an encrypted hash
+
+        if(logMINOR) Logger.minor(this, "\nEncrypted: 
"+HexUtil.bytesToHex(digestTemp)+" ("+plaintext.length+" bytes plaintext)");
+        
+        PCFBMode pcfb = PCFBMode.create(sessionCipher, digestTemp);
+        pcfb.blockEncipher(output, digestLength, plaintext.length);
+        
+        //Logger.minor(this, "Ciphertext:\n"+HexUtil.bytesToHex(output, 
digestLength, plaintext.length));
+        
+        // We have a packet
+        // Send it
+        
+        if(logMINOR) Logger.minor(this,"Sending packet of length 
"+output.length+" (" + Fields.hashCode(output) + " to "+kt.pn);
+        
+        // pn.getPeer() cannot be null
+        try {
+               sendPacket(output, kt.pn.getPeer(), kt.pn, 
alreadyReportedBytes);
+               } catch (LocalAddressException e) {
+                       Logger.error(this, "Tried to send data packet to local 
address: "+kt.pn.getPeer()+" for "+kt.pn.allowLocalAddresses());
+               }
+        kt.pn.sentPacket();
+    }
+
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#sendHandshake(freenet.node.PeerNode)
+        */
+    public void sendHandshake(PeerNode pn) {
+       int negType = pn.bestNegType(this);
+       if(negType == -1) {
+               if(pn.isRoutingCompatible())
+                       Logger.error(this, "Could not negotiate with "+pn+" : 
no common negTypes available!: his negTypes: 
"+StringArray.toString(pn.negTypes)+" my negTypes: 
"+StringArray.toString(supportedNegTypes())+" despite being up to date!!");
+               else
+                       Logger.minor(this, "Could not negotiate with "+pn+" : 
no common negTypes available!: his negTypes: 
"+StringArray.toString(pn.negTypes)+" my negTypes: 
"+StringArray.toString(supportedNegTypes())+" (probably just too old)");
+               return;
+       }
+       if(logMINOR) Logger.minor(this, "Possibly sending handshake to "+pn+" 
negotiation type "+negType);
+        DiffieHellmanContext ctx;
+        Peer[] handshakeIPs;
+        if(!pn.shouldSendHandshake()) {
+               if(logMINOR) Logger.minor(this, "Not sending handshake to 
"+pn.getPeer()+" because pn.shouldSendHandshake() returned false");
+               return;
+        }
+        long firstTime = System.currentTimeMillis();
+        handshakeIPs = pn.getHandshakeIPs();
+        long secondTime = System.currentTimeMillis();
+        if((secondTime - firstTime) > 1000)
+            Logger.error(this, "getHandshakeIPs() took more than a second to 
execute ("+(secondTime - firstTime)+") working on "+pn.userToString());
+        if(handshakeIPs.length == 0) {
+            pn.couldNotSendHandshake();
+            long thirdTime = System.currentTimeMillis();
+            if((thirdTime - secondTime) > 1000)
+                Logger.error(this, "couldNotSendHandshake() (after 
getHandshakeIPs()) took more than a second to execute ("+(thirdTime - 
secondTime)+") working on "+pn.userToString());
+            return;
+        } else {
+            long DHTime1 = System.currentTimeMillis();
+            ctx = DiffieHellman.generateContext();
+            long DHTime2 = System.currentTimeMillis();
+            if((DHTime2 - DHTime1) > 1000)
+                Logger.error(this, "DHTime2 is more than a second after 
DHTime1 ("+(DHTime2 - DHTime1)+") working on "+pn.userToString());
+            pn.setKeyAgreementSchemeContext(ctx);
+            long DHTime3 = System.currentTimeMillis();
+            if((DHTime3 - DHTime2) > 1000)
+                Logger.error(this, "DHTime3 is more than a second after 
DHTime2 ("+(DHTime3 - DHTime2)+") working on "+pn.userToString());
+        }
+        int sentCount = 0;
+        long loopTime1 = System.currentTimeMillis();
+        for(int i=0;i<handshakeIPs.length;i++){
+               Peer peer = handshakeIPs[i];
+               FreenetInetAddress addr = peer.getFreenetAddress();
+               if(!crypto.allowConnection(pn, addr)) {
+                       if(logMINOR)
+                               Logger.minor(this, "Not sending handshake 
packet to "+peer+" for "+pn);
+               }
+               if(peer.getAddress(false) == null) {
+                       if(logMINOR) Logger.minor(this, "Not sending handshake 
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because the DNS lookup failed or 
it's a currently unsupported IPv6 address");
+                       continue;
+               }
+               if((!pn.allowLocalAddresses()) && 
(!peer.isRealInternetAddress(false, false))) {
+                       if(logMINOR) Logger.minor(this, "Not sending handshake 
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because it's not a real Internet 
address and metadata.allowLocalAddresses is not true");
+                       continue;
+               }
+               sendFirstHalfDHPacket(0, negType, ctx.getOurExponential(), pn, 
peer);
+               pn.sentHandshake();
+               sentCount += 1;
+        }
+        long loopTime2 = System.currentTimeMillis();
+        if((loopTime2 - loopTime1) > 1000)
+               Logger.normal(this, "loopTime2 is more than a second after 
loopTime1 ("+(loopTime2 - loopTime1)+") working on "+pn.userToString());
+        if(sentCount==0) {
+            pn.couldNotSendHandshake();
+        }
+    }
+
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#isDisconnected(freenet.io.comm.PeerContext)
+        */
+    public boolean isDisconnected(PeerContext context) {
+        if(context == null) return false;
+        return !((PeerNode)context).isConnected();
+    }
+
+       public void resend(ResendPacketItem item) throws 
PacketSequenceException, WouldBlockException, KeyChangedException, 
NotConnectedException {
+               processOutgoingPreformatted(item.buf, 0, item.buf.length, 
item.kt, item.packetNumber, item.callbacks, 0);
+       }
+
+       public int[] supportedNegTypes() {
+               return new int[] { 1 };
+       }
+
+       public int fullHeadersLengthOneMessage() {
+               return fullHeadersLengthOneMessage;
+       }
+
+       public SocketHandler getSocketHandler() {
+               return sock;
+       }
+
+       public Peer[] getPrimaryIPAddress() {
+               return crypto.detector.getPrimaryPeers();
+       }
+
+       public byte[] getCompressedNoderef() {
+               return crypto.myCompressedFullRef();
+       }
+
+       public boolean alwaysAllowLocalAddresses() {
+               return crypto.config.alwaysAllowLocalAddresses();
+       }
+}
+=======
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import net.i2p.util.NativeBigInteger;
+
+import freenet.crypt.BlockCipher;
+import freenet.crypt.DSASignature;
+import freenet.crypt.DiffieHellman;
+import freenet.crypt.DiffieHellmanContext;
+import freenet.crypt.EntropySource;
+import freenet.crypt.PCFBMode;
+import freenet.crypt.SHA256;
+import freenet.io.comm.*;
+import freenet.io.comm.Peer.LocalAddressException;
+import freenet.support.Fields;
+import freenet.support.HexUtil;
+import freenet.support.Logger;
+import freenet.support.StringArray;
+import freenet.support.TimeUtil;
+import freenet.support.WouldBlockException;
+
+/**
+ * @author amphibian
+ * 
+ * Encodes and decodes packets for FNP.
+ * 
+ * This includes encryption, authentication, and may later
+ * include queueing etc. (that may require some interface
+ * changes in IncomingPacketFilter).
+ */
+public class FNPPacketMangler implements OutgoingPacketMangler, 
IncomingPacketFilter {
+
+       private static boolean logMINOR;
+    final Node node;
+    final NodeCrypto crypto;
+    final MessageCore usm;
+    final PacketSocketHandler sock;
+    final EntropySource fnpTimingSource;
+    final EntropySource myPacketDataSource;
+    private static final int MAX_PACKETS_IN_FLIGHT = 256; 
+    private static final int RANDOM_BYTES_LENGTH = 12;
+    private static final int HASH_LENGTH = 32;
+    /** Minimum headers overhead */
+       private static final int HEADERS_LENGTH_MINIMUM =
+               4 + // sequence number
+       RANDOM_BYTES_LENGTH + // random junk
+       1 + // version
+       1 + // assume seqno != -1; otherwise would be 4
+       4 + // other side's seqno
+       1 + // number of acks
+       0 + // assume no acks
+       1 + // number of resend reqs
+       0 + // assume no resend requests
+       1 + // number of ack requests
+       0 + // assume no ack requests
+       1 + // no forgotten packets
+       HASH_LENGTH + // hash
+       1; // number of messages
+       /** Headers overhead if there is one message and no acks. */
+       static public final int HEADERS_LENGTH_ONE_MESSAGE = 
+               HEADERS_LENGTH_MINIMUM + 2; // 2 bytes = length of message. 
rest is the same.

-   
+       final int fullHeadersLengthMinimum;
+       final int fullHeadersLengthOneMessage;
+       
+    public FNPPacketMangler(Node node, NodeCrypto crypt, PacketSocketHandler 
sock) {
+        this.node = node;
+        this.crypto = crypt;
+        this.usm = node.usm;
+        this.sock = sock;
+        fnpTimingSource = new EntropySource();
+        myPacketDataSource = new EntropySource();
+        fullHeadersLengthMinimum = HEADERS_LENGTH_MINIMUM + 
sock.getHeadersLength();
+        fullHeadersLengthOneMessage = HEADERS_LENGTH_ONE_MESSAGE + 
sock.getHeadersLength();
+               logMINOR = Logger.shouldLog(Logger.MINOR, this);
+    }
+
     /**
+     * Packet format:
+     *
+     * E_session_ecb(
+     *         4 bytes:  sequence number XOR first 4 bytes of node identity
+     *         12 bytes: first 12 bytes of H(data)
+     *         )
+     * E_session_ecb(
+     *         16 bytes: bytes 12-28 of H(data)
+     *         ) XOR previous ciphertext XOR previous plaintext
+     * 4 bytes: bytes 28-32 of H(data) XOR bytes 0-4 of H(data)
+     * E_session_pcfb(data) // IV = first 32 bytes of packet
+     * 
+     */
+    
+       /**
+        * Decrypt and authenticate packet.
+        * Then feed it to USM.checkFilters.
+        * Packets generated should have a PeerNode on them.
+        * Note that the buffer can be modified by this method.
+        */
+    public void process(byte[] buf, int offset, int length, Peer peer) {
+        node.random.acceptTimerEntropy(fnpTimingSource, 0.25);
+               logMINOR = Logger.shouldLog(Logger.MINOR, this);
+               if(logMINOR) Logger.minor(this, "Packet length "+length+" from 
"+peer);
+
+        /**
+         * Look up the Peer.
+         * If we know it, check the packet with that key.
+         * Otherwise try all of them (on the theory that nodes 
+         * occasionally change their IP addresses).
+         */
+        PeerNode opn = node.peers.getByPeer(peer);
+        if(opn != null && opn.getOutgoingMangler() != this) {
+               Logger.error(this, "Apparently contacted by "+opn+") on "+this);
+               opn = null;
+        }
+        PeerNode pn;
+        
+        if(opn != null) {
+            if(logMINOR) Logger.minor(this, "Trying exact match");
+            if(length > HEADERS_LENGTH_MINIMUM) {
+                if(tryProcess(buf, offset, length, 
opn.getCurrentKeyTracker())) return;
+                // Try with old key
+                if(tryProcess(buf, offset, length, 
opn.getPreviousKeyTracker())) return;
+                // Try with unverified key
+                if(tryProcess(buf, offset, length, 
opn.getUnverifiedKeyTracker())) return;
+            }
+            if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH + 2) {
+                // Might be an auth packet
+                if(tryProcessAuth(buf, offset, length, opn, peer)) return;
+            }
+        }
+        PeerNode[] peers = crypto.getPeerNodes();
+        if(length > HASH_LENGTH + RANDOM_BYTES_LENGTH + 4 + 6) {
+            for(int i=0;i<peers.length;i++) {
+                pn = peers[i];
+                if(pn == opn) continue;
+                if(tryProcess(buf, offset, length, pn.getCurrentKeyTracker())) 
{
+                    // IP address change
+                    pn.changedIP(peer);
+                    return;
+                }
+                if(tryProcess(buf, offset, length, 
pn.getPreviousKeyTracker())) {
+                    // IP address change
+                    pn.changedIP(peer);
+                    return;
+                }
+                if(tryProcess(buf, offset, length, 
pn.getUnverifiedKeyTracker())) {
+                    // IP address change
+                    pn.changedIP(peer);
+                    return;
+                }
+            }
+        }
+        if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH + 2) {
+            for(int i=0;i<peers.length;i++) {
+                pn = peers[i];
+                if(pn == opn) continue;
+                if(tryProcessAuth(buf, offset, length, pn, peer)) return;
+            }
+        }
+        Logger.normal(this,"Unmatchable packet from "+peer);
+    }
+
+    /**
+     * Is this a negotiation packet? If so, process it.
+     * @param buf The buffer to read bytes from
+     * @param offset The offset at which to start reading
+     * @param length The number of bytes to read
+     * @param opn The PeerNode we think is responsible
+     * @param peer The Peer to send a reply to
+     * @return True if we handled a negotiation packet, false otherwise.
+     */
+    private boolean tryProcessAuth(byte[] buf, int offset, int length, 
PeerNode opn, Peer peer) {
+        BlockCipher authKey = opn.incomingSetupCipher;
+        if(logMINOR) Logger.minor(this, "Decrypt key: 
"+HexUtil.bytesToHex(opn.incomingSetupKey)+" for "+peer+" : "+opn+" in 
tryProcessAuth");
+        // Does the packet match IV E( H(data) data ) ?
+        PCFBMode pcfb = PCFBMode.create(authKey);
+        int ivLength = pcfb.lengthIV();
+        MessageDigest md = SHA256.getMessageDigest();
+        int digestLength = HASH_LENGTH;
+        if(length < digestLength + ivLength + 4) {
+            if(logMINOR) Logger.minor(this, "Too short: "+length+" should be 
at least "+(digestLength + ivLength + 4));
+            SHA256.returnMessageDigest(md);
+            return false;
+        }
+        // IV at the beginning
+        pcfb.reset(buf, offset);
+        // Then the hash, then the data
+        // => Data starts at ivLength + digestLength
+        // Decrypt the hash
+        byte[] hash = new byte[digestLength];
+        System.arraycopy(buf, offset+ivLength, hash, 0, digestLength);
+        pcfb.blockDecipher(hash, 0, hash.length);
+        
+        int dataStart = ivLength + digestLength + offset+2;
+        
+        int byte1 = ((pcfb.decipher(buf[dataStart-2])) & 0xff);
+        int byte2 = ((pcfb.decipher(buf[dataStart-1])) & 0xff);
+        int dataLength = (byte1 << 8) + byte2;
+        if(logMINOR) Logger.minor(this, "Data length: "+dataLength+" (1 = 
"+byte1+" 2 = "+byte2+ ')');
+        if(dataLength > length - (ivLength+hash.length+2)) {
+            if(logMINOR) Logger.minor(this, "Invalid data length 
"+dataLength+" ("+(length - (ivLength+hash.length+2))+") in tryProcessAuth");
+            SHA256.returnMessageDigest(md);
+            return false;
+        }
+        // Decrypt the data
+        byte[] payload = new byte[dataLength];
+        System.arraycopy(buf, dataStart, payload, 0, dataLength);
+        pcfb.blockDecipher(payload, 0, payload.length);
+        
+        md.update(payload);
+        byte[] realHash = md.digest();
+        SHA256.returnMessageDigest(md); md = null;
+        
+        if(Arrays.equals(realHash, hash)) {
+            // Got one
+            processDecryptedAuth(payload, opn, peer);
+            opn.reportIncomingBytes(length);
+            return true;
+        } else {
+            if(logMINOR) Logger.minor(this, "Incorrect hash in tryProcessAuth 
for "+peer+" (length="+dataLength+"): \nreal 
hash="+HexUtil.bytesToHex(realHash)+"\n bad hash="+HexUtil.bytesToHex(hash));
+            return false;
+        }
+    }
+
+    /**
+     * Process a decrypted, authenticated auth packet.
+     * @param payload The packet payload, after it has been decrypted.
+     */
+    private void processDecryptedAuth(byte[] payload, PeerNode pn, Peer 
replyTo) {
+        if(logMINOR) Logger.minor(this, "Processing decrypted auth packet from 
"+replyTo+" for "+pn);
+        if(pn.isDisabled()) {
+               if(logMINOR) Logger.minor(this, "Won't connect to a disabled 
peer ("+pn+ ')');
+               return;  // We don't connect to disabled peers
+       }
+        
+        long now = System.currentTimeMillis();
+       int delta = (int) (now - pn.lastSentPacketTime());
+       
+        int negType = payload[1];
+        int packetType = payload[2];
+        int version = payload[0];
+       
+        if(logMINOR) Logger.minor(this, "Received auth packet for 
"+pn.getPeer()+" (phase="+packetType+", v="+version+", nt="+negType+") (last 
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) from "+replyTo+"");
+        
+        /* Format:
+         * 1 byte - version number (1)
+         * 1 byte - negotiation type (0 = simple DH, will not be supported 
when implement JFKi || 1 = StS)
+         * 1 byte - packet type (0-3)
+         */
+        if(version != 1) {
+            Logger.error(this, "Decrypted auth packet but invalid version: 
"+version);
+            return;
+        }
+
+        if(negType == 0) {
+               Logger.error(this, "Old ephemeral Diffie-Hellman (negType 0) 
not supported.");
+               return;
+        }else if (negType == 1) {
+               // Four stage Diffie-Hellman. 0 = ephemeral, 1 = payload stages 
are signed (not quite STS)
+               // FIXME reduce to 3 stages and implement STS properly (we have 
a separate validation mechanism in PeerNode)
+               // AFAICS this (with negType=1) is equivalent in security to 
STS; it expands the second phase into a second and a fourth phase.
+               // A -> B g^x
+               // B -> A g^y
+               // A -> B E^k ( ... )
+               // B -> A E^k ( ... )
+        
+               if((packetType < 0) || (packetType > 3)) {
+                       Logger.error(this, "Decrypted auth packet but unknown 
packet type "+packetType+" from "+replyTo+" possibly from "+pn);
+                       return;
+               }
+                   
+               // We keep one DiffieHellmanContext per node ONLY
+               /*
+                * Now, to the real meat
+                * Alice, Bob share a base, g, and a modulus, p
+                * Alice generates a random number r, and: 1: Alice -> Bob: 
a=g^r
+                * Bob receives this and generates his own random number, s, 
and: 2: Bob -> Alice: b=g^s
+                * Alice receives this, calculates K = b^r, and: 3: Alice -> 
Bob: E_K ( H(data) data )
+                *    where data = [ Alice's startup number ]
+                * Bob does exactly the same as Alice for packet 4.
+                * 
+                * At this point we are done.
+                */
+               if(packetType == 0) {
+                       // We are Bob
+                       // We need to:
+                       // - Record Alice's a
+                       // - Generate our own s and b
+                       // - Send a type 1 packet back to Alice containing this
+
+                       DiffieHellmanContext ctx = 
+                               processDHZeroOrOne(0, payload, pn);
+                       if(ctx == null) return;
+                       // Send reply
+                       sendFirstHalfDHPacket(1, negType, 
ctx.getOurExponential(), pn, replyTo);
+                       // Send a type 1, they will reply with a type 2
+               } else if(packetType == 1) {
+                       // We are Alice
+                       DiffieHellmanContext ctx = 
+                               processDHZeroOrOne(1, payload, pn);
+                       if(ctx == null) return;
+                               sendSignedDHCompletion(2, ctx.getCipher(), pn, 
replyTo, ctx);
+                       // Send a type 2
+               } else if(packetType == 2) {
+                       // We are Bob
+                       // Receiving a completion packet
+                       // Verify the packet, then complete
+                       // Format: IV E_K ( H(data) data )
+                       // Where data = [ long: bob's startup number ]
+                       processSignedDHTwoOrThree(2, payload, pn, replyTo, 
true);
+               } else if(packetType == 3) {
+                       // We are Alice
+                       processSignedDHTwoOrThree(3, payload, pn, replyTo, 
false);
+               }
+        }else {
+            Logger.error(this, "Decrypted auth packet but unknown negotiation 
type "+negType+" from "+replyTo+" possibly from "+pn);
+            return;
+        }
+    }
+
+    /**
      * Send a signed DH completion message.
      * Format:
      * IV
@@ -638,13 +2245,11 @@
      * @param replyTo The Peer to which to send the packet (not necessarily 
the same
      * as the one on pn as the IP may have changed).
      */
-         
-    private void sendSignedDHCompletion(int phase, BlockCipher cipher, 
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) 
-     {
+    private void sendSignedDHCompletion(int phase, BlockCipher cipher, 
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) {
         PCFBMode pcfb = PCFBMode.create(cipher);
         byte[] iv = new byte[pcfb.lengthIV()];

-        byte[] myRef = node.myCompressedSetupRef();
+        byte[] myRef = crypto.myCompressedSetupRef();
         byte[] data = new byte[myRef.length + 8];
         System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0, 8);
         System.arraycopy(myRef, 0, data, 8, myRef.length);
@@ -658,7 +2263,7 @@
         md.update(data);
         byte[] hash = md.digest();

-        DSASignature sig = node.sign(hash);
+        DSASignature sig = crypto.sign(hash);

         byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
         byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
@@ -713,15 +2318,15 @@
         if(logMINOR) Logger.minor(this, "Processed: 
"+HexUtil.bytesToHex(data));
         long time2 = System.currentTimeMillis();
         if((time2 - time1) > 200) {
-          Logger.error(this, "sendFirstHalfDHPacket: time2 is more than 200ms 
after time1 ("+(time2 - time1)+") working on "+replyTo+" of "+pn.getName());
+          Logger.error(this, "sendFirstHalfDHPacket: time2 is more than 200ms 
after time1 ("+(time2 - time1)+") working on "+replyTo+" of 
"+pn.userToString());
         }
         sendAuthPacket(1, negType, phase, data, pn, replyTo);
         long time3 = System.currentTimeMillis();
         if((time3 - time2) > 500) {
-          Logger.error(this, "sendFirstHalfDHPacket:sendAuthPacket() time3 is 
more than half a second after time2 ("+(time3 - time2)+") working on 
"+replyTo+" of "+pn.getName());
+          Logger.error(this, "sendFirstHalfDHPacket:sendAuthPacket() time3 is 
more than half a second after time2 ("+(time3 - time2)+") working on 
"+replyTo+" of "+pn.userToString());
         }
         if((time3 - time1) > 500) {
-          Logger.error(this, "sendFirstHalfDHPacket: time3 is more than half a 
second after time1 ("+(time3 - time1)+") working on "+replyTo+" of 
"+pn.getName());
+          Logger.error(this, "sendFirstHalfDHPacket: time3 is more than half a 
second after time1 ("+(time3 - time1)+") working on "+replyTo+" of 
"+pn.userToString());
         }
     }

@@ -745,13 +2350,13 @@
      */
     private void sendAuthPacket(byte[] output, PeerNode pn, Peer replyTo) {
         int length = output.length;
-        if(length > node.usm.getMaxPacketSize()) {
+        if(length > sock.getMaxPacketSize()) {
             throw new IllegalStateException("Cannot send auth packet: too 
long: "+length);
         }
         BlockCipher cipher = pn.outgoingSetupCipher;
         if(logMINOR) Logger.minor(this, "Outgoing cipher: 
"+HexUtil.bytesToHex(pn.outgoingSetupKey));
         PCFBMode pcfb = PCFBMode.create(cipher);
-        int paddingLength = node.random.nextInt(100);
+        int paddingLength = node.fastWeakRandom.nextInt(100);
         byte[] iv = new byte[pcfb.lengthIV()];
         node.random.nextBytes(iv);
         byte[] hash = SHA256.digest(output);
@@ -767,23 +2372,22 @@
         pcfb.blockEncipher(output, 0, output.length);
         System.arraycopy(output, 0, data, hash.length+iv.length+2, 
output.length);
         byte[] random = new byte[paddingLength];
-        // FIXME don't use node.random
-        node.random.nextBytes(random);
+        node.fastWeakRandom.nextBytes(random);
         System.arraycopy(random, 0, data, 
hash.length+iv.length+2+output.length, random.length);
         try {
                sendPacket(data, replyTo, pn, 0);
                } catch (LocalAddressException e) {
-                       Logger.error(this, "Tried to send auth packet to local 
address: "+replyTo+" for "+pn);
+                       Logger.error(this, "Tried to send auth packet to local 
address: "+replyTo+" for "+pn+" - maybe you should set allowLocalAddresses for 
this peer??");
                }
                if(logMINOR) Logger.minor(this, "Sending auth packet (long) to 
"+replyTo+" - size "+data.length+" data length: "+output.length);
      }

     private void sendPacket(byte[] data, Peer replyTo, PeerNode pn, int 
alreadyReportedBytes) throws LocalAddressException {
-       if(pn.isIgnoreSourcePort()) {
+       if(pn.isIgnoreSource()) {
                Peer p = pn.getPeer();
                if(p != null) replyTo = p;
        }
-       usm.sendPacket(data, replyTo, pn.allowLocalAddresses());
+       sock.sendPacket(data, replyTo, pn.allowLocalAddresses());
        pn.reportOutgoingBytes(data.length);
        node.outputThrottle.forceGrab(data.length - alreadyReportedBytes);
        }
@@ -931,7 +2535,7 @@
     private boolean tryProcess(byte[] buf, int offset, int length, KeyTracker 
tracker) {
         // Need to be able to call with tracker == null to simplify code above
         if(tracker == null) {
-            if(logMINOR) Logger.minor(this, "Tracker == null");
+            if(Logger.shouldLog(Logger.DEBUG, this)) Logger.debug(this, 
"Tracker == null");
             return false;
         }
         if(logMINOR) Logger.minor(this,"Entering tryProcess: 
"+Fields.hashCode(buf)+ ',' +offset+ ',' +length+ ',' +tracker);
@@ -1203,7 +2807,7 @@
             ptr+=length;
             if(m != null) {
                 //Logger.minor(this, "Dispatching packet: "+m);
-                usm.checkFilters(m);
+                usm.checkFilters(m, sock);
             }
         }
         if(logMINOR) Logger.minor(this, "Done");
@@ -1246,7 +2850,7 @@
                     }
                     int packetNumber = 
kt.allocateOutgoingPacketNumberNeverBlock();
                     this.processOutgoingPreformatted(buf, 0, buf.length, 
pn.getCurrentKeyTracker(), packetNumber, mi.cb, mi.alreadyReportedBytes);
-                    mi.onSent(buf.length + FULL_HEADERS_LENGTH_ONE_MESSAGE);
+                    mi.onSent(buf.length + fullHeadersLengthOneMessage);
                 } catch (NotConnectedException e) {
                     Logger.normal(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString);
                     // Requeue
@@ -1283,7 +2887,7 @@
             } else {
                 byte[] data = mi.getData(pn);
                 messageData[x] = data;
-                if(data.length > node.usm.getMaxPacketSize()) {
+                if(data.length > sock.getMaxPacketSize()) {
                     Logger.error(this, "Message exceeds packet size: 
"+messages[i]+" size "+data.length+" message "+mi.msg);
                     // Will be handled later
                 }
@@ -1317,7 +2921,7 @@
         }
         if(x != callbacksCount) throw new IllegalStateException();

-        if((length + HEADERS_LENGTH_MINIMUM < node.usm.getMaxPacketSize()) &&
+        if((length + HEADERS_LENGTH_MINIMUM < sock.getMaxPacketSize()) &&
                 (messageData.length < 256)) {
                        mi_name = null;
             try {
@@ -1325,7 +2929,7 @@
                 for(int i=0;i<messageData.length;i++) {
                        MessageItem mi = newMsgs[i];
                                        mi_name = (mi.msg == null ? "(not a 
Message)" : mi.msg.getSpec().getName());
-                                       mi.onSent(messageData[i].length + 2 + 
(FULL_HEADERS_LENGTH_MINIMUM / messageData.length));
+                                       mi.onSent(messageData[i].length + 2 + 
(fullHeadersLengthMinimum / messageData.length));
                 }
             } catch (NotConnectedException e) {
                 Logger.normal(this, "Caught "+e+" while sending messages 
("+mi_name+") to "+pn.getPeer()+requeueLogString);
@@ -1362,7 +2966,7 @@
                 else thisLength = (messageData[i].length + 2);
                 int newLength = length + thisLength;
                 count++;
-                if((newLength + HEADERS_LENGTH_MINIMUM > 
node.usm.getMaxPacketSize()) || (count > 255) || (i == messages.length)) {
+                if((newLength + HEADERS_LENGTH_MINIMUM > 
sock.getMaxPacketSize()) || (count > 255) || (i == messages.length)) {
                     // lastIndex up to the message right before this one
                     // e.g. lastIndex = 0, i = 1, we just send message 0
                     if(lastIndex != i) {
@@ -1372,7 +2976,7 @@
                             for(int j=lastIndex;j<i;j++) {
                                MessageItem mi = newMsgs[j];
                                                                mi_name = 
(mi.msg == null ? "(not a Message)" : mi.msg.getSpec().getName());
-                                                               
mi.onSent(messageData[j].length + 2 + (FULL_HEADERS_LENGTH_MINIMUM / 
(i-lastIndex)));
+                                                               
mi.onSent(messageData[j].length + 2 + (fullHeadersLengthMinimum / 
(i-lastIndex)));
                             }
                         } catch (NotConnectedException e) {
                             Logger.normal(this, "Caught "+e+" while sending 
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString);
@@ -1561,6 +3165,20 @@
                1 + // no forgotten packets
                length; // the payload !

+        // Padding
+        // This will do an adequate job of disguising the contents, and a poor 
(but not totally
+        // worthless) job of disguising the traffic. FIXME!!!!!
+        // Ideally we'd mimic the size profile - and the session bytes! - of a 
common protocol.
+        
+        int paddedLen = ((packetLength + 63) / 64) * 64;
+        paddedLen += node.fastWeakRandom.nextInt(64);
+        if(packetLength <= 1280 && paddedLen > 1280) paddedLen = 1280;
+
+        byte[] padding = new byte[paddedLen - packetLength];
+        node.fastWeakRandom.nextBytes(padding);
+        
+        packetLength = paddedLen;
+        
         if(logMINOR) Logger.minor(this, "Packet length: "+packetLength+" 
("+length+")");

         byte[] plaintext = new byte[packetLength];
@@ -1670,6 +3288,9 @@
         System.arraycopy(buf, offset, plaintext, ptr, length);
         ptr += length;

+        System.arraycopy(padding, 0, plaintext, ptr, padding.length);
+        ptr += padding.length;
+        
         if(ptr != plaintext.length) {
                Logger.error(this, "Inconsistent length: "+plaintext.length+" 
buffer but "+(ptr)+" actual");
                byte[] newBuf = new byte[ptr];
@@ -1743,7 +3364,92 @@
         //Logger.minor(this, "Ciphertext:\n"+HexUtil.bytesToHex(output, 
digestLength, plaintext.length));

         // We have a packet
+        // Send it
+        
+        if(logMINOR) Logger.minor(this,"Sending packet of length 
"+output.length+" (" + Fields.hashCode(output) + " to "+kt.pn);
+        
+        // pn.getPeer() cannot be null
+        try {
+               sendPacket(output, kt.pn.getPeer(), kt.pn, 
alreadyReportedBytes);
+               } catch (LocalAddressException e) {
+                       Logger.error(this, "Tried to send data packet to local 
address: "+kt.pn.getPeer()+" for "+kt.pn.allowLocalAddresses());
+               }
+        kt.pn.sentPacket();
+    }
+
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#sendHandshake(freenet.node.PeerNode)
         */
+    public void sendHandshake(PeerNode pn) {
+       int negType = pn.bestNegType(this);
+       if(negType == -1) {
+               if(pn.isRoutingCompatible())
+                       Logger.error(this, "Could not negotiate with "+pn+" : 
no common negTypes available!: his negTypes: 
"+StringArray.toString(pn.negTypes)+" my negTypes: 
"+StringArray.toString(supportedNegTypes())+" despite being up to date!!");
+               else
+                       Logger.minor(this, "Could not negotiate with "+pn+" : 
no common negTypes available!: his negTypes: 
"+StringArray.toString(pn.negTypes)+" my negTypes: 
"+StringArray.toString(supportedNegTypes())+" (probably just too old)");
+               return;
+       }
+       if(logMINOR) Logger.minor(this, "Possibly sending handshake to "+pn+" 
negotiation type "+negType);
+        DiffieHellmanContext ctx;
+        Peer[] handshakeIPs;
+        if(!pn.shouldSendHandshake()) {
+               if(logMINOR) Logger.minor(this, "Not sending handshake to 
"+pn.getPeer()+" because pn.shouldSendHandshake() returned false");
+               return;
+        }
+        long firstTime = System.currentTimeMillis();
+        handshakeIPs = pn.getHandshakeIPs();
+        long secondTime = System.currentTimeMillis();
+        if((secondTime - firstTime) > 1000)
+            Logger.error(this, "getHandshakeIPs() took more than a second to 
execute ("+(secondTime - firstTime)+") working on "+pn.userToString());
+        if(handshakeIPs.length == 0) {
+            pn.couldNotSendHandshake();
+            long thirdTime = System.currentTimeMillis();
+            if((thirdTime - secondTime) > 1000)
+                Logger.error(this, "couldNotSendHandshake() (after 
getHandshakeIPs()) took more than a second to execute ("+(thirdTime - 
secondTime)+") working on "+pn.userToString());
+            return;
+        } else {
+            long DHTime1 = System.currentTimeMillis();
+            ctx = DiffieHellman.generateContext();
+            long DHTime2 = System.currentTimeMillis();
+            if((DHTime2 - DHTime1) > 1000)
+                Logger.error(this, "DHTime2 is more than a second after 
DHTime1 ("+(DHTime2 - DHTime1)+") working on "+pn.userToString());
+            pn.setKeyAgreementSchemeContext(ctx);
+            long DHTime3 = System.currentTimeMillis();
+            if((DHTime3 - DHTime2) > 1000)
+                Logger.error(this, "DHTime3 is more than a second after 
DHTime2 ("+(DHTime3 - DHTime2)+") working on "+pn.userToString());
+        }
+        int sentCount = 0;
+        long loopTime1 = System.currentTimeMillis();
+        for(int i=0;i<handshakeIPs.length;i++){
+               Peer peer = handshakeIPs[i];
+               FreenetInetAddress addr = peer.getFreenetAddress();
+               if(!crypto.allowConnection(pn, addr)) {
+                       if(logMINOR)
+                               Logger.minor(this, "Not sending handshake 
packet to "+peer+" for "+pn);
+               }
+               if(peer.getAddress(false) == null) {
+                       if(logMINOR) Logger.minor(this, "Not sending handshake 
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because the DNS lookup failed or 
it's a currently unsupported IPv6 address");
+                       continue;
+               }
+               if((!pn.allowLocalAddresses()) && 
(!peer.isRealInternetAddress(false, false))) {
+                       if(logMINOR) Logger.minor(this, "Not sending handshake 
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because it's not a real Internet 
address and metadata.allowLocalAddresses is not true");
+                       continue;
+               }
+               sendFirstHalfDHPacket(0, negType, ctx.getOurExponential(), pn, 
peer);
+               pn.sentHandshake();
+               sentCount += 1;
+        }
+        long loopTime2 = System.currentTimeMillis();
+        if((loopTime2 - loopTime1) > 1000)
+               Logger.normal(this, "loopTime2 is more than a second after 
loopTime1 ("+(loopTime2 - loopTime1)+") working on "+pn.userToString());
+        if(sentCount==0) {
+            pn.couldNotSendHandshake();
+        }
+    }
+
+    /* (non-Javadoc)
+        * @see 
freenet.node.OutgoingPacketMangler#isDisconnected(freenet.io.comm.PeerContext)
+        */
     public boolean isDisconnected(PeerContext context) {
         if(context == null) return false;
         return !((PeerNode)context).isConnected();
@@ -1756,4 +3462,25 @@
        public int[] supportedNegTypes() {
                return new int[] { 1 };
        }
+
+       public int fullHeadersLengthOneMessage() {
+               return fullHeadersLengthOneMessage;
+       }
+
+       public SocketHandler getSocketHandler() {
+               return sock;
+       }
+
+       public Peer[] getPrimaryIPAddress() {
+               return crypto.detector.getPrimaryPeers();
+       }
+
+       public byte[] getCompressedNoderef() {
+               return crypto.myCompressedFullRef();
+       }
+
+       public boolean alwaysAllowLocalAddresses() {
+               return crypto.config.alwaysAllowLocalAddresses();
+       }
 }
+>>>>>>> .merge-right.r14698


Reply via email to