X Audio System Implementation Design Notes

Ray Tice (X Consortium)

Mark Welch (X Consortium)

1.0 Introduction ..

This document describes the implementation of the consortium implementation of the library and server for the X audio system. Please see the X Audio Protocol Specification and X Audio Library Specification for further details on the external interfaces of the server and library. This document is not a porting guide. That should be a separate docu ment focused on that task.

[Right now, there's a bit of a split to this document - it has the beginnings of some organi zation up fromt, followed by notes from individual contributors. Long term I expect that the up fromt portion will become of complete and organized, and provide pointers to the relevant spots in the contributor's notes. A more integrated approach would be nice. But we haven't the time just now.]

2.0 Interfaces ..

The interface for the C library will be ANSI C compliant.

Internally, the implementation may use C++, with the following restrictions to permit portability:

Large scale usage of threads within the implementation is out of the question, since some vendors implementations will keel over. The target is to implement in a single thread. (We'll void that if absolutely needed.) The client library should allow for multi-threaded clients)

The implementation may use POSIX C library routines. [need to be more specific.]

3.0 Coding Conventions ..

3.1 Capitalization ..

[need some explanation]

3.2 Prefixes ..

3.3 Comments ..

3.4 Indentation ..

          switch (programmer)
          {
            case: Mark)
              alphaMugNeedsRefilling = TRUE;
          ...)
          if (rayWentToFlorentinasForLunch &&
                  (itWasSunnyOutside || notTooCold) &&
                  didNotHaveToQuicklyScarfLunchDown)
              rayIsSated = TRUE.
          if (something_complex)
          {
              statements...
              if (something_nested)
              {
                  more statements...
              }
          } 
          else 
          {
              more statements...
          }
          if (something_complex) {
              statements...
              if (something_nested){
                  more statements...
              }
          } else {
                  more statements...
          }

4.0 Client Library Design ..

4.1 Components ..

The library is broken down into several internal components:

4.2 API Wrappers ..

The API Wrappers are used to dispatch API calls to connection specific routines. They will also be used to bridge to C++. They will have the form:

           XaSet(XaAudio *connection, XaTag object, ...)
          {
              va_list ap;
              va_start(ap, object);
          
              if (conn->isRemoteObject(object))
                    conn->set(object, ap);
              else
                    conn->local->set(object, ap);
              va_end(ap);
          }
Wrappers will be created for the following functions:

5.0 Server Design ..

5.1 Components ..

The library is broken down into several internal components:

5.2 Class design ..

[as proposed but not implemented. David took another approach for now. This ought to be saved away somewhere for the future, in case we need it. For the current state of things, see "Object Model Design" on page 6.]

Each class is described by a collection of attribute records, with each record having pointer to routines for setValidation, set, (and get?) for that attribute. The collection list of attribute records is stored in a list of arrays. A subclass or specialization of a class adds attributes to the description by adding an array af attribute records to the list.

Each instance of that class would then have a pointer to a ddx specific collection of attribute records (or maybe not?), and a collection of value records in the same order as the attribute records of the class. The value records provide storage of the values for this instance of the c lass, and maybe some other info (like whether setting that particular attribute causes an event?)

5.3 Request Attribute Parsing ..

[This section may be superceded by Peter's note on "Encoding Parsing of Protocol Requests" on page 12. Is that right, Peter?]

When a set request comes in, first we identify each attribute and do a rough validation of it. So for each attribute:

After each attribute has identified and validated, a second pass is done to set all of the val ues. [Does each attribute have a setValues routine?]

An instance specific validate & set routine is then called. (Provides a hook for ddx).

Finally, any events registered on this object are generated as appropriate.

6.0 Directory Structure ..

Date: Tue Feb 27, 1996 From:Ray Tice

The current source tree structure is shown below. We want to encourage re-use of non- audio specific code, so we've put reusable parts of the server in the lib tree. In addition, the server and library share code, so that shared code goes in the lib tree too. We still need to do some clean-up though.

          xc                (x core distribution)
             lib               (x libraries)
               Xa                (audio - lib & lib/server code) 
                nop               (Normalized Object Protocol)
                  os                (interface to os)
                  protocol          (request code)
                test              (informal tests used in coding)
            programs
              Xaserver          (Audio specific parts of server)
                dia               (device independent objects)
                mi                (helper routines for use)
                hw                (hardware specific code)
                  sun               (code for sun's systems)
                  dec               (code for digital's systems)
                test              (informal tests used in coding)
              ...               (audio clients)

7.0 Object Model Design ..

Date: Wed, 03 Jan 1996 From: Ray Tice <ray@x.org>

I finally checked in some working class code, that uses David's object code. Some changes still need to be made, but here's how it sits at the moment:

7.1 Creating a Class ..

Server-specific class definitions belong in programs/Xaserver/dia. Each class should define a defintion routine of the form:

           XaErrorCode XaCreateClassCore(XaConnection *);
(this function signature is typedef'd as XaCreateClassFunc.)

The defintion routines are called from CreateWellKnownClasses in programs/Xaserver/ dia/classes.cc. The call to a class' defintion routine must occur after those of its super classes.

The definition routine should call the XaClass constructor with a structure defining the class, and an array defining its attributes. For example, here is the core class definition routine in lib/Xa/nop/object/core.cc:

           XaErrorCode XaCreateClassCore(XaConnection *conn)  
          {      
              return (new XaClass(conn, XaTobject, 
                      &XaCoreClassInit,XaCoreAttrInit)) ? 
                      XaESuccess : XaEFailure; 
           }
The structure that is passed in to define the class has information about superclass, allowed access, and class specific callbacks. See XaClassInitRec in lib/Xa/nop/object/ cclass.h for more info.

Each attribute of the class has a XaAttrInitRec (defined in lib/Xa/nop/object/cclass.h, which has the name, type, typeCheck callback and access information for that attribute.

Look at lib/Xa/nop/object/core.cc or lib/Xa/format.cc for examples.

7.2 Creating an Object Instance ..

To create an object (instance of a class) within the server or library, call XaCreateObject. (Applications will call this directly.) As an example, the test module lib/Xa/test fmt_basic_test.cc creates a format object with default values like:

          fmt = XaCreateObject(conn, XaTformat, dummyObjTag,
                   dummyNameTag, NULL, 0);

7.3 How It works ..

(Read carefully - the terms are a bit overloaded)

A protocol visible class is represented internally by an XaClass instance. Each attribute of that class is rerepesented represented by an instance of XaClassAttribute. The XaClass instance and XaClassAttribute instances together describe an externally visible class and act as the factory for instances of that class.

An externally visible object (an instance of externally visible class) is represented with an instance of XaObject. Each attribute of that object is represented by an instance of XaAt tribute.

Callback hooks for create, destroy, set and get are available on a per class and per instance basis.

(This does seem like a lot of c++ instances to create for each protcol visble object. How ever, David felt that given our small number of objects, this design was the quickest to implement.)

(Also, we had a requirement that class and objects be loadable - this meant that both should be creatable across a "C" interface. This led to a less c++ oriented implementation of the pbject model.)

7.4 Things to do ..

There are a number of things that need to be done yet...

8.0 Connections ..

(Date: Fri, 05 Jan 96 From: "Peter Derr" <pderr@zk3.dec.com> )

8.1 Connection class hierarchy ..

                      XaConnection
                       |        |
          XaMasterConnection     XaProtocolConnection
                                 |                 |
                        XaClientConnection    XaServerConnection
[reformatted below, in case above figure gets mangled]

XaConnection
XaMasterConnection::XaConnection
XaProtocolConnection::XaConnection
XaClientConnection::XaProtocolConnection
XaServerConnection::XaProtocolConnection

8.2 Connection setup and authentication ..

The X Audio System uses ICE to authenticate and establish a connection, and as the transport for the Audio protocol.

8.3 Connection sequence ..

8.3.1 Server

XaMasterConnection::setup (programs/Xaserver/dia/clientconn.cc)

8.3.2 Client

(code is in lib/Xa/serverconn.cc)

XaOpenAudio(char *networkID, char *errString, int argc, char *argv[])

XaServerConnection contstructor

XaServerConnection::open(char *networkIds)

8.3.3 Server

Detect new connection on listen fd

ServerMasterConnection::acceptConnection(int newFd)

XaClientConnection::ProcessMessage(XaTask *task, void *data)

8.3.4 Client

XaServerConnection::open(char *networkIds) (continued)

8.3.5 Server

XaMasterConnection::protocolActivateProc

8.3.6 Client

XaServerConnection::open(char *networkIds) (continued)

This completes the connection sequence.

8.4 Authentication ..

[Note this is not the final implementation...]

To get basic authentication working I lifted code from the R6 xsm sample session manager that uses the SM protocol layered on ICE. This code uses the MIT-MAGIC-COOKIE-1 authentication implemented in the ICE sample implementation library (but not part of the ICE specification). When the server starts it uses the iceauth command to write MIT- MAGIC-COOKIE-1 keys into ~/.ICEauthority. When the client starts it reads these keys from ~/.ICEauthority and uses them to authenticate itself to the server.

The authentication code from xsm is in lib/Xa/nop/os and is used by the server to setup auth key data, write it to ~/.ICEauthority, and clean it out from ~/.ICEauthority when it exits. In auth.c, the SetAuthentication routine generates the keys and writes them to ~/ .ICEauthority. There is a different key for each transport and each key is used for both the ICE and X_Consortium_Audio protocols. When the server is running, you can use the `iceauth list' command to see the entries in ~/.ICEauthority.

When the server calls IceRegisterForProtocolReply (in XaMasterConnection::setup) it specifies "MIT-MAGIC-COOKIE-1" as the one supported authentication method and _IcePaMagicCookie1Proc as the authentication callback for this method.

When the client calls IceRegisterForProtocolSetup (in XaServerConnection constructor) it specifies "MIT-MAGIC-COOKIE-1" as the one supported authentication method and _IcePoMagicCookie1Proc as the authentication callback for this method. _IcePaMagicCookie1Proc and _IcePoMagicCookie1Proc are undocumented procedures in libICE. I felt safe using them since xsm did.

If the same user with the same home directory is running the X Audio server and clients the right key will automatically be found in ~/.ICEauthority. Otherwise, the keys will have to propagated either by copying ~/.ICEauthority or using iceauth in to propagate keys, just as xauth can be used to propagate X keys.

8.5 Identifying the ICE ports ..

ICE does not provide any way of specifying port numbers. When you listen for connec tions, ICE picks the port and you have to communicate this information to the client some how. In the current state of the implementation, the server writes the network IDs to stdout when it starts up like this:

Audio server network connection ids:

          local/island.zk3.dec.com:/tmp/.ICE-unix/23144,tcp/
          island.zk3.dec.com:3027
You can also find them in the ~/.ICEauthority using the `iceauth list' command.

This seems sufficient for now, but we will need to invent a more automatic way of pro viding the network IDs to clients.

Perhaps we should also reconsider whether or not a well-known port is needed or desir able. If so, a modification to ICE would be required.

8.6 Error handling ..

Error handling code so far is rudimentary.

In the server, in the call to IceRegisterForProtocolReply, XaMasterConnection::errorHan dler is specified as the error handler. If an ICE error occurs and the error handler is called, the client is marked for delete. This will most likely happen during the call to IceProcess Messages in XaClientConnection::ProcessMessage. When IceProcessMessages returns if the client is marked for delete, the client is removed by calling masterConnec tion:RemoveClient.

In the client the error handler (XaServerConnection::errorHandler) does nothing but print a message.

9.0 Encoding Parsing of Protocol Requests ..

(Date:Thu, 11 Jan 96 From: "Peter Derr" <pderr@zk3.dec.com>)

This describes the state of sending and parsing protocol requests from client to server.

Once the Audio protocol has been set up using ICE, the client and server can send and receive messages over the transport mechanism ICE has provided.

This document will only describe Requests (messages from the client to the server) since events are still in the design stage.

9.1 Request encoding ..

The encoding of requests, as specified in the Protocol document, is defined in the Xaproto col.h header file. For each request there is a struct defined that exactly matches the wire encoding of the request (except for variable-length data at the end of many requests which is not included in the struct definitions). At The beginning of each request is the ICE header:

          CARD8       majorOpcode;
          CARD8       minorOpcode;
          CARD8       data[2];
          CARD32      length B32;
The two bytes of data in this header are not used by ICE and the Audio protocol is free to use them. So far only the Find request does.

Each field in each request must be naturally aligned. Also, the total length of each request must be padded to an 8-byte boundary.

9.2 Client-side Request pipeline ..

I'll describe how a request is generated and processed, starting with the client API.

Each request has a corresponding API that generates it. There's no requirement for this mapping, it just turned out that way. For requests that send item lists (name-value pairs) the API functions have variable argument lists which have sequences of name (char *) and value (XaArgVal).

Each request-generating API function and the corresponding XaServerConnection mem ber function are typically in a separate source file: create.cc, destroy.cc, find.cc get.cc, read.cc, set.cc, and write.cc. These are all in lib/Xa but some of them are symlinks into lib/Xa/nop/protocol.

Each API that has a variable argument list first calls the va_start macro to get the va_list argument list pointer and then calls the corresponding XaServerConnection member func tion, passing the variable argument list pointer.

Each XaServerConnection member function that generates a request declares a local struct of the type matching the request encoding as defined in Xaprotocol.h. For example, the XaServerConnection::set function declares `XaSetRequest req'. The function fills in all the fixed-length fields with data passed into it in the regular parameters.

9.2.1 Processing variable argument lists

For those requests that pass item lists in the protocol (Create, Set, Find) the variable argu ment list is processed by calling XaServerConnection::processVarargs. The process Varargs function walks the variable argument list converting it to protocol which it stores in the XaServerConnection's protoBuffer.

The XaServerConnection's protoBuffer is an object of the XaProtoBuffer class (defined in connection.h, member functions in connection.cc). It has methods for to write, reset, lock, unlock, reserver space, and return the buffer pointer, insertion point, or stored protocol length. It also allows a stack of protocol buffers to handle the case where processing argu ment lists to generate a request requires generating another request before the first is ready to send (This only happens when we need to find the atom for an item name).

If processVarargs finds an item name in the array category (XaParray, XaPcollectionRe place, XaPcollectionAdd, or XaPcollectionSubtract) it calls addProtoArray to process the array-type value, else it calls processArg to process the value.

If the value is a scalar, processArg calls addProtoItem which finds the atom for the name by calling findAtom (can cause recursion, generating a Find request) and writes the name atom and the value to the protoBuffer.

If the value is an XaArgList (array of XaArg structs) processArg calls processArgList which walks the XaArgList in much the same way as processVarargs walks the variable argument list, converting it to protocol.

The processArg function does not yet handle XaPfetchArgs, XaParrayPart, XaPatTime, or XaPafterTime. It does recognize XaPfinish, storing the value in the finish parameter which is passed back up the chain to the caller of processVarargs, the function which will send the request.

All of this has many possible ways of recursing, all of which work correctly, I believe.

The XaGet API function also has a variable argument list, but it is not name-value pairs, it is simply a list of names, so the XaServerConnection::get function does its own simple processing of variable argument lists.

9.2.2 Sending the request message -- XaServerConnection::sendMessage

After the request struct is populated and the argument lists processed the request is ready to send. Each XaServerConnection member function that sends requests does so by call ing XaServerConnection::sendMessage.

The parameters of the sendMessage function are the request struct, the variable length data and its length, and the finish struct. This variable length data is a pointer into the proto Buffer.

The function starts with a switch statement on the minorOpcode (the audio opcode) in the request. For each request it calls a convoluted macro _XaServerConnection_GetHeader passing the opcode and the type name of the request struct. This macro calls the IceGetH eader macro to reserve space in the ICE connection's output buffer, and then copies the request struct into this buffer. NOTE: This macro also resets the length field of the mes sage header because when the IceGetHeader macro sets the length it assumes that size of the header will be padded to a multiple of 8 bytes -- we are padding the header to a 4-byte boundary.

If variable length data is to be in the request, the IceWriteData macro is used to write it to the ICE output buffer.

If necessary, the total message is padded to an 8-byte boundary using IceWritePad, and IceFlush is called to actually send the message across the wire.

After this there is code to wait for a reply to the message if expected, but this will have to be completely reworked when the event design is more final and when an event queue is added to the client library.

9.3 Receiving and parsing requests in the server ..

(Most of this is in programs/Xaserver/dia/clientconn.cc )

          Array of XaAttributeCBData structs:
          typedef struct {
              XaTag   name;
              XaTag   type;            // not filled in
              void    *value;        // should really be XaArgVal
              void    *newValue;      // not filled in
          } XaAttributeCBData;
          struct XaArray {
              ATOM        arrayAtom;
              CARD32      length;         /* number of elements */
              ATOM        name;
              CARD32      *elements;
          };

10.0 Utility Classes ..

These classes are used by both the client and server.

...

11.0 Server Scheduler ..

(From: Mark Welch <mw@x.org> Date: Wed, 21 Feb 1996)

The X Audio scheduler (1st post-implementation draft) -- with some notes on object han dling

The X Audio scheduler has been developed more or less from scratch, with guidance from the X, AF and TCL schedulers. I decided against direct reuse of AF or TCL because of simplicity, and because there was a large amount of "dead" (irrelevant) code in both the AF and TCL implementations. In addition, the dead code prevented me from hiding the scheduler implementation, which to me is a moderate-priority goal of the scheduler design. For example, in the AF server (which inherited heavily from the X11R4 server) the select() mechanism is exposed in order to support block and unblock handlers, which are unused even in the AF server.

However, I did use ideas from AF, especially in implementing the ready file task queue (below). I also had large discussions with other people in the X group, in order to avoid certain pitfalls in building the scheduler and learn some hard-won knowledge in an easier way than they did.

11.1 Notes on C++ implementation ..

I used object invariant checking whenever possible in the scheduler. What this means is: For each object I declared a method:

          void Invariant(void) const;
whose sole purpose is to assert a number of invariant conditions within each object. Then, I have each non-const member function of each object call the INVARIANT() macro. (The macro is defined to either do nothing or to call the Invariant() method, depending on the presence of the preprocessor symbol USE_INVARIANTS. Check out the implementa tion of XaArray (in programs/Xaserver/dia/scheduler.cc) for a reasonable example of how to use object invariant checking.

Invariant checking saved me from many problems that I would otherwise have spent a lot of time fixing.

11.2 Current scheduler design ..

I built an object-oriented scheduling mechanism, relying on inline functions in order to maximize performance. I do not presently know if I have caused catastrophic degradation in performance relative to AF, but I do not believe that that has happened. I also believe that I have managed to generate much more readable code in this process (although there are still a number of style warts in the present version that need to be fixed before release).

Since I did not have utility classes on hand, I built two general-purpose collection classes: XaArray and XaSortedArray (which presently live in scheduler.cc pending relocation or removal in favor of XAL classes).

Things the scheduler presently delivers are:

Things the scheduler does not presently deliver, but which need to be added soon:

I don't include such items as port service/creation or breakup of large client requests, because these features use the scheduler but do not impinge directly on its design. How ever, these need to be added to the dia layer.

11.3 Object taxonomy ..

11.3.1 XaObject:

Base class of all objects in the audio server. (Also, the simplest, as it has no data or func tion members at this time.)

11.3.2 XaWallTime:

This should eventually devolve into two classes, XaTime and XaSystemClock. XaWall Time is a C++ wrapper around a struct timeval (used by gettimeofday()) with the follow ing basic categories of methods defined:

11.3.3 XaTask:

This represents a thing for the scheduler to do when some specified condition has been met. The constructor takes a pointer to a function of the form:

          void    MyTaskHandler(XaTask* task, void *userData);
as well as a pointer to userData (which is passed back to the function when the task is run). The method XaTask::HasFD() is a hack designed to let the scheduler know whether to treat the task as a file descriptor- based task or a timed task. (The way this is done should be rearranged somewhat, but it works presently.)

11.3.4 XaFileTask:

A task associated with a file descriptor. Currently, the file descriptor passed into the con structor is assumed to be an input file descriptor; therefore, the scheduler polls only for input availability on the file descriptor. (See notes below on file descriptors)

11.3.5 XaTimedTask:

A task that is to be run at a specific time. Currently, this time is expressible only as an XaWallTime (system clock). Some time after the specified wall clock time has passed (usually right at the specified time), the scheduler dequeues and runs the task.

An interesting method that will be of use is the Reschedule() method:

          void     Reschedule(const XaWallTime& when);
Given a time, this method causes the task to be rescheduled to the new time (and therefore reinserted into the scheduling queue). There are a couple of advantages to doing resched uling this way: a) reused tasks mean fewer opportunities for memory leakage, and b) it may be easier to generate an adaptive profile of how long a given periodic task takes to run. A disadvantage is that this does not lend itself well to a C interface, but there might be ways in which we can alleviate this.

11.3.6 XaScheduler:

The scheduling engine itself. There is only one of these per server. The Scheduler() func tion returns a reference to the scheduler object. Methods of the scheduler, and some basic descriptions:

Private functions:

          int        NextTaskTimeOffset(void) const;
This function returns the number of milliseconds until the earliest timed task is to execute.

          XaTask*         NextReadyTimedTask(void);
This function returns the earliest timed task that is ready for service. If no more ready timed tasks remain, NextReadyTimedTask returns NULL.

          XaTask*         NextReadyFileTask(void);
This function returns the next file descriptor-based task that is ready for service. If no more ready file descriptor tasks remain, NextReadyFileTask returns NULL.

          XaTask*     NextReadyTask(void);
This function calls NextReadyTimedTask(), returning the next ready timed task if avail able. If no timed task is ready, NextReadyTask calls NextReadyFileTask() and returns the result.

          int             WaitForReadyTask(void);
This method determines the amount of time until the first timed task, then performs a poll() on all file descriptors with a timeout set to correspond to that of the earliest timed event.

Public functions:

          void     AddTask(XaTask& task); 
          void     RemoveTask(XaTask& task);
Add or remove a task from the scheduler.

          void     Run(void);
Runs the scheduling loop. (It's about 8-9 lines long. Should be easy to read.)

11.3.7 XaArray:

An arbitrary-sized ordered collection. Array indices start with 0, but this can be changed pretty quickly. The most controversial calls in the array are malloc(), realloc(), and mem move(). As pointer buffer space runs out, the size is doubled via the internal method XaArray::GrowArray(). Some notable methods:

          void            Add(XaObject *elem, int beforeWhich);
Adds an object before element (beforeWhich) in the collection. If (beforeWhich) is less than or equal to 0, the new item is placed at the beginning of the list; if (beforeWhich) is greater than or equal to the number of elements in the list, the new item is appended to the list.

          virtual int     Find(XaObject *elem) const;
XaArray::Find locates an object in the array whose pointer is the same as (elem) and returns the array index at which it may be found.

          void            Delete(XaObject *elem); XaObject *      
          AtDelete(int whichElem);
Delete and AtDelete do not destroy the objects, but merely remove them from the list.

11.3.8 XaSortedArray:

An array sorted according to a sort function specified by the creator. The sort function must be of the form:

          int mySortFunction(XaObject *obj1, XaObject *obj2);
Legal return values are:

          const int           XaObjectsEqual = 0; 
          const int           XaObject1Greater = 1; 
          const int           XaObject1Less = -1;
(Yes, the names aren't stylistically correct; this will change, but everyone will be warned beforehand.)

The Add method for an XaSortedArray only takes an object pointer; the location in the array is determined by the sort function. Do not use the Append() method on an XaSorte dArray, because it will corrupt the sort order and the invariant checker will become quite unhappy. Otherwise, use as you would an XaArray.

11.4 Issues ..

11.4.1 Usage of poll()

I decided to use poll() instead of select() after a conversation with Stephen Gildea. From what I can tell, poll() is a POSIX system call as well, and provides better fd configuration options and a cleaner interface.

To support the pollfd array needed by poll(), I set up a large array of pollfd structures within the scheduler object, each element of which corresponds to its like-numbered XaFi leTask in the file task list. I did this in order to facilitate quick replacement of fds in a file task (perhaps by a device or extension?). Problems may occur with keeping file descrip tors in sync with file tasks, especially in situations where the server receives a signal to reset, kill, or recheck all connections. I don't know of any problems, but the code feels a bit unstable at this time.

11.4.2 File descriptors

My current thoughts on this are that the constructor for XaFileTask ought to take a set of flags indicating the intended use of the file descriptor (input/output of various types etc). I'm not sure how to represent this, since we could either go with a poll() type model for flags (with many different checkable conditions on an fd) or a select() type model (input, output, and exception only). I'm leaning towards a select() model even though we use poll() presently in the scheduler, because it's easier to express select() conditions in a poll() than the other way around. I'm open to suggestions here.

11.4.3 Regarding multiple timelines

I've had some go-around on this between myself and David and Ray, and here is a current set of my thoughts on this (for those of you just tuning in).

There is a problem associated with scheduling timed tasks: when a task asks to be awak ened at a specific time, the scheduler can only guarantee that the task will execute *no ear lier than* that time. This is because there is no good way presently to measure how quickly other tasks in the server execute. For example, if task 1 is scheduled at 2:00 and task 2 at 2:05, but task 1 takes 7 minutes to execute, the scheduler presently can only run task 2 at 2:07. There is no way that I know of (other than replacing Unix) to set up a prior ity interrupt system within a process, or to set up multiple real-time clock interrupts.

We should allow expression of times in terms of arbitrary timelines, as long as there is a translation available between the reference clock and the system clock. (These clocks, as David pointed out, need not be visible to the client, but need only exist for the benefit of the scheduler.) A mapping will exist between each device clock and the system clock, adjusted periodically by the server. I'm not sure how to represent the current time index; I think we want to embed the current time index within the clock for now, but this is only useful for the implementation itself; time values per se are still distinct from the notion of timelines per recent documentation.

Therefore, a constructor for XaTimedTask might appear as follows:

          XaTime  myTime; XaClock myClock;
          myClock = pDev->GetClock();  // Clock (timeline) struc
          ture myTime = myClock->GetTime() + 1000; // 1000 sam
          ples from now
          // for system clock we'll have a global function Sys
          temClock() // for offsets from current wall time we'll 
          have
          task = new XaTimedTask(myProc, myData, myTime, 
          myClock);

12.0 Encoding, sending, and receiving of events and replies ..

(Date: Fri, 23 Feb 96 From: "Peter Derr" <pderr@zk3.dec.com>)

This document will the describe encoding, sending, and receiving of events and replies, messages from the server to the client.

[ Note on confusing names: XaServerConnection is the client's connection to the server; XaClientConnection is the server's connection to the client. ]

12.1 Events and Replies ..

Events and Replies are messages from the server to the client. The term `event' is used here as a generic term meaning both events and replies. A reply is an event sent in response to a request which is explicitly expecting the reply.

12.2 Event encoding ..

The encoding of events, as specified in the Protocol document, is defined in the Xaproto col.h header file. For each event there is a struct defined that exactly matches the wire encoding of the event (except for variable-length data at the end of many events which is not included in the struct definitions). At The beginning of each event is the ICE header:

          CARD8       majorOpcode; 
          CARD8       minorOpcode; 
          CARD8       data[2]; 
          CARD32      length B32;
The two bytes of data in this header are not used by ICE and the Audio protocol is free to use them, although it doesn't yet.

Each field in each event must be naturally aligned. Also, the total length of each event must be padded to an 8-byte boundary.

For each event that is a reply the next field after the header is the replyID:

          CARD32 replyID;

12.3 Server-side Event pipeline ..

12.3.1 Preparing the event

So far the only events that the server can send are the Startup event and the replies to the FindAtom, FindObject, Get, and Read requests. In programs/Xaserver/dia/clientconn.cc the XaMasterConnection::protocolActivateProc prepares and sends the Startup event and each of the XaClientConnection::FINDATOMprocessMsg, FINDOBJECTprocessMsg, GETprocessMsg, and READprocessMsg functions prepares and sends the corresponding replies. In each case a struct of the appropriate event type is declared and populated with the minorOpcode (the Audio protocol opcode) and the data specific to the event. If the event has variable-length data this is prepared in a buffer. Then XaClientConnec tion::sendMessage is called to actually send the message.

There is no code yet that sends Change, Create, or Destroy events. There are several cases in the code where Error Events or replies should be sent but it doesn't yet.

          

12.3.2 Sending the event -- XaClientConnection::sendMessage

(In programs/Xaserver/dia/clientconn.cc)

          XaClientConnection::sendMessage(XaProtoHeader *msg
          Header, int dataLen,char *data)
After the event struct is populated and the variable data prepared the request is ready to send. Each XaClientConnection member function that sends requests does so by calling XaClientConnection::sendMessage.

The parameters of the sendMessage function are the event struct, the variable length data and its length. This variable length data can be a pointer into the protocolBuffer.

The function starts with a switch statement on the minorOpcode (the audio opcode) in the event. For each event it calls a convoluted macro _XaClientConnection_GetHeader pass ing the opcode and the type name of the event struct. This macro calls the IceGetHeader macro to reserve space in the ICE connection's output buffer, and then copies the event struct into this buffer. NOTE: This macro also resets the length field of the message header because when the IceGetHeader macro sets the length it assumes that size of the header will be padded to a multiple of 8 bytes -- we are padding the header to a 4-byte boundary.

If variable length data is to be in the event, the IceWriteData macro is used to write it to the ICE output buffer.

If necessary, the total message is padded to an 8-byte boundary using IceWritePad, and IceFlush is called to actually send the message across the wire.

12.4 Receiving and parsing events in the client library ..

Whenever the client library expects to receive a message it calls IceProcessMessages. IceProcessMessages will check the major opcode of the message and if it is ICE protocol, ICE will handle the message; if it is Audio protocol, it will call the callback (XaServer Connection::processMsgProc) previously registered in the call to IceRegisterForProto colSetup. The first few messages will be ICE protocol messages handling the setup and authentication of the connection, but after that all messsages should be Audio protocol messages.

The client library expects to receive a message when

12.4.1 Waiting for replies in XaServerConnection::sendMessage

(this is in lib/Xa/serverconn.cc)

When the client sends a request that expects a reply, a unique number (the ICE message sequence number) is assigned to the reply_id field of the request.

When waiting for a reply, the replyWaitInfo and replyReady parameters of IceProcess Messages are used to pass and receive information to/from the processMsgProc callback and are used to determine whether or not the reply received is the expected reply. The pro cessMsgProc checks the message it reads comparing it with the info in the IceReplyWait Info *replyWait struct. If it is the reply being waited for it fills in the reply field of the replyWait struct and sets the replyReadyRet to True and returns. If it is not the reply being waited for (or if replyWait is NULL) it must put the message on the event queue or per haps find an event handler waiting for this event and call it (NOT YET IMPLEMENTED) and (if replyWait is not NULL) sets the replyReadyRet to False and returns.

When expecting a reply, sendMessage loops calling IceProcessMessages until it gets the reply it's waiting for.

Since the IceReplyWaitInfo struct has no provision for client data, the reply field of the IceReplyWaitInfo is overloaded to pass an address of a struct XaReplyWaitInfo into the processMsgProc. XaReplyWaitInfo has two fields:

          CARD32              replyID; XaArgVal            
          *returnData;
The replyID is assigned the same number as the reply_id field of the request. The return Data holds an address into which the processMsgProc can write the address of the reply's variable length data.

12.4.2 XaServerConnection::processMsgProc

          XaServerConnection::processMsgProc(IceConn iceConn, 
          IcePointer clientData,     int opcode, unsigned long 
          length, Bool swap,     IceReplyWaitInfo *replyWait,     Bool 
          *replyReadyRet)
(This is in lib/Xa/events.cc)

First there is a switch statement on the opcode. Each case calls the _XaServerConnection_ReadHeader macro passing the opcode and type type name of the corresponding event struct. This macro uses the IceReadMessageHeader macro to get a pointer of the event type into the ICE input buffer, and then it calls the appropriate XaSer verConnection method for the opcode.

12.4.3 XaServerConnection::_opcode##processMsg

          XaServerConnection::_opcode##processMsg(ptr##_opcode, 
          length, swap,       replyWait, replyReadyRet);
There's one of these member functions for each event, for example, CHANGE_EVENTprocessMsg(XaFindRequest *req, unsigned long length, Bool swap).

The XaServerConnection class has two different XaProtoBuffer members. The protocol Buffer is used to read the raw protocol from the ICE connection and the parseBuffer is used to store the parsed protocol.

Each of these functions calculates the maximum length of the message's variable length data which includes any padding. Then this amount of space is reserved in the protocol Buffer and the data is read using IceReadData and stored in the protocolBuffer. Next, for those events that have item lists (Change event, Create event, Destroy event, Get reply) the protocol is parsed into an XaArgList by parseParamList.

Audio data or collections of tags don't require any parsing and can be used directly from the protocolBuffer (unless swapping is required, and that hasn't been dealt with at all yet!!!)

For replies, the replyID from the XaReplyWaitInfo *replyInfo that was previously stuffed into the IceReplyWaitInfo *replyWait reply field is used to decide if this is the reply we're waiting for. If it is, we put the pointer to the reply into replyWait reply field and write the address of the reply's variable length data into the XaReplyWaitInfo *replyInfo returnData address so that XaServerConnection::sendMessage can get the value from the reply and return it to it's caller (closing the loop in the synchronous reply case).

If the reply is not being waited for, we must either find an event handler registered expect ing this reply and call it or put the reply on the event queue, _none_ of which is yet imple mented.

12.4.4 XaProtocolConnection::parseParamList

The parseParamlist function used to be a member of XaClientConnection but a new super class XaProtocolConnection was created to hold those members that XaClientConnection and XaServerConnection have in common (but XaMasterConnection does not). Hierar chy looks like this:

                      XaConnection
                       |        |
          XaMasterConnection     XaProtocolConnection
                                 |                 |
                        XaClientConnection    XaServerConnection
To avoid duplicating code, parseParamlist was modified so that it can either create the arrays of XaAttributeCBData or XaArgs. When it's used in the server parsing item lists from requests it creates the XaAttributeCBData arrays; when it's used in the client parsing item lists from events it creates XaArgList (array of XaArgs).

parseParamList is passed the pointer into the protocolBuffer which it treats as XaProtoI tem *itemList, the protocol length, and by reference the attribute count which it will update.