Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

IPX: the Great Communicator


MAY92: IPX: THE GREAT COMMUNICATOR This article contains the following executables: IPX.ARC

Rahner is an independent consultant living near Sacramento, Calif. He can be reached by phone at 916-722-1939 or through CompuServe at 71450,757.


When I first began using networks, the most obvious means of communication between nodes was to open a file and have all the nodes access that file. Records would be written to that open file, flushed, and read by another node. With only a couple of nodes on a network communicating sparingly in this fashion, the method appeared to work.

With a moderate load, however, the network seemed to lose pep. Sometimes the nodes would fail to flush their buffers properly and data would never pass into the open file. Often the entire system would bog down in the message pool, inhibiting normal data-access functions.

I knew there must be a better way, and happily, Novell provided an apparent answer to my need for speed--Internetwork Packet Exchange, or lPX. IPX is an example of the third layer of what the International Standards Organization (ISO) proposed standard calls the "Open System Interconnection" (OSI) model, as it relates to Novell's Netware. IPX lets programmers perform high-speed, peer-to-peer communication on Novell's Netware. IPX is the lowest level of communication that can be performed on a network without resorting to direct access of the hardware. Novell refers to the functions that enable this type of communication as "IPX/SPX Communication Services." (SPX stands for Sequenced Packet Exchange.) Here, I'll refer to IPX/SPX functions as "XPX."

In this article, I'll discuss IPX/SPX and present a library of IPX functions--implemented as a stream--that significantly improves IPX throughput without additional server time. I've also written a working program called TEST1.C that uses the major XPX functions. TEST1.C is run on multiple network nodes and continually transfers packets to any other nodes from which it receives broadcasts. The library and test sample program are available electronically; see page 3 in this issue.

An XPX Backgrounder

Normal Novell operating-system accesses are through the MS-DOS INT 21h window. XPX, however, uses a slightly different mechanism. Before any function calls can be made, the application must call the MS-DOS multiplex and get the vector to the XPX entry point. The assembly code segment for this is shown in Example 1. All XPX routines are accessed by making far calls using the IPX_Vector.XPX uses register BX to hold an XPX command number. An XPX call is not kind to unused registers and, in general, sensitive registers should be saved (especially BP). By using a far call (rather than MS-DOS's INT 21h), most IPX functions can be called from a background process without the programmer worrying about trashing the system. All XPX functions return the status of their result in AL.

Example 1: Assembly code calls MS-DOS multiplex and gets vector to the XPX entry point.

    mov ax, 7a00h            ; Function 7Ah, AL = 0
    int 2fh                  ; MS-DOS Multiplex interrupt
                             ; Returns with AL == 0FFh if xPX
                             ;  exists and ES:DI == xPX vector
    inc al                   ; Set ZERO if AL == -1
    jnz outta_here           ; Quit if xPX isn't around
    mov IPX_Vector, di       ; Save the xPX entry factor
    mov IPX_Vector+2, es

In my mind, the XPX functions fall into two major categories: initialization/information and communication. The initialization/information functions start up the XPX internals, open communication pathways, and give the application information about how things have been setup. The communication functions are responsible for sending/receiving packets of information to/from peers on the network.

All network interfaces (Ethernet, Arc-net, and so on) require a unique 6-byte node ID to differentiate between them. The node ID only differentiates a network interface within a single network. Therefore, two networks can have interfaces with identical node IDs. Because a single computer can have multiple network interfaces or a network interface can be placed in another computer, a node ID does not necessarily specify a single, distinct computer system.

Every Novell network has a 4-byte ID number that uniquely differentiates it from any other Novell networks to which it is connected. If no other networks are connected, any number is unique.

Every node connected to a network can open several different channels of communication, called "sockets." Sockets allow applications to differentiate types of communication performed by a node. One socket can be used to broadcast one message to all nodes while another socket can be used to send and/or receive messages from specific nodes or groups of nodes. By default, XPX can support up to 20 sockets on a single node. Through configuration, the number of sockets that may be open can be increased to 150. A 2-byte number is used as a socket ID.

Numerically, the network, node, and socket IDs are in Motorola format. The format is actually of no consequence because the magnitude, order, and content are not relevant to the application or XPX. There are special cases of each type of ID, but all are palindromes, so byte order is still not important. The 4-byte network ID can be viewed as a 32-bit long integer with no significance associated with its magnitude. The 6-byte node ID can be viewed as a non-ASCII string, such as a filename, with no naming convention. The 2-byte socket ID can be a short integer that also has no significance associated with its magnitude.

XPX Structures

The basic control structure used with both IPX and SPX is the Event Control Block (ECB); see Table 1. The ECB is passed to XPX to describe its associated channel and buffers. The first 34 bytes of the ECB contain control and addressing information. A list of "associated fragments" immediately follows this 34-byte header.

Table 1: Event Control Block format

Region    Description

0-3       Link to next ECB, filled and used by XPX. While XPX is not
          using this ECB, the application can use this field for its own
          ECB management.

4-7       Far pointer to the Event Service Routine (ESR) associated with
          this ECB. This can be NULL, if asynchronous processing is not
          desired.

8         In Use Flag. Used by XPX to show the current state of ECB
          processing. Set to 0 when XPX is done with the ECB.

9         Completion Code. Set by XPX when the ECB In Use Flag is set to
          0. Valid only when XPX has finished with the ECB. A 0
          indicates that the ECB task was completed successfully. Any
          other value indicates an error condition.

10-11     Socket ID. Set by the application to tell XPX with which
          socket the ECB is to communicate.

12-27     Used internally by XPX.

28-33     Immediate Address. This is the local node ID with which this
          ECB is to communicate. If ECB is being sent (a talker) using
          IPX (rather than SPX), this field should be filled in by the
          application. If ECB is a listener or uses SPX, this field is
          filled by XPX.

34-35     Fragment Count. Filled in by application to tell XPX how many
          fragment descriptors are to follow. All ECBs must have at
          least one fragment descriptor to point to an XPX packet
          header. The cumulative size of the fragments associated with
          an ECB can not exceed 576 bytes.

36-39     Fragment Pointer 1. Far pointer to the first fragment
          associated with the ECB. The application must have at least
          one fragment that contains a complete IPX or SPX header. Any
          additional data buffers can be contiguous extensions of the
          XPX header or segmented into unconnected memory locations.
          Noncontiguous memory fragments require more than one fragment
          descriptor.

40-41     Fragment Size 1. The number of bytes in the first fragment. If
          the size of the first fragment is not at least the size of
          either an IPX or SPX header, XPX returns an error. Fragments
          that follow must contain at least 1 byte.

42-45     Fragment Pointer 2. This fragment descriptor and those that
          follow are optional and need not be used or declared if the
          first fragment descriptor describes the packet buffer in its
          entirety.

46-47     Fragment Size 2. Optional size of the second fragment.

...       ...

nn-nn     Fragment Pointer n

mm-mm     Fragment Size m

The Event Service Routine (ESR) is a function provided by the application and called by XPX when the ECB has been processed. The ESR is called in the following conditions:

  • ES:SI points to the ECB that was processed.
  • All registers except SS and SP have been saved on the stack.
  • Interrupts are disabled.
  • AL is 0FFh if called by IPX, or 0 if called by the Asynchronous Event Scheduler.
  • All segment registers are in unknown states.
An ESR must return with an RETF instruction and interrupts disabled and maintain the stack's integrity. It can call any XPX function except Close Socket; reschedule itself through an AES call; and enable interrupts during operation, as long as blocking is done against another event calling the same function.
Listing One shows my ESR implementation.

An application could conceivably have a separate ESR for every ECB; however, that approach would be excessive. The ESR should be fairly general purpose and should be approached with the same mind-set as an Interrupt Service Routine.

The In Use Flag field can reflect a variety of states as the ECB nears completion. These state values are defined within my header file, NETWORK.H. All definitions associated with the In Use Flag are prefixed with IU_. The application can poll In Use to check on the current status of a particular ECB or wait until XPX calls the ESR.

The Completion Code field is filled by XPX after the ECB has been processed. The contents of this field have no meaning until the In Use Flag equals 0, so the application should have no expectations of Completion Code until that time. Many of the Novell-documented completion codes are defined within NETWORK.H. All definitions associated with Completion Code are prefixed with CC_.

The first fragment descriptor must point to either a header structure for an IPX or SPX packet before the ECB can be passed to any XPX function. Both packet types start with the same structure. The format of the IPX header is shown in Table 2. The application needs to fill the IPX structure only if the packet is being transmitted. If the packet is being used as a listener, XPX fills in all the fields.

Table 2: Format for IPX header

Region    Description
0-1       Checksum. Set by XPX to -1.
2-3       Length of the entire XPX packet, including all other fragments
          associated with the ECB. Filled by XPX.
4         Transport Control. Set to 0 by XPX.
5         Packet Type. Set by application to 4 for an IPX packet, or 5
          for an SPX packet if the packet is being sent (talker).
6-9       Destination Network ID. Set by application if XPX packet is
          being sent (talker). If set to 0, the current network is used
          regardless of its true ID.
10-15     Destination Node ID. Set by application if packet is being
          sent (talker). If set to all 0FFh's, the IPX packet will be
          sent to all IPX listeners on the network with an equal socket
          ID, including active listeners on transmitting node.
16-17     Destination Socket ID. Set by application if packet is being
          sent (talker). Socket ID must be opened before socket ID can
          be used.
18-21     Source Network ID. Set by XPX to the network ID of packet's
          source.
22-27     Source Node ID. Set by XPX to node ID of packet's source.
28-29     Source Socket ID. Set by XPX to socket ID of packet's source.

The SPX packet header is a superset of the IPX packet header; see Table 3. None of the SPX-specific fields need to be filled in by the application for either transmissions or receptions. These fields must be available for SPX packets.

Table 3: The SPX packet header structure

Region    Description

0-29      IPX header previously defined.

30        Connection Control. Used by SPX to control flow of data.

31        Data-stream Type. Information byte that can be used by
          application for any purpose. SPX reserves values 0FEh and 0FFh
          for its own use.

32-33     Source Connection ID. Connection number of source node for this
          SPX packet. Created by SPX for use by application.

34-35     Destination Connection ID. Connection number of destination
          node for this SPX packet. Created by SPX for use by
          application.

36-37     Sequence Number. Used by SPX to keep sequence of received and
          transmitted packets straight.

38-39     Acknowledge Number. Used by SPX to acknowledge receipt of a
          packet.

40-41     Allocation Number. Used by SPX to keep track of packets sent
          but not acknowledged.

The size of an SPX packet header is 12 bytes larger than the header for IPX packets. Because the headers must be part of every XPX packet, the maximum size of data that can be sent with an IPX packet is 546 bytes, and the maximum for SPX is 534 bytes.

Simplicity Breeds Attempts

When I first began coding access functions, IPX demonstrated itself superior to file- or pipe-access methods. Eventually, however, IPX began to lose packets. I discovered that under a moderate transmission load, my application spent too much time minding its business, while IPX-filled listening packets weren't being serviced. Polling was not able to keep up with the data-transfer demand. Novell's documentation confirmed this, stating that IPX yields about a 95 percent delivery rate (although I was never able to achieve this rate).

I first decided that if more than a few packets per second are being transferred, an IPX communication needs ESR support to function effectively. I then wrote a library of ESR-powered IPX functions. My experimentation yielded a 100 percent delivery rate for over 100,000 packet transmissions. To find a reasonable upper limit, I ran a program on four 80386 PCs on my Ethernet network. Each computer sent and received 512-byte IPX packets continuously. I didn't notice any packet loss until they reached about 70 packets/second/node (280 packets/second, overall). At that point, the 16-MHz 386SX started to go deaf.

After I implemented ESR support, I decided that the functions and data structures were cumbersome. At an application level, I didn't want to have to deal with the asynchronous approach. Because I was already using a file-based approach, I figured that the entire IPX access would be best implemented as a streams type of interface. With a streams implementation, analogous structures became apparent. The full network-address structure (network ID, node ID, and socket ID) became the "filename." Access flags could be used to define the type of packet (listener or talker) and communication method (IPX or SPX). I merged the ECB and a superset structure of the IPX/SPX header.

To keep track of the stream's operation, I created a structure called XPX_STREAM_T, which is documented in the file NETWORK.H. XPX_STREAM_T allows the programmer to open a channel to a node (or nodes, if it is a broadcast channel) and perform reads, writes, and queries on that channel. Multiple XPX packets are automatically allocated and provided for the stream's I/O.

Descriptions of Major Functions

The major functions written for the stream approach are XPX_INIT(), XPX_OPEN(), XPX_READ(), XPX_WRITE(), XPX_CLOSE(), and IPX_READ_ORPHAN(). Except for XPX_INIT() and IPX_READ_ORPHAN(), the functions are used in a fashion analogous to their file counterparts. This function library, a text file that details the internals of IPX functions, my NETWORK.H, and the TEST1.C sample program are available electronically; see page 3.

XPX_INIT() (see Listing Two) initializes the SPX internals and gets the entry vector for accessing IPX. This function queries IPX for the application's node ID and network address, which are placed in the global structure Our_Address. After XPX is initialized, a single-socket ID is opened and dynamically generated by IPX. This socket ID is placed in the Our_Address structure, and it can be used as an application's private channel. The open socket is not needed for the operation of any functions, and is provided only for the sake of convenience.

XPX_OPEN() allows the application to open a communication channel to another node on the network. The channel can be read only, write only, or read/write. The node ID can be specific or a broadcast channel. A specific open will receive packets only from the defined target-node ID. A broadcast channel will accept packets from any node ID at that socket. There is no such thing as a broadcast socket, so socket IDs are important. Any packet received by a node from itself will be ignored. Note that an IPX stream cannot share the same socket as an SPX stream, but mutiple IPX streams can share the same socket.

XPX_READ() allows the application to read the next received packet data partially or completely. The read will only be made up to the end of the first available packet. Data from multiple packets can only be read by successive reads. Packets may only be read in the order they were received from the source node.

XPX_WRITE() allows the application to write data to a stream. The data written does not have to be of a particular size. XPX_WRITE() will packetize the data and send the packets to XPX. XPX_WRITE() returns the number of bytes written. If a done pointer is provided for IPX_WRITE(), IPX_WRITE() will set the flag to a nonzero value, and the ESR will reset the flag to 0 when a packet has been sent.

XPX_CLOSE() closes the communication channel and frees all data buffers allocated by XPX_OPEN().

IPX_READ_ORPHAN() allows the application to read any packets sent to a socket ID that do not match any open stream addresses. (Broadcast streams with read ability match all nodes.) This function should be called periodically, so that all the listening packets do not get used up by spurious receptions. If orphan packets are not an issue, the application can set the global variable _Ignore_Nomatch to a nonzero value.

Four macros have been defined within NETWORK.H to provide status information for the application. XPX_ERROR_STATUS() returns the number of packet errors that have occurred since the last call to IPX_INIT(). XPX_ORPHAN_STATUS() returns the current number of unprocessed orphan packets. XPX_READ_STATUS() returns the number of packets associated with a stream that have not been read. XPX_WRITE_STATUS() returns the number of packets that are available to a stream for transmission.

Conclusion

IPX is a high-performance protocol available on Novell Netware networks. IPX adds a level of complexity and uncertainty to peer-to-peer communications, but it can be fashioned into a useful resource. SPX is slightly slower, but guarantees data delivery. Given the scope of this article, I have dwelled upon IPX and only touched upon SPX. I leave these functions as a foundation on which you can build.

DDJ



_IPX: THE GREAT COMMUNICATOR_
by Rahner James


[LISTING ONE]
<a name="0113_000e">

; **************************************************************************
; * Title: ESRS.ASM -- by Rahner James
; * Copyright (c) January 1991, Ryu Consulting, 916/722-1939
; * File contains default Event Service Routine for listening & talking packets
; **************************************************************************

_ESRS_ASM_  equ 1
ifdef   LARGEMODEL
    .model  large,c
else
    .model  small,c
endif
ADDRESS_S struct
    network     dw  ?,? ; Network number
    node        dw  3 dup(?); Node address on that network
    socket      dw  ?   ; Socket number on that node
ADDRESS_S ends
IPX_PACKET_S struct
    next        dd  ?  ; Used by IPX/SPX when the ECB is active
    function    dd  ?  ; Called after packet sent/recd, called ESR
    in_use      db  ?  ; Set to !0 by IPX/SPX when packet is in use
    completion_code db  ?  ; Set by XPX after packet task is complete
    socket      dw  ?  ; Socket to use for this ECB
    IPX_work    dd  ?  ; Workspace used internally by IPX
    driver_work dd  ?,?,? ; Workspace used internally by IPX driver
    dest_address    db  6 dup(?); Destination address for packet
    fragment_count  dw  ?   ; Fragments descriptors that follow
    hdr     dd  ?   ; -> IPX/SPX packet descriptor to use
    size_hdr    dw  ?   ; Size of the IPX(30) or SPX(42) descriptor
    buffer_ptr  dd  ?   ; -> data buffer to use for transmission/reception
    buffer_size dw  ?   ; Number of bytes in that buffer
    next_allocated  dd  ?   ; -> next allocated packet structure
    next_sibling    dd  ?   ; -> next packet for stream and condition
    parent      dd  ?   ; -> parent stream definition packet
    default_buffer  dd  ?   ; -> default buffer to use for IPX or SPX
    default_size    dw  ?   ; Size of the default buffer
    done_flag   dd  ?   ; Set by the ESR with the completion code

    checksum    dw  ?   ; Dummy checksum of 30-byte packet header
    packet_length   dw  ?   ; Length of complete IPX packet
    control     db  ? ; Transport control byte for internet bridges
    packet_type db  ?   ; Packet type: IPX(4)/SPX(5)

    dest_network    dd  ?   ; Destination network address
    dest_node   db  6 dup(?); Destination node address
    dest_socket dw  ?   ; Destination socket

    src_network dd  ?   ; Source network address
    src_node    db  6 dup(?); Source node address
    src_socket  dw  ?   ; Source socket
IPX_PACKET_S ends
XPX_STREAM_S struct
    next        dd  ?   ; -> next stream structure opened
    first_allocated dd  ?   ; -> first allocated packet for handle
    last_allocated  dd  ?   ; -> last allocated packet for handle
    first_unread    dd  ?   ; -> first unread packet
    last_unread dd  ?   ; -> last unread packet in the list
    first_free  dd  ?   ; -> first packet available for talking
    first_error dd  ?   ; -> first packet encountering an error
    last_error  dd  ?   ; -> last packet encountering an error

    dest_network    dd  ?   ; Destination network address
    dest_node   db  6 dup(?); Destination node address
    dest_socket dw  ?   ; Destination socket
    local_target    db  6 dup(?); Node address of local target for dest
    connection_ID   dw  ?   ; Connection ID used for SPX
    total_talkers   dw  ?   ; Number of talkers for this stream
    total_listeners dw  ?   ; Number of listeners for this stream
    unread_count    dw  ?   ; Number of packets unread by app
    free_count  dw  ?   ; Number of packets ready for talking
    maximum_unread  dw  ?   ; Maximum number of unread packets
    error_count dw  ?   ; Number of unprocessed error packets

    total_transmissions dd  ?   ; Number of transmissions performed
    total_receptions    dd  ?   ; Number of receptions performed
    total_errors    dd  ?   ; Number of errors encountered
XPX_STREAM_S ends
 .data
        extern  _Ignore_Nomatch:byte, _Our_Address:word, IPX_Vector:dword,
                                                            _First_Stream:dword
    extern  _First_Nomatch:dword, _Last_Nomatch:dword, _Total_Nomatchs:word
 .code
Last_Broad_Ptr      dw  0,0 ; -> last checked broadcast stream

; **************************************************************************
; *  void far TALK_ESR( void ) -- Event Service Routine (ESR) for IPX
; *  functions and their talking packets
; *  Given: AL = 0 if AES called this ESR, 0xff if this is a normal event
; *     ES:SI -> ECB that just finished talking
; *  Returns: Packet either glued onto the free list or the error list
; *  Note: Interrupts are enabled at this point and should stay that way
; **************************************************************************
talk_esr proc far
; * See if we need to set the done flag
    lds bx, es:[si].IPX_PACKET_S.done_flag ; DS:BX -> process done flag
    mov cl, es:[si].IPX_PACKET_S.completion_code
    mov ax, ds
    or  ax, bx
    jz  @F          ; If DS:BX -> NULL, just skip it
    mov [bx], cl        ; Set the flag with our completion code
    mov word ptr es:[si].IPX_PACKET_S.done_flag, 0 ; Make it NULL
    mov word ptr es:[si].IPX_PACKET_S.done_flag+2, 0
; * Check whether the packet goes in the error list or the free list
@@: lds bx, es:[si].IPX_PACKET_S.parent    ; DS:BX -> parent structure
    or  cl, cl            ; See if we got a transmission error
    jnz talk20_esr        ; Jump if we got one
; * Here's where we process the good transmissions
    add word ptr [bx].XPX_STREAM_S.total_transmissions, 1
    adc word ptr [bx].XPX_STREAM_S.total_transmissions+2, 0
    inc [bx].XPX_STREAM_S.free_count;
    mov cx, word ptr [bx].XPX_STREAM_S.first_free ; DX:CX -> first free
    mov dx, word ptr [bx].XPX_STREAM_S.first_free+2
    mov word ptr [bx].XPX_STREAM_S.first_free, si
    mov word ptr [bx].XPX_STREAM_S.first_free+2, es
    mov word ptr es:[si].IPX_PACKET_S.next_sibling, cx
    mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, dx
talk10_esr:
    ret
; * Here's where we take care of our challenged packets
talk20_esr:
    add word ptr [bx].XPX_STREAM_S.total_errors, 1
    adc word ptr [bx].XPX_STREAM_S.total_errors+2, 0
    inc [bx].XPX_STREAM_S.error_count
    mov cx, word ptr [bx].XPX_STREAM_S.last_error ; DX:CX ->last error
    mov dx, word ptr [bx].XPX_STREAM_S.last_error+2
    mov word ptr [bx].XPX_STREAM_S.last_error, si ; Set new last error
    mov word ptr [bx].XPX_STREAM_S.last_error+2, es
    mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0
    mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0
    mov ax, cx             ; See if we need to do the first as well
    or  ax, dx
    jnz @F
    mov word ptr [bx].XPX_STREAM_S.first_error, si ;Set new first error
    mov word ptr [bx].XPX_STREAM_S.first_error+2, es
    ret
@@: mov ds, dx                        ; DS:BX -> the first born
    mov bx, cx
    mov word ptr [bx].IPX_PACKET_S.next_sibling, si ; Point old end
    mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es
    ret
talk_esr endp

; **************************************************************************
; *  void far LISTEN_ESR( void ) -- Event Service Routine (ESR) for IPX
; *  functions and their listening packets
; *  Given:  AL = 0 if AES called this ESR, 0xff if this is a normal event
; *          ES:SI -> ECB that just got something
; *  Returns: Packet put at the end of the unread packet list of the stream it
; *  was intended for.
; *  Note: Interrupts are enabled at this point and should stay that way. This
; *  packet may not be put with its parent if there are multiple parents
; *  associated with one socket
; **************************************************************************
listen_esr proc far
    mov ax, @Data               ; DS = our data segment
    mov ds, ax
; * First see if we sent it as a broadcast and it got back to us
    mov ax, _Our_Address.ADDRESS_S.node+4
    cmp word ptr es:[si].IPX_PACKET_S.src_node+4, ax
    jne listen10_esr
    mov ax, _Our_Address.ADDRESS_S.node+2
    cmp word ptr es:[si].IPX_PACKET_S.src_node+2, ax
    jne listen10_esr
    mov ax, _Our_Address.ADDRESS_S.node
    cmp word ptr es:[si].IPX_PACKET_S.src_node, ax
    jne listen10_esr
    mov ax, _Our_Address.ADDRESS_S.network+2
    cmp word ptr es:[si].IPX_PACKET_S.src_network+2, ax
    jne listen10_esr
    mov ax, _Our_Address.ADDRESS_S.network
    cmp word ptr es:[si].IPX_PACKET_S.src_network, ax
    jne listen10_esr
listen_again_buckwheat:
    mov bx, 4           ; BX = IPX Listen For Packet command
    call    dword ptr IPX_Vector    ; Call the IPX function
    ret
; * See if we need to set the done flag
listen10_esr:
    mov ax, es:[si].IPX_PACKET_S.packet_length  ; Change format
    xchg    ah, al
    sub ax, es:[si].IPX_PACKET_S.size_hdr
    mov es:[si].IPX_PACKET_S.packet_length, ax

    lds bx, es:[si].IPX_PACKET_S.done_flag ; DS:BX -> process done flag
    mov cl, es:[si].IPX_PACKET_S.completion_code
    mov ax, ds
    or  ax, bx
    jz  listen20_esr
    mov [bx], cl
    mov word ptr es:[si].IPX_PACKET_S.done_flag, 0
    mov word ptr es:[si].IPX_PACKET_S.done_flag+2, 0
; * Check whether the packet goes in the error list or the free list
listen20_esr:
    lds bx, es:[si].IPX_PACKET_S.parent   ; DS:BX -> parent structure
    or  cl, cl                ; See if we got a reception error
    jz  listen40_esr         ; Jump if we have an unimpaired reception
; * Here's where we take care of our datistically challenged packets
    add word ptr [bx].XPX_STREAM_S.total_errors, 1
    adc word ptr [bx].XPX_STREAM_S.total_errors+2, 0
    inc [bx].XPX_STREAM_S.error_count
    mov cx, word ptr [bx].XPX_STREAM_S.last_error ; DX:CX->last error
    mov dx, word ptr [bx].XPX_STREAM_S.last_error+2
    mov word ptr [bx].XPX_STREAM_S.last_error, si ; Set new last error
    mov word ptr [bx].XPX_STREAM_S.last_error+2, es
    mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0
    mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0
    mov ax, dx            ; See if we are the only packet here
    or  ax, cx
    jnz @F           ; Skip out of this ESR if we are not alone
    mov word ptr [bx].XPX_STREAM_S.first_error, si ;Set new first error
    mov word ptr [bx].XPX_STREAM_S.first_error+2, es
    ret
@@: mov ds, dx                   ; DS:BX -> the first born
    mov bx, cx
    mov word ptr [bx].IPX_PACKET_S.next_sibling, si
    mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es
    ret
; * Here's where we process the good transmissions
listen40_esr:
    push    ds            ; DX:CX -> the first stream structure
    mov ax, @Data
    mov ds, ax
    mov cx, word ptr _First_Stream
    mov dx, word ptr _First_Stream+2
    pop ds
    mov Last_Broad_Ptr, 0
    mov Last_Broad_Ptr+2, 0
listen50_esr:
    cmp [bx].XPX_STREAM_S.total_listeners, 0 ; See if READ ONLY stream
    jz  not_parent           ; Skip this one if it is READ ONLY
    mov ax, word ptr [bx].XPX_STREAM_S.dest_node
    and ax, word ptr [bx].XPX_STREAM_S.dest_node+2
    and ax, word ptr [bx].XPX_STREAM_S.dest_node+4
    inc ax
    jnz @F            ; Skip if it is not a broadcast type
    mov Last_Broad_Ptr, bx    ; Save this for later
    mov Last_Broad_Ptr+2, ds
    jmp short not_parent      ; Still not necessarily the right one
@@: mov ax, word ptr [bx].XPX_STREAM_S.local_target+4   ; Match parent
    cmp word ptr es:[si].IPX_PACKET_S.src_node+4, ax
    jne not_parent
    mov ax, word ptr [bx].XPX_STREAM_S.local_target+2
    cmp word ptr es:[si].IPX_PACKET_S.src_node+2, ax
    jne not_parent
    mov ax, word ptr [bx].XPX_STREAM_S.local_target
    cmp word ptr es:[si].IPX_PACKET_S.src_node, ax
    jne not_parent
    mov ax, word ptr es:[si].IPX_PACKET_S.src_network+2
    cmp word ptr es:[si].IPX_PACKET_S.dest_network+2, ax
    jne not_parent
    mov ax, word ptr es:[si].IPX_PACKET_S.src_network
    cmp word ptr es:[si].IPX_PACKET_S.dest_network, ax
    je  found_listener
; * At this point, the current structure has been determined not to be suitable
not_parent:
    mov ax, cx              ; See if we are at the end of our rope
    or  ax, dx
    jz  no_listener         ; No stream match found

    mov ds, dx              ; DS:BX -> next stream definition
    mov bx, cx
    mov cx, word ptr [bx].XPX_STREAM_S.next ; DX:CX -> next stream
    mov dx, word ptr [bx].XPX_STREAM_S.next+2
    jmp listen50_esr                ; Loop until we poop
; * Stream ID matches up with destination address, so add packet to stream list
found_listener:
    add word ptr [bx].XPX_STREAM_S.total_receptions, 1
    adc word ptr [bx].XPX_STREAM_S.total_receptions+2, 0
    inc [bx].XPX_STREAM_S.unread_count
    mov ax, [bx].XPX_STREAM_S.unread_count  ; Update our statistics
    cmp [bx].XPX_STREAM_S.maximum_unread, ax
    jnc found10_listener            ; Skip if no need to update
    mov [bx].XPX_STREAM_S.maximum_unread, ax
found10_listener:
    mov cx, word ptr [bx].XPX_STREAM_S.last_unread
    mov dx, word ptr [bx].XPX_STREAM_S.last_unread+2
    mov word ptr [bx].XPX_STREAM_S.last_unread, si
    mov word ptr [bx].XPX_STREAM_S.last_unread+2, es
    mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0
    mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0
    mov ax, cx             ; See if we need to do the first as well
    or  ax, dx
    jnz @F             ; All done if there are others
    mov word ptr [bx].XPX_STREAM_S.first_unread, si
    mov word ptr [bx].XPX_STREAM_S.first_unread+2, es
    ret
@@: mov ds, dx                   ; DS:BX -> the first born
    mov bx, cx
    mov word ptr [bx].IPX_PACKET_S.next_sibling, si
    mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es
    ret
; * At this point, packet is an orphan and must be sent off to farm or be glue
no_listener:
    lds bx, dword ptr Last_Broad_Ptr ; DS:BX->last broadcast stream
    mov ax, ds
    or  ax, bx
    jnz found_listener  ; If one was found, use as last resort
    cmp _Ignore_Nomatch, al ; Ignore orphans or adopt
    jmp listen_again_buckwheat  ; Put back on the mountaintop
no10_listener:
    mov ax, @Data       ; DS = our most lovable data segment
    mov ds, ax
    inc _Total_Nomatchs
    mov cx, word ptr _Last_Nomatch     ; DX:CX -> last error packet
    mov dx, word ptr _Last_Nomatch+2
    mov word ptr _Last_Nomatch, si         ; Make it point to us
    mov word ptr _Last_Nomatch+2, es
    mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0
    mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0
    mov ax, cx             ; See if we need to do the first as well
    or  ax, dx
    jnz @F          ; All done if there are others
    mov word ptr _First_Nomatch, si    ; Set us as the new first error
    mov word ptr _First_Nomatch+2, es
    ret
@@: mov ds, dx          ; DS:BX -> the first born
    mov bx, cx
    mov word ptr [bx].IPX_PACKET_S.next_sibling, si
    mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es
    ret
listen_esr endp
    end




<a name="0113_000f">
<a name="0113_0010">
[LISTING TWO]
<a name="0113_0010">

; **************************************************************************
; * Title:  XPX_INIT.ASM -- Rahner James
; * Copyright (c) December 1991, Ryu Consulting, 916/722-1939
; * File contains all the functions to support initializing IPX engine
; **************************************************************************

_XPX_INIT_ASM_  equ 1
    include network.inc
 .data
        public IPX_Vector, _Socket_Life, _SPX_Version, _SPX_Max_Connections,
                                                                  _Our_Address
    public  _SPX_Available_Connections, _SPX_Retry_Count

IPX_Vector    dw    offset dummy_IPX_function,@Code   ; -> IPX support function
_Socket_Life           db  0    ; 0=socket closed at app termination
                    ; 0ffh= socket closed when requested
_SPX_Version           dw  0    ; SPX version #: MSByte=major, LSByte=minor
_SPX_Max_Connections       dw  0    ; Max number of SPX connections
_SPX_Available_Connections dw  0    ; # of SPX connections available to app
_SPX_Retry_Count       db  0    ; Retry count for SPX establish connection
_SPX_Bowser_Flag       db  1    ; Watchdog flag, 0=disable, 1=enable
_Our_Address label dword            ; Global access for this structure
network            dd  0    ; Network address
node               db  6 dup(0) ; Node address
socket             dw  0    ; Socket number

 .code
; **************************************************************************
; * int XPX_INIT( us SOCKET_NUMBER ) -- Initializes all IPX/SPX internals
; * Given: SOCKET_NUMBER = socket number to open for listening, 0 opens
; *             the next available
; * Returns: 0 if IPX was initialized successfully
; *     -1 = socket already open (!)
; *     -2 = socket table full
; *     -3 = IPX or SPX is not installed
; *  Note: Initializes IPX vector, opens a listening socket for IPX driver.
; *  Internal vectors, counter, & pointers are brought to initial conditions
; **************************************************************************
xpx_init proc uses di si, socket_number:word
    mov ax, 7a00h       ; Get the IPX vector
    int 2fh         ; Query the DOS multiplexer
    inc al          ; AL = 0ffh if IPX is there
    jnz derr_xpx_init       ; Quit in disgrace if it's not there
    mov IPX_Vector, di          ; IPX function vector returned in ES:DI
    mov IPX_Vector+2, es
; * See if we need to close the old stuff down
    mov dx, socket
    or  dx, dx          ; See if we opened a socket
    jz  xpx10_init      ; Skip if we didn't
    cmp socket_number, 0
    jz  xpx20_init      ; Skip if so
    cmp socket_number, dx   ; See if it's the same as before
    je  xpx20_init      ; Skip if it is
    IPX 1           ; IPX Close Socket command
; * Now, open the socket
xpx10_init:
    mov dx, socket_number
    mov al, _Socket_Life
    IPX_CHECK   0       ; IPX Open Socket command
    jnz done_xpx_init       ; Quit if an error
xpx20_init:
    mov socket, dx
; * Get our internetwork address
    mov ax, ds          ; ES = DS
    mov es, ax
    mov si, offset network

    mov di, si
    IPX 9           ; IPX Get Internetwork Address command
; * Last, we have to initialize the SPX interface
    xor ax, ax
    IPX_CHECK   10h     ; SPX Initialize command
    jz  derr_xpx_init       ; Quit if it's not there
    mov _SPX_Version, bx    ; Save the information returned
    mov _SPX_Max_Connections, cx
    mov _SPX_Available_Connections, dx
    xor ax, ax          ; Good return
    jmp short done_xpx_init
derr_xpx_init:
    mov al, -3          ; It's gone McCreedy!
done_xpx_init:
    cbw
    ret
xpx_init endp

; **************************************************************************
; * int DUMMY_IPX_FUNCTION( void ) -- Dummy function that returns error
; *      code -10 so that system will not hang if not initialized
; * Given: nothing
; * Returns: -10 always
; **************************************************************************
dummy_IPX_function proc far
    mov ax, -10
    ret
dummy_IPX_function endp
    end



Example 1:

     mov  ax, 7a00h           ; Function 7Ah, AL = 0
     int  2fh                 ; MS-DOS Multiplex interrupt
                              ; Returns with AL == 0FFh if xPX
                              ;  exists and ES:DI == xPX vector

     inc  al                  ; Set ZERO if AL == -1
     jnz  outta_here          ; Quit if xPX isn't around
     mov  IPX_Vector, di      ; Save the xPX entry vector
     mov  IPX_Vector+2, es


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.