Communications library HOWTO
Hans Petter Jansson, hpj@teletopia.no
Release 1, 19981015

This document describes the high-level communications facilities in libflux.
It consists of a general introduction and an example of block parsing. This
is not intended as a reference, and does not cover all the details on
function calls and return values.

For full comprehension and use, reading the sock-howto and ttree-howto is
required. Communication relies mainly on the structures and functions
explained therein.


1.0.0  Introduction

The comm functions are capable of sending and receiving whole token trees
(data stored in hierarchical memory structures). 

To send or receive a ttree, you need a connected libflux socket.

1.1.0  Block Layout

Comprehensioin of block layout is not required for use. The information is
included for inquisitive minds and protocol developers.

Transactions are done in blocks, where one block corresponds to one token
tree. All elements of the block meta-information are in network byte order.

Each block starts off with a header. It looks like this:

aaaabbbbccdd
|   |   | |
|   |   | `--- (uword) Flags.
|   |   `----- (uword) Transaction ID, across several blocks.
|   `--------- (ulong) Serial ID, uniquifies block.
`------------- (ulong) Total data length.

Currently, the serial number is assigned automatically, the transaction ID
is not in use (zeroed), and none of the flags have been worked out. These
fields (even the serial number) may be dropped in the next protocol version if
nobody can find any use for them. The transaction ID was originally intended
for multiplexing different series of ttrees over the same connection, and
the serial number for identification and queuing of blocks. The need for
these is eliminated when using multiple connections for various data
streams. This keeps the communications complexity down, and reduces overhead
somewhat. After all, TCP/IP is made to do this multiplexing.

The total data length, however, is neccessary, as it provides the length of
the rest of the block, in bytes. This includes metainformation and excepts the
header.

As you probably remember from reading about ttrees, they consist of a number
of nodes, linked to form a tree. Each node holds a certain amount of octet
data (8-bit bytes).

Immediately following the header is therefore the root node of the tree.
The following metainformation is similar for all nodes, including the root
node:

aaaabbbbc...
|   |   |
|   |   `--- (uchar) * <aaaa> Data bytes.
|   `------- (ulong) Number of child nodes following this one.
`----------- (ulong) Length of this node's data.

A node's data can be zero-length. If the node has children, they follow
immediately (depth-first). Tree reconstruction is best done recursively.

1.2.0  Sending and Receiving Data

When you know the sock and ttree basics, sending and receiving blocks is a
very simple matter. In fact, you need only two functions.

comm_send() takes a socket and a ttree as its arguments, and immediately sends
the ttree over the sock, blocking. It returns the number of nodes sent, or a
negative value if an error occurred.

comm_recv() takes a socket and a timeout as its arguments. It waits for data
to begin arriving on the socket, no longer than the number of usecs
specified (or forever, if the number specified is zero). When data starts
arriving, it blocks until a full block (ttree) is received, returning a
pointer to it - NULL if there was an error, such as no data arriving within
the specified interval.

2.0.0 Examples

2.1.0 Block Reconstruction

Let's see how incoming data is reconstructed by a comm_recv()-like function.
Given the following data:

0x0000003b, 0x00000000, 0x0000, 0x0000,  | Block header.
0x00000004, 0x00000002, "root",          | Root node.
0x00000007, 0x00000001, "child 1",       | First child of root.
0x00000009, 0x00000000, "child 1.1",
0x00000007, 0x00000000, "child 2"        | Second child of root.

First, we see that the total data is 0x0000003b (58) bytes long. Second, we
get the root node. It has 4 bytes of data and 2 children. The data is the
ASCII string "root". At this time the tree looks like this:

root

We recurse one level to get the first of root's two children. The first child
has 7 bytes of data ("child 1") and one child.

root
|
`- child 1

To insert child 1's child, we recurse again. This node has "child 1.1" as
data, and has no children.

root
|
`- child 1
   |
   `- child 1.1

There are no more nodes on this level, so we go up to the previous one
(root's children). There is one more node to be inserted at that level,
namely the one having "child 2" as data.

root
|
|- child 1
|  |
|  `- child 1.1
|
`- child 2

As child 2 has no children, and there are no more nodes to insert at the top
level, we're done. We can now return the tree to the caller.
