java.lang.Object
net.i2p.router.transport.ntcp.OutboundNTCP2State
- All Implemented Interfaces:
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 ClassesModifier and TypeClassDescriptionprivate static enumState of the outbound NTCP2 handshake.Field Summary
Modifier and TypeFieldDescriptionprivate final RouterInfoAlice's router info.private final intSize of Alice's router info, in bytes.private final BanLoggerRecords why routers were banned.private final SessionKeyHash of Bob's handshake.private final byte[]IV of Bob's handshake.private final NTCPConnectionThe connection being established.private final RouterContextThe router context.private StringReason the handshake failed, null until then.private final HandshakeStateHandshake state for the ratchet keys.private final LogLogger for the NTCP connection.private intPadding length of message 2.private final intPadding length of message 3.private longOur clock skew to the peer, in ms.private intBytes received so far.private OutboundNTCP2State.StateCurrent state of the handshake.private final ObjectLock for the handshake state.private final byte[]Scratch buffer for the handshake fields.private final NTCPTransportThe NTCP transport we serve.private final intVersion of the handshake.static final intIV length in bytes, taken from the tail of the static key (16).static final intStatic key length in bytes (32).static final intMAC length in bytes, appended to each encrypted block (16).static final longIn SECONDS.(package private) static final intThe short handshake message total used by i2pd (287).static final intNTCP2 message 1 size in bytes (64).static final intNTCP2 message 2 size in bytes.static final intNTCP2 message 3 part 1 size in bytes (48).static final intOptions block size in bytes.static final intOptions block size in bytes.private static final intSize of the options field in message 3.(package private) static final intExclusive upper bound for the "short" handshake padding, i2pd's 287 - 64 (223).private static final intLargest 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
ConstructorsConstructorDescriptionOutboundNTCP2State(RouterContext ctx, NTCPTransport transport, NTCPConnection con) Create a new outbound NTCP2 state.Method Summary
Modifier and TypeMethodDescriptionprivate voidMoves to the next handshake state.voidRelease resources on timeout.protected voidFail the handshake with the given reason.protected voidFail the handshake with the given reason and exception.protected voidFail the handshake, optionally skew-related.(package private) static byte[][]generateSipHashKeys(RouterContext ctx, HandshakeState state) KDF for SipHash.Failure reason if the handshake failed.intNTCP version.booleanDon't synchronize this, deadlocks all over.booleanDid the handshake fail for some reason?booleanCheck if the connection is in initial state and ready forprepareOutbound().booleanCheck if the connection is in a state where prepareOutbound() has already been called to avoid duplicate calls that cause IllegalStateException.(package private) static intmsg1BufSize(int version) Bytes needed to hold message 1 or its padding, whichever is longer.(package private) static intmsg1PayloadOffset(int version) Offset of the plaintext options block within the message 1 buffer.(package private) static intmsg1Size(int version) Full message 1 length (no padding) for the version: X (32) + options (16) + options MAC (16) + PQ frame.(package private) static intmsg2PaddingMax(int version, int padlen1) The padding cap for message 2, scaled to what Alice sent.(package private) static intmsg2Size(int version) Full message 2 length (no padding) for the version: Y (32) + options (16) + options MAC (16) + PQ ciphertext frame.(package private) static intpadding1Max(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 intpadding1RecvMax(int version) Largest message 1 padding we accept, per session version.(package private) static intpadding2Max(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 intpqMsg1Extra(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 intpqMsg2Extra(int version) Extra bytes in message 2 for Bob's ML-KEM ciphertext and its MAC, 0 for the non-PQ version 2.voidWe are Alice.private voidWe are Alice.voidreceive(ByteBuffer src) Parse the contents of the buffer as part of the handshake.private voidWe are Alice, so receive these bytes as part of an outbound connection.private voidreleaseBufs(boolean isVerified) Only call once.private voidNTCP 2 only.(package private) static inttmpSize(int version) Bytes needed by the outbound handshake buffer.toString()String representation of this state.
Field Details
_context
The router context._log
Logger for the NTCP connection._banLogger
Records why routers were banned._transport
The NTCP transport we serve._con
The connection being established._tmp
private final byte[] _tmpScratch buffer for the handshake fields._received
private int _receivedBytes received so far._peerSkew
private long _peerSkewOur clock skew to the peer, in ms.KEY_SIZE
MAC_SIZE
public static final int MAC_SIZEMAC length in bytes, appended to each encrypted block (16).- See Also:
IV_SIZE
public static final int IV_SIZEIV length in bytes, taken from the tail of the static key (16).- See Also:
OPTIONS1_SIZE
MSG1_SIZE
MSG_SHORT_MAX
static final int MSG_SHORT_MAXThe 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_MAXExclusive 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_MAXLargest padding length in message 3.- See Also:
OPTIONS2_SIZE
MSG2_SIZE
MSG3P1_SIZE
public static final int MSG3P1_SIZENTCP2 message 3 part 1 size in bytes (48).- See Also:
OPTIONS3_SIZE
private static final int OPTIONS3_SIZESize of the options field in message 3.- See Also:
MAX_SKEW
ZEROLEN
private static final byte[] ZEROLENShared empty array for zero-length values.ZEROKEY
public static final byte[] ZEROKEYAll-zero key marking a static key or IV as unset.SIPHASH
private static final byte[] SIPHASHFor SipHash keygen._stateLock
Lock for the handshake state._state
Current state of the handshake._failReason
Reason the handshake failed, null until then._handshakeState
Handshake state for the ratchet keys._aliceRI
Alice's router info._aliceRISize
private final int _aliceRISizeSize of Alice's router info, in bytes._padlen2
private int _padlen2Padding length of message 2._padlen3
private final int _padlen3Padding length of message 3._bobHash
Hash of Bob's handshake._bobIV
private final byte[] _bobIVIV of Bob's handshake._version
private final int _versionVersion of the handshake.
Constructor Details
OutboundNTCP2State
Create a new outbound NTCP2 state.- Parameters:
ctx- supplies the log and the ban loggertransport- supplies the XDH factory and the transport settingscon- 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 belowpadding1Max(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, otherwisePADDING_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 versionpadlen1- 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
Moves to the next handshake state.- Parameters:
state- the new state
receive
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:
receivein interfaceEstablishState- Parameters:
src- the buffer to parse
isCorrupt
public boolean isCorrupt()Did the handshake fail for some reason?- Specified by:
isCorruptin interfaceEstablishState- Returns:
- whether the handshake failed
isComplete
public boolean isComplete()Don't synchronize this, deadlocks all over.- Specified by:
isCompletein interfaceEstablishState- Returns:
- is the handshake complete and valid?
getVersion
isInitialState
public boolean isInitialState()Check if the connection is in initial state and ready forprepareOutbound().- 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:
prepareOutboundin interfaceEstablishState- Throws:
IllegalStateException- if the connection is not in the initial outbound state
receiveOutbound
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
KDF for SipHash.- Parameters:
ctx- the router context, supplying the HKDF implementationstate- 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
Release resources on timeout.- Specified by:
closein interfaceEstablishState- Parameters:
reason- the text logged with the warning and returned byEstablishState.getFailReason()e- may be null
fail
Fail the handshake with the given reason.- Parameters:
reason- stored as the fail reason and reported bygetFailReason()
fail
Fail the handshake with the given reason and exception.- Parameters:
reason- stored as the fail reason and reported bygetFailReason()e- the exception cause, may be null
fail
Fail the handshake, optionally skew-related.- Parameters:
reason- stored as the fail reason and reported bygetFailReason()e- the exception cause, may be nullbySkew- true to suppress the skew stat, as when we caused the skew ourselves
getFailReason
Failure reason if the handshake failed.- Specified by:
getFailReasonin interfaceEstablishState- 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