/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    protocol.c

Abstract:

    ipxwan protocol processing

Author:

    Stefan Solomon  02/14/1996

Revision History:


--*/

#include    "precomp.h"
#pragma     hdrstop

PCHAR	    Workstationp = "WORKSTATION";
PCHAR	    NumberedRip = "NUMBERED RIP";
PCHAR	    UnnumberedRip = "UNNUMBERED RIP";
PCHAR	    OnDemand = "ON DEMAND, STATIC ROUTING";


DWORD
GeneratePacket(PACB	    acbp,
	       PUCHAR	    ipxhdrp,
	       UCHAR	    PacketType);

ULONG
GetRole(PUCHAR		hdrp,
	PACB		acbp);

DWORD
StartSlaveTimer(PACB	    acbp);

DWORD
ProcessInformationResponsePacket(PACB	    acbp,
				 PUCHAR     rcvhdrp);

DWORD
MakeTimerRequestPacket(PACB	    acbp,
		       PUCHAR	    rcvhdrp,
		       PUCHAR	    hdrp);

DWORD
MakeTimerResponsePacket(PACB		acbp,
			PUCHAR		rcvhdrp,
			PUCHAR		hdrp);

DWORD
MakeInformationRequestPacket(PACB	    acbp,
			     PUCHAR	    rcvhdrp,
			     PUCHAR	    hdrp);

DWORD
MakeInformationResponsePacket(PACB		acbp,
			      PUCHAR		rcvhdrp,
			      PUCHAR		hdrp);

DWORD
MakeNakPacket(PACB		acbp,
	      PUCHAR		rcvhdrp,
	      PUCHAR		hdrp);

DWORD
SendReXmitPacket(PACB		    acbp,
		 PWORK_ITEM	    wip);

VOID
fillpadding(PUCHAR	    padp,
	    ULONG	    len);

//** AcbFailure **

// after this macro is called, clean-up for this adapter is done as follows:
// ipxcp will delete the route (as in ndiswan route to ipx stack)
// this will trigger an adapter deleted indication which will call StopIpxwanProtocol
// this last call will flush the timer queue
// when all pending work items are freed the adapter gets deleted

#define     AcbFailure(acbp)	    Trace(IPXWAN_TRACE, "IPXWAN Configuration failed for adapter %d\n", (acbp)->AdapterIndex);\
				    (acbp)->OperState = OPER_STATE_DOWN;\
				    IpxcpConfigDone((acbp)->ConnectionId, NULL, NULL, NULL, FALSE);

UCHAR	    allffs[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
UCHAR	    allzeros[] = { 0, 0, 0, 0, 0, 0 };

#define     ERROR_IGNORE_PACKET 	1
#define     ERROR_DISCONNECT		2
#define     ERROR_GENERATE_NAK		3

/*++

Function:	StartIpxwanProtocol

Descr:		Called when an adapter is created.
		Starts IPXWAN negotiation on this adapter.

Remark: 	>> called with the adapter lock held <<

--*/

VOID
StartIpxwanProtocol(PACB	acbp)
{
    PWORK_ITEM	    wip;

    Trace(IPXWAN_TRACE, "StartIpxwanProtocol: Entered for adapter # %d\n", acbp->AdapterIndex);

    // initialize the IPXWAN states

    acbp->OperState = OPER_STATE_UP;
    acbp->AcbLevel = ACB_TIMER_LEVEL;
    acbp->AcbRole = ACB_UNKNOWN_ROLE;

    // initialize the IPXWAN database

    acbp->InterfaceType = IpxcpGetInterfaceType(acbp->ConnectionId);
    if((acbp->InterfaceType == IF_TYPE_STANDALONE_WORKSTATION_DIALOUT) ||
       (acbp->InterfaceType == IF_TYPE_ROUTER_WORKSTATION_DIALOUT)) {

	memset(acbp->InternalNetNumber, 0, 4);
    }
    else
    {
	IpxcpGetInternalNetNumber(acbp->InternalNetNumber);
    }

    acbp->IsExtendedNodeId = FALSE;
    acbp->SupportedRoutingTypes = 0;

    // set the routing type and node id to be sent in timer request
    switch(acbp->InterfaceType) {

	case IF_TYPE_WAN_ROUTER:
	case IF_TYPE_PERSONAL_WAN_ROUTER:

	    if(EnableUnnumberedWanLinks) {

		memset(acbp->WNodeId, 0, 4);
		acbp->IsExtendedNodeId = TRUE;
		memcpy(acbp->ExtendedWNodeId, acbp->InternalNetNumber, 4);

		SET_UNNUMBERED_RIP(acbp->SupportedRoutingTypes);
	    }
	    else
	    {
		memcpy(acbp->WNodeId, acbp->InternalNetNumber, 4);
		SET_NUMBERED_RIP(acbp->SupportedRoutingTypes);
	    }

	    break;

	case IF_TYPE_WAN_WORKSTATION:

	    memcpy(acbp->WNodeId, acbp->InternalNetNumber, 4);
	    SET_WORKSTATION(acbp->SupportedRoutingTypes);
	    SET_NUMBERED_RIP(acbp->SupportedRoutingTypes);
	    break;

	case IF_TYPE_ROUTER_WORKSTATION_DIALOUT:
	case IF_TYPE_STANDALONE_WORKSTATION_DIALOUT:

	    memset(acbp->WNodeId, 0, 4);
	    SET_WORKSTATION(acbp->SupportedRoutingTypes);

	    break;

	default:

	    Trace(IPXWAN_TRACE, "StartIpxwanProtocol: adpt# %d, Invalid interface type, DISCONNECT",
		  acbp->AdapterIndex);
	    SS_ASSERT(FALSE);

	    AcbFailure(acbp);
	    break;
    }

    // init negotiated values

    acbp->RoutingType = 0;
    memset(acbp->Network, 0, 4);
    memset(acbp->LocalNode, 0, 6);
    memset(acbp->RemoteNode, 0, 6);

    // no net number allocated yet
    acbp->AllocatedNetworkIndex = INVALID_NETWORK_INDEX;

    if(GeneratePacket(acbp, NULL, TIMER_REQUEST) != NO_ERROR) {

	Trace(IPXWAN_TRACE, "StartIpxwanProtocol: adpt# %d, ERROR: cannot generate TIMER_REQUEST, DISCONNECT\n",
	      acbp->AdapterIndex);

	AcbFailure(acbp);
    }
    else
    {
	Trace(IPXWAN_TRACE, "StartIpxwanProtocol: adpt# %d, Sent TIMER_REQUEST\n",
	      acbp->AdapterIndex);
    }
}

/*++

Function:	StopIpxwanProtocol

Descr:		Called when an adapter is deleted.
		Stops IPXWAN negotiation if still going.

Remark: 	>> called with the adapter lock held <<

--*/

VOID
StopIpxwanProtocol(PACB 	acbp)
{
    Trace(IPXWAN_TRACE, "StopIpxwanProtocol: Entered for adapter # %d\n", acbp->AdapterIndex);

    acbp->OperState = OPER_STATE_DOWN;

    // remove all work items referencing this acb from the timer queue
    StopWiTimer(acbp);

    // free allocated wan net if any
    if(acbp->AllocatedNetworkIndex != INVALID_NETWORK_INDEX) {

	IpxcpReleaseWanNetNumber(acbp->AllocatedNetworkIndex);
    }
}

/*++

Function:   IpxwanConfigDone

Descr:	    remove items referencing this acb from the timer queue
	    sets the new configured values in the ipx stack

Remark:     >> called with the adapter lock held <<

--*/

VOID
IpxwanConfigDone(PACB	    acbp)
{
    DWORD	    rc;
    IPXWAN_INFO     IpxwanInfo;

    StopWiTimer(acbp);

    memcpy(IpxwanInfo.Network, acbp->Network, 4);
    memcpy(IpxwanInfo.LocalNode, acbp->LocalNode, 6);
    memcpy(IpxwanInfo.RemoteNode, acbp->RemoteNode, 6);

    rc = IpxWanSetAdapterConfiguration(acbp->AdapterIndex,
				       &IpxwanInfo);

    if(rc != NO_ERROR) {

	Trace(IPXWAN_TRACE, "IpxwanConfigDone: Error %d in IpxWanSetAdapterConfiguration\n",
	      rc);
	AcbFailure(acbp);
	SS_ASSERT(FALSE);
    }
    else
    {
	IpxcpConfigDone(acbp->ConnectionId,
			acbp->Network,
			acbp->LocalNode,
			acbp->RemoteNode,
			TRUE);

	Trace(IPXWAN_TRACE,"\n*** IPXWAN final configuration ***");
	Trace(IPXWAN_TRACE,"    Network:     %.2x%.2x%.2x%.2x\n",
		   acbp->Network[0],
		   acbp->Network[1],
		   acbp->Network[2],
		   acbp->Network[3]);

	Trace(IPXWAN_TRACE,"    LocalNode:   %.2x%.2x%.2x%.2x%.2x%.2x",
		   acbp->LocalNode[0],
		   acbp->LocalNode[1],
		   acbp->LocalNode[2],
		   acbp->LocalNode[3],
		   acbp->LocalNode[4],
		   acbp->LocalNode[5]);

	Trace(IPXWAN_TRACE,"    RemoteNode:  %.2x%.2x%.2x%.2x%.2x%.2x",
		   acbp->RemoteNode[0],
		   acbp->RemoteNode[1],
		   acbp->RemoteNode[2],
		   acbp->RemoteNode[3],
		   acbp->RemoteNode[4],
		   acbp->RemoteNode[5]);
    }
}

/*++

Function:	ProcessReceivedPacket

Descr:

Remark: 	>> called with the adapter lock held <<

--*/

VOID
ProcessReceivedPacket(PACB		acbp,
		      PWORK_ITEM	wip)
{
    PUCHAR	    ipxhdrp;	  // ipx header
    PUCHAR	    wanhdrp;	  // ipx wan header
    PUCHAR	    opthdrp;	  // option header
    DWORD	    rc = NO_ERROR;
    USHORT	    pktlen;
    ULONG	    role;
    USHORT	    rcvsocket;
    PCHAR	    Slavep = "SLAVE";
    PCHAR	    Masterp = "MASTER";

    if(acbp->OperState == OPER_STATE_DOWN) {

	return;
    }

    // validate packet
    ipxhdrp = wip->Packet;

    // check the packet length
    GETSHORT2USHORT(&pktlen, ipxhdrp + IPXH_LENGTH);

    if(pktlen > MAX_IPXWAN_PACKET_LEN) {

	// bad length packet
	Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Reject packet because of invalid length %d\n", pktlen);
	return;
    }

    // check remote socket and confidence id
    GETSHORT2USHORT(&rcvsocket, ipxhdrp + IPXH_SRCSOCK);
    if(rcvsocket != IPXWAN_SOCKET) {

	Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Reject packet because of invalid socket %x\n", rcvsocket);
	return;
    }

    wanhdrp = ipxhdrp + IPXH_HDRSIZE;

    if(memcmp(wanhdrp + WIDENTIFIER,
	      IPXWAN_CONFIDENCE_ID,
	      4)) {

	// no confidence
	Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Reject packet because of invalid confidence id\n");
	return;
    }

    switch(*(wanhdrp + WPACKET_TYPE)) {

	case TIMER_REQUEST:

	    role = GetRole(ipxhdrp, acbp);

	    switch(role) {

		case ACB_SLAVE_ROLE:

		    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd TIMER_REQUEST adpt# %d, local role %s",
			  acbp->AdapterIndex,
			  Slavep);

		    acbp->AcbRole = ACB_SLAVE_ROLE;
		    acbp->RoutingType = 0;

		    if(acbp->AcbLevel != ACB_TIMER_LEVEL) {

			acbp->AcbLevel = ACB_TIMER_LEVEL;
		    }

		    rc = GeneratePacket(acbp, ipxhdrp, TIMER_RESPONSE);

		    switch(rc) {

			case NO_ERROR:

			    acbp->AcbLevel = ACB_INFO_LEVEL;

			    // start the slave timeout
			    if(StartSlaveTimer(acbp) != NO_ERROR) {

				Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT adpt# %d: cannot start slave timer",
				      acbp->AdapterIndex);
				AcbFailure(acbp);
			    }

			    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: TIMER_RESPONSE sent OK on adpt # %d",
				  acbp->AdapterIndex);

			    break;

			case ERROR_DISCONNECT:

			    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT: Error generating TIMER_RESPONSE on adpt# %d",
				  acbp->AdapterIndex);
			    AcbFailure(acbp);
			    break;

			case ERROR_IGNORE_PACKET:
			default:

			    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Ignore received TIMER_REQUEST on adpt# %d",
				  acbp->AdapterIndex);
			    break;
		    }

		case ACB_MASTER_ROLE:

		    if(acbp->AcbLevel != ACB_TIMER_LEVEL) {

			// ignore
			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: ignore TIMER_REQUEST on adpt# %d because not at TIMER LEVEL",
			      acbp->AdapterIndex);
			return;
		    }
		    else
		    {
			 acbp->AcbRole = ACB_MASTER_ROLE;
			 Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd TIMER_REQUEST adpt# %d, local role %s",
			       acbp->AdapterIndex,
			       Masterp);
		    }

		    break;

		default:

		    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT adpt# %d: Unknown role with rcvd TIMER_REQUEST",
			  acbp->AdapterIndex);
		    AcbFailure(acbp);
	    }

	    break;

	case TIMER_RESPONSE:

	    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd TIMER_RESPONSE on adpt# %d",
		  acbp->AdapterIndex);

	    // validate
	    if((acbp->AcbRole == ACB_SLAVE_ROLE) ||
	       !(acbp->AcbLevel == ACB_TIMER_LEVEL)) {

		Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd TIMER_RESPONSE, DISCONNECT adpt# %d: role not MASTER or state not TIMER LEVEL",
		      acbp->AdapterIndex);
		AcbFailure(acbp);
	    }
	    else if(*(wanhdrp + WSEQUENCE_NUMBER) == acbp->ReXmitSeqNo) {

		// rfc 1634 - link delay calculation
		acbp->LinkDelay = (USHORT)((GetTickCount() - acbp->TReqTimeStamp) * 6);

		rc = GeneratePacket(acbp, ipxhdrp, INFORMATION_REQUEST);

		switch(rc) {

		    case NO_ERROR:

			acbp->AcbLevel = ACB_INFO_LEVEL;
			acbp->AcbRole = ACB_MASTER_ROLE;

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: INFORMATION_REQUEST sent OK on adpt # %d",
			      acbp->AdapterIndex);

			break;

		    case ERROR_DISCONNECT:

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT adpt# %d: Error generating INFORMATION_REQUEST",
			      acbp->AdapterIndex);
			AcbFailure(acbp);
			break;

		    case ERROR_IGNORE_PACKET:
		    default:

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Ignore received TIMER_RESPONSE on adpt# %d",
			      acbp->AdapterIndex);

			break;
		}
	    }
	    else
	    {
		Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Ignore TIMER RESPONSE and adpt# %d, non-matching seq no",
		      acbp->AdapterIndex);
	    }

	    break;

	case INFORMATION_REQUEST:

	    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd INFORMATION_REQUEST on adpt# %d",
		  acbp->AdapterIndex);

	    if((acbp->AcbLevel == ACB_INFO_LEVEL) && (acbp->AcbRole == ACB_SLAVE_ROLE)) {

		rc = GeneratePacket(acbp, ipxhdrp, INFORMATION_RESPONSE);

		switch(rc) {

		    case NO_ERROR:

			acbp->AcbLevel = ACB_CONFIGURED_LEVEL;

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: INFORMATION_RESPONSE sent OK on adpt # %d",
			      acbp->AdapterIndex);

			IpxwanConfigDone(acbp);

			// stop the slave timer
			StopWiTimer(acbp);

			break;

		    case ERROR_DISCONNECT:

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT adpt# %d: Error processing rcvd INFORMATION_REQUEST",
			      acbp->AdapterIndex);

			AcbFailure(acbp);
			break;

		    case ERROR_IGNORE_PACKET:
		    default:

			Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Ignore rcvd INFORMATION_REQUEST on adpt# %d",
			      acbp->AdapterIndex);
			break;
		}
	    }
	    else
	    {
		Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT on rcvd INFORMATION_REQUEST on adpt# %d\nState not INFO LEVEL or Role not SLAVE\n",
		      acbp->AdapterIndex);
		AcbFailure(acbp);
	    }

	    break;

	case INFORMATION_RESPONSE:

	    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd INFORMATION_RESPONSE on adpt# %d",
		  acbp->AdapterIndex);

	    if((acbp->AcbLevel == ACB_INFO_LEVEL) && (acbp->AcbRole == ACB_MASTER_ROLE)) {

		if(*(wanhdrp + WSEQUENCE_NUMBER) == acbp->ReXmitSeqNo) {

		    rc = ProcessInformationResponsePacket(acbp, wip->Packet);

		    switch(rc) {

			case NO_ERROR:

			    acbp->AcbLevel = ACB_CONFIGURED_LEVEL;
			    IpxwanConfigDone(acbp);
			    break;

			case ERROR_DISCONNECT:

			    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT adpt# %d: Error processing rcvd INFORMATION_RESPONSE",
				  acbp->AdapterIndex);

			    AcbFailure(acbp);
			    break;

			case ERROR_IGNORE_PACKET:
			default:

			    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Ignore rcvd INFORMATION_RESPONSE on adpt# %d",
				  acbp->AdapterIndex);

			    break;
		    }
		}
	    }
	    else
	    {
		Trace(IPXWAN_TRACE, "ProcessReceivedPacket: DISCONNECT on rcvd INFORMATION_RESPONSE on adpt# %d\nState not INFO LEVEL or Role not MASTER\n",
		      acbp->AdapterIndex);

		AcbFailure(acbp);
	    }

	    break;

	case NAK:

	    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd NAK on adpt# %d, DISCONNECT\n",
		  acbp->AdapterIndex);

	    AcbFailure(acbp);
	    break;

	default:

	    Trace(IPXWAN_TRACE, "ProcessReceivedPacket: Rcvd unknown packet on adpt# %d, IGNORE\n",
		  acbp->AdapterIndex);

	    break;
    }
}

/*++

Function:	ProcessReXmitPacket

Descr:

Remark: 	>> called with the adapter lock held <<

--*/

VOID
ProcessReXmitPacket(PWORK_ITEM		wip)
{
    PACB	acbp;
    UCHAR	WPacketType;
    DWORD	rc;
    PCHAR	PacketTypep;

    acbp = wip->acbp;

    if(acbp->OperState != OPER_STATE_UP) {

	FreeWorkItem(wip);
	return;
    }

    WPacketType = *(wip->Packet + IPXH_HDRSIZE + WPACKET_TYPE);

    if(!((acbp->AcbLevel == ACB_TIMER_LEVEL) && (WPacketType == TIMER_REQUEST)) &&
       !((acbp->AcbLevel == ACB_INFO_LEVEL) && (WPacketType == INFORMATION_REQUEST))) {

	FreeWorkItem(wip);
	return;
    }

    switch(wip->WiState) {

	case WI_SEND_COMPLETED:

	    StartWiTimer(wip, REXMIT_TIMEOUT);
	    acbp->RefCount++;
	    break;

	case WI_TIMEOUT_COMPLETED:

	    switch(WPacketType) {

		case TIMER_REQUEST:

		    PacketTypep = "TIMER_REQUEST";
		    break;

		case INFORMATION_REQUEST:
		default:

		    PacketTypep = "INFORMATION_REQUEST";
		    break;
	    }

	    if(acbp->ReXmitCount) {

		Trace(IPXWAN_TRACE, "ProcessReXmitPacket: Re-send %s on adpt# %d\n",
		      PacketTypep,
		      acbp->AdapterIndex);

		if(SendReXmitPacket(acbp, wip) != NO_ERROR) {

		    Trace(IPXWAN_TRACE, "ProcessReXmitPacket: failed to send on adpt# %d, DISCONNECT\n",
			  acbp->AdapterIndex);

		    AcbFailure(acbp);
		}
	    }
	    else
	    {
		Trace(IPXWAN_TRACE, "ProcessReXmitPacket: Exhausted retry limit for sending %s on adpt# %d, DISCONNECT\n",
		      PacketTypep,
		      acbp->AdapterIndex);

		AcbFailure(acbp);
	    }

	    break;

	default:

	    SS_ASSERT(FALSE);
	    break;
    }
}


/*++

Function:	ProcessTimeout

Descr:

Remark: 	>> called with the adapter lock held <<

--*/

VOID
ProcessTimeout(PWORK_ITEM      wip)
{
    PACB	acbp;
    UCHAR	WPacketType;
    DWORD	rc;

    acbp = wip->acbp;

    FreeWorkItem(wip);

    if(acbp->OperState != OPER_STATE_UP) {

	return;
    }

    if((acbp->AcbRole == ACB_SLAVE_ROLE) && (acbp->AcbLevel != ACB_CONFIGURED_LEVEL)) {

	AcbFailure(acbp);
    }
}

/*++

Function:	SendReXmitPacket

Descr:		adjusts the rexmit count and seq no and sends the packet

Remark: 	>> called with adapter lock held <<

--*/

DWORD
SendReXmitPacket(PACB		    acbp,
		 PWORK_ITEM	    wip)
{
    DWORD	rc;

    // set the wi rexmit fields
    acbp->ReXmitCount--;
    acbp->ReXmitSeqNo++;
    *(wip->Packet + IPXH_HDRSIZE + WSEQUENCE_NUMBER) = acbp->ReXmitSeqNo;
    rc = SendSubmit(wip);

    if(rc == NO_ERROR) {

	acbp->RefCount++;
    }

    acbp->TReqTimeStamp = GetTickCount();

    return rc;
}

/*++

Function:	GeneratePacket

Descr:		allocate the work item,
		constructs the response packet to the received packet (if any)
		send the response as a rexmit packet or as a one time send packet

Returns:	NO_ERROR
		ERROR_IGNORE_PACKET - ignore the received packet
		ERROR_DISCONNECT - disconnect the adapter because of fatal error

Remark: 	>> called with the adapter lock held <<

--*/

DWORD
GeneratePacket(PACB	    acbp,
	       PUCHAR	    ipxhdrp,
	       UCHAR	    PacketType)
{
    DWORD	rc;
    ULONG	WiType;
    PWORK_ITEM	wip;

    if((wip = AllocateWorkItem(SEND_PACKET_TYPE)) == NULL) {

	return ERROR_DISCONNECT;
    }

    switch(PacketType) {

	case TIMER_REQUEST:

	    rc = MakeTimerRequestPacket(acbp, ipxhdrp, wip->Packet);
	    break;

	case TIMER_RESPONSE:

	    rc = MakeTimerResponsePacket(acbp, ipxhdrp, wip->Packet);
	    break;

	case INFORMATION_REQUEST:

	    rc = MakeInformationRequestPacket(acbp, ipxhdrp, wip->Packet);
	    break;

	case INFORMATION_RESPONSE:

	    rc = MakeInformationResponsePacket(acbp, ipxhdrp, wip->Packet);
	    break;

	default:

	    rc = ERROR_DISCONNECT;
	    break;
    }

    if(rc == NO_ERROR) {

	// no error making the packet -> try to send it
	wip->AdapterIndex = acbp->AdapterIndex;
	wip->WiState = WI_INIT;

	switch(PacketType) {

	    case TIMER_REQUEST:
	    case INFORMATION_REQUEST:

		// re-xmit packet type
		wip->ReXmitPacket = TRUE;

		// create a reference to the adapter CB
		wip->acbp = acbp;

		acbp->ReXmitCount = MAX_REXMIT_COUNT;
		acbp->ReXmitSeqNo = 0xFF;

		if(SendReXmitPacket(acbp, wip) != NO_ERROR) {

		    rc = ERROR_DISCONNECT;
		}

		break;

	    case TIMER_RESPONSE:
	    case INFORMATION_RESPONSE:
	    default:

		// one time send
		wip->ReXmitPacket = FALSE;

		if(SendSubmit(wip) != NO_ERROR) {

		    rc = ERROR_DISCONNECT;
		}

		break;
	}
    }

    if(rc != NO_ERROR) {

	// error making or trying to send the packet
	if(rc != ERROR_GENERATE_NAK) {

	    FreeWorkItem(wip);
	}
	else
	{
	    // if we were requested to generate a NAK packet instead, try to it it
	    MakeNakPacket(acbp, ipxhdrp, wip->Packet);

	    wip->ReXmitPacket = FALSE;

	    if(SendSubmit(wip) != NO_ERROR) {

		FreeWorkItem(wip);
		rc = ERROR_DISCONNECT;
	    }
	    else
	    {
		rc = ERROR_IGNORE_PACKET;
	    }
	}
    }

    return rc;
}

ULONG
GetRole(PUCHAR		hdrp,
	PACB		acbp)
{
    ULONG	RemoteWNodeId;
    ULONG	LocalWNodeId;
    PUCHAR	ipxwanhdrp = hdrp + IPXH_HDRSIZE;
    PUCHAR	optp;
    USHORT	optlen;
    BOOL	IsRemoteExtendedNodeId = FALSE;
    ULONG	RemoteExtendedWNodeId;
    ULONG	LocalExtendedWNodeId;
    ULONG	i;

    GETLONG2ULONG(&LocalWNodeId, acbp->WNodeId);
    GETLONG2ULONG(&RemoteWNodeId,  ipxwanhdrp + WNODE_ID);

    if((LocalWNodeId == 0) && (RemoteWNodeId == 0)) {

	// check if received timer request has the extended node id option
	for(optp = ipxwanhdrp + IPXWAN_HDRSIZE, i=0;
	    i < *(ipxwanhdrp + WNUM_OPTIONS);
	    i++)
	{
	    if(*(optp + WOPTION_NUMBER) == EXTENDED_NODE_ID_OPTION) {

		IsRemoteExtendedNodeId = TRUE;
		GETLONG2ULONG(&RemoteExtendedWNodeId, optp + WOPTION_DATA);
		break;
	    }

	    GETSHORT2USHORT(&optlen, optp + WOPTION_DATA_LEN);
	    optp += OPTION_HDRSIZE + optlen;
	}

	if(acbp->IsExtendedNodeId && IsRemoteExtendedNodeId) {

	    GETLONG2ULONG(&LocalExtendedWNodeId, acbp->ExtendedWNodeId);
	    if(LocalExtendedWNodeId > RemoteExtendedWNodeId) {

		return ACB_MASTER_ROLE;
	    }
	    else if(LocalExtendedWNodeId < RemoteExtendedWNodeId) {

		return ACB_SLAVE_ROLE;
	    }
	    else
	    {
		return ACB_UNKNOWN_ROLE;
	    }
	}
	else if(acbp->IsExtendedNodeId)  {

	    return ACB_MASTER_ROLE;
	}
	else if(IsRemoteExtendedNodeId) {

	    return ACB_SLAVE_ROLE;
	}
	else
	{
	    return ACB_UNKNOWN_ROLE;
	}
    }
    else if(LocalWNodeId > RemoteWNodeId) {

	return ACB_MASTER_ROLE;
    }
    else if(LocalWNodeId < RemoteWNodeId) {

	return ACB_SLAVE_ROLE;
    }
    else
    {
	return ACB_UNKNOWN_ROLE;
    }
}



/*++

Function:	MakeTimerRequestPacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		hdrp	    - ptr to the new packet to be made

--*/

DWORD
MakeTimerRequestPacket(PACB	    acbp,
		       PUCHAR	    rcvhdrp,
		       PUCHAR	    hdrp)
{
    PUCHAR	ipxwanhdrp;
    PUCHAR	optp;
    USHORT	padlen = TIMER_REQUEST_PACKET_LENGTH;

    // set IPX Header
    memcpy(hdrp + IPXH_CHECKSUM, allffs, 2);
    PUTUSHORT2SHORT(hdrp + IPXH_LENGTH, TIMER_REQUEST_PACKET_LENGTH);
    *(hdrp + IPXH_XPORTCTL) = 0;
    *(hdrp + IPXH_PKTTYPE) = IPX_PACKET_EXCHANGE_TYPE;
    memcpy(hdrp + IPXH_DESTNET, allzeros, 4);
    memcpy(hdrp + IPXH_DESTNODE, allffs, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);
    memcpy(hdrp + IPXH_SRCNET, allzeros, 4);
    memcpy(hdrp + IPXH_SRCNODE, allzeros, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);

    // set IPXWAN Header
    ipxwanhdrp = hdrp + IPXH_HDRSIZE;

    memcpy(ipxwanhdrp + WIDENTIFIER, IPXWAN_CONFIDENCE_ID, 4);
    *(ipxwanhdrp + WPACKET_TYPE) = TIMER_REQUEST;
    memcpy(ipxwanhdrp + WNODE_ID, acbp->WNodeId, 4);
    // the sequence number is written when the packet gets sent
    *(ipxwanhdrp + WNUM_OPTIONS) = 0;

    padlen -= (IPXH_HDRSIZE + IPXWAN_HDRSIZE);

    // set OPTIONS
    optp = ipxwanhdrp + IPXWAN_HDRSIZE;

    if(IS_WORKSTATION(acbp->SupportedRoutingTypes)) {

	(*(ipxwanhdrp + WNUM_OPTIONS))++;
	*(optp + WOPTION_NUMBER) = ROUTING_TYPE_OPTION;
	*(optp + WACCEPT_OPTION) = YES;
	PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, ROUTING_TYPE_DATA_LEN);
	*(optp + WOPTION_DATA) = WORKSTATION_ROUTING_TYPE;

	optp += OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN;
	padlen -= (OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN);
    }
    if(IS_NUMBERED_RIP(acbp->SupportedRoutingTypes)) {

	(*(ipxwanhdrp + WNUM_OPTIONS))++;
	*(optp + WOPTION_NUMBER) = ROUTING_TYPE_OPTION;
	*(optp + WACCEPT_OPTION) = YES;
	PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, ROUTING_TYPE_DATA_LEN);
	*(optp + WOPTION_DATA) = NUMBERED_RIP_ROUTING_TYPE;

	optp += OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN;
	padlen -= (OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN);
    }
    if(IS_UNNUMBERED_RIP(acbp->SupportedRoutingTypes)) {

	(*(ipxwanhdrp + WNUM_OPTIONS))++;
	*(optp + WOPTION_NUMBER) = ROUTING_TYPE_OPTION;
	*(optp + WACCEPT_OPTION) = YES;
	PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, ROUTING_TYPE_DATA_LEN);
	*(optp + WOPTION_DATA) = UNNUMBERED_RIP_ROUTING_TYPE;

	optp += OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN;
	padlen -= (OPTION_HDRSIZE + ROUTING_TYPE_DATA_LEN);
    }
    if(acbp->IsExtendedNodeId) {

	(*(ipxwanhdrp + WNUM_OPTIONS))++;
	*(optp + WOPTION_NUMBER) = EXTENDED_NODE_ID_OPTION;
	*(optp + WACCEPT_OPTION) = YES;
	PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, EXTENDED_NODE_ID_DATA_LEN);
	memcpy(optp + WOPTION_DATA, acbp->ExtendedWNodeId, EXTENDED_NODE_ID_DATA_LEN);

	optp += OPTION_HDRSIZE + EXTENDED_NODE_ID_DATA_LEN;
	padlen -= (OPTION_HDRSIZE + EXTENDED_NODE_ID_DATA_LEN);
    }

    // PAD
    padlen -= OPTION_HDRSIZE;

    (*(ipxwanhdrp + WNUM_OPTIONS))++;
    *(optp + WOPTION_NUMBER) = PAD_OPTION;
    *(optp + WACCEPT_OPTION) = YES;
    PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, padlen);

    fillpadding(optp + WOPTION_DATA, padlen);

    return NO_ERROR;
}


/*++

Function:	MakeTimerResponsePacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		rcvhdrp	    - ptr to the received TIMER_REQUEST packet
		hdrp	    - ptr to the new packet to be made

--*/

DWORD
MakeTimerResponsePacket(PACB		acbp,
			PUCHAR		rcvhdrp,
			PUCHAR		hdrp)
{
    USHORT	rcvlen;
    USHORT	optlen;
    PUCHAR	ipxwanhdrp;
    PUCHAR	optp;
    ULONG	RemoteWNodeId;
    ULONG	i;

    Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: Entered adapter # %d", acbp->AdapterIndex);

    // check received packet length
    GETSHORT2USHORT(&rcvlen, rcvhdrp + IPXH_LENGTH);

    if(rcvlen < TIMER_REQUEST_PACKET_LENGTH) {

	return ERROR_IGNORE_PACKET;
    }

    memcpy(hdrp, rcvhdrp, rcvlen);

    // set IPX Header
    memcpy(hdrp + IPXH_CHECKSUM, allffs, 2);
    PUTUSHORT2SHORT(hdrp + IPXH_LENGTH, TIMER_REQUEST_PACKET_LENGTH);
    *(hdrp + IPXH_XPORTCTL) = 0;
    *(hdrp + IPXH_PKTTYPE) = IPX_PACKET_EXCHANGE_TYPE;
    memcpy(hdrp + IPXH_DESTNET, allzeros, 4);
    memcpy(hdrp + IPXH_DESTNODE, allffs, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);
    memcpy(hdrp + IPXH_SRCNET, allzeros, 4);
    memcpy(hdrp + IPXH_SRCNODE, allzeros, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);

    // set IPXWAN Header
    ipxwanhdrp = hdrp + IPXH_HDRSIZE;

    *(ipxwanhdrp + WPACKET_TYPE) = TIMER_RESPONSE;
    GETLONG2ULONG(&RemoteWNodeId, ipxwanhdrp + WNODE_ID);
    memcpy(ipxwanhdrp + WNODE_ID, acbp->InternalNetNumber, 4);

    // parse each option in the received timer request packet
    for(optp = ipxwanhdrp + IPXWAN_HDRSIZE, i=0;
	i < *(ipxwanhdrp + WNUM_OPTIONS);
	i++, optp += OPTION_HDRSIZE + optlen)
    {
	GETSHORT2USHORT(&optlen, optp + WOPTION_DATA_LEN);

	switch(*(optp + WOPTION_NUMBER)) {

	    case ROUTING_TYPE_OPTION:

		if(optlen != ROUTING_TYPE_DATA_LEN) {

		    return ERROR_GENERATE_NAK;
		}

		if((*(optp + WOPTION_DATA) == WORKSTATION_ROUTING_TYPE) &&
		   (IS_WORKSTATION(acbp->SupportedRoutingTypes)) &&
		   (acbp->RoutingType == 0) &&
		   (*(optp + WACCEPT_OPTION) == YES)) {

		    SET_WORKSTATION(acbp->RoutingType);
		    Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept routing type: %s",
			  acbp->AdapterIndex,
			  Workstationp);
		}
		else if((*(optp + WOPTION_DATA) == UNNUMBERED_RIP_ROUTING_TYPE) &&
		       (IS_UNNUMBERED_RIP(acbp->SupportedRoutingTypes)) &&
		       (acbp->RoutingType == 0) &&
		       (*(optp + WACCEPT_OPTION) == YES)) {

		    SET_UNNUMBERED_RIP(acbp->RoutingType);
		    Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept routing type: %s",
			  acbp->AdapterIndex,
			  UnnumberedRip);
		}
		else if((*(optp + WOPTION_DATA) == NUMBERED_RIP_ROUTING_TYPE) &&
		       (acbp->RoutingType == 0) &&
		       (*(optp + WACCEPT_OPTION) == YES)) {

			if(IS_NUMBERED_RIP(acbp->SupportedRoutingTypes)) {

			SET_NUMBERED_RIP(acbp->RoutingType);
			Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept routing type: %s",
			      acbp->AdapterIndex,
			      NumberedRip);

			}
			else if((IS_UNNUMBERED_RIP(acbp->SupportedRoutingTypes)) &&
				RemoteWNodeId) {

			    // the local router cannot assign net numbers but it
			    // accepts the numbered rip type because the remote router
			    // claims it can assign a net number (because remote node id is not null).

			    SET_NUMBERED_RIP(acbp->RoutingType);
			    Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept routing type: %s",
				  acbp->AdapterIndex,
				  NumberedRip);
			}
			else
			{
			    *(optp + WACCEPT_OPTION) = NO;
			    Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, decline routing type: %d",
				  acbp->AdapterIndex,
				  *(optp + WOPTION_NUMBER));

			}
		    }
		    else
		    {
			*(optp + WACCEPT_OPTION) = NO;
			Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, decline routing type: %d",
			      acbp->AdapterIndex,
			      *(optp + WOPTION_DATA));
		    }

		break;

	    case EXTENDED_NODE_ID_OPTION:

		if(optlen != EXTENDED_NODE_ID_DATA_LEN) {

		    return ERROR_GENERATE_NAK;
		}

		*(optp + WACCEPT_OPTION) = YES;
		Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept extended node id",
		      acbp->AdapterIndex);

		break;

	    case PAD_OPTION:

		*(optp + WACCEPT_OPTION) = YES;
		Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, accept padding",
		      acbp->AdapterIndex);

		break;

	    default:

		*(optp + WACCEPT_OPTION) = NO;
		Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, decline option number %d",
		      acbp->AdapterIndex,
		      *(optp + WOPTION_NUMBER));

		break;
	}
    }

    // check if we have agreed on a routing type
    if(!acbp->RoutingType) {

	Trace(IPXWAN_TRACE, "MakeTimerResponsePacket: adapter # %d, negotiation failed: no routing type accepted",
	      acbp->AdapterIndex);

	return ERROR_DISCONNECT;
    }

    return NO_ERROR;
}

/*++

Function:	MakeInformationRequestPacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		rcvhdrp     - ptr to the received TIMER_RESPONSE packet
		hdrp	    - ptr to the new packet to be made

--*/

DWORD
MakeInformationRequestPacket(PACB	    acbp,
			     PUCHAR	    rcvhdrp,
			     PUCHAR	    hdrp)
{
    PUCHAR	    optp;
    USHORT	    optlen;
    PUCHAR	    rcvipxwanhdrp, ipxwanhdrp;
    ULONG	    rt_options_count = 0;
    USHORT	    pktlen = 0;
    ULONG	    i;
    ULONG	    ComputerNameLen;
    CHAR	    ComputerName[49];

    memset(ComputerName, 0, 49);

    Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: Entered for adpt# %d", acbp->AdapterIndex);

    rcvipxwanhdrp = rcvhdrp + IPXH_HDRSIZE;

    // establish the routing type
    for(optp = rcvipxwanhdrp + IPXWAN_HDRSIZE, i=0;
	i < *(rcvipxwanhdrp + WNUM_OPTIONS);
	i++, optp += OPTION_HDRSIZE + optlen)
    {
	GETSHORT2USHORT(&optlen, optp + WOPTION_DATA_LEN);

	if(*(optp + WOPTION_NUMBER) == ROUTING_TYPE_OPTION) {

	    rt_options_count++;

	    if(optlen != ROUTING_TYPE_DATA_LEN) {

		Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: Invalid ROUTING TYPE data len, make NAK for adpt# %d", acbp->AdapterIndex);
		return ERROR_GENERATE_NAK;
	    }

	    if((*(optp + WOPTION_DATA) == WORKSTATION_ROUTING_TYPE) &&
		(IS_WORKSTATION(acbp->SupportedRoutingTypes)) &&
		(acbp->RoutingType == 0) &&
		(*(optp + WACCEPT_OPTION) == YES)) {

		SET_WORKSTATION(acbp->RoutingType);
		Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d, accept routing type: %s",
			  acbp->AdapterIndex,
			  Workstationp);

	    }
	    else if((*(optp + WOPTION_DATA) == UNNUMBERED_RIP_ROUTING_TYPE) &&
		     (IS_UNNUMBERED_RIP(acbp->SupportedRoutingTypes)) &&
		     (acbp->RoutingType == 0) &&
		     (*(optp + WACCEPT_OPTION) == YES)) {

		SET_UNNUMBERED_RIP(acbp->RoutingType);
		Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d, accept routing type: %s",
		      acbp->AdapterIndex,
		      UnnumberedRip);

	    }
	    else if((*(optp + WOPTION_DATA) == NUMBERED_RIP_ROUTING_TYPE) &&
		     (acbp->RoutingType == 0) &&
		     (IS_NUMBERED_RIP(acbp->SupportedRoutingTypes)) &&
		     (*(optp + WACCEPT_OPTION) == YES)) {

		 SET_NUMBERED_RIP(acbp->RoutingType);
		 Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d, accept routing type: %s",
		       acbp->AdapterIndex,
		       NumberedRip);
	    }
	}
    }

    // there should be one and only one routing type option in the timer response
    if(rt_options_count != 1) {

	Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d negotiation failed, no/too many routing options",
	      acbp->AdapterIndex);
	return ERROR_DISCONNECT;
    }

    //
    //*** MASTER: Set the common network number and the local node number ***
    //

    if(IS_UNNUMBERED_RIP(acbp->RoutingType)) {

	memset(acbp->Network, 0, 4);
    }
    else
    {
	// call ipxcp to get a net number
	if(IpxcpGetWanNetNumber(acbp->Network,
			   &acbp->AllocatedNetworkIndex,
			   acbp->InterfaceType) != NO_ERROR) {

	    Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d negotiation failed, cannot allocate net number",
	      acbp->AdapterIndex);

	    return ERROR_DISCONNECT;
	}
    }

    memset(acbp->LocalNode, 0, 6);
    memcpy(acbp->LocalNode, acbp->InternalNetNumber, 4);

    // set IPX Header
    pktlen = IPXH_HDRSIZE + IPXWAN_HDRSIZE + OPTION_HDRSIZE + RIP_SAP_INFO_EXCHANGE_DATA_LEN;

    memcpy(hdrp + IPXH_CHECKSUM, allffs, 2);
    *(hdrp + IPXH_XPORTCTL) = 0;
    *(hdrp + IPXH_PKTTYPE) = IPX_PACKET_EXCHANGE_TYPE;
    memcpy(hdrp + IPXH_DESTNET, allzeros, 4);
    memcpy(hdrp + IPXH_DESTNODE, allffs, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);
    memcpy(hdrp + IPXH_SRCNET, allzeros, 4);
    memcpy(hdrp + IPXH_SRCNODE, allzeros, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);

    // set IPXWAN Header
    ipxwanhdrp = hdrp + IPXH_HDRSIZE;
    memcpy(ipxwanhdrp + WIDENTIFIER, IPXWAN_CONFIDENCE_ID, 4);
    *(ipxwanhdrp + WPACKET_TYPE) = INFORMATION_REQUEST;
    memcpy(ipxwanhdrp + WNODE_ID, acbp->InternalNetNumber, 4);
    // the sequence number is written when the packet gets sent
    *(ipxwanhdrp + WNUM_OPTIONS) = 1;

    // set OPTIONS
    optp = ipxwanhdrp + IPXWAN_HDRSIZE;

    *(optp + WOPTION_NUMBER) = RIP_SAP_INFO_EXCHANGE_OPTION;
    *(optp + WACCEPT_OPTION) = YES;
    PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, RIP_SAP_INFO_EXCHANGE_DATA_LEN);

    PUTUSHORT2SHORT(optp + WAN_LINK_DELAY, acbp->LinkDelay);
    memcpy(optp + COMMON_NETWORK_NUMBER, acbp->Network, 4);

    memset(optp + ROUTER_NAME, 0, 48);

    ComputerNameLen = 48;

    if(!GetComputerName(optp + ROUTER_NAME,
			&ComputerNameLen)) {

	// failed to get machine name
	return ERROR_DISCONNECT;
    }

    memcpy(ComputerName, optp + ROUTER_NAME, ComputerNameLen);
    Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d, Delay %d\nCommon Net %.2x%.2x%.2x%.2x\nRouterName: %s\n",
	  acbp->AdapterIndex,
	  acbp->LinkDelay,
	  acbp->Network[0],
	  acbp->Network[1],
	  acbp->Network[2],
	  acbp->Network[3],
	  ComputerName);

    //
    //*** MASTER: Set the remote node number ***
    //
    if(acbp->InterfaceType == IF_TYPE_WAN_WORKSTATION) {

	// if the remote machine is a connecting wksta we should provide it with a node
	// number
	pktlen += OPTION_HDRSIZE + NODE_NUMBER_DATA_LEN;
	(*(ipxwanhdrp + WNUM_OPTIONS))++;

	optp += OPTION_HDRSIZE + RIP_SAP_INFO_EXCHANGE_DATA_LEN;

	*(optp + WOPTION_NUMBER) = NODE_NUMBER_OPTION;
	*(optp + WACCEPT_OPTION) = YES;
	PUTUSHORT2SHORT(optp + WOPTION_DATA_LEN, NODE_NUMBER_DATA_LEN);

	if(IpxcpGetRemoteNode(acbp->ConnectionId, optp + WOPTION_DATA) != NO_ERROR) {

	    return ERROR_DISCONNECT;
	}

	memcpy(acbp->RemoteNode, optp + WOPTION_DATA, 6);

	Trace(IPXWAN_TRACE, "MakeInformationRequestPacket: adpt# %d add NIC Address Option: %.2x%.2x%.2x%.2x%.2x%.2x\n",
		   acbp->RemoteNode[0],
		   acbp->RemoteNode[1],
		   acbp->RemoteNode[2],
		   acbp->RemoteNode[3],
		   acbp->RemoteNode[4],
		   acbp->RemoteNode[5]);

    }
    else
    {
	// remote machine is a router -> its node number is derived from its internal net
	memset(acbp->RemoteNode, 0, 6);
	memcpy(acbp->RemoteNode, rcvipxwanhdrp + WNODE_ID, 4);
    }

    PUTUSHORT2SHORT(hdrp + IPXH_LENGTH, pktlen);

    return NO_ERROR;
}

/*++

Function:	MakeInformationResponsePacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		rcvhdrp     - ptr to the received INFORMATION_REQUEST packet
		hdrp	    - ptr to the new packet to be made

--*/


DWORD
MakeInformationResponsePacket(PACB		acbp,
			      PUCHAR		rcvhdrp,
			      PUCHAR		hdrp)
{
    USHORT	rcvlen;
    USHORT	optlen;
    PUCHAR	ipxwanhdrp;
    PUCHAR	optp;
    UCHAR	RcvWNodeId[4];
    ULONG	RipSapExchangeOptionCount = 0;
    ULONG	NodeNumberOptionCount = 0;
    UCHAR	LocalNode[6];
    ULONG	i;
    ULONG	ComputerNameLen=48;

    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: Entered adpt# %d", acbp->AdapterIndex);

    memset(LocalNode, 0, 6);

    // get received packet length
    GETSHORT2USHORT(&rcvlen, rcvhdrp + IPXH_LENGTH);

    if(rcvlen < IPXH_HDRSIZE + IPXWAN_HDRSIZE + OPTION_HDRSIZE + RIP_SAP_INFO_EXCHANGE_DATA_LEN) {

	// malformed packet
	return ERROR_IGNORE_PACKET;
    }

    memcpy(hdrp, rcvhdrp, rcvlen);

    // set IPX Header
    memcpy(hdrp + IPXH_CHECKSUM, allffs, 2);
    *(hdrp + IPXH_XPORTCTL) = 0;
    *(hdrp + IPXH_PKTTYPE) = IPX_PACKET_EXCHANGE_TYPE;
    memcpy(hdrp + IPXH_DESTNET, allzeros, 4);
    memcpy(hdrp + IPXH_DESTNODE, allffs, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);
    memcpy(hdrp + IPXH_SRCNET, allzeros, 4);
    memcpy(hdrp + IPXH_SRCNODE, allzeros, 6);
    PUTUSHORT2SHORT(hdrp + IPXH_DESTSOCK, IPXWAN_SOCKET);

    // set IPXWAN Header
    ipxwanhdrp = hdrp + IPXH_HDRSIZE;

    *(ipxwanhdrp + WPACKET_TYPE) = INFORMATION_RESPONSE;
    memcpy(RcvWNodeId, ipxwanhdrp + WNODE_ID, 4);
    memcpy(ipxwanhdrp + WNODE_ID, acbp->InternalNetNumber, 4);

    // parse each option in the received information request packet
    for(optp = ipxwanhdrp + IPXWAN_HDRSIZE, i=0;
	i < *(ipxwanhdrp + WNUM_OPTIONS);
	i++, optp += OPTION_HDRSIZE + optlen)
    {
	GETSHORT2USHORT(&optlen, optp + WOPTION_DATA_LEN);

	switch(*(optp + WOPTION_NUMBER)) {

	    case RIP_SAP_INFO_EXCHANGE_OPTION:

		if(RipSapExchangeOptionCount++) {

		    // more then one rip/sap exchange option
		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: more then 1 RIP_SAP_EXCHANGE_OPTION in rcvd INFORAMTION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		if(optlen != RIP_SAP_INFO_EXCHANGE_DATA_LEN) {

		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: bad length RIP_SAP_EXCHANGE_OPTION in rcvd INFORAMTION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_GENERATE_NAK;
		}

		if(*(optp + WACCEPT_OPTION) != YES) {

		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: ACCEPT==NO RIP_SAP_EXCHANGE_OPTION in rcvd INFORAMTION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		GETSHORT2USHORT(&acbp->LinkDelay, optp + WAN_LINK_DELAY);

		// validate routing type and common net number
		if((IS_NUMBERED_RIP(acbp->RoutingType)) &&
		   !memcmp(optp + COMMON_NETWORK_NUMBER, allzeros, 4)) {

		    // negotiation error
		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: NUMBERED RIP Routing but Network==0 in rcvd INFORAMTION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		if((IS_UNNUMBERED_RIP(acbp->RoutingType)) &&
		   memcmp(optp + COMMON_NETWORK_NUMBER, allzeros, 4)) {

		    // negotiation error
		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: ON DEMAND Routing but Network!=0 in rcvd INFORAMTION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		// check we were handed a unique net number
		if(memcmp(optp + COMMON_NETWORK_NUMBER, allzeros, 4)) {

		    switch(acbp->InterfaceType) {

			case  IF_TYPE_WAN_ROUTER:
			case  IF_TYPE_PERSONAL_WAN_ROUTER:
			case  IF_TYPE_ROUTER_WORKSTATION_DIALOUT:

			    if(IpxcpIsRoute(optp + COMMON_NETWORK_NUMBER)) {

				Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: Network not unique in rcvd INFORAMTION_REQUEST\n",
				      acbp->AdapterIndex);

				return ERROR_DISCONNECT;
			    }

			    break;

			default:

			    break;
		    }
		}

		//
		//*** SLAVE: Set the common net number and the remote node ***
		//
		memcpy(acbp->Network, optp + COMMON_NETWORK_NUMBER, 4);

		Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, Recvd Common Network Number %.2x%.2x%.2x%.2x\n",
		      acbp->AdapterIndex,
		      acbp->Network[0],
		      acbp->Network[1],
		      acbp->Network[2],
		      acbp->Network[3]);

		// make the remote node number from its remote WNODE ID field
		memset(acbp->RemoteNode, 0, 6);
		memcpy(acbp->RemoteNode, RcvWNodeId, 4);

		// give our router name
		memset(optp + ROUTER_NAME, 0, 48);

		if(!GetComputerName(optp + ROUTER_NAME, &ComputerNameLen)) {

		    // failed to get machine name
		    return ERROR_DISCONNECT;
		}

		break;

	    case NODE_NUMBER_OPTION:

		if(NodeNumberOptionCount++) {

		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: more than 1 NODE_NUMBER_OPTION in rcvd INFORMATION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		if(optlen != NODE_NUMBER_DATA_LEN) {

		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: bad length for NODE_NUMBER_OPTION in rcvd INFORMATION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_GENERATE_NAK;
		}

		if(*(optp + WACCEPT_OPTION) != YES) {

		    Trace(IPXWAN_TRACE, "MakeInformationResponsePacket: adpt# %d, ERROR: ACCEPT==NO for NODE_NUMBER_OPTION in rcvd INFORMATION_REQUEST\n",
		    acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		memcpy(LocalNode, optp + WOPTION_DATA, 6);

		break;

	    default:

		*(optp + WACCEPT_OPTION) = NO;
		break;
	}
    }

    //
    //*** SLAVE: Set local node ***
    //
    if(NodeNumberOptionCount) {

	memcpy(acbp->LocalNode, LocalNode, 6);
    }
    else
    {
	// make the local node from our internal net
	memset(acbp->LocalNode, 0, 6);
	memcpy(acbp->LocalNode, acbp->InternalNetNumber, 4);
    }

    return NO_ERROR;
}

/*++

Function:	MakeNakPacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		rcvhdrp     - ptr to the received UNKNOWN packet
		hdrp	    - ptr to the new packet to be made

--*/

DWORD
MakeNakPacket(PACB		acbp,
	      PUCHAR		rcvhdrp,
	      PUCHAR		hdrp)
{
    USHORT	    rcvlen;
    PUCHAR	    ipxwanhdrp;

    // get received packet length
    GETSHORT2USHORT(&rcvlen, rcvhdrp + IPXH_LENGTH);

    memcpy(hdrp, rcvhdrp, rcvlen);

    // set IPXWAN Header
    ipxwanhdrp = hdrp + IPXH_HDRSIZE;

    *(ipxwanhdrp + WPACKET_TYPE) = NAK;

    return NO_ERROR;
}

/*++

Function:	ProcessInformationResponsePacket

Descr:

Arguments:	acbp	    - ptr to adapter CB
		rcvhdrp     - ptr to the received INFORMATION_RESPONSE packet

--*/

DWORD
ProcessInformationResponsePacket(PACB	    acbp,
				 PUCHAR     rcvhdrp)
{
    USHORT	rcvlen;
    USHORT	optlen;
    PUCHAR	ipxwanhdrp;
    PUCHAR	optp;
    ULONG	RipSapExchangeOptionCount = 0;
    ULONG	i;

    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: Entered adpt# %d", acbp->AdapterIndex);

    // get received packet length
    GETSHORT2USHORT(&rcvlen, rcvhdrp + IPXH_LENGTH);

    if(rcvlen < IPXH_HDRSIZE + IPXWAN_HDRSIZE + OPTION_HDRSIZE + RIP_SAP_INFO_EXCHANGE_DATA_LEN) {

	// malformed packet
	return ERROR_IGNORE_PACKET;
    }

    ipxwanhdrp =rcvhdrp + IPXH_HDRSIZE;

    // parse each option in the received information response packet
    for(optp = ipxwanhdrp + IPXWAN_HDRSIZE, i=0;
	i < *(ipxwanhdrp + WNUM_OPTIONS);
	i++, optp += OPTION_HDRSIZE + optlen)
    {
	GETSHORT2USHORT(&optlen, optp + WOPTION_DATA_LEN);

	switch(*(optp + WOPTION_NUMBER)) {

	    case RIP_SAP_INFO_EXCHANGE_OPTION:

		if(RipSapExchangeOptionCount++) {

		    // more then one rip/sap exchange option
		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: more then 1 RIP_SAP_INFO_EXCHANGE_OPTION in rcvd INFORMATION_RESPONSE\n",
		    acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		if(optlen != RIP_SAP_INFO_EXCHANGE_DATA_LEN) {

		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: bad length RIP_SAP_EXCHANGE_OPTION in rcvd INFORMATION_RESPONSE\n",
			  acbp->AdapterIndex);

		    return ERROR_GENERATE_NAK;
		}

		if(*(optp + WACCEPT_OPTION) != YES) {

		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: ACCEPT==NO RIP_SAP_EXCHANGE_OPTION in rcvd INFORMATION_RESPONSE\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		if(memcmp(optp + COMMON_NETWORK_NUMBER, acbp->Network, 4)) {

		    // we don't agree on the common net number
		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: Different common net returned\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		break;

	    case NODE_NUMBER_OPTION:

		if(optlen != NODE_NUMBER_DATA_LEN) {

		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: bad length NODE_NUMBER_OPTION in rcvd INFORMATION_REQUEST\n",
			  acbp->AdapterIndex);

		    return ERROR_GENERATE_NAK;
		}

		if(*(optp + WACCEPT_OPTION) != YES) {

		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: ACCEPT==NO NODE_NUMBER_OPTION in rcvd INFORMATION_RESPONSE\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		// check that it coincides with the number we assigned
		if(memcmp(optp + WOPTION_DATA, acbp->RemoteNode, 6)) {

		    Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: Different remote node number returned\n",
			  acbp->AdapterIndex);

		    return ERROR_DISCONNECT;
		}

		break;

	    default:

		Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: Unrequested option in rcvd INFORMATION_RESPONSE\n",
		      acbp->AdapterIndex);

		return ERROR_DISCONNECT;
		break;
	}
    }

    if(!RipSapExchangeOptionCount) {

	Trace(IPXWAN_TRACE, "ProcessInformationResponsePacket: adpt# %d, ERROR: RIP_SAP_EXCHANGE_OPTION missing from rcvd INFORMATION_RESPONSE\n",
	      acbp->AdapterIndex);

	return ERROR_DISCONNECT;
    }

    return NO_ERROR;
}


VOID
fillpadding(PUCHAR	    padp,
	    ULONG	    len)
{
    ULONG	i;

    for(i=0; i<len; i++)
    {
	*(padp + i) = (UCHAR)i;
    }
}

/*++

Function:	StartSlaveTimer

Descr:		A timer is started when the slave gets its role (i.e. slave) and sends
		a timer response. This insures the slave won't wait forever to receive
		an information request.

Remark: 	>> called with the adapter lock held <<

--*/

DWORD
StartSlaveTimer(PACB	    acbp)
{
    PWORK_ITEM	    wip;

    if((wip = AllocateWorkItem(WITIMER_TYPE)) == NULL) {

	return ERROR_DISCONNECT;
    }

    wip->acbp = acbp;
    StartWiTimer(wip, SLAVE_TIMEOUT);
    acbp->RefCount++;

    return NO_ERROR;
}