{$B-,V-,X+}
UNIT nwIPX;

{$DEFINE ProtMode}
{$IFDEF MSDOS} {$UNDEF ProtMode} {$DEFINE RealMode} {$ENDIF}
{$IFDEF ProtMode} sorry, protected mode not supported (yet) {$ENDIF}

{ nwIPX unit as of 950301 / NwTP 0.6 API. (c) 1993,1995, R.Spronk }

INTERFACE

Uses Dos,nwMisc;

{ Primary IPX calls:           Subf:  Comments:

  IPXCancelEvent                 6    AES
* IPXCloseSocket                 1    (1)
  IPXDisconnectFromTarget        B    (1)
* IPXGetInterNetworkaddress      9
* IPXGetIntervalMarker           8
* IPXGetLocalTarget              2
- IPXGetPacketSize               D    (IPX internal use only)
* IPXInitialize                       INT 2F
- IPXInitializeNetworkAddress    C    (IPX internal use only)
* IPXListenForPacket             4
* IPXOpenSocket                  0
* IPXRelinquishControl           A
* IPXScheduleIPXEvent            5    AES
  IPXScheduleSpecialEvent        7
* IPXSendPacket                  3
- IPXTerminateSockets            E    (IPX internal use only)

  Secondary calls:

* IPXpresent
* IPXsetupSendECB
* IPXsetupListenECB

  Notes: (1) These functions use INT 21 and are not to be called from
             within an ESR.
}

CONST
    LONG_LIVED_SOCKET  = TRUE;   { IPXopenSocket }
    SHORT_LIVED_SOCKET = FALSE;

 {*** PACKET TYPES ***}

    UNKNOWN_PACKET_TYPE   =0;  { (basic) Unknown IPX packet }
    IPX_PACKET_TYPE       =0;
    RIP_PACKET_TYPE       =1;  { Routing Information Packet }
    ECHO_PACKET_TYPE      =2;
    ERROR_PACKET_TYPE     =3;
    PEP_PACKET_TYPE       =4;  { Packet Exchange Protocol   }
    SPX_PACKET_TYPE       =5;  { Sequenced Packet Protocol Packet }
    PUP_PACKET_TYPE       =12;
    DOD_IP_PACKET_TYPE    =13; { Internet Protocol packet Type }
    NCP_PACKET_TYPE       =17; { NetWare Core Protocol }
    { Experimental packet types: 20 - 37 }

 {*** SOCKET NUMBERS ***}

    {0001-0BB8 Registered with Xerox                              }
    SKT_XEROX_ROUTING_INFORMATION= $0001;
    SKT_ECHO_PROTOCOL            = $0002;
    SKT_ERROR_HANDLER            = $0003;

    {0020-003F Xerox : Experimental                               }
    SKT_NW4_TIME_SYNC_SERVER     = $0040; { used by OT_NW4_TIME_SYNC_SERVER }
    SKT_FILE_SERVICE             = $0451; { see also $8140, used by OT_RSPCX_SERVER }
    SKT_SERVICE_ADVERTISING      = $0452; { SAP }
    SKT_ROUTING_INFORMATION      = $0453; { Novell's RIP Socket   }
    SKT_NETBIOS                  = $0455;
    SKT_DIAGNOSTIC               = $0456;
    { 0457h	??? (appears to be related to server serial numbers) }

    {0BB9-FFFF Xerox : Dynamically assignable Sockets             }
      {0BB9-3FFF Novell:                                              }
      SKT_NMA_AGENT                =$2F90; { used by OT_NMA_AGENT (NMS) }

      {4000-7FFF Novell: Dynamically assignable Sockets               }
      { Use a socket in this range for your own applications.         }
      { To avoid conflicts with other programs, you are advised NOT   }
      { to use sockets numbers where the hi-byte equals the low-byte, }
      { C programmers mostly use those to avoid byte-order swapping.  }

     { ! See the SKT_XXX file in the XIPX archive for the latest info
       on socket numbers... }

      {8000-FFFF Novell: Well known sockets, registered with Novell.  }
      SKT_EMAIL_CHAT               =$8055; { Niche Corp. }
      SKT_EMAIL_CHAT_2             =$8056; { Niche Corp. }
      SKT_BTRIEVE                  =$8058;
      SKT_BTRIEVE_2                =$8059;
      SKT_NW_SQL                   =$805A;
      SKT_NW_SQL_2                 =$805B;
      SKT_GAMESERVER               =$805C;
      SKT_GAMESERVER_2             =$805D;
      SKT_PRINT_SERVER             =$8060;
      SKT_DIGITAL_CHAT             =$806C; { Digital Inc. }
      SKT_NW_ACCESS_SERVER         =$806F;
      SKT_OXXI_EMAIL_CHAT          =$80C3; { Oxxi Inc. }
      SKT_PRINT_SERVER_2           =$811E;
      SKT_INTEL_EMAIL_CHAT         =$845F; { Intel Corp. }
      SKT_WINDOWS_EMAIL_CHAT       =$9017;
      SKT_JOB_SERVER               =$9022;

Var Result:word; { unit errorcode variable }

Type TipxHeader=Record
                checksum         :word; { not used, set to $FFFF }
                length           :word; { total number of bytes }
                TransportControl :byte; { used by bridges: low 4 bits= hop count }
                packetType       :byte; { ignored by IPX, used by higher level
                                          protocols only. $00=unknown packet type}
                destination,
                source           :TinternetWorkAddress;
                                         { if dest.network equals 0; dest
                                           assumed on same network as sender }
                                         { if dest.node =$FFFFFFFFFFFF, packet
                                           will be sent to all nodes. }
                end;
    { Fields within IPX and SPX are high-low. Byte swapping will be done
      by the IPX functions, except network and node addresses. }

    Tfragment=record { address and size of buffer fragment. }
              Address:Pointer;
              Size:word;
              end;

    Tecb=record
         Linkaddress     :Pointer; { used by IPX itself }
         ESRaddress      :Pointer;
         InUseFlag       :Byte; { reset to $00 when request completed }
         CompletionCode  :Byte; { valid after InUseFlag becomes $00;
                                 completionCode=$00: packet sent/received. }
         SocketNumber    :word;
         IPXworkspace    :array[1..4] of byte;
         DriverWorkspace :array[1..12] of byte;
         Immediateaddress:Tnodeaddress; { 6 bytes }
         FragmentCount   :word;  { must be >0 }
         Fragment        :array[1..2] of Tfragment; { [1..FragmentCount] }
                          { The number of fragments is unlimited.
                            However, most applications use 1 or 2. }
         end;
    Tpecb=^Tecb;

{    TAESecb=:
Offset	Size	Description
 00h	DWORD	Link
 04h	DWORD	ESR address
 08h	BYTE	in use flag (see below)
 09h  5 BYTEs	AES workspace }

Function IpxPresent:boolean;
{ Determines if an IPX driver is loaded. Calls IPXInitialize. }

Function IPXinitialize:Boolean;
{ Determines if an IPX driver is loaded. }

{IPX/SPX: 09h}
Function IPXGetInternetworkAddress(Var Address:TinterNetworkAddress):boolean;
{ This call returns the network and node address of the requesting workstation. }
{ The two byte socketnumber must be appended to the end to form a full }
{ 12-Byte network address. The socketnumber will be set to 0000, indicating
  that it has to be filled later with a meaningfule number. }

{IPX/SPX: 00h}
Function IPXOpenSocket(Var socket:word; PermanentSocket:boolean):boolean;
{ When an application wants to send or receive packets on a socket,
  it should first open the socket. PermanentSocket should be set to TRUE
  if the socket is used by a TSR. This way, the socket will only be
  closed when the IPXcloseSocket function is called. Otherwise, set to FALSE. }

{IPX/SPX: 01h}
Function IPXCloseSocket(socket:word):boolean;
{ Closes the socket. TSRs should close permanent sockets before terminating. }

{IPX/SPX: 02h}
Function IPXGetLocalTarget(Address:TinternetworkAddress;
                           Var ImmAddr:TnodeAddress;
                           Var Ticks:word                ):boolean;
{ Returns the nodeaddress (Immediate address) of a bridge/router that
  connects the senders' network with the target-network. If the target
  lies within the same network as the sender, the returned node address
  is the same as the target node-address. }

{IPX/SPX: 03h}
Function IPXSendPacket(Var Ecb:Tecb):boolean;
{ After calling this function, control is immediately turned back to the
  calling process, whilst in the background the IPX driver is trying to
  send the packet. To check if the message has been sent, check the
  ECB.InUseFlag or use a SendESR.
  The ecb must be filled with appropriate values before calling this function,
  the socket to send on must be open. }

{IPX/SPX: 0Fh}
Function IPXInternalSendPacket(Var Ecb:Tecb):boolean;

{IPX/SPX: 04h}
Function IPXListenForPacket(Var Ecb:Tecb):Boolean;
{ After calling this function, control is immediately turned back to the
  calling process. The IPX driver will wait in the background for a packet
  to be received. To check if a message has been received, check the
  ECB.InUseFlag or use a ListenESR.
  The ecb must be filled with appropriate values before calling this function,
  the socket to receive on must be open. }

{IPX/SPX: 0Ah}
Function IPXrelinquishControl:boolean;
{ Temporarily gives away CPU time to bakcground processes. This call
  improves efficeincy by informing the IPX driver that the CPU is
  available. }

{IPX/SPX: 08h}
Function IPXgetIntervalMarker(Var ticks:word):boolean;
{ Gets a time marker from IPX. The difference between two known
  time-markers can be used to determine if a timeout has occurred.
  1 Tick = 1/18.2 second. }

{IPX/SPX: 06h}
Function IPXcancelEvent(ECB:Tecb):boolean;
{ AES call: Cancel an event.
  When the event is canceled, the ECB.InUseFlag will be set to $00 and the
  ECB.CompletionCode to $FC: Event Canceled. }

{IPX/SPX: 0Bh}
Function IPXdisconnectFromTarget(Address:TinternetworkAddress):boolean;
{ Informs the listening socket at the specified adress that no more
  packets will be sent to the listening socket.
  This function is not required in your application, it is merely used
  to inform some drivers that the connection (if any) has ended. }

{IPX/SPX: 05h}
Function IPXscheduleIPXevent(ticks:word;Var ECB:Tecb):boolean;
{ AES call: schedule an event.
  After calling this function, control is immediately turned back to the
  calling process. After waiting the number of ticks specified
  (1 tick= 1/18.2 sec.), the IPX driver activates the ECB.
  This function should never be called with an ECB that is still in use
  by the IPXdriver (i.e. ECB.InUseFlag should be 0 before calling)
}

{UPX: 0007}
Function IPXscheduleSpecialEvent(ticks:word;Var ECB:Tecb):boolean;

Procedure IpxSpxSystemCall(Var regs:registers);
{ Provides an entry into the INT A7 interrupt handler;
  Valid only if IPXinitialize or IPXinstalled were called previously. }

{************** Secondary Procedures ***************************************}

Procedure IPXSetupListenECB(ESRptr:Pointer; ReceiveSocket:word;
                            BufPtr:Pointer; BufSize:word;
                     {out:} Var IpxHdr:TipxHeader; Var ecb:Tecb);
{ Clears IPXheader and ECB, sets values of the required fields within
  the ecb and IPX header. }

Procedure IPXsetupSendECB(ESRptr:pointer; SourceSocket:Word;
                          DestAddr:TinterNetworkAddress;
                          BufPtr:pointer; BufSize:word;
                   {out:} Var IpxHdr:TipxHeader; Var ecb:Tecb);
{ Clears IPXheader and ECB, sets values of the required fields within
  the ecb and IPX header. }

IMPLEMENTATION {==============================================================}

CONST
    IPX_MAX_DATA_LENGTH  =546;

Var IpxSpxCall:Procedure;

Procedure IpxSpxSystemCall(Var regs:registers); assembler;
{ This method of calling IPX/SPX is preferred by Novell. }
{ For what its' worth: this call is 48 bytes longer than the other one.. }
asm
   { check if IpxSpxCall known. If not, return error $FF in fake AL }
xor ah,ah
mov al,$FF
les di,IpxSpxCall
mov bx,es
cmp bx,$0000
je  @1
   { move fake regs registers to 'real' registers }
   { AX, BX, CX, DX, SI, DI, ES only. }
les di,regs
mov ax,es:[di+16]
push ax            { push new es }
mov ax,es:[di+12]
push ax            { push new di }
mov ax,es:[di]
mov bx,es:[di+2]
mov cx,es:[di+4]
mov dx,es:[di+6]
mov si,es:[di+10]
pop di
pop es
   { farr call to A7 interrup handler }
push bp
CALL IpxSpxCall
pop bp
@1: { move 'real' registers to fake regs registers }
push es
push di
les di,regs
mov es:[di],ax
mov es:[di+2],bx
mov es:[di+4],cx
mov es:[di+6],dx
mov es:[di+10],si
pop ax             { ax:= 'di' }
mov es:[di+12],ax
pop ax             { ax:= 'es' }
mov es:[di+16],ax
end;

Function IPXinitialize:Boolean;
CONST DOS_MULTIPLEX =$2F;
Var regs:registers;
begin
Regs.AX:=$7A00;
INTR(DOS_MULTIPLEX,Regs);
if regs.AL<>$FF
 then begin
      Result:=IPX_NOT_INSTALLED;
      IpxInitialize:=false
      end
 else begin
      @IpxSpxCall:=Ptr(Regs.es,Regs.di);
      Result:=0;
      IpxInitialize:=true;
      end;
end;

Function IpxPresent:boolean;
begin
IpxPresent:=IpxInitialize
end;

{IPX: 09h}
Function IPXGetInternetworkAddress(Var Address:TinterNetworkAddress):boolean;
{ This call returns the network and node address of the requesting workstation. }
{ The two byte socketnumber must be appended to the end to form a full }
{ 12-Byte network address. }
Var regs:registers;
begin
regs.bx:=$0009;
regs.es:=seg(Address);
regs.si:=ofs(Address);
IpxSpxSystemCall(Regs);
result:=regs.al;
address.socket:=$0000; { unknown, to be set later. }
if result<>$FF
 then result:=$00;
IPXGetInternetworkAddress:=(result=$00);
{ possible resultcodes: $00 Successful; $FF IPX not initialized }
end;

{IPX: 00}
Function IPXOpenSocket(Var socket:word; permanentSocket:boolean):boolean;
Var regs:registers;
    reqForSocket:boolean;
begin
regs.bx:=$0000;
if permanentSocket
 then regs.al:=$FF
 else regs.al:=$00;
regs.dx:=swap(socket); {hi-lo}
reqForSocket:=(socket=$0000);

IpxSpxSystemCall(Regs);

result:=regs.al;
if reqForSocket
 then socket:=swap(regs.dx); {force lo-hi}
IPXopenSocket:=(result=0);
{ resultcodes: $00 successful; $FE Socket Table Is Full;
               $FF socket already open OR IPX not initilazed. }
end;

{IPX: 01}
Function IPXCloseSocket(socket:word):boolean;
Var regs:registers;
begin
regs.bx:=$01;
regs.dx:=swap(socket);
IpxSpxSystemCall(regs);
result:=regs.al;
if result<>$FF then result:=$00;
IPXCloseSocket:=(result=$00);
{ possible resultcodes: $00 Successful; $FF IPX not initialized }
end;


{IPX: 02}
Function IPXGetLocalTarget(Address:TinternetworkAddress;
                           Var ImmAddr:TnodeAddress;
                           VAR ticks:Word               ):boolean;
{ Ticks = estimated transmission time, in number of ticks (1/18 sec) }
Var reqAddr:TinternetworkAddress;
    repNode:TnodeAddress;
    Regs   :registers;
begin
move(Address,reqAddr,10);
reqAddr.socket:=swap(Address.socket); {hi-lo}
With regs
 do begin
    bx:=$0002;
    es:=seg(reqAddr); si:=ofs(reqAddr); di:=ofs(repNode);
    IpxSpxSystemCall(regs);
    ticks:=regs.cx;
    result:=regs.al;
    if result=0
     then move(repNode,ImmAddr,6);
    end;
IPXGetLocalTarget:=(result=$00);
{ resultcodes: $00 Successful; $FA No path to destination node found;
               $FF IPX not initialized. }
end;

Function IPXSendPacket(Var Ecb:Tecb):boolean;
{ the ecb must be filled, before calling this function }
{ Right after this call, IPXrelinquishControl should be called Iteratively,
  this allows the sending of the IPX packet. }
Var regs:Registers;
begin
regs.bx:=$0003;
regs.es:=seg(ecb);
regs.si:=ofs(ecb);
IpxSpxSystemCall(Regs);
result:=regs.al;
if result<>$FF then result:=$00;
IpxSendPacket:=(result=$00);
{ possible resultcodes: $00 Successful; $FF IPX not initialized }
end;


Function IPXInternalSendPacket(Var Ecb:Tecb):boolean;
Var regs:Registers;
begin
regs.bx:=$000F;
regs.es:=seg(ecb);
regs.si:=ofs(ecb);
IpxSpxSystemCall(Regs);
result:=regs.al;
if result<>$FF then result:=$00;
IpxInternalSendPacket:=(result=$00);
{ possible resultcodes: $00 Successful; $FF IPX not initialized }
end;


Function IPXListenForPacket(Var Ecb:Tecb):Boolean;
{ socket must be opened, ECB (partly) filled. }
Var regs:Registers;
begin
regs.bx:=$0004;
regs.es:=seg(ecb);
regs.si:=ofs(ecb);
IpxSpxSystemCall(Regs);
result:=regs.al;
if result<>$FF
 then result:=$00;
IpxListenForPacket:=(result=$00);
{resultcodes: $00 Successful;
              $FF Listening Socket doesn't exist OR IPX not initialized }
end;

Function IPXrelinquishControl:boolean;
Var regs:Registers;
begin
regs.bx:=$000A;
IpxSpxSystemCall(Regs);
result:=regs.al;
if result<>$FF then result:=$00;
IpxrelinquishControl:=(result=$00);
{resultcodes: $00 Successful; $FF IPX not initialized }
end;

Function IPXgetIntervalMarker(Var ticks:word):boolean;
Var regs:Registers;
begin
regs.bx:=$0008;
IpxSpxSystemCall(Regs);
ticks:=regs.ax;
result:=$00;
IPXgetIntervalMarker:=True;
end;

Function IPXcancelEvent(ECB:Tecb):boolean;
Var regs:registers;
begin
regs.bx:=$0006;
regs.es:=seg(ecb);
regs.si:=ofs(ecb);
IpxSpxSystemCall(Regs);
result:=regs.al;
IPXcancelEvent:=(result=0);
{ resultcodes: 00 Successful; F9 ECB cannot be canceled;
               FF ECB not in use OR IPX not initialized. }
end;

Function IPXdisconnectFromTarget(Address:TinternetworkAddress):boolean;
VAR regs:registers;
    LocAddr:TinternetworkAddress;
begin
move(Address,LocAddr,10);
LocAddr.socket:=swap(Address.socket);
regs.bx:=$000B;
regs.es:=seg(LocAddr);
regs.si:=ofs(LocAddr);
IpxSpxSystemCall(Regs);
result:=regs.al;
if result<>$FF
 then result:=$00;
IPXdisconnectFromTarget:=(result=0);
{resultcodes: $00 Successful; $FF IPX not initialized }
end;

Function IPXscheduleIPXevent(ticks:word;Var ECB:Tecb):boolean;
Var regs:registers;
begin
regs.bx:=$0005;
regs.ax:=ticks;
regs.es:=seg(ECB);
regs.si:=ofs(ECB);
IpxSpxSystemCall(Regs);
if result<>$FF
 then result:=$00;
IPXscheduleIPXevent:=(result=0);
{resulcodes: 00 successful; FF IPX not initialized }
end;

Function IPXscheduleSpecialEvent(ticks:word;Var ECB:Tecb):boolean;
Var regs:registers;
begin
regs.bx:=$0007;
regs.ax:=ticks;
regs.es:=seg(ECB);
regs.si:=ofs(ECB);
IpxSpxSystemCall(Regs);
if result<>$FF
 then result:=$00;
IPXscheduleSpecialEvent:=(result=0);
{resulcodes: 00 successful; FF IPX not initialized }
end;

{************** Secondary Procedures ***************************************}

Procedure IPXSetupListenECB(ESRptr:Pointer;ReceiveSocket:word;
                            BufPtr:Pointer;BufSize:word;
                     {out:} Var IpxHdr:TipxHeader; Var ecb:Tecb);
{ Clears IPXheader and ECB, sets values of the required fields within
  the ecb and IPX header. }
{ ECB: ESR adress field, socket number, fragment count, frag.descriptor fields }
begin
FillChar(ecb,SizeOf(Tecb),#0);
FillChar(ipxHdr,SizeOF(TipxHeader),#0);
WITH ECB
 do begin
    if ESRptr<>NIL
     then ESRaddress:=ESRptr;
    Fragmentcount:=2;
    socketNumber:=swap(ReceiveSocket); {hi-lo}

    Fragment[1].Address:=@ipxHdr;
    Fragment[2].Address:=BufPtr;
    Fragment[1].size:=SizeOf(Tipxheader);
    Fragment[2].size:=BufSize;
    end;
end;

Procedure IPXsetupSendECB(ESRptr:pointer; SourceSocket:word;
                          DestAddr:TinterNetworkAddress;
                          BufPtr:pointer; BufSize:word;
                   {out:} Var IpxHdr:TipxHeader; Var ecb:Tecb);
{ Clears IPXheader and ECB, sets values of the required fields within
  the ecb and IPX header. }
Var ImmAddr:TnodeAddress;
    Ticks:word;
begin
fillchar(ipxHdr,SizeOf(TipxHeader),#0);
with ipxhdr
 do begin
    PacketType:=IPX_PACKET_TYPE;
    Move(DestAddr,Destination,10);
    destination.socket:=swap(DestAddr.socket); {hi-lo}
    end;
IPXGetLocalTarget(DestAddr,ImmAddr,Ticks);
fillchar(ecb,sizeOf(ecb),#0);
With ecb
 do begin
    if ESRptr<>NIL
     then ESRaddress:=ESRptr;
    socketNumber:=swap(SourceSocket); {hi-lo}
    Move(ImmAddr,ImmediateAddress,6);
    FragmentCount:=2;
    fragment[1].Address:=@ipxhdr;
    fragment[1].size:=SizeOf(TipxHeader);
    fragment[2].Address:=BufPtr;
    fragment[2].size:=BufSize;
    end;
end;



end.
