Each element of a participant array is actually not just an address, but a stack of addresses; each address in the stack is a byte string of some length. The motivation for using a stack of addresses to identify each participant will become clear in a moment. For now, here are the four operations for manipulating participant addresses provided by the participant library:
void partInit(Part *participants, int number)
int partLength(Part *participants)
void partPush(Part *participant, char *address, int length)
char *partPop(Part *participant)
The first operation initializes a participant list that is to hold the address stacks for the specified number of participants. For a typical one-to-one channel, number is two. The second operation returns the number of participants in the participant list; e.g., two.
The third and fourth operations are used to add (remove) addresses to (from) the address stack of a single participant. partPush pushes the specified address of length bytes onto the stack, while partPop pops the next address off the stack. Giving a length of 0 to partPush denotes a special wildcard address, indicating to the low-level protocol that it is free to substitute any meaningful value. This could be used, for example, to allow a server to accept connections from any client. partPop does not include a length field since the returned address is a NULL-terminated character string.
Now to the question of why each participant is identified by a stack
of addresses. The short answer is that addresses are too complicated
to be stored as a simple byte string. To see why, consider the
following. First, each protocol expects participants to identify
themselves and their peers with a compound address of the form
demux key, host address
. Second, each protocol
defines its own notion of what participant addresses should look like.
That is, when high-level protocol HLP opens a channel (session) via
low-level protocol LLP, it identifies its peer according to LLP's
definition of addresses. Third, while all protocols define what they
expect in the form of a demux key, only a few protocols are in the
business of defining network addresses for hosts. (IP is the only
such protocol in the Internet protocol suite.) Thus, so as not to
overly restrict the type of addresses accepted by any one
protocol---thereby making it possible to flexibly configure different
protocols on top of each other---the x-kernel allows protocols to
under specify the form of addresses they accept.
Practically speaking, this means that at the top of the protocol graph, the application identifies each participant by first pushing a host address onto the participant stack, and then pushing a demux key that will be understood by the topmost protocol onto the stack. The topmost protocol then pops off the first component (the demux key), but only pops the host address off the stack if it is interested in defining its own notion of a host address. Suppose it is not. Then, the protocol pushes a new demux key onto the stack---one that is meaningful to the protocol upon which it depends---and opens that protocol. It never sees, nor cares about, the type of the host address. This happens all the way down the protocol graph---pop off the demux key and replace it with a demux key that the next lower level will understand---until a protocol that cares about host addresses is encountered. Such a protocol pops both the demux key and the host address off the address stack, translates the host address into some lower-level host address, and pushes this new host address, along with a new demux key, onto the stack.
In other words, no protocol is restricted to to being composed with protocols that expect a certain type of host address; that is, the protocol is under specified. Another way of saying this is that address stacks allow protocols to be polymorphic with respect to addresses it never examines.