java.lang.Object
net.i2p.router.transport.ntcp.OutboundNTCP2State
All Implemented Interfaces:
EstablishState

class OutboundNTCP2Stateextends Object implements EstablishState
NTCP 2 only. We are Alice.

Also contains static constants and methods used by InboundEstablishState for NTCP2. Does not extend EstablishBase.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    private static enum 
    State of the outbound NTCP2 handshake.
  • Field Summary

    Modifier and Type
    Field
    Description
    private final RouterInfo
    Alice's router info.
    private final int
    Size of Alice's router info, in bytes.
    private final BanLogger
    Records why routers were banned.
    private final SessionKey
    Hash of Bob's handshake.
    private final byte[]
    IV of Bob's handshake.
    private final NTCPConnection
    The connection being established.
    private final RouterContext
    The router context.
    private String
    Reason the handshake failed, null until then.
    private final HandshakeState
    Handshake state for the ratchet keys.
    private final Log
    Logger for the NTCP connection.
    private int
    Padding length of message 2.
    private final int
    Padding length of message 3.
    private long
    Our clock skew to the peer, in ms.
    private int
    Bytes received so far.
    Current state of the handshake.
    private final Object
    Lock for the handshake state.
    private final byte[]
    Scratch buffer for the handshake fields.
    private final NTCPTransport
    The NTCP transport we serve.
    private final int
    Version of the handshake.
    static final int
    IV length in bytes, taken from the tail of the static key (16).
    static final int
    Static key length in bytes (32).
    static final int
    MAC length in bytes, appended to each encrypted block (16).
    static final long
    In SECONDS.
    (package private) static final int
    The short handshake message total used by i2pd (287).
    static final int
    NTCP2 message 1 size in bytes (64).
    static final int
    NTCP2 message 2 size in bytes.
    static final int
    NTCP2 message 3 part 1 size in bytes (48).
    static final int
    Options block size in bytes.
    static final int
    Options block size in bytes.
    private static final int
    Size of the options field in message 3.
    (package private) static final int
    Exclusive upper bound for the "short" handshake padding, i2pd's 287 - 64 (223).
    private static final int
    Largest padding length in message 3.
    private static final byte[]
    For SipHash keygen.
    static final byte[]
    All-zero key marking a static key or IV as unset.
    private static final byte[]
    Shared empty array for zero-length values.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Create a new outbound NTCP2 state.
  • Method Summary

    Modifier and Type
    Method
    Description
    private void
    Moves to the next handshake state.
    void
    close(String reason, Exception e)
    Release resources on timeout.
    protected void
    fail(String reason)
    Fail the handshake with the given reason.
    protected void
    fail(String reason, Exception e)
    Fail the handshake with the given reason and exception.
    protected void
    fail(String reason, Exception e, boolean bySkew)
    Fail the handshake, optionally skew-related.
    (package private) static byte[][]
    KDF for SipHash.
    Failure reason if the handshake failed.
    int
    NTCP version.
    boolean
    Don't synchronize this, deadlocks all over.
    boolean
    Did the handshake fail for some reason?
    boolean
    Check if the connection is in initial state and ready for prepareOutbound().
    boolean
    Check if the connection is in a state where prepareOutbound() has already been called to avoid duplicate calls that cause IllegalStateException.
    (package private) static int
    msg1BufSize(int version)
    Bytes needed to hold message 1 or its padding, whichever is longer.
    (package private) static int
    msg1PayloadOffset(int version)
    Offset of the plaintext options block within the message 1 buffer.
    (package private) static int
    msg1Size(int version)
    Full message 1 length (no padding) for the version: X (32) + options (16) + options MAC (16) + PQ frame.
    (package private) static int
    msg2PaddingMax(int version, int padlen1)
    The padding cap for message 2, scaled to what Alice sent.
    (package private) static int
    msg2Size(int version)
    Full message 2 length (no padding) for the version: Y (32) + options (16) + options MAC (16) + PQ ciphertext frame.
    (package private) static int
    padding1Max(int version)
    Upper bound (exclusive) for the message 1 padding we send, per version, equal to i2pd's maxPaddingLength in CreateSessionRequestMessage: offset + 32 for a PQ frame, clamped so the whole message fits i2pd's 2528-byte handshake budget (944 + MLKEM-1024 key + 16), which is what pulls MLKEM-1024 back down to 880.
    (package private) static int
    padding1RecvMax(int version)
    Largest message 1 padding we accept, per session version.
    (package private) static int
    padding2Max(int version)
    Upper bound (exclusive) for the message 2 padding, in both directions, equal to i2pd's maxPaddingLength in CreateSessionCreatedMessage (offset + 32, clamped by the same 2528-byte budget).
    (package private) static int
    pqMsg1Extra(int version)
    Extra bytes in message 1 for the PQ frame (PQ AEAD MAC + encapsulation key), 0 for the non-PQ version 2.
    (package private) static int
    pqMsg2Extra(int version)
    Extra bytes in message 2 for Bob's ML-KEM ciphertext and its MAC, 0 for the non-PQ version 2.
    void
    We are Alice.
    private void
    We are Alice.
    void
    Parse the contents of the buffer as part of the handshake.
    private void
    We are Alice, so receive these bytes as part of an outbound connection.
    private void
    releaseBufs(boolean isVerified)
    Only call once.
    private void
    NTCP 2 only.
    (package private) static int
    tmpSize(int version)
    Bytes needed by the outbound handshake buffer.
    String representation of this state.

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Field Details

    • _context

      private final RouterContext _context
      The router context.
    • _log

      private final Log _log
      Logger for the NTCP connection.
    • _banLogger

      private final BanLogger _banLogger
      Records why routers were banned.
    • _transport

      private final NTCPTransport _transport
      The NTCP transport we serve.
    • _con

      private final NTCPConnection _con
      The connection being established.
    • _tmp

      private final byte[] _tmp
      Scratch buffer for the handshake fields.
    • _received

      private int _received
      Bytes received so far.
    • _peerSkew

      private long _peerSkew
      Our clock skew to the peer, in ms.
    • KEY_SIZE

      public static final int KEY_SIZE
      Static key length in bytes (32).
      See Also:
    • MAC_SIZE

      public static final int MAC_SIZE
      MAC length in bytes, appended to each encrypted block (16).
      See Also:
    • IV_SIZE

      public static final int IV_SIZE
      IV length in bytes, taken from the tail of the static key (16).
      See Also:
    • OPTIONS1_SIZE

      public static final int OPTIONS1_SIZE
      Options block size in bytes.
      See Also:
    • MSG1_SIZE

      public static final int MSG1_SIZE
      NTCP2 message 1 size in bytes (64).
      See Also:
    • MSG_SHORT_MAX

      static final int MSG_SHORT_MAX
      The short handshake message total used by i2pd (287). It compares the message it just built or received against this to decide which padding scale to use.
      See Also:
    • PADDING_SHORT_MAX

      static final int PADDING_SHORT_MAX
      Exclusive upper bound for the "short" handshake padding, i2pd's 287 - 64 (223). Used with a peer that does not advertise PQ support and may predate the 2026 padding increase.
      See Also:
    • PADDING3_MAX

      private static final int PADDING3_MAX
      Largest padding length in message 3.
      See Also:
    • OPTIONS2_SIZE

      public static final int OPTIONS2_SIZE
      Options block size in bytes.
      See Also:
    • MSG2_SIZE

      public static final int MSG2_SIZE
      NTCP2 message 2 size in bytes.
      See Also:
    • MSG3P1_SIZE

      public static final int MSG3P1_SIZE
      NTCP2 message 3 part 1 size in bytes (48).
      See Also:
    • OPTIONS3_SIZE

      private static final int OPTIONS3_SIZE
      Size of the options field in message 3.
      See Also:
    • MAX_SKEW

      public static final long MAX_SKEW
      In SECONDS.
      See Also:
    • ZEROLEN

      private static final byte[] ZEROLEN
      Shared empty array for zero-length values.
    • ZEROKEY

      public static final byte[] ZEROKEY
      All-zero key marking a static key or IV as unset.
    • SIPHASH

      private static final byte[] SIPHASH
      For SipHash keygen.
    • _stateLock

      private final Object _stateLock
      Lock for the handshake state.
    • _state

      private OutboundNTCP2State.State _state
      Current state of the handshake.
    • _failReason

      private String _failReason
      Reason the handshake failed, null until then.
    • _handshakeState

      private final HandshakeState _handshakeState
      Handshake state for the ratchet keys.
    • _aliceRI

      private final RouterInfo _aliceRI
      Alice's router info.
    • _aliceRISize

      private final int _aliceRISize
      Size of Alice's router info, in bytes.
    • _padlen2

      private int _padlen2
      Padding length of message 2.
    • _padlen3

      private final int _padlen3
      Padding length of message 3.
    • _bobHash

      private final SessionKey _bobHash
      Hash of Bob's handshake.
    • _bobIV

      private final byte[] _bobIV
      IV of Bob's handshake.
    • _version

      private final int _version
      Version of the handshake.
  • Constructor Details

    • OutboundNTCP2State

      public OutboundNTCP2State(RouterContext ctx, NTCPTransport transport, NTCPConnection con)
      Create a new outbound NTCP2 state.
      Parameters:
      ctx - supplies the log and the ban logger
      transport - supplies the XDH factory and the transport settings
      con - the connection being established; supplies the remote address, the protocol version, and the peer hash
      Throws:
      IllegalArgumentException - on bad address in the connection
  • Method Details

    • pqMsg1Extra

      static int pqMsg1Extra(int version)
      Extra bytes in message 1 for the PQ frame (PQ AEAD MAC + encapsulation key), 0 for the non-PQ version 2. This is the Noise "F" token: the ML-KEM public key written via encryptAndHash(), so it carries one MAC. Mirrors i2pd's SessionRequest ML-KEM frame (keyLen + 16).
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      the extra byte count, 0 for version 2
    • msg1Size

      static int msg1Size(int version)
      Full message 1 length (no padding) for the version: X (32) + options (16) + options MAC (16) + PQ frame. For MLKEM-768 this is 1264, byte-identical to i2pd's SessionRequest (X + encapKey+tag + options+tag).
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      message 1 byte count excluding padding
    • padding1Max

      static int padding1Max(int version)
      Upper bound (exclusive) for the message 1 padding we send, per version, equal to i2pd's maxPaddingLength in CreateSessionRequestMessage: offset + 32 for a PQ frame, clamped so the whole message fits i2pd's 2528-byte handshake budget (944 + MLKEM-1024 key + 16), which is what pulls MLKEM-1024 back down to 880. Version 2 takes i2pd's short bound: i2pd only uses long padding once the peer advertises PQ support (its pq address option), and we reach versions 3-5 for exactly those peers, so a peer predating the 2026 padding increase (Java _X = 256, i2pd m_Buffer = 287) never sees more than 222 bytes from us. Taking i2pd's value for every version also keeps our handshake size distribution the same as theirs.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      exclusive upper bound for padlen1 (longest padding = value - 1)
    • padding1RecvMax

      static int padding1RecvMax(int version)
      Largest message 1 padding we accept, per session version. Never below padding1Max(int): i2pd's Alice drops to a non-PQ session one time in four (ClientLogin) but keeps long padding because the peer advertises PQ, so a version 2 session may legally carry 880 bytes of padding even though we would only ever send 223 there.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      inclusive maximum padlen1 a peer may announce
    • pqMsg2Extra

      static int pqMsg2Extra(int version)
      Extra bytes in message 2 for Bob's ML-KEM ciphertext and its MAC, 0 for the non-PQ version 2. Same Noise "F" token as message 1, with the ciphertext lengths instead of the encapsulation key lengths.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      the extra byte count, 0 for version 2
    • msg2Size

      static int msg2Size(int version)
      Full message 2 length (no padding) for the version: Y (32) + options (16) + options MAC (16) + PQ ciphertext frame.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      message 2 byte count excluding padding
    • padding2Max

      static int padding2Max(int version)
      Upper bound (exclusive) for the message 2 padding, in both directions, equal to i2pd's maxPaddingLength in CreateSessionCreatedMessage (offset + 32, clamped by the same 2528-byte budget). Bob applies it only when Alice used long padding, otherwise PADDING_SHORT_MAX.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      exclusive upper bound for padlen2 (longest padding = value - 1)
    • msg2PaddingMax

      static int msg2PaddingMax(int version, int padlen1)
      The padding cap for message 2, scaled to what Alice sent. i2pd's Bob uses long padding only when Alice's message 1 came in longer than the short handshake - ProcessSessionRequestMessage sets m_IsLongPadding - so a peer that kept its message 1 small gets a small answer back, and our reply lands in the same size class as i2pd's Bob.
      Parameters:
      version - the negotiated NTCP2 intro version
      padlen1 - the padding Alice announced in message 1
      Returns:
      exclusive upper bound for padlen2 (longest padding = value - 1)
    • msg1PayloadOffset

      static int msg1PayloadOffset(int version)
      Offset of the plaintext options block within the message 1 buffer. For versions 3-5 the Noise F token - the ML-KEM encapsulation key and its MAC - is written between X and the options, so the options have to be staged past it: writeMessage() lays down the F token first and only then reads the payload, so staging them at KEY_SIZE makes it encrypt the key material instead, and the peer gets a valid-looking tag over garbage options. For version 2 this is KEY_SIZE, as it always was.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      offset of the options block within message 1
    • msg1BufSize

      static int msg1BufSize(int version)
      Bytes needed to hold message 1 or its padding, whichever is longer. The inbound handshake buffer holds message 1 first, then is reused from offset 0 for the padding, so the two do not add up.
      Parameters:
      version - the NTCP2 intro version to size for
      Returns:
      buffer size in bytes
    • tmpSize

      static int tmpSize(int version)
      Bytes needed by the outbound handshake buffer. Message 1 and its padding are written back to back and sent as one block, so they add up; message 2 and its padding are each read from offset 0, so each only has to fit on its own.
      Parameters:
      version - the negotiated NTCP2 intro version
      Returns:
      buffer size in bytes
    • changeState

      private void changeState(OutboundNTCP2State.State state)
      Moves to the next handshake state.
      Parameters:
      state - the new state
    • receive

      public void receive(ByteBuffer src)
      Parse the contents of the buffer as part of the handshake.

      All data must be copied out of the buffer as Reader.processRead(NTCPConnection) will return it to the pool.

      Specified by:
      receive in interface EstablishState
      Parameters:
      src - the buffer to parse
    • isCorrupt

      public boolean isCorrupt()
      Did the handshake fail for some reason?
      Specified by:
      isCorrupt in interface EstablishState
      Returns:
      whether the handshake failed
    • isComplete

      public boolean isComplete()
      Don't synchronize this, deadlocks all over.
      Specified by:
      isComplete in interface EstablishState
      Returns:
      is the handshake complete and valid?
    • getVersion

      public int getVersion()
      NTCP version.
      Specified by:
      getVersion in interface EstablishState
      Returns:
      2-5
    • isInitialState

      public boolean isInitialState()
      Check if the connection is in initial state and ready for prepareOutbound().
      Returns:
      whether initial state
    • isPrepareOutboundCalled

      public boolean isPrepareOutboundCalled()
      Check if the connection is in a state where prepareOutbound() has already been called to avoid duplicate calls that cause IllegalStateException.
      Returns:
      whether prepare outbound called
    • prepareOutbound

      public void prepareOutbound()
      We are Alice. We are establishing an outbound connection, so prepare ourselves by writing the first message in the handshake. Encrypt X and write X, the options block, and the padding. Save last half of encrypted X as IV for message 2 AES.
      Specified by:
      prepareOutbound in interface EstablishState
      Throws:
      IllegalStateException - if the connection is not in the initial outbound state
    • receiveOutbound

      private void receiveOutbound(ByteBuffer src)
      We are Alice, so receive these bytes as part of an outbound connection. This method receives message 2, and sends message 3.

      IV (CBC from msg 1) must be in _bobIV

      All data must be copied out of the buffer as Reader.processRead(NTCPConnection) will return it to the pool.

      Caller must synch.

      Parameters:
      src - the buffer holding the received message 2 bytes
    • prepareOutbound3

      private void prepareOutbound3()
      We are Alice. Write the 3rd message.

      Caller must synch.

    • setDataPhase

      private void setDataPhase()
      NTCP 2 only. We are Alice. State machine for establishing outbound NTCP2 connections as Alice. Manages cryptographic handshake, key exchange, and transition to data phase.
    • generateSipHashKeys

      static byte[][] generateSipHashKeys(RouterContext ctx, HandshakeState state)
      KDF for SipHash.
      Parameters:
      ctx - the router context, supplying the HKDF implementation
      state - the handshake state whose chaining key and handshake hash are the KDF inputs
      Returns:
      rv[0] is sip_ab, rv[1] is sip_ba
    • close

      public void close(String reason, Exception e)
      Release resources on timeout.
      Specified by:
      close in interface EstablishState
      Parameters:
      reason - the text logged with the warning and returned by EstablishState.getFailReason()
      e - may be null
    • fail

      protected void fail(String reason)
      Fail the handshake with the given reason.
      Parameters:
      reason - stored as the fail reason and reported by getFailReason()
    • fail

      protected void fail(String reason, Exception e)
      Fail the handshake with the given reason and exception.
      Parameters:
      reason - stored as the fail reason and reported by getFailReason()
      e - the exception cause, may be null
    • fail

      protected void fail(String reason, Exception e, boolean bySkew)
      Fail the handshake, optionally skew-related.
      Parameters:
      reason - stored as the fail reason and reported by getFailReason()
      e - the exception cause, may be null
      bySkew - true to suppress the skew stat, as when we caused the skew ourselves
    • getFailReason

      public String getFailReason()
      Failure reason if the handshake failed.
      Specified by:
      getFailReason in interface EstablishState
      Returns:
      the failure reason, or null if not failed
    • releaseBufs

      private void releaseBufs(boolean isVerified)
      Only call once.

      Caller must synch.

      Parameters:
      isVerified - whether the handshake data was verified; currently unused
    • toString

      public String toString()
      String representation of this state.
      Overrides:
      toString in class Object