X Audio System Implementation Design Notes
Ray Tice (X Consortium)
Mark Welch (X Consortium)
[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.]
Internally, the implementation may use C++, with the following restrictions to permit portability:
The implementation may use POSIX C library routines. [need to be more specific.]
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... }
The second is unix hacker style:
if (something_complex) { statements... if (something_nested){ more statements... } } else { more statements... }
The API wrappers dispatch the set/get/create/destroy calls to connection specific func tions that hang from the connection structure. This includes Connection dispatched API wrappers are also provided for connection management functions.
These wrappers encode the set/get/create/destroy calls into requests.
These are provide a unified interface for other routines to access the contents of var- args and arg lists.
This code provides the link to the X11/Xt resource database.
These routines bridge to the ICE library.
This code manipulates client side objects. (This code is shared with the server, and is described in "Object Model Design".
This code handles dispatching of events and queuing of events.
This code creates the client side objects (such as connections).
This code encapsulates formatting data streams into and out of audio file formats.
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:
This code provides the ability to define classes, and create instances of the classes. This code is shared with the library and is described in "Object Model Design" on page 6
This code sits above and interfaces with ICE, and controls acceptance of new connec tions. Much of this is shared with the client. See the description in "Connections" on page 7.
This code parses the variable length portion of requests in a form the server can deal with. This code is described in "Encoding Parsing of Protocol Requests" on page 12.
This deals with applying the request to objects or classes.
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?)
When a set request comes in, first we identify each attribute and do a rough validation of it. So for each attribute:
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.
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)
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:
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.
fmt = XaCreateObject(conn, XaTformat, dummyObjTag, dummyNameTag, NULL, 0);
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.)
XaConnection | | XaMasterConnection XaProtocolConnection | | XaClientConnection XaServerConnection[reformatted below, in case above figure gets mangled]
XaConnection
XaMasterConnection::XaConnection
XaProtocolConnection::XaConnection
XaClientConnection::XaProtocolConnection
XaServerConnection::XaProtocolConnection
This specifies Audio protocol name and version, and specifies the callback to be used to process Audio protocol messages (XaMasterConnection::processMsgProc)
It also specifies the authentication method names and procedures (more about this below).
This returns the major opcode that identifies the Audio protocol to ICE (I call this the iceOpCode).
This establishes the ports (sockets) to listen for new ICE connections. Note that you cannot tell ICE ahead of time what port to use (more about this below).
This creates ICE listen objects (IceListenObj), one per transport. You normally would get two, one for IP and one for the local transport.
XaOpenAudio(char *networkID, char *errString, int argc, char *argv[])
The networkID is the port identifying string that IceListenForConnections generated when the server called it. The client has to know this somehow. If the networkID is NULL it can be in the argv list as the value of the "-conn" option.
Then a new XaServerConnection object is created.
This is the client-side analogue of IceRegisterForProtocolReply. It specifies the Audio protocol name and version, and specifies the callback to be used to process Audio pro tocol messages (XaServerConnection::processMsgProc). It also specifies the authenti cation method names and procedures (more about this below).
This returns the major opcode that identifies the Audio protocol to ICE (I call this the iceOpCode).
This is called by XaOpenAudio. If the networkIds is NULL, checks the value of the XAUDIO environment variable and uses that as the ICE network ID.
Calls IceOpenConnection passing the networkIds and iceOpCode that identifies the Audio protocol.
(continued below, now to the server side)
When the scheduler (using poll) sees something to read on the listen fd it calls the task's runProc which calls acceptConnection.
Calls IceAcceptConnection passing the ICE listen object that matches the fd.
If successful, create a new XaClientConnection object. The XaClientConnection con structor uses the ICE connection id to find the fd and creates a new XaFileTask passing the fd to the constructor. XaClientConnection::ProcessMessage is specified as the Run Proc for the new XaFileTask.
The new client (XaClientConnection object) is added to the client list using XaMaster Connection::AddClient which also adds the client's XaFileTask to the Scheduler's task list which means that its fd will now be polled.
Now for each message the server receives on the client's fd XaClientConnection::Pro cessMessage is called, and in turn 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 (XaServerConnection::pro cessMsgProc) previously registered in the call to IceRegisterForProtocolSetup. 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.
When we last left the XaServerConnection::open routine it had just called IceOpen Connection. When IceOpenConnection returns all the connection setup and authenti cation will have been completed and the return status indicates whether it succeeded.
If successful, the client next needs to send an ICE Protocol Setup message which it does by calling IceProtocolSetup.
(continued below, now back the server side)
After the Protocol Setup message is received, ICE will invoke the protocol_activate_proc that was specified in the call to IceRegisterForProtocolReply. This is XaMasterConnection::protocolActivateProc which sends the initial startup mes sage or "connection burst" to the client.
Finally, the open routine calls IceProcessMessages to receive the startup message.
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.
Audio server network connection ids:
local/island.zk3.dec.com:/tmp/.ICE-unix/23144,tcp/ island.zk3.dec.com:3027You 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.
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.
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.
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.
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.
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.
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.
Now for each message the server receives on the client's fd XaClientConnection::Pro cessMessage is called, and in turn 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 (XaMasterConnection::pro cessMsgProc) previously registered in the call to IceRegisterForProtocolSetup. 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.
First, the client is identified by the iceConn ICE connection id.
Then there is a switch statement on the opcode. Each case calls the _XaMasterConnection_ReadHeader macro passing the opcode and type name of the corresponding request struct. This macro uses the IceReadMessageHeader macro to get a pointer of the request type into the ICE input buffer, and then it calls the appropri ate XaClientConnection method for the opcode.
There's one of these member functions for each request, for example FINDpro cessMsg(XaFindRequest *req, unsigned long length, Bool swap).
The XaClientConnection class has two different XaProtoBuffer members. The proto colBuffer 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 protocolBuffer and the data is read using IceReadData and stored in the protocolBuffer. Next, for those requests that have item lists (Find, Create, Set) the protocol is parsed into an array of XaAttributeCBData by parseParamList.
(this is in lib/Xa/nop/protocol/paramlist.cc)
parseParamList is passed the pointer into the protocolBuffer which it treats as XaPro toItem *itemList, the protocol length, and by reference the attribute count which it will update. It returns a pointer to an array of XaAttributeCBData structs.
A pointer of type XaProtoItem * is advanced through the raw protocol buffer, continu ing as long as the length is not exceeded and the name atom in the XaProtoItem is not 0 (the itemlist can be null-terminated). For each item, space is reserved in the parse Buffer. For scalar valued items name atom and value are simply written to the parse Buffer.
If the name atom is XaAarray, XaAcollectionReplace, XaAcollectionAdd, or XaAcol lectionSubtract the item is array-valued and the pointer into the raw protocol buffer is treated as a pointer to XaProtoArray. An XaArray struct and space for the array are allocated (with new). The fields of XaArray struct are populated with values from the XaProtoArray pointer into the protocol buffer and the XaArray struct elements field is the newly allocated array itself. The array data is copied from the raw protocolBuffer into the new array.
(XaArray and XaProtoArray are defined in Xaprotocol.h)
NOTE: multi-dimensional arrays and arrays with array-valued elements are not yet handled. Shouldn't be too difficult (they are handled on the client side).
NOTE: A Boolean swap that was provided by ICE is passed this far. It tells us whether byte swapping is necessary. We need to use it and do byte swapping as required.
So, the parsed item lists that need to be passed to the server objects' set, find, and cre ate methods look like this:
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;
If the name is atom is XaAarray, XaAcollectionReplace, XaAcollectionAdd, or XaAc ollectionSubtract then the value is a pointer to an XaArray struct:
struct XaArray { ATOM arrayAtom; CARD32 length; /* number of elements */ ATOM name; CARD32 *elements; };
The arrayAtom will be XaAarray, XaAcollectionReplace, XaAcollectionAdd, or XaA collectionSubtract. The name will be the attribute name. The elements will be a pointer to the array itself (array of CARD32).
Now we need to figure out who owns these parsed arrays and who frees them.
For the Get and Write requests no parsing of the variable lenght data is required and it should be OK to pass it directly to the server object's methods.
...
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.
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.
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:
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.)
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.
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.)
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.
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.
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.
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);
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. ]
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;
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.
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.
The client library expects to receive a message when
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.
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.
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.
XaConnection | | XaMasterConnection XaProtocolConnection | | XaClientConnection XaServerConnectionTo 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.