/* File: GetServer.c Copyright: © 1989-1992 by Apple Computer, Inc., all rights reserved. Change History (most recent first): 12/10/92 chp Added an #include to supply a prototype for memset(). <9> 11/23/92 DTY Fix FillRect call in show_Icon so that this file compiles again with the new QuickDraw.h. <8> 2/27/91 PKE KS: (affects ROM build only) Change thesocket from char back to short to match current AppleTalk.h (see <6>). Fixes Mac32 build. Per Kevin Stinson, because he doesn't have write access. <7> 11/18/90 PWD Restore our borrowed lomem (B50) to -1 after closing the socket. <6> 10/9/90 JAL Changed theSocket to a char to match change in AppleTalk.h. <5> 3/9/90 PWD Faster server lookup; more aggressive retries for missed image blocks. <4> 2/20/90 PWD More aggressive about sending wks_reqs if the server is found. <3> 2/1/90 PWD re-checking in after fighting with projector <3> 2/1/90 PWD Always allocate memory on the heap. <1.1> 12/12/89 PWD Added support for self-authenticating images <1.0> 10/30/89 PWD Adding to EASE To Do: */ /*EASE$$$ READ ONLY COPY of file “GetServer.c” ** 1.1 PWD 12/12/1989 Added support for self-authenticating images ** 1.0 PWD 10/30/1989 Adding to EASE ** END EASE MODIFICATION HISTORY */ /* File: GetServer.c Project:Das Boot Author: Patrick Dyson These routines find the server, set up our socket listener, and send the user map request. */ /* Modification History -------------------------- 5/30/89 10:19:20 AM : New Today 5/31/89 2:49:45 PM : find_server done, support routines in progress. 5/31/89 8:10:25 PM : support routines done. To do: socket listener mods, open/close skt 6/1/89 7:29:03 PM : socket listener routine for user record reply done. 6/5/89 2:14:22 PM : socket listener does image packets now, separate C glue for that packet type. Single copy restored for image data. 6/9/89 9:52:29 AM : request backoff for user requests done. Receive image bitmap setting put into socket listener. Starting image data request retransmission, history, and backoff. 6/14/89 1:23:24 PM : Boot protocol first phase done. Retransmissions constant, dynamic memory, statistics, etc. 6/19/89 6:33:36 PM : Dynamic retransmission backoff during image reception. More errors checked. ================== TO BE DONE ================== • Multiple-section images • Memory allocation & sizing schemes date -s */ #include #include #include #include #include #include /* for netBoot.h */ #include /* just for kicks !*/ #include #include #include /* BufPtr */ #include /* NumToString */ #include /* memset() */ #include "netBoot.h" /* general equates for network booting */ #include "ATBootEqu.h" /* the equates for the ATBoot driver */ /* setupGrafWorld(&DQDGlobals); */ void setupGrafWorld(GrafWorld *g) { mySetA5((int)&(g->globs)); InitGraf((Ptr)&(g->thePort)); OpenPort(&(g->ourPort)); }; void killGrafWorld(GrafWorld *g) { ClosePort(&(g->ourPort)); }; void centerRect( Rect *r, short HOffset, short VOffset) { r->top += ((r->bottom-r->top) >> 1) - VOffset; /* center and offset */ r->left += ((r->right-r->left) >> 1) - HOffset; r->bottom = r->top + VOffset + VOffset; r->right = r->left + HOffset + HOffset; }; /* show_Icon Plot the passed icon into the passed grafworld. The grafworld must have been set up by setting A5 and calling setupGrafWorld. if erase is non-zero we fill the to-be-plotted space with gray first to avoid holes under small, masked icons. */ extern void myGetMask(); extern Ptr myGetIcn(int whichIcon); void show_Icon(GrafWorld *g,Ptr iconPtr,int erase) { BitMap srcBits,maskBits; Rect dstRect = g->ourPort.portRect; centerRect(&dstRect,16,16); /* make it the middle of the screen */ SetPort(&(g->ourPort)); srcBits.baseAddr = iconPtr; srcBits.rowBytes = 4; SetRect(&(srcBits.bounds),0,0,32,32); maskBits.baseAddr = (Ptr)myGetMask; maskBits.rowBytes = 4; SetRect(&(maskBits.bounds),0,0,32,32); if (erase) FillRect(&dstRect,&(g->gray)); // erase any remnant icon. CopyMask(&srcBits,&maskBits,&(g->ourPort.portBits), &(srcBits.bounds),&(maskBits.bounds),&dstRect); }; void showProgress(register GrafWorld *g) { Ptr p; int erase = 0; // set to 1 if erase before plot icon. if (g->curIcon >= 3) return; if (g->curIcon == 0) erase = 1; p = myGetIcn(g->curIcon); g->curIcon++; show_Icon(g,p,erase); }; /* send_wksURequestPacket takes a fully formed packet and a destination address and issues a synchronous ddpWrite to send the packet. It returns any error. */ OSErr send_wksURequestPacket( RequestPacket *req, AddrBlock to) { char DDPheader[17]; /* At this point the socket, checksumflag, and wds pointer are set up. We need to set up the wds block */ /* stamp the packet */ req->record.timestamp = TickCount(); /* build the wds */ BuildDDPwds( (Ptr) req->wds, /* the wds for this write */ (Ptr) DDPheader, /* stuffed by this call */ (char *) &(req->record), /* the packet to send */ to, /* the destination address */ BOOTDDPTYPE, /* the DDP type */ sizeof(UserRecordRequest)); /* the packet size */ return PWriteDDP(&(req->MPPpb),false); }; /* do_synch_lookup performs a quick, small synchronous lookup for servers and returns any result in nbpBuffer, any non-fatal error in the result field of nbpStuff, and any fatal error as a function result. */ #define SYNCH_INTERVAL 2 /* (eight tick units) Randy Carr numbers (InterPoll) */ #define SYNCH_COUNT 2 /* same, takes 1/2 second... */ #define TO_GET 2 OSErr do_lookup( Str255 type, /* the type string to lookup */ Str255 name, Ptr nbpBuffer, /* the buffer to write into */ nbpLookupRecord *nbpStuff, /* working storage */ short numToGet, Boolean asynch, char interval, char count) { register MPPParamBlock *mppPb = &(nbpStuff->myABRecord); NBPSetEntity(nbpStuff->myEntry,name,type,"\p*"); mppPb->NBPentityPtr = &(nbpStuff->myEntry[0]); mppPb->NBPinterval = interval; /* magic numbers */ mppPb->NBPcount = count; mppPb->NBPretBuffPtr = nbpBuffer; mppPb->NBPretBuffSize = MAX_NBP_BUFFER; mppPb->NBPmaxToGet = numToGet; mppPb->MPPioCompletion = NULL; /* there are no non-fatal errors */ return PLookupName(&(nbpStuff->myABRecord),asynch); }; void make_wksURequestPacket( UserRecordRequest *requestInfo, /* to fill out req */ RequestPacket *r) { /* and the DDP info.. */ r->MPPpb.DDPsocket = BOOTSOCKET; r->MPPpb.DDPchecksumFlag = false; r->MPPpb.DDPwdsPointer = (Ptr)(r->wds); /* and the user info.. */ r->record = *requestInfo; /* moved to the wds info stage */ }; void myNumToStr(short number, Str255 string) { char *translate = "0123456789ABCDEF"; string[1] = *(translate + (number & 0x000F)); string[2] = *(translate + ((number >> 4) & 0x000F)); string[3] = *(translate + ((number >> 8) & 0x000F)); string[4] = *(translate + ((number >> 12) & 0x000F)); string[0] = (char)4; }; /* find_server looks for the server in a structured way. First, send our request to hint, if valid. Next, do a synchronous lookup for the first two servers with a short timeout and small retry rate. The socket listener communicates with the world through it's globals struct. This includes our current image id and boot server, buffers, and errors. The hint that we stored is given one directed request to respond, then is expected to get caught by the two succeeding lookups. This routine has been changed to find the first appropriate server - the one with a name matchine the number stored in pram. */ struct AddrBlock find_server( unsigned char interval, unsigned char count, Str255 type, /* what to nbp for */ unsigned short number, /* who to nbp for */ UserRecordRequest *requestInfo, /* to fill out req */ SktLsnGPtr SktGPtr)/* the socket listeners globals */ { RequestPacket req; Ptr nbpBuffer = NULL; /* buffer of nbp names table entries */ nbpLookupRecord nbpStuff; /* buffer for nbplookups */ AddrBlock server_addr; EntityName curEntity; /* for nbpextract */ AddrBlock to; /* for nbpextract */ short tuple = 1; /* for nbpextract */ Str255 server_ID; /* for the lookup */ /* make sure our socket listener is doing things, clear any requests */ SET_INVALID(server_addr); if (SktGPtr == NULL) goto exit; SktGPtr->bootImageID = INVALID_IMAGE; /* make the server name */ myNumToStr(number,&server_ID); /* build our request packet */ make_wksURequestPacket(requestInfo,&req); #ifdef USE_HINT /* send a request to the hint, if it is valid */ if ((*(int *)&hint) != INVALID_ADDR) { short error; register int now = TickCount(); error = send_wksURequestPacket(&req,hint); // wait a short while for a response... unless we got an error (like no such address) now = TickCount(); while (((TickCount()-now) < SktGPtr->retransThreshold) && (SktGPtr->bootImageID == INVALID_IMAGE) && (error == 0)) ; }; #endif nbpBuffer = NewPtrSys(MAX_NBP_BUFFER); if (nbpBuffer == NULL) goto exit; /* do a synchronous lookup for the first two servers. */ if (SktGPtr->bootImageID == INVALID_IMAGE) if (do_lookup(type,server_ID,nbpBuffer,&nbpStuff,1,false,interval,count)) goto exit; // if an error... /* send requests to any valid replies */ /* while the soket listener hasn't found a reply && we are not out of servers && nbpextract doesn't have a problem, send requests. The key is (SktGPtr->bootImageID == INVALID_IMAGE) which will change when we get a valid response. */ while ((SktGPtr->bootImageID == INVALID_IMAGE) && (tuple <= nbpStuff.myABRecord.NBPnumGotten) && ((nbpStuff.errCode = NBPExtract(nbpBuffer,nbpStuff.myABRecord.NBPnumGotten, tuple++,&curEntity,&to)) == noErr)) { send_wksURequestPacket(&req,to); }; /* while loop */ /* if we found a bootserver, we stop looking, start sending (wait retrans) */ // timeout is nbpVars.interval * 8 * 8 which is 8 ticks per interval unit, eight times // We retransmit four times, at timeout/4 = (interval * 8 * 2) if (nbpStuff.myABRecord.NBPnumGotten > 0) { register int start = TickCount(), // at the tone, the time is... retrans_interval = interval<<4; int last_sent = start; int max_wait; // how long we can wait max_wait = retrans_interval<<2; // standard retrans interval*8 while (((TickCount()-start) < max_wait) && (SktGPtr->bootImageID == INVALID_IMAGE)) if ((TickCount()-last_sent) > retrans_interval) { last_sent = TickCount(); send_wksURequestPacket(&req,to); }; }; exit: if (SktGPtr->bootImageID != INVALID_IMAGE) server_addr = SktGPtr->server_addr; /* stuff the found server addr */ if (nbpBuffer != NULL) DisposPtr(nbpBuffer); return server_addr; }; /* CLISTENER is called by the socket listener just after it reads the packet into an area in the globals. The listener checks that the packet is of our ddptype before calling, all other checks are left to us. We handle only user record replies. Packet filtering is done by the socket listener. Communication with other routines is done through the socket listener globals area, a pointer to which is passed to us by the listener. User record replies are tested for validity, and return result. Bad records, or those with non-zero error codes, and we update the tupletable. A good record and we update the image id in the globals. This makes the findserver routine quit looking and booting continues. We update the retransmission timer with the round trip time*4. */ void CLISTENER( AddrBlock from, /* packet source address */ SktLsnGPtr SktGPtr ) /* pointer to shared globals */ { if (SktGPtr->u.userReply.osID == SktGPtr->machineID) { /* did we succeed ?*/ if (SktGPtr->u.userReply.result == noErr) { /* no need to update the tuple table, just signal that we got a good one */ SktGPtr->bootImageID = SktGPtr->u.userReply.imageID; SktGPtr->server_addr = from; /* now update the retransmit timer to (4*roundtrip). The packet was stamped at the time it left, we subtract that from the time now & *4 it */ /* we update the roundtrip delay for the next portion - the image reception. */ SktGPtr->roundTrip = (TickCount()-SktGPtr->u.userReply.userData) << 2; SktGPtr->roundTrip = (SktGPtr->roundTrip < DEFAULT_ROUNDTRIP) ? DEFAULT_ROUNDTRIP : SktGPtr->roundTrip; }; }; }; /* DOIMAGEDATA is called by the socket listener AFTER reading the packet into the appropriate area in the image storage buffer. The main task is to "do the protocol stuff". This involves retransmission timers, reception bitmaps, etc. Image data packets are stored in the image data area, and several tracking fields are updated. The first is the image bitmap, set to indicate a new packet. Next is the progress and retransmission timers. We update the retransmission timer. If it is ours and we didn't have one already, we also update the progress timer. This allows the user-level routine to figure out when to retransmit an image request. The handler passes -1 in goodPacket if the packet was from our server and for our image, but one we already had. It passes 0 if the above and we didn't have this one yet. */ void DOIMAGEDATA( SktLsnGPtr SktGPtr, /* pointer to shared globals */ int newOne) /* 0 if new packet, -1 if have already */ { int now = TickCount(); /* a new, good packet has arrived */ if (newOne == 0) { if (SktGPtr->lastRetransmission) { /* non-zero if no data packets yet */ // SktGPtr->roundTrip = ((SktGPtr->roundTrip * SMOOTHING_FACTOR) + // (now-SktGPtr->lastRetransmission)) / (SMOOTHING_FACTOR + 1); SktGPtr->lastRetransmission = 0; }; SktGPtr->progressTimer = now; SktGPtr->packetsReceived++; }; /* any packet resets these */ SktGPtr->trafficTimer = now; /* and record when we got it */ }; void makeImageRequest( ImageRequestPacket *r, SocketListenerGlobals *g) { unsigned short i,mybitmapsize,lastbits = 0; r->MPPpb.DDPsocket = BOOTSOCKET; r->MPPpb.DDPchecksumFlag = false; r->MPPpb.DDPwdsPointer = (Ptr)(r->wds); r->record.Command = 3; r->record.pversion = 1; r->record.bir.imageID = g->bootImageID; r->record.bir.section = 0; r->record.bir.flags = 0; mybitmapsize = g->lastBlockNo / 8; /* one bit block, eight bits per byte */ if (lastbits = (g->lastBlockNo >> 3)) { mybitmapsize++; }; for (i=0;irecord.bir.bitmap[i] = 0xFF; if (lastbits) r->record.bir.bitmap[mybitmapsize-1] = 0xFF; }; /* makeBitmap takes two bitmaps: the already-received bitmap with a bit set for each block received, and the request bitmap which needs a bit set for each block that we don't have. We make the dest = ~the source. */ void makeBitmap( char *receivedMap, char *retransMap, short sizeInBits) { short size = (sizeInBits / 8); while (size--) *retransMap++ = ~(*receivedMap++); /* if there is a partial byte on the end, doittoit */ if (sizeInBits >> 3) { *retransMap = ~(*receivedMap); }; }; OSErr sendImageRequest( ImageRequestPacket *r, AddrBlock to, SocketListenerGlobals *g) { char DDPheader[17]; /* At this point the socket, checksumflag, and wds pointer are set up. We need to set up the wds block */ /* build the wds */ BuildDDPwds( (Ptr) r->wds, /* the wds for this write */ (Ptr) DDPheader, /* stuffed by this call */ (char *) &(r->record), /* the packet to send */ to, /* the destination address */ BOOTDDPTYPE, /* the DDP type */ /* header + bitmap size + 1 if the bitmap is not evenly divided by 8 */ 8+(g->lastBlockNo / 8)+((g->lastBlockNo >> 3) ? 1 : 0)); /* the packet size */ return PWriteDDP(&(r->MPPpb),false); }; extern void BOOTLISTEN(); extern void BOOTSTORAGE(Ptr p); /* get_image implements the Apple Boot Protocol Phase II. It looks up the server, sends user requests, asks for an image, and does the retransmissions as necessary. get_image returns 0 if all went well, an error code if not. The error codes are defined and enumerated in ATBootEqu.h. In the event of an error the code backs out as necessary, disposing and closing what needs be. The error code is held in ret_val and disposal goes through a common exit (err_exit). */ #define BITMAP_BYTES 512 /* bytes in a bitmap, one based */ extern int get_image( int lookup_timeout, /* how long (ticks) to look for the server */ unsigned short server_ID, UserRecordRequest *requestInfo, /* to fill out req */ unsigned char nbpVars, /* nbp timeouts for lookup */ Ptr *buffer, /* a place to return the image... */ AddrBlock *netServerAddr, /* a place to return the server address */ BootPktRply *ur) /* place to return the user record */ { SocketListenerGlobals g; /* our socket listener globals */ short error,i; short thesocket = 0; /* show socket not opened */ /*<8>*/ AddrBlock theserver; /* to be returned by findserver */ unsigned char interval, /* place for unpacked nbpVars */ count; int now; int ret_val = 0; /* function result holder */ ImageRequestPacket imageRequest; char bitmap[BITMAP_BYTES]; /* bitmap of outstanding image packets */ int OldA5; /* place to stash caller's A5 */ GrafWorld GrafStuff; memset((char *)&GrafStuff, 0, sizeof(GrafWorld)); OldA5 = myGetA5(); setupGrafWorld(&GrafStuff); /* set up the socket listener globals */ g.bootImageID = INVALID_IMAGE; g.machineID = MACHINE_MAC; g.bitmapBase = (Ptr) bitmap; g.lastBlockNo = 0; g.packetsReceived = 0; g.imageBuffer = NULL; g.blockSize = 0; g.trafficTimer = 0; g.progressTimer = 0; g.retransThreshold = lookup_timeout >> 3; // start with 1/8th the time g.roundTrip = DEFAULT_ROUNDTRIP; g.backOff = DEFAULT_BACKOFF; g.lastRetransmission = 0; (* (int *)&g.server_addr) = INVALID_ADDR; // cast for C 3.0 /* set up the socket listener's globals pointer */ #ifdef DEBUG DebugStr("\pSetting up listener globals"); #endif BOOTSTORAGE((Ptr) &g); /* open appletalk */ error = MPPOpen(); if (error) return error; /* build the nbpVars */ if (nbpVars != 0x00) { interval = (nbpVars & 0x0F) * 4; // low nibble is interval between lookups count = (nbpVars & 0xF0) >> 4; // high nibble is count of lookups } else { interval = 4; count = 1; }; /* open our socket */ thesocket = BOOTSOCKET; error = DDPOpenSocket(&thesocket,(Ptr)BOOTLISTEN); if (error) return error; /* findserver */ requestInfo->type = 1; requestInfo->version = 1; // set by caller from pram: requestInfo->machineID = 1; requestInfo->timestamp = 0; // set by caller from pram: strcpy(requestInfo->userName,"Patrick"); SET_INVALID(theserver); theserver = find_server(interval,count,"\pBootServer",server_ID,requestInfo,&g); if ((g.bootImageID != INVALID_IMAGE) && ((*(int *)&theserver) != INVALID_ADDR)) { /* we found an image */ /* set the globals to the newly acquired values */ g.blockSize = g.u.userReply.blockSize; /* send size */ g.lastBlockNo = g.u.userReply.imageSize - 1; /* even number of blocks */ if ((g.lastBlockNo/8 >= BITMAP_BYTES-1) || // bigger than we can ask for ((g.lastBlockNo*g.blockSize) > ((*(long *)(BufPtr)) / 4))) { // bigger than we can fit ret_val = IMAGE_TOO_BIG; goto err_exit; }; if ((g.imageBuffer = NewPtrSys(g.u.userReply.imageSize*g.u.userReply.blockSize)) == NULL) { ret_val = IMAGE_TOO_BIG; goto err_exit; }; /* build the image request */ makeImageRequest(&imageRequest,&g); /* reset the machine */ for (i=0;i<(g.lastBlockNo/8)+1;i++) bitmap[i] = 0x00; /* g.packetsReceived is 1 based, g.lastBlockNo is zero based. This makes the limit comparison below a <= one... */ while ((g.packetsReceived <= g.lastBlockNo) && (g.retransThreshold < lookup_timeout)) { /* if not progress or no packets, retransmit request */ now = TickCount(); if ((g.packetsReceived*3 / g.lastBlockNo) >= GrafStuff.curIcon) showProgress(&GrafStuff); // tell the world that we are working on it if (((now-g.trafficTimer) > g.retransThreshold) || /* we got an image packet */ ((now-g.progressTimer) > g.retransThreshold*50) ){ if (g.lastRetransmission != 0) { /* no packets since last request */ g.backOff += g.backOff; /* double the backoff factor */ g.retransThreshold = g.backOff*g.roundTrip; /* update the retransmission */ }; makeBitmap(bitmap,imageRequest.record.bir.bitmap,g.lastBlockNo); g.trafficTimer = g.progressTimer = g.lastRetransmission = now; (void) sendImageRequest(&imageRequest,theserver,&g); }; }; /* while packet not in... */ } else ret_val = NOT_FOUND; /* we didn't find an image... */ err_exit: /* dispos of the graf world */ killGrafWorld(&GrafStuff); /* restore A5 */ mySetA5(OldA5); /* close the socket */ if (thesocket) error = DDPCloseSocket(thesocket); /* reset our borrowed lomem */ BOOTSTORAGE((Ptr) -1L); // PWD <7> reset B50 to -1 /* return that most treasured buffer! */ if ((g.imageBuffer != NULL) && (ret_val)) DisposPtr((Ptr) g.imageBuffer); else *buffer = g.imageBuffer; if (ret_val) return ret_val; if (g.packetsReceived <= g.lastBlockNo) return TIMED_OUT; if ((* (int *)&theserver) == INVALID_ADDR) return NOT_FOUND; *(int *)netServerAddr = *(int *)&theserver; // return the server addr for pram hint BlockMove((Ptr)&(g.u.userReply),(Ptr)ur,sizeof(BootPktRply)); // return the user record return ret_val; };