Skip to content

Instantly share code, notes, and snippets.

@ry
Created October 30, 2009 05:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ry/222164 to your computer and use it in GitHub Desktop.
Save ry/222164 to your computer and use it in GitHub Desktop.
/* Copyright (c) 2009 Ryan Dahl <ry@tinyclouds.org>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#ifndef EVC_H_
#define EVC_H_
/* Compile with HAVE_GNUTLS=1 to use GnuTLS encryption. */
#ifndef HAVE_GNUTLS
# define HAVE_GNUTLS 0
#endif
#if HAVE_GNUTLS
# include <gnutls/gnutls.h>
#endif
enum evc_error
/* No error. */
{ EVC_ERROR_OK = 0
/* A system error occurred. May indicate a bug in EVC. */
, EVC_ERROR_SYS
/* Connection reset by peer. */
, EVC_ERROR_CONNRESET
/* For Unix domain streams, permission denied. */
, EVC_ERROR_ACCESS
/* GnuTLS error. Use evc_stream_tls_error() to access the error code. */
, EVC_ERROR_TLS
};
enum evc_transport_status
{ EVC_TRANSPORT_NEEDS_READ = 0x0001
, EVC_TRANSPORT_NEEDS_WRITE = 0x0002
, EVC_TRANSPORT_DONE = 0x0004
};
/*
An evc_transport has many evc_streams and evc_servers. It provides
efficient idle-timeouts for streams as well as a callback for interfacing
with the event-loop.
*/
struct evc_transport;
typedef struct evc_transport evc_transport;
/*
One of these callbacks is accoiated with each evc_transport.
- 'data' is pointer given as the last argument to evc_transport_new()
- 'status' has one of the following values:
EVC_TRANSPORT_NEEDS_READ
EVC_TRANSPORT_NEEDS_WRITE
EVC_TRANSPORT_NEEDS_READ | EVC_TRANSPORT_NEEDS_WRITE
EVC_TRANSPORT_DONE
- of the last two arguments, only one will be filled, but not both.
The user needs to wait for the obj1 or obj2 to become either readable or
writeable before continuing.
*/
typedef void (*evc_transport_cb)(void *data,
int status,
evc_server * server,
evc_stream * stream);
/*
Create a new transport.
The first argument is the timeout. The timeout argument specifies the idle
timeout for all evc_streams in the transport, in seconds. A non-positive
value for timeout will disable idle timeouts in this transport.
The second argument is a callback. The third is a pointer which will be
passed to the callback.
*/
evc_transport * evc_transport_new (double timeout,
evc_transport_cb cb,
void * data);
/*
Processes timeouts for evc_streams, invoking EVC_STREAM_TIMEOUT events for
streams which have reached their idle limit.
Returns the time in seconds until the next evc_stream times out. The user
should call this function again after that many seconds.
A negative return value means zero active evc_stream are on the transport.
*/
double evc_transport_timeout (evc_transport *);
/*
Destroys a transport, frees memory associated with it. Will close all
streams belonging to this transport.
*/
void evc_transport_destroy (evc_transport *);
/*
This callback is made each time a new connection appears on the server.
The user is expected to examine the remote address and decide if the
connection is allowed. If so, the user must allocate and initialize (with
evc_stream_init()) an evc_stream object and return a pointer to it.
Returning NULL from this callback rejects the connection.
This returned evc_stream will be filled with the connection information
and attached to its transport during this call. It will begin receiving data
immediately after. The EVC_STREAM_CONNECTED event will be issued for the
given stream directly following this callback.
*/
typedef evc_stream * (*evc_server_cb)(evc_server *,
struct sockaddr *addr,
socklen_t addrlen);
struct evc_server {
/* public */
evc_server_cb cb;
void * data;
/* read-only */
int fd;
evc_transport * transport;
/* private */
int connection_fd;
};
typedef evc_server evc_server;
void evc_server_init(evc_server *,
evc_transport *transport,
evc_server_cb cb,
void * data);
void evc_server_process(evc_server *);
/*
Destroy all references to this object immediately. No more callbacks will
be issued. If open, the server will stop accepting connections. You may
free the memory associated with the server immediately following a call to
this method.
*/
void evc_server_release(evc_server *);
/* Listen and bind to the given address. */
int evc_server_listen(evc_server *,
struct sockaddr *addr,
socklen_t addrlen,
int backlog);
/*
Stops listening for new connections, closes the file descriptor associated
with the server. The server will be inactive after this call. To reuse
the object, evc_server_init() must be called again. It is okay to free the
memory associated with the server after this call.
*/
void evc_server_close(evc_server *);
enum evc_stream_event
/*
The EVC_STREAM_ACTIVE event occurs when the stream becomes first
attached to the event loop. In the case of a client-side stream, this
happens when evc_stream_connect() is called; for a server-side stream,
this event is emitted, directly after connection is accepted. The
object's memory cannot be freed until EVC_STREAM_INACTIVE is emitted or
evc_stream_release() is called.
*/
{ EVC_STREAM_ACTIVE,
/*
EVC_STREAM_CONNECTED is emitted once the stream becomes initially
usable. For a server-side stream, this happens immediately after
evc_server_accept(). For client-side streams, it may take some time to
establish the connection and this event is only emitted once the 3-way
TCP handshake is complete.
This event is a good time to issue a evc_stream_start_tls() command.
*/
EVC_STREAM_CONNECTED,
/*
HANDSHAKE_COMPLETE is emitted only after evc_stream_start_tls() has
been called. This is useful to examine TLS credentials before
continuing to communicate.
*/
EVC_STREAM_HANDSHAKE_COMPLETE,
/*
EVC_STREAM_READABLE is emitted whenever incoming data is present. It is
edge-triggered; that is, it will only be called once when the incoming
data buffer's state changes from 'empty' to 'non-empty'. If you fail to
process all of the incoming data in this event, no more
EVC_STREAM_READABLE events will be emitted. It is expected that you
will drain the incoming data buffer.
Use evc_stream_read() to retrieve incoming data.
Use evc_is_readable() to efficiently check if data is present.
*/
EVC_STREAM_READABLE,
/*
This event is made when a stream becomes writable. It is edge-triggered;
that is, it will only be called once when the stream changes state from
unwritable to writable. This event is necessary because
evc_stream_write() may not be able to send all of the data given to it.
In that case you must queue up data until this event is made and then
call evc_stream_write() again. Use evc_is_writable() to efficiently
check at any point if a stream is writable.
*/
EVC_STREAM_WRITABLE,
/*
This event is issued when a TCP FIN packet or an EOF for UNIX streams
is received. This event will be called exactly once per stream. Writing
to the stream is still allowed, but you should be sure the other end
knows how to consume input after having sent an FIN packet.
*/
EVC_STREAM_EOF,
/*
Emitted when the idle timeout is reached. The idle timeout is defined by
the evc_transport that the evc_stream belongs to.
*/
EVC_STREAM_TIMEOUT,
/*
This event occurs when the stream will no longer emit any events. It is
emitted exactly once per stream. It is safe to destroy the memory
associated with the stream during the callback issuing this event. Any
open stream which becomes inactive is closed. The stream may become
inactive for any number of reasons, including errors.
If a stream is in a half-closed state (readable but not writable) and
receives a FIN packet from the peer, the stream will be closed and this
event emitted as an indication that the connection is completely dead.
Use evc_stream_error() and evc_stream_tls_error() to check errors.
*/
EVC_STREAM_INACTIVE };
typedef void (*evc_stream_cb)(evc_stream *, enum evc_stream_event);
struct evc_stream {
/* public */
evc_stream_cb cb;
void * data;
/* read-only */
int fd;
double last_activity;
evc_transport * transport;
/* private */
evc_stream * prev;
evc_stream * next;
long flags;
enum evc_error errorno;
#if HAVE_GNUTLS
int gnutls_errorno;
gnutls_session_t tls_session;
#endif
};
typedef struct evc_stream evc_stream;
/*
Constructor. This must be called before any other methods are called on
the object. In particular, this must be called before the object is passed
to evc_server_accept().
The second parameter specifies a transport to add the stream to - the
transport defines the idle timeout a stream will have.
Calling this function does not open any connection or attach anything to
the event loop.
*/
void evc_stream_init(evc_stream *stream,
evc_transport *transport,
evc_stream_cb cb,
void * data);
void evc_stream_process(evc_stream *, int got_read, int got_write);
/*
Destructor. Call this method at any time to tell evc to never access the
stream's memory again. EVC will close the associated file descriptor if it
is open. EVC_STREAM_INACTIVE will be emitted during the call of this
method.
*/
void evc_stream_release(evc_stream *);
/*
The three methods
evc_stream_open_addr(),
evc_stream_open_read(),
evc_stream_open_write()
activate an initialized evc_stream to a transport.
*/
/* Connects the stream to the specified TCP or UNIX address. */
int evc_stream_open_addr(evc_stream *,
struct sockaddr * address,
socklen_t addrlen);
int evc_stream_open_read(evc_stream *, int fd);
int evc_stream_open_write(evc_stream *, int fd);
/*
Read incoming data from a stream. The second argument is a buffer to read
into, the third argument is the size of that buffer. A successful read
will return the number of bytes read.
If no data is available this function will return 0. The user should wait
until a EVC_STREAM_READABLE event occurs to try again. The
EVC_STREAM_READABLE event will not be triggered until this function is
called and returns 0. The user is encouraged to drain the incoming data on
each EVC_STREAM_READABLE event.
IMPORTANT NOTE: Unlike POSIX read(), this function does not indicate EOF
by returning 0. EOF is indicated by the EVC_STREAM_EOF event.
*/
size_t evc_stream_read(evc_stream *, const char * buf, size_t * len);
ssize_t evc_stream_write(evc_stream *, const char * buf, size_t len);
/*
Sends a FIN packet for TCP stream or an EOF on a UNIX stream. The
connection is half-closed after calling this. Further writing is not
allowed, but further reading is allowed until the EVC_STREAM_EOF event is
fired indicating that the peer has also closed the connection.
*/
void evc_stream_close(evc_stream *);
/*
Start a TLS session with the given argument. This method can only be
called after EVC_STREAM_CONNECTED is emitted.
EVC_STREAM_HANDSHAKE_COMPLETE is emitted after the TLS handshake is
complete, and evc_stream_tls_error() can be used to check for any TLS
errors after EVC_STREAM_INACTIVE.
*/
void evc_stream_start_tls(evc_stream *, gnutls_session_t session);
static inline enum evc_error evc_stream_error (evc_stream * stream) {
return stream->errorno;
}
static inline int evc_stream_tls_error (evc_stream * stream) {
return stream->gnutls_errorno;
}
enum evc_flags /* Internal use. */
{ EVC_ACTIVE = 0x0001
, EVC_READABLE = 0x0002
, EVC_WRITABLE = 0x0004
, EVC_CONNECTED = 0x0010
};
#define evc_is_active(s) ((s)->flags & EVC_ACTIVE)
#define evc_is_readable(s) ((s)->flags & EVC_READABLE)
#define evc_is_writable(s) ((s)->flags & EVC_WRITABLE)
#define evc_is_connected(s) ((s)->flags & EVC_CONNECTED)
#endif /* EVC_H_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment