Class QuicConnection

java.lang.Object
io.github.mahmoudimus.quic.QuicConnection
All Implemented Interfaces:
AutoCloseable

public final class QuicConnection extends Object implements AutoCloseable
A QUIC connection with a blocking API made for virtual threads: open and accept streams, read and write them as InputStreams and OutputStreams, send and receive datagrams. Operations block while flow control, the stream limits or the peer hold them up, and fail with a QuicConnectionException once the connection is gone.

Connections come from QuicServer.accept() and QuicClient.connect(java.net.InetSocketAddress). Each one is driven by a virtual thread of its endpoint; any number of threads may use a connection and its streams at once.

Migration. A server's connection follows a client that moves to a new address (a NAT rebinding, or a client that migrates), validating the new path first, unless its configuration forbids migration; remoteAddress() then changes. A client connection moves with migrate(java.net.InetSocketAddress) (probing the new path first) or rebind(java.net.InetSocketAddress) (at once), and to a server's preferred address on its own.

0-RTT. A connection from QuicClient.connectEarly(java.net.InetSocketAddress, java.lang.String, java.time.Duration) may still be handshaking: its streams can be opened and written at once, and what is written goes out as 0-RTT data. A server that accepted 0-RTT hands the connection out before its handshake completes; data that arrived as early data (QuicStream.receivedEarlyData()) may have been replayed by an attacker.

  • Method Details

    • awaitHandshake

      public boolean awaitHandshake(Duration timeout) throws IOException
      Waits for the handshake to complete: for a connection from QuicClient.connectEarly(java.net.InetSocketAddress, java.lang.String, java.time.Duration) or one a server accepted with 0-RTT data. Returns at once for other connections.
      Parameters:
      timeout - how long to wait, or null for no limit
      Returns:
      true if the handshake completed, false on timeout
      Throws:
      QuicConnectionException - if the connection ended first
      InterruptedIOException - if interrupted while waiting
      IOException
    • isHandshakeComplete

      public boolean isHandshakeComplete()
      Whether the handshake has completed.
      Returns:
      true once complete
    • isResumed

      public boolean isResumed()
      Whether the TLS handshake resumed an earlier session from a ticket.
      Returns:
      true if resumed (a client knows once the server answered)
    • earlyDataAccepted

      public boolean earlyDataAccepted()
      Whether the server accepted 0-RTT data: on a client, known once the handshake completes; on a server, from the start.
      Returns:
      true if accepted
    • isZeroRtt

      public boolean isZeroRtt()
      Client: whether data written now goes out as 0-RTT data, before the handshake completes.
      Returns:
      true while sending 0-RTT data
    • openBidirectionalStream

      public QuicStream openBidirectionalStream() throws IOException
      Opens a bidirectional stream, waiting while the peer's stream limit is reached. Nothing is sent until data is written.
      Returns:
      the stream
      Throws:
      QuicConnectionException - if the connection is gone
      InterruptedIOException - if interrupted while waiting
      IOException
    • openUnidirectionalStream

      public QuicStream openUnidirectionalStream() throws IOException
      Opens a unidirectional (send-only) stream, waiting while the peer's stream limit is reached.
      Returns:
      the stream
      Throws:
      QuicConnectionException - if the connection is gone
      InterruptedIOException - if interrupted while waiting
      IOException
    • acceptStream

      public QuicStream acceptStream() throws IOException
      Waits for a stream the peer opened, bidirectional or unidirectional (receive-only).
      Returns:
      the stream
      Throws:
      QuicConnectionException - if the connection is gone
      InterruptedIOException - if interrupted while waiting
      IOException
    • sendDatagram

      public void sendDatagram(byte[] data) throws IOException
      Sends an unreliable datagram (RFC 9221). When the send buffer is full the oldest queued datagrams are dropped.
      Parameters:
      data - the payload, at most maxDatagramSize() bytes
      Throws:
      QuicConnectionException - if the connection is gone
      IllegalStateException - if either side does not accept datagrams
      IllegalArgumentException - if the payload is too large
      IOException - if the connection is gone
    • receiveDatagram

      public byte[] receiveDatagram() throws IOException
      Waits for a datagram from the peer.
      Returns:
      the payload
      Throws:
      QuicConnectionException - if the connection is gone
      InterruptedIOException - if interrupted while waiting
      IOException
    • receiveDatagram

      public byte[] receiveDatagram(Duration timeout) throws IOException
      Waits for a datagram from the peer, up to a timeout.
      Parameters:
      timeout - how long to wait, or null to wait without a limit
      Returns:
      the payload, or null if none arrived in time
      Throws:
      QuicConnectionException - if the connection is gone
      InterruptedIOException - if interrupted while waiting
      IOException
    • maxDatagramSize

      public int maxDatagramSize()
      The largest datagram payload sendDatagram(byte[]) takes now.
      Returns:
      bytes, or -1 if the peer does not accept datagrams
    • updateKeys

      public boolean updateKeys()
      Starts a key update (RFC 9001 section 6). Keys are also updated routinely.
      Returns:
      true if an update started; false while the previous one is unconfirmed
    • close

      public void close(long code, String reason)
      Closes the connection with an application error code: CONNECTION_CLOSE is sent at once and streams fail. Data not yet acknowledged is abandoned; finish streams and wait for the peer's reply first to deliver it.
      Parameters:
      code - the application error code
      reason - the reason phrase
    • close

      public void close()
      Closes the connection with code 0 and no reason.
      Specified by:
      close in interface AutoCloseable
    • awaitClosed

      public ConnectionError awaitClosed(Duration timeout) throws InterruptedIOException
      Waits until the connection has ended, for any reason.
      Parameters:
      timeout - how long to wait, or null for no limit
      Returns:
      why it ended, or null on timeout
      Throws:
      InterruptedIOException - if interrupted while waiting
    • isOpen

      public boolean isOpen()
      Whether the connection is open.
      Returns:
      false once it is closing or gone
    • closeReason

      public ConnectionError closeReason()
      Why the connection ended.
      Returns:
      the reason, or null while it is open
    • alpn

      public String alpn()
      The application protocol agreed with ALPN.
      Returns:
      the protocol
    • serverName

      public String serverName()
      The server name: the one a client connected to, or the client's SNI on a server.
      Returns:
      the name, or null if the client sent none
    • peerCertificates

      public List<X509Certificate> peerCertificates()
      The peer's certificate chain, end-entity first.
      Returns:
      the chain; empty if the peer sent none (a client without a certificate)
    • cipherSuite

      public CipherSuite cipherSuite()
      The negotiated cipher suite.
      Returns:
      the suite
    • keyExchangeGroup

      public NamedGroup keyExchangeGroup()
      The key exchange group the handshake used.
      Returns:
      the group
    • remoteAddress

      public InetSocketAddress remoteAddress()
      The peer's address.
      Returns:
      the address
    • localAddress

      public InetSocketAddress localAddress()
      The local address the connection sends from: the endpoint's socket, a socket of its own after it migrated, or a server's preferred address once its client moved there.
      Returns:
      the address
    • migrate

      public InetSocketAddress migrate(InetSocketAddress local) throws IOException
      Client: moves the connection to a new local address (RFC 9000 section 9), waiting until it has. A UDP socket is bound at local; the connection probes the path from there to the server with PATH_CHALLENGE and a connection ID of the server's it has not used, and switches to the path when the server answers. Its streams carry on meanwhile on the old path; the old socket is closed once no connection uses it (a client's first socket then gives way to the new one for later connections). The congestion and RTT state carry over when only the port changes, and start over otherwise. A new port on the same address is what a NAT rebinding looks like to the server.
      Parameters:
      local - the address to bind, port 0 for an ephemeral port
      Returns:
      the new local address, with its port
      Throws:
      IOException - if the socket cannot be bound; if the connection cannot migrate (a server's, one still handshaking, one where either side disabled active migration, one whose server gave no unused connection ID, one on an endpoint without a socket); or if the server did not answer on the new path (the connection stays where it was)
      QuicConnectionException - if the connection ended meanwhile
    • migrate

      public InetSocketAddress migrate(DatagramChannel channel) throws IOException
      Client: moves the connection to a socket of the caller's, as migrate(InetSocketAddress) does with one it binds. The connection owns the channel from now on, and closes it when it no longer uses it.
      Parameters:
      channel - a bound socket
      Returns:
      the new local address
      Throws:
      IOException - if the channel is not bound, or as migrate(InetSocketAddress)
    • rebind

      public InetSocketAddress rebind(InetSocketAddress local) throws IOException
      Client: moves the connection to a new local address at once, without probing the path first, as quinn's endpoint does when it rebinds: packets leave from a UDP socket bound at local from now on, with a connection ID of the server's not used before when it gave one, and the server follows as it follows a NAT rebinding, validating the new path itself. Use it with servers that never answer migrate(java.net.InetSocketAddress)'s probe on any path (aioquic's queues the response on the probed path and sends only on its active one). The old socket is closed once no connection uses it.
      Parameters:
      local - the address to bind, port 0 for an ephemeral port
      Returns:
      the new local address, with its port
      Throws:
      IOException - if the socket cannot be bound, or the connection cannot migrate (as for migrate(java.net.InetSocketAddress), except that the server need not have given an unused connection ID)
      QuicConnectionException - if the connection ended meanwhile
    • initialDestinationId

      public ConnectionId initialDestinationId()
      The Destination Connection ID of the client's first Initial packet (after a Retry, of its first Initial with the Retry's token): on a server, the ID the connection was created for.
      Returns:
      the ID
    • version

      public QuicVersion version()
      The QUIC version in use.
      Returns:
      the version
    • stats

      public QuicConnection.Stats stats()
      A snapshot of the connection's statistics. The connection keeps them as plain fields that its own thread updates, so taking a snapshot costs a hold of the connection's lock and nothing else does.
      Returns:
      the statistics
    • toString

      public String toString()
      Overrides:
      toString in class Object