WindowsXP-SP1/ds/nw/rdr/exchange.c

4977 lines
129 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
exchange.c
Abstract:
This module implements the File Create routine for the NetWare
redirector called by the dispatch driver.
Author:
Hans Hurvig [hanshu] Aug-1992 Created
Colin Watson [ColinW] 19-Dec-1992
Revision History:
--*/
#include "procs.h"
#include "tdikrnl.h"
#include <STDARG.H>
#define Dbg (DEBUG_TRACE_EXCHANGE)
//
// Exchange.c Global constants
//
// broadcast to socket 0x0452
TA_IPX_ADDRESS SapBroadcastAddress =
{
1,
sizeof(TA_IPX_ADDRESS), TDI_ADDRESS_TYPE_IPX,
0, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, SAP_SOCKET
};
UCHAR SapPacketType = PACKET_TYPE_SAP;
UCHAR NcpPacketType = PACKET_TYPE_NCP;
extern BOOLEAN WorkerRunning; // From timer.c
ULONG DropCount = 0;
#ifdef NWDBG
int AlwaysAllocateIrp = 1;
#endif
NTSTATUS
CompletionSend(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
);
NTSTATUS
FspGetMessage(
IN PIRP_CONTEXT IrpContext
);
NTSTATUS
CompletionWatchDogSend(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
);
USHORT
NextSocket(
IN USHORT OldValue
);
NTSTATUS
FormatRequest(
PIRP_CONTEXT pIrpC,
PEX pEx,
char* f,
va_list a // format specific parameters
);
VOID
ScheduleReconnectRetry(
PIRP_CONTEXT pIrpContext
);
NTSTATUS
CopyIndicatedData(
PIRP_CONTEXT pIrpContext,
PCHAR RspData,
ULONG BytesIndicated,
PULONG BytesTaken,
ULONG ReceiveDatagramFlags
);
NTSTATUS
AllocateReceiveIrp(
PIRP_CONTEXT pIrpContext,
PVOID ReceiveData,
ULONG BytesAvailable,
PULONG BytesAccepted,
PNW_TDI_STRUCT pTdiStruct
);
NTSTATUS
ReceiveIrpCompletion(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
NTSTATUS
FspProcessServerDown(
PIRP_CONTEXT IrpContext
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, NextSocket )
#pragma alloc_text( PAGE, ExchangeWithWait )
#pragma alloc_text( PAGE, NewRouteRetry )
#ifndef QFE_BUILD
#pragma alloc_text( PAGE1, FspGetMessage )
#pragma alloc_text( PAGE1, Exchange )
#pragma alloc_text( PAGE1, BuildRequestPacket )
#pragma alloc_text( PAGE1, ParseResponse )
#pragma alloc_text( PAGE1, ParseNcpResponse )
#pragma alloc_text( PAGE1, FormatRequest )
#pragma alloc_text( PAGE1, PrepareAndSendPacket )
#pragma alloc_text( PAGE1, PreparePacket )
#pragma alloc_text( PAGE1, SendPacket )
#pragma alloc_text( PAGE1, AppendToScbQueue )
#pragma alloc_text( PAGE1, KickQueue )
#pragma alloc_text( PAGE1, SendNow )
#pragma alloc_text( PAGE1, SetEvent )
#pragma alloc_text( PAGE1, CompletionSend )
#pragma alloc_text( PAGE1, CopyIndicatedData )
#pragma alloc_text( PAGE1, AllocateReceiveIrp )
#pragma alloc_text( PAGE1, ReceiveIrpCompletion )
#pragma alloc_text( PAGE1, VerifyResponse )
#pragma alloc_text( PAGE1, ScheduleReconnectRetry )
#pragma alloc_text( PAGE1, ReconnectRetry )
#pragma alloc_text( PAGE1, NewRouteBurstRetry )
#endif
#endif
#if 0 // Not pageable
ServerDatagramHandler
WatchDogDatagramHandler
SendDatagramHandler
CompletionWatchDogSend
MdlLength
FreeReceiveIrp
FspProcessServerDown
// see ifndef QFE_BUILD above
#endif
NTSTATUS
_cdecl
Exchange(
PIRP_CONTEXT pIrpContext,
PEX pEx,
char* f,
... // format specific parameters
)
/*++
Routine Description:
This routine is a wrapper for _Exchange. See the comment
in _Exchange for routine and argument description.
--*/
{
va_list Arguments;
NTSTATUS Status;
va_start( Arguments, f );
Status = FormatRequest( pIrpContext, pEx, f, Arguments );
if ( !NT_SUCCESS( Status ) ) {
return( Status );
}
//
// We won't be completing this IRP now, so mark it pending.
//
IoMarkIrpPending( pIrpContext->pOriginalIrp );
//
// Start the packet on it's way to the wire.
//
Status = PrepareAndSendPacket( pIrpContext );
return( Status );
}
NTSTATUS
_cdecl
BuildRequestPacket(
PIRP_CONTEXT pIrpContext,
PEX pEx,
char* f,
... // format specific parameters
)
/*++
Routine Description:
This routine is a wrapper for FormatRequest. See the comment
in FormatRequest for routine and argument description.
--*/
{
va_list Arguments;
NTSTATUS Status;
va_start( Arguments, f );
Status = FormatRequest( pIrpContext, pEx, f, Arguments );
if ( !NT_SUCCESS( Status ) ) {
return( Status );
}
return( Status );
}
NTSTATUS
_cdecl
ParseResponse(
PIRP_CONTEXT IrpContext,
PUCHAR Response,
ULONG ResponseLength,
char* FormatString,
... // format specific parameters
)
/*++
Routine Description:
This routine parse an NCP response.
Arguments:
pIrpC - Supplies the irp context for the exchange request. This may
be NULL for generic packet types.
f... - supplies the information needed to create the request to the
server. The first byte indicates the packet type and the
following bytes contain field types.
Packet types:
'B' Burst primary response ( byte * )
'N' NCP response ( void )
'S' Burst secondary response ( byte * )
'G' Generic packet ( )
Field types, request/response:
'b' byte ( byte* )
'w' hi-lo word ( word* )
'x' ordered word ( word* )
'd' hi-lo dword ( dword* )
'e' ordered dword ( dword* )
'-' zero/skip byte ( void )
'=' zero/skip word ( void )
._. zero/skip string ( word )
'p' pstring ( char* )
'p' pstring to Unicode ( UNICODE_STRING * )
'c' cstring ( char* )
'r' raw bytes ( byte*, word )
'R' ASCIIZ to Unicode ( UNICODE_STRING *, word )
Added 3/29/95 by CoryWest:
'W' lo-hi word ( word / word*)
'D' lo-hi dword ( dword / dword*)
'S' unicode string copy as NDS_STRING (UNICODE_STRING *)
'T' terminal unicode string copy as NDS_STRING (UNICODE_STRING *)
't' terminal unicode string with the nds null copied
as NDS_STRING (UNICODE_STRING *) (for GetUseName)
Not in use:
's' cstring copy as NDS_STRING (char* / char *, word)
'V' sized NDS value ( byte **, dword *)
'l' what's this?
Return Value:
STATUS - The converted error code from the NCP response.
--*/
{
PEPresponse *pResponseParameters;
PCHAR FormatByte;
va_list Arguments;
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS NcpStatus;
ULONG Length;
va_start( Arguments, FormatString );
//
// Make sure that we have an IrpContext unless we are doing
// a scan of a generic packet.
//
#ifdef NWDBG
if ( *FormatString != 'G' ) {
ASSERT( IrpContext != NULL );
}
#endif
switch ( *FormatString ) {
//
// NCP response.
//
case 'N':
Length = 8; // The data begins 8 bytes into the packet
pResponseParameters = (PEPresponse *)( ((PEPrequest *)Response) + 1);
//
// If there's a message pending for us on the server and we have
// popups disabled, we won't pick it up, but we should continue
// processing NCPs correctly!
//
if ( ( pResponseParameters->status == 0 ) ||
( pResponseParameters->status == 0x40 ) ) {
Status = NwErrorToNtStatus( pResponseParameters->error );
} else {
Status = NwConnectionStatusToNtStatus( pResponseParameters->status );
if ( Status == STATUS_REMOTE_DISCONNECT ) {
Stats.ServerDisconnects++;
IrpContext->pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
}
}
break;
//
// Burst response, first packet
//
case 'B':
{
PNCP_BURST_HEADER BurstResponse = (PNCP_BURST_HEADER)Response;
byte* b = va_arg ( Arguments, byte* );
ULONG Result;
ULONG Offset = BurstResponse->BurstOffset;
*b = BurstResponse->Flags;
Length = 28; // The data begins 28 bytes into the packet
if ( Offset == 0 ) {
//
// This is the first packet in the burst response. Look
// at the result code.
//
// Note that the result DWORD is in lo-hi order.
//
Result = *(ULONG UNALIGNED *)(Response + 36);
switch ( Result ) {
case 0:
case 3: // No data
break;
case 1:
Status = STATUS_DISK_FULL;
break;
case 2: // I/O error
Status = STATUS_UNEXPECTED_IO_ERROR;
break;
default:
Status = NwErrorToNtStatus( (UCHAR)Result );
break;
}
}
break;
}
#if 0
//
// Burst response, secondary packet
//
case 'S':
{
byte* b = va_arg ( Arguments, byte* );
*b = Response[2];
Length = 28; // The data begins 28 bytes into the packet
break;
}
#endif
case 'G':
Length = 0; // The data begins at the start of the packet
break;
default:
ASSERT( FALSE );
Status = STATUS_UNSUCCESSFUL;
break;
}
//
// If this packet contains an error, simply return the error.
//
if ( !NT_SUCCESS( Status ) ) {
return( Status );
}
NcpStatus = Status;
FormatByte = FormatString + 1;
while ( *FormatByte ) {
switch ( *FormatByte ) {
case '-':
Length += 1;
break;
case '=':
Length += 2;
break;
case '_':
{
word l = va_arg ( Arguments, word );
Length += l;
break;
}
case 'b':
{
byte* b = va_arg ( Arguments, byte* );
*b = Response[Length++];
break;
}
case 'w':
{
byte* b = va_arg ( Arguments, byte* );
b[1] = Response[Length++];
b[0] = Response[Length++];
break;
}
case 'x':
{
word* w = va_arg ( Arguments, word* );
*w = *(word UNALIGNED *)&Response[Length];
Length += 2;
break;
}
case 'd':
{
byte* b = va_arg ( Arguments, byte* );
b[3] = Response[Length++];
b[2] = Response[Length++];
b[1] = Response[Length++];
b[0] = Response[Length++];
break;
}
case 'e':
{
dword UNALIGNED * d = va_arg ( Arguments, dword* );
*d = *(dword UNALIGNED *)&Response[Length];
Length += 4;
break;
}
case 'c':
{
char* c = va_arg ( Arguments, char* );
word l = (word)strlen( &Response[Length] );
memcpy ( c, &Response[Length], l+1 );
Length += l+1;
break;
}
case 'p':
{
char* c = va_arg ( Arguments, char* );
byte l = Response[Length++];
memcpy ( c, &Response[Length], l );
c[l+1] = 0;
break;
}
case 'P':
{
PUNICODE_STRING pUString = va_arg ( Arguments, PUNICODE_STRING );
OEM_STRING OemString;
OemString.Length = Response[Length++];
OemString.Buffer = &Response[Length];
//
// Note the the Rtl function would set pUString->Buffer = NULL,
// if OemString.Length is 0.
//
if ( OemString.Length != 0 ) {
Status = RtlOemStringToCountedUnicodeString( pUString, &OemString, FALSE );
if (!NT_SUCCESS( Status )) {
pUString->Length = 0;
NcpStatus = Status;
}
} else {
pUString->Length = 0;
}
break;
}
case 'r':
{
byte* b = va_arg ( Arguments, byte* );
word l = va_arg ( Arguments, word );
TdiCopyLookaheadData( b, &Response[Length], l, 0);
Length += l;
break;
}
case 'R':
{
//
// Interpret the buffer as an ASCIIZ string. Convert
// it to unicode in the preallocated buffer.
//
PUNICODE_STRING pUString = va_arg ( Arguments, PUNICODE_STRING );
OEM_STRING OemString;
USHORT len = va_arg ( Arguments, USHORT );
OemString.Buffer = &Response[Length];
OemString.Length = (USHORT)strlen( OemString.Buffer );
OemString.MaximumLength = OemString.Length;
//
// Note the the Rtl function would set pUString->Buffer = NULL,
// if OemString.Length is 0.
//
if ( OemString.Length != 0) {
Status = RtlOemStringToCountedUnicodeString( pUString, &OemString, FALSE );
if (!NT_SUCCESS( Status )) {
ASSERT( Status == STATUS_BUFFER_OVERFLOW );
pUString->Length = 0;
NcpStatus = Status;
}
} else {
pUString->Length = 0;
}
Length += len;
break;
}
case 'W':
{
WORD *w = va_arg ( Arguments, WORD* );
*w = (* (WORD *)&Response[Length]);
Length += 2;
break;
}
case 'D':
{
DWORD *d = va_arg ( Arguments, DWORD* );
*d = (* (DWORD *)&Response[Length]);
Length += 4;
break;
}
case 'S':
{
PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
USHORT strl;
if (pU) {
strl = (USHORT)(* (DWORD *)&Response[Length]);
//
// Don't count the null terminator that is part of
// Novell's counted unicode string.
//
pU->Length = strl - sizeof( WCHAR );
Length += 4;
RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
Length += ROUNDUP4(strl);
} else {
//
// Skip over the string since we don't want it.
//
Length += ROUNDUP4((* (DWORD *)&Response[Length] ));
Length += 4;
}
break;
}
case 's':
{
PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
USHORT strl;
if (pU) {
strl = (USHORT)(* (DWORD *)&Response[Length]);
pU->Length = strl;
Length += 4;
RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
Length += ROUNDUP4(strl);
} else {
//
// Skip over the string since we don't want it.
//
Length += ROUNDUP4((* (DWORD *)&Response[Length] ));
Length += 4;
}
break;
}
case 'T':
{
PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
USHORT strl;
if (pU) {
strl = (USHORT)(* (DWORD *)&Response[Length] );
strl -= sizeof( WCHAR ); // Don't count the NULL from NDS.
if ( strl <= pU->MaximumLength ) {
pU->Length = strl;
Length += 4;
RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
//
// No need to advance the pointers since this is
// specifically a termination case!
//
} else {
pU->Length = 0;
}
}
break;
}
case 't':
{
PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
USHORT strl;
if (pU) {
strl = (USHORT)(* (DWORD *)&Response[Length] );
if ( strl <= pU->MaximumLength ) {
pU->Length = strl;
Length += 4;
RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
//
// No need to advance the pointers since this is
// specifically a termination case!
//
} else {
pU->Length = 0;
}
}
break;
}
/*
case 's':
{
char *c = va_arg( Arguments, char * );
WORD l = va_arg( Arguments, WORD );
ULONG len = (* (DWORD *)&Response[Length]);
Length += 4;
// How to fix this?
// l = WideCharToMultiByte(CP_ACP,0,(WCHAR *)&Response[Length],Length/2,c,l,0,0);
// if (!l) {
// #ifdef NWDBG
// DbgPrint( "ParseResponse case s couldnt translate from WCHAR.\n" );
// #endif
// goto ErrorExit;
// }
len = ROUNDUP4(len);
Length += len;
break;
}
case 'V':
{
BYTE **b = va_arg( Arguments, BYTE **);
DWORD *pLen = va_arg ( Arguments, DWORD *);
DWORD len = (* (DWORD *)&Response[Length]);
Length += 4;
if (b) {
*b = (BYTE *)&Response[Length];
}
if (pLen) {
*pLen = len;
}
Length += ROUNDUP4(len);
break;
}
case 'l':
{
BYTE* b = va_arg ( Arguments, BYTE* );
BYTE* w = va_arg ( Arguments, BYTE* );
WORD i;
b[1] = Response[Length++];
b[0] = Response[Length++];
for ( i = 0; i < ((WORD) *b); i++, w += sizeof(WORD) )
{
w[1] = Response[Length++];
w[0] = Response[Length++];
}
break;
}
*/
#ifdef NWDBG
default:
DbgPrintf ( "*****exchange: invalid response field, %x\n", *FormatByte );
DbgBreakPoint();
#endif
}
if ( Length > ResponseLength ) {
#ifdef NWDBG
DbgPrintf ( "*****exchange: not enough response data, %d\n", Length );
if ( IrpContext ) {
Error( EVENT_NWRDR_INVALID_REPLY,
STATUS_UNEXPECTED_NETWORK_ERROR,
NULL,
0,
1,
IrpContext->pNpScb->ServerName.Buffer );
}
#endif
return( STATUS_UNEXPECTED_NETWORK_ERROR );
}
FormatByte++;
}
va_end( Arguments );
return( NcpStatus );
}
NTSTATUS
ParseNcpResponse(
PIRP_CONTEXT IrpContext,
PNCP_RESPONSE Response
)
{
NTSTATUS Status;
if ( Response->Status == 0 ) {
Status = NwErrorToNtStatus( Response->Error );
} else {
Status = NwConnectionStatusToNtStatus( Response->Status );
if ( Status == STATUS_REMOTE_DISCONNECT ) {
Stats.ServerDisconnects++;
IrpContext->pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
}
}
return( Status );
}
NTSTATUS
FormatRequest(
PIRP_CONTEXT pIrpC,
PEX pEx,
char* f,
va_list a // format specific parameters
)
/*++
Routine Description:
Send the packet described by f and the additional parameters. When a
valid response has been received call pEx with the resonse.
An exchange is a generic way of assembling a request packet of a
given type, containing a set of fields, sending the packet, receiving
a response packet, and disassembling the fields of the response packet.
The packet type and each field is specified by individual
characters in a format string.
The exchange procedure takes such a format string plus additional
parameters as necessary for each character in the string as specified
below.
Arguments: '']
pIrpC - supplies the irp context for the exchange request.
pEx - supplies the routine to process the data.
f... - supplies the information needed to create the request to the
server. The first byte indicates the packet type and the
following bytes contain field types.
Packet types:
'A' SAP broadcast ( void )
'B' NCP burst ( dword, dword, byte )
'C' NCP connect ( void )
'F' NCP function ( byte )
'S' NCP subfunction ( byte, byte )
'N' NCP subfunction w/o size ( byte, byte )
'D' NCP disconnect ( void )
'E' Echo data ( void )
Field types, request/response:
'b' byte ( byte / byte* )
'w' hi-lo word ( word / word* )
'd' hi-lo dword ( dword / dword* )
'W' lo-hi word ( word / word* )
'D' lo-hi dword ( dword / dword* )
'-' zero/skip byte ( void )
'=' zero/skip word ( void )
._. zero/skip string ( word )
'p' pstring ( char* )
'u' p unicode string ( UNICODE_STRING * )
'U' p uppercase string( UNICODE_STRING * )
'J' variant of U ( UNICODE_STRING * )
'c' cstring ( char* )
'v' cstring ( UNICODE_STRING* )
'r' raw bytes ( byte*, word )
'w' fixed length unicode ( UNICODE_STRING*, word )
'C' Component format name, with count ( UNICODE_STRING * )
'N' Component format name, no count ( UNICODE_STRING * )
'f' separate fragment ( PMDL )
An 'f' field must be last, and in a response it cannot be
preceeded by 'p' or 'c' fields.
Return Value:
Normally returns STATUS_SUCCESS.
--*/
{
NTSTATUS status;
char* z;
word data_size;
PNONPAGED_SCB pNpScb = pIrpC->pNpScb;
dword dwData;
ASSERT( pIrpC->NodeTypeCode == NW_NTC_IRP_CONTEXT );
ASSERT( pIrpC->pNpScb != NULL );
status= STATUS_LINK_FAILED;
pIrpC->pEx = pEx; // Routine to process reply
pIrpC->Destination = pNpScb->RemoteAddress;
ClearFlag( pIrpC->Flags, IRP_FLAG_SEQUENCE_NO_REQUIRED );
switch ( *f ) {
case 'A':
// Send to local network (0), a broadcast (-1), socket 0x452
pIrpC->Destination = SapBroadcastAddress;
pIrpC->PacketType = SAP_BROADCAST;
data_size = 0;
pNpScb->RetryCount = 3;
pNpScb->MaxTimeOut = 2 * pNpScb->TickCount + 10;
pNpScb->TimeOut = pNpScb->MaxTimeOut;
SetFlag( pIrpC->Flags, IRP_FLAG_RETRY_SEND );
break;
case 'E':
pIrpC->Destination = pNpScb->EchoAddress;
pIrpC->PacketType = NCP_ECHO;
//
// For echo packets use a short timeout and a small retry count.
// Set the retry send bit, so that SendNow doesn't reset the
// RetryCount to a bigger number. If we start getting packets
// after we've timed out, we'll increase the wait time.
//
pNpScb->RetryCount = 0;
pNpScb->MaxTimeOut = 2 * pNpScb->TickCount + 7 + pNpScb->LipTickAdjustment;
pNpScb->TimeOut = pNpScb->MaxTimeOut;
SetFlag( pIrpC->Flags, IRP_FLAG_RETRY_SEND );
SetFlag( pIrpC->Flags, IRP_FLAG_REROUTE_ATTEMPTED );
data_size = 0;
break;
case 'C':
pIrpC->PacketType = NCP_CONNECT;
*(PUSHORT)&pIrpC->req[0] = PEP_COMMAND_CONNECT;
pIrpC->req[2] = 0x00;
pIrpC->req[3] = 0xFF;
pIrpC->req[4] = 0x00;
pIrpC->req[5] = 0xFF;
data_size = 6;
pNpScb->MaxTimeOut = 16 * pNpScb->TickCount + 10;
pNpScb->TimeOut = 4 * pNpScb->TickCount + 10;
pNpScb->SequenceNo = 0;
break;
case 'F':
pIrpC->PacketType = NCP_FUNCTION;
goto FallThrough;
case 'S':
case 'N':
pIrpC->PacketType = NCP_SUBFUNCTION;
goto FallThrough;
case 'L':
pIrpC->PacketType = NCP_SUBFUNCTION;
goto FallThrough;
case 'D':
pIrpC->PacketType = NCP_DISCONNECT;
FallThrough:
if ( *f == 'D' ) {
*(PUSHORT)&pIrpC->req[0] = PEP_COMMAND_DISCONNECT;
} else {
*(PUSHORT)&pIrpC->req[0] = PEP_COMMAND_REQUEST;
}
pNpScb->RetryCount = DefaultRetryCount ;
pNpScb->MaxTimeOut = 2 * pNpScb->TickCount + 10;
pNpScb->TimeOut = pNpScb->SendTimeout;
//
// Mark this packet as SequenceNumberRequired. We need to guarantee
// the packets are sent in sequence number order, so we will
// fill in the sequence number when we are ready to send the
// packet.
//
SetFlag( pIrpC->Flags, IRP_FLAG_SEQUENCE_NO_REQUIRED );
pIrpC->req[3] = pNpScb->ConnectionNo;
pIrpC->req[5] = pNpScb->ConnectionNoHigh;
if ( pIrpC->Icb != NULL && pIrpC->Icb->Pid != INVALID_PID ) {
pIrpC->req[4] = (UCHAR)pIrpC->Icb->Pid;
} else {
pIrpC->req[4] = 0xFF;
}
data_size = 6;
if ( *f == 'L' ) {
pIrpC->req[data_size++] = NCP_LFN_FUNCTION;
}
if ( *f != 'D' ) {
pIrpC->req[data_size++] = va_arg( a, byte );
}
if ( *f == 'S' ) {
data_size += 2;
pIrpC->req[data_size++] = va_arg( a, byte );
}
if ( *f == 'N' ) {
pIrpC->req[data_size++] = va_arg( a, byte );
}
break;
case 'B':
pIrpC->PacketType = NCP_BURST;
*(PUSHORT)&pIrpC->req[0] = PEP_COMMAND_BURST;
pNpScb->TimeOut = pNpScb->MaxTimeOut;
//
// tommye - MS bug 2743 changed the RetryCount from 20 to be based off the
// default retry count, nudged up a little.
//
if ( !BooleanFlagOn( pIrpC->Flags, IRP_FLAG_RETRY_SEND ) ) {
pNpScb->RetryCount = DefaultRetryCount * 2;
}
pIrpC->req[3] = 0x2; // Stream Type = Big Send Burst
*(PULONG)&pIrpC->req[4] = pNpScb->SourceConnectionId;
*(PULONG)&pIrpC->req[8] = pNpScb->DestinationConnectionId;
LongByteSwap( (*(PULONG)&pIrpC->req[16]) , pNpScb->CurrentBurstDelay ); // Send delay time
dwData = va_arg( a, dword ); // Size of data
LongByteSwap( pIrpC->req[24], dwData );
dwData = va_arg( a, dword ); // Offset of data
LongByteSwap( pIrpC->req[28], dwData );
pIrpC->req[2] = va_arg( a, byte ); // Burst flags
data_size = 34;
break;
default:
DbgPrintf ( "*****exchange: invalid packet type, %x\n", *f );
DbgBreakPoint();
va_end( a );
return status;
}
z = f;
while ( *++z && *z != 'f' )
{
switch ( *z )
{
case '=':
pIrpC->req[data_size++] = 0;
case '-':
pIrpC->req[data_size++] = 0;
break;
case '_':
{
word l = va_arg ( a, word );
ASSERT( data_size + l <= MAX_SEND_DATA );
while ( l-- )
pIrpC->req[data_size++] = 0;
break;
}
case 's':
{
word l = va_arg ( a, word );
ASSERT ( data_size + l <= MAX_SEND_DATA );
data_size += l;
break;
}
case 'i':
pIrpC->req[4] = va_arg ( a, byte );
break;
case 'b':
pIrpC->req[data_size++] = va_arg ( a, byte );
break;
case 'w':
{
word w = va_arg ( a, word );
pIrpC->req[data_size++] = (byte) (w >> 8);
pIrpC->req[data_size++] = (byte) (w >> 0);
break;
}
case 'd':
{
dword d = va_arg ( a, dword );
pIrpC->req[data_size++] = (byte) (d >> 24);
pIrpC->req[data_size++] = (byte) (d >> 16);
pIrpC->req[data_size++] = (byte) (d >> 8);
pIrpC->req[data_size++] = (byte) (d >> 0);
break;
}
case 'W':
{
word w = va_arg ( a, word );
*(word UNALIGNED *)&pIrpC->req[data_size] = w;
data_size += 2;
break;
}
case 'D':
{
dword d = va_arg ( a, dword );
*(dword UNALIGNED *)&pIrpC->req[data_size] = d;
data_size += 4;
break;
}
case 'c':
{
char* c = va_arg ( a, char* );
word l = (word)strlen( c );
ASSERT (data_size + l <= MAX_SEND_DATA );
RtlCopyMemory( &pIrpC->req[data_size], c, l+1 );
data_size += l + 1;
break;
}
case 'v':
{
PUNICODE_STRING pUString = va_arg ( a, PUNICODE_STRING );
OEM_STRING OemString;
ULONG Length;
Length = RtlUnicodeStringToOemSize( pUString ) - 1;
ASSERT (( data_size + Length <= MAX_SEND_DATA) && ( (Length & 0xffffff00) == 0) );
OemString.Buffer = &pIrpC->req[data_size];
OemString.MaximumLength = (USHORT)Length + 1;
status = RtlUnicodeStringToCountedOemString( &OemString, pUString, FALSE );
ASSERT( NT_SUCCESS( status ));
data_size += (USHORT)Length + 1;
break;
}
case 'p':
{
char* c = va_arg ( a, char* );
byte l = (byte)strlen( c );
if ((data_size+l>MAX_SEND_DATA) ||
( (l & 0xffffff00) != 0) ) {
ASSERT("***exchange: Packet too long!2!\n" && FALSE );
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
pIrpC->req[data_size++] = l;
RtlCopyMemory( &pIrpC->req[data_size], c, l );
data_size += l;
break;
}
case 'J':
case 'U':
case 'u':
{
PUNICODE_STRING pUString = va_arg ( a, PUNICODE_STRING );
OEM_STRING OemString;
PUCHAR pOemString;
ULONG Length;
ULONG i;
//
// Calculate required string length, excluding trailing NUL.
//
Length = RtlUnicodeStringToOemSize( pUString ) - 1;
ASSERT( Length < 0x100 );
if (( data_size + Length > MAX_SEND_DATA ) ||
( (Length & 0xffffff00) != 0) ) {
ASSERT("***exchange:Packet too long or name >255 chars!4!\n" && FALSE);
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
pIrpC->req[data_size++] = (UCHAR)Length;
OemString.Buffer = &pIrpC->req[data_size];
OemString.MaximumLength = (USHORT)Length + 1;
if ( *z == 'u' ) {
status = RtlUnicodeStringToCountedOemString(
&OemString,
pUString,
FALSE );
} else {
status = RtlUpcaseUnicodeStringToCountedOemString(
&OemString,
pUString,
FALSE );
}
if ( !NT_SUCCESS( status ) ) {
return status;
}
data_size += (USHORT)Length;
if (( Japan ) &&
( *z == 'J' )) {
//
// Netware Japanese version The following single byte character is replaced with another one
// if the string is for File Name only when sending from Client to Server.
//
// U+0xFF7F SJIS+0xBF -> 0x10
// U+0xFF6E SJIS+0xAE -> 0x11
// U+0xFF64 SJIS+0xAA -> 0x12
//
for ( i = 0 , pOemString = OemString.Buffer ; i < Length ; i++ , pOemString++ ) {
//
// In fact Novell server seems to convert all 0xBF, 0xAA, 0xAE
// and 0x5C even if they are DBCS lead or trail byte.
// We can't single out DBCS case in the conversion.
//
if( FsRtlIsLeadDbcsCharacter( *pOemString ) ) {
if(*pOemString == 0xBF ) {
*pOemString = 0x10;
}else if(*pOemString == 0xAE ) {
*pOemString = 0x11;
}else if(*pOemString == 0xAA ) {
*pOemString = 0x12;
}
// Trail byte
i++; pOemString++;
if(*pOemString == 0x5C ) {
//
// The trailbyte is 0x5C, replace it with 0x13
//
*pOemString = 0x13;
}
//
// Continue to check other conversions for trailbyte.
//
}
if ( *pOemString == 0xBF ) {
*pOemString = 0x10;
} else if ( *pOemString == 0xAA ) {
*pOemString = 0x12;
} else if ( *pOemString == 0xAE ) {
*pOemString = 0x11;
}
}
}
break;
}
case 'r':
{
byte* b = va_arg ( a, byte* );
word l = va_arg ( a, word );
if (data_size+l>MAX_SEND_DATA) {
ASSERT("***exchange: Packet too long!6!\n"&& FALSE);
return STATUS_UNSUCCESSFUL;
}
RtlCopyMemory( &pIrpC->req[data_size], b, l );
data_size += l;
break;
}
case 'x':
{
PUNICODE_STRING pUString = va_arg ( a, PUNICODE_STRING );
ULONG RequiredLength = va_arg( a, word );
ULONG Length;
OEM_STRING OemString;
//
// Convert this string to an OEM string.
//
status = RtlUnicodeStringToCountedOemString( &OemString, pUString, TRUE );
ASSERT( NT_SUCCESS( status ));
if (!NT_SUCCESS(status)) {
return status;
}
if ( data_size + RequiredLength > MAX_SEND_DATA ) {
ASSERT("***exchange: Packet too long!4!\n" && FALSE);
return STATUS_UNSUCCESSFUL;
}
//
// Copy the oem string to the buffer, padded with 0's if
// necessary.
//
Length = MIN( OemString.Length, RequiredLength );
RtlMoveMemory( &pIrpC->req[data_size], OemString.Buffer, Length );
if ( RequiredLength > Length ) {
RtlFillMemory(
&pIrpC->req[data_size+Length],
RequiredLength - Length,
0 );
}
RtlFreeAnsiString(&OemString);
data_size += (USHORT)RequiredLength;
break;
}
case 'C':
case 'N':
{
PUNICODE_STRING pUString = va_arg ( a, PUNICODE_STRING );
OEM_STRING OemString;
PWCH thisChar, lastChar, firstChar;
PCHAR componentCountPtr, pchar;
CHAR componentCount;
UNICODE_STRING UnicodeString;
int i;
//
// Copy the oem string to the buffer, in component format.
//
thisChar = pUString->Buffer;
lastChar = &pUString->Buffer[ pUString->Length / sizeof(WCHAR) ];
//
// Skip leading path separators
//
while ( (thisChar < lastChar) &&
(*thisChar == OBJ_NAME_PATH_SEPARATOR)) {
thisChar++;
}
componentCount = 0;
if ( *z == 'C' ) {
componentCountPtr = &pIrpC->req[data_size++];
}
while ( thisChar < lastChar ) {
if ( data_size >= MAX_SEND_DATA - 1 ) {
ASSERT( ("***exchange: Packet too long or name > 255 chars!5!\n" && FALSE) );
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
firstChar = thisChar;
while ( thisChar < lastChar &&
*thisChar != OBJ_NAME_PATH_SEPARATOR ) {
thisChar++;
}
++componentCount;
UnicodeString.Buffer = firstChar;
UnicodeString.Length = (USHORT) (( thisChar - firstChar ) * sizeof(WCHAR));
OemString.Buffer = &pIrpC->req[data_size + 1];
OemString.MaximumLength = MAX_SEND_DATA - data_size - 1;
status = RtlUnicodeStringToCountedOemString( &OemString, &UnicodeString, FALSE );
pIrpC->req[data_size] = (UCHAR)OemString.Length;
data_size += OemString.Length + 1;
if ( !NT_SUCCESS( status ) || data_size > MAX_SEND_DATA ) {
// ASSERT("***exchange: Packet too long or name > 255 chars!5!\n" && FALSE );
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
//
// Search the result OEM string for the character 0xFF.
// If it's there, fail this request. The server doesn't
// deal with 0xFF very well.
//
for ( pchar = OemString.Buffer, i = 0;
i < OemString.Length;
pchar++, i++ ) {
//
// We need to check for dbcs, because 0xff is a
// legal trail byte for EUDC characters.
//
if ( FsRtlIsLeadDbcsCharacter( (UCHAR)*pchar ) ) {
//
// Skip dbcs character.
//
pchar++; i++;
continue;
}
if (( (UCHAR)*pchar == LFN_META_CHARACTER ) ||
!FsRtlIsAnsiCharacterLegalHpfs(*pchar, FALSE) ) {
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
}
thisChar++; // Skip the path separator
}
if ( *z == 'C' ) {
*componentCountPtr = componentCount;
}
break;
}
default:
#ifdef NWDBG
DbgPrintf ( "*****exchange: invalid request field, %x\n", *z );
DbgBreakPoint();
#endif
;
}
if ( data_size > MAX_SEND_DATA )
{
DbgPrintf( "*****exchange: CORRUPT, too much request data\n" );
DbgBreakPoint();
va_end( a );
return STATUS_UNSUCCESSFUL;
}
}
pIrpC->TxMdl->ByteCount = data_size;
if ( *z == 'f' )
{
PMDL mdl;
//
// Fragment of data following Ipx header. Next parameter is
// the address of the mdl describing the fragment.
//
++z;
mdl = (PMDL) va_arg ( a, byte* );
pIrpC->TxMdl->Next = mdl;
data_size += (USHORT)MdlLength( mdl );
}
if ( *f == 'S' ) {
pIrpC->req[7] = (data_size-9) >> 8;
pIrpC->req[8] = (data_size-9);
} else if ( *f == 'B' ) {
//
// For burst packets set the number of bytes in this packet to
// a real number for burst requests, and to 0 for a missing packet
// request.
//
if ( *(PUSHORT)&pIrpC->req[34] == 0 ) {
USHORT RealDataSize = data_size - 36;
ShortByteSwap( pIrpC->req[32], RealDataSize );
} else {
*(PUSHORT)&pIrpC->req[32] = 0;
}
}
va_end( a );
return( STATUS_SUCCESS );
}
NTSTATUS
PrepareAndSendPacket(
PIRP_CONTEXT pIrpContext
)
{
PreparePacket( pIrpContext, pIrpContext->pOriginalIrp, pIrpContext->TxMdl );
return SendPacket( pIrpContext, pIrpContext->pNpScb );
}
VOID
PreparePacket(
PIRP_CONTEXT pIrpContext,
PIRP pIrp,
PMDL pMdl
)
/*++
Routine Description:
This routine builds the IRP for sending a packet.
Arguments:
IrpContext - A pointer to IRP context information for the request
being processed.
Irp - The IRP to be used to submit the request to the transport.
Mdl - A pointer to the MDL for the data to send.
Return Value:
None.
--*/
{
PIO_COMPLETION_ROUTINE CompletionRoutine;
PNW_TDI_STRUCT pTdiStruct;
DebugTrace(0, Dbg, "PreparePacket...\n", 0);
pIrpContext->ConnectionInformation.UserDataLength = 0;
pIrpContext->ConnectionInformation.OptionsLength = sizeof( UCHAR );
pIrpContext->ConnectionInformation.Options =
(pIrpContext->PacketType == SAP_BROADCAST) ?
&SapPacketType : &NcpPacketType;
pIrpContext->ConnectionInformation.RemoteAddressLength = sizeof(TA_IPX_ADDRESS);
pIrpContext->ConnectionInformation.RemoteAddress = &pIrpContext->Destination;
#if NWDBG
dump( Dbg,
&pIrpContext->Destination.Address[0].Address[0],
sizeof(TDI_ADDRESS_IPX));
dumpMdl( Dbg, pMdl);
#endif
//
// Set the socket to use for this send. If unspecified in the
// IRP context, use the default (server) socket.
//
pTdiStruct = pIrpContext->pTdiStruct == NULL ?
&pIrpContext->pNpScb->Server : pIrpContext->pTdiStruct;
CompletionRoutine = pIrpContext->CompletionSendRoutine == NULL ?
CompletionSend : pIrpContext->CompletionSendRoutine;
TdiBuildSendDatagram(
pIrp,
pTdiStruct->pDeviceObject,
pTdiStruct->pFileObject,
CompletionRoutine,
pIrpContext,
pMdl,
MdlLength( pMdl ),
&pIrpContext->ConnectionInformation );
//
// Set the run routine to send now, only if this is the main IRP
// for this irp context.
//
if ( pIrp == pIrpContext->pOriginalIrp ) {
pIrpContext->RunRoutine = SendNow;
}
return;
}
NTSTATUS
SendPacket(
PIRP_CONTEXT pIrpC,
PNONPAGED_SCB pNpScb
)
/*++
Routine Description:
Queue a packet created by exchange and try to send it to the server.
Arguments:
pIrpC - supplies the irp context for the request creating the socket.
pNpScb - supplies the server to receive the request.
Return Value:
STATUS_PENDING
--*/
{
if ( AppendToScbQueue( pIrpC, pNpScb ) ) {
KickQueue( pNpScb );
}
return STATUS_PENDING;
}
BOOLEAN
AppendToScbQueue(
PIRP_CONTEXT IrpContext,
PNONPAGED_SCB NpScb
)
/*++
Routine Description:
Queue an IRP context to the SCB, if it is not already there.
Arguments:
IrpContext - Supplies the IRP context to queue.
NpScb - Supplies the server to receive the request.
Return Value:
TRUE - The IRP Context is at the front of the queue.
FALSE - The IRP Context is not at the front of the queue.
--*/
{
PLIST_ENTRY ListEntry;
#ifdef MSWDBG
KIRQL OldIrql;
#endif
DebugTrace(0, Dbg, "AppendToScbQueue... %08lx\n", NpScb);
DebugTrace(0, Dbg, "IrpContext = %08lx\n", IrpContext );
//
// Look at the IRP Context flags. If the IRP is already on the
// queue, then it must be at the front and ready for processing.
//
if ( FlagOn( IrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE ) ) {
ASSERT( NpScb->Requests.Flink == &IrpContext->NextRequest );
return( TRUE );
}
#ifdef MSWDBG
NpScb->RequestQueued = TRUE;
#endif
#if 0 // Resource layout changed on Daytona. Disable for now.
//
// Make sure that this thread isn't holding the RCB while waiting for
// the SCB queue.
//
ASSERT ( NwRcb.Resource.InitialOwnerThreads[0] != (ULONG)PsGetCurrentThread() );
#endif
//
// The IRP Context was not at the front. Queue it, then look to
// see if it was appended to an empty queue.
//
SetFlag( IrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE );
#ifdef MSWDBG
ExAcquireSpinLock( &NpScb->NpScbSpinLock, &OldIrql );
if ( IsListEmpty( &NpScb->Requests ) ) {
ListEntry = NULL;
} else {
ListEntry = NpScb->Requests.Flink;
}
InsertTailList( &NpScb->Requests, &IrpContext->NextRequest );
IrpContext->SequenceNumber = NpScb->SequenceNumber++;
ExReleaseSpinLock( &NpScb->NpScbSpinLock, OldIrql );
#else
ListEntry = ExInterlockedInsertTailList(
&NpScb->Requests,
&IrpContext->NextRequest,
&NpScb->NpScbSpinLock );
#endif
if ( ListEntry == NULL ) {
ASSERT( NpScb->Requests.Flink == &IrpContext->NextRequest );
DebugTrace(-1, Dbg, "AppendToScbQueue -> TRUE\n", 0);
return( TRUE );
} else {
DebugTrace(-1, Dbg, "AppendToScbQueue -> FALSE\n", 0);
return( FALSE );
}
}
VOID
KickQueue(
PNONPAGED_SCB pNpScb
)
/*++
Routine Description:
Queue a packet created by exchange and try to send it to the server.
Note: NpScbSpinLock must be held before calling this routine.
Arguments:
pNpScb - supplies the server queue to kick into life.
Return Value:
none.
--*/
{
PIRP_CONTEXT pIrpC;
PRUN_ROUTINE RunRoutine;
KIRQL OldIrql;
DebugTrace( +1, Dbg, "KickQueue...%08lx\n", pNpScb);
KeAcquireSpinLock( &pNpScb->NpScbSpinLock, &OldIrql );
if ( IsListEmpty( &pNpScb->Requests )) {
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
DebugTrace( -1, Dbg, " Empty Queue\n", 0);
return;
}
pIrpC = CONTAINING_RECORD(pNpScb->Requests.Flink, IRP_CONTEXT, NextRequest);
ASSERT( pIrpC->pNpScb->Requests.Flink == &pIrpC->NextRequest );
ASSERT( pIrpC->NodeTypeCode == NW_NTC_IRP_CONTEXT);
RunRoutine = pIrpC->RunRoutine;
// Only call the routine to tell it it is at the front once
pIrpC->RunRoutine = NULL;
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
//
// If the redir is shutting down do not process this request
// unless we must.
//
if ( NwRcb.State != RCB_STATE_RUNNING &&
!FlagOn( pIrpC->Flags, IRP_FLAG_SEND_ALWAYS ) ) {
//
// Note that it's safe to call the pEx routine without the
// spin lock held since this IrpContext just made it to the
// front of the queue, and so can't have i/o in progress.
//
if ( pIrpC->pEx != NULL) {
pIrpC->pEx( pIrpC, 0, NULL );
DebugTrace( -1, Dbg, "KickQueue\n", 0);
return;
}
}
if ( RunRoutine != NULL ) {
ASSERT( pNpScb->Receiving == FALSE );
RunRoutine( pIrpC );
}
DebugTrace( -1, Dbg, "KickQueue\n", 0);
return;
}
VOID
SendNow(
PIRP_CONTEXT IrpContext
)
/*++
Routine Description:
This routine submits a TDI send request to the tranport layer.
Arguments:
IrpContext - A pointer to IRP context information for the request
being processed.
Return Value:
None.
--*/
{
PNONPAGED_SCB pNpScb;
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp;
pNpScb = IrpContext->pNpScb;
if ( !BooleanFlagOn( IrpContext->Flags, IRP_FLAG_RETRY_SEND ) ) {
pNpScb->RetryCount = DefaultRetryCount;
}
//
// Ensure that this IRP Context is really at the front of the queue.
//
ASSERT( pNpScb->Requests.Flink == &IrpContext->NextRequest );
IrpContext->RunRoutine = NULL;
//
// Make sure that this is a correctly formatted send request.
//
IrpSp = IoGetNextIrpStackLocation( IrpContext->pOriginalIrp );
ASSERT( IrpSp->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL );
ASSERT( IrpSp->MinorFunction == TDI_SEND_DATAGRAM );
//
// This IRP context has a packet ready to send. Send it now.
//
pNpScb->Sending = TRUE;
if ( !BooleanFlagOn( IrpContext->Flags, IRP_FLAG_NOT_OK_TO_RECEIVE ) ) {
pNpScb->OkToReceive = TRUE;
}
pNpScb->Receiving = FALSE;
pNpScb->Received = FALSE;
//
// If this packet requires a sequence number, set it now.
// The sequence number is updated when we receive a response.
//
// We do not need to synchronize access to SequenceNo since
// this is the only active packet for this SCB.
//
if ( BooleanFlagOn( IrpContext->Flags, IRP_FLAG_SEQUENCE_NO_REQUIRED ) ) {
ClearFlag( IrpContext->Flags, IRP_FLAG_SEQUENCE_NO_REQUIRED );
IrpContext->req[2] = pNpScb->SequenceNo;
}
//
// If this packet is a burst packet, fill in the burst sequence number
// now, and burst request number.
//
if ( BooleanFlagOn( IrpContext->Flags, IRP_FLAG_BURST_PACKET ) ) {
LongByteSwap( IrpContext->req[12], pNpScb->BurstSequenceNo );
pNpScb->BurstSequenceNo++;
ShortByteSwap( IrpContext->req[20], pNpScb->BurstRequestNo );
ShortByteSwap( IrpContext->req[22], pNpScb->BurstRequestNo );
}
DebugTrace( +0, Dbg, "Irp %X\n", IrpContext->pOriginalIrp);
DebugTrace( +0, Dbg, "pIrpC %X\n", IrpContext);
DebugTrace( +0, Dbg, "Mdl %X\n", IrpContext->TxMdl);
#if NWDBG
dumpMdl( Dbg, IrpContext->TxMdl);
#endif
{
ULONG len = 0;
PMDL Next = IrpContext->TxMdl;
do {
len += MmGetMdlByteCount(Next);
} while (Next = Next->Next);
Stats.BytesTransmitted.QuadPart += len;
}
Status = IoCallDriver(pNpScb->Server.pDeviceObject, IrpContext->pOriginalIrp);
DebugTrace( -1, Dbg, "Transport returned: %08lx\n", Status );
Stats.NcpsTransmitted.QuadPart++;
return;
}
VOID
SetEvent(
PIRP_CONTEXT IrpContext
)
/*++
Routine Description:
This routine set the IrpContext Event to the signalled state.
Arguments:
IrpContext - A pointer to IRP context information for the request
being processed.
Return Value:
None.
--*/
{
//
// Ensure that this IRP Context is really at the front of the queue.
//
ASSERT( IrpContext->pNpScb->Requests.Flink == &IrpContext->NextRequest );
//
// This IRP context has a thread waiting to get to the front of
// the queue. Set the event to indicate that it can continue.
//
#ifdef MSWDBG
ASSERT( IrpContext->Event.Header.SignalState == 0 );
IrpContext->DebugValue = 0x105;
#endif
DebugTrace( +0, Dbg, "Setting event for IrpContext %X\n", IrpContext );
NwSetIrpContextEvent( IrpContext );
}
USHORT
NextSocket(
IN USHORT OldValue
)
/*++
Routine Description:
This routine returns the byteswapped OldValue++ wrapping from 7fff.
Arguments:
OldValue - supplies the existing socket number in the range
0x4000 to 0x7fff.
Return Value:
USHORT OldValue++
--*/
{
USHORT TempValue = OldValue + 0x0100;
if ( TempValue < 0x100 ) {
if ( TempValue == 0x007f ) {
// Wrap back to 0x4000 from 0xff7f
return 0x0040;
} else {
// Go from something like 0xff40 to 0x0041
return TempValue + 1;
}
}
return TempValue;
}
ULONG
MdlLength (
register IN PMDL Mdl
)
/*++
Routine Description:
This routine returns the number of bytes in an MDL.
Arguments:
IN PMDL Mdl - Supplies the MDL to determine the length on.
Return Value:
ULONG - Number of bytes in the MDL
--*/
{
register ULONG Size = 0;
while (Mdl!=NULL) {
Size += MmGetMdlByteCount(Mdl);
Mdl = Mdl->Next;
}
return Size;
}
NTSTATUS
CompletionSend(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This routine does not complete the Irp. It is used to signal to a
synchronous part of the driver that it can proceed.
Arguments:
DeviceObject - unused.
Irp - Supplies Irp that the transport has finished processing.
Context - Supplies the IrpContext associated with the Irp.
Return Value:
The STATUS_MORE_PROCESSING_REQUIRED so that the IO system stops
processing Irp stack locations at this point.
--*/
{
PNONPAGED_SCB pNpScb;
PIRP_CONTEXT pIrpC = (PIRP_CONTEXT) Context;
KIRQL OldIrql;
//
// Avoid completing the Irp because the Mdl etc. do not contain
// their original values.
//
DebugTrace( +1, Dbg, "CompletionSend\n", 0);
DebugTrace( +0, Dbg, "Irp %X\n", Irp);
DebugTrace( +0, Dbg, "pIrpC %X\n", pIrpC);
DebugTrace( +0, Dbg, "Status %X\n", Irp->IoStatus.Status);
pNpScb = pIrpC->pNpScb;
KeAcquireSpinLock( &pNpScb->NpScbSpinLock, &OldIrql );
ASSERT( pNpScb->Sending == TRUE );
pNpScb->Sending = FALSE;
//
// If we got a receive indication while waiting for send
// completion and the data is all valid, call the receive handler routine now.
//
if ( pNpScb->Received ) {
pNpScb->Receiving = FALSE;
pNpScb->Received = FALSE;
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
pIrpC->pEx(
pIrpC,
pIrpC->ResponseLength,
pIrpC->rsp );
} else if (( Irp->IoStatus.Status == STATUS_DEVICE_DOES_NOT_EXIST ) ||
( Irp->IoStatus.Status == STATUS_BAD_NETWORK_PATH ) ||
( Irp->IoStatus.Status == STATUS_INVALID_BUFFER_SIZE ) ||
( Irp->IoStatus.Status == STATUS_NETWORK_UNREACHABLE )) {
//
// The send failed.
//
//
// If this SCB is still flagged okay to receive (how could it not?)
// simply call the callback routine to indicate failure.
//
// If the SendCompletion hasn't happened, set up so that send
// completion will call the callback routine.
//
if ( pNpScb->OkToReceive ) {
pNpScb->OkToReceive = FALSE;
ClearFlag( pIrpC->Flags, IRP_FLAG_RETRY_SEND );
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
DebugTrace(+0, Dbg, "Send failed\n", 0 );
pIrpC->ResponseParameters.Error = ERROR_UNEXP_NET_ERR;
pIrpC->pEx( pIrpC, 0, NULL );
} else {
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
}
} else {
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
}
DebugTrace( -1, Dbg, "CompletionSend STATUS_MORE_PROCESSING_REQUIRED\n", 0);
return STATUS_MORE_PROCESSING_REQUIRED;
UNREFERENCED_PARAMETER( DeviceObject );
UNREFERENCED_PARAMETER( Irp );
}
#if NWDBG
BOOLEAN UseIrpReceive = FALSE;
#endif
NTSTATUS
ServerDatagramHandler(
IN PVOID TdiEventContext,
IN int SourceAddressLength,
IN PVOID SourceAddress,
IN int OptionsLength,
IN PVOID Options,
IN ULONG ReceiveDatagramFlags,
IN ULONG BytesIndicated,
IN ULONG BytesAvailable,
OUT ULONG *BytesTaken,
IN PVOID Tsdu,
OUT PIRP *IoRequestPacket
)
/*++
Routine Description:
This routine is the receive datagram event indication handler for the
Server socket.
Arguments:
TdiEventContext - Context provided for this event, a pointer to the
non paged SCB.
SourceAddressLength - Length of the originator of the datagram.
SourceAddress - String describing the originator of the datagram.
OptionsLength - Length of the buffer pointed to by Options.
Options - Options for the receive.
ReceiveDatagramFlags - Ignored.
BytesIndicated - Number of bytes this indication.
BytesAvailable - Number of bytes in complete Tsdu.
BytesTaken - Returns the number of bytes used.
Tsdu - Pointer describing this TSDU, typically a lump of bytes.
IoRequestPacket - TdiReceive IRP if MORE_PROCESSING_REQUIRED.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
PNONPAGED_SCB pNpScb = (PNONPAGED_SCB)TdiEventContext;
NTSTATUS Status = STATUS_DATA_NOT_ACCEPTED;
UCHAR PacketType;
PUCHAR RspData = (PUCHAR)Tsdu;
PIRP_CONTEXT pIrpC;
PNW_TDI_STRUCT pTdiStruct;
BOOLEAN AcceptPacket = TRUE;
PNCP_BURST_READ_RESPONSE pBurstRsp;
NTSTATUS BurstStatus;
*IoRequestPacket = NULL;
#if DBG
pTdiStruct = NULL;
#endif
if (pNpScb->NodeTypeCode != NW_NTC_SCBNP ) {
DebugTrace(+0, 0, "nwrdr: Invalid Server Indication %x\n", pNpScb );
#if DBG
DbgBreakPoint();
#endif
return STATUS_DATA_NOT_ACCEPTED;
}
#if NWDBG
// Debug only trick to test IRP receive.
if ( UseIrpReceive ) {
BytesIndicated = 0;
}
#endif
DebugTrace(+1, Dbg, "ServerDatagramHandler\n", 0);
DebugTrace(+0, Dbg, "Server %x\n", pNpScb);
DebugTrace(+0, Dbg, "BytesIndicated %x\n", BytesIndicated);
DebugTrace(+0, Dbg, "BytesAvailable %x\n", BytesAvailable);
//
// SourceAddress is the address of the server or the bridge tbat sent
// the packet.
//
#if NWDBG
dump( Dbg, SourceAddress, SourceAddressLength );
dump( Dbg, Tsdu, BytesIndicated );
#endif
if ( OptionsLength == 1 ) {
PacketType = *(PCHAR)Options;
DebugTrace(+0, Dbg, "PacketType %x\n", PacketType);
} else {
DebugTrace(+0, Dbg, "OptionsLength %x\n", OptionsLength);
#if NWDBG
dump( Dbg, Options, OptionsLength );
#endif
}
KeAcquireSpinLockAtDpcLevel(&pNpScb->NpScbSpinLock );
if ( !pNpScb->OkToReceive ) {
//
// This SCB is not expecting to receive any data.
// Discard this packet.
//
DropCount++;
DebugTrace(+0, Dbg, "OkToReceive == FALSE - discard packet\n", 0);
AcceptPacket = FALSE;
goto process_packet;
}
pIrpC = CONTAINING_RECORD(pNpScb->Requests.Flink, IRP_CONTEXT, NextRequest);
ASSERT( pIrpC->NodeTypeCode == NW_NTC_IRP_CONTEXT);
//
// Verify that this packet came from where we expect it to come from,
// and that is has a minimum size.
//
if ( ( pIrpC->PacketType != SAP_BROADCAST &&
RtlCompareMemory(
&pIrpC->Destination,
SourceAddress,
SourceAddressLength ) != (ULONG)SourceAddressLength ) ||
BytesIndicated < 8 ) {
AcceptPacket = FALSE;
#ifdef NWDBG
DbgPrintf ( "***exchange: stray response tossed\n", 0 );
#endif
goto process_packet;
}
switch ( pIrpC->PacketType ) {
case SAP_BROADCAST:
//
// We are expected a SAP Broadcast frame. Ensure that this
// is a correctly formatted SAP.
//
if ( pIrpC->req[0] != RspData[0] ||
pIrpC->req[2] != RspData[2] ||
pIrpC->req[3] != RspData[3] ||
SourceAddressLength != sizeof(TA_IPX_ADDRESS) ) {
DbgPrintf ( "***exchange: bad SAP packet\n" );
AcceptPacket = FALSE;
}
pTdiStruct = &pNpScb->Server;
break;
case NCP_BURST:
if ( *(USHORT UNALIGNED *)&RspData[0] == PEP_COMMAND_BURST ) {
if ( BytesIndicated < 36 ) {
AcceptPacket = FALSE;
} else if ( ( RspData[2] & BURST_FLAG_SYSTEM_PACKET ) &&
RspData[34] == 0 &&
RspData[35] == 0 ) {
//
// We have burst mode busy reponse.
//
DebugTrace(+0, Dbg, "Burst mode busy\n", 0 );
NwProcessPositiveAck( pNpScb );
AcceptPacket = FALSE;
} else {
USHORT Brn;
//
// Check the burst sequence number.
//
ShortByteSwap( Brn, RspData[20] );
if ( pNpScb->BurstRequestNo == Brn ) {
pTdiStruct = &pNpScb->Burst;
AcceptPacket = TRUE;
} else {
AcceptPacket = FALSE;
}
}
} else {
AcceptPacket = FALSE;
}
break;
case NCP_ECHO:
//
// If this is the LIP packet that we are expecting, then accept it.
// However, on a slow link, it could be an old LIP packet that we
// have already given up on. If this is the case, we should drop
// the packet and increase the LIP max wait time.
//
// The sequence number is the fourth DWORD in the response and the
// maximum LIP tick adjustment that we will allow is 18 ticks, which
// is 1 second.
//
pTdiStruct = &pNpScb->Echo;
if ( *(DWORD UNALIGNED *)&RspData[12] != pNpScb->LipSequenceNumber ) {
DebugTrace( 0, DEBUG_TRACE_ALWAYS, "LIP packet received out of order.\n", 0 );
if ( pNpScb->LipTickAdjustment < 18 ) {
pNpScb->LipTickAdjustment += 2;
}
AcceptPacket = FALSE;
} else {
AcceptPacket = TRUE;
}
break;
default:
pTdiStruct = &pNpScb->Server;
//
// This is the handling for all packets types other than
// SAP Broadcasts.
//
ASSERT( (pIrpC->PacketType == NCP_CONNECT) ||
(pIrpC->PacketType == NCP_FUNCTION) ||
(pIrpC->PacketType == NCP_SUBFUNCTION) ||
(pIrpC->PacketType == NCP_DISCONNECT));
if ( *(USHORT UNALIGNED *)&RspData[0] == PEP_COMMAND_ACKNOWLEDGE ) {
AcceptPacket = FALSE;
if ( RspData[2] == pIrpC->req[2] &&
RspData[3] == pIrpC->req[3] ) {
//
// We have received an ACK frame.
//
DebugTrace(+0, Dbg, "Received positive acknowledge\n", 0 );
NwProcessPositiveAck( pNpScb );
}
break;
} else if ( *(USHORT UNALIGNED *)&RspData[0] == PEP_COMMAND_BURST ) {
//
// This is a stray burst response, ignore it.
//
AcceptPacket = FALSE;
break;
} else if ( *(USHORT UNALIGNED *)&RspData[0] != PEP_COMMAND_RESPONSE ) {
//
// We have received an invalid frame.
//
DbgPrintf ( "***exchange: invalid Response\n" );
AcceptPacket = FALSE;
break;
} else if ( pIrpC->PacketType == NCP_CONNECT ) {
pNpScb->SequenceNo = RspData[2];
pNpScb->ConnectionNo = RspData[3];
pNpScb->ConnectionNoHigh = RspData[5];
// We should now continue to process the Connect
break;
}
//
// Make sure this the response we expect.
//
if ( !VerifyResponse( pIrpC, RspData ) ) {
//
// This is a stray or corrupt response. Ignore it.
//
AcceptPacket = FALSE;
break;
} else {
//
// We have received a valid, in sequence response.
// Bump the current sequence number.
//
++pNpScb->SequenceNo;
}
if ( pIrpC->PacketType == NCP_FUNCTION ||
pIrpC->PacketType == NCP_SUBFUNCTION ) {
if ( ( RspData[7] &
( NCP_STATUS_BAD_CONNECTION |
NCP_STATUS_NO_CONNECTIONS ) ) != 0 ) {
//
// We've lost our connection to the server.
// Try to reconnect if it is allowed for this request.
//
pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
if ( BooleanFlagOn( pIrpC->Flags, IRP_FLAG_RECONNECTABLE ) ) {
ClearFlag( pIrpC->Flags, IRP_FLAG_RECONNECTABLE );
AcceptPacket = FALSE;
if (!pNpScb->Sending) {
ScheduleReconnectRetry( pIrpC );
pNpScb->OkToReceive = FALSE;
} else {
//
// If we are sending, it is not OK schedule the
// retry now, because if we do and the send
// completion hasnt been run we could end up
// with 2 guys thinking they are at the front
// of the queue. We let the send complete and
// wait for that to fail instead. We will
// eventually reconnect.
//
}
}
break;
} else if ( ( RspData[7] & NCP_STATUS_SHUTDOWN ) != 0 ) {
//
// This server's going down. We need to process this
// message in the FSP. Copy the indicated data and
// process in the FSP.
//
pNpScb->State = SCB_STATE_ATTACHING;
AcceptPacket = FALSE;
pNpScb->OkToReceive = FALSE;
pNpScb->Receiving = TRUE;
CopyIndicatedData(
pIrpC,
RspData,
BytesIndicated,
BytesTaken,
ReceiveDatagramFlags );
pIrpC->PostProcessRoutine = FspProcessServerDown;
Status = NwPostToFsp( pIrpC, FALSE );
break;
}
} else if ( pIrpC->PacketType == NCP_DISCONNECT ) {
//
// We have received a disconnect frame.
//
break;
}
}
process_packet:
if ( AcceptPacket ) {
ASSERT ( !IsListEmpty( &pNpScb->Requests ));
ASSERT( pIrpC->pEx != NULL );
//
// If we received this packet without a retry, adjust the
// send timeout value.
//
if (( !BooleanFlagOn( pIrpC->Flags, IRP_FLAG_RETRY_SEND ) ) &&
( pIrpC->PacketType != NCP_BURST )) {
SHORT NewTimeout;
NewTimeout = ( pNpScb->SendTimeout + pNpScb->TickCount ) / 2;
//
// tommye - MS bug 10511 - added code to set pNpScb->TimeOut
// to sames as pNpScb->SendTimeout per bug report recommendation.
//
pNpScb->TimeOut = pNpScb->SendTimeout = MAX( NewTimeout, pNpScb->TickCount + 1 );
DebugTrace( 0, Dbg, "Successful exchange, new send timeout = %d\n", pNpScb->SendTimeout );
}
//
// If the transport didn't indicate all of the data, we'll need
// to post a receive IRP.
//
#ifdef NWDBG
if (( BytesIndicated < BytesAvailable ) ||
( AlwaysAllocateIrp )){
#else
if ( BytesIndicated < BytesAvailable ) {
#endif
if ( ( BooleanFlagOn( pIrpC->Flags, IRP_FLAG_BURST_REQUEST ) ) &&
( IsListEmpty( &pIrpC->Specific.Read.PacketList ) ) ) {
pBurstRsp = (PNCP_BURST_READ_RESPONSE)RspData;
BurstStatus = NwBurstResultToNtStatus( pBurstRsp->Result );
//
// If this entire burst failed with an error, we can't
// let the receive data routine signal the caller until
// the pEx gets called and we exit on the correct paths.
//
if ( !NT_SUCCESS( BurstStatus ) ) {
DebugTrace( 0, Dbg, "Special burst termination %08lx.\n", BurstStatus );
pIrpC->Specific.Read.Status = BurstStatus;
if ( pNpScb->Sending ) {
//
// If the send hasn't completed yet, we can't accept
// the packet because IPX may not have completed back
// to us yet!
//
KeReleaseSpinLockFromDpcLevel(&pNpScb->NpScbSpinLock );
DebugTrace(-1, Dbg, "ServerDatagramHandler -> STATUS_DATA_NOT_ACCEPTED (%08lx)\n", BurstStatus );
return( STATUS_DATA_NOT_ACCEPTED );
} else {
//
// Handle this one just like normal, except that we
// know it's going to fail in the receive data routine
// and we don't want the timeout routine to fire
// causing us all sort of grief, so we set OkToReceive
// to FALSE.
//
pNpScb->OkToReceive = FALSE;
}
}
}
FreeReceiveIrp( pIrpC ); // Free old Irp if one was allocated
Status = AllocateReceiveIrp(
pIrpC,
RspData,
BytesAvailable,
BytesTaken,
pTdiStruct );
if (Status == STATUS_MORE_PROCESSING_REQUIRED) {
pNpScb->OkToReceive = FALSE;
pNpScb->Receiving = TRUE;
} else if (!NT_SUCCESS( Status ) ) {
pIrpC->ReceiveIrp = NULL;
Status = STATUS_INSUFFICIENT_RESOURCES;
}
KeReleaseSpinLockFromDpcLevel(&pNpScb->NpScbSpinLock );
*IoRequestPacket = pIrpC->ReceiveIrp;
} else {
pNpScb->OkToReceive = FALSE;
//
// The transport has indicated all of the data.
// If the send has completed, call the pEx routine,
// otherwise copy the data to a buffer and let the
// send completion routine call the pEx routine.
//
if ( pNpScb->Sending ) {
DebugTrace( 0, Dbg, "Received data before send completion\n", 0 );
Status = CopyIndicatedData(
pIrpC,
RspData,
BytesIndicated,
BytesTaken,
ReceiveDatagramFlags );
if (NT_SUCCESS(Status)) {
pNpScb->Received = TRUE;
pNpScb->Receiving = TRUE;
} else {
// Ignore this packet
pNpScb->OkToReceive = TRUE;
}
KeReleaseSpinLockFromDpcLevel(&pNpScb->NpScbSpinLock );
} else {
pNpScb->Receiving = FALSE;
pNpScb->Received = FALSE;
KeReleaseSpinLockFromDpcLevel(&pNpScb->NpScbSpinLock );
DebugTrace(+0, Dbg, "Call pIrpC->pEx %x\n", pIrpC->pEx );
Status = pIrpC->pEx(pIrpC,
BytesAvailable,
RspData);
}
*BytesTaken = BytesAvailable;
}
} else { //(!AcceptPacket)
KeReleaseSpinLockFromDpcLevel(&pNpScb->NpScbSpinLock );
Status = STATUS_DATA_NOT_ACCEPTED;
}
Stats.NcpsReceived.QuadPart++;
Stats.BytesReceived.QuadPart += BytesAvailable;
DebugTrace(-1, Dbg, "ServerDatagramHandler -> %08lx\n", Status );
return( Status );
} // ServerDatagramHandler
NTSTATUS
CopyIndicatedData(
PIRP_CONTEXT pIrpContext,
PCHAR ReceiveData,
ULONG BytesIndicated,
PULONG BytesAccepted,
ULONG ReceiveDatagramFlags
)
/*++
Routine Description:
This routine copies indicated data to a buffer. If the packet is small
enough the data is copied to the preallocated receive buffer in the
IRP context. If the packet is too long, a new buffer is allocated.
Arguments:
pIrpContext - A pointer the block of context information for the request
in progress.
ReceiveData - A pointer to the indicated data.
BytesIndicated - The number of bytes available in the received packet.
BytesAccepted - Returns the number of bytes accepted by the receive
routine.
ReceiveDatagramFlags - Receive flags given to us by the transport.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
NTSTATUS Status;
PMDL ReceiveMdl;
PVOID MappedVa;
ULONG BytesToCopy;
BOOLEAN DeleteMdl = FALSE;
pIrpContext->ResponseLength = BytesIndicated;
//
// If there is a receive data routine, use it to generate the receive
// MDL, otherwise use the default MDL.
//
if ( pIrpContext->ReceiveDataRoutine != NULL ) {
Status = pIrpContext->ReceiveDataRoutine(
pIrpContext,
BytesIndicated,
BytesAccepted,
ReceiveData,
&ReceiveMdl );
if ( !NT_SUCCESS( Status ) ) {
return( Status );
}
//
// We can accept up to the size of a burst read header, plus
// 3 bytes of fluff for the unaligned read case.
//
ASSERT( *BytesAccepted <= sizeof(NCP_BURST_READ_RESPONSE) + 3 );
BytesIndicated -= *BytesAccepted;
ReceiveData += *BytesAccepted;
DeleteMdl = TRUE;
} else {
*BytesAccepted = 0;
ReceiveMdl = pIrpContext->RxMdl;
}
if ( ReceiveMdl != NULL ) {
while ( BytesIndicated > 0 && ReceiveMdl != NULL ) {
MappedVa = MmGetSystemAddressForMdlSafe( ReceiveMdl, NormalPagePriority );
BytesToCopy = MIN( MmGetMdlByteCount( ReceiveMdl ), BytesIndicated );
TdiCopyLookaheadData( MappedVa, ReceiveData, BytesToCopy, ReceiveDatagramFlags );
ReceiveMdl = ReceiveMdl->Next;
BytesIndicated -= BytesToCopy;
ReceiveData += BytesToCopy;
ASSERT( !( BytesIndicated != 0 && ReceiveMdl == NULL ) );
}
if (DeleteMdl) {
PMDL Mdl = pIrpContext->Specific.Read.PartialMdl;
PMDL NextMdl;
while ( Mdl != NULL ) {
NextMdl = Mdl->Next;
DebugTrace( 0, Dbg, "Freeing MDL %x\n", Mdl );
FREE_MDL( Mdl );
Mdl = NextMdl;
}
pIrpContext->Specific.Read.PartialMdl = NULL;
}
}
return( STATUS_SUCCESS );
}
NTSTATUS
AllocateReceiveIrp(
PIRP_CONTEXT pIrpContext,
PVOID ReceiveData,
ULONG BytesAvailable,
PULONG BytesAccepted,
PNW_TDI_STRUCT pTdiStruct
)
/*++
Routine Description:
This routine allocates an IRP and if necessary a receive buffer. It
then builds an MDL for the buffer and formats the IRP to do a TDI
receive.
Arguments:
pIrpContext - A pointer the block of context information for the request
in progress.
ReceiveData - The indicated data.
BytesAvailable - The number of bytes available in the received packet.
BytesAccepted - Returns the number of bytes accepted from the packet.
pTdiStruct - A pointer to the TdiStruct which has indicated the receive.
Return Value:
NTSTATUS - Status of receive operation
STATUS_MORE_PROCESSING_REQUIRED means we were successful.
--*/
{
PIRP Irp = NULL;
NTSTATUS Status = STATUS_SUCCESS;
ASSERT( pTdiStruct != NULL );
Irp = ALLOCATE_IRP( pIrpContext->pNpScb->Server.pDeviceObject->StackSize, FALSE );
if ( Irp == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanExit;
}
//
// If there is no receive data routine for this IRP, the
// RxMdl must point to a valid place to put the data.
//
// If there is a ReceiveDataRoutine it will build an MDL
//
if ( pIrpContext->ReceiveDataRoutine == NULL ) {
ULONG LengthOfMdl;
LengthOfMdl = MdlLength( pIrpContext->RxMdl );
//
// If the server sent more data than we can receive, simply
// ignore the excess. In particular 3.11 pads long name
// response with an excess of junk.
//
if ( BytesAvailable > LengthOfMdl ) {
BytesAvailable = LengthOfMdl;
}
Irp->MdlAddress = pIrpContext->RxMdl;
*BytesAccepted = 0;
} else {
Status = pIrpContext->ReceiveDataRoutine(
pIrpContext,
BytesAvailable,
BytesAccepted,
ReceiveData,
&Irp->MdlAddress );
if ( !NT_SUCCESS( Status ) ||
Irp->MdlAddress == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanExit;
}
SetFlag( pIrpContext->Flags, IRP_FLAG_FREE_RECEIVE_MDL );
}
CleanExit:
if ( !NT_SUCCESS( Status ) ) {
if ( Irp != NULL ) {
FREE_IRP( Irp );
}
Irp = NULL;
pIrpContext->ReceiveIrp = NULL;
Status = STATUS_DATA_NOT_ACCEPTED;
return( Status );
}
pIrpContext->ReceiveIrp = Irp;
Status = STATUS_MORE_PROCESSING_REQUIRED;
pIrpContext->ResponseLength = BytesAvailable;
TdiBuildReceive(
Irp,
pTdiStruct->pDeviceObject,
pTdiStruct->pFileObject,
ReceiveIrpCompletion,
pIrpContext,
Irp->MdlAddress,
0,
BytesAvailable - *BytesAccepted );
IoSetNextIrpStackLocation( Irp );
return( Status );
}
NTSTATUS
ReceiveIrpCompletion(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
/*++
Routine Description:
This routine is called when a recieve IRP completes.
Arguments:
DeviceObject - Unused.
Irp - The IRP that completed.
Context - A pointer the block of context information for the request
in progress.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
PIRP_CONTEXT IrpContext = (PIRP_CONTEXT)Context;
PIO_STACK_LOCATION IrpSp;
PNONPAGED_SCB pNpScb;
PMDL Mdl, NextMdl;
KIRQL OldIrql;
ASSERT( Irp == IrpContext->ReceiveIrp );
pNpScb = IrpContext->pNpScb;
IrpSp = IoGetCurrentIrpStackLocation( Irp );
//
// Free the IRP MDL if we allocated one specifically for this IRP.
//
if ( BooleanFlagOn( IrpContext->Flags, IRP_FLAG_FREE_RECEIVE_MDL ) ) {
Mdl = IrpContext->Specific.Read.PartialMdl;
IrpContext->Specific.Read.PartialMdl = NULL;
while ( Mdl != NULL ) {
NextMdl = Mdl->Next;
DebugTrace( 0, Dbg, "Freeing MDL %x\n", Mdl );
FREE_MDL( Mdl );
Mdl = NextMdl;
}
}
if ( !NT_SUCCESS( Irp->IoStatus.Status ) ) {
//
// Failed to receive the data. Wait for more.
//
pNpScb->OkToReceive = TRUE;
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
// If the send has completed, call the pEx routine,
// otherwise copy the data to a buffer and let the
// send completion routine call the pEx routine.
//
KeAcquireSpinLock( &pNpScb->NpScbSpinLock, &OldIrql );
if ( pNpScb->Sending ) {
DebugTrace( 0, Dbg, "Received data before send completion\n", 0 );
//
// Tell send completion to call pEx.
//
pNpScb->Received = TRUE;
KeReleaseSpinLock(&pNpScb->NpScbSpinLock, OldIrql );
} else {
pNpScb->Receiving = FALSE;
pNpScb->Received = FALSE;
KeReleaseSpinLock( &pNpScb->NpScbSpinLock, OldIrql );
DebugTrace(+0, Dbg, "Call pIrpC->pEx %x\n", IrpContext->pEx );
IrpContext->pEx(
IrpContext,
IrpContext->ResponseLength,
IrpContext->rsp );
}
return STATUS_MORE_PROCESSING_REQUIRED;
}
VOID
FreeReceiveIrp(
PIRP_CONTEXT IrpContext
)
/*++
Routine Description:
This routine frees a IRP that was allocated to do a receive.
Arguments:
IrpContext - A pointer the block of context information for the request
in progress.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
if ( IrpContext->ReceiveIrp == NULL ) {
return;
}
FREE_IRP( IrpContext->ReceiveIrp );
IrpContext->ReceiveIrp = NULL;
}
NTSTATUS
WatchDogDatagramHandler(
IN PVOID TdiEventContext,
IN int SourceAddressLength,
IN PVOID SourceAddress,
IN int OptionsLength,
IN PVOID Options,
IN ULONG ReceiveDatagramFlags,
IN ULONG BytesIndicated,
IN ULONG BytesAvailable,
OUT ULONG *BytesTaken,
IN PVOID Tsdu,
OUT PIRP *IoRequestPacket
)
/*++
Routine Description:
This routine is the receive datagram event indication handler for the
Server socket.
Arguments:
TdiEventContext - Context provided for this event, a pointer to the
non paged SCB.
SourceAddressLength - Length of the originator of the datagram.
SourceAddress - String describing the originator of the datagram.
OptionsLength - Length of the buffer pointed to by Options.
Options - Options for the receive.
ReceiveDatagramFlags - Ignored.
BytesIndicated - Number of bytes this indication.
BytesAvailable - Number of bytes in complete Tsdu.
BytesTaken - Returns the number of bytes used.
Tsdu - Pointer describing this TSDU, typically a lump of bytes.
IoRequestPacket - TdiReceive IRP if MORE_PROCESSING_REQUIRED.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
PNONPAGED_SCB pNpScb = (PNONPAGED_SCB)TdiEventContext;
PUCHAR RspData = (PUCHAR)Tsdu;
*IoRequestPacket = NULL;
//
// Transport will complete the processing of the request, we don't
// want the datagram.
//
DebugTrace(+1, Dbg, "WatchDogDatagramHandler\n", 0);
DebugTrace(+0, Dbg, "SourceAddressLength %x\n", SourceAddressLength);
DebugTrace(+0, Dbg, "BytesIndicated %x\n", BytesIndicated);
DebugTrace(+0, Dbg, "BytesAvailable %x\n", BytesAvailable);
DebugTrace(+0, Dbg, "BytesTaken %x\n", *BytesTaken);
//
// SourceAddress is the address of the server or the bridge tbat sent
// the packet.
//
#if NWDBG
dump( Dbg, SourceAddress, SourceAddressLength );
dump( Dbg, Tsdu, BytesIndicated );
#endif
if (pNpScb->NodeTypeCode != NW_NTC_SCBNP ) {
DebugTrace(+0, 0, "nwrdr: Invalid Watchdog Indication %x\n", pNpScb );
#if DBG
DbgBreakPoint();
#endif
return STATUS_DATA_NOT_ACCEPTED;
}
Stats.NcpsReceived.QuadPart++;
Stats.BytesReceived.QuadPart += BytesAvailable;
if ( RspData[1] == NCP_SEARCH_CONTINUE ) {
PIRP pIrp;
PIRP_CONTEXT pIrpContext;
pIrp = ALLOCATE_IRP( pNpScb->WatchDog.pDeviceObject->StackSize, FALSE);
if (pIrp == NULL) {
DebugTrace(-1, Dbg, " %lx\n", STATUS_DATA_NOT_ACCEPTED);
return STATUS_DATA_NOT_ACCEPTED;
}
try {
pIrpContext = AllocateIrpContext( pIrp );
} except( EXCEPTION_EXECUTE_HANDLER ) {
FREE_IRP( pIrp );
DebugTrace(-1, Dbg, " %lx\n", STATUS_DATA_NOT_ACCEPTED);
return STATUS_DATA_NOT_ACCEPTED;
}
pIrpContext->req[0] = pNpScb->ConnectionNo;
//
// Response 'Y' or connection is valid and its from the right server,
// or 'N' if it is not.
//
if (( RspData[0] == pNpScb->ConnectionNo ) &&
( RtlCompareMemory(
((PTA_IPX_ADDRESS)SourceAddress)->Address[0].Address,
&pNpScb->ServerAddress,
8) == 8 ))
{
LARGE_INTEGER KillTime, Now;
BOOL ScbIsOld ;
//
// Check if this is a not-logged-in SCB that has not been used
// for while. If it is, answer NO. In attach.c, we dont disconnect
// from a nearest server immediately to avoid the re-connect
// overheads. This is where we time it out.
//
KeQuerySystemTime( &Now );
KillTime.QuadPart = Now.QuadPart - ( NwOneSecond * DORMANT_SCB_KEEP_TIME);
ScbIsOld = ((pNpScb->State == SCB_STATE_LOGIN_REQUIRED) &&
(pNpScb->LastUsedTime.QuadPart < KillTime.QuadPart)) ;
pIrpContext->req[1] = ScbIsOld ? 'N' : 'Y';
if (ScbIsOld)
{
pNpScb->State = SCB_STATE_RECONNECT_REQUIRED ;
//
//---- Multi-user code merge ----
//
Stats.Sessions--;
if ( pNpScb->MajorVersion == 2 ) {
Stats.NW2xConnects--;
} else if ( pNpScb->MajorVersion == 3 ) {
Stats.NW3xConnects--;
} else if ( pNpScb->MajorVersion == 4 ) {
Stats.NW4xConnects--;
}
//---------------------------------
}
DebugTrace(-1,Dbg,"WatchDog Response: %s\n", ScbIsOld ? "N" : "Y");
} else {
pIrpContext->req[1] = 'N';
}
pIrpContext->TxMdl->ByteCount = 2;
pIrpContext->ConnectionInformation.UserDataLength = 0;
pIrpContext->ConnectionInformation.OptionsLength = sizeof( UCHAR );
pIrpContext->ConnectionInformation.Options = &SapPacketType;
pIrpContext->ConnectionInformation.RemoteAddressLength = sizeof(TA_IPX_ADDRESS);
pIrpContext->ConnectionInformation.RemoteAddress = &pIrpContext->Destination;
BuildIpxAddress(
((PTA_IPX_ADDRESS)SourceAddress)->Address[0].Address[0].NetworkAddress,
((PTA_IPX_ADDRESS)SourceAddress)->Address[0].Address[0].NodeAddress,
((PTA_IPX_ADDRESS)SourceAddress)->Address[0].Address[0].Socket,
&pIrpContext->Destination);
TdiBuildSendDatagram(
pIrpContext->pOriginalIrp,
pNpScb->WatchDog.pDeviceObject,
pNpScb->WatchDog.pFileObject,
&CompletionWatchDogSend,
pIrpContext,
pIrpContext->TxMdl,
MdlLength(pIrpContext->TxMdl),
&pIrpContext->ConnectionInformation);
IoCallDriver(
pNpScb->WatchDog.pDeviceObject,
pIrpContext->pOriginalIrp );
}
DebugTrace(-1, Dbg, " %lx\n", STATUS_DATA_NOT_ACCEPTED);
return STATUS_DATA_NOT_ACCEPTED;
UNREFERENCED_PARAMETER( SourceAddressLength );
UNREFERENCED_PARAMETER( BytesIndicated );
UNREFERENCED_PARAMETER( BytesAvailable );
UNREFERENCED_PARAMETER( BytesTaken );
UNREFERENCED_PARAMETER( Tsdu );
UNREFERENCED_PARAMETER( OptionsLength );
UNREFERENCED_PARAMETER( Options );
}
NTSTATUS
CompletionWatchDogSend(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This routine does not complete the Irp. It is used to signal to a
synchronous part of the driver that it can proceed.
Arguments:
DeviceObject - unused.
Irp - Supplies Irp that the transport has finished processing.
Context - Supplies the IrpContext associated with the Irp.
Return Value:
The STATUS_MORE_PROCESSING_REQUIRED so that the IO system stops
processing Irp stack locations at this point.
--*/
{
PIRP_CONTEXT pIrpC = (PIRP_CONTEXT) Context;
//
// Avoid completing the Irp because the Mdl etc. do not contain
// their original values.
//
DebugTrace( +1, Dbg, "CompletionWatchDogSend\n", 0);
DebugTrace( +0, Dbg, "Irp %X\n", Irp);
DebugTrace( -1, Dbg, "pIrpC %X\n", pIrpC);
FREE_IRP( pIrpC->pOriginalIrp );
pIrpC->pOriginalIrp = NULL; // Avoid FreeIrpContext modifying freed Irp.
FreeIrpContext( pIrpC );
return STATUS_MORE_PROCESSING_REQUIRED;
UNREFERENCED_PARAMETER( DeviceObject );
UNREFERENCED_PARAMETER( Irp );
}
NTSTATUS
SendDatagramHandler(
IN PVOID TdiEventContext,
IN int SourceAddressLength,
IN PVOID SourceAddress,
IN int OptionsLength,
IN PVOID Options,
IN ULONG ReceiveDatagramFlags,
IN ULONG BytesIndicated,
IN ULONG BytesAvailable,
OUT ULONG *BytesTaken,
IN PVOID Tsdu,
OUT PIRP *IoRequestPacket
)
/*++
Routine Description:
This routine is the receive datagram event indication handler for the
Server socket.
Arguments:
TdiEventContext - Context provided for this event, a pointer to the
non paged SCB.
SourceAddressLength - Length of the originator of the datagram.
SourceAddress - String describing the originator of the datagram.
OptionsLength - Length of the buffer pointed to by Options.
Options - Options for the receive.
ReceiveDatagramFlags - Ignored.
BytesIndicated - Number of bytes this indication.
BytesAvailable - Number of bytes in complete Tsdu.
BytesTaken - Returns the number of bytes used.
Tsdu - Pointer describing this TSDU, typically a lump of bytes.
IoRequestPacket - TdiReceive IRP if MORE_PROCESSING_REQUIRED.
Return Value:
NTSTATUS - Status of receive operation
--*/
{
PNONPAGED_SCB pNpScb = (PNONPAGED_SCB)TdiEventContext;
PUCHAR RspData = (PUCHAR)Tsdu;
PIRP_CONTEXT pIrpContext;
PLIST_ENTRY listEntry;
PIRP Irp;
*IoRequestPacket = NULL;
DebugTrace(0, Dbg, "SendDatagramHandler\n", 0);
Stats.NcpsReceived.QuadPart++;
Stats.BytesReceived.QuadPart += BytesAvailable;
//
// Transport will complete the processing of the request, we don't
// want the datagram.
//
DebugTrace(+1, Dbg, "SendDatagramHandler\n", 0);
DebugTrace(+0, Dbg, "SourceAddressLength %x\n", SourceAddressLength);
DebugTrace(+0, Dbg, "BytesIndicated %x\n", BytesIndicated);
DebugTrace(+0, Dbg, "BytesAvailable %x\n", BytesAvailable);
DebugTrace(+0, Dbg, "BytesTaken %x\n", *BytesTaken);
//
// SourceAddress is the address of the server or the bridge tbat sent
// the packet.
//
#if NWDBG
dump( Dbg, SourceAddress, SourceAddressLength );
dump( Dbg, Tsdu, BytesIndicated );
#endif
if (pNpScb->NodeTypeCode != NW_NTC_SCBNP ) {
DebugTrace(+0, Dbg, "nwrdr: Invalid SendDatagram Indication %x\n", pNpScb );
#if DBG
DbgBreakPoint();
#endif
return STATUS_DATA_NOT_ACCEPTED;
}
if (RspData[1] == BROADCAST_MESSAGE_WAITING ) {
//
// Broadcast message waiting. If the scavenger
// isn't running, it's safe to go get it.
//
KeAcquireSpinLockAtDpcLevel( &NwScavengerSpinLock );
if ( WorkerRunning ) {
//
// The scavenger is running, we can't pick up this
// message until the scavenger is done!
//
DebugTrace( 0, DEBUG_TRACE_ALWAYS, "Delaying get message for scavenger.\n", 0 );
KeReleaseSpinLockFromDpcLevel( &NwScavengerSpinLock );
} else {
//
// Make sure the scavenger doesn't start.
//
WorkerRunning = TRUE;
KeReleaseSpinLockFromDpcLevel( &NwScavengerSpinLock );
listEntry = ExInterlockedRemoveHeadList(
&NwGetMessageList,
&NwMessageSpinLock );
if ( listEntry != NULL ) {
pIrpContext = CONTAINING_RECORD( listEntry, IRP_CONTEXT, NextRequest );
//
// Clear the cancel routine for this IRP.
//
Irp = pIrpContext->pOriginalIrp;
IoAcquireCancelSpinLock( &Irp->CancelIrql );
IoSetCancelRoutine( Irp, NULL );
IoReleaseCancelSpinLock( Irp->CancelIrql );
pIrpContext->PostProcessRoutine = FspGetMessage;
pIrpContext->pNpScb = pNpScb;
pIrpContext->pScb = pNpScb->pScb;
NwPostToFsp( pIrpContext, TRUE );
} else {
WorkerRunning = FALSE;
}
}
}
DebugTrace(-1, Dbg, " %lx\n", STATUS_DATA_NOT_ACCEPTED);
return STATUS_DATA_NOT_ACCEPTED;
UNREFERENCED_PARAMETER( SourceAddressLength );
UNREFERENCED_PARAMETER( BytesIndicated );
UNREFERENCED_PARAMETER( BytesAvailable );
UNREFERENCED_PARAMETER( BytesTaken );
UNREFERENCED_PARAMETER( Tsdu );
UNREFERENCED_PARAMETER( OptionsLength );
UNREFERENCED_PARAMETER( Options );
}
NTSTATUS
FspGetMessage(
IN PIRP_CONTEXT IrpContext
)
/*++
Routine Description:
This routine continues process a broadcast message waiting message.
Arguments:
pIrpContext - A pointer to the IRP context information for the
request in progress.
Return Value:
The status of the operation.
--*/
{
KIRQL OldIrql;
PLIST_ENTRY ScbQueueEntry;
PNONPAGED_SCB pNpScb;
UNICODE_STRING Message;
NTSTATUS Status;
PNWR_SERVER_MESSAGE ServerMessage;
PUNICODE_STRING ServerName;
ULONG MessageLength;
short int i;
PAGED_CODE();
NwReferenceUnlockableCodeSection();
//
// The Scb may be being deleted so carefully walk the list and reference it if
// we find it.
//
KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
ScbQueueEntry = ScbQueue.Flink;
while ( ScbQueueEntry != &ScbQueue ) {
pNpScb = CONTAINING_RECORD( ScbQueueEntry, NONPAGED_SCB, ScbLinks );
if (pNpScb == IrpContext->pNpScb ) {
NwReferenceScb( pNpScb );
break;
}
ScbQueueEntry = ScbQueueEntry->Flink;
}
KeReleaseSpinLock( &ScbSpinLock, OldIrql );
if (pNpScb != IrpContext->pNpScb ) {
//
// Server deleted. Its easiest to continue processing the IrpContext
// with an error than try to recover it and return it to the queue.
//
Status = STATUS_UNSUCCESSFUL;
NwDereferenceUnlockableCodeSection();
//
// Re-enable the scavenger before we return!
//
WorkerRunning = FALSE;
return( Status );
}
//
// If the message is telling us that the server is going down then don't
// work too hard trying to get the message. The server is persistent with
// respect to other messages so we'll come through here again when the
// problem has been resolved.
//
SetFlag( IrpContext->Flags, IRP_FLAG_REROUTE_ATTEMPTED );
if ( UP_LEVEL_SERVER( IrpContext->pScb ) ) {
Status = ExchangeWithWait(
IrpContext,
SynchronousResponseCallback,
"S",
NCP_MESSAGE_FUNCTION, NCP_GET_ENTIRE_MESSAGE );
} else {
Status = ExchangeWithWait(
IrpContext,
SynchronousResponseCallback,
"S",
NCP_MESSAGE_FUNCTION, NCP_GET_MESSAGE );
}
if ( !NT_SUCCESS( Status ) ) {
NwDereferenceScb( pNpScb );
NwDereferenceUnlockableCodeSection();
//
// Re-enable the scavenger before we return!
//
WorkerRunning = FALSE;
return( Status );
}
ServerMessage = (PNWR_SERVER_MESSAGE)IrpContext->Specific.FileSystemControl.Buffer;
MessageLength = IrpContext->Specific.FileSystemControl.Length;
ServerName = &IrpContext->pNpScb->ServerName;
if ( ServerName->Length + FIELD_OFFSET( NWR_SERVER_MESSAGE, Server ) + sizeof(WCHAR) > MessageLength ) {
Status = STATUS_BUFFER_TOO_SMALL;
NwDereferenceScb( pNpScb );
NwDereferenceUnlockableCodeSection();
//
// Re-enable the scavenger before we return!
//
WorkerRunning = FALSE;
return( Status );
} else {
// --- Multi-user -------------
// Need Login ID to send messages
//
ServerMessage->LogonId = *((PLUID)&IrpContext->pScb->UserUid);
//
// Copy the server name to the output buffer.
//
ServerMessage->MessageOffset =
ServerName->Length +
FIELD_OFFSET( NWR_SERVER_MESSAGE, Server ) +
sizeof(WCHAR);
RtlMoveMemory(
ServerMessage->Server,
ServerName->Buffer,
ServerName->Length );
ServerMessage->Server[ ServerName->Length / sizeof(WCHAR) ] = L'\0';
}
//
// Copy the message to the user's buffer.
//
Message.Buffer = &ServerMessage->Server[ ServerName->Length / sizeof(WCHAR) ] + 1;
Message.MaximumLength = (USHORT)( MessageLength - ( ServerName->Length + FIELD_OFFSET( NWR_SERVER_MESSAGE, Server ) + sizeof(WCHAR) ) );
if ( NT_SUCCESS( Status) ) {
Status = ParseResponse(
IrpContext,
IrpContext->rsp,
IrpContext->ResponseLength,
"NP",
&Message );
}
if ( !NT_SUCCESS( Status ) ) {
NwDereferenceScb( pNpScb );
NwDereferenceUnlockableCodeSection();
//
// Re-enable the scavenger before we return!
//
WorkerRunning = FALSE;
return( Status );
}
//
// Strip the trailing spaces and append a NUL terminator to the message.
//
for ( i = Message.Length / sizeof(WCHAR) - 1; i >= 0 ; i-- ) {
if ( Message.Buffer[ i ] != L' ') {
Message.Length = (i + 1) * sizeof(WCHAR);
break;
}
}
if ( Message.Length > 0 ) {
Message.Buffer[ Message.Length / sizeof(WCHAR) ] = L'\0';
}
IrpContext->pOriginalIrp->IoStatus.Information =
ServerName->Length +
FIELD_OFFSET( NWR_SERVER_MESSAGE, Server ) + sizeof(WCHAR) +
Message.Length + sizeof(WCHAR);
NwDereferenceScb( pNpScb );
NwDereferenceUnlockableCodeSection();
//
// Re-enable the scavenger before we return!
//
WorkerRunning = FALSE;
return( Status );
}
NTSTATUS
_cdecl
ExchangeWithWait(
PIRP_CONTEXT pIrpContext,
PEX pEx,
char* f,
... // format specific parameters
)
/*++
Routine Description:
This routine sends a NCP packet and waits for the response.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
pEX, Context, f - See _Exchange
Return Value:
NTSTATUS - Status of the operation.
--*/
{
NTSTATUS Status;
va_list Arguments;
PAGED_CODE();
//KeResetEvent( &pIrpContext->Event );
va_start( Arguments, f );
Status = FormatRequest( pIrpContext, pEx, f, Arguments );
if ( !NT_SUCCESS( Status )) {
return( Status );
}
va_end( Arguments );
Status = PrepareAndSendPacket( pIrpContext );
if ( !NT_SUCCESS( Status )) {
return( Status );
}
Status = KeWaitForSingleObject(
&pIrpContext->Event,
Executive,
KernelMode,
FALSE,
NULL
);
if ( !NT_SUCCESS( Status )) {
return( Status );
}
Status = pIrpContext->pOriginalIrp->IoStatus.Status;
if ( NT_SUCCESS( Status ) &&
pIrpContext->PacketType != SAP_BROADCAST ) {
Status = NwErrorToNtStatus( pIrpContext->ResponseParameters.Error );
}
return( Status );
}
BOOLEAN
VerifyResponse(
PIRP_CONTEXT pIrpContext,
PVOID Response
)
/*++
Routine Description:
This routine verifies that a received response is the expected
response for the current request.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Response - A pointer to the buffer containing the response.
Return Value:
TRUE - This is a valid response.
FALSE - This is an invalid response.
--*/
{
PNCP_RESPONSE pNcpResponse;
PNONPAGED_SCB pNpScb;
pNcpResponse = (PNCP_RESPONSE)Response;
pNpScb = pIrpContext->pNpScb;
if ( pNcpResponse->NcpHeader.ConnectionIdLow != pNpScb->ConnectionNo ) {
DebugTrace(+0, Dbg, "VerifyResponse, bad connection number\n", 0);
return( FALSE );
}
if ( pNcpResponse->NcpHeader.SequenceNumber != pNpScb->SequenceNo ) {
DebugTrace(+1, Dbg, "VerifyResponse, bad sequence number %x\n", 0);
DebugTrace(+0, Dbg, " pNcpResponse->NcpHeader.SequenceNumber %x\n",
pNcpResponse->NcpHeader.SequenceNumber);
DebugTrace(-1, Dbg, " pNpScb->SequenceNo %x\n", pNpScb->SequenceNo );
return( FALSE );
}
return( TRUE );
}
VOID
ScheduleReconnectRetry(
PIRP_CONTEXT pIrpContext
)
/*++
Routine Description:
This routine schedules an a reconnect attempt, and then resubmits
our request if the reconnect was successful.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Return Value:
None.
--*/
{
PWORK_CONTEXT workContext;
if (WorkerThreadRunning == TRUE) {
//
// Prepare the work context
//
workContext = AllocateWorkContext();
if (workContext == NULL) {
pIrpContext->pEx( pIrpContext, 0, NULL );
return;
}
workContext->pIrpC = pIrpContext;
workContext->NodeWorkCode = NWC_NWC_RECONNECT;
//
// and queue it.
//
DebugTrace( 0, Dbg, "Queueing reconnect work.\n", 0 );
KeInsertQueue( &KernelQueue,
&workContext->Next
);
} else {
//
// The worker thread is not running...
//
pIrpContext->pEx( pIrpContext, 0, NULL );
return;
}
}
VOID
ReconnectRetry(
IN PIRP_CONTEXT pIrpContext
)
/*++
Routine Description:
This routine attempts to reconnect to a disconnected server. If it
is successful it resubmits an existing request.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Return Value:
None.
--*/
{
PIRP_CONTEXT pNewIrpContext;
PSCB pScb, pNewScb;
PNONPAGED_SCB pNpScb;
NTSTATUS Status;
PAGED_CODE();
pNpScb = pIrpContext->pNpScb;
pScb = pNpScb->pScb;
Stats.Reconnects++;
if ( pScb == NULL ) {
pScb = pNpScb->pScb;
pIrpContext->pScb = pScb;
}
//
// Allocate a temporary IRP context to use to reconnect to the server
//
if ( !NwAllocateExtraIrpContext( &pNewIrpContext, pNpScb ) ) {
pIrpContext->pEx( pIrpContext, 0, NULL );
return;
}
pNewIrpContext->Specific.Create.UserUid = pScb->UserUid;
pNewIrpContext->pNpScb = pNpScb;
pNewIrpContext->pScb = pScb;
//
// Reset the sequence numbers.
//
pNpScb->SequenceNo = 0;
pNpScb->BurstSequenceNo = 0;
pNpScb->BurstRequestNo = 0;
//
// Now insert this new IrpContext to the head of the SCB queue for
// processing. We can get away with this because we own the IRP context
// currently at the front of the queue. With the RECONNECT_ATTEMPT
// flag set, ConnectScb() will not remove us from the head of the queue.
//
SetFlag( pNewIrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE );
SetFlag( pNewIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT );
ExInterlockedInsertHeadList(
&pNpScb->Requests,
&pNewIrpContext->NextRequest,
&pNpScb->NpScbSpinLock );
pNewScb = pNpScb->pScb;
Status = ConnectScb( &pNewScb,
pNewIrpContext,
&pNpScb->ServerName,
NULL,
NULL,
NULL,
FALSE,
FALSE,
TRUE );
if ( !NT_SUCCESS( Status ) ) {
//
// Couldn't reconnect. Free the extra IRP context, complete the
// original request with an error.
//
NwDequeueIrpContext( pNewIrpContext, FALSE );
NwFreeExtraIrpContext( pNewIrpContext );
pIrpContext->pEx( pIrpContext, 0, NULL );
return;
}
ASSERT( pNewScb == pScb );
//
// Try to reconnect the VCBs.
//
NwReopenVcbHandlesForScb( pNewIrpContext, pScb );
//
// Dequeue and free the bonus IRP context.
//
NwDequeueIrpContext( pNewIrpContext, FALSE );
NwFreeExtraIrpContext( pNewIrpContext );
//
// Resubmit the original request, with a new sequence number. Note that
// it's back at the front of the queue, but no longer reconnectable.
//
pIrpContext->req[2] = pNpScb->SequenceNo;
pIrpContext->req[3] = pNpScb->ConnectionNo;
pIrpContext->req[5] = pNpScb->ConnectionNoHigh;
PreparePacket( pIrpContext, pIrpContext->pOriginalIrp, pIrpContext->TxMdl );
SendNow( pIrpContext );
return;
}
NTSTATUS
NewRouteRetry(
IN PIRP_CONTEXT pIrpContext
)
/*++
Routine Description:
This routine attempts to establish a new route to a non-responding server.
If it is successful it resubmits the request in progress.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Return Value:
None.
--*/
{
NTSTATUS Status;
PNONPAGED_SCB pNpScb = pIrpContext->pNpScb;
LARGE_INTEGER CurrentTime = {0, 0};
PAGED_CODE();
//
// Don't bother to re-rip if we are shutting down.
//
if ( NwRcb.State != RCB_STATE_SHUTDOWN ) {
Status = GetNewRoute( pIrpContext );
} else {
Status = STATUS_REMOTE_NOT_LISTENING;
}
//
// Ask the transport to establish a new route to the server.
//
if ( !NT_SUCCESS( Status ) ) {
//
// Attempt to get new route failed, fail the current request.
//
pIrpContext->ResponseParameters.Error = ERROR_UNEXP_NET_ERR;
pIrpContext->pEx( pIrpContext, 0, NULL );
if ( pNpScb != &NwPermanentNpScb ) {
KeQuerySystemTime( &CurrentTime );
if ( CanLogTimeOutEvent( pNpScb->NwNextEventTime,
CurrentTime
)) {
Error(
EVENT_NWRDR_TIMEOUT,
STATUS_UNEXPECTED_NETWORK_ERROR,
NULL,
0,
1,
pNpScb->ServerName.Buffer );
//
// Set the LastEventTime to the CurrentTime
//
UpdateNextEventTime(
pNpScb->NwNextEventTime,
CurrentTime,
TimeOutEventInterval
);
}
pNpScb->State = SCB_STATE_ATTACHING;
}
} else {
//
// Got a new route, resubmit the request. Allow retries
// with the new route.
//
pIrpContext->pNpScb->RetryCount = DefaultRetryCount / 2;
PreparePacket( pIrpContext, pIrpContext->pOriginalIrp, pIrpContext->TxMdl );
SendNow( pIrpContext );
}
//
// Return STATUS_PENDING so that the FSP dispatcher doesn't complete
// this request.
//
return( STATUS_PENDING );
}
NTSTATUS
NewRouteBurstRetry(
IN PIRP_CONTEXT pIrpContext
)
/*++
Routine Description:
This routine attempts to establish a new route to a non-responding server.
If it is successful it resubmits the request in progress.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Return Value:
None.
--*/
{
NTSTATUS Status;
PIRP_CONTEXT pNewIrpContext;
PNONPAGED_SCB pNpScb = pIrpContext->pNpScb;
BOOLEAN LIPNegotiated ;
LARGE_INTEGER CurrentTime = {0, 0};
PAGED_CODE();
//
// Don't bother to re-rip if we are shutting down.
//
if ( NwRcb.State == RCB_STATE_SHUTDOWN ) {
return( STATUS_REMOTE_NOT_LISTENING );
}
//
// Ask the transport to establish a new route to the server.
//
Status = GetNewRoute( pIrpContext );
if ( NT_SUCCESS( Status ) ) {
//
// If this is a burst write, we must first complete the write
// request (there is no way to tell the server to abandon the write).
//
// Set packet size down to 512 to guarantee that the packets will be
// forwarded, and resend the burst data. Queue the new IRP context
// behind the burst write, so that we can establish a new burst
// connection.
//
// Note that ResubmitBurstWrite may complete the request and
// free the IrpContext.
//
pNpScb->RetryCount = DefaultRetryCount / 2;
if ( BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_BURST_WRITE ) ) {
Status = ResubmitBurstWrite( pIrpContext );
} else {
//
// Allocate a temporary IRP context to use to reconnect to the server
//
if ( NT_SUCCESS( Status ) ) {
if ( !NwAllocateExtraIrpContext( &pNewIrpContext, pNpScb ) ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
} else {
pNewIrpContext->Specific.Create.UserUid = pIrpContext->Specific.Create.UserUid;
SetFlag( pNewIrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE );
SetFlag( pNewIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT );
//
// Since we're doing this from a worker thread, we can't
// let the dpc timer schedule _another_ worker thread
// request if this also times out or we may deadlock
// the delayed work queue.
//
SetFlag( pNewIrpContext->Flags, IRP_FLAG_REROUTE_ATTEMPTED );
pNewIrpContext->pNpScb = pNpScb;
}
}
if ( NT_SUCCESS( Status ) ) {
//
// Insert this new IrpContext to the head of
// the SCB queue for processing. We can get away with this
// because we own the IRP context currently at the front of
// the queue.
//
ExInterlockedInsertHeadList(
&pNpScb->Requests,
&pNewIrpContext->NextRequest,
&pNpScb->NpScbSpinLock );
//
// Now prepare to resend the burst read.
//
PreparePacket( pIrpContext, pIrpContext->pOriginalIrp, pIrpContext->TxMdl );
//
// Renegotiate the burst connection, this will automatically re-sync
// the burst connection.
//
// TRACKING: We lose sizeof( NCP_BURST_WRITE_REQUEST ) each time
// we do this right now.
//
NegotiateBurstMode( pNewIrpContext, pNpScb, &LIPNegotiated );
//
// Reset the sequence numbers.
//
pNpScb->BurstSequenceNo = 0;
pNpScb->BurstRequestNo = 0;
//
// Dequeue and free the bonus IRP context.
//
ASSERT( pNpScb->Requests.Flink == &pNewIrpContext->NextRequest );
ExInterlockedRemoveHeadList(
&pNpScb->Requests,
&pNpScb->NpScbSpinLock );
ClearFlag( pNewIrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE );
NwFreeExtraIrpContext( pNewIrpContext );
//
// Got a new route, resubmit the request
//
Status = ResubmitBurstRead( pIrpContext );
}
}
}
if ( !NT_SUCCESS( Status ) ) {
//
// Attempt to get new route failed, fail the current request.
//
pIrpContext->ResponseParameters.Error = ERROR_UNEXP_NET_ERR;
pIrpContext->pEx( pIrpContext, 0, NULL );
if ( pNpScb != &NwPermanentNpScb ) {
KeQuerySystemTime( &CurrentTime );
if ( CanLogTimeOutEvent( pNpScb->NwNextEventTime,
CurrentTime
)) {
Error(
EVENT_NWRDR_TIMEOUT,
STATUS_UNEXPECTED_NETWORK_ERROR,
NULL,
0,
1,
pNpScb->ServerName.Buffer );
//
// Set the LastEventTime to the CurrentTime
//
UpdateNextEventTime(
pNpScb->NwNextEventTime,
CurrentTime,
TimeOutEventInterval
);
}
}
}
//
// Return STATUS_PENDING so that the FSP dispatcher doesn't complete
// this request.
//
return( STATUS_PENDING );
}
NTSTATUS
FspProcessServerDown(
PIRP_CONTEXT IrpContext
)
/*++
Routine Description:
This routine process a response with the server shutdown bit set.
It close all open handles for the server, and puts the server in
the attaching state.
Arguments:
pIrpContext - A pointer to the context information for this IRP.
Return Value:
STATUS_PENDING.
--*/
{
KIRQL OldIrql;
PNONPAGED_SCB pNpScb = IrpContext->pNpScb;
//
// Avoid the Scb from disappearing under us.
//
NwReferenceScb( pNpScb );
//
// Move the IrpContext from the front of the queue just in-case it
// owns the Rcb.
//
KeAcquireSpinLock( &IrpContext->pNpScb->NpScbSpinLock, &OldIrql );
if ( IrpContext->pNpScb->Sending ) {
//
// Let send completion call the pEx routine
//
IrpContext->pNpScb->Received = TRUE;
KeReleaseSpinLock( &IrpContext->pNpScb->NpScbSpinLock, OldIrql );
} else {
IrpContext->pNpScb->Receiving = FALSE;
IrpContext->pNpScb->Received = FALSE;
KeReleaseSpinLock( &IrpContext->pNpScb->NpScbSpinLock, OldIrql );
//
// Now call the callback routine.
//
IrpContext->pEx(
IrpContext,
IrpContext->ResponseLength,
IrpContext->rsp );
}
//
// Close all active handles for this server.
//
NwAcquireExclusiveRcb( &NwRcb, TRUE );
NwInvalidateAllHandlesForScb( pNpScb->pScb );
NwReleaseRcb( &NwRcb );
NwDereferenceScb( pNpScb );
//
// Return STATUS_PENDING so that the FSP process doesn't complete
// this request.
//
return( STATUS_PENDING );
}
VOID
NwProcessSendBurstFailure(
PNONPAGED_SCB NpScb,
USHORT MissingFragmentCount
)
/*++
Routine Description:
This routine adjust burst parameters after an unsuccessful burst operation.
Arguments:
NpScb - A pointer to the SCB that has experienced a burst failure.
MissingFragmentCount - A measure of how many chunks were lost.
Return Value:
None.
--*/
{
LONG temp;
DebugTrace( 0, DEBUG_TRACE_LIP, "Burst failure, NpScb = %X\n", NpScb );
if ( NpScb->NwSendDelay != NpScb->CurrentBurstDelay ) {
//
// This burst has already failed
//
return;
}
NpScb->NwBadSendDelay = NpScb->NwSendDelay;
//
// Add to the send delay. Never let it go above 5000ms.
//
temp = NpScb->NwGoodSendDelay - NpScb->NwBadSendDelay;
if (temp >= 0) {
NpScb->NwSendDelay += temp + 2;
} else {
NpScb->NwSendDelay += -temp + 2;
}
if ( NpScb->NwSendDelay > NpScb->NwMaxSendDelay ) {
NpScb->NwSendDelay = NpScb->NwMaxSendDelay;
//
// If we have slowed down a lot then it might be that the server or a
// bridge only has a small buffer on its NIC. If this is the case then
// rather than sending a big burst with long even gaps between the
// packets, we should try to send a burst the size of the buffer.
//
if ( !DontShrink ) {
if (((NpScb->MaxSendSize - 1) / NpScb->MaxPacketSize) > 2 ) {
// Round down to the next packet
NpScb->MaxSendSize = ((NpScb->MaxSendSize - 1) / NpScb->MaxPacketSize) * NpScb->MaxPacketSize;
//
// Adjust SendDelay below threshold to see if things improve before
// we shrink the size again.
//
NpScb->NwSendDelay = NpScb->NwGoodSendDelay = NpScb->NwBadSendDelay = MinSendDelay;
} else {
//
// We reached the minimum size with the maximum delay. Give up on burst.
//
NpScb->SendBurstModeEnabled = FALSE;
}
}
}
NpScb->NtSendDelay.QuadPart = NpScb->NwSendDelay * -1000 ;
DebugTrace( 0, DEBUG_TRACE_LIP, "New Send Delay = %d\n", NpScb->NwSendDelay );
NpScb->SendBurstSuccessCount = 0;
}
VOID
NwProcessReceiveBurstFailure(
PNONPAGED_SCB NpScb,
USHORT MissingFragmentCount
)
/*++
Routine Description:
This routine adjust burst parameters after an unsuccessful burst operation.
Arguments:
NpScb - A pointer to the SCB that has experienced a burst failure.
MissingFragmentCount - A measure of how many chunks were lost.
Return Value:
None.
--*/
{
LONG temp;
DebugTrace(+0, DEBUG_TRACE_LIP, "Burst failure, NpScb = %X\n", NpScb );
if ( NpScb->NwReceiveDelay != NpScb->CurrentBurstDelay ) {
//
// This burst has already failed
//
return;
}
NpScb->NwBadReceiveDelay = NpScb->NwReceiveDelay;
//
// Add to the Receive delay. Never let it go above 5000ms.
//
temp = NpScb->NwGoodReceiveDelay - NpScb->NwBadReceiveDelay;
if (temp >= 0) {
NpScb->NwReceiveDelay += temp + 2;
} else {
NpScb->NwReceiveDelay += -temp + 2;
}
if ( NpScb->NwReceiveDelay > NpScb->NwMaxReceiveDelay ) {
NpScb->NwReceiveDelay = MaxReceiveDelay;
//
// If we have slowed down a lot then it might be that the server or a
// bridge only has a small buffer on its NIC. If this is the case then
// rather than Receiveing a big burst with long even gaps between the
// packets, we should try to Receive a burst the size of the buffer.
//
if ( !DontShrink ) {
if (((NpScb->MaxReceiveSize - 1) / NpScb->MaxPacketSize) > 2 ) {
// Round down to the next packet
NpScb->MaxReceiveSize = ((NpScb->MaxReceiveSize - 1) / NpScb->MaxPacketSize) * NpScb->MaxPacketSize;
//
// Adjust ReceiveDelay below threshold to see if things improve before
// we shrink the size again.
//
NpScb->NwReceiveDelay = NpScb->NwGoodReceiveDelay = NpScb->NwBadReceiveDelay = MinReceiveDelay;
} else {
//
// We reached the minimum size with the maximum delay. Give up on burst.
//
NpScb->ReceiveBurstModeEnabled = FALSE;
}
}
}
NpScb->ReceiveBurstSuccessCount = 0;
DebugTrace( 0, DEBUG_TRACE_LIP, "New Receive Delay = %d\n", NpScb->NwReceiveDelay );
}
VOID
NwProcessSendBurstSuccess(
PNONPAGED_SCB NpScb
)
/*++
Routine Description:
This routine adjust burst parameters after a successful burst operation.
Arguments:
NpScb - A pointer to the SCB that has completed the burst.
Return Value:
None.
--*/
{
LONG temp;
DebugTrace( 0, DEBUG_TRACE_LIP, "Successful burst, NpScb = %X\n", NpScb );
if ( NpScb->NwSendDelay != NpScb->CurrentBurstDelay ) {
//
// This burst has already failed
//
return;
}
if ( NpScb->SendBurstSuccessCount > BurstSuccessCount ) {
if (NpScb->NwSendDelay != MinSendDelay ) {
NpScb->NwGoodSendDelay = NpScb->NwSendDelay;
temp = NpScb->NwGoodSendDelay - NpScb->NwBadSendDelay;
if (temp >= 0) {
NpScb->NwSendDelay -= 1 + temp;
} else {
NpScb->NwSendDelay -= 1 - temp;
}
if (NpScb->NwSendDelay < MinSendDelay ) {
NpScb->NwSendDelay = MinSendDelay;
}
NpScb->NtSendDelay.QuadPart = NpScb->NwSendDelay * -1000;
DebugTrace( 0, DEBUG_TRACE_LIP, "New Send Delay = %d\n", NpScb->NwSendDelay );
//
// Start monitoring success at the new rate.
//
NpScb->SendBurstSuccessCount = 0;
} else if ( NpScb->SendBurstSuccessCount > BurstSuccessCount2 ) {
//
// We may have had a really bad patch causing BadSendDelay to be very big.
// If we leave it at its current value then at the first sign of trouble
// we will make SendDelay very big
//
NpScb->NwGoodSendDelay = NpScb->NwBadSendDelay = NpScb->NwSendDelay;
//
// Is it time to increase the number of packets in the burst?
// AllowGrowth == 0 to be the same as the VLM client.
//
if (( AllowGrowth ) &&
( NpScb->NwSendDelay <= MinSendDelay ) &&
( NpScb->MaxSendSize < NwMaxSendSize)) {
NpScb->MaxSendSize += NpScb->MaxPacketSize;
if ( NpScb->MaxSendSize > NwMaxSendSize) {
NpScb->MaxSendSize = NwMaxSendSize;
}
}
NpScb->SendBurstSuccessCount = 0;
} else {
NpScb->SendBurstSuccessCount++;
}
} else {
NpScb->SendBurstSuccessCount++;
}
}
VOID
NwProcessReceiveBurstSuccess(
PNONPAGED_SCB NpScb
)
/*++
Routine Description:
This routine adjust burst parameters after a successful burst operation.
Arguments:
NpScb - A pointer to the SCB that has completed the burst.
Return Value:
None.
--*/
{
LONG temp;
DebugTrace( 0, DEBUG_TRACE_LIP, "Successful burst, NpScb = %X\n", NpScb );
if ( NpScb->NwReceiveDelay != NpScb->CurrentBurstDelay ) {
//
// This burst has already failed
//
return;
}
if ( NpScb->ReceiveBurstSuccessCount > BurstSuccessCount ) {
//
// Once the vlm client reaches the Maximum delay it does not
// shrink again.
//
if ( NpScb->NwReceiveDelay != MinReceiveDelay ) {
NpScb->NwGoodReceiveDelay = NpScb->NwReceiveDelay;
temp = NpScb->NwGoodReceiveDelay - NpScb->NwBadReceiveDelay;
if (temp >= 0) {
NpScb->NwReceiveDelay -= 1 + temp;
} else {
NpScb->NwReceiveDelay -= 1 - temp;
}
DebugTrace( 0, DEBUG_TRACE_LIP, "New Receive Delay = %d\n", NpScb->NwReceiveDelay );
if (NpScb->NwReceiveDelay < MinReceiveDelay ) {
NpScb->NwReceiveDelay = MinReceiveDelay;
}
//
// Start monitoring success at the new rate.
//
NpScb->ReceiveBurstSuccessCount = 0;
} else if ( NpScb->ReceiveBurstSuccessCount > BurstSuccessCount2 ) {
//
// We may have had a really bad patch causing BadReceiveDelay to be very big.
// If we leave it at its current value then at the first sign of trouble
// we will make ReceiveDelay very big
//
NpScb->NwGoodReceiveDelay = NpScb->NwBadReceiveDelay = NpScb->NwReceiveDelay;
//
// Is it time to increase the number of packets in the burst?
//
if (( AllowGrowth ) &&
( NpScb->NwReceiveDelay <= MinReceiveDelay ) &&
( NpScb->MaxReceiveSize < NwMaxReceiveSize)) {
NpScb->MaxReceiveSize += NpScb->MaxPacketSize;
if ( NpScb->MaxReceiveSize > NwMaxReceiveSize) {
NpScb->MaxReceiveSize = NwMaxReceiveSize;
}
}
NpScb->ReceiveBurstSuccessCount = 0;
} else {
NpScb->ReceiveBurstSuccessCount++;
}
} else {
NpScb->ReceiveBurstSuccessCount++;
}
}
VOID
NwProcessPositiveAck(
PNONPAGED_SCB NpScb
)
/*++
Routine Description:
This routine processes a positive acknowledgement.
Arguments:
NpScb - A pointer to the SCB that has experienced a burst failure.
Return Value:
None.
--*/
{
DebugTrace( 0, Dbg, "Positive ACK, NpScb = %X\n", NpScb );
//
// tommye MS 90541 / MCS 277
//
// Theory has it that we end up here about every half second,
// but I don't think we really know how long this packet has been
// outstanding. So, we'll just count this half-second event towards
// our timeout. Once this exceeds NwAbsoluteTotalWaitTime, then we
// won't reset the RetryCount and the DPC should handle it from there.
//
NpScb->TotalWaitTime++;
//
// If we have not waited longer than the absolute total, keep waiting.
// If we have waited too long, let ourselves timeout.
//
// If NwAbsoluteTotalWaitTime is 0, then we are prepared to wait forever.
//
if ( NpScb->TotalWaitTime < NwAbsoluteTotalWaitTime ||
NwAbsoluteTotalWaitTime == 0) {
NpScb->RetryCount = DefaultRetryCount;
} else {
DebugTrace( 0, Dbg, "Request exceeds absolute total wait time\n", 0 );
}
}