The dwnx programmers’ guide
This document describes a basic usage of dwnx library and common pitfalls which programmers might encounter.
Initialization
The dwnx_conn is an opaque object that corresponds to a single
QMux connection. If the endpoint is a client, use
dwnx_conn_client_new() to initialized it. For a server, use
dwnx_conn_server_new() instead.
These functions take the following objects:
The dwnx_callbacks stores the callbacks that are called
throughout the lifetime of the connection. The mandatory callbacks
are:
dwnx_callbacks.rand: This callback is called when the library needs the unpredictable bytes of data.
The remaining callbacks are optional.
The dwnx_settings stores the configuration of the QMux
connection. It should be first initialized by
dwnx_settings_default(). dwnx_settings.conn_id is the
identifier of this connection. This is not a part of QMux protocol,
and just used in logging purpose only to identify the particular
connection. dwnx_settings.initial_ts is the initial
timestamp passed to dwnx_conn. The timestamp is updated by
calling the functions that take dwnx_tstamp during the
connection lifetime.
The dwnx_transport_params stores the local QUIC transport
parameters that are sent to the remote endpoint. It should be
initialized by dwnx_transport_params_default(). For HTTP/3 server, at
least the following fields should be nonzero to allow the client to
create a stream and send data:
For HTTP/3 client, the following fields should be nonzero to allow the server to create a stream and send data:
dwnx_transport_params.max_record_size is basically ignored,
and the library always uses 16384.
Read the transport data
To pass the data read from the underlying reliable transport, call
dwnx_conn_read() with the current timestamp. The application can pass
the byte stream to this function without worrying about the QMux
record boundary. The function processes the data in streaming
fashion. If it returns the negative error code, close the connection.
After reading data, try writing data by calling
dwnx_conn_writev_stream(). See below.
Write a QMux record
To write a QMux record, call dwnx_conn_writev_stream(). It writes a
single QMux record to the given buffer. It can accept the stream ID
and its stream data to send. The application should handle the
following negative error code gracefully:
DWNX_ERR_STREAM_DATA_BLOCKED: The stream data cannot be sent due to the flow control limit. Try sending the stream data of the another stream if any.DWNX_ERR_STREAM_SHUT_WR: The send side of the stream has been closed. Try sending the stream data of the another stream if any.DWNX_ERR_WRITE_MORE: The application is allowed to write more stream data if any.
If the one of the above negative error codes are returned, call
dwnx_conn_writev_stream() again with the same dest and destlen
parameters.
In general, the application must keep calling
dwnx_conn_writev_stream() repeatedly until it returns the integer >=
0, or the negative error codes other than the ones listed above. If 0
is returned, no QMux record is produced, which means that there is no
data to send. If the integer > 0 is returned, the QMux record is
completely written to the given buffer and its length is the returned
value. If one of the negative error codes other than the ones listed
above is returned, close the connection.
When the stream data is written, the number of data written is
assigned to the object pointed by pdatalen. Because
dwnx_conn_writev_stream() may close the stream, when the function
returns, the stream has already been closed. To make sure that the
number of the written data is notified to the application, use
dwnx_callbacks.write_stream_data_offset callback.
The buffer size should be 16384 for the optimal performance if the underlying transport is TLS. The application may choose the smaller sized buffer while the congestion window is smaller than the maximum TLS record to avoid the additional round trip for decryption.
Connection close
The application can close the connection simply by closing the underlying transport connection.
It can also send CONNECTION_CLOSE frame with the error code and
the reason phrase. dwnx_conn_write_connection_close() writes this
final QMux record containing CONNECTION_CLOSE frame in the given
buffer. The application can send this final record, and then close
the underlying connection.
If the connection is closed because of idle timeout, it is strongly
recommended just close the underlying connection without sending
CONNECTION_CLOSE frame.
Timeout
To get the next timeout, call dwnx_conn_get_expiry(). It should be
called after reading or writing data. It is not necessary to call
this function each time after calling dwnx_conn_read() or
dwnx_conn_writev_stream(). In typical scenario, call dwnx_conn_read()
repeatedly until all data is drained from the socket, and then call
dwnx_conn_get_expiry(). Similarly, call dwnx_conn_writev_stream()
repeatedly to write the data until the write is blocked by the
underlying transport. Then call dwnx_conn_get_expiry().
When the timer has fired, call dwnx_conn_handle_expiry(). If it
returns DWNX_ERR_IDLE_CLOSE, the connection has been
quiescent too long. The underlying transport should be closed without
sending CONNECTION_CLOSE frame.