Airhook Library

#define AIRHOOK_DEBUG 1 /* or 0; optional */
#include <airhook.h>

The AIRHOOK_DEBUG macro controls whether assertions are used in the Airhook implementation to catch programming errors on the part of the user and bugs in the library. If it is not set, the assertions are on by default unless the NDEBUG macro is defined.

Nothing in this header file or the Airhook library relies on anything not defined by ANSI C. The Airhook library performs no I/O; it is up to the client to provide the physical network transport. The Airhook library does not allocate memory; the function call interface is set up so all structures are allocated and managed by the client.

General-purpose data structures

struct airhook_time {
	unsigned long second;
	unsigned long nanosecond;

Airhook uses time values expressed in seconds since some fixed "epoch". This is often the standard Unix epoch (1 January 1970), but it doesn't have to be; Airhook only cares about relative time and never sends absolute time values over the network. Seconds since system boot, or any other consistent origin, would work just as well.

struct airhook_data {
	const unsigned char *begin;
	const unsigned char *end;

A sequence of bytes is represented by a pointer to the first byte (begin) and a pointer to one byte past the last (end). Please note that this structure merely points to data, and does not make any statements about its "ownership"; the data's lifetime must be managed independently.

enum airhook_state { ah_pending, ah_sent, ah_confirmed, ah_discarded };

These four values are used to represent the status of some network transaction:

Nothing has been sent; the remote host cannot have received the data.
The data has been sent at least once, but not confirmed; the remote host may or may not have received it.
The data has been sent and received by the remote host.
The data has been abandoned by the sender; it may or may not have been sent, and the remote host may or may not have seen it.


struct airhook_socket;
void airhook_init(struct airhook_socket *,unsigned long session);

Each struct airhook_socket represents one endpoint of an Airhook session and should be associated with a single network connection. (Normally each airhook_socket corresponds to a connected UDP/IP socket, but Airhook can be used with any network transport.)

The airhook_socket structure is opaque but not incomplete. (Clients should never access its fields directly, but its size is known.) The structure is allocated by the client and initialized by the library. Once initialized, it should never be copied or moved, and must remain allocated as long as it is in use.

A session number must be specified when an airhook_socket is initialized. This may be any number which is nonzero and which is different from the number of any previous or subsequent session which might share the same physical link. (That is, if someone restarts your program, the session number should be different.) Common practice is to use a value derived from the current time and/or process ID.

Airhook sessions aren't closed explicitly but rather abandoned. Clients may decide to stop using an airhook_socket, cease related network activity and deallocate all related structures at any point. (Clients usually have an application-level "I'm done now" message.) Since the Airhook library never allocates memory or creates any I/O handles itself, there is no need to notify the library when you stop using it.

struct airhook_status {
	struct airhook_settings settings;
	unsigned long session,remote_session;
	enum airhook_state state,remote_state;
	signed long wanted;
	struct airhook_time last_transmit;
	struct airhook_time next_transmit;
	struct airhook_time last_response;

struct airhook_status airhook_status(const struct airhook_socket *);

Clients may request the status of an Airhook socket any time after it has been initialized. The fields of the returned structure are as follows:

The current values of client-tunable parameters; see below. Initially set to "reasonable defaults".
The local session number, i.e. the session parameter passed to airhook_init.
The last known session number of the remote endpoint, or 0 if unknown. When this number changes, it means that the Airhook protocol has resynchronized with a new remote endpoint instance; most application protocols need to be reset when this happens. (It's also possible that you use session numbers which are somehow meaningful, but this is uncommon.)
Whether the local session number has been sent and confirmed with the remote endpoint. ("Do they know they're talking to us?".)
Whether receipt of the remote session number has been confirmed with the remote endpoint. ("Do they know we know they're talking to us?".) This is rarely of use, but included for completeness.
The number of additional bytes the Airhook congestion control algorithm would like to see transmitted. If this number is not greater than zero, the client is recommended to wait before transmitting more data. (If the client transmits data anyway, it will be queued.)
The last time a packet was transmitted.
The time in the future when Airhook would like to send another packet. If this time is before the present time, Airhook would like to send a packet immediately.
The time when the last packet whose receipt has been confirmed was transmitted. If data is being continuously sent, then the difference between last_response and the current time is the network round-trip time.
struct airhook_settings {
	struct airhook_time retransmit;
	unsigned long window_size;

void airhook_settings(struct airhook_socket *,struct airhook_settings);

Tunable parameters may be adjusted at any point after the Airhook socket has been initialized. The fields of this structure are as follows:

The interval between retransmission of "probe packets" when Airhook has not received a response from the remote host. Normally set to one second, may be adjusted to a smaller value for quick recovery or a larger value for very slow networks (such as two-way paging networks).
The Airhook congestion control algorithm currently uses a simple fixed window size, and this is it. Future versions will include adaptive window sizing (like TCP, but hopefully without misinterpreting packet loss as congestion). Should be set to your best guess at the bandwidth-delay product of the network you are using.

To work properly with future versions of the Airhook library (which may include additional parameters), clients should not build airhook_settings structures from scratch. Instead, clients should use the settings member of the airhook_status structure to get the current settings, modify them as needed, and then use airhook_settings to update the parameters.

Sending Messages

struct airhook_outgoing;
void airhook_init_outgoing(
	struct airhook_outgoing *outgoing,
	struct airhook_socket *socket,
	struct airhook_data data,void *user);

Messages are sent by initializing an airhook_outgoing structure. (Like airhook_socket, airhook_outgoing is opaque but not incomplete, and must be allocated by the client and initialized by the library.) The parameters are as follows:

An allocated, uninitialized airhook_outgoing structure. This structure must remain allocated and unmoved until the message is explicitly discarded (see airhook_discard_outgoing below).
An initialized airhook_socket structure. The message will be transmitted using this socket (see airhook_transmit below).
The data to transmit in this message, which must be at least 1 byte and no more than 255 bytes (use the airhook_size_maximum symbolic constant instead of the magic value 255, if you like). The data must remain allocated and valid until the message's reception is confirmed or the message is discarded.
An opaque pointer which is never used directly by the library but which is returned to the client via airhook_outgoing_status (see below).
struct airhook_outgoing_status {
	struct airhook_data data;
	void *user;
	enum airhook_state state;
	unsigned long transmit_count;
	struct airhook_time last_change;

struct airhook_outgoing_status airhook_outgoing_status(const struct airhook_outgoing *);

Clients may request the status of a message any time after its airhook_outgoing structure is initialized. This is particularly crucial because it indicates when the airhook_outgoing structure and message data may be deallocated. (See airhook_next_changed below to find out how to learn when the status of a message changes.) The fields of the returned structure are as follows:

The message data, exactly as passed to airhook_init_outgoing.
The opaque user pointer, exactly as passed to airhook_init_outgoing.
The state of the message. When (and only when) this is ah_confirmed (or ah_discarded), the airhook_outgoing_status structure as well as the message data may be deallocated without ruining the socket.
The number of times the message has been physically transmitted. (See airhook_transmit below.) For informational use.
The time when the state of this message last changed.
void airhook_discard_outgoing(struct airhook_outgoing *);

Clients may discard a message any time after its airhook_outgoing structure is initialized. Discarding the message cancels (re)transmission and allows its resources (the airhook_outgoing structure and the message data itself) to be reclaimed by the client.

Messages are typically discarded once their receipt is confirmed, but clients may discard messages sooner if they are deemed to be no longer worth sending.

It's safe (but pointless) to discard a message multiple times (but not after the structure is deallocated, of course).

int airhook_next_changed(struct airhook_socket *socket,struct airhook_outgoing **out);

Returns the airhook_outgoing structure corresponding to the next message whose state has changed since it was initialized or last returned by airhook_next_changed. If any messages meet this criteria, a pointer will be stored in *out and a nonzero value returned. If none have changed, zero will be returned and *out unmodified.

Typically, clients enumerate the changed messages after any network transaction (see airhook_receive and airhook_transmit below); if airhook_outgoing_status reports a state of ah_confirmed, the client calls airhook_discard_outgoing and deallocates the airhook_outgoing structure, the message data itself, and any associated resources.

size_t airhook_transmit(
	struct airhook_socket *socket,
	struct airhook_time now,
	size_t length,unsigned char *data);

When the time specified by next_transmit (in the structure returned by airhook_status) arrives, the client should call airhook_transmit to generate an actual packet to send. The client should then send that packet. The parameters are as follows:

The initialized airhook_socket structure to transmit from.
The current time.
The maximum length of the packet to send (i.e. the length of your buffer). Set this according to the MTU of the transmission medium you're using.
The packet buffer, provided by the application, of (at length) the length specified in length.

If the Airhook doesn't want to transmit anything, it will return zero (this hardly ever happens). If the value returned is greater than length, then the smallest possible packet is too big to fit in the buffer allocated; the client should allocate more space and try again. Otherwise, a packet has been stored in the buffer, and the value returned is its length; the client should send the data immediately.

Clients may call airhook_transmit before the time specified; the Airhook library will obligingly generate a packet and adapt. This can be used if the client wants to ensure a maximum delay between packets for "keepalive"; if there is no data to be sent, the library will generate a probe packet.

Receiving Messages

int airhook_receive(
	struct airhook_socket *socket,
	struct airhook_time now,
	struct airhook_data data);

Clients should call airhook_receive whenever a packet arrives on the wire. Clients are responsible for preserving the integrity of packet contents, but not guaranteeing delivery or ordering (UDP's native guarantees are fine). The parameters are as follows:

The initialized airhook_socket structure to receive to.
The time when the packet was received.
The contents of the packet. This data must remain valid until the client is done enumerating messages (see airhook_next_incoming below).

After calling airhook_receive, clients can (usually in this order) check to see if the socket status has changed in any exciting ways (with airhook_status), check to see if any messages have been confirmed (with airhook_next_changed), and check to see if any new messages have arrived (with airhook_next_incoming, below).

int airhook_next_incoming(struct airhook_socket *socket,struct airhook_data *data);

If the last packet processed with airhook_receive contained any incoming messages, airhook_next_incoming will return nonzero, store a reference to the message data in *data, and remove the message from the internal queue. The data is returned as a reference to the data last passed to airhook_receive, and will remain valid as long as that data does.

Typically, clients call airhook_next_incoming in a loop until it returns zero.

When airhook_receive is called again, the incoming queue is flushed, so clients should make sure to fully drain the queue after every packet is received.