/* * PAP.c * * Implementation of PAP (Printer Access Protocol) * as described in Inside AppleTalk, Second Edition, 1990. * * Written by: Ken McLeod, 2026-01-07 * Last update: 2026-02-23 * * This software is provided as-is, with no warranties expressed or implied, * under the terms of the BSD 2-Clause License. See separate "LICENSE" file. */ #include #include #include #include #include #include #include #include #include #include #include "Logging.h" #include "QueryParser.h" #include "SpoolFiles.h" #include "PAP.h" /* we can't use PSendRequest since we can't get the response's user bytes */ #define USING_PSENDREQUEST_API 0 /* don't wait for a status request to be sent before asking for more data */ #define WAIT_FOR_POST_DATA_STATUS 0 /* function prototypes */ void HandleRequest(void); void HandleResponse(void); void CancelRequest(void); int SendRequest(char connID, char funcID); int SendResponse(char connID, char funcID); int SetUpRequestData(char funcID, Ptr reqBuf); int SetUpResponseData(char funcID, Ptr respBuf); void SendTickle(void); void StartTickling(void); void StopTickling(void); /* globals */ /* %%% these should all go into a PAPServer struct, * so we could potentially have multiple instances */ int requestsPending = 0; /* how many calls to PGetRequest are pending */ int responsesPending = 0; /* how many calls to PSendRequest are pending */ long jobsSpooled = 0L; /* how many print jobs we have received and spooled */ Boolean listening = false; /* are we actively listening for incoming requests? */ Boolean respondedToFirstPostConnectionStatusReq = false; /* can we call SendData yet? */ short serverRefNum = 0; /* reference number of server instance */ char gConnID = 0; /* currently open connection ID, or 0 if no connection */ char gConnSocket = 0; /* destination socket to use for this connection */ char gFlowQuantum = 0; /* flow quantum to use for this connection */ short gNet = 0; char gNode = 0; char gSocket = 0; unsigned short gWaitTime = 0; unsigned short gDataSeq = 0; /* PAP data sequence number, increments from 1 to 65,535 */ long int gLastTickleRecd = 0L; /* last time we received any packet for connection */ long int gLastTickleSent = 0L; /* last time we sent a tickle for connection */ short gMPPRef = 0; short gMyNet = 0, gMyNode = 0; short gMyRecvSocket = 0, gMySendSocket = 0; short gATPRequestResult = 0; NamesTableEntry *NTPtr = 0L; EntityName name; MPPParamBlock p; ATPPBPtr gReqPBPtr; ATPPBPtr gTickleReqPBPtr; ATPPBPtr gSendReqPBPtr; ATPPBPtr gSendRespPBPtr; ATATPRecHandle gSendReqHdl; /* outgoing request for ATPRequest */ ATATPRecPtr gSendReqPtr; /* so we only need to dereference locked gSendReqHdl once */ ATATPRecHandle gSendRespHdl; /* outgoing response for ATPResponse */ ATATPRecPtr gSendRespPtr; /* so we only need to dereference locked gSendReqHdl once */ Ptr gRReqBuf; /* buffer for received request data */ Ptr gSReqBuf; /* buffer for our sent request data */ Ptr gRRespBuf; /* buffer for response we will get back from ATPRequest */ Ptr gSRespBuf; /* buffer for outgoing response data from ATPResponse */ BDSPtr gSReqBdsPtr; /* BDS for request data that we send */ BDSPtr gRRespBdsPtr; /* BDS for the received response to our request */ BDSPtr gSRespBdsPtr; /* BDS for response data that we send */ long int gDataLen; /* total data received on current connection */ int gPrinterType; /* {0..3}, see kPrinterType definitions */ Boolean gColorRibbon; Boolean gSheetFeeder; Boolean gIW1Mode; Boolean gNoReverse; Boolean gFinishedQuery = false; Handle gQueryHdl = nil; /* query sent to us by the LW driver */ int gQueryLength; /* length of query */ short gQueryReqSeq = 0; /* sequence ID of query (last SendData we received) */ short gQueryReqTID = 0; /* transaction ID of query */ /* constants */ #define kMaxAddrBlocks 100 #define kMaxPacketSize 578 /* maximum ATP packet size we can receive */ #define kMaxResponses 8 /* maximum number of responses to expect */ #define kRespBufSize (kMaxPacketSize * kMaxResponses) #define kTickleTicks (60 * 30) /* 30 seconds in ticks */ #define kIdleTimeoutTicks (60 * 60 * 2) /* 2 minutes in ticks */ #define kPrinterTypeUnknown 0 #define kPrinterTypeLaserWriter 1 #define kPrinterTypeLQ 2 #define kPrinterTypeImageWriter 3 #define COLOR_RIBBON_INSTALLED 0x80 /* 0b10000000 */ #define SHEET_FEEDER_INSTALLED 0x40 /* 0b01000000 */ #define PAPER_OUT_ERROR 0x20 /* 0b00100000 */ #define COVER_OPEN_ERROR 0x10 /* 0b00010000 */ #define PRINTER_OFF_LINE 0x08 /* 0b00001000 */ #define PAPER_JAM_ERROR 0x04 /* 0b00000100 */ #define PRINTER_FAULT 0x02 /* 0b00000010 */ #define PRINTER_ACTIVE 0x01 /* 0b00000001 */ char *PAPFunctionNames[] = { "", /* 0 */ "OpenConn", /* 1 */ "OpenConnReply", /* 2 */ "SendData", /* 3 */ "Data", /* 4 */ "Tickle", /* 5 */ "CloseConn", /* 6 */ "CloseConnReply", /* 7 */ "SendStatus", /* 8 */ "Status", /* 9 */ }; void SetPrinterOptions(Boolean colorRibbon, Boolean sheetFeeder, Boolean iw1Mode, Boolean noReverse) { gColorRibbon = colorRibbon; gSheetFeeder = sheetFeeder; gIW1Mode = iw1Mode; gNoReverse = noReverse; } void GetPrinterOptions(Boolean *colorRibbon, Boolean *sheetFeeder, Boolean *iw1Mode, Boolean *noReverse) { if (colorRibbon) { *colorRibbon = gColorRibbon; } if (sheetFeeder) { *sheetFeeder = gSheetFeeder; } if (iw1Mode) { *iw1Mode = gIW1Mode; } if (noReverse) { *noReverse = gNoReverse; } } int GetActiveClientConnections(void) { return (gConnID) ? 1 : 0; } long JobsSpooled(void) { return jobsSpooled; } /* ---------------------------------------------------------------------------*/ /* server routines */ pascal int SLInit (EntityPtr printerName, /* AppleTalk entity name to serve */ int flowQuantum, /* # of 512-byte buffers for read */ PAPStatusPtr statusBuf, /* initial status is returned here */ int *refNum) /* server refNum for this instance */ { OSErr err; int len; if (serverRefNum != 0) { /* error: we already have a server up */ return aspNoMoreSess; } /* allocate buffers */ gReqPBPtr = (ATPPBPtr)(NewPtrClear(sizeof(ATPParamBlock))); gSendReqPBPtr = (ATPPBPtr)(NewPtrClear(sizeof(ATPParamBlock))); gSendRespPBPtr = (ATPPBPtr)(NewPtrClear(sizeof(ATPParamBlock))); gTickleReqPBPtr = (ATPPBPtr)(NewPtrClear(sizeof(ATPParamBlock))); gRReqBuf = NewPtrClear(kMaxPacketSize); gSReqBuf = NewPtrClear(kMaxPacketSize); gRRespBuf = NewPtrClear(kRespBufSize); /* 4624 bytes */ gSRespBuf = NewPtrClear(kMaxPacketSize); /* 578 bytes */ gSReqBdsPtr = (BDSPtr)(NewPtrClear(sizeof(BDSElement)*8)); gRRespBdsPtr = (BDSPtr)(NewPtrClear(sizeof(BDSElement)*8)); gSRespBdsPtr = (BDSPtr)(NewPtrClear(sizeof(BDSElement)*8)); gSendReqHdl = (ATATPRecHandle)NewHandleClear(atpSize); MoveHHi((Handle)gSendReqHdl); HLock((Handle)gSendReqHdl); gSendReqPtr = *gSendReqHdl; gSendRespHdl = (ATATPRecHandle)NewHandleClear(atpSize); MoveHHi((Handle)gSendRespHdl); HLock((Handle)gSendRespHdl); gSendRespPtr = *gSendRespHdl; gPrinterType = kPrinterTypeUnknown; len = printerName->typeStr[0]; if (len == 11 && !memcmp(&printerName->typeStr[1],"LaserWriter",len)) { gPrinterType = kPrinterTypeLaserWriter; } else if (len == 2 && !memcmp(&printerName->typeStr[1],"LQ",len)) { gPrinterType = kPrinterTypeLQ; } else if (len == 11 && !memcmp(&printerName->typeStr[1],"ImageWriter",len)) { gPrinterType = kPrinterTypeImageWriter; } if (statusBuf) { /* set initial status (same as a Status response) */ (void)SetUpResponseData(kPAPStatus,(Ptr)&statusBuf); } Message(LOG_ERR,"\pStarting network setup"); err = OpenDriver("\p.MPP",&gMPPRef); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pAppleTalk could not be opened:", err); return err; } err = GetNodeAddress(&gMyNode, &gMyNet); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not obtain our address:", err); return err; } gReqPBPtr->ATP.atpSocket = 0; /* dynamically allocate a socket */ gReqPBPtr->ATP.addrBlock.aNet = 0; /* accept requests from anyone */ gReqPBPtr->ATP.addrBlock.aNode = 0; gReqPBPtr->ATP.addrBlock.aSocket = 0; err = POpenATPSkt(gReqPBPtr, false); /* socket is returned in atpSocket */ gMyRecvSocket = (short)gReqPBPtr->ATP.atpSocket; if (gMyRecvSocket == 0 || err != noErr || gReqPBPtr->ATP.ioResult != noErr) { MessageWithIntValue(LOG_ERR_X,"\pReceive socket could not be opened:", err); return err; } else { MessageWithNetAddr(LOG_WARN,"\pListening on",gMyNet,gMyNode,gMyRecvSocket); } gSendReqPBPtr->ATP.atpSocket = 0; /* dynamically allocate a socket */ gSendReqPBPtr->ATP.addrBlock.aNet = 0; /* accept responses from anyone */ gSendReqPBPtr->ATP.addrBlock.aNode = 0; gSendReqPBPtr->ATP.addrBlock.aSocket = 0; err = POpenATPSkt(gSendReqPBPtr, false); /* socket is returned in atpSocket */ gMySendSocket = (short)gSendReqPBPtr->ATP.atpSocket; if (gMySendSocket == 0 || err != noErr || gSendReqPBPtr->ATP.ioResult != noErr) { MessageWithIntValue(LOG_ERR_X,"\pSend socket could not be opened:",err); return err; } else { MessageWithNetAddr(LOG_WARN,"\pSending on",gMyNet,gMyNode,gMySendSocket); } serverRefNum++; if (refNum) { *refNum = serverRefNum; } gFlowQuantum = flowQuantum; err = PAPRegName(serverRefNum, printerName); if (err == noErr) { listening = 1; } return err; } pascal int PAPRegName (int refNum, /* server refNum for this name */ EntityPtr printerName) /* entity name to register */ { OSErr err; short index; if (!refNum) { return paramErr; } NTPtr = (NamesTableEntry*) NewPtrClear(sizeof(NamesTableEntry)); if (!NTPtr) { return(MemError()); } NTPtr->nt.nteAddress.aSocket = (char)gMyRecvSocket & 0x00FF; p.NBPinterval = 3; p.NBPcount = 3; p.NBPverifyFlag = true; p.NBPntQElPtr = (Ptr) NTPtr; BlockMove(printerName->objStr,&(NTPtr->nt.entityData[0]),33L); index = printerName->objStr[0] + 1; BlockMove(printerName->typeStr,&(NTPtr->nt.entityData[index]),33L); index += printerName->typeStr[0] + 1; BlockMove(printerName->zoneStr,&(NTPtr->nt.entityData[index]),33L); err = PRegisterName(&p,true); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pPRegisterName returned error", err); } while (p.MPPioResult == 1) ; /* poll until PRegisterName completes */ err = p.MPPioResult; while (err == nbpDuplicate) { printerName->objStr[0]++; printerName->objStr[printerName->objStr[0]] = '1'; BlockMove(printerName->objStr,&(NTPtr->nt.entityData[0]),33L); index = printerName->objStr[0] + 1; BlockMove(printerName->typeStr,&(NTPtr->nt.entityData[index]),33L); index += printerName->typeStr[0] + 1; BlockMove(printerName->zoneStr,&(NTPtr->nt.entityData[index]),33L); err = PRegisterName(&p,true); while (p.MPPioResult == 1) ; /* try again once more */ err = p.MPPioResult; } /* save a copy of the name */ BlockMove(printerName, &name, sizeof(EntityName)); BlockMove(printerName->objStr,&name.objStr[0],33L); /* we want to be able to print from our own node to the spooler */ if (err == noErr) { p.SETSELF.newSelfFlag = 1; err = PSetSelfSend(&p, false); if (err == controlErr) { /* running on system 6? */ MessageWithIntValue(LOG_WARN_X,"\pCould not enable self-send mode:",err); err = noErr; /* don't really need this to continue running */ } } return err; } pascal int PAPRemName (int refNum, /* server refNum for this name */ EntityPtr printerName) /* entity name to remove */ { #pragma unused (printerName) OSErr err; if (gMyRecvSocket && NTPtr && refNum) { p.NBPntQElPtr = (Ptr) &(NTPtr->nt.entityData[0]); err = PRemoveName(&p,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not unregister spooler:",err); } else { MessageWithQuotedString(LOG_ERR,"\pUnregistered",(char*)&NTPtr->nt.entityData[0]); } DisposPtr((Ptr) NTPtr); gReqPBPtr->ATP.atpSocket = (char)gMyRecvSocket & 0x00FF; err = PCloseATPSkt(gReqPBPtr,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not close receive socket:",err); } else { Message(LOG_WARN,"\pReceive socket closed"); } gSendReqPBPtr->ATP.atpSocket = (char)gMySendSocket & 0x00FF; err = PCloseATPSkt(gSendReqPBPtr,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not close send socket:",err); } else { Message(LOG_WARN,"\pSend socket closed"); } } else { err = badATPSkt; } return err; } pascal int HeresStatus (int refNum, /* server refNum for updated status */ PAPStatusPtr statusBuf) /* new status string */ { #pragma unused (refNum, statusBuf) return unimpErr; /* TBI */ } pascal int SLClose (int refNum) /* server refNum from SLInit */ { OSErr err; /* Cancel any request which may be in flight */ CancelRequest(); /* Unregister from NBP and close our sockets */ err = PAPRemName(refNum, &name); requestsPending = 0; responsesPending = 0; listening = 0; serverRefNum = 0; gMyRecvSocket = 0; gMySendSocket = 0; gPrinterType = kPrinterTypeUnknown; respondedToFirstPostConnectionStatusReq = false; gConnID = 0; gDataSeq = 0; gDataLen = 0; /* Clean up allocations. */ if (gReqPBPtr) { DisposePtr((Ptr)(gReqPBPtr)); } if (gSendReqPBPtr) { DisposePtr((Ptr)(gSendReqPBPtr)); } if (gSendRespPBPtr) { DisposePtr((Ptr)(gSendRespPBPtr)); } if (gTickleReqPBPtr) { DisposePtr((Ptr)(gTickleReqPBPtr)); } if (gRReqBuf) { DisposePtr(gRReqBuf); } if (gSReqBuf) { DisposePtr(gSReqBuf); } if (gRRespBuf) { DisposePtr(gRRespBuf); } if (gSRespBuf) { DisposePtr(gSRespBuf); } if (gSReqBdsPtr) { DisposePtr((Ptr)(gSReqBdsPtr)); } if (gRRespBdsPtr) { DisposePtr((Ptr)(gRRespBdsPtr)); } if (gSRespBdsPtr) { DisposePtr((Ptr)(gSRespBdsPtr)); } if (gSendReqHdl) { HUnlock((Handle)gSendReqHdl); DisposeHandle((Handle)gSendReqHdl); } if (gSendRespHdl) { HUnlock((Handle)gSendRespHdl); DisposeHandle((Handle)gSendRespHdl); } return err; } /* ---------------------------------------------------------------------------*/ /* client routines */ /* %%% These are all unimplemented at the moment, since we're a server only. * Our implementation wants to make asynchronous ATP calls to avoid blocking * on the network. We poll request and response status by calling CheckATP * from the main runloop, but could alternatively provide ioCompletion routines. * We would need to make synchronous ATP calls instead if this synchronous * interface were to be implemented, or else spin up an inner runloop inside * each API while we wait. There doesn't seem to be a good reason to do that. */ pascal int PAPOpen (int *refNum, /* returned: connection ID ref */ EntityPtr printerName, /* AppleTalk entity name */ int flowQuantum, /* # of 512-byte buffers for read */ PAPStatusPtr statusBuf, /* returned: status string */ int *compState) /* returned: status, 0=success */ { #pragma unused (refNum, printerName, flowQuantum, statusBuf, compState) return unimpErr; } pascal int PAPStatus (EntityPtr printerName, /* AppleTalk entity name */ PAPStatusPtr statusBuf, /* returned: status string */ long int *nodeAddr) /* returned: node address */ { #pragma unused (printerName, statusBuf, nodeAddr) return unimpErr; } pascal int PAPRead (int refNum, /* connection ID from PAPOpen */ char *rxBuf, /* buffer in which to read the data */ int *rxBufLen, /* size of the data read on output */ int *eofByte, /* set to positive value when EOF */ int *compState) /* returned: status, 0=success */ { #pragma unused (refNum, rxBuf, rxBufLen, eofByte, compState) return unimpErr; } pascal int PAPWrite (int refNum, /* connection ID from PAPOpen */ char *txBuf, /* buffer with data to be written */ int txBufLen, /* size of the data to be written */ int eofByte, /* set to positive on last buffer */ int *compState) /* returned: status, 0=success */ { #pragma unused (refNum, txBuf, txBufLen, eofByte, compState) return unimpErr; } pascal int PAPClose (int refNum) /* connection ID from PAPOpen */ { #pragma unused (refNum) return unimpErr; } pascal int PAPUnload (void) /* close connections, free storage */ { return unimpErr; } /* ---------------------------------------------------------------------------*/ void HandleRequest(void) { ATPPBPtr atp = gReqPBPtr; char connID = *((char*)&atp->ATP.userData + 0); char funcID = *((char*)&atp->ATP.userData + 1); short seqID = atp->ATP.userData & 0x0000FFFF; short net = atp->ATP.addrBlock.aNet; char node = atp->ATP.addrBlock.aNode; char socket = atp->ATP.addrBlock.aSocket; if (funcID < kPAPOpenConn || funcID > kPAPStatus) { funcID = 0; /* map unhandled functions to string */ } MessageReceivedFromNetAddr(LOG_INFO, funcID, false, net, node, socket); if (gConnID == connID && connID != 0) { gLastTickleRecd = TickCount(); /* update last activity on connection */ } if (gConnID && connID && funcID == kPAPOpenConn) { /* We only handle one connection at a time, and we already have one open. Generally, a client trying to open a connection will keep retrying until it succeeds or they decide to not wait anymore. If this is a duplicate retry attempt for the already-open connection, then we'll drop it. Otherwise, we reply with OpenConnReply "server busy" status, per IA:10-7. */ if (gConnID == connID) { return; } } switch (funcID) { case kPAPOpenConn: if (gConnID) { MessageWithIntValue(LOG_WARN_X,"\pIgnoring new connection",(int)connID & 0xFF); SendResponse(connID, kPAPOpenConnReply); return; } MessageWithNetAddr(LOG_ERR,"\pReceiving print job from", net, node, socket); MessageWithIntValue(LOG_ERR,"\pOpening connection",(int)connID & 0xFF); (void) OpenSpoolFile(gPrinterType); gNet = net; gNode = node; gSocket = socket; gConnSocket = gRReqBuf[0]; gFlowQuantum = gRReqBuf[1]; /* set before sending OpenConnReply */ if (gFlowQuantum < kPAPMinQuantum) { gFlowQuantum = kPAPMinQuantum; } if (gFlowQuantum > kPAPMaxQuantum) { gFlowQuantum = kPAPMaxQuantum; } BlockMove(&gRReqBuf[2], &gWaitTime, 2); /* %%% gWaitTime is supposed to tell us which OpenConn to handle first. * But we ignore it and just accept the first connection immediately. */ SendResponse(connID, kPAPOpenConnReply); gConnID = connID; /* connection is now established */ StartTickling(); /* send periodic tickles */ break; case kPAPOpenConnReply: /* this is never sent as a request, only as a response */ break; case kPAPSendData: /* client is asking us to send data */ #if 0 /* %%% while this is correct behavior, if we miss the first SendData, we will drop subsequent retries as dupes. Need to keep track of what we processed successfully. */ if (gQueryReqSeq >= seqID) { MessageWithIntValue(LOG_INFO_X,"\pIgnoring dup SendData seq:",seqID); break; /* avoid processing a duplicate request, per IA:10-11 */ } #endif /* remember transaction ID and new sequence ID */ gQueryReqSeq = seqID; if (!gQueryReqTID) { gQueryReqTID = atp->OTH2.transID; } if ((gPrinterType != kPrinterTypeLaserWriter) || (!gFinishedQuery && gQueryHdl && gQueryLength)) { /* we can respond right away */ SendResponse(connID, kPAPData); gFinishedQuery = true; gQueryReqTID = 0; } else { /* need to pend the response until we've read Data with the query */ MessageWithIntValue(LOG_INFO,"\pPending response to TID:",gQueryReqTID); /* %%% DEBUG * It would seem logical to set gFinishedQuery=false here, so the * next Data would be put into gQueryHdl instead of the spool file, * but unfortunately actual data comes in before query data, and * we quickly run out of memory. For now, gFinishedQuery cannot be * set back to false again once the initial query has been processed. * Need to better understand how to handle multiple SendData requests * from the client which have different TIDs. */ } break; case kPAPData: /* this is never sent as a request, only as a response */ break; case kPAPTickle: /* client sent us a tickle so we don't time out, handled above */ break; case kPAPCloseConn: /* this is the normal sequence on success: client closes the connection */ if (gDataLen > 0) { MessageWithIntValue(LOG_ERR,"\pReceived data bytes:", gDataLen); jobsSpooled++; } CloseSpoolFile(gDataLen); gDataLen = 0; if (gConnID) { StopTickling(); /* stop periodic tickles before connection closes */ CancelRequest(); /* cancel any outstanding requests so we don't wait */ MessageWithIntValue(LOG_ERR,"\pClosing connection",(int)connID & 0xFF); SendResponse(connID, kPAPCloseConnReply); gConnID = 0; /* connection is now closed */ respondedToFirstPostConnectionStatusReq = false; gLastTickleRecd = 0L; } break; case kPAPCloseConnReply: /* this is never sent as a request, only as a response */ break; case kPAPSendStatus: SendResponse(connID, kPAPStatus); if (gConnID && !respondedToFirstPostConnectionStatusReq) { respondedToFirstPostConnectionStatusReq = true; gDataSeq = 1; gDataLen = 0; SendRequest(gConnID, kPAPSendData); /* ask for data segment #1 */ } break; case kPAPStatus: /* this is never sent as a request, only as a response */ break; default: MessageWithNetAddr(LOG_WARN,"\pGot unknown request from", net, node, socket); break; } } void CancelRequest(void) { OSErr err = noErr; #if USING_PSENDREQUEST_API if (gSendReqPBPtr->ATP.ioResult > noErr) { err = PKillSendReq(gSendReqPBPtr, true); gSendReqPBPtr->ATP.ioResult = reqAborted; } #else if (gSendReqPtr->abResult > noErr) { err = ATPReqCancel(gSendReqHdl, true); gSendReqPtr->abResult = reqAborted; } #endif if (responsesPending > 0) { responsesPending--; } if (err != noErr && err != cbNotFound) { MessageWithIntValue(LOG_WARN_X,"\pUnable to cancel request:",err); } } #if USING_PSENDREQUEST_API void HandleResponse_from_PSendRequest(void) { /* %%% PROBLEM %%% * The ATPPBPtr (to ATPParamBlock) does not give us access to the * user bytes for the response, separate from the request. * Request user bytes are in MPPATPHeader's userData field, but * where is the info we would find in ATATPRec's atpRspUData field? */ ATPPBPtr atp = gSendReqPBPtr; char connID = *((char*)&atp->ATP.userData + 0); char funcID = *((char*)&atp->ATP.userData + 1); char eofByte = *((char*)&atp->ATP.userData + 2); short net = atp->ATP.addrBlock.aNet; char node = atp->ATP.addrBlock.aNode; char socket = atp->ATP.addrBlock.aSocket; if (funcID < kPAPOpenConn || funcID > kPAPStatus) { funcID = 0; /* map unhandled functions to string */ } MessageReceivedFromNetAddr(LOG_INFO,funcID, true, net, node, socket); if (gConnID) { if (gConnID == connID) { gLastTickleRecd = TickCount(); /* update last activity for connection */ } } switch (funcID) { case kPAPData: MessageWithIntValue(LOG_INFO,"\pReceived data of length", ((BDSPtr)atp->ATP.bdsPointer)->dataSize); gDataLen += ((BDSPtr)atp->ATP.bdsPointer)->dataSize; if (!eofByte) { /* as long as we haven't hit EOF, keep asking for more data */ if (++gDataSeq == 0) { gDataSeq = 1; /* reset to 1 if we wrap around, per IA 10-11 */ } SendRequest(gConnID, kPAPSendData); /* ask for next data block */ } break; case kPAPCloseConnReply: break; default: MessageWithNetAddr(LOG_WARN,"\pGot unknown response from", net, node, socket); break; } } #endif void HandleResponse_from_ATPRequest(void) { ATATPRecPtr abp = gSendReqPtr; char connID, funcID; char eofByte; short net = abp->atpAddress.aNet; char node = abp->atpAddress.aNode; char socket = abp->atpAddress.aSocket; char *data = (char*)abp->atpRspBuf; BDSPtr bds = abp->atpRspBDSPtr; int numRsp = abp->atpNumRsp; int index, dataLen, length = 0; for (index=0; index < abp->atpNumRsp; index++) { dataLen = ((BDSElement*)bds)[index].dataSize; /* align response blocks so their data is contiguous */ BlockMove(((BDSElement*)bds)[index].buffPtr,data+length,dataLen); length += dataLen; } abp->atpRspUData = ((BDSElement*)bds)[numRsp-1].userBytes; connID = *((char*)&abp->atpRspUData + 0); funcID = *((char*)&abp->atpRspUData + 1); eofByte = *((char*)&abp->atpRspUData + 2); if (funcID < kPAPOpenConn || funcID > kPAPStatus) { funcID = 0; /* map unhandled functions to string */ } MessageReceivedFromNetAddr(LOG_INFO,funcID, true, net, node, socket); if (gConnID) { if (gConnID == connID) { gLastTickleRecd = TickCount(); /* update last activity for connection */ } } switch (funcID) { case kPAPData: MessageWithIntValue(LOG_INFO,"\pReceived data with length",length); MessageWithIntValue(LOG_DEBUG,"\p-- eofByte:",(short)eofByte); MessageWithIntValue(LOG_DEBUG,"\p-- dataSeq:",(short)gDataSeq); MessageWithDataPointer(LOG_DEBUG,"\p-- data:",data,length); /* The LQ driver will send us a 2-byte Data response as the first * and last packets of the data sequence (at minimum). If we haven't * responded to its SendData request, this will be a self-id command * (esc-?, or 0x1B3F). If we have responded to its SendData with the * 'LQ1\r' identity string, then the data we get here will be simply 'LQ'. * Either way, we need to filter this out as it isn't meant to be printed. */ if ((length == 2) && (gPrinterType == kPrinterTypeLQ) && ((*(data+0) == 0x1B && *(data+1) == 0x3F) || (*(data+0) == 0x4C && *(data+1) == 0x51))) { gDataSeq++; Message(LOG_INFO,"\pFiltering 2 data bytes (LQ self-id)"); } else if (!gFinishedQuery && gPrinterType == kPrinterTypeLaserWriter) { /* data sent to us after their SendData is a query, until '%%EOF' */ OSErr err; gDataSeq++; /* save this query; we will need it to compose a response */ if (!gQueryHdl) { /* first part of query */ gQueryHdl = NewHandle((Size)length); /* handle error if this call fails, no pun intended */ if ((err=MemError()) == noErr) { HLock(gQueryHdl); BlockMove(data,*gQueryHdl,length); HUnlock(gQueryHdl); gQueryLength = length; } } else { /* continuation of query */ SetHandleSize(gQueryHdl,(Size)gQueryLength+length); /* handle error if this call fails, no pun intended */ if ((err=MemError()) == noErr) { HLock(gQueryHdl); BlockMove(data,*gQueryHdl+(Size)gQueryLength,length); HUnlock(gQueryHdl); gQueryLength += length; } } if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not save query:",err); return; } MessageWithIntValue(LOG_INFO,"\pFiltering query bytes:",length); MessageWithDataPointer(LOG_DEBUG,"\p-- end: ",data+(length-6),5); if ((length>5 && !memcmp("%%EOF",data+(length-6),5)) || (length>9 && !memcmp("unknown\r\r",data+(length-10),9))) { /* got EOF or unknown at end of PostScript query; * now we can reply to it */ MessageWithIntValue(LOG_INFO,"\pGot EOF; total query:",gQueryLength); if (gQueryReqTID) { SendResponse(gConnID, kPAPData); gFinishedQuery = true; gQueryReqTID = 0; } /* else we have to wait for the SendData request to be retried */ } eofByte = 0; /* clear so we ask for more data */ } else { WriteDataToSpoolFile(length,data); gDataLen += length; if (++gDataSeq == 0) { gDataSeq = 1; /* reset to 1 if we wrap around, per IA 10-11 */ } } if (!eofByte) { /* as long as we haven't seen EOF, keep asking for more data */ SendRequest(gConnID, kPAPSendData); /* ask for next data block */ } else { /* this is EOF */ /* NOTE: we usually don't get here with ImageWriter or LQ drivers. * The driver is supposed to set eof in the last data packet, but * more frequently, it just sends CloseConn to indicate that it's * done with us. */ MessageWithIntValue(LOG_ERR,"\pReceived data bytes:", gDataLen); CloseSpoolFile(gDataLen); if (gDataLen != 0) { gDataLen = 0; jobsSpooled++; } /* we don't send CloseConn on EOF; workstation does, per IA:10-11 */ } break; case kPAPCloseConnReply: break; default: MessageWithNetAddr(LOG_WARN,"\pGot unknown response from", net, node, socket); break; } } void HandleResponse(void) { #if USING_PSENDREQUEST_API HandleResponse_from_PSendRequest(); #else HandleResponse_from_ATPRequest(); #endif } int SendRequest_with_ATPRequest(char connID, char funcID) { ATATPRecPtr abp = gSendReqPtr; short destSocket = (gConnSocket) ? gConnSocket : gSocket; short retryInterval, retryCount; int nBufs, dataLen; OSErr err; if (abp->abResult > noErr) { /* %%% should we allocate a new ATATPRec and free in ioCompletion? */ /* %%% or does this just mean the connection has closed abnormally? */ Message(LOG_WARN_X,"\pRequest still in flight, cannot send yet"); return tooManyReqs; } retryInterval = 5; /* default timeout for our requests */ retryCount = 1; if (funcID == kPAPSendData) { /*retryInterval = 5; /* should be 15 seconds, per IA 10-10, but use 5 for now */ retryCount = SHRT_MAX; /* keep retrying until success or connection timeout */ } dataLen = SetUpRequestData(funcID, gSReqBuf); /* set up BDS to buffer a response that comes in across multiple packets */ nBufs = BuildBDS(gRRespBuf,(Ptr)(gRRespBdsPtr),kRespBufSize); abp->atpUserData = 0L; BlockMove(&connID,(char*)&abp->atpUserData + 0, 1); BlockMove(&funcID,(char*)&abp->atpUserData + 1, 1); if (funcID == kPAPSendData) { BlockMove(&gDataSeq,(char*)&abp->atpUserData + 2, 2); } abp->abOpcode = tATPRequest; abp->abResult = 0; abp->abUserReference = 0; abp->atpAddress.aNet = gNet; abp->atpAddress.aNode = gNode; abp->atpAddress.aSocket = destSocket; abp->atpSocket = (char)gMySendSocket & 0x00FF; abp->atpReqCount = dataLen; abp->atpDataPtr = gSReqBuf; abp->atpRspBDSPtr = gRRespBdsPtr; abp->atpRspBuf = gRRespBuf; abp->atpRspSize = kRespBufSize; abp->atpNumBufs = nBufs; abp->atpXO = true; abp->atpTimeOut = retryInterval; abp->atpRetries = retryCount; err = ATPSndRequest(gSendReqHdl,true); /* send this async so we aren't blocked */ if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pATPSndRequest returned", err); } else { responsesPending++; MessageSentToNetAddr(LOG_INFO,funcID, false, gNet, gNode, (short)destSocket & 0xFF); } return err; } #if USING_PSENDREQUEST_API int SendRequest_with_PSendRequest(char connID, char funcID) { ATPPBPtr atp = gSendReqPBPtr; short destSocket = (gConnSocket) ? gConnSocket : gSocket; short retryInterval, retryCount; int dataLen = 0; int nBufs = 0; OSErr err; if (atp->ATP.ioResult > noErr) { /* we can't use this ATPPBPtr since it's still waiting for a response */ /* %%% should we allocate a new ATPParamBlock and free in ioCompletion? */ Message(LOG_WARN_X,"\pRequest still in flight, cannot send yet"); return tooManyReqs; } retryInterval = 5; /* default timeout for our requests */ retryCount = 1; if (funcID == kPAPSendData) { /*retryInterval = 5; /* should be 15 seconds, per IA 10-10, but use 5 for now */ retryCount = SHRT_MAX; /* keep retrying until success or connection timeout */ } dataLen = SetUpRequestData(funcID, gSReqBuf); /* %%% FIXME this is wrong; using request data buffer for response buffer */ nBufs = BuildBDS(gSReqBuf,(Ptr)(gSReqBdsPtr),kMaxPacketSize); atp->ATP.userData = 0L; BlockMove(&connID,(char*)&atp->ATP.userData + 0, 1); BlockMove(&funcID,(char*)&atp->ATP.userData + 1, 1); if (funcID == kPAPSendData) { BlockMove(&gDataSeq,(char*)&atp->ATP.userData + 2, 2); } atp->ATP.ioCompletion = nil; /* we will poll for completion */ atp->ATP.atpSocket = (char)gMySendSocket & 0x00FF; atp->ATP.atpFlags = atpXOvalue; atp->ATP.addrBlock.aNet = gNet; atp->ATP.addrBlock.aNode = gNode; atp->ATP.addrBlock.aSocket = (char)destSocket & 0x00FF; atp->ATP.reqLength = dataLen; /* length of request buffer */ atp->ATP.reqPointer = gSReqBuf; /* pointer to buffer holding request data */ atp->ATP.bdsPointer = (Ptr)gSReqBdsPtr; /* buffer to hold response BDS */ atp->OTH1.u0.numOfBuffs = kMaxResponses; /* number of response buffers expected */ atp->SREQ.timeOutVal = retryInterval; atp->SREQ.retryCount = retryCount; atp->SREQ.TRelTime = 0; /* minimum 30 second timeout for other end of connection */ err = PNSendRequest(atp,true); /* send this async so we aren't blocked */ if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pPNSendRequest returned", err); } else { responsesPending++; MessageSentToNetAddr(LOG_INFO,funcID, false, gNet, gNode, (short)destSocket & 0xFF); } return err; } #endif int SendRequest(char connID, char funcID) { #if USING_PSENDREQUEST_API return SendRequest_with_PSendRequest(connID, funcID); #else return SendRequest_with_ATPRequest(connID, funcID); #endif } int SendResponse(char connID, char funcID) { OSErr err; int dataLen = 0; int nBufs = 0; int NumOfBufs = 0; #if 0 long int thisBit; /* Walk through the request bitmap and see how many response buffers you need. */ for (thisBit = 0; thisBit < 8; thisBit++) { /* Each bit that is set corresponds to a buffer. */ if (BitTst(&gReqPBPtr->OTH1.u0.bitMap, thisBit) == true) { /* Your routine to fill in the appropriate response data. */ dataLen += SetUpResponseData(funcID, gSRespBuf); NumOfBufs = NumOfBufs + 1; } } #else /* our responses always fit into a single packet, for now */ dataLen += SetUpResponseData(funcID, gSRespBuf); NumOfBufs = 1; #endif /* Put your response data into the BDS structure. */ nBufs = BuildBDS(gSRespBuf,(Ptr)(gSRespBdsPtr),dataLen); gSendRespPBPtr->ATP.atpSocket = gReqPBPtr->ATP.atpSocket; gSendRespPBPtr->ATP.atpFlags = atpEOMvalue; /* indicate end of message */ /* Send response to the machine that sent you the request. */ gSendRespPBPtr->ATP.addrBlock.aNet = gReqPBPtr->ATP.addrBlock.aNet; gSendRespPBPtr->ATP.addrBlock.aNode = gReqPBPtr->ATP.addrBlock.aNode; gSendRespPBPtr->ATP.addrBlock.aSocket = gReqPBPtr->ATP.addrBlock.aSocket; gSendRespPBPtr->ATP.bdsPointer = (Ptr)(gSRespBdsPtr); gSendRespPBPtr->OTH1.u0.numOfBuffs = NumOfBufs; /* send all of the responses back now */ gSendRespPBPtr->OTH2.bdsSize = nBufs; /* indicate how many responses you are sending */ gSendRespPBPtr->OTH2.transID = gReqPBPtr->OTH2.transID; /* use transID returned from the PGetRequest */ /* Set up ATP user data in response */ gSendRespPBPtr->ATP.userData = 0L; BlockMove(&connID,(char*)&gSendRespPBPtr->ATP.userData + 0, 1); BlockMove(&funcID,(char*)&gSendRespPBPtr->ATP.userData + 1, 1); if (funcID == kPAPData) { char eofMarker = (nBufs > 1) ? 0 : 1; /* %%% how to handle last buf? */ BlockMove(&eofMarker,(char*)&gSendRespPBPtr->ATP.userData + 2, 1); } /* err = PSendResponse(gSendRespPBPtr,false);*/ /* %%% PROBLEM %%% /* PSendResponse is overwriting the 4-byte userData portion of the ATP header. */ /* The ATPPBPtr does not give us a way to set the response user bytes! */ /* Is this a misunderstanding and they need to go into a different field? */ /* Instead, set up the old ATATPRec response struct and use ATPResponse. */ { ATATPRecPtr abp = (ATATPRecPtr)gSendRespPtr; if (abp->abResult > noErr) { /* we can't use this ATATPRec, since a response is still being sent */ /* %%% should we allocate a new ATATPRec and free in ioCompletion? */ /* %%% or does this just mean the connection has closed abnormally? */ Message(LOG_WARN_X,"\pResponse still being sent, cannot send yet"); return tooManyReqs; } abp->abOpcode = tATPResponse; abp->abResult = 0; abp->atpSocket = gReqPBPtr->ATP.atpSocket; abp->atpEOM = true; abp->atpAddress.aNet = gReqPBPtr->ATP.addrBlock.aNet; abp->atpAddress.aNode = gReqPBPtr->ATP.addrBlock.aNode; abp->atpAddress.aSocket = gReqPBPtr->ATP.addrBlock.aSocket; abp->atpRspUData = gSendRespPBPtr->ATP.userData; abp->atpRspBuf = gSRespBuf; abp->atpRspSize = dataLen; abp->atpTransID = gReqPBPtr->OTH2.transID; /* if this is a Data response from us, it needs to go to the right TID */ if (funcID == kPAPData && gQueryReqTID) { abp->atpTransID = gQueryReqTID; } err = ATPResponse(gSendRespHdl,true); /* send async so we aren't blocked */ } if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pATPResponse error", err); } else { short net = gSendRespPBPtr->ATP.addrBlock.aNet; char node = gSendRespPBPtr->ATP.addrBlock.aNode; char socket = gSendRespPBPtr->ATP.addrBlock.aSocket; MessageSentToNetAddr(LOG_INFO,funcID, true, net, node, socket); } return err; } int SetUpRequestData(char funcID, Ptr reqBuf) { #pragma unused (reqBuf) int dataLen = 0; if (funcID == kPAPSendData || funcID == kPAPTickle || funcID == kPAPCloseConn || funcID == kPAPCloseConnReply || funcID == kPAPSendStatus) { return dataLen; /* these requests have no additional data to send */ } /* %%% the only other request type that needs data is kPAPOpenConn, * but we don't make those requests yet. */ return dataLen; } int SetUpResponseData(char funcID, Ptr respBuf) { int index, dataLen = 0; unsigned char status = 0; respBuf[0] = 0; respBuf[1] = 0; respBuf[2] = 0; respBuf[3] = 0; if (gColorRibbon) { status |= COLOR_RIBBON_INSTALLED; } if (gSheetFeeder) { status |= SHEET_FEEDER_INSTALLED; } if (funcID == kPAPOpenConnReply) { respBuf[0] = (char)gMyRecvSocket & 0x00FF; /* our responding socket */ respBuf[1] = gFlowQuantum; /* flow quantum (passed to us in OpenConn) */ /* respBuf[2] = 0; /* result */ /* respBuf[3] = 0; /* result */ if (gConnID) { /* error result: not opening this connection request since we're busy */ *((short*)&respBuf[2]) = aspServerBusy; } } if (funcID == kPAPOpenConnReply || funcID == kPAPStatus) { /* status string contents vary depending on printer type */ if (gPrinterType == kPrinterTypeLaserWriter) { /* The format of LaserWriter status strings is documented in * the first edition of the PostScript Language Reference Manual * (the red book), Appendix D, p.283. It can include up to 3 * key:value pairs, is bracketed by %%[ and ]%%, with fields * separated by semicolons: job (omitted if none), status, and * source (omitted if idle). Inside AppleTalk 10-12 confirms * that this is a Pascal format string with a length byte. * * Possible values for the status key: * 'idle' (no job in progress) * 'busy' (executing PostScript) * 'waiting' (for I/O mid-job) * 'printing' (paper in motion) * 'PrinterError: ' (no paper, jam, etc.) * 'initializing' (printer is starting up) * 'printing test page' (during startup) * * Possible values for the source key: * 'serial 9' (the 9-pin RS-422 serial port) * 'serial 25' (the 25-pin RS-232 serial port) * 'AppleTalk' (the network) * * Example status strings: * %%[ status: idle ]%% * %%[ job: Fred's Memo; status: busy; source: AppleTalk ]%% */ index = 4; BlockMove("\p%%[ status: ",&respBuf[index],13); index += 13; if (!gFinishedQuery || !gDataLen) { BlockMove("waiting",&respBuf[index],7); index += 7; } else { BlockMove((gConnID) ? "busy":"idle",&respBuf[index],4); index += 4; } BlockMove(" ]%%",&respBuf[index],4); index += 4; respBuf[4] = index-5; } else { /* ImageWriter or LQ */ /* documented in AppleTalk Technical Note #9 "The PAP Status Buffer" */ respBuf[4] = 2; /* length of status "string" (just 2 bytes of flags) */ respBuf[5] = (char)status; respBuf[6] = (gConnID) ? 0x80 : 0x00; /* high bit set if printer busy */ } dataLen = 5 + respBuf[4]; } if (funcID == kPAPData) { if (gPrinterType == kPrinterTypeLaserWriter) { /* build response to current PostScript query */ HLock(gQueryHdl); dataLen = BuildResponseToPSQuery((char*)*gQueryHdl, gQueryLength, (char*)respBuf, kMaxPacketSize); HUnlock(gQueryHdl); /* we have consumed the current query and can release it now */ DisposeHandle(gQueryHdl); gQueryHdl = nil; gQueryLength = 0; MessageWithIntValue(LOG_INFO,"\pSending data with length",dataLen); MessageWithDataPointer(LOG_DEBUG,"\p-- data:",respBuf,dataLen); } else if (gPrinterType == kPrinterTypeLQ) { /* possible options: * '1' = 15-inch carriage (always the case for the LQ) * 'C' = color ribbon installed * 'F' = sheet feeder installed * 'E' = envelope feeder installed * F and E are mutually exclusive, and are followed * by a numeral indicating the number of feeder bins. * 'P' = tractor drive in pull tractor position * examples: * 'LQ1\r' 'LQ1P\r' 'LQ1CF1\r' 'LQ1CE3\r' */ index = 0; dataLen = 4; /* min length of self ID string bytes */ respBuf[index++] = 'L'; /* first two bytes are 'LQ' */ respBuf[index++] = 'Q'; respBuf[index++] = '1'; /* 15-inch carriage (only option) */ if (gColorRibbon) { respBuf[index++] = 'C'; /* color ribbon installed */ dataLen += 1; } if (gSheetFeeder) { respBuf[index++] = 'F'; /* sheet feeder installed */ respBuf[index++] = '1'; /* number of feeder bins */ dataLen += 2; } respBuf[index++] = '\r'; /* carriage return (^M, aka 0x0D) */ } else if (gPrinterType == kPrinterTypeImageWriter) { index = 0; dataLen = 4; /* min length of self ID string bytes */ respBuf[index++] = 'I'; /* first two bytes are 'IW' */ respBuf[index++] = 'W'; respBuf[index++] = '1'; /* 10-inch carriage, first digit */ respBuf[index++] = '0'; /* 10-inch carriage, second digit */ if (gColorRibbon) { respBuf[index++] = 'C'; /* color ribbon installed */ dataLen += 1; } if (gSheetFeeder) { respBuf[index++] = 'F'; /* sheet feeder installed */ dataLen += 1; } } } return dataLen; } void SendTickle(void) { OSErr err; ATPPBPtr atp = gTickleReqPBPtr; char connID = gConnID; char funcID = kPAPTickle; short destSocket = (gConnSocket) ? gConnSocket : gSocket; BlockMove(&connID,(char*)&atp->ATP.userData + 0, 1); BlockMove(&funcID,(char*)&atp->ATP.userData + 1, 1); atp->ATP.ioCompletion = nil; atp->ATP.atpSocket = (char)gMySendSocket & 0x00FF; atp->ATP.atpFlags = 0; /* must be sent in ALO mode, not XO mode (IA:10-9) */ atp->ATP.addrBlock.aNet = gNet; atp->ATP.addrBlock.aNode = gNode; atp->ATP.addrBlock.aSocket = (char)destSocket & 0x00FF; atp->ATP.reqLength = 0; /* length of request buffer */ atp->ATP.reqPointer = nil; /* pointer to buffer holding request data */ atp->ATP.bdsPointer = (Ptr)nil; /* buffer to hold response BDS */ atp->OTH1.u0.numOfBuffs = 0; /* number of response buffers expected */ atp->SREQ.timeOutVal = 1; /* timeout in seconds before retrying */ atp->SREQ.retryCount = 0; /* how many times to retry the request */ atp->SREQ.TRelTime = 0; /* minimum 30 second timeout for other end of connection */ err = PNSendRequest(atp,true); /* send this async so we aren't blocked */ if (err != noErr) { MessageWithIntValue(LOG_WARN_X,"\pCould not start tickle:", err); } else { MessageSentToNetAddr(LOG_INFO,funcID, false, gNet, gNode, (short)destSocket & 0xFF); } } void StartTickling(void) { gLastTickleSent = 1L; gLastTickleRecd = TickCount(); } void StopTickling(void) { OSErr err = PKillSendReq(gTickleReqPBPtr,false); if (err != noErr && err != cbNotFound) { MessageWithIntValue(LOG_WARN_X,"\pCould not stop tickle:", err); } else { MessageWithIntValue(LOG_WARN,"\pStopped tickle for connection",(int)gConnID & 0xFF); } gLastTickleSent = 0L; } void CloseConnection(void) { OSErr err; StopTickling(); CancelRequest(); /* cancel outstanding request (if any) so we don't wait */ err = SendRequest(gConnID, kPAPCloseConn); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCloseConn request error", err); } else { MessageWithIntValue(LOG_ERR,"\pClosing connection",(int)gConnID & 0xFF); } gConnID = 0; /* connection is now closed */ gDataLen = 0; gLastTickleRecd = 0L; respondedToFirstPostConnectionStatusReq = false; } void CheckATP(void) { OSErr err; if (!listening || !gReqPBPtr || !gSendReqPBPtr || !gSendReqPtr) { return; /* no server started, or no buffers allocated */ } if (gConnID != 0) { long curTicks = TickCount(); if (gLastTickleRecd && (gLastTickleRecd + kIdleTimeoutTicks < curTicks)) { /* time to close this connection due to inactivity */ MessageWithIntValue(LOG_ERR,"\pReceived data bytes:", gDataLen); CloseSpoolFile(gDataLen); CloseConnection(); } else if (gLastTickleSent && (gLastTickleSent + kTickleTicks < curTicks)) { /* time to send a tickle */ SendTickle(); gLastTickleSent = curTicks; } } #if USING_PSENDREQUEST_API if (gSendReqPBPtr->ATP.ioResult <= noErr) #else if (gSendReqPtr->abResult <= noErr) #endif { /* either a sent request completed, or we haven't made any request yet */ if (responsesPending > 0) { /* got an incoming response */ responsesPending--; #if USING_PSENDREQUEST_API err = gSendReqPBPtr->ATP.ioResult; #else err = gSendReqPtr->abResult; #endif if (err != noErr) { /* don't report failure if connection is already closed */ if (!(gConnID == 0 && err == reqFailed)) { MessageWithIntValue(LOG_ERR_X,"\pSendRequest returned", err); } if (gConnID) { CloseConnection(); } } else { /* handle this response to our request */ HandleResponse(); } } } if (gReqPBPtr->ATP.ioResult <= noErr) { /* either an incoming request completed, or we haven't asked for one yet */ if (requestsPending > 0) { /* got an incoming request */ requestsPending--; err = gReqPBPtr->ATP.ioResult; if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pGetRequest returned", err); } else { /* handle the request and send response */ HandleRequest(); } } /* start a new PGetRequest */ gReqPBPtr->ATP.ioCompletion = nil; /* we will poll for completion */ gReqPBPtr->ATP.atpSocket = gMyRecvSocket & 0x00FF; /* use our socket */ gReqPBPtr->ATP.reqLength = kMaxPacketSize; /* on input, max length of request buffer */ gReqPBPtr->ATP.reqPointer = gRReqBuf; /* pointer to buffer for incoming request data */ err = PGetRequest(gReqPBPtr,true); /* issue asynchronous PGetRequest */ if (err == noErr) { requestsPending++; } else { MessageWithIntValue(LOG_ERR_X,"\pPGetRequest error", err); } } }