TweetFollow Us on Twitter

Networking
Volume Number:1
Issue Number:11
Column Tag:C Workshop

Programmer's Guide to Networking

What Apple Doesn't Tell You!

By Robert B. Denny, Timothy T. Coad, Alisa Systems, Inc., MacTutor Editorial Board

It is deceptively easy to get started with AppleTalk. But once you get into non-trivial applications, things get difficult rather quickly. The drivers are poorly documented, there are a number of "features" and limitations that don't come to light right away, and there are a few bugs in the ROM that show up when using AppleTalk.

When I set about to write this article, I was completely surprised at the amount of information that I had digested over the last six months of working with AppleTalk. Back then, Tim Coad, Mike Schuster and I were given a set of pre-release drivers and a copy of Inside AppleTalk which had no documentation on the drivers at all.

We were desperate. We disassembled and commented the drivers. It started out as a "quickie" project that quickly became a time-burning obsession. We learned a lot about the internals of AppleTalk, and finally completed the project after about a month.

We then set about writing some "practice" applications and desk accessories. We all spent a good deal of time disassembling and tracing in the ROM. There are bugs in the current Mac ROM that have to do with handling asynchronous I/O and completion routines. The annoying part was that when we called Apple about the bugs, they said "Oh yes, we know about that one and its fixed in the new ROMS." I wonder why no mention was made in the software supplements? In fact, I have yet to see any information from Apple about known bugs.

Anyway, the information we had gleaned from disassembling the AppleTalk drivers turned out to be of immense value in developing applications. There are many subleties in using the MPP and ATP driver services, particularly in asynchronous I/O and completion routines.

I will try to share some of this information with you. To whet your appetite, Figure 1 shows the layout of the various components of the AppleTalk Manager (drivers and resources). The arrows are meant to show pointer relationships.

As usual for this column, the content is geared to the advanced programmer who is familiar with the Mac I/O conventions and services of the Device Manager. If you plan to do any development using AppleTalk, you'll need Inside AppleTalk, available from Apple. At a minimum, you'll need the AppleTalk Manager Programmer's Guide, furnished with the May/June "software supplement" for developers. Earlier versions contain many errors.

Basic Concepts: Requestors and Responders

Before diving in, let's look at the sequence of events of a typical requestor and responder pair using ATP and NBP. We'll use the dial-a-fortune example described last month. The server accepts ATP requests and responds with a fortune cookie message.

First, the responder (dial-a-fortune server ) opens an ATP responding socket, letting ATP assign a random (dynamic) socket number. Then it registers itself using NBP as an object of type "Dial-A-Fortune" with some arbitrary name field. At this point, the server can be located by name using NBP. Finally, the server issues an ATP "getRequest", making it ready to receive incoming fortune requests.

The requestor (client) must locate a server by name, since socket numbers are normally dynamically assigned. So it asks NBP to "Tell me the internet addresses of all objects of type 'Dial-A-Server'." Then it picks out a server and uses the corresponding internet address to issue a request. Note that the requestor does not "open" a socket with ATP. Instead, ATP opens a new socket automatically every time a request is issued, and closes it when the response is received or an error occurs. The requestor client need not be concerned with socket numbers.

The server responds to the request with a fortune cookie message. Then it issues another getRequest, making it ready for the next incoming request. Meanwhile the requestor receives the cookie response and displays it on the screen, or whatever. That's all there is to it.

AppleTalk Data Structures in C

Last month's C Workshop covered the overall architecture of the AppleTalk network and presented the "dial-a-fortune" server application written (mostly) in C. There was not enough room to publish the file "ATALK.H", which contains the structure and symbol definitions needed to compile the example.

Much can be learned from studying the data structures involved when using the services of the AppleTalk drivers MPP and ATP. Therefore, we'll discuss some practical aspects of using AppleTalk I/O services while displaying the relevant C structure definitions. We shall limit the discussion to AppleTalk Transaction Protocol (ATP) and Name Binding Protocol (NBP). Most applications need only these services. Other parts of the AppleTalk manager are useful only in rare situations.

Common Data Structures

#define byte unsigned char
#define word unsigned short
#define longword unsigned long

First, we define some new type names, "byte", "word" and "longword", which simply make the other structure definitions more easily readable.

/* AppleTalk Network Address Block */
typedef struct
 {
 word net;/* Network number */
 byte node; /* Node number */
 byte skt;/* Socket number */
 } addrBlock;

The Address Block contains the complete specification of an internetwork address. The network number is used for routing; once a packet gets to the destination net all nodes on that net can "hear" it, and the proper one picks it up and delivers it to the indicated socket in that node. The Address Block is used throughout for addressing network visible entities.

/* Unit and Reference Numbers */
#define mppUnitNum 9
#define atpUnitNum 10
#define mppRefNum-(mppUnitNum+1)
#define atpRefNum-(atpUnitNum+1)

Using Name Binding Protocol requires that you issue I/O requests to the '.MPP' device. Likewise, using AppleTalk Transaction Protocol requires I/O to the '.ATP' device. Their Device Manager unit and reference numbers are fixed as defined below:

Definitions for Name Binding Protocol

/* Entity Name Tuple */
typedef struct
 {
 addrBlock addr; /* Internet address */
 byte enumerator;/* Enumerator */
 byte entity[33*3];/* Entity Name */
 } NBPTuple;

The Name Binding Protocol provides the facilities for registering a network-visible entity (net, node and socket, the internet address) by name, and for locating registered entities by name and getting their net address. Registration assigns an entity name to a given internet address, and lookup returns one or more internet addresses given an entity name specification.

The entity name consists of three fields: the name, the type and the zone. Each field is a counted (Pascal) string up to 32 bytes in length. The name and type fields in a lookup request may contain an equal sign ("=") to indicate all names or types. The zone field always contains an asterisk ("*"), meaning "this zone". Zone support will be implemented in the future. The individual fields are Pascal strings, and the delimiters are placed in between and are not counted. A complete entity name consists of those three fields separated by delimiters as shown below:

 name : type @*  (zone is always "*")

Entity names and their corresponding internet addresses form a pair called a tuple. When registering a new entity, you supply a tuple with the nbpRegister I/O request. When doing an nbpLookup, tuples are sent to you from other systems on the internet which have entities registered that match your lookup specification. The tuple structure shown below contains an "enumerator" field, which is used internally by the NBP software to speed up filtering of duplicate lookup responses from other nodes. You need not be concerned about this.

/* Names Table Entity Queue Element */
typedef struct _NBPElem
 {
 struct _NBPElem *link;   /* Next element */
 NBPTuple tuple; /* Entity tuple */
 } NBPElem;

NBP keeps the names registered on its local node in a linked list. The tuple you supply when registering a name must be preceded by a longword that NBP will use as a list link. Together, the tuple and link longword are called a Names Table Queue Element. The list doesn't really function as a queue; there is no implied ordering in the list. However, the Mac documentation frequently refers to order-free linked lists as queues, so we will follow Apple's conventions.

/* Retry Specification */
typedef struct
 {
 byte interval;  /* Retry interval, ticks/8 */
 byte count;/* Retry count */
 } Retry;

NBP allows specification of retry interval and count on their I/O requests. The retry interval is specified in 8-tick units. Setting these values requires more careful thought than you might suppose at first.

If a remote node is too busy, it might not see a particular NBP lookup request (which may be a "real" request or a "verification" request made when checking the uniqueness of a name during registration). Therefore each "lookup" must really be a sequence of broadcast lookup requests, in the hope that everyone out there will see at least one of them and reply as required.

If you specify too few retries, or if the retries are too close together in time, some stations may miss the activity. This can result in your not finding something, or worse, registering a duplicate name. If you try too many times, it clogs the network with unnecessary traffic. If the retries span too long a time, your user will get sick of waiting for the lookup process to complete. This is the delay you get when the message "Looking for LaserWriter" appears on the screen during LaserWriter printing.

Name binding services are handled by the MPP driver, part of the AppleTalk Manager package. All NBP requests are mapped as control calls to the driver. The definition of the I/O parameter block used for MPP calls is shown below.

NOTE: Never alter the contents of a parameter block used with AppleTalk until the corresponding I/O request completes. You must use a separate parameter block for each concurrent outstanding AppleTalk I/O request.

typedef struct _MPPBlock
 {
 struct __MPBlock *ioLink;/* List link */
 short ioType; /* Next 3 used by Device Mgr */
 short ioTrap;   
 Ptr ioCmdAddr;
 ProcPtr ioCompletion;  /* -> Completion routine */
 short ioResult; /* Result code from req. */
 char *ioFileName; /* Unused *./
 short ioVRefNum;/* Unused */
 short ioRefNum; /* Driver RefNum (-10) */
 short csCode;   /* Op-code (see below) */
 MPPCsParam csParam;  /* (see below) */
 } MPPBlock;

/* NBP csCodes */
#define _nbpLoad 249 /* Load NBP */
#define _nbpConfirm 250 /* Confirm name */
#define _nbpLookup 251    /* Lookup name */
#define _nbpRemove 252  /* Remove name */
#define _nbpRegister 253  /* Register name */
#define _nbpKill 254 /* Kill NBP req */
#define _nbpUnload 255    /* Unload NBP */

The structure is typical for a control call as documented in the Device Manager section of Inside Macintosh. The csCode field selects which NBP service is being requested. The csParam field is a union of function-dependent structures relating to the particular service. This union is described later.

The _nbpLoad and _nbpUnload functions are normally not needed, as the AppleTalk drivers are loaded during startup on a 512K Mac. I haven't tried to do AppleTalk development on a 128K Mac, but I suspect it would be frustrating.

The csParam "field" is really a function-dependent continuation of the parameter block. In C, it's easiest to define this as a union, with each member struct defining the parameter layout for the corresponding driver service. It is not used with the nbpKill function, which simply kills any outstanding NBP I/O.

/* MPP Control Block */
typedef union
 {
 struct /* For "Register Name"  (csCode=253) */
 {
 Retry retry;    /* Retry int. and count */
 NBPElem *elem;  /* Pointer to entity spec */
 byte verify;    /* Whether to verify  */
 byte unused;
 } nbpRegister;

The first struct is used with csCode = _nbpRegister. You supply the retry interval and count. This determines how hard NBP tries to check for a duplicate name. A good starting point is 16 ticks (retry interval = 16/8 = 2) and 20 retries. This means it will take 5.3 seconds to register a name. Hopefully, registering a name is an infrequent operation. What you don't want is a duplicate name, so it's worth it to try over and over again to be sure your name isn't in use.

You also supply the entity name table queue element, a tuple with a link field as described previously. Finally, you may disable the name uniqueness check. If you know that the name you want to register is unique, you can bypass the check and save time. Not recommended.

 struct /* For Remove Name (csCode = 252) */
 {
 word unused;
 Entity *entity; /* Pointer to entity spec */
 } nbpRemove;

To remove a name, supply the entity specification. Nothing else is needed.

 struct /* For Lookup (csCode = 251) */
 {
 Retry retry;    /* Retry int. and count */
 Entity *entity; /* Pointer to entity spec */
 byte *retBuffPtr; /* Pointer to resp. buffer */
 word retBuffSize; /* Size of resp. buffer */
 word maxToGet;  /* Max responses to get */
 word numGotten; /* No. actually gotten */
 } nbpLookup;

The lookup is the most complex NBP operation. You supply the retry info, an entity specification, a place to put the tuples that you get back, and the maximum number of tuples that place can hold. When you issue the lookup request, NBP starts yelling all over the network "Hey, anyone out there have any names matching this description?" The retry information you supply governs the number of yells and the elapsed time between them.

If the entity specification contains any wildcard fields (name and/or type is "="), you may get back many tuples. Suppose you wanted to find all of the objects of type "Phone". You would supply an entity string of:

=:Phone@*

or "all phones" (in "this zone", but that's not really supported). NBP would then put into your buffer all of the tuples it got back from it's repeated requests. Each unique tuple is entered only once. Then it fills in the "numGotten" field with the number of unique tuples received. The tuples are packed end-to-end in the buffer.

 struct /* For Confirm (csCode = 250) */
 {
 Retry retry;    /* Retry int. and count */
 Entity *entity; /* Pointer to entity spec */
 addrBlock addr; /* addrBlock to confirm */
 byte newSkt;  /* Current socket no. */
 byte unused;
 } nbpConfirm;
 } MPPCsParam; /* End of typdef union */

As we have seen, lookup requests can take an annoying amount of time to complete. NBP provides a way to "confirm" the internet address of a named entity that may have been looked up in the past. This makes it possible for an application to keep a "black book" of names and internet addresses locally, to minimize the need for NBP lookups.

This time, we supply the usual retry info and specific entity spec (no wildcards) plus our "guess" at the corresponding internet address. If our target object is still registered at that address, we'll get back a confirmation. NBP will stop as soon as it gets a single confirmation, and only the target node will respond, so this a lot faster.

Note the newSkt field in the confirm parameters. If the named object has changed socket numbers, but is otherwise at the same internet address, the confirm will still locate it. Why is this important? Most named objects on the network will be assigned dynamic socket numbers. If the target system is rebooted, for example, the process which owns the socket in question may have been assigned a new and different socket number. Since dynamic socket numbers are randomly assigned each time a socket is opened, it is important to be able to confirm with a change in socket number. Always use the returned socket number.

NBP - Hints & Inside Information

NBP has a few quirks and gotchas that aren't mentioned in the Apple documentation. First, NBP is not actually implemented "in" the MPP driver. Rather, its code is contained in two resources of type NBPC. Resource NBPC-1 contains the code for NBP initialization, and for looking up and confirming names on other nodes. NBPC-2 contains the code implementing the name server functions, used only if names are registered on the local node. Refer to Figure 1.

Presumably, this splitting was done to make the most out of the limited memory available on the 128K Mac. On that system, you must manually "load NBP" via an MPP driver call, and the NBPC-1 resource goes into the application heap. Then if you register a name, the NBPC-1 code reads in the NBPC-2 code, again into the application heap. When your application exits to the Finder, the NBPC resources are disposed, and all resistered names are lost. On the 512K Mac, both resources are read into the system heap by the MPP open routine, and they remain there permanently.

When an application registers a name, it supplies the name table queue element. Normally, this is located in the application heap. The registration process simply links the queue element into the list of registered names. The application should take care to explicitly remove any registered names before exiting to the Finder.

If it doesn't, an interesting "safety net" process takes place. The MPP driver has the "need goodbye" flag set in its DCE flags. When an application does an _ExitToShell, the Memory Manager is called to re-initialize the application heap in preparation for loading and starting the Finder (or whatever). During this process, each driver which has need goodbye set receives a control request with a negative csCode. The driver (or desk accessory) is thus given a chance to clean up after the application.

In the case of registered names, this is very important. It is possible that a server driver may have registered a name permanently, in the system heap. The linked list of names table queue elements would be corrupted if the application heap was initialized when it contained names table entries.

When the MPP driver receives it's need goodbye call, it checks to see if the NBPC-2 resource is loaded. If so (always on 512K), it calls a special routine in the NBPC-2 code which unlinks any names table entries that it finds in the application heap. It does not touch names in the system heap. This action protects the integrity of the names table linked list.

This has important implications for desk accessories which have registered names. A desk accessory is a driver, and if it has its need goodbye flag set, it will get called at application exit. If it has a registered name, the desk accessory may explicitly call NBP to remove it as part of its clean-up operation.

What if the MPP driver gets called for need goodbye before the desk accessory? By the time the desk accessory gets its need goodbye call, the names in the application heap have already been removed by the MPP cleanup call to NBPC-2. When the desk accessory issues it's nbpRemove call, it will return with an error because the name has already been removed. The desk accessory should check for this error and ignore it.

Another thing that isn't documented is that only one NBP lookup request can be active at any time. Each lookup request is sent with a "transaction ID", and the incoming replies to that request must carry that "TID". This mechanism was presumably meant to allow multiple concurrent NBP lookups. Using the TID in the reply, the NBP can match the reply to the appropriate lookup request.

In the current implementation of NBP, the TID is kept in a single memory location in NBP's private variables. When NBP starts processing a new lookup request, it creates a new TID and jams it into the single memory cell. This instantly invalidates any incoming replies from previous lookup requests, since the TID's will no longer match.

One last thing to keep in mind is that names registered on your own node are invisible to your NBP lookups. The only way to find out what's registered on your own node is to locate the names table and search it directly. Be careful not to alter any of the information in the names table.

At location $2D8 is a pointer to MPP's private variables. At offset $90 into this area is the pointer to the first names table entry, the head of the list. See Figure 1 for an overview. This is not documented in Inside AppleTalk, nor does the offset appear in any of Apple's "equates" files, so use this for experimenting only.

Definitions for AppleTalk Transaction Protocol

Due to space limitations, this section assumes that you have access to the AppleTalk Manager Programmer's Guide, and have a reasonable understanding of ATP's theory of operation, including the "exactly once" concept.

ATP provides an error-free request/response type service. The request can carry up to 578 bytes of data, and the response can consist of up to 8 blocks of up to 578 data bytes each. The send and receive ATP I/O functions require that each of these data blocks be described by a buffer descriptor structure (BDS). The meanings of some fields vary depending on whether the BDS is being used with a send or a receive request.

/* Buffer Data Structure (S=Send, R=Receive) */
typedef struct
 {
 word buffSize;  /* S: data len R: buf len */
 byte *buffPtr;  /* S: data addr R: buf addr */
 word dataSize;  /* S: used int. R: data len */
 longword userData;/* 4 user bytes */
 } BDS;

typedef struct
 {
 BDS  bds[8];
 } BDSType;

The userData field deserves special mention. Suppose you want to build a server that delivers blocks of information that exceed 578 bytes in length. No problem, the ATP response from the server may contain up to 8 blocks of that size. Simply set up buffer descriptors to point to contiguous chunks of the response data in the responder application's buffer. The requestor application can set up receiving descriptors which point to chunks of a contiguous buffer. ATP will then reassemble the chunks at the receiving end, leaving the large data block ready to use.

But suppose the response needs some sort of "tag" or other auxiliary information. If you put it into the data, it would make the just described automatic fragmentation and reassembly technique difficult to use. That's where the userData longwords come in.

For example, the AppleTalk Printer Access Protocol (PAP) uses ATP for error and flow control, while maintaining the notion of "connections" between printing Macs and a LaserWriter. PAP uses the userBytes field to carry the "connection ID" for a particular request/response pair, the PAP message type (op-code), and an optional EOF marker.

Each BDS describes a single packet of data to be sent or received by ATP. Responses may carry up to eight such packets. When issuing a multi-packet response, the individual buffer descriptors are set up as an array of descriptors as defined above.

typedef struct _ATPBlock
 {
 struct __ATPBlock *ioLink; /* I/O Queue Link */
 short ioType; /* Next 3 used by device mgr */
 short ioTrap;
 Ptr ioCmdAddr;
 ProcPtr ioCompletion;  /* -> Completion routine */
 short ioResult; /* I/O result code */
 longword userData;/* 4 user bytes */
 short transID;  /* Transaction ID */
 short ioRefNum; /* ATP refNum (-11) */
 short csCode;   /* Op-Code (see below) */
 ATPCsParam csParam; /* Function-dependent */
 } ATPBlock;

/* ATP csCodes */
#define _atpRelRspCB 249  /* Release RspCB */
#define _atpCloseSkt 250  /* Close socket */
#define _atpAddResponse 251 /* Add response */
#define _atpSendResponse 252/* Send response */
#define _atpGetRequest 253  /* Get request */
#define _atpOpenSkt 254 /* Open socket */
#define _atpSendRequest 255 /* Send request */
#define _atpRelTCB 256    /* Release TCB */

ATP services are handled by the ATP driver, part of the AppleTalk Manager package. All ATP requests are mapped as control calls to the driver. The definition of the I/O parameter block used for ATP calls is shown below. Note that the AppleTalk Manager Programmer's Guide is wrong about the reqTID field offset for the sendRequest function.

The structure is typical for a control call as documented in the Device Manager section of Inside Macintosh. The csCode field selects which ATP service is being requested. The csParam field is a union of function-dependent structures relating to the particular service. This union is described later.

/* ATP Control Block */
typedef union
 {
 struct /* for OpenSkt (csCode = 254) */
 {
 byte skt;/* Socket number or 0 */
 byte unused;
 addrBlock addr; /* Request filter data */
 } atpOpenSkt;

The csParam "field" is really a function-dependent continuation of the parameter block. In C, it's easiest to define this as a union, with each member struct defining the parameter layout for the corresponding driver service.

The open responding socket function is used by server type applications to set up a network-visible entity which receives ATP requests and responds to them. If you supply a non-zero socket number, it will be used if possible. If the socket number is zero, ATP assigns a dynamic socket number. Normally, you should only use dynamic sockets. Statically assigned sockets are meant for special generic services that cannot use Name Binding Protocol for name-based references.

You may have ATP filter incoming requests by filling in the addrBlock with non-zero fields. If any of the addrBlock fields is non-aero, ATP will discard incoming requests that are not from the indicated socket, node and/or network.

 struct /* for CloseSkt (csCode = 250) */
 {
 byte skt;/* Responding socket to close */
 byte unused;
 } atpCloseSkt;

To close an ATP responding socket, simply issue the close function and pass the socket number. If you have registered the socket using NBP, be sure to remove the name before closing the socket.

 struct /* For SendRequest (csCode = 252) */
 {
 byte bitMap;  /* Error ctrl bitmap */
 byte flags;/* C/S flags */
 addrBlock addr; /* Resp's internet addr */
 word reqSize; /* Length of req. data */
 byte *reqPtr; /* -> Request data */
 BDS *bdsPtr;  /* -> 1st response BDS */
 byte numOfBuffs;/* No. of resp. buffers */
 byte timeOutVal;/* Timeout interval (sec.) */
 byte numOfResps;/* No. resp blocks rec'd */
 byte retryCount;/* Retry count */
 } atpSendRequest;

Once you have determined the internet address of an ATP responder, you may make requests to its responding socket by issuing a SendRequest. The request data is not described by a BDS, instead you supply a pointer and length. The user data bytes are also part of the parameter block.

As with NBP, the retry interval and count is an important design consideration when using ATP. It is common for an ATP request to be missed by the responder, usually because the responder is busy servicing a previously received request. Some responders attempt to reduce their "blind window" by issuing a series of asynchronous getRequests. Nonetheless, it is still important for the requestor to make multiple attempts to get the request across.

Unlike NBP, an ATP request may complete before the retries are exhausted. If for some reason the server is unable to respond at all, the request will take the full time (retry interval times count) before completing with an error. Normally, you should try very hard to complete an ATP request, but if it is common for the server be unable to respond, you might not want to make your user wait too long to find out.

The I/O function completes when all blocks (up to 8) of the response have been received, or the retry count is exhausted. If you issue the request asynchronously, you can monitor its progress by watching the retry count and bitmap. The retry count is decremented with each retry, the bitmap indicates which blocks of the reply have already been correctly received. The error control bitmap is used during the progress of receiving the reply to tell which pieces of the reply have been received. Be sure to clear the bitmap prior to issuing the sendRequest.

The atpXObit flag is used to request the "exactly once" mode of transaction. This should be used when you want the action generated on the server by your request to be performed only once. Remember that your request must be tried several times. In practice, you'll probably use this mode most of the time.

 struct
 {
 byte skt;/* Socket to listen on */
 byte flags;/* Request flags */
 addrBlock addr; /* Reqstr internet addr */
 word reqSize; /* Length of reqst data */
 byte *reqPtr; /* -> Rqst data buffer */
 longword unused1;
 byte bitmap;    /* Request bitmap */
 byte unused2;
 word transID; /* Transaction ID */
 } atpGetRequest;

The GetRequest function is issued by the responder to prepare for receiving a request. The socket on which to listen must have previously been set-up with an OpenATPSkt. Remember that responding sockets are "permanent" and requesting sockets live only as long as the request is in progress.

A lot of information is returned in the parameter block when the request completes. You get back the four userData bytes as a longword, the requestor's TID, atpFlags byte and bitmap, the internet address of the requestor and the actual length of the request data placed into your buffer. The bitmap is used to determine the number of blocks that the requestor expects in the response. This is required information for sending back the response (which may be sent back in pieces), as we shall see later.

Normally, requests can come from anywhere in the internet. Anyone who discovers your internet address can send you a request. You, as a responder, must be prepared to deal with "foreign" requests. It is possible that a request will have been meant for a different responder/server, and the requestor is using an out of date internet address (perhaps he should have done an NBP confirm?). The simplest solution is to simply discard the unknown requests. It's not too efficient, though, because the requestor will retry the request a number of times, wasting network and machine bandwidth. There is no way to "refuse" a request in ATP.

It may be possible to restrict the set of internet addresses from which requests will be accepted by putting non-zero values into the addrBlock field of the getRequest parameter block. This is documented in the "latest" AppleTalk Manager Programmer's Guide, but I personally have not tried it, and it's not in the code we disassembled last Winter. If it works, it could be a useful way to make a conversation private after an initial exchange of information

 struct /* for sendResponse (csCode = 252) */
 {
 byte skt;/* Response socket no. */
 byte flags;/* Response flags */
 addrBlock addr; /* Reqstr internet addr */
 word unused1;
 longword unused2;
 BDS *bdsPtr;  /* -> Response BDS's */
 byte numOfBuffs;/* # blocks in response */
 byte bdsSize;
 word transID; /* Transaction ID */
 } atpSendResponse;

Once a server has received a request, it usually performs some action then sends back a response, using the SendResponse function. Normally, all blocks of the response will be sent back in a single SendResponse operation. It is possible to send a partial response, however, then use the AddResponse function decribed later to send the remaining blocks that make up the complete response.

If you are sending back the complete response with the sendResponse, you must indicate this by setting the EOM bit in the flags. Also, if the response is part of an "exactly once" transaction, you must set the XO bit in the flags. The latter requirement is not documented; we found it to be necessary through inspection of our disassembled drivers. It may not be necessary in later versions of the drivers, but it can't hurt.

The socket number must match that of the socket on which the corresponding request was received. The internet address must be set to that of the requestor to whom the reply is being sent. The same goes for the TID, it must match that of the request.

The presence of both the numOfBufs and bdsSize fields can be confusing. bdsSize indicates the maximum number of blocks (up to 8) that may be sent back in the response, and numOfBufs indicates the number of blocks being returned in this sendResponse. More blocks may be added to the response using the addResponse function to be discussed next. Normally, though, you will set the two values to be equal and send the entire response back with the sendResponse.

 struct /* for sendResponse (csCode = 252) */
 {
 byte skt;/* Response socket no. */
 byte flags;/* Response flags */
 addrBlock addr; /* Reqstr internet addr */
 word unused1;
 longword unused2;
 BDS *bdsPtr;  /* -> Response BDS's */
 byte numOfBuffs;/* # blocks in response */
 byte bdsSize;
 word transID; /* Transaction ID */
 } atpSendResponse;

Once in a while, a situation arises in which it is desirable to overlap the processes of preparing the response data and sending back the response. Suppose the request is to look up a series of records in a complex data base and return the records in the response. If the lookup process is time-consuming, you may want the responder to return a partial result in the sendResponse, which would be issued asynchronously, then complete the transaction by issuing addResponse calls as additional pieces of information become available. The EOM bit must be set in the flags byte of the last block.

Note that this function does not use a BDS. Rather, each addResponse can send a single block. For example, if the response is to be made up of 6 blocks and one is sent with the sendResponse, then five additional addResponse calls would be needed to complete the response.

There is a snag in this if the response is part of an XO transaction. The sendResponse does not complete until the requesting end sends a "release" indicating that it has received the complete response. If you send a partial response with the sendResponse, intending to finish with one or more addResponse calls, you'll get stuck unless the sendResponse is issued asynchronously. In this situation, it is also very important that any buffers supplied in addResponse calls not be touched until the sendResponse completes. The safest way to manage a multi-phase response is to use separate parameter blocks and buffers for each phase and leave everything alone until the sendResponse completes.

 struct /* For RelRspCB (csCode = 249) */
 {
 byte skt;/* Responding socket */
 byte unused1;
 addrBlock addr; /* Source of request */
 word unused2[6];
 word transID; /* TID of request */
 } atpRelRspCB;

The "release response control block" function is used by the responder when it decides to "forget" about an exactly-once transaction before it receives the release from the requestor or it times out. It has the effect of cancelling an outstanding sendResponse. The requestor may never get the response. This function should be used only when a responder wants to abort a transaction.

 struct /* for relTCB (csCode = 256) */
 {
 word unused1;
 addrBlock addr; /* Rspndr internet addr */
 word unused2[6];
 word transID; /* Transaction ID */
 } atpRelTCB;
 } ATPCsParam;

If you want to kill an outstanding SendRequest, issue a relTCB. You must have the transaction ID (in the sendRequest parameter block). The reqTID field will contain the TID only after ATP has assigned it. You must wait for the tidValid bit in the flags to be set before you can trust the value in reqTID. This is documented in the AppleTalk Manager Programmer's Guide, but it's easy to miss.

NOTE: It is not necessary for the requesting client to manually send the XO release to the responder. The ATP driver does this automatically when it sees that the complete response has been received.

ATP - Hints and Inside Information

There were a number of errors and omissions in early AppleTalk Manager Programmer's Guides, including the version in the Promotional Edition of Inside Macintosh (the "telephone book"). It is important that you get a version at least as recent as that shipped with the May/June 1985 Software Supplement.

The Apple documents make an almost invisible mention of a very important characteristic of ATP: ATP completion routines are called with SCC interrupts disabled. This has far-reaching consequences. The SCC is used for serial communications, Imagewriter printing, many brands of hard disks and most important of all, to control the mouse. If SCC interrupts are disabled, the mouse is frozen, and no serial port activity can take place.

Often an ATP responder will issue an asynchronous getRequest specifying a completion routine, then return to the system so that useful things can happen while the responder waits for an incoming request. This sort of thing is a necessity if the responder is a desk accessory.

When the request arrives, the responder system's current activity is interrupted and the getRequest completion routine is called. If the completion tries to issue a synchronous sendResponse, the system will deadlock with a frozen mouse. The sendResponse won't complete until the SCC is turned back on, but the SCC won't be re-enabled until the completion routine returns, and it won't return until the sendResponse completes, etc. There's more ...

What if the responder has to look something up on disk before it can respond? If it has to use the floppy, the system becomes totally blind to anything on AppleTalk and the serial port for the duration of the floppy I/O. But what if the disk runs off of the serial port? Deadlock city with frozen mouse. The completion routine issues I/O to the disk which needs the SCC to function. Too bad.

Solutions to this dilemma have one thing in common. I/O should be deferred until the completion routine returns. It is important that time spent in AppleTalk completion routines must be kept to a minimum.

If the response requires little processing, it is possible issue the sendResponse asynchronously, then return from the completion routine. The SCC interrupts will be enabled and the sendResponse will proceed. WARNING: In the current ROM, there is a timing bug which will cause you trouble if you try to issue I/O in the completion routine. This is supposed to be fixed in the next release of the ROM. The problem is explained later in the article. For now, avoid doing any I/O during a completion routine or follow Mike Schuster's prescription for avoiding trouble.

If the responder is an application, the completion routine may post an event for the application to pick up the next time around its event loop. Then it can process the request and issue the sendResponse from the normal application state.

If the responder is a desk accessory, the completion routine can set a flag which is tested each time the desk accessory receives an accRun call from the system. If the flag is set, the desk accessory can process the request and issue the sendResponse asunchronously, then return to the system and the application.

There is one feature of the current ATP driver that is not documented in any of the programmer's guides, but is hinted at in Inside AppleTalk's ATP section. There is a facility where the responder may ask the requestor for a status report. The responder sets the atpSTS bit in the flags byte of a sendResponse or addResponse. When the requestor ATP receives a response with the STS bit set, it retransmits the request message with the current response bitmap. I haven't figured out why this is useful yet, but the support is there.

ATP is implemented in the '.ATP' driver (see Fig. 1) and is a client of the Datagram Delivery Protocol (DDP). ATP uses DDP as a "best efforts" routing and delivery service for it's packets. There are a few things about DDP that you should know.

DDP can manage only twelve concurrent open sockets. This limit is imposed by the speed with which the 68000 can search the socket table for a match during reception of an incoming packet. That's right, the 68000 is completely consumed during reception of an AppleTalk packet. It loops waiting for the SCC ready bit to set, then reads the byte, then decides what to do next, then loops waiting for the next byte to arrive, etc. It's the "decides what to do next" part that limits DDP's capacity.

As soon as the byte containing the socket number arrives, the DDP protocol handler has to search the socket table to locate the socket listener's entry point, then call the socket listener, which is responsible for controlling the reception of the rest of the packet. The developers presumably found that the largest socket table that could be reliably searched contained twelve entries for sockets.

When AppleTalk is loaded and started, one socket is immediately allocated to the routing information listener, who is responsible for keeping track of any internet routers on the local network. Later, If a name is registered on the local node, the "names information socket" is opened by the local NBP, making it possible for other nodes to locate the locally registered name(s).

So you might as well consider two sockets to be the property of routing and names information processes. This leaves ten sockets available for ATP.

Meanwhile, ATP has limits in its data structure allocations. Alert readers may have noticed the terms "transaction control block" (TCB) and "response control block" (respCB) used in the previous section. These are internal control structures used by the ATP requestor and responder, respectively, to maintain the status of an in-progress transaction.

ATP contains space for six TCB's, six open responding sockets, eight respCB's and 3 "I/O control blocks" (IOCB's). Each of these allocations imposes a limit on ATP's capacity. None of this is documented in the AppleTalk Manager Programmer's Guide, and believe me you can go crazy diagnosing errors without understanding these limits.

The limit of six TCB's means that a maximum of six concurrent ATP requests can be active. There can be no more than six responding sockets open at once. The respCB allocation means that there can be no more than eight concurrent XO responses in progress (there may be multiple XO responses going on a single responding socket).

Finally, the limit of three IOCB's means that there can be no more than three concurrent active DDP I/O requests. This deserves further explanation. DDP requests are active only briefly, when ATP needs to send a packet, and when it needs to open or close a DDP socket (which eventually becomes the ATP socket). This includes the following ATP operations:

openATPSkt closeATPSkt

sendRequest sendResponse

addResponse relRspCB

relTCB close ATP driver

If you try to close the ATP driver when there are not anough IOCB's available, you may be in trouble. The ATP driver's "close" routing loops waiting for a free IOCB, creating an opportunity for a deadlock. Moral: clean up before closing the ATP driver. In fact, don't close it at all unless you're running on a 128K system.

An ATP getRequest does not use an IOCB because the reception of ATP packets is handled by the ATP socket listener, which is attached to the socket when the DDP socket is opened. No further DDP I/O is needed.

If you have a busy multi-tasking application which issues lots of asynchronous ATP requests, it is possible to run out of IOCB's. There is a specific error code for that case. Now you know what it means. Since IOCB exhaustion is a timing-related problem, one way to deal with the error is to have the requesting process sleep for a while then try the ATP operation again.

With all these limits in mind, don't lose track of DDP's total limit of 12 sockets. An ATP request uses a socket for the duration of the request. An open ATP responding socket obviously uses a DDP socket. An NBP lookup request uses a DDP socket for the duration of the request.

Finally, Mike Schuster found a serious bug in the ROM's file and device queue management code. The effect of this bug is that if you perform an I/O call within an SCC interrupt routine or within a VBL task, your Macintosh may bomb. When the following code is executed, a0 contains a pointer to the I/O parameter block:

403924  lea $360,a1; a1 ->  to file system
 ; queue header
403928  jsr *-$2E08; Enqueue pb (a0) on file
 ; system queue (a1)
40392C  movesr,-(sp) ; save current status reg.
40392E  ori #$300,sr ; dsabl SCC/VBL interrupts
403932  bset#0,$360; set file system busy bit

Suppose an SCC or VBL interrupt occurs during executing of the instruction at $40392C. At this time, the parameter block has been enqueued on the file system queue, but the busy bit has not been set. If the interrupt handler performs an I/O trap, then that I/O as well as the one in the queue will be completed before the interrupt handler returns. When it does return, the file system queue is empty, and the instruction at $403932 sets the busy bit. Code following $403932 then attempts to dequeue a paramter block and execute its command code, causing a bomb.

A similar sequence of code for device drivers begins at $40118E. This code exhibits the same problem, except that the timing hole is two instructions wide. Thus, if the file system or a device driver queue is not empty and the busy bit is not set, you must not perform the I/O trap.

What is the solution? Simple. If the queue is not empty and the busy bit is not set, set the busy bit yourself, perform your async I/O trap, and reset the busy bit. Hence, your I/O is simply enqueued, and when you return, the interrupted ROM code processes the queued I/O calls correctly.

This bug has important implications to clients of the AppleTalk protocols, since (as we know) the ioCompletion calls are performed at SCC interrupt time. For example, if your ioCompletion routine for a ATP GetRequest call does a SendResponse, be sure to check the state of the ATP driver's I/O queue in its DCE. The busy bit in a DCE is bit 7 of the high order byte of the dCtlQueue word.

Final Words

This is the longest article I have written for MacTutor. It's also the most disorganized. I don't know whether to apologize for the rambling style. There was so much information to present, I was almost forced to adopt a sort-of "stream of conciousness" approach, loosely organized around the data structures needed to compile the program in last month's column.

If you find this level of information useful and/or interesting, and would like to see more, please write to me, care of MacTutor, and let him know.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.