Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Networking /
Chapter 7 - Datagram Delivery Protocol (DDP) / Using DDP


A Sample Socket Listener

There are many ways to implement a socket listener that follow the requirements described previously for using and preserving registers and reading packets. This section uses a sample socket listener that shows one way to implement the process within a DDP socket-client application that reads in the packet contents. The sample code also shows those segments of the sample client application that set up the socket listener and check to determine when a packet that the socket listener has read is available for processing by the client application.

Some of the tasks that your socket listener can do that this sample socket listener does not illustrate are how to

The sample socket listener does, however, show you how to

Socket Listener Queues and Buffers

The sample socket listener uses two standard operating-system queues to manage the contents of the packets that it receives and makes available to the socket-client application. It calls these linked lists a free queue and a used queue. The use of two queues allows the socket listener to receive and process packets while the client application is reading the data from those packets that the socket listener has already processed.

The free queue is used to manage available buffers that consist of data structures declared as PacketBuffer records. The sample socket listener uses the buffers in the free queue one at a time to hold the contents of an incoming packet as it processes
the packet header and data fields. The socket listener's initialization module, SL_InitSktListener, shown in Listing 7-5 on page 7-24, releases the first element
or buffer of the free queue and points to it from the current queue element (current_qelem) variable; it is this buffer that the socket listener uses when the .MPP driver calls the socket listener with a packet for it to process.

After the socket listener fills in the fields of the record pointed to by current_qelem with the processed contents of the packet, it moves the buffer into the used queue, pointed to by used_queue, for the client application to read. Then the socket listener releases the next record buffer from the free queue and points to it using the current_qelem variable. The sample code in Listing 7-7 on page 7-31 shows that when the client application has finished reading the contents of a used queue buffer element, it returns the buffer to the free queue pointed to by free_queue to make the buffer available again to the socket listener.

The socket listener uses the variables declared in Listing 7-1 to point to

Listing 7-1 Declarations for pointers to the sample socket listener's queues and packet buffer

SL_Locals    PROC
         ENTRY free_queue,used_queue,current_qelem
free_queue        DC.L    0      ;pointer to freeQ QHdr ;
                                 ; initialized by InitSktListener
used_queue        DC.L    0      ;pointer to usedQ QHdr ;
                                 ; initialized by InitSktListener
current_qelem     DC.L    0      ;pointer to current
                                 ; PacketBuffer record
ENDP;
Listing 7-4 on page 7-23 shows the Pascal-language client application SetUpSocketListener procedure. This procedure calls the SL_InitSktListener function to pass to the socket listener pointers to these two operating-system queues.

When the .MPP driver calls the socket listener, if there is an available buffer, the socket listener processes the packet and returns in the fields of the packet buffer record the DDP type, the destination node ID, the source address in AddrBlock format, the hop count, the size of the packet, a flag to indicate whether a checksum error occurred, and the data delivered in the packet. If you use the sample record data structure as a model, you can extend it to include fields to hold additional values, such as the tick count at the time when the .MPP driver called your socket listener. Listing 7-2 shows the assembly-
language declaration for the PacketBuffer record.

Listing 7-2 Declaration for the sample socket listener's packet buffer record

PacketBuffer          RECORD    0
qLink                 DS.L      1
qType                 DS.W      1
buffer_Type           DS.W      1      ;DDP protocol type
buffer_NodeID         DS.W      1      ;destination node
buffer_Address        DS.L      1      ;source address in AddrBlock format
buffer_Hops           DS.W      1      ;hop count
buffer_ActCount       DS.W      1      ;length of DDP datagram
buffer_CheckSum       DS.W      1      ;chksum error returned here
                                       ; (cksumErr or noErr)
buffer_Data           DS.B      ddpMaxData   
                                       ;the DDP datagram
                      ENDR
Listing 7-3 shows the socket listener's declaration for the queue header record, which is defined and used to make the code easier to read.

Listing 7-3 Declaration for the sample socket listener's queue header record

QHdr     RECORD    0
qFlags   DS.W      1
qHead    DS.L      1
qTail    DS.L      1
         ENDR

Setting Up the Socket Listener

The client application that includes the sample socket listener uses a Pascal procedure, SetUpSocketListener, to set up the socket listener's initialization routine.
The SetUpSocketListener procedure defines

If you base your own code on the sample code, you can add new fields to the record declaration, if you need them. If you do this, you must modify the packet buffer data structure defined in the socket listener code to match the high-level language record declaration.

Listing 7-4 shows the client-application's Pascal code that initializes the packet buffer records and then adds them to the free queue using the _Enqueue trap. The code calls the SL_InitSktListener routine and passes to it pointers to the queue header for the free queue and the queue header for the used queue.

Listing 7-4 Setting up the socket listener from the client application

CONST
   ddpMaxData = 586;
TYPE
   PacketBuffer = RECORD
      qLink: QElemPtr;
      qType: Integer;
      buffer_Type: Integer;
      buffer_NodeID: Integer;
      buffer_Address: AddrBlock;
      buffer_Hops: Integer;
      buffer_ActCount: Integer;
      buffer_CheckSum: OSErr;
      buffer_Data: ARRAY[1..ddpMaxData] OF SignedByte;
END;

VAR
   freeQ, usedQ: QHdr;
   Buffers: ARRAY[1..10] OF PacketBuffer;

PROCEDURE SL_TheListener;
External;

FUNCTION SL_InitSktListener (freeQ, usedQ: QHdrPtr): OSErr;
External;

PROCEDURE SetUpSocketListener;
   VAR
      err: OSErr;
      i: Integer;
   BEGIN
      freeQ.QHead := NIL;     {initialize to nil to indicate empty queue}
      freeQ.QTail := NIL;     {initialize to nil to indicate end of queue}
      usedQ.QHead := NIL;     {initialize to nil to indicate empty queue}
      usedQ.QTail := NIL;     {initialize to nil to indicate end of queue}
      FOR i := 1 TO 10 DO     {add all buffers to the free queue}
          Enqueue(@Buffers[i], @freeQ);

      err := SL_InitSktListener(@freeQ, @usedQ); 
                              {initialize the socket listener code}


      IF err <> noErr THEN                                  
            BEGIN
               {Perform error processing here}
            END;
      {You can now call POpenSkt because the socket listener is ready to }
      { process packets.} 
   END;

Initializing the Socket Listener

The sample socket-client application procedure SetUpSocketListener (shown in the preceding listing) calls the socket listener SL_InitSktListener initialization routine provided in Listing 7-5 to pass it pointers to the two operating-system queues (used and free) that the socket listener uses after the SetUpSocketListener procedure initializes these queues.

The SL_InitSktListener routine sets up its local variables used_queue and free_queue to point to the queue headers for the two queues. Then the routine releases from the free queue the first buffer and sets the current_qelem variable to point to it. This is the buffer that the socket listener uses when it next reads a packet.

Listing 7-5 Initializing the socket listener

;Function SL_InitSktListener(freeQ, usedQ: QHdrPtr): OSErr;
;
SL_InitSktListener PROC EXPORT

StackFrame     RECORD    {A6Link},DECR ;build a stack frame record
Result1        DS.W      1             ;function's result returned to caller
ParamBegin     EQU       *             ;start parameters after this point
freeQ          DS.L      1             ;freeQ parameter
usedQ          DS.L      1             ;usedQ parameter
ParamSize      EQU       ParamBegin-*  ;size of all the passed parameters
RetAddr        DS.L      1             ;placeholder for return address
A6Link         DS.L      1             ;placeholder for A6 link
LocalSize      EQU       *             ;size of all the local variables
               ENDR

   WITH       StackFrame,QHdr;         ;use these record types
   LINK       A6,#LocalSize            ;allocate your local stack frame

;Copy the queue header pointers into our local storage for use in the 
; listener


   LEA        used_queue,A0            ;copy usedQ into used_queue
   MOVE.L     usedQ(A6),(A0)

   LEA        free_queue,A0            ;copy freeQ into free_queue
   MOVE.L     freeQ(A6),(A0)

;Release the first buffer record from freeQ and set current_qelem to it

   MOVEA.L    freeQ(A6),A1             ; A1 = ^freeQ
   LEA        current_qelem,A0         ;copy freeQ.qHead into current_qelem
   MOVE.L     qHead(A1),(A0)
   MOVEA.L    qHead(A1),A0             ;A0 = freeQ.qHead
   _Dequeue
   MOVE.W     D0,Result1(A6)           ;return status

@1 UNLK       A6                       ;destroy the link
   MOVEA.L    (SP)+,A0                 ;pull off the return address
   ADDA.L     #ParamSize,SP            ;strip all of the caller's parameters
   JMP        (A0)                     ;return to the caller
   ENDP
   END

Processing a Packet

When the .MPP driver calls the sample socket listener, the socket listener's main module, the SL_TheListener procedure, reads and processes a packet addressed to the socket-
client application. However, the socket listener can only process a packet if there is a packet buffer record available to hold the processed packet.

The code shown in Listing 7-6 determines if the current_qelem variable is NIL or not. If it is not NIL, the code gets a buffer, if one is available.

If the socket listener reads the packet successfully, it processes the header information that the hardware driver has stored in the .MPP driver's local variable space pointed to by the value in register A2. To do this, the socket listener

The socket listener then returns control to the calling program and waits until the .MPP driver calls it again when the .MPP driver next receives a packet addressed to a socket that is associated with the socket listener. Listing 7-6 shows the SL_TheListener procedure.

Listing 7-6 Receiving and processing a DDP packet

;SL_TheListener
;Input:
;     D0 (byte) = packet's destination socket number
;     D1 (word) = number of bytes left to read in packet
;     A0 points to the bytes to checksum
;     A1 points to the bytes to checksum
;     A2 points to MPP's local variables
;     A3 points to next free byte in read-header area
;     A4 points to ReadPacket and ReadRest jump table
;
;Return:
;     D0 is modified
;     D3 (word) = accumulated checksum

SL_TheListener  PROC    EXPORT
   WITH    PacketBuffer

;Get pointer to current PacketBuffer.
GetBuffer:
   LEA       current_qelem,A3    ;get the pointer to PacketBuffer
   MOVE.L    (A3),A3
   MOVE.L    A3,D0               ;if no PacketBuffer
   BEQ.S     NoBuffer            ; then ignore packet

;Read rest of packet into PacketBuffer.datagramData.
   MOVE.L    D1,D3               ;read rest of packet
   LEA       buffer_data(A3),A3  ;A3 = ^bufferData
   JSR       2(A4)               ;call ReadRest
   BEQ.S     ProcessPacket       ;if no error, continue
   BRA       RcvRTS              ;if error, ignore the packet
;No buffer; ignore the packet.
NoBuffer      CLR D3             ;set to ignore packet (buffer size = 0)
   JSR       2(A4)               ;call ReadRest
   BRA       GetNextBuffer       ;no buffer available, so read next packet;
                                 ; maybe there will be a buffer
                                 ; for the next packet

;Process the packet you just read in.
; ReadRest has been called so registers A0-A3 and D0-D3 are free
; to use. Use registers this way:
PktBuff         EQU    A0     ;current PacketBuffer
MPPLocals       EQU    A2     ;pointer to MPP's local variables
                              ; (still set up from entry to
                              ; socket listener)
HopCount        EQU    D0     ;gets the hop count
DatagramLength  EQU    D1     ;determines the datagram length
SourceNetAddr   EQU    D2     ;builds the source network address
ProcessPacket:
   LEA        current_qelem,PktBuff
                              ;PktBuff =  current_qelem
   MOVE.L     (PktBuff),PktBuff

;Do everything that's common to both long and short DDP headers
; first, clear buffer_Type and buffer_NodeID to ensure their high
; bytes are 0.
   CLR.W      buffer_Type(PktBuff) 
                                 ;clear buffer_Type
   CLR.W      buffer_NodeID(PktBuff)
                                 ;clear buffer_NodeID 

;Clear SourceNetAddr to prepare to build network address.
   MOVEQ      #0,SourceNetAddr   ;build the network address in
                                 ; SourceNetAddr
;Get the hop count
   MOVE.W     toRHA+lapHdSz+ddpLength(MPPLocals),HopCount 
                                 ;get hop/length field
   ANDI.W     #DDPHopsMask,HopCount
                                 ;mask off the hop count bits
   LSR.W      #2,HopCount        ;shift hop count into low bits
                                 ; of high byte
   LSR.W      #8,HopCount        ;shift hop count into low byte
   MOVE.W     HopCount,buffer_Hops(PktBuff)  
                                 ; and move it into the
                                 ; PacketBuffer
 
;Get the packet length (including the DDP header).
   MOVE.W     toRHA+lapHdSz+ddpLength(MPPLocals),DatagramLength 
                                 ;get length field
   ANDI.W     #ddpLenMask,DatagramLength     
                                 ;mask off the hop count bits

;Now, find out if the DDP header is long or short.
   MOVE.B     toRHA+lapType(MPPLocals),D3    
                                 ;get LAP type
   CMPI.B     #shortDDP,D3       ;is this a long or short DDP
                                 ; header?
   BEQ.S      IsShortHdr         ;skip if short DDP header

;It's a long DDP header.
   MOVE.B toRHA+lapHdSz+ddpType(MPPLocals),buffer_Type+1(PktBuff) 
                                 ;get DDP type
   MOVE.B
      toRHA+lapHdSz+ddpDstNode(MPPLocals),buffer_NodeID+1(PktBuff)
                                 ;get destination node from frame header 
   MOVE.L     toRHA+lapHdSz+ddpSrcNet(MPPLocals),SourceNetAddr
                                 ;source network in high word,
                                 ; source node in low byte
   LSL.W      #8,SourceNetAddr   ;shift source node up to high byte
                                 ; of low word; get source socket
                                 ; from DDP header
   MOVE.B     toRHA+lapHdSz+ddpSrcSkt(MPPLocals),SourceNetAddr
   SUB.W      #ddpType+1,DatagramLength 
                                 ;DatagramLength = number of
                                 ; bytes in datagram
   BRA.S      MoveToBuffer

;Determine if there is a checksum. 
   TST.W     toRHA+lapHdSz+ddpChecksum(MPPLocals) 
                                 ;does packet have checksum?
   BEQ.S     noChecksum

;Calculate checksum for the DDP header.
   MOVE.L    DatagramLength,-(SP);save DatagramLength (D1)
   CLR       D3                  ;set checksum to 0
   MOVEQ     #ddphSzLong-ddpDstNet,D1   
                                 ;D1 = length of header part to
                                 ; checksum pointer to destination
                                 ; network number in DDP header
   LEA        toRHA+lapHdSz+ddpDstNet(MPPLocals),A1
   JSR        SL_DoChksum         ;checksum of DDP header part
                                  ; (D3 holds accumulated
                                  ; checksum)

;Calculate checksum for the data portion of the packet (if any).
   MOVE.L     buffer_Data(PktBuff),A1
                                 ;pointer to datagram
   MOVE.L     (SP)+,DatagramLength
                                 ;restore DatagramLength (D1)
   MOVE.L     DatagramLength,-(SP)
                                 ;save DatagramLength (D1)
                                 ; before calling SL_DoChksum
   BEQ.S      TestChecksum       ;don't checksum datagram if
                                 ; its length = 0
   JSR        SL_DoChksum        ;checksum of DDP datagram part
                                 ; (D3 holds accumulated checksum)

TestChecksum:
   MOVE.L     (SP)+,DatagramLength
                                 ;restore DatagramLength (D1)

;Now make sure the checksum is OK.
   TST.W      D3                 ;is the calculated value 0?
   BNE.S      NotZero            ;if nonzero, go and use it
   SUBQ.W     #1,D3              ;if 0, make it -1

NotZero:
   CMP.W      toRHA+lapHdSz+ddpChecksum(MPPLocals),D3
   BNE.S      ChecksumErr        ;bad checksum
   MOVE.W     #0,buffer_CheckSum(A0)         
                                 ;no errors
   BRA.S      noChecksum

ChecksumErr:
   MOVE.W     #ckSumErr,buffer_CheckSum(PktBuff) 
                                 ;checksum error

noChecksum:
   BRA.S      MoveToBuffer



;It's a short DDP header.
IsShortHdr:
   MOVE.B     toRHA+lapHdSz+sddpType(MPPLocals),buffer_Type+1(PktBuff) 
                                 ;get DDP type
   MOVE.B     toRHA+lapDstAdr(MPPLocals),buffer_NodeID+1(PktBuff)
                                 ;get destination node from LAP header
   MOVE.B     toRHA+lapSrcAdr(MPPLocals),SourceNetAddr 
                                 ;get source node from LAP header
   LSL.W      #8,SourceNetAddr   ;shift src node up to high byte of low word
   MOVE.B     toRHA+lapHdSz+sddpSrcSkt(MPPLocals),SourceNetAddr
                                 ;get source socket from short DDP header
   SUB.W      #sddpType+1,DatagramLength   
                                 ;DatagramLength = number of bytes in
                                 ; datagram
MoveToBuffer:
   MOVE.L     SourceNetAddr,buffer_Address(PktBuff)
                                 ;move source network address into
                                 ; PacketBufffer
   MOVE.W     DatagramLength,buffer_ActCount(PktBuff)
                                 ;move datagram length into PacketBuffer

;Write the packet into the used queue and 
; get another buffer from the free queue for the next packet.
   LEA        used_queue,A1      ;A1 = ^used_queue
   MOVE.L     (A1),A1            ;A1 = used_queue (pointer to usedQ)
   _Enqueue                      ;put the PacketBuffer in the used queue

GetNextBuffer:
   LEA        free_queue,A1      ;A1 = ^free_queue
   MOVE.L     (A1),A1            ;A1 = free_queue (pointer to freeQ)
   LEA        current_qelem,A0   ;copy freeQ.qHead into current_qelem
   MOVE.L     qHead(A1),(A0)
   MOVEA.L    qHead(A1),A0       ;A0 = freeQ.qHead
   _Dequeue

RcvRTS:
   RTS                           ;return to caller
   ENDP

Testing for Available Packets

Your client application must include a routine that determines if the socket listener has processed a packet for a socket associated with your client application. If it has, your client application routine must itself read and process the packet's contents, which are made available by the socket listener.

If your client application includes several processes each with its own socket that use the same socket listener, your client application routine must include a mechanism to scan for packets addressed to specific sockets.

If you expect to receive multiple packets for a specific socket, you should anticipate the possibility that the client application might handle the first packet for a socket before
the socket listener processes the second packet for that socket. For example, to prepare for reception of multiple related packets addressed to the same socket, the sample client application's routine could check the socket listener's used queue QHead field for addi-
tional packets periodically after it read the first packet.

If you design your socket listener based on the sample one, your client's application should define a sufficient number of packet buffers so that as the client application releases a buffer from the used queue, processes its contents, and then moves that buffer back into the free queue for the socket listener to use, there are always buffers available in the free queue.

Listing 7-7 shows the code that the sample client application uses for this purpose. It periodically checks the QHead element of the socket listener's used queue. When QHead is not NIL, the client application knows that a packet is available for processing.

Listing 7-7 Determining if the socket listener has processed a packet

TYPE
   PacketBuffer = RECORD
      qLink: QElemPtr;
      qType: Integer;
      buffer_Type: Integer;
      buffer_NodeID: Integer;
      buffer_Address: AddrBlock;
      buffer_Hops: Integer;
      buffer_ActCount: Integer;
      buffer_CheckSum: OSErr;
      buffer_Data: ARRAY[1..ddpMaxData] OF SignedByte;
END;
PacketPtr = ^PacketBuffer;

VAR
   freeQ, usedQ: QHdr;
   bufPtr : PacketPtr;
         .
         .
         .
   WHILE (usedQ.QHead <> nil) DO 
      BEGIN
         bufPtr := PacketPtr(usedQ.QHead); {get the packet ptr}
         IF (Dequeue(QElemPtr(bufPtr), @usedQ) <> noErr) THEN
            BEGIN
                                          {process the packet information}
            Enqueue(QElemPtr(bufPtr), @freeQ); 
                                          {requeue the packet buffer for use}
      END
         ELSE
            BEGIN
               {Error occurred dequeueing packet - perform error }
               { processing here. However, because this is the only }
               { place in the code where buffers are dequeued, your error }
               { code should never be called. You can include a debugging }
               { statement here.}
   END;
END;

Previous Book Contents Book Index Next

© Apple Computer, Inc.
7 JUL 1996