From a1ee8220bbc53b5e23c750b3742ea5057688f8c6 Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Thu, 17 Mar 2022 10:48:10 -0400
Subject: [PATCH] SSU2: Relay and Peer Test WIP

Packet building, fix peer test block
Not hooked in
---
 .../router/transport/udp/PacketBuilder2.java  | 252 +++++++++---------
 .../i2p/router/transport/udp/SSU2Payload.java |  23 +-
 2 files changed, 144 insertions(+), 131 deletions(-)

diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
index 158dbbed20..b8df75bcd6 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
@@ -50,6 +50,9 @@ class PacketBuilder2 {
     static final int TYPE_FIRST = 62;
     static final int TYPE_ACK = TYPE_FIRST;
     static final int TYPE_PUNCH = 63;
+    static final int TYPE_RESP = 64;
+    static final int TYPE_INTRO = 65;
+    static final int TYPE_RREQ = 66;
     static final int TYPE_TCB = 67;
     static final int TYPE_TBC = 68;
     static final int TYPE_TTA = 69;
@@ -528,176 +531,151 @@ class PacketBuilder2 {
     }
 
     /**
-     * Build a packet as if we are Alice and we either want Bob to begin a 
-     * peer test or Charlie to finish a peer test.
+     * Build a packet as Alice, to Bob to begin a  peer test.
+     * In-session, message 1.
      * 
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toIntroKey, long nonce, SessionKey aliceIntroKey) {
-        return buildPeerTestFromAlice(toIP, toPort, toIntroKey, toIntroKey, nonce, aliceIntroKey);
+    public UDPPacket buildPeerTestFromAlice(byte[] signedData, PeerState2 bob) {
+        Block block = new SSU2Payload.PeerTestBlock(1, 0, null, signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
+        rv.setMessageType(TYPE_TFA);
+        return rv;
     }
-*/
 
     /**
-     * Build a packet as if we are Alice and we either want Bob to begin a 
-     * peer test or Charlie to finish a peer test.
+     * Build a packet as Alice to Charlie.
+     * Out-of-session, message 6.
      * 
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toCipherKey, SessionKey toMACKey,
-                                            long nonce, SessionKey aliceIntroKey) {
-        UDPPacket packet = buildShortPacketHeader(PEER_TEST_FLAG_BYTE);
-        DatagramPacket pkt = packet.getPacket();
-        byte data[] = pkt.getData();
-        int off = SHORT_HEADER_SIZE;
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Sending peer test " + nonce + " to Bob");
-        
-        // now for the body
-        DataHelper.toLong(data, off, 4, nonce);
-        off += 4;
-        data[off++] = 0; // neither Bob nor Charlie need Alice's IP from her
-        DataHelper.toLong(data, off, 2, 0); // neither Bob nor Charlie need Alice's port from her
-        off += 2;
-        System.arraycopy(aliceIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
-        off += SessionKey.KEYSIZE_BYTES;
-        
-        pkt.setLength(off);
-        authenticate(packet, toCipherKey, toMACKey);
+    public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey introKey,
+                                            long sendID, long rcvID, byte[] signedData) {
+        long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
+        long token = _context.random().nextLong();
+        UDPPacket packet = buildLongPacketHeader(sendID, n, PEER_TEST_FLAG_BYTE, rcvID, token);
+        Block block = new SSU2Payload.PeerTestBlock(6, 0, null, signedData);
+        byte[] ik = introKey.getData();
+        encryptPeerTest(packet, ik, n, ik, ik, toIP.getAddress(), toPort, block);
         setTo(packet, toIP, toPort);
         packet.setMessageType(TYPE_TFA);
         packet.setPriority(PRIORITY_LOW);
         return packet;
     }
-*/
 
     /**
-     * Build a packet as if we are either Bob or Charlie and we are helping test Alice.
-     * Not for use as Bob, as of 0.9.52; use in-session cipher/mac keys instead.
+     * Build a packet as Bob to Alice, with the response from Charlie,
+     * or a rejection by Bob.
+     * In-session, message 4.
      *
+     * @param charlieHash fake hash (all zeros) if rejected by bob
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort,
-                                          SessionKey aliceIntroKey, SessionKey charlieIntroKey, long nonce) {
-        return buildPeerTestToAlice(aliceIP, alicePort, aliceIntroKey, aliceIntroKey, charlieIntroKey, nonce);
+    public UDPPacket buildPeerTestToAlice(int code, Hash charlieHash, byte[] signedData, PeerState2 alice) {
+        Block block = new SSU2Payload.PeerTestBlock(4, code, charlieHash, signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), alice);
+        rv.setMessageType(TYPE_TTA);
+        return rv;
     }
-*/
 
     /**
-     * Build a packet as if we are either Bob or Charlie and we are helping test Alice.
+     * Build a packet as Charlie to Alice.
+     * Out-of-session, messages 5 and 7.
      * 
-     * @param aliceCipherKey the intro key if we are Charlie
-     * @param aliceMACKey the intro key if we are Charlie
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort,
-                                          SessionKey aliceCipherKey, SessionKey aliceMACKey,
-                                          SessionKey charlieIntroKey, long nonce) {
-        UDPPacket packet = buildShortPacketHeader(PEER_TEST_FLAG_BYTE);
-        DatagramPacket pkt = packet.getPacket();
-        byte data[] = pkt.getData();
-        int off = SHORT_HEADER_SIZE;
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Sending peer test " + nonce + " to Alice");
-        
-        // now for the body
-        DataHelper.toLong(data, off, 4, nonce);
-        off += 4;
-        byte ip[] = aliceIP.getAddress();
-        data[off++] = (byte) ip.length;
-        System.arraycopy(ip, 0, data, off, ip.length);
-        off += ip.length;
-        DataHelper.toLong(data, off, 2, alicePort);
-        off += 2;
-        System.arraycopy(charlieIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
-        off += SessionKey.KEYSIZE_BYTES;
-        
-        pkt.setLength(off);
-        authenticate(packet, aliceCipherKey, aliceMACKey);
+    public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort, SessionKey introKey,
+                                          boolean firstSend,
+                                          long sendID, long rcvID, byte[] signedData) {
+        long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
+        long token = _context.random().nextLong();
+        UDPPacket packet = buildLongPacketHeader(sendID, n, PEER_TEST_FLAG_BYTE, rcvID, token);
+        int msgNum = firstSend ? 5 : 7;
+        Block block = new SSU2Payload.PeerTestBlock(msgNum, 0, null, signedData);
+        byte[] ik = introKey.getData();
+        encryptPeerTest(packet, ik, n, ik, ik, aliceIP.getAddress(), alicePort, block);
         setTo(packet, aliceIP, alicePort);
         packet.setMessageType(TYPE_TTA);
         packet.setPriority(PRIORITY_LOW);
         return packet;
     }
-*/
 
     /**
-     * Build a packet as if we are Bob sending Charlie a packet to help test Alice.
+     * Build a packet as Bob to Charlie to help test Alice.
+     * In-session, message 2.
      * 
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestToCharlie(InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, long nonce, 
-                                            InetAddress charlieIP, int charliePort, 
-                                            SessionKey charlieCipherKey, SessionKey charlieMACKey) {
-        UDPPacket packet = buildShortPacketHeader(PEER_TEST_FLAG_BYTE);
-        DatagramPacket pkt = packet.getPacket();
-        byte data[] = pkt.getData();
-        int off = SHORT_HEADER_SIZE;
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Sending peer test " + nonce + " to Charlie");
-        
-        // now for the body
-        DataHelper.toLong(data, off, 4, nonce);
-        off += 4;
-        byte ip[] = aliceIP.getAddress();
-        data[off++] = (byte) ip.length;
-        System.arraycopy(ip, 0, data, off, ip.length);
-        off += ip.length;
-        DataHelper.toLong(data, off, 2, alicePort);
-        off += 2;
-        System.arraycopy(aliceIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
-        off += SessionKey.KEYSIZE_BYTES;
-        
-        pkt.setLength(off);
-        authenticate(packet, charlieCipherKey, charlieMACKey);
-        setTo(packet, charlieIP, charliePort);
-        packet.setMessageType(TYPE_TBC);
-        packet.setPriority(PRIORITY_LOW);
-        return packet;
+    public UDPPacket buildPeerTestToCharlie(Hash aliceHash, byte[] signedData, PeerState2 charlie) {
+        Block block = new SSU2Payload.PeerTestBlock(2, 0, aliceHash, signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), charlie);
+        rv.setMessageType(TYPE_TBC);
+        return rv;
     }
-*/
     
     /**
-     * Build a packet as if we are Charlie sending Bob a packet verifying that we will help test Alice.
+     * Build a packet as Charlie to Bob verifying that we will help test Alice.
+     * In-session, message 3.
      * 
      * @return ready to send packet, or null if there was a problem
      */
-/*
-    public UDPPacket buildPeerTestToBob(InetAddress bobIP, int bobPort, InetAddress aliceIP, int alicePort,
-                                        SessionKey aliceIntroKey, long nonce,
-                                        SessionKey bobCipherKey, SessionKey bobMACKey) {
-        UDPPacket packet = buildShortPacketHeader(PEER_TEST_FLAG_BYTE);
-        DatagramPacket pkt = packet.getPacket();
-        byte data[] = pkt.getData();
-        int off = SHORT_HEADER_SIZE;
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Sending peer test " + nonce + " to Bob");
-        
-        // now for the body
-        DataHelper.toLong(data, off, 4, nonce);
-        off += 4;
-        byte ip[] = aliceIP.getAddress();
-        data[off++] = (byte) ip.length;
-        System.arraycopy(ip, 0, data, off, ip.length);
-        off += ip.length;
-        DataHelper.toLong(data, off, 2, alicePort);
-        off += 2;
-        System.arraycopy(aliceIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
-        off += SessionKey.KEYSIZE_BYTES;
-        
-        pkt.setLength(off);
-        authenticate(packet, bobCipherKey, bobMACKey);
-        setTo(packet, bobIP, bobPort);
-        packet.setMessageType(TYPE_TCB);
-        packet.setPriority(PRIORITY_LOW);
-        return packet;
+    public UDPPacket buildPeerTestToBob(int code, byte[] signedData, PeerState2 bob) {
+        Block block = new SSU2Payload.PeerTestBlock(3, code, null, signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
+        rv.setMessageType(TYPE_TCB);
+        return rv;
+    }
+
+    /**
+     *  build intro packets for each of the published introducers
+     *
+     *  @param emgr only to call emgr.isValid()
+     *  @return empty list on failure
+     */
+    public List<UDPPacket> buildRelayRequest(EstablishmentManager emgr, OutboundEstablishState2 state) {
+        return null;
+    }
+
+    /**
+     *  From Alice to Bob.
+     *  In-session.
+     *
+     *  @return null on failure
+     */
+    private UDPPacket buildRelayRequest(byte[] signedData, PeerState2 bob) {
+        Block block = new SSU2Payload.RelayRequestBlock(signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), bob);
+        rv.setMessageType(TYPE_RREQ);
+        rv.setPriority(PRIORITY_HIGH);
+        return rv;
+    }
+
+    /**
+     *  From Bob to Charlie.
+     *  In-session.
+     *
+     *  @return null on failure
+     */
+    UDPPacket buildRelayIntro(byte[] signedData, PeerState2 charlie) {
+        Block block = new SSU2Payload.RelayIntroBlock(signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), charlie);
+        rv.setMessageType(TYPE_INTRO);
+        return rv;
+    }
+
+    /**
+     *  From Charlie to Bob or Bob to Alice.
+     *  In-session.
+     *
+     *  @param state Alice or Bob
+     *  @return null on failure
+     */
+    UDPPacket buildRelayResponse(byte[] signedData, PeerState2 state) {
+        Block block = new SSU2Payload.RelayResponseBlock(signedData);
+        UDPPacket rv = buildPacket(Collections.emptyList(), Collections.singletonList(block), state);
+        rv.setMessageType(TYPE_RESP);
+        return rv;
     }
-*/
 
     /**
      *  Creates an empty unauthenticated packet for hole punching.
@@ -865,17 +843,33 @@ class PacketBuilder2 {
      */
     private void encryptRetry(UDPPacket packet, byte[] chachaKey, long n,
                               byte[] hdrKey1, byte[] hdrKey2, byte[] ip, int port) {
+        encryptPeerTest(packet, chachaKey, n, hdrKey1, hdrKey2, ip, port, null);
+    }
+
+    /**
+     *  Also used for retry with ptBlock = null
+     *
+     *  @param packet containing only 32 byte header
+     *  @param ptBlock null for retry
+     */
+    private void encryptPeerTest(UDPPacket packet, byte[] chachaKey, long n,
+                                 byte[] hdrKey1, byte[] hdrKey2, byte[] ip, int port,
+                                 Block ptBlock) {
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
         try {
-            List<Block> blocks = new ArrayList<Block>(3);
+            List<Block> blocks = new ArrayList<Block>(4);
             Block block = new SSU2Payload.DateTimeBlock(_context);
             int len = block.getTotalLength();
             blocks.add(block);
             block = new SSU2Payload.AddressBlock(ip, port);
             len += block.getTotalLength();
             blocks.add(block);
+            if (ptBlock != null) {
+                len += ptBlock.getTotalLength();
+                blocks.add(ptBlock);
+            }
             // plenty of room
             block = getPadding(len, 1280);
             len += block.getTotalLength();
diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java b/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java
index ef3c671bda..f7e294df12 100644
--- a/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java
+++ b/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java
@@ -774,18 +774,37 @@ class SSU2Payload {
     }
 
     public static class PeerTestBlock extends Block {
+        private final int n;
+        private final int c;
+        private final Hash h;
         private final byte[] d;
 
-        public PeerTestBlock(byte[] data) {
+        /**
+         *  @param hash may be null
+         */
+        public PeerTestBlock(int msgNum, int code, Hash hash, byte[] data) {
             super(BLOCK_PEERTEST);
+            n = msgNum;
+            c = code;
+            h = hash;
             d = data;
         }
 
         public int getDataLength() {
-            return d.length;
+            int rv = 3 + d.length;
+            if (h != null)
+                rv += Hash.HASH_LENGTH;
+            return rv;
         }
 
         public int writeData(byte[] tgt, int off) {
+            tgt[off++] = (byte) n;
+            tgt[off++] = (byte) c;
+            tgt[off++] = 0;  // flag
+            if (h != null) {
+                System.arraycopy(h.getData(), 0, tgt, off, Hash.HASH_LENGTH);
+                off += Hash.HASH_LENGTH;
+            }
             System.arraycopy(d, 0, tgt, off, d.length);
             return off + d.length;
         }
-- 
GitLab