The first thing you must do (and only once) to use the library is initializing it, which you can do with a call to imquic_init. A successful initialization will return 0
, so a negative value being return will indicate something went wrong and the library won't be usable. The only argument it expects is an optional path to a file to use for storing the exchanged crypto secret: this is only needed in case you want to, e.g., use tools like Wireshark to debug the QUIC message exchanges even when encrypted, pretty much as other applications do when using the SSLKEYLOGFILE
format. For instance, initializing the stack like this:
if(imquic_init("/home/lminiero/mykeys.log") < 0) { // Oh no! exit(1); }
will store all crypto secrets for all QUIC sessions in the provided file. Using that file in the "Master-Secret" section of the "Protocols/TLS" section of Wireshark will allow you to see the QUIC exchanges as unencrypted, for debugging purposes.
When you're done with the application, a call to imquic_deinit will take care of the cleanup.
You can configure the logging level of the library with a call to imquic_set_log_level. The logging level can be tweaked dynamically, which means you can make that part configurable in your application, if you so prefer. The library comes with a few macros to allow you to log messages at different debugging levels: IMQUIC_PRINT
will always print the message, while IMQUIC_LOG
will only show the message if the log level associated to the message is lower or equal to the configured log level. Check debug.h for details.
Versioning information can be obtained with different methods: imquic_get_version returns a synthetic numeric version, that's computed from major, minor and patch version, and ensures that any bump results in a higher number; imquic_get_version_major, imquic_get_version_minor and imquic_get_version_patch, instead, return the individual major, minor and patch version numbers. The version is also available as a string in imquic_get_version_string, which serializes major, minor and patch numbers separating them with a dot (e.g., 0.0.1
). A release type of the current version (e.g., "alpha", "dev" or "stable") is provided in imquic_get_version_release. A complete representation of the current version as a string, which serialized version and release separated by a backslash, is instead available in imquic_get_version_string_full (e.g., 0.0.1/alpha
). To conclude, the imquic_get_build_time and imquic_get_build_sha functions provide info on the code itself, namely when this specific build of the library was compiled, and which git hash it refers to (which is useful when needing to figure out debugging information).
As far as using the library is concerned, it usually involves going through the following steps:
In a nutshell, this summarizes a typical usage of the library, and specifically the one we've used in all of our demo examples. Of course, multiple client and server can be created at the same time, each with their own callback functions if required.
Creating a new QUIC endpoint means either creating a new QUIC server, or a new QUIC client. You can create a new QUIC server using imquic_create_server, while on the other end imquic_create_client will create a client endpoint instead. Both use a variable argument approach to dictate what these endpoints should be like, specifically using a sequence of imquic_config key/value properties started with a IMQUIC_CONFIG_INIT
and ended by a IMQUIC_CONFIG_DONE
.
Whether you created a client or a server, you'll need to configure some callbacks to receive events (e.g., connections coming and going, or incoming data) before actually starting the endpoint with a call to imquic_start_endpoint.
Once an endpoint is live, the configured callbacks will be triggered on relevant events. A new client connecting to your server, or your client connecting to the server, will trigger a call to the callback function configured in imquic_set_new_connection_cb, with a pointer to the new imquic_connection instance associated to that specific connection. Further events associated to that connection will refer to the same instance, and using that pointer in active calls will allow you to interact with a connection (e.g., to send data, or close a connection). Considering the potentially multithread nature of the library and its use in your application, it's a good idea to increase a reference to the connection with imquic_connection_ref when you're first notified about it.
The imquic_set_stream_incoming_cb and imquic_set_datagram_incoming_cb functions allow you to configure callback functions to be notified about incoming data, on STREAM
and DATAGRAM
respectively. To send data, you can use imquic_send_on_stream and imquic_send_on_datagram instead. Notice that sending data on a STREAM
will only possible if the stack knows about it, which means that either it's a stream your peer created, or one you created yourself with imquic_new_stream_id.
The imquic_set_connection_gone_cb connection notifies you when a connection failed or is not available anymore: this can happens when the the server is unreachable, the peer closed it remotely, an error occurred within the library (e.g., a protocol violation in the communication), or you programmatically closed the connection with a call to imquic_close_connection. If you increased the reference to the connection when first notified about it, you should decrease it when you don't need it anymore, and the connection gone callback function is a good place to do that, since the library won't notify you about that connection anymore after that. Of course you must not remove the reference if you didn't increase it in the first place, e.g., if the connection was never established and so this callback was invoked to notify you that the connection failed.