Streams Library version 1.3

Copyright (C) Angus J. C. Duggan 1997-1998

This library is used by several of my programs, including my NNTP buffer and
SMTP buffer Windows NT services. It provides abstractions of socket and file
streams, some common network protocol methods, and some other useful stream
abstractions.


INSTALLATION:

The library should be compiled and installed in a publically-accessible
directory on your LIB path. The include file streams.h should be placed in a
publically-accessible directory on you INCLUDE path.


INTERFACING:

Most functions in the library return an invalid result when they fail, and
use GetLastError (Win32) or errno (Unix/POSIX) to return an error code. Some
streams allow an error callback function to be supplied, which is used for
non-fatal errors which don't return invalid results. The error callback is
called with the stream, an error code, and parameters specific to the error.
The pre-defined streams define two values for the code,
SOCKSTREAM_ERROR_CLOSE and FILESTREAM_ERROR_CLOSE, which indicate that a
close socket on a socket stream or a close file on a filestream failed. The
parameters to these are the handle of the file or socket that had the error,
and the actual error code returned by GetLastError()/errno.

If you are using a debug compilation of the library, you will need to provide
the following functions:

	void stream_debugstr(long cookie, char *string, int length) ;
	void stream_debug(long cookie, char *format, ...) ;

The cookies are values that get passed back to the stream_debug*
routines to identify where the message originated. stream_debugstr is called
with a cookie, a string and a length, which can be used for writing out debug
information. stream_debug takes a printf-style format string and can be used
to write out debug information. It should support "%d" and "%s" formats at
the minimum. The following cookies are used by the pre-defined streams:

	SOCKSTREAM_DEBUG	0x524f434b /* 'SOCK' */
	FILESTREAM_DEBUG	0x46494c45 /* 'FILE' */
	STDIOSTREAM_DEBUG	0x5354494f /* 'STIO' */
	LINESTREAM_DEBUG_READ	0x4c494e52 /* 'LINR' */
	LINESTREAM_DEBUG_WRITE	0x4c494e57 /* 'LINW' */


LIBRARY:

The library provides the following types:

Stream
	This is an opaque type used for passing arguments to and from library
	functions. In general, you should let the library deal with
	allocating and deallocating these.

The library provides the following generic stream functions:

NewStream
	Stream *NewStream(Stream *this, Stream *under, short subclassbytes,
			  Handler reader, Handler writer, Ioctl ioctl,
			  Error error, Destructor destructor) ;

	typedef long Handler(Stream *this, void *buffer, long bufsize) ;
	typedef long Ioctl(Stream *this, int op, va_list args) ;
	typedef void Error(Stream *this, int err, va_list args) ;
	typedef void Destructor(Stream *this) ;

	This function creates a new stream, or initialises an existing Stream
	variable. "under" is the underlying stream, which the handlers
	"reader" and "writer" will read from. "ioctl" is a specialised I/O
	handler, "error" is an error callback for non-fatal errors, and
	"destructor" is a specialised function to destroy the stream.
	"subclassbytes" is the number of bytes required in a subclassed
	stream structure.

	In general, you should not call this function directly, but wrap it
	in an abstraction layer such as the MakeSocketStream or
	MakeFileStream below.

	If an existing stream is supplied, it *must* have been originally
	created by NewStream.

	The reader and writer handlers get called with the stream itself, and
	the buffer and length parameters given to the ReadStream or
	WriteStream function. They return the number of bytes read or written
	to or from the stream. They should return -1 on error, and use
	SetLastError/errno to set an appropriate error code.

	The ioctl handler gets called with the stream, an operation, and a
	va_args list. It should ignore the standard ioctl codes if it does
	not want to prevent them. It should return -1 on failure, and use
	SetLastError/errno to set an appropriate error code. If an ioctl
	handler is not supplied, Ioctls will be forwarded to the underlying
	stream. If an ioctl handler is supplied, and unrecognised Ioctls
	should be passed to the underlying streams, this should be done by
	calling IoctlStreamArgs() with the underlying stream as the first
	argument. If forwarding of unrecognised ioctls is not desired, a
	handler should be created which returns -1 for such codes. Ioctl
	codes should be kept unique across all streams; this is done in the
	pre-supplied streams by using a long hex value representative of the
	stream name and Ioctl function.

	The error handler gets called with the stream, an error code, and a
	va_args list. This will usually be supplied by a consumer of a
	stream. Errors in the destructor will usually be logged through this
	handler. If the handler is not supplied, errors logged through
	ErrorStream will be ignored.

	The destructor handler should free any extra resources attached to
	the stream, and close any handles stored in it. Errors should be
	logged through the error function. No return code is set. It should
	not affect the underlying stream, or free the stream pointer itself.

	NewStream returns NULL if an error occurred, and sets an error code
	that can be examined with GetLastError()/errno.

DestroyStream
	Stream *DestroyStream(Stream *this) ;

	DestroyStream destroys a single stream, calling the stream destructor
	and freeing the stream memory if necessary. It returns the underlying
	stream, so to free a chain of streams, you can write:

	while ( stream )
	  stream = DestroyStream(stream) ;

ReadStream
	long ReadStream(Stream *this, void *buffer, long bufsize) ;

	ReadStream reads bytes from a stream into a buffer. There are two
	modes of operation which may be supported by streams. The
	non-buffered mode requires the caller to supply a buffer into
	which the data will be put, whereas the self-buffered mode lets the
	stream implementation allocated a buffer itself. Some streams may
	support only one mode or the other.

	If bufsize is not zero, the call is a non-buffered read, and
	the stream will put up to the number of bytes specified into the
	buffer given, and return the number of bytes read.

	If bufsize is zero, the ReadStream will return the number of bytes
	available, and will store a pointer to the data in the address
	passed in the buffer parameter. The stream implementation will
	assume that all of the bytes have been consumed before the next call.

	ReadStream checks the error and eof flags, before passing the call
	to the read handler to actually implement the read.

	ReadStream returns -1 if an error occurs, and sets an error code that
	can be looked at with GetLastError()/errno.

WriteStream
	long WriteStream(Stream *this, void *buffer, long bufsize) ;

	WriteStream writes out a number of bytes up to bufsize from buffer to
	a stream. It returns the number of bytes actually written.

	WriteStream checks the error and eof flags, before passing the call
	to the write handler to actually implement the read.

	WriteStream returns -1 if an error occurs, and sets an error code that
	can be looked at with GetLastError()/errno.

WriteStreamAll
	long WriteStreamAll(Stream *this, void *buffer, long bufsize) ;

	WriteStreamAll is similar to WriteStream, but ensures that the number
	of bytes in the buffer is written out unless an error occurs. It
	returns a true value if there was no error, and a false value if
	there was an error, in which case it sets an error code that can be
	looked at with GetLastError()/errno.

IoctlStream
	long IoctlStream(Stream *this, long op, ...) ;
	enum { STREAM_CLEAREOF = 0x43454f46, /* 'CEOF' */
	       STREAM_CLEARERR = 0x43455252, /* 'CERR' */
	       STREAM_REWIND = 0x52574e44, /* 'RWND' */
	       STREAM_POSITION = 0x504f534e /* 'POSN' */ } ;

	IoctlStream performs the I/O control operation specified. Some
	streams may not support all or any operations, and some may provide
	extra operations. The variable arguments are used for extended
	operations provided by specialised streams.

	The standard ioctls are:

	STREAM_CLEAREOF
		Clears the EOF condition on a stream, allowing another read
		or write to be attempted.
	STREAM_CLEARERR
		Clears the error condition on a stream, allowing another read
		or write to be attempted.
	STREAM_REWIND
		Rewinds the stream to the start if possible.
	STREAM_POSITION
		Positions the stream at the position specified by the
		argument (a long), if possible.

	IoctlStream returns -1 if an error occurred, and sets an error code
	that can be looked at with GetLastError()/errno.

	If an ioctl handler is not supplied by a stream, requests will
	automatically be forwarded to underlying streams.

IoctlStreamArgs
	long IoctlStreamArgs(Stream *this, long op, va_list args) ;

	IoctlStreamArgs is provided for authors of new streams. It is used
	for forwarding unrecognised Ioctls for underlying streams, as in this
	code fragment:

	long myioctl(Stream *me, long op, va_list args)
	{
	  if ( op == MY_IOCTL ) {
	    /* Do whatever I my operation implies */
	    return 0 ;
	  } else {
	    return IoctlStreamArgs(UnderlyingStream(me), op, args) ;
	  }
	}

	This function is only available if  is included.

LockStream and UnlockStream
	void LockStream(Stream *this) ;
	void UnlockStream(Stream *this) ;

	The LockStream and UnlockStream calls should be used with extreme
	care, or not at all. They grant exclusive access to the stream for
	the calling thread. Note that LockStream and UnlockStream are
	implicitly used in ReadStream and WriteStream, so you don't need to
	use them for single reads or writes.

SendStream
	long SendStream(Stream *this, char *format, ...) ;

	SendStream sends a printf-type formatted string to a stream. The
	format codes supported are:

		%d	Decimal integer
		%s	Null-terminated string
		%c	Character
		%t	NNTP-style date and time, from FILETIME parameter.
			(May not be available on all operating systems.)
		%%	Percent character

	The maximum size of the string with format replacements is BUFSIZ.

	SendStream returns a the number of bytes written if there were no
	errors, or -1 if there were. In this case it sets an error code that
	can be looked at with GetLastError()/errno.

The library provides the following specialised functions for particular
stream types:

MakeSocketStream
	Stream *MakeSocketStream(StreamSocket socket, Error socketerror) ;

	MakeSocketStream provides a stream interface to an existing socket.
	Socket streams can be read in self-buffered or non-buffered mode.
	Socket writes are passed straight through to the underlying socket.

	MakeSocketStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

	Socket streams do not support the STREAM_REWIND or STREAM_POSITION
	ioctl operations.

	The socketerror callback supplied may be called with one error code,
	SOCKSTREAM_ERROR_CLOSE. The extra parameters to the error handler
	will be the socket handle and the error number as returned by
	GetLastError()/errno.

	The StreamSocket type is a typedef for SOCKET on Win32 or socket on
	Unix/POSIX.

MakeFileStream
	Stream *MakeFileStream(char *filename, int access, Error fileerror) ;

	MakeFileStream provides a stream interface to an existing file.
	File streams can be read in self-buffered or non-buffered mode.
	File writes are passed straight through to the underlying file.

	The access parameter is one of FILESTREAM_READ, FILESTREAM_WRITE, or
	FILESTREAM_READWRITE.

	MakeFileStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

	File streams support the STREAM_REWIND and STREAM_POSITION ioctl
	operations.

	The fileerror callback supplied may be called with one error code,
	FILESTREAM_ERROR_CLOSE. The extra parameters to the error handler
	will be the socket handle and the error number as returned by
	GetLastError()/errno.

MakeStdioStream
	Stream *MakeStdioStream(FILE *fp) ;

	MakeStdioStream allows standard I/O streams to be used through the
	Streams interface. Stdio streams can be read in self-buffered or
	non-buffered mode. Stdio writes are passed straight through to the
	underlying file.

	MakeStdioStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

	Stdio streams support the STREAM_REWIND ioctl and STREAM_POSITION
	operations.

MakeLineStream
	Stream *MakeLineStream(Stream *under, char *term) ;
	enum { LINESTREAM_TERM = 0x4c494e54, /* 'LINT' */
	       LINESTREAM_AGAIN = 0x4c494e41 /* 'LINA' */ } ;

	Line streams read or write a line at a time from an underlying
	stream. Line streams operate in self-buffered read mode only, and
	the underlying stream for a line stream must also support
	self-buffered read mode.

	The line terminator specified is included in the line read or written.

	There are two extra ioctls for line streams:

	LINESTREAM_TERM
		This takes an extra parameter, which is a string (char *) to
		which the line terminator is set.
	LINESTREAM_AGAIN
		This causes the immediately previously read line to be
		available again for the next ReadStream call.

	MakeLineStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

Reponse
	long Response(Stream *this) ;

	Response reads a line from a line stream and returns the decimal
	numeric value of the digits at the start of the line, or -1 on error.

Reason
	long Reason(Stream *this, char *buffer, int bufsize) ;

	Reason copies up to bufsize-1 characters from the last line of a line
	stream into the supplied buffer, and NULL-terminates the buffer. It
	returns the number of bytes copied into the buffer.

MakeDotStream
	Stream *MakeDotStream(Stream *under) ;

	Dot streams are read-only, and operate in self-buffered read mode.
	They read up to a line containing a single dot followed by CR and LF.
	The dot line is not returned as part of the stream. The underlying
	stream for a dot stream should be a line stream.

	MakeDotStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

MakeHeadStream
	Stream *MakeHeadStream(Stream *under) ;

	Head streams are read-only, and operate in self-buffered read mode.
	They read up to the first line containing just CR followed by LF.
	The CR-LF line is not returned as part of the stream. The underlying
	stream for a head stream should be a line stream.

	MakeHeadStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

MakeBodyStream
	Stream *MakeBodyStream(Stream *under) ;

	Body streams are read-only, and operate in self-buffered read mode.
	They read everything beyond the first line containing just CR
	followed by LF. The CR-LF line is not returned as part of the stream.
	The underlying stream for a body stream should be a line stream.

	MakeBodyStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.

MakeSearchStream
	Stream *MakeSearchStream(Stream *under, void *search, int len) ;
	enum { SEARCHSTREAM_RESULT = 0x53434852 , /* 'SCHR' */
	       SEARCHSTREAM_AGAIN = 0x53434841 /* 'SCHA' */ } ;

	Search streams are read-only. They search for a pattern of bytes in a
	stream, returning EOF when the pattern is found. The result can be
	tested through the SEARCHSTREAM_RESULT ioctl, which returns a positive
	value if the pattern was found. The search can be reset with the
	SEARCHSTREAM_AGAIN ioctl.

	MakeSearchStream returns NULL on error, and sets an error code
	that can be looked at with GetLastError()/errno.


CREATING NEW STREAMS:
	New streams can be easily defined. A new stream which does not
	contain data can be defined with a call to NewStream with the
	subclassbytes parameter set to zero. A new stream which requires data
	should define a structure to store the data; the first element of
	this structure must be a stream pointer. The sub-class should avoid
	manipulating this pointer. The call to NewStream should have the size
	of the structure as the subclassbytes parameter. If pointers to
	dynamically allocated memory are stored in this structure, the stream
	should define a destructor to clean up after itself.

	The handler, ioctl, error and destructor routines should cast the
	incoming Stream pointer into the sub-class before using sub-class
	fields.

	For example, the following structure defines a sub-class with some
	extra data. The outline of a constructor function and the call to
	NewStream are shown:

	typedef struct {
	  Stream *base ; /* Do not manipulate this pointer */
	  int my_data ;
	} MyStream ;

	Stream *MakeMyStream(Stream *under, int data)
	{
	  Stream *this = NewStream(NULL, under, sizeof(MyStream),
				   mystream_reader, mystream_writer,
				   mystream_ioctl, mystream_error,
				   mystream_destructor) ;
	  if ( this ) {
	    MyStream *mine = (MyStream *)this ;
	    mine->my_data = data ;
	  }

	  return this ;
	}

							AJCD 23rd October 1998

[Home] [Up]
Wrapped on 23rd October 1998 by angus@harlequin.com