All declarations needed to use the SDSI library are collected in the include file `sdsi.h', which is built from `sdsi.h.in' and the `.table' files in the `lib' directory. A small additional set of useful macros can be found in the include file `sdsi2-useful.h'.
The SDSI library has many features that can be turned on and off at
library build-time in `sdsi.h.in', by selectively commenting
out #defines. There might be too many features.
See section Library Features for the complete listing.
The most important C data type in the SDSI library is the sdsi2Sexp. This
is a union type that can represent all of the different kinds of S-expressions
used in the library:
typedef union _sdsi2Sexp {
int type;
sdsi2BufferSet buffer_set;
sdsi2Canonical canon;
sdsi2Sparts sparts;
sdsi2Hash hash;
sdsi2Signature signature;
sdsi2Cert cert;
sdsi2Name name;
sdsi2Key key;
sdsi2Tag tag;
sdsi2Sequence sequence;
sdsi2Acl acl;
#ifdef SDSI2_FEATURE_ONLINE_TESTS
sdsi2ValidityList vlist;
#endif /* SDSI2_FEATURE_ONLINE_TESTS */
} sdsi2Sexp;
The elements of this union are structs that define the specific
SDSI object types. By examining the SDSI2_OBJECT_ value in a
sdsi2Sexp's type element, you can select the correct arm of
the union for an object.
All of the different object elements of a sdsi2Sexp deliberately
intersect on a leading sdsi2BufferSet member. Many other SDSI2
data structures also start with a sdsi2BufferSet member.
A leading sdsi2BufferSet element allows the containing data
structure to be collected in a doubly linked list with other data
structures, possibly of different types, that also lead with a
sdsi2BufferSet.
/* sdsi2BufferSet */
typedef struct _sdsi2BufferSet {
int type;
int do_not_free;
struct _sdsi2BufferSet *next, **prev;
} sdsi2BufferSet;
The leading type can be filled with a value indicating the real
type of data structure that this sdsi2BufferSet leads. Note that
this deliberately intersects with a sdsi2Sexp's type element.
do_not_free should be set to non-zero for a member of a buffer
set that should not be freed when the set is freed. This is normally
the case for a set member that isn't in its own allocated buffer,
but is really inside some other allocated buffer.(5)
next and prev are the double links in the list.
There is always a function sdsi2_heap_free_bufferset, for freeing
portions of buffer sets. Under SDSI2_FEATURE_SEXP_GENERATION,
a function sdsi2_heap_alloc_bufferset is available.
While all of the different object elements of a sdsi2Sexp
do deliberately intersect on a leading sdsi2BufferSet member, they
really intersect on a leading sdsi2Canonical member, which itself
leads with a sdsi2BufferSet.
A sdsi2Canonical structure keeps track of an S-expression's
canonical form. During and after parsing an S-expression, this
structure holds information about the canonical-form bytes that have
been read into one or more sdsi2CanonicalBuffer buffers.
A sdsi2Canonical buffer looks like:
/* sdsi2CanonicalBuffer */
typedef struct _sdsi2CanonicalBuffer {
sdsi2BufferSet buffer_set;
struct _sdsi2CanonicalBuffer *next, **prev;
_SDSI_TYPE_SIZE size, bytes;
_SDSI_TYPE_BYTE buffer[0];
} sdsi2CanonicalBuffer;
The leading sdsi2BufferSet lets this structure be a member of a
buffer set. The next and prev elements allow canonical
buffers holding different parts of the same S-expression to be doubly
linked together. size and bytes hold the maximum number
of bytes that can be put into buffer, and the number of bytes
that have been, respectively.
A sdsi2Canonical buffer looks like:
/* sdsi2Canonical */
typedef struct _sdsi2Canonical {
sdsi2BufferSet buffer_set;
#ifdef SDSI2_FEATURE_SEXP_UNPARSE
int Canonical_dirty;
#endif /* SDSI2_FEATURE_SEXP_UNPARSE */
sdsi2CanonicalBuffer *Canonical_buffer;
_SDSI_TYPE_SIZE Canonical_offset;
_SDSI_TYPE_SIZE Canonical_length;
} sdsi2Canonical;
The leading sdsi2BufferSet lets this structure be a member of a
buffer set. Under SDSI2_FEATURE_SEXP_UNPARSE, if
Canonical_dirty is non-zero, any canonical form pointed to by the
remainder of the structure does not represent the true current
S-expression.
Otherwise, Canonical_buffer points to the first
sdsi2CanonicalBuffer holding this S-expression's canonical form.
Canonical_offset is the starting offset within
Canonical_buffer->buffer of the first byte of that form.
Canonical_length is the total number of bytes of that form, which
may be greater than Canonical_buffer->bytes - Canonical_offset,
meaning that the canonical form spills over into
Canonical_buffer->next and possibly beyond.
All of the scanning routines, and the occasional parsing routine, deal
with sdsi2Canonicals. The idea is this: natively, the library
only understands the S-expression canonical form. To parse in a new
expression, the canonical form is scanned into a series of
sdsi2CanonicalBuffers. The various SDSI objects found within have
their sdsi2Canonicals filled appropriately to point to their
portions of that form, and then they also keep their own information
about or pointers to individual items of interest within that form.
For example, after a hash object is parsed into a sdsi2Hash
structure:
/* sdsi2Hash */
typedef struct _sdsi2Hash {
sdsi2Canonical canon;
int hash_algorithm;
sdsi2ByteString hash_value;
sdsi2ByteString uri;
} sdsi2Hash;
The canon points to the entire scanned canonical form of the
hash, hash_algorithm is the SDSI2_HASH_ identifier of
the hash algorithm used, hash_value points to the value of the hash,
and uri points to the optional URI of the hash.
hash_value and uri in the above sdsi2Hash are both
sdsi2ByteStrings. A sdsi2ByteString holds information about
a scanned byte string:
/* sdsi2ByteString */
typedef struct _sdsi2ByteString {
_SDSI_TYPE_BYTE *display_type, *byte_string;
_SDSI_TYPE_SIZE display_type_length, byte_string_length;
} sdsi2ByteString;
This is just two matched pairs of pointers and lengths to the byte
string's data and optional display type. Normally, the pointers and
lengths are into a sdsi2CanonicalBuffer, but the regions the
pointers point to are always contiguous. A byte string's data and
display type may be in different sdsi2CanonicalBuffers, but they
themselves are never split across sdsi2CanonicalBuffers, even
though the larger S-expression may be.
Scanning in an S-expression's canonical form into a set of character buffers and filling structures with only information about or pointers to the interesting portions of that form has some advantages:
There is one big disadvantage in all of this: unparsing modified
S-expression data structures is not easy. The code to do so (the
sdsi2_sexp_unparse function and its cohorts, the internal
_sdsi2_..._get_canonical_elements) is rather large and
ugly. It is compiled into the library under
SDSI2_FEATURE_SEXP_UNPARSE.
The central beast in unparsing changed S-expressions is the sdsi2CanonicalElement:
/* sdsi2CanonicalElement */
#define SDSI2_CANON_EL_WORDS_COUNT 4
typedef struct _sdsi2CanonicalElement {
int type;
int el_typeA, el_typeB;
union {
sdsi2ByteString byte_string;
sdsi2Canonical canon;
sdsi2Sexp *sexp;
_SDSI_TYPE_TIME date;
struct {
int words[SDSI2_CANON_EL_WORDS_COUNT];
_SDSI_TYPE_BYTE delimiter;
} words;
struct {
_SDSI_TYPE_VOID *state;
int (*function)(_SDSI_TYPE_VOID *, struct _sdsi2CanonicalElement **, int *);
} callback;
} el;
} sdsi2CanonicalElement;
TBD - more discussion about this. (Applications that use the library
will rarely have to deal with making
sdsi2CanonicalElements. Right now, actually, there's no reason
you would, since there are no public functions that take them. Later,
though, user-defined S-expression types may be possible, and in these
cases you will have to know how to supply sdsi2CanonicalElements
back to the library.)
The vast majority of the SDSI library functions return an int
that is either SDSI2_OK (zero) on success, or a SDSI2_ERROR_
code indicating the type of error that occurred. Functions that do not
return an int, or that return an int that is not directly
taken as an error code, usually have some "exceptional" return value
that indicates an error has happened.
If an error has occurred, the global sdsi2_error_context is filled
with the error information. This is of type sdsi2ErrorContext:
/* sdsi2ErrorContext: */
typedef struct {
int error_major_id;
int error_minor_id;
int system_error_id;
int error_locus;
#ifdef SDSI2_FEATURE_ERROR_SEXP_DUMP
sdsi2Canonical canon;
#endif /* SDSI2_FEATURE_ERROR_SEXP_DUMP */
} sdsi2ErrorContext;
error_major_id is a general, major SDSI error code - one of the
SDSI2_ERROR_ values. This is the same value returned by the
function that encountered an error, if it will return error codes as its
value.
error_minor_id is a more specific, minor SDSI error code - one of
the SDSI2_ERRORm_ values. This identifies in a little more detail
why the major error happened. The idea is that a major error code might
be reported by very different kinds of operations. Because it might be
so general, having a minor code for inspection can be useful.
system_error_id is only significant under certain
error_major_ids, and is an error code from the operating system.
SDSI2_ERROR_SYSTEM_ERROR, SDSI2_ERROR_IO_READ_ERROR, and
SDSI2_ERROR_IO_WRITE_ERROR will set this. It may be undefined
under any other values of error_major_id.
error_locus is one of the SDSI2_LOCUS_ codes. It tries to give
information on where an error was encountered or triggered.
Under SDSI2_FEATURE_ERROR_SEXP_DUMP, the sdsi2Canonical in
canon might give some part of the canonical representation of the
object that caused the error.

Right now canon is always undefined.
If the last SDSI library call did not return an error, the contents of
sdsi2_error_context are undefined.
One last data structure is the sdsi2Stream, which describes
input/output streams:
/* sdsi2Stream */
typedef struct _sdsi2Stream {
int INTERFACE (*readv)(_SDSI_TYPE_VOID *, struct iovec *, int);
int INTERFACE (*writev)(_SDSI_TYPE_VOID *, struct iovec *, int);
_SDSI_TYPE_VOID *stream_read_state;
_SDSI_TYPE_VOID *stream_write_state;
_SDSI_TYPE_SIZE force_buffered_read_size;
_SDSI_TYPE_SIZE read_buffer_size_mask, read_buffer_head, read_buffer_tail;
_SDSI_TYPE_SIZE write_buffer_size, write_buffer_bytes;
_SDSI_TYPE_VOID *read_buffer;
_SDSI_TYPE_VOID *write_buffer;
} sdsi2Stream;
sdsi2Streams are used with any library function that needs to exchange
bytes "with the outside". It's a basic I/O abstraction. Only four elements
of it are considered public:
readv and writev point to functions that read and write
bytes, respectively. stream_read_state and
stream_write_state are always passed in as the first arguments to
their respective functions. The readv and writev functions
must behave exactly like readv(2) and writev(2) - and,
in fact, if you're using file descriptors, you can cast them into the
stream_..._state elements, and just drop readv
and writev right in.(6)
The remainder of the sdsi2Stream elements obviously make up a
buffering system, but they remain officially undocumented, and may
change unpredictably.
The interface to the SDSI library consists of the data types and
functions defined in the header files. The most important data types
were introduced in the previous section; the others (specifically the
per-object-type structures, like sdsi2Tag and sdsi2Acl) aren't
covered here yet, but their definitions also aren't too obscure.
The library functions are grouped by the kind of thing they generally
work with. There are collections of functions that begin
sdsi2_bytestring_ and sdsi2_hash_, for example.
Documentation for all functions is in this section.
Like parts of the data structures aren't necessarily public, there are functions that aren't public, either. These functions are used internally by the library, and their prototypes do not appear in the public header files. These functions should not be used by applications.
However, these library-private functions are documented here along with their public counterparts, in the hope that some words about every function in the library will help people to understand how it works when it does and why it doesn't when it doesn't.
All of this library documentation can be found here.Go to the first, previous, next, last section, table of contents.