[comp.sys.mac.programmer] AppleTalk Zone Sample Code

rmh@apple.com (Rick Holzgrafe) (09/22/90)

I've gotten a small flood of requests for the sample AppleTalk code I 
offered a few days ago. There's been enough interest that I think I'll 
just post it. I'll do it here rather than in the source newsgroup, since 
this is where the initial interest was raised, and because the source 
newsgroup is so low-volume I'm afraid a posting there would go unnoticed. 
(If this is newsgroup abuse, my apologies!)

The following is MPW C 3.0 source for dealing with AppleTalk Zones and 
NBP. Included are routines to get the name of the local zone; a list of 
all zones; and routines for NBP registry, de-registry, and lookup. The 
code can be used without an a5 world. The NBP stuff (as opposed to the 
zone stuff) can be used asynchronously, and can be called from interrupt 
level. You will have to supply your error handling and your own 
destinations and processing for returned data.

Anyone having trouble, I'll try to help. But I'm not employed by Apple DTS 
and my presence on the net is on my own time, so I certainly can't 
guarantee support. PLEASE OBSERVE THE DISCLAIMER: this is not "official" 
Apple code. This stuff is culled from some production code I wrote a while 
back. The original code worked fine, but your mileage may vary. Use at 
your own risk.

==========================================================================
Rick Holzgrafe              |    {sun,voder,nsc,mtxinu,dual}!apple!rmh
Software Engineer           | AppleLink HOLZGRAFE1          rmh@apple.com
Apple Computer, Inc.        |  "All opinions expressed are mine, and do
20525 Mariani Ave. MS: 3-PK |    not necessarily represent those of my
Cupertino, CA 95014         |        employer, Apple Computer Inc."
==========================================================================

#include <AppleTalk.h>

/*
 * These are "global variables". They could be
 * true globals in an application; in some
 * other environment (like a DA or an INIT)
 * you can pass a pointer to this structure,
 * as this code does, to get around the lack
 * of an A5 world.
 */
typedef struct
	{
	short				index;				/* Index into list of object names		*/
	Boolean				lookupInProgress;	/* True during NBP lookup				*/
	EntityName			en;					/* Holds an object name					*/
	NBPparms			nbp;				/* For NBP calls						*/
	} myGlobals;

/*
 * GetZones:
 *	Gets the list of all zones, and the name of the local zone.
 *	Local zone is returned in myZone (which must point to at
 *	least 33 bytes of room). The list of zones must be saved
 *	in some fashion useful to you; this code just has a comment
 *	where that should happen.
 *
 *	This routine is synchronous: when it returns, all zone names
 *	have been found.
 */
OSErr GetZones (char *myZone)
	{
	short zoneIndex = 1;
	OSErr err;
	short atpRefNum, mppRefNum;
	short myNode, myNet;
	SendReqparms atp;
	BDSElement bds;
	char buf[578];	// No #define for this value, but buf MUST be this big!
		
	
	err = OpenDriver ("\P.MPP", &mppRefNum);
	if (err != noErr)
		{
		return (err);
		}
		
	GetNodeAddress (&myNode, &myNet); 
	
	err = OpenDriver ("\P.ATP", &atpRefNum);
	if (err != noErr)
		{
		return (err);
		}

	bds.buffSize = sizeof (buf);
	bds.buffPtr = buf;
	
	
	/*
	 * Get My Zone
	 */
	atp.ioCompletion = NULL;
	atp.ioRefNum = atpRefNum;
	atp.csCode = 255; // sendRequest
	
	atp.userData = 0x07000000;	// GetMyZone
	atp.atpFlags = 0;
	atp.addrBlock.aNet = myNet;
	atp.addrBlock.aSocket = 6;
	// TODO: GetBridgeAddress is new, if not imp. use low-mem global ABusVars.sysABridge
	// TODO: if bridge == 0 or net == 0, no bridge and no zones!
	atp.addrBlock.aNode = GetBridgeAddress ();
	atp.reqLength = 0;
	atp.reqPointer = (Ptr)0;
	atp.bdsPointer = (Ptr)&bds;
	atp.filler = 1;	// NumOfBuffs -- honest!
	atp.timeOutVal = 1;
	atp.retryCount = 3;
	atp.atpFlags = 0;
	atp.retryCount = 3;
	
	PBControl ((ParmBlkPtr)&atp, false);
	err = atp.ioResult;
		
	if (err != noErr)
		{
		return (err);
		}

	if (buf[0] > 32)
		buf[0] = 32;
	Pstrcpy (myZone, buf);

	/*
	 * Get all zones
	 */
	atp.ioCompletion = NULL;
	atp.ioRefNum = atpRefNum;
	atp.csCode = 255; // sendRequest
	
	atp.userData = 0x08000000 + zoneIndex;	// GetZones + index, origin 1
	atp.atpFlags = 0;
	atp.addrBlock.aNet = myNet;
	atp.addrBlock.aSocket = 6;
	// TODO: GetBridgeAddress is new, if not imp. use low-mem global ABusVars.sysABridge
	// TODO: if bridge == 0 or net == 0, no bridge and no zones!
	atp.addrBlock.aNode = GetBridgeAddress ();
	atp.reqLength = 0;
	atp.reqPointer = (Ptr)0;
	atp.bdsPointer = (Ptr)&bds;
	atp.filler = 1;	// NumOfBuffs -- honest!
	atp.timeOutVal = 1;
	atp.retryCount = 3;

	do
		{
		char *p = buf;
		
		/*
		 * This is a loop because there may be more zones
		 * than will fit in a single response to our
		 * GetZones query. So we make the request multiple
		 * times, saving off the response each time,
		 * until we have the full list.
		 */
		atp.atpFlags = 0;
		atp.retryCount = 3;
		
		PBControl ((ParmBlkPtr)&atp, false);
		err = atp.ioResult;
		
		if (err != noErr)
			{
			return (err);
			}
			
		/* save the zone names */
		while ( zoneIndex < (bds.userBytes & 0x0000FFFF) + ((atp.userData & 0x0FFFF) - 1))
			{
			/*
			 * p points to a zone name. Save it somewhere useful to you -
			 * perhaps in a List Manager list.
			 */
			zoneIndex++;
			p += (*p) + 1;
			}
			
		atp.userData = 0x08000000 + zoneIndex;	// GetZones + index again
		} while (!(bds.userBytes & 0xFF000000));

	return (noErr);
	}

/*
 * DoLookup
 *	This routine broadcasts a request for object names.
 *	Type should be the name of the type of object you're
 *	looking for (for example "\pLaserWriter").
 *	Zone should be the name of the zone you want to
 *	look in (may be "\p*" for the local zone).
 *
 *	This is an asynchronous routine: it merely places
 *	the request, and does not wait around for the results.
 *	Responses will trickle in slowly, over a period of
 *	seconds. Call PollLookup every so often to collect
 *	the responses.
 */
OSErr DoLookup (myGlobals *g, char *type, char *zone)
	{
	char *p;
	Ptr bufPtr;
	short mppRefNum;
	OSErr err;
	
	
	err = OpenDriver ("\P.MPP", &mppRefNum);
	if (err != noErr) 
		return (err);
	
	p = (char *)&g->en;
	Pstrcpy (p, "\P=");
	p += (*p + 1);
	Pstrcpy (p, type);
	p += (*p + 1);
	Pstrcpy (p, zone);
	
	/*
	 * You can't predict how many responses you'll get,
	 * nor how long the names will be. So you just have to
	 * guess how much room will be enough. Here we make room
	 * for a minimum of 30 names (at max 33 bytes per name)
	 * and (below) specify that we won't accept more than 100
	 * in any case.
	 */
	bufPtr = NewPtr (1024);
	
	g->nbp.csCode = 251;	/* NBP Lookup */
	g->nbp.ioRefNum = mppRefNum;
	g->nbp.ioCompletion = NULL;
	g->nbp.interval = 11;	// Probably should be in a resource, not hard-coded
	g->nbp.count = 5;		// Probably should be in a resource, not hard-coded
	g->nbp.NBPPtrs.entityPtr = (Ptr)&g->en;
	g->nbp.parm.Lookup.retBuffPtr = bufPtr;
	g->nbp.parm.Lookup.retBuffSize = GetPtrSize (bufPtr);
	g->nbp.parm.Lookup.maxToGet = 100;		// Arbitrarily large

	PBControl ((ParmBlkPtr)&g->nbp, true);
	g->lookupInProgress = true;
	g->index = 1;

	return (noErr);
	}

/*
 * PollLookup
 *	Call this routine periodically while there's an
 *	NBP Lookup in progress. It will collect any new
 *	responses that have recently come in.
 *
 *	It does this by looking at the values in the
 *	parameter block passes to the original NBP Lookup
 *	call. These values are updated automagically as
 *	responses arrive from the network. All we have
 *	to do is check them periodically, and save the
 *	new responses in whatever fashion pleases us.
 */
void PollLookup (myGlobals *g)
	{
	short i;
	AddrBlock addr;
	EntityName en;
	
	for (i = g->index; i <= g->nbp.parm.Lookup.numGotten; i++)
		{
		NBPExtract (g->nbp.parm.Lookup.retBuffPtr,
		            g->nbp.parm.Lookup.numGotten, i, &en, &addr);
		/*
		 * One new response has been placed in "en".
		 * Do whatever you like with it here:
		 * for example, you could add it to a display of
		 * names in a List Manager list.
		 */
		}
	
	/*
	 * Check to see if the lookup is still in progress.
	 * (It will terminate automatically after a while.)
	 */
	if (g->nbp.ioResult != 1)
		{
		g->lookupInProgress = false;
		DisposPtr (g->nbp.parm.Lookup.retBuffPtr);
		g->nbp.parm.Lookup.retBuffPtr = NULL;
		
		if (g->nbp.ioResult == nbpBuffOvr)
			{
			// More names than we had room for!
			}
		}
	else
		g->index = i;
	}

/*
 * myKillNBP
 *	Call this routine if you need to stop an ongoing
 *	NBP lookup before it completes on its own.
 *	The second parameter must be a pointer to
 *	the parameter block passed to the original
 *	NBP Lookup call, e.g.:
 *		(Ptr)&g->nbp
 *
 *	The actual kill request is made asynchronously,
 *	and then we just sit in a tight loop and poll
 *	for its completion. I don't remember why I did it
 *	that way, instead of just doing a synchronous kill.
 *	But in some environments synchronous calls are illegal
 *	or dangerous. This method is equivalent, and will
 *	always work, so there's no harm done in any case.
 */
void myKillNBP (myGlobals *g)
	{
	MPPParamBlock mpp;
	
	
	mpp.NBPKILL.csCode = 254;
	mpp.NBPKILL.ioCompletion = NULL;
	mpp.NBPKILL.nKillQEl = (Ptr)&g->nbp;
	PKillNBP (&mpp, true);
	while (mpp.NBPKILL.ioResult == 1)
		;
	g->lookupInProgress = false;
	if (g->nbp.parm.Lookup.retBuffPtr != NULL)
		{
		DisposPtr (g->nbp.parm.Lookup.retBuffPtr);
		g->nbp.parm.Lookup.retBuffPtr = NULL;
		}
	}

/*
 * NBPreg
 *	Given a name and type of an object,
 *	NBP register it on the given socket.
 */
void NBPreg (char socket, char *name, char *type)
	{
	NBPparms pb;
	short mppRefNum;
	OSErr err;
	NamesTableEntry nte;
	
	
	err = OpenDriver ("\P.MPP", &mppRefNum);
	if (err != noErr) 
		return (err);
	
	pb.ioCompletion = NULL;
	pb.ioRefNum = mppRefNum;
	pb.csCode = registerName;
	pb.interval = 8;
	pb.count = 1;
	pb.parm.verifyFlag = true;

	NTEcopy (name, type, &nte);
	nte.nt.nteAddress.aSocket = socket;
	pb.NBPPtrs.ntQElPtr = (Ptr)&nte;
	
	PBControl ((ParmBlkPtr)&pb, true);
	while (pb.ioResult == 1)
		;
	}

/*
 * NTEcopy
 *	Given name and type as pascal strings,
 *	copies them into the given NBP NamesTableEntry.
 *	(Also adds a '*' for the zone name.)
 */
void NTEcopy (char *name, char *type, NamesTableEntry *nte)
	{
	char *p;
	
	p = nte->nt.entityData;
	Pstrcpy (p, name);
	
	p += (*p) + 1;
	Pstrcpy (p, type);

	p += (*p) + 1;
	*p++ = 1;
	*p++ = '*';
	}

/*
 * UnRegister
 *	Remove a object's NBP registry
 */
void UnRegister (char *name, char *type)
	{
	NBPparms pb;
	short mppRefNum;
	NamesTableEntry nte;
	
	err = OpenDriver ("\P.MPP", &mppRefNum);
	if (err != noErr) 
		return (err);

	pb.ioCompletion = NULL;
	pb.ioRefNum = mppRefNum;
	pb.csCode = removeName;

	NTEcopy (name, type, &nte);
	pb.NBPPtrs.entityPtr = nte.nt.entityData;
	
	PBControl ((ParmBlkPtr)&pb, true);
	while (pb.ioResult == 1)
		;
	}