src/lib/transport.h
branchnew-lib-errors
changeset 219 cefec18b8268
parent 196 873796250c60
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/transport.h	Thu May 28 01:17:36 2009 +0300
@@ -0,0 +1,179 @@
+#ifndef LIBQMSK_TRANSPORT_H
+#define LIBQMSK_TRANSPORT_H
+
+/**
+ * @file
+ *
+ * Defines a intermediate-level (as opposed to high-level or low-level) API for connected streams of data, presumeably
+ * non-blocking ones.
+ */
+#include "error.h"
+#include <stddef.h>
+
+/**
+ * Opaque transport state handle.
+ *
+ * Transports are reliable byte streams, connected with some endpoint over some medium. Common implementations are
+ * e.g. TCP, SSL or fifo transports (using the OS file/socket API).
+ *
+ * Transports can be connected or unconnected. For synchronous opens (e.g. fifo_open_read()), the transport returned
+ * will already be connected, meaning that the transport_callbacks::on_connect callback is unused. For async connects
+ * such as sock_tcp_connect()/sock_ssl_connect(), the transport returned is *not* connected, and you must wait for
+ * transport_callbacks::on_connect to be called before being able to send/recieve data on the transport.
+ *
+ * Once you have an opened transport, sending and receiving data is simple - just call transport_read()/transport_write().
+ * These implement unbuffered I/O, so they may do partial reads/writes. In terms of the system read/write calls, the
+ * main difference is in the error return codes. On EOF, instead of returning zero, they return ERR_TRANSPORT_EOF (or
+ * ERR_WRITE_EOF for transport_write, for whoever knows what that means...). This means that when the underlying
+ * transport is unable to fufill the request due to lack of data/buffer space, these can return zero to signifiy
+ * something simliar to EAGAIN.
+ *
+ * The transport API also implements non-blocking/event-based operation (usually on top of libevent), although at a
+ * slightly different level than the normal select/poll API. Instead of the user asking the transport to notify for
+ * read/write after transport_read/transport_write return zero, the transport will take care of this itself. 
+ * 
+ * Specifically, the user can supply a mask of events they are currently interested in. By default, this should be the
+ * full TRANSPORT_READ | TRANSPORT_WRITE, as the transport will take care of managing events by itself. If you wish to
+ * e.g. throttle read/write, you may set a different event mask using transport_events(), which will prevent the
+ * relevant callback from being triggered.
+ *
+ * For reads, the transport maintains a persistent read event, and will always call on_read when data is available on
+ * the socket (i.e. normal select() semantics). If masked out using transport_events(), there should be no event
+ * activity on the transport (i.e. the fd read event is removed).
+ *
+ * For writes, the transport maintains a write event that is disabled by default. If transport_write() returns zero, it will
+ * become enabled *once*, and consequently trigger transport_callbacks::on_write *once*, after which you must call
+ * transport_write() to possibly enable it again. If masked out using transport_events(), transport_write() will not
+ * enable the write event, and any pending write event is cancelled. If masked back in using transport_events(), the
+ * write event will *not* be registered, so if you have pending data, do a transport_write() after enabling
+ * TRANSPORT_WRITE.
+ *
+ * Note that transport_write() returning fewer bytes than given will *not* enable the write event! You must call
+ * transport_write() until you have either written all of your data, or it returns zero!
+ */
+struct transport;
+
+/**
+ * @see transport
+ */
+typedef struct transport transport_t;
+
+/**
+ * Errors
+ */
+enum transport_error_code {
+    ERR_TRANSPORT_NONE,
+    ERR_TRANSPORT_EOF,          ///< EOF
+    ERR_TRANSPORT_READABLE,     ///< transport not readable
+    ERR_TRANSPORT_WRITEABLE,    ///< transport not writeable
+};
+
+const struct error_list transport_errors;
+
+/**
+ * User callbacks for transports
+ *
+ * @see transport
+ */
+struct transport_callbacks {
+    /**
+     * The transport is now connected
+     */
+    void (*on_connect) (transport_t *transport, void *arg);
+
+    /**
+     * Data is now available for reading from the transport
+     */
+    void (*on_read) (transport_t *transport, void *arg);
+
+    /**
+     * The transport has become writeable
+     */
+    void (*on_write) (transport_t *transport, void *arg);
+
+    /**
+     * An asynchronous error has occured. This is only called for errors that occur while being called directly from
+     * the underlying event loop, and never from inside an API function.
+     *
+     * You must call transport_destroy to release the transport.
+     */
+    void (*on_error) (transport_t *transport, const error_t *err, void *arg);
+};
+
+/**
+ * Bitmask of available events
+ *
+ * @see transport
+ */
+enum transport_event {
+    TRANSPORT_READ  = 0x01,
+    TRANSPORT_WRITE = 0x02,
+};
+
+/**
+ * User info required to build a transport
+ *
+ * @see transport
+ */
+struct transport_info {
+    /** The callbacks table */
+    const struct transport_callbacks *cb_tbl;
+
+    /** The callback context argument */
+    void *cb_arg;
+
+    /** Initial event mask using transport_event flags */
+    short ev_mask;
+};
+
+/**
+ * Read a series of bytes from the transport into the given \a buf (up to \a len bytes). If succesfull, this returns
+ * the number of bytes read (which will be less than or equal to \a len). If the transport is nonblocking, and there is
+ * no data available, this returns zero, and need not be called again until transport_callbacks::on_read is invoked.
+ *
+ * On errors, this returns the negative error code, and more info via \a err. Note that as opposed to read(2), EOF is
+ * handled as an error, returning ERR_EOF.
+ *
+ * @param transport the transport state
+ * @param buf the buffer to read the bytes into
+ * @param len the number of bytes to read into the buffer
+ * @param err returned error info
+ * @return bytes read, zero if none available, -err_t
+ */
+int transport_read (transport_t *transport, void *buf, size_t len, error_t *err);
+
+/**
+ * Write a series of bytes from the given \a buf (containing \a len bytes) to the transport. If succesfull, this
+ * returns the number of bytes written (which may be less than \a len). If the transport is nonblocking, and the
+ * operation would have blocked, no data will be written, and zero is returned; in this case, the transport's write
+ * event is enabled (unless TRANSPORT_WRITE is masked out).
+ *
+ * On errors, this returns the negative error code, along with extended info via \a err.
+ *
+ * @param transport the transport state
+ * @param buf the buffer to write the bytes from
+ * @param len number of bytes to write
+ * @param err returned error info
+ * @return bytes written, zero if would have blocked, -err_t
+ */
+int transport_write (transport_t *transport, const void *buf, size_t len, error_t *err);
+
+/**
+ * Change the mask of enabled events.
+ */
+err_t transport_events (transport_t *transport, short mask);
+
+/**
+ * Install a new set of callback handlers, replacing the old ones.
+ */
+void transport_set_callbacks (transport_t *transport, const struct transport_callbacks *cb_tbl, void *cb_arg);
+
+/**
+ * Close and destroy the transport immediately, severing any established connection rudely.
+ *
+ * This will release all resources associated with the transport, including the transport itself, which must not be
+ * used anymore.
+ */
+void transport_destroy (transport_t *transport);
+
+#endif