mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2098 lines
54 KiB
2098 lines
54 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
transvr.cxx
|
|
|
|
Abstract:
|
|
|
|
This module is the manager of loadable transport interface modules
|
|
for the server side of the runtime. We take care of dynamically
|
|
loading transport interface modules as necessary and binding them to
|
|
addresses. In addition, we provide the receive any / receive specific
|
|
functionality.
|
|
|
|
Author:
|
|
|
|
Steve Zeck (stevez) 06-May-1991
|
|
|
|
Revision History:
|
|
|
|
01-Mar-1992 mikemon
|
|
|
|
Rewrote the majority of the code and added comments.
|
|
|
|
--*/
|
|
|
|
#include <precomp.hxx>
|
|
#include <osfpcket.hxx>
|
|
#include <rpctran.h>
|
|
#include <hndlsvr.hxx>
|
|
#include <secsvr.hxx>
|
|
#include <osfsvr.hxx>
|
|
#include <sdict2.hxx>
|
|
#include <queue.hxx>
|
|
#include <transvr.hxx>
|
|
|
|
|
|
TRANS_ADDRESS::TRANS_ADDRESS (
|
|
IN RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo,
|
|
IN OUT RPC_STATUS PAPI * RpcStatus
|
|
#ifdef NTENV
|
|
) : OSF_ADDRESS(RpcStatus), ReceiveAnyMutex(RpcStatus, 0)
|
|
#else
|
|
) : OSF_ADDRESS(RpcStatus), ReceiveAnyMutex(RpcStatus)
|
|
#endif
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
For this constructor we just have got to allocate the memory and
|
|
initialize things. Another method is used to actually get the
|
|
address moving.
|
|
|
|
Arguments:
|
|
|
|
RpcServerInfo - Supplies a pointer to information describing the
|
|
loadable transport interface which this address will use.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS * errptr, err;
|
|
errptr = &err;
|
|
|
|
ALLOCATE_THIS_PLUS(TRANS_ADDRESS, RpcServerInfo->SizeOfAddress,
|
|
errptr, RPC_S_OUT_OF_MEMORY);
|
|
|
|
ServerInfo = RpcServerInfo;
|
|
SetupAddressOccurred = 0;
|
|
IsSlaveAddress = FALSE ;
|
|
|
|
#ifdef NTENV
|
|
ReceiveAnyMutex.Raise();
|
|
#endif
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_ADDRESS::SetupAddressWithEndpoint (
|
|
IN RPC_CHAR PAPI * Endpoint,
|
|
OUT RPC_CHAR PAPI * PAPI * lNetworkAddress,
|
|
OUT unsigned int PAPI * NumNetworkAddress,
|
|
IN void PAPI * SecurityDescriptor, OPTIONAL
|
|
IN unsigned int PendingQueueSize,
|
|
IN RPC_CHAR PAPI * RpcProtocolSequence,
|
|
IN unsigned long EndpointFlags,
|
|
IN unsigned long NICFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
At this point, we need to setup the loadable transport interface.
|
|
We also need to obtain the network address for this server. After
|
|
allocating a buffer to hold the network address, we will call
|
|
the loadable transport interface to let it do its thing.
|
|
|
|
Arguments:
|
|
|
|
Endpoint - Supplies the endpoint to be used will this address.
|
|
|
|
NetworkAddress - Returns the network address for this server. The
|
|
ownership of the buffer allocated to contain the network address
|
|
passes to the caller.
|
|
|
|
SecurityDescriptor - Optionally supplies a security descriptor to
|
|
be placed on this address. Whether or not this is suppored depends
|
|
on the particular combination of transport interface and operating
|
|
system.
|
|
|
|
PendingQueueSize - Supplies the size of the queue of pending
|
|
requests which should be created by the transport. Some transports
|
|
will not be able to make use of this value, while others will.
|
|
|
|
RpcProtocolSequence - Supplies the protocol sequence for which we
|
|
are trying to setup an address. This argument is necessary so
|
|
that a single transport interface dll can support more than one
|
|
protocol sequence.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - We successfully setup this address.
|
|
|
|
RPC_S_INVALID_SECURITY_DESC - The supplied security descriptor is
|
|
invalid.
|
|
|
|
RPC_S_CANT_CREATE_ENDPOINT - The endpoint format is correct, but
|
|
the endpoint can not be created.
|
|
|
|
RPC_S_INVALID_ENDPOINT_FORMAT - The endpoint is not a valid
|
|
endpoint for this particular transport interface.
|
|
|
|
RPC_S_OUT_OF_RESOURCES - Insufficient resources are available to
|
|
setup the address.
|
|
|
|
RPC_S_OUT_OF_MEMORY - Insufficient memory is available to setup
|
|
the address.
|
|
|
|
--*/
|
|
{
|
|
|
|
RPC_STATUS Status;
|
|
unsigned int NetworkAddressLength;
|
|
|
|
NetworkAddressLength = 20;
|
|
|
|
while (1)
|
|
{
|
|
*lNetworkAddress = new RPC_CHAR[NetworkAddressLength];
|
|
|
|
if (*lNetworkAddress == 0)
|
|
{
|
|
delete (*lNetworkAddress);
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
Status = ServerInfo->SetupWithEndpoint( InqRpcTransportAddress(),
|
|
Endpoint,
|
|
*lNetworkAddress,
|
|
NumNetworkAddress,
|
|
NetworkAddressLength,
|
|
SecurityDescriptor,
|
|
PendingQueueSize,
|
|
RpcProtocolSequence,
|
|
EndpointFlags,
|
|
NICFlags);
|
|
|
|
if (Status != RPC_P_NETWORK_ADDRESS_TOO_SMALL)
|
|
{
|
|
break;
|
|
}
|
|
delete(*lNetworkAddress);
|
|
NetworkAddressLength *= 2;
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
if ( Status == RPC_S_OK || Status == RPC_P_THREAD_LISTENING )
|
|
{
|
|
if (Status == RPC_P_THREAD_LISTENING)
|
|
{
|
|
IsSlaveAddress = TRUE;
|
|
while (GlobalRpcServer->CommonAddress == 0)
|
|
{
|
|
Sleep(100) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RpcpStringCompare(RPC_CONST_STRING("ncacn_np"),
|
|
RpcProtocolSequence) != 0)
|
|
{
|
|
ASSERT(GlobalRpcServer->CommonAddress == 0) ;
|
|
|
|
// this is the common address
|
|
GlobalRpcServer->CommonAddress = this ;
|
|
}
|
|
}
|
|
#else
|
|
if ( Status == RPC_S_OK )
|
|
{
|
|
#endif
|
|
SetupAddressOccurred = 1;
|
|
}
|
|
else
|
|
{
|
|
delete (*lNetworkAddress);
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
ASSERT( (Status == RPC_S_OK)
|
|
|| (Status == RPC_S_INVALID_SECURITY_DESC)
|
|
|| (Status == RPC_S_INVALID_ARG)
|
|
|| (Status == RPC_S_CANT_CREATE_ENDPOINT)
|
|
|| (Status == RPC_S_INVALID_ENDPOINT_FORMAT)
|
|
|| (Status == RPC_S_OUT_OF_RESOURCES)
|
|
|| ( Status == RPC_S_PROTSEQ_NOT_SUPPORTED )
|
|
|| ( Status == RPC_S_DUPLICATE_ENDPOINT )
|
|
|| (Status == RPC_S_OUT_OF_MEMORY)
|
|
|| (Status == RPC_P_THREAD_LISTENING));
|
|
#else
|
|
ASSERT( (Status == RPC_S_OK)
|
|
|| (Status == RPC_S_INVALID_SECURITY_DESC)
|
|
|| (Status == RPC_S_INVALID_ARG)
|
|
|| (Status == RPC_S_CANT_CREATE_ENDPOINT)
|
|
|| (Status == RPC_S_INVALID_ENDPOINT_FORMAT)
|
|
|| (Status == RPC_S_OUT_OF_RESOURCES)
|
|
|| ( Status == RPC_S_PROTSEQ_NOT_SUPPORTED )
|
|
|| ( Status == RPC_S_DUPLICATE_ENDPOINT )
|
|
|| (Status == RPC_S_OUT_OF_MEMORY));
|
|
#endif
|
|
|
|
return(Status);
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
|
|
RPC_STATUS
|
|
TRANS_ADDRESS::StartListening (
|
|
)
|
|
{
|
|
RPC_STATUS Status ;
|
|
|
|
if (ServerInfo->StartListening != 0)
|
|
{
|
|
Status = ServerInfo->StartListening(
|
|
InqRpcTransportAddress());
|
|
|
|
return Status ;
|
|
}
|
|
|
|
return (RPC_S_OK) ;
|
|
}
|
|
#endif
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_ADDRESS::SetupAddressUnknownEndpoint (
|
|
OUT RPC_CHAR PAPI * PAPI * Endpoint,
|
|
OUT RPC_CHAR PAPI * PAPI * lNetworkAddress,
|
|
OUT unsigned int PAPI * NumNetworkAddress,
|
|
IN void PAPI * SecurityDescriptor, OPTIONAL
|
|
IN unsigned int PendingQueueSize,
|
|
IN RPC_CHAR PAPI * RpcProtocolSequence,
|
|
IN unsigned long EndpointFlags,
|
|
IN unsigned long NICFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
At this point, we need to setup the loadable transport interface.
|
|
The loadable transport interface will need to generate an endpoint
|
|
for this address. We need to allocate a buffer to contain the
|
|
allocated endpoint. We also need to obtain the network address
|
|
for this server. After allocating a buffer to hold the network
|
|
address, we will call the loadable transport interface to let it
|
|
do its thing.
|
|
|
|
Arguments:
|
|
|
|
Endpoint - Returns the endpoint for this address. The ownership
|
|
of the buffer allocated to contain the endpoint passes to the
|
|
caller.
|
|
|
|
NetworkAddress - Returns the network address for this server. The
|
|
ownership of the buffer allocated to contain the network address
|
|
passes to the caller.
|
|
|
|
SecurityDescriptor - Optionally supplies a security descriptor to
|
|
be placed on this address. Whether or not this is suppored depends
|
|
on the particular combination of transport interface and operating
|
|
system.
|
|
|
|
PendingQueueSize - Supplies the size of the queue of pending
|
|
requests which should be created by the transport. Some transports
|
|
will not be able to make use of this value, while others will.
|
|
|
|
RpcProtocolSequence - Supplies the protocol sequence for which we
|
|
are trying to setup an address. This argument is necessary so
|
|
that a single transport interface dll can support more than one
|
|
protocol sequence.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - We successfully setup this address.
|
|
|
|
RPC_S_INVALID_SECURITY_DESC - The supplied security descriptor is
|
|
invalid.
|
|
|
|
RPC_S_OUT_OF_RESOURCES - Insufficient resources are available to
|
|
setup the address.
|
|
|
|
RPC_S_OUT_OF_MEMORY - Insufficient memory is available to setup
|
|
the address.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS Status;
|
|
unsigned int EndpointLength;
|
|
unsigned int NetworkAddressLength;
|
|
|
|
NetworkAddressLength = 20;
|
|
EndpointLength = 16;
|
|
|
|
|
|
*Endpoint = new RPC_CHAR[EndpointLength];
|
|
if ( *Endpoint == 0 )
|
|
{
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
*lNetworkAddress = new RPC_CHAR[NetworkAddressLength];
|
|
if ( *lNetworkAddress == 0 )
|
|
{
|
|
delete *Endpoint;
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
Status = ServerInfo->SetupUnknownEndpoint(
|
|
InqRpcTransportAddress(), *Endpoint, EndpointLength,
|
|
*lNetworkAddress, NumNetworkAddress, NetworkAddressLength,
|
|
SecurityDescriptor, PendingQueueSize, RpcProtocolSequence,
|
|
EndpointFlags,NICFlags);
|
|
|
|
if ( Status == RPC_P_NETWORK_ADDRESS_TOO_SMALL )
|
|
{
|
|
delete( *lNetworkAddress);
|
|
NetworkAddressLength *= 2;
|
|
*lNetworkAddress = new RPC_CHAR[NetworkAddressLength];
|
|
if (*lNetworkAddress == 0)
|
|
{
|
|
delete *Endpoint;
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
}
|
|
else if ( Status == RPC_P_ENDPOINT_TOO_SMALL )
|
|
{
|
|
delete *Endpoint;
|
|
EndpointLength *= 2;
|
|
*Endpoint = new RPC_CHAR[EndpointLength];
|
|
if (*Endpoint == 0)
|
|
{
|
|
delete(*lNetworkAddress);
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if defined(NTENV) || defined(WIN96)
|
|
|
|
#ifdef DEBUGRPC
|
|
if ( (Status != RPC_S_OK)
|
|
&& (Status != RPC_S_INVALID_SECURITY_DESC)
|
|
&& (Status != RPC_S_PROTSEQ_NOT_SUPPORTED)
|
|
&& (Status != RPC_S_OUT_OF_RESOURCES)
|
|
&& (Status != RPC_S_CANT_CREATE_ENDPOINT)
|
|
&& (Status != RPC_S_OUT_OF_MEMORY)
|
|
&& (Status != RPC_P_THREAD_LISTENING))
|
|
{
|
|
PrintToDebugger("RPC: %lx\n", Status);
|
|
}
|
|
#endif // DEBUGRPC
|
|
|
|
ASSERT( (Status == RPC_S_OK)
|
|
|| (Status == RPC_S_INVALID_SECURITY_DESC)
|
|
|| (Status == RPC_S_PROTSEQ_NOT_SUPPORTED)
|
|
|| (Status == RPC_S_OUT_OF_RESOURCES)
|
|
|| (Status == RPC_S_OUT_OF_MEMORY)
|
|
|| (Status == RPC_S_CANT_CREATE_ENDPOINT)
|
|
|| (Status == RPC_P_THREAD_LISTENING) );
|
|
#else
|
|
|
|
#ifdef DEBUGRPC
|
|
if ( (Status != RPC_S_OK)
|
|
&& (Status != RPC_S_INVALID_SECURITY_DESC)
|
|
&& (Status != RPC_S_PROTSEQ_NOT_SUPPORTED)
|
|
&& (Status != RPC_S_OUT_OF_RESOURCES)
|
|
&& (Status != RPC_S_CANT_CREATE_ENDPOINT)
|
|
&& (Status != RPC_S_OUT_OF_MEMORY))
|
|
{
|
|
PrintToDebugger("RPC: %lx\n", Status);
|
|
}
|
|
#endif // DEBUGRPC
|
|
|
|
ASSERT( (Status == RPC_S_OK)
|
|
|| (Status == RPC_S_INVALID_SECURITY_DESC)
|
|
|| (Status == RPC_S_PROTSEQ_NOT_SUPPORTED)
|
|
|| (Status == RPC_S_OUT_OF_RESOURCES)
|
|
|| (Status == RPC_S_OUT_OF_MEMORY)
|
|
|| (Status == RPC_S_CANT_CREATE_ENDPOINT));
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
if ( Status == RPC_S_OK || Status == RPC_P_THREAD_LISTENING )
|
|
{
|
|
if (Status == RPC_P_THREAD_LISTENING)
|
|
{
|
|
IsSlaveAddress = TRUE;
|
|
while (GlobalRpcServer->CommonAddress == 0)
|
|
{
|
|
Sleep(100) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RpcpStringCompare(RPC_CONST_STRING("ncacn_np"),
|
|
RpcProtocolSequence) != 0)
|
|
{
|
|
ASSERT(GlobalRpcServer->CommonAddress == 0) ;
|
|
|
|
// this is the common address
|
|
GlobalRpcServer->CommonAddress = this ;
|
|
}
|
|
}
|
|
#else
|
|
if ( Status == RPC_S_OK )
|
|
{
|
|
#endif
|
|
SetupAddressOccurred = 1;
|
|
}
|
|
else
|
|
{
|
|
delete *Endpoint;
|
|
delete(*lNetworkAddress);
|
|
}
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
TRANS_ADDRESS::~TRANS_ADDRESS (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We need to clean up the address after it has been partially
|
|
initialized. This routine will only be called before FireUpManager
|
|
is called, but it may have been called before or after one of
|
|
SetupAddressWithEndpoint or SetupAddressUnknownEndpoint is called.
|
|
We will keep track of whether or not SetupAddress* occurred
|
|
successfully; if so, we need to call AbortSetupAddress to give the
|
|
loadable transport module a chance to clean things up.
|
|
|
|
--*/
|
|
{
|
|
if (SetupAddressOccurred != 0)
|
|
ServerInfo->AbortSetupAddress(InqRpcTransportAddress());
|
|
}
|
|
|
|
|
|
inline int
|
|
TRANS_ADDRESS::TransMarkReceiveAny (
|
|
IN OSF_SCONNECTION * SConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to indicate that the connection is receive any.
|
|
If the connection has closed, the return code will indicate that.
|
|
|
|
Arguments:
|
|
|
|
SConnection - Supplies the connection which should be put into the
|
|
receive any state.
|
|
|
|
Return Value:
|
|
|
|
A return value of zero indicates that the operation completed
|
|
successfully, otherwise, non-zero will be returned indicating
|
|
that the connection has buffers in its queue of buffers.
|
|
|
|
--*/
|
|
{
|
|
UNUSED(this);
|
|
|
|
return(((TRANS_SCONNECTION *) SConnection)->MarkReceiveAny());
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_ADDRESS::TransReceive (
|
|
OUT OSF_SCONNECTION ** SConnection,
|
|
OUT void ** Buffer,
|
|
OUT unsigned int * BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method is used to perform a receive any operation on an address.
|
|
What we do is receive on all connections which have been marked receive
|
|
any. We are actually receiving on all connection and logically receiving
|
|
on only the receive any connections.
|
|
|
|
Arguments:
|
|
|
|
SConnection - Returns the connection from which we just received a
|
|
packet or which just closed.
|
|
|
|
Buffer - Returns the packet which we received.
|
|
|
|
BufferLength - Returns the length of the packet which we received.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - The receive operation completed successfully.
|
|
|
|
RPC_P_CONNECTION_CLOSED - The connection returned has closed.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_TRANSPORT_CONNECTION RpcTransportConnection;
|
|
TRANS_SCONNECTION * TransSConnection;
|
|
unsigned int Ignore;
|
|
|
|
ReceiveAnyMutex.Request();
|
|
|
|
for (;;)
|
|
{
|
|
// The first thing we need to do is to delete any connections which
|
|
// are in the set of connections to be deleted.
|
|
|
|
RequestGlobalMutex();
|
|
while ( ConnectionsToBeDeleted.IsQueueEmpty() == 0 )
|
|
{
|
|
TransSConnection = (TRANS_SCONNECTION *)
|
|
ConnectionsToBeDeleted.TakeOffQueue(&Ignore);
|
|
ASSERT( TransSConnection != 0 );
|
|
ClearGlobalMutex();
|
|
delete TransSConnection;
|
|
RequestGlobalMutex();
|
|
}
|
|
ClearGlobalMutex();
|
|
|
|
RpcTransportConnection = 0;
|
|
*Buffer = 0;
|
|
RpcStatus = ServerInfo->ReceiveAny(
|
|
InqRpcTransportAddress(), &RpcTransportConnection, Buffer,
|
|
BufferLength, -1);
|
|
|
|
ASSERT( (RpcStatus == RPC_S_OK)
|
|
|| (RpcStatus == RPC_S_OUT_OF_MEMORY)
|
|
|| (RpcStatus == RPC_S_OUT_OF_RESOURCES)
|
|
|| (RpcStatus == RPC_P_SERVER_TRANSPORT_ERROR)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)) ;
|
|
|
|
// At this point, the ReceiveAny operation had better not return
|
|
// RPC_P_TIMEOUT because we passed in negative one (-1) indicating
|
|
// that the transport interface module should wait forever.
|
|
|
|
ASSERT( RpcStatus != RPC_P_TIMEOUT );
|
|
|
|
if ( RpcStatus == RPC_P_CONNECTION_CLOSED )
|
|
{
|
|
TransSConnection = InqTransSConnection(RpcTransportConnection);
|
|
if ( TransSConnection->ConnectionClosed() == 0 )
|
|
{
|
|
// The connection is not receive any, so we go around
|
|
// the loop to try again.
|
|
|
|
continue;
|
|
}
|
|
|
|
*SConnection = TransSConnection;
|
|
ReceiveAnyMutex.Clear();
|
|
return(RpcStatus);
|
|
}
|
|
|
|
if ( RpcStatus != RPC_S_OK )
|
|
{
|
|
// This means that either the we are out of resources or out
|
|
// of memory. In order, to give the machine a chance to
|
|
// recover, we will pause for a tenth of a second. This is
|
|
// an arbitrary amount of time.
|
|
|
|
PauseExecution(100L);
|
|
continue;
|
|
}
|
|
|
|
// If we reached here, we just received a packet.
|
|
|
|
TransSConnection = InqTransSConnection(RpcTransportConnection);
|
|
RequestGlobalMutex();
|
|
if ( TransSConnection->NotifyBufferReceived(
|
|
*Buffer, *BufferLength) == 0 )
|
|
{
|
|
// The connection is not receive any, so we go around
|
|
// the loop to try again.
|
|
|
|
ClearGlobalMutex();
|
|
continue;
|
|
}
|
|
|
|
ASSERT(*Buffer != 0);
|
|
|
|
TransSConnection->MakeReceiveSpecific();
|
|
|
|
ClearGlobalMutex();
|
|
*SConnection = TransSConnection;
|
|
ReceiveAnyMutex.Clear();
|
|
Server->PacketReceived();
|
|
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
// This will never be reached.
|
|
|
|
ReceiveAnyMutex.Clear();
|
|
|
|
return(RPC_S_INTERNAL_ERROR);
|
|
}
|
|
|
|
|
|
TRANS_SCONNECTION *
|
|
TRANS_ADDRESS::NewConnection (
|
|
IN int ConnectionKey,
|
|
OUT unsigned int PAPI * ReceiveDirectFlag
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We will create a new connection which belongs to this address.
|
|
|
|
Arguments:
|
|
|
|
ConnectionKey - Supplies the connection key specified for this
|
|
connection by the loadable transport.
|
|
|
|
ReceiveDirectFlag - Returns an indication of whether the new connection
|
|
is receive direct or receive any. A value of zero indicates that
|
|
it is receive any; otherwise, it is receive direct.
|
|
|
|
Return Value:
|
|
|
|
The new connection will be returned unless insufficient memory
|
|
is available, in which case, zero will be returned.
|
|
|
|
--*/
|
|
{
|
|
TRANS_SCONNECTION * SConnection;
|
|
int DictKey;
|
|
RPC_STATUS RpcStatus = RPC_S_OK;
|
|
|
|
SConnection = new ('0', ServerInfo->SizeOfConnection)TRANS_SCONNECTION
|
|
(this, ServerInfo, ConnectionKey, &RpcStatus);
|
|
|
|
if ( RpcStatus != RPC_S_OK )
|
|
{
|
|
delete SConnection;
|
|
SConnection = 0;
|
|
}
|
|
|
|
if ( SConnection == 0 )
|
|
{
|
|
return(0);
|
|
}
|
|
|
|
RequestGlobalMutex();
|
|
DictKey = SConnectionDict.Insert(SConnection);
|
|
ClearGlobalMutex();
|
|
if (DictKey == -1)
|
|
{
|
|
SConnection->Delete();
|
|
return(0);
|
|
}
|
|
SConnection->SetDictKey(DictKey);
|
|
|
|
if ( ServerInfo->ReceiveDirect != 0 )
|
|
{
|
|
SConnection->SetReceiveDirectFlag(1);
|
|
MaybeMakeReceiveDirect(SConnection, ReceiveDirectFlag);
|
|
SConnection->SetReceiveDirectFlag(*ReceiveDirectFlag);
|
|
}
|
|
else
|
|
{
|
|
SConnection->SetReceiveDirectFlag(0);
|
|
*ReceiveDirectFlag = 0;
|
|
}
|
|
|
|
return(SConnection);
|
|
}
|
|
|
|
|
|
TRANS_SCONNECTION *
|
|
TRANS_ADDRESS::FindConnection (
|
|
IN int ConnectionKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We will find the connection with the corresponding connection key
|
|
in this routine.
|
|
|
|
Arguments:
|
|
|
|
ConnectionKey - Supplies the connection key of the connection which
|
|
we are trying to find.
|
|
|
|
Return Value:
|
|
|
|
The connection corresponding to the connection key will be returned.
|
|
|
|
--*/
|
|
{
|
|
TRANS_SCONNECTION * SConnection;
|
|
|
|
RequestGlobalMutex();
|
|
SConnectionDict.Reset();
|
|
while ((SConnection = SConnectionDict.Next()) != 0)
|
|
{
|
|
if (SConnection->CheckConnectionKey(ConnectionKey) != 0)
|
|
{
|
|
ClearGlobalMutex();
|
|
return(SConnection);
|
|
}
|
|
}
|
|
ClearGlobalMutex();
|
|
return(0);
|
|
}
|
|
|
|
|
|
TRANS_SCONNECTION::TRANS_SCONNECTION (
|
|
IN TRANS_ADDRESS * TheAddress,
|
|
IN RPC_SERVER_TRANSPORT_INFO * ServerInfo,
|
|
IN int ConnectionKey,
|
|
IN OUT RPC_STATUS PAPI * RpcStatus
|
|
) : OSF_SCONNECTION (RpcStatus), ConnectionEvent(RpcStatus)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We need to allocate and initialize a new instance of the connection
|
|
bound to this address.
|
|
|
|
Arguments:
|
|
|
|
TheAddress - Supplies the address to which the connection belongs.
|
|
|
|
ServerInfo - Supplies the pointers to the loadable transport routines.
|
|
|
|
ConnectionKey - Supplies the connection key specified for this
|
|
connection by the loadable transport module.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS * errptr, err;
|
|
errptr = &err;
|
|
|
|
ALLOCATE_THIS_PLUS(TRANS_SCONNECTION, ServerInfo->SizeOfConnection,
|
|
errptr, RPC_S_OUT_OF_MEMORY);
|
|
|
|
this->ServerInfo = ServerInfo;
|
|
this->ConnectionKey = ConnectionKey;
|
|
ConnectionClosedFlag = 0;
|
|
Address = TheAddress;
|
|
ReceiveAnyFlag = 1;
|
|
DictKey = -1;
|
|
CanMigrateToReceiveAny = 0 ;
|
|
|
|
if ( *RpcStatus == RPC_S_OK )
|
|
{
|
|
ConnectionEvent.Lower();
|
|
}
|
|
}
|
|
|
|
|
|
TRANS_SCONNECTION::~TRANS_SCONNECTION (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We finally get to delete the connection. After making sure that there
|
|
are no buffer in the queue for this connection, we can remove the
|
|
connection from the dictionary of connections (maintained by the address).
|
|
|
|
--*/
|
|
{
|
|
unsigned int Ignore;
|
|
void PAPI * Buffer;
|
|
|
|
while ( BufferQueue.IsQueueEmpty() == 0 )
|
|
{
|
|
Buffer = BufferQueue.TakeOffQueue(&Ignore);
|
|
ASSERT( Buffer != 0 );
|
|
TransFreeBuffer(Buffer);
|
|
}
|
|
|
|
if ( DictKey != -1 )
|
|
{
|
|
RequestGlobalMutex();
|
|
Address->RemoveConnection(DictKey);
|
|
ClearGlobalMutex();
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_SCONNECTION::TransReceive (
|
|
OUT void * * Buffer,
|
|
OUT unsigned int * BufferLength,
|
|
IN unsigned int CanMigrate
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
CanMigrateToReceiveAny = CanMigrate ;
|
|
if ( ReceiveDirectFlag != 0 )
|
|
{
|
|
if ( ConnectionClosedFlag != 0 )
|
|
{
|
|
return(RPC_P_CONNECTION_CLOSED);
|
|
}
|
|
*Buffer = 0;
|
|
RpcStatus = ServerInfo->ReceiveDirect(InqRpcTransportConnection(),
|
|
Buffer, BufferLength);
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
ASSERT( ( RpcStatus == RPC_S_OK )
|
|
|| ( RpcStatus == RPC_S_OUT_OF_MEMORY )
|
|
|| ( RpcStatus == RPC_S_OUT_OF_RESOURCES )
|
|
|| ( RpcStatus == RPC_P_CONNECTION_CLOSED )
|
|
|| ( RpcStatus == RPC_P_TIMEOUT) );
|
|
#else
|
|
ASSERT( ( RpcStatus == RPC_S_OK )
|
|
|| ( RpcStatus == RPC_S_OUT_OF_MEMORY )
|
|
|| ( RpcStatus == RPC_S_OUT_OF_RESOURCES )
|
|
|| ( RpcStatus == RPC_P_CONNECTION_CLOSED ) );
|
|
#endif
|
|
if ( RpcStatus == RPC_S_OK )
|
|
{
|
|
Address->Server->PacketReceived();
|
|
}
|
|
else if ( RpcStatus == RPC_P_CONNECTION_CLOSED )
|
|
{
|
|
ConnectionClosedFlag = 1;
|
|
}
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
ASSERT(ReceiveAnyFlag == 0);
|
|
|
|
RequestGlobalMutex();
|
|
|
|
if (ConnectionClosedFlag != 0)
|
|
{
|
|
ClearGlobalMutex();
|
|
return(RPC_P_CONNECTION_CLOSED);
|
|
}
|
|
|
|
if (BufferQueue.IsQueueEmpty() == 0)
|
|
{
|
|
// This means that there is a buffer on the queue. We just need
|
|
// to remove it, and go on our way.
|
|
|
|
*Buffer = BufferQueue.TakeOffQueue(BufferLength);
|
|
ClearGlobalMutex();
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
// There is nothing in the queue, so we need to wait until another
|
|
// thread reads something and puts it there.
|
|
|
|
ConnectionEvent.Lower();
|
|
ClearGlobalMutex();
|
|
ConnectionEvent.Wait();
|
|
|
|
// Ok, we got woken up. This means that either the connection closed,
|
|
// or someone read a buffer for us.
|
|
|
|
if (ConnectionClosedFlag != 0)
|
|
{
|
|
return(RPC_P_CONNECTION_CLOSED);
|
|
}
|
|
|
|
RequestGlobalMutex();
|
|
|
|
// If the connection is not closed, that means there must be something
|
|
// in the queue for us. We just take it off of the queue and return.
|
|
|
|
ASSERT(BufferQueue.IsQueueEmpty() == 0);
|
|
|
|
*Buffer = BufferQueue.TakeOffQueue(BufferLength);
|
|
ClearGlobalMutex();
|
|
Address->Server->PacketReceived();
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
|
|
int
|
|
TRANS_SCONNECTION::ConnectionClosed (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to TRANS_ADDRESS to notify a connection that it
|
|
has been closed. If the connection is not receive any, we need to
|
|
wake up the thread that might be waiting on this connection.
|
|
|
|
There is a potential race condition here if you are not careful. You
|
|
can not clear the mutex and then return the value of the receive any
|
|
flag; another thread may change the value of the receive any flag
|
|
between you clearing the mutex and returning the value of the receive
|
|
any flag.
|
|
|
|
Return Value:
|
|
|
|
Zero will be returned if the connection is not in the receive any
|
|
state, otherwise, non-zero will be returned.
|
|
|
|
--*/
|
|
{
|
|
RequestGlobalMutex();
|
|
|
|
ConnectionClosedFlag = 1;
|
|
|
|
if (ReceiveAnyFlag == 0)
|
|
{
|
|
ConnectionEvent.Raise();
|
|
ClearGlobalMutex();
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
ClearGlobalMutex();
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
TRANS_SCONNECTION::NotifyBufferReceived (
|
|
IN void * Buffer,
|
|
IN unsigned int BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to TRANS_ADDRESS to notify a connection that a
|
|
packet has been received for it. If the connection is not receive any,
|
|
after queuing the packet, we need to wake up the thread that might
|
|
be waiting on this connection.
|
|
|
|
There is a potential race condition here if you are not careful. You
|
|
can not clear the mutex and then return the value of the receive any
|
|
flag; another thread may change the value of the receive any flag
|
|
between you clearing the mutex and returning the value of the receive
|
|
any flag.
|
|
|
|
Arguments:
|
|
|
|
Buffer - Supplies the buffer received on this connection.
|
|
|
|
BufferLength - Supplies the length of the buffer.
|
|
|
|
Return Value:
|
|
|
|
Zero will be returned if the connection is not in the receive any
|
|
state, otherwise, non-zero will be returned.
|
|
|
|
--*/
|
|
{
|
|
rpcconn_common PAPI * Packet = (rpcconn_common PAPI *)Buffer;
|
|
|
|
ASSERT(Buffer != 0);
|
|
|
|
if (Packet->PTYPE == rpc_remote_alert)
|
|
{
|
|
if (Thread)
|
|
{
|
|
RpcCancelThread(Thread->ThreadHandle());
|
|
AlertCount++;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
if (Packet->PTYPE == rpc_orphaned)
|
|
{
|
|
if (Thread)
|
|
{
|
|
RpcCancelThread(Thread->ThreadHandle());
|
|
}
|
|
|
|
CallOrphaned = 1;
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
// DceSecurityInfo.ReceiveSequenceNumber += 1;
|
|
if (ReceiveAnyFlag == 0)
|
|
{
|
|
if (BufferQueue.PutOnQueue(Buffer, BufferLength) != 0)
|
|
{
|
|
// If we reach here, we have run out of memory. In an
|
|
// attempt to recover, we will discard the packet, and
|
|
// claim that the connection is closed.
|
|
|
|
TransFreeBuffer(Buffer);
|
|
ConnectionClosedFlag = 1;
|
|
}
|
|
|
|
ConnectionEvent.Raise();
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
TRANS_SCONNECTION::MarkReceiveAny (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to indicate that this connection is receive any.
|
|
If this connection has closed, the return code will indicate that.
|
|
|
|
Return Value:
|
|
|
|
A return value of zero indicates that the operation completed
|
|
successfully, otherwise, non-zero will be returned indicating
|
|
that this connection has buffers in its queue of buffers.
|
|
|
|
--*/
|
|
{
|
|
ASSERT(ReceiveAnyFlag == 0);
|
|
|
|
RequestGlobalMutex();
|
|
|
|
if (CallOrphaned)
|
|
{
|
|
CallOrphaned = 0;
|
|
ReceiveAnyFlag = 1;
|
|
ClearGlobalMutex();
|
|
return(0);
|
|
}
|
|
if (BufferQueue.IsQueueEmpty() != 0)
|
|
{
|
|
// The buffer queue is empty; now check to make sure that this
|
|
// connection has not closed.
|
|
|
|
if (ConnectionClosedFlag == 0)
|
|
{
|
|
// Ok, the connection is not closed either, so we just go ahead
|
|
// and make the connection receive any.
|
|
|
|
ReceiveAnyFlag = 1;
|
|
ClearGlobalMutex();
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
ClearGlobalMutex();
|
|
|
|
// Otherwise, we need notify that caller that there are still buffers
|
|
// for this connection which need to be received, or that the connection
|
|
// has closed.
|
|
|
|
return(1);
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_SCONNECTION::TransSend (
|
|
IN void * Buffer,
|
|
IN unsigned int BufferLength
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
ASSERT( ( ReceiveAnyFlag == 0 )
|
|
|| ( ReceiveDirectFlag != 0 ) );
|
|
|
|
if (ConnectionClosedFlag != 0)
|
|
return(RPC_P_CONNECTION_CLOSED);
|
|
|
|
DceSecurityInfo.SendSequenceNumber += 1;
|
|
|
|
RpcStatus = ServerInfo->Send(InqRpcTransportConnection(),
|
|
Buffer, BufferLength);
|
|
|
|
ASSERT( (RpcStatus == RPC_S_OK)
|
|
|| (RpcStatus == RPC_S_OUT_OF_MEMORY)
|
|
|| (RpcStatus == RPC_S_OUT_OF_RESOURCES)
|
|
|| (RpcStatus == RPC_P_SEND_FAILED));
|
|
|
|
if ( RpcStatus == RPC_S_OK )
|
|
{
|
|
Address->Server->PacketSent();
|
|
}
|
|
|
|
if ( RpcStatus == RPC_P_SEND_FAILED )
|
|
{
|
|
ConnectionClosedFlag = 1;
|
|
}
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_SCONNECTION::TransSendReceive (
|
|
IN void * SendBuffer,
|
|
IN unsigned int SendBufferLength,
|
|
IN OUT void * * ReceiveBuffer,
|
|
IN OUT unsigned int * ReceiveBufferLength
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (ConnectionClosedFlag != 0)
|
|
{
|
|
return(RPC_P_CONNECTION_CLOSED);
|
|
}
|
|
|
|
RpcStatus = TransSend(SendBuffer, SendBufferLength);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
return(RpcStatus);
|
|
}
|
|
|
|
return(TransReceive(ReceiveBuffer, ReceiveBufferLength, 0));
|
|
}
|
|
|
|
|
|
unsigned int
|
|
TRANS_SCONNECTION::TransMaximumSend (
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
{
|
|
return(ServerInfo->MaximumPacketSize);
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_SCONNECTION::TransImpersonateClient (
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
// If the transport module supports impersonation it will provide the
|
|
// sImpersonateClient entry point, in which case we call it. If an
|
|
// error occurs (indicated by sImpersonateClient returning non-zero),
|
|
// then no context is available. NOTE: this is the correct error code
|
|
// for NT; it may not be the right one (or only one) for other transports
|
|
// which support impersonation.
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if ( ServerInfo->ImpersonateClient == 0 )
|
|
{
|
|
return(RPC_S_CANNOT_SUPPORT);
|
|
}
|
|
|
|
RpcStatus = ServerInfo->ImpersonateClient(InqRpcTransportConnection());
|
|
ASSERT( (RpcStatus == RPC_S_OK)
|
|
|| (RpcStatus == RPC_S_NO_CONTEXT_AVAILABLE));
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
|
|
void
|
|
TRANS_SCONNECTION::TransRevertToSelf (
|
|
)
|
|
/*++
|
|
|
|
--*/
|
|
// As with TransImpersonateClient, if the transport module supports
|
|
// impersonation, then sRevertToSelf will be non-zero. We do not have
|
|
// to worry about errors.
|
|
//
|
|
// For revert to self to work in NT, the transport module needs to know
|
|
// the handle of the calling thread when it was originally created. None
|
|
// of the other operating systems we support at this point have
|
|
// impersonation built into the transports.
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if ( ServerInfo->RevertToSelf != 0 )
|
|
{
|
|
#ifdef NTENV
|
|
RpcStatus = ServerInfo->RevertToSelf(InqRpcTransportConnection(),
|
|
NtCurrentThread());
|
|
#else // NTENV
|
|
RpcStatus = ServerInfo->RevertToSelf(InqRpcTransportConnection(),0);
|
|
#endif // NTENV
|
|
ASSERT( RpcStatus == RPC_S_OK );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TRANS_SCONNECTION::TransQueryClientProcess (
|
|
OUT RPC_CLIENT_PROCESS_IDENTIFIER * ClientProcess
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We need to obtain the client process identifier for the client process
|
|
at the other end of this connection. This is necessary so that we can
|
|
determine whether or not a connection should belong to a given
|
|
association. We need to do this so that context handles (which hang off
|
|
of associations) are secure.
|
|
|
|
Arguments:
|
|
|
|
ClientProcess - Returns the client process identifier for the client
|
|
process at the other end of this connection.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if ( ServerInfo->QueryClientProcess == 0 )
|
|
{
|
|
ClientProcess->FirstPart = 0;
|
|
ClientProcess->SecondPart = 0;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = ServerInfo->QueryClientProcess(InqRpcTransportConnection(),
|
|
ClientProcess);
|
|
ASSERT( RpcStatus == RPC_S_OK );
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
TRANS_SCONNECTION::TransQueryClientNetworkAddress (
|
|
OUT RPC_CHAR ** NetworkAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to query the network address of the client at the
|
|
other end of this connection.
|
|
|
|
Arguments:
|
|
|
|
NetworkAddress - Returns the client's network address.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - The client's network address has successfully been obtained.
|
|
|
|
RPC_S_OUT_OF_MEMORY - Insufficient memory is available to complete the
|
|
operation.
|
|
|
|
RPC_S_CANNOT_SUPPORT - This particular transport implementation does
|
|
not support this operation.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
unsigned int NetworkAddressLength = 16;
|
|
|
|
if ( ( ServerInfo->TransInterfaceVersion < 2 )
|
|
|| ( ServerInfo->QueryClientAddress == 0 ) )
|
|
{
|
|
return(RPC_S_CANNOT_SUPPORT);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
*NetworkAddress = new RPC_CHAR[NetworkAddressLength];
|
|
if ( *NetworkAddress == 0 )
|
|
{
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
RpcStatus = ServerInfo->QueryClientAddress(InqRpcTransportConnection(),
|
|
*NetworkAddress, NetworkAddressLength);
|
|
if ( RpcStatus != RPC_P_NETWORK_ADDRESS_TOO_SMALL )
|
|
{
|
|
break;
|
|
}
|
|
delete *NetworkAddress;
|
|
NetworkAddressLength *= 2;
|
|
}
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
|
|
void
|
|
TRANS_SCONNECTION::Delete (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We want to indicate that the connection should be deleted, however,
|
|
the connection can not actually be deleted until there is no thread
|
|
performing a ReceiveAny operation. This is due to a race condition
|
|
between this thread deleting the connection, and a ReceiveAny thread
|
|
detecting (and servicing) a transport event on this connection.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if ( ReceiveDirectFlag != 0 )
|
|
{
|
|
if (ConnectionClosedFlag == 0)
|
|
{
|
|
ConnectionClosedFlag = 1;
|
|
RpcStatus = ServerInfo->Close(InqRpcTransportConnection());
|
|
ASSERT( RpcStatus == RPC_S_OK );
|
|
}
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
RequestGlobalMutex();
|
|
if ( ConnectionClosedFlag == 0 )
|
|
{
|
|
ConnectionClosedFlag = 1;
|
|
ClearGlobalMutex();
|
|
RpcStatus = ServerInfo->Close(InqRpcTransportConnection());
|
|
ASSERT( RpcStatus == RPC_S_OK );
|
|
}
|
|
else
|
|
{
|
|
ClearGlobalMutex();
|
|
}
|
|
|
|
RemoveFromAssociation();
|
|
RequestGlobalMutex();
|
|
if (Address->IsSlaveAddress)
|
|
{
|
|
ASSERT(GlobalRpcServer->CommonAddress) ;
|
|
((TRANS_ADDRESS *) GlobalRpcServer->CommonAddress)->
|
|
DeleteThisConnection(this) ;
|
|
}
|
|
else
|
|
{
|
|
Address->DeleteThisConnection(this);
|
|
}
|
|
ClearGlobalMutex();
|
|
}
|
|
|
|
extern RPC_SERVER *GlobalRpcServer ;
|
|
|
|
RPC_TRANSPORT_CONNECTION RPC_ENTRY
|
|
I_RpcTransServerNewConnection (
|
|
IN RPC_TRANSPORT_ADDRESS TransAddress,
|
|
IN int ConnectionKey,
|
|
OUT unsigned int PAPI * ReceiveDirectFlag
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will be called by a loadable transport module to obtain
|
|
a new connection. The connection is not yet ready to be received
|
|
from.
|
|
|
|
Arguments:
|
|
|
|
TransAddress - Supplies the address which will own the connection.
|
|
|
|
ConnectionKey - Supplies the connection key which the loadable
|
|
transport wishes to assign to this connection.
|
|
|
|
ReceiveDirectFlag - Returns an indication of whether the new connection
|
|
is receive direct or receive any. A value of zero indicates that
|
|
it is receive any; otherwise, it is receive direct.
|
|
|
|
Return Value:
|
|
|
|
The new connection will be returned, unless we run out of memory,
|
|
in which case, zero will be returned.
|
|
|
|
--*/
|
|
{
|
|
TRANS_SCONNECTION * SConnection;
|
|
TRANS_ADDRESS *Address ;
|
|
|
|
Address = InqTransAddress(TransAddress) ;
|
|
|
|
SConnection = Address->NewConnection(ConnectionKey, ReceiveDirectFlag);
|
|
if ( SConnection == 0 )
|
|
{
|
|
return(0);
|
|
}
|
|
|
|
return(SConnection->InqRpcTransportConnection());
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
|
|
int RPC_ENTRY
|
|
I_RpcTransMaybeMakeReceiveDirect (
|
|
IN RPC_TRANSPORT_ADDRESS TransAddress,
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will be called by a loadable transport module to migrate
|
|
thread from receiveany to receivedirect
|
|
|
|
Arguments:
|
|
|
|
TransAddress - Supplies the address which will own the connection.
|
|
|
|
|
|
Return Value:
|
|
1 - if we were able to get a receive direct thread
|
|
0 - otherwise
|
|
|
|
--*/
|
|
{
|
|
unsigned int ReceiveDirectFlag = 0;
|
|
TRANS_SCONNECTION *SConnection ;
|
|
|
|
SConnection = InqTransSConnection(ThisConnection) ;
|
|
|
|
ASSERT(SConnection->InvalidHandle(SCONNECTION_TYPE) == 0) ;
|
|
|
|
if (SConnection->InqReceiveDirectFlag() == 0 &&
|
|
SConnection->InqReceiveAnyFlag() == 1)
|
|
{
|
|
SConnection->SetReceiveDirectFlag(1) ;
|
|
InqTransAddress(TransAddress)->MaybeMakeReceiveDirect(
|
|
SConnection, &ReceiveDirectFlag);
|
|
|
|
SConnection->SetReceiveDirectFlag(ReceiveDirectFlag);
|
|
}
|
|
|
|
return (ReceiveDirectFlag) ;
|
|
}
|
|
|
|
|
|
int RPC_ENTRY
|
|
I_RpcTransMaybeMakeReceiveAny (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will be called by a loadable transport module to migrate
|
|
thread from receivedirect to receiveany
|
|
|
|
Arguments:
|
|
|
|
TransAddress - Supplies the address which will own the connection.
|
|
|
|
|
|
Return Value:
|
|
1 - if its ok to migrate to receive any
|
|
0 - otherwise
|
|
|
|
--*/
|
|
{
|
|
TRANS_SCONNECTION *SConnection ;
|
|
|
|
SConnection = InqTransSConnection(ThisConnection) ;
|
|
|
|
if (SConnection->InqMigratePossibleToReceiveAny())
|
|
{
|
|
SConnection->SetReceiveDirectFlag(0) ;
|
|
ASSERT(SConnection->TransGetReceiveAnyFlag() == 1) ;
|
|
|
|
SConnection->NotifyReceiveDirectCancelled() ;
|
|
|
|
return 1 ;
|
|
}
|
|
|
|
return (0) ;
|
|
}
|
|
|
|
|
|
void RPC_ENTRY
|
|
I_RpcTransCancelMigration(
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
{
|
|
TRANS_SCONNECTION *SConnection ;
|
|
|
|
SConnection = InqTransSConnection(ThisConnection) ;
|
|
|
|
SConnection->SetReceiveDirectFlag(0) ;
|
|
ASSERT(SConnection->TransGetReceiveAnyFlag() == 1) ;
|
|
}
|
|
#endif
|
|
|
|
|
|
RPC_TRANSPORT_CONNECTION RPC_ENTRY
|
|
I_RpcTransServerFindConnection (
|
|
IN RPC_TRANSPORT_ADDRESS pAdd,
|
|
IN int ConnectionKey
|
|
)
|
|
{
|
|
TRANS_SCONNECTION * SConnection;
|
|
|
|
SConnection = InqTransAddress(pAdd)->FindConnection(ConnectionKey);
|
|
|
|
// BUGBUG A call thread can delete a connection from under the receiveany
|
|
// thread, making the connection unfindable.
|
|
|
|
// ASSERT(SConnection != 0);
|
|
|
|
if (SConnection != 0)
|
|
{
|
|
return(SConnection->InqRpcTransportConnection());
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
RPC_STATUS RPC_ENTRY
|
|
I_RpcTransServerReallocBuffer (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN OUT void PAPI * PAPI * Buffer,
|
|
IN unsigned int OldBufferLength,
|
|
IN unsigned int NewBufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The server side transport interface modules will use this routine to
|
|
increase the size of a buffer so that the entire packet to be
|
|
received will fit into it, or to allocate a new buffer. If the buffer
|
|
is to be reallocated, the data from the old buffer is copied
|
|
into the beginning of the new buffer. The old buffer will be freed.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - Supplies the connection for which we are reallocating
|
|
a transport buffer.
|
|
|
|
Buffer - Supplies the buffer which we want to reallocate to
|
|
be larger. If no buffer is supplied, then a new one is allocated
|
|
anyway. The new buffer is returned via this argument.
|
|
|
|
OldBufferLength - Supplies the current length of the buffer in bytes.
|
|
This information is necessary so we know how much of the buffer
|
|
needs to be copied into the new buffer.
|
|
|
|
NewBufferLength - Supplies the required length of the buffer in bytes.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - The requested larger buffer has successfully been allocated.
|
|
|
|
RPC_S_OUT_OF_MEMORY - Insufficient memory is available to allocate
|
|
the buffer.
|
|
|
|
--*/
|
|
{
|
|
void PAPI * NewBuffer;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if(OldBufferLength > NewBufferLength)
|
|
{
|
|
ASSERT(!"Old buffer length > new buffer length");
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcStatus = InqTransSConnection(ThisConnection)->TransGetBuffer(
|
|
&NewBuffer, NewBufferLength);
|
|
if ( RpcStatus != RPC_S_OK )
|
|
{
|
|
ASSERT( RpcStatus == RPC_S_OUT_OF_MEMORY );
|
|
return(RpcStatus);
|
|
}
|
|
|
|
if ( *Buffer != 0 )
|
|
{
|
|
RpcpMemoryCopy(NewBuffer, *Buffer, OldBufferLength);
|
|
InqTransSConnection(ThisConnection)->TransFreeBuffer(*Buffer);
|
|
}
|
|
*Buffer = NewBuffer;
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
|
|
unsigned short RPC_ENTRY
|
|
I_RpcTransServerMaxFrag (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The server side transport interface modules will use this routine to
|
|
determine the negotiated maximum fragment size.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - Supplies the connection for which we are returning
|
|
the maximum fragment size.
|
|
|
|
--*/
|
|
{
|
|
return InqTransSConnection(ThisConnection)->InqMaximumFragmentLength();
|
|
}
|
|
|
|
|
|
void RPC_ENTRY
|
|
I_RpcTransServerFreeBuffer (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN void PAPI * Buffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We need to free a transport buffer for a transport connection; this
|
|
will typically occur when the connection is being closed.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - Supplies the transport connection which owns the
|
|
buffer.
|
|
|
|
Buffer - Supplies the buffer to be freed.
|
|
|
|
--*/
|
|
{
|
|
InqTransSConnection(ThisConnection)->TransFreeBuffer(Buffer);
|
|
}
|
|
|
|
|
|
void PAPI * RPC_ENTRY
|
|
I_RpcTransServerProtectThread (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
In some cases, if an asyncronous io operation has been started by a
|
|
thread, the thread can not be deleted because the io operation will
|
|
be cancelled. This routine will be called by a transport to indicate
|
|
that the current thread can not be deleted.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the thread will be returned. This is necessary, so that
|
|
later the thread can be unprotected.
|
|
|
|
--*/
|
|
{
|
|
THREAD PAPI * Thread = ThreadSelf();
|
|
|
|
Thread->ProtectThread();
|
|
return((void PAPI *) Thread);
|
|
}
|
|
|
|
|
|
void RPC_ENTRY
|
|
I_RpcTransServerUnprotectThread (
|
|
IN void PAPI * Thread
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a thread no longer needs to be protected from deletion, this
|
|
routine must be called.
|
|
|
|
Arguments:
|
|
|
|
Thread - Supplies the thread which no longer needs to be protected
|
|
from deletion.
|
|
|
|
--*/
|
|
{
|
|
((THREAD PAPI *) Thread)->UnprotectThread();
|
|
}
|
|
|
|
|
|
void RPC_ENTRY
|
|
I_RpcTransServerReceiveDirectReady (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The transport will call this routine once for each new receive direct
|
|
connection when it is ready to start receiving remote procedure calls.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - Supplies the receive direct connection which is ready
|
|
to start receiving remote procedure calls.
|
|
|
|
--*/
|
|
{
|
|
InqTransSConnection(ThisConnection)->NotifyReceiveDirectReady();
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
#define MAX_PROTSEQ_LENGTH MAX_DLLNAME_LENGTH
|
|
#endif
|
|
|
|
|
|
class SERVER_LOADABLE_TRANSPORT
|
|
/*++
|
|
|
|
Class Description:
|
|
|
|
This class is used an an item in a dictionary of loaded loadable
|
|
transports. It contains the information we are interested in
|
|
(RPC_SERVER_TRANSPORT_INFO) as well as the name of dll we loaded the transport
|
|
interface from.
|
|
|
|
Fields:
|
|
|
|
RpcServerInfo - Contains a pointer to the required information
|
|
about a loadable transport so that we can make use of it.
|
|
|
|
DllName - Contains the name of the dll from which we loaded
|
|
this transport interface.
|
|
|
|
--*/
|
|
{
|
|
private:
|
|
|
|
RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo;
|
|
RPC_CHAR DllName[MAX_DLLNAME_LENGTH + 1];
|
|
#if defined(NTENV) || defined(WIN96)
|
|
RPC_CHAR RpcProtocolSequence[MAX_PROTSEQ_LENGTH + 1];
|
|
#endif
|
|
|
|
public:
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
SERVER_LOADABLE_TRANSPORT (
|
|
IN RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo,
|
|
IN RPC_CHAR * DllName,
|
|
IN RPC_CHAR * ProtocolSequence
|
|
);
|
|
int
|
|
IsTransportInterfaceSupported (
|
|
IN RPC_CHAR * DllName,
|
|
IN RPC_CHAR * ProtocolSequence
|
|
);
|
|
#else
|
|
SERVER_LOADABLE_TRANSPORT (
|
|
IN RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo,
|
|
IN RPC_CHAR * DllName
|
|
);
|
|
int
|
|
IsTransportInterfaceSupported (
|
|
IN RPC_CHAR * DllName
|
|
);
|
|
#endif
|
|
|
|
RPC_SERVER_TRANSPORT_INFO PAPI *
|
|
InqRpcServerInfo (
|
|
);
|
|
};
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
|
|
SERVER_LOADABLE_TRANSPORT::SERVER_LOADABLE_TRANSPORT (
|
|
IN RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo,
|
|
IN RPC_CHAR * DllName,
|
|
IN RPC_CHAR PAPI * ProtocolSequence
|
|
)
|
|
#else
|
|
|
|
SERVER_LOADABLE_TRANSPORT::SERVER_LOADABLE_TRANSPORT (
|
|
IN RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo,
|
|
IN RPC_CHAR * DllName
|
|
)
|
|
#endif
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
To construct the object, all we have got to do is to copy the
|
|
arguments into the object.
|
|
|
|
Arguments:
|
|
|
|
RpcServerInfo - Supplies the server information describing the loadable
|
|
transport.
|
|
|
|
DllName - Supplies the name of the dll from which this transport
|
|
interface was loaded.
|
|
|
|
--*/
|
|
{
|
|
RPC_CHAR * String;
|
|
ALLOCATE_THIS(SERVER_LOADABLE_TRANSPORT);
|
|
|
|
for (String = this->DllName; *DllName != 0; DllName++, String++)
|
|
*String = *DllName;
|
|
*String = 0;
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
for (String = this->RpcProtocolSequence;
|
|
*ProtocolSequence != 0; ProtocolSequence++, String++)
|
|
*String = *ProtocolSequence;
|
|
*String = 0;
|
|
#endif
|
|
|
|
this->RpcServerInfo = RpcServerInfo;
|
|
}
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
|
|
inline int
|
|
SERVER_LOADABLE_TRANSPORT::IsTransportInterfaceSupported (
|
|
IN RPC_CHAR * DllName,
|
|
IN RPC_CHAR PAPI * ProtocolSequence
|
|
)
|
|
#else
|
|
|
|
inline int
|
|
SERVER_LOADABLE_TRANSPORT::IsTransportInterfaceSupported (
|
|
IN RPC_CHAR * DllName
|
|
)
|
|
#endif
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method is used to search the dictionary. It compares a
|
|
SERVER_LOADABLE_TRANSPORT with a transport interface to see if
|
|
they match.
|
|
|
|
Arguments:
|
|
|
|
DllName - Supplies the name of the dll from which this loadable
|
|
transport interface was loaded.
|
|
|
|
Return Value:
|
|
|
|
0 - They do not match.
|
|
|
|
1 - This object provides the transport interface specified.
|
|
|
|
--*/
|
|
{
|
|
#if defined(NTENV) || defined(WIN96)
|
|
return(((RpcpStringCompare(DllName, this->DllName) == 0) &&
|
|
(RpcpStringCompare(ProtocolSequence, this->RpcProtocolSequence) == 0)
|
|
? 1 : 0));
|
|
#else
|
|
return((RpcpStringCompare(DllName, this->DllName) == 0 ? 1 : 0));
|
|
#endif
|
|
}
|
|
|
|
inline RPC_SERVER_TRANSPORT_INFO PAPI *
|
|
SERVER_LOADABLE_TRANSPORT::InqRpcServerInfo (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
All we do is return a pointer to the RPC_SERVER_TRANSPORT_INFO.
|
|
|
|
Return Value:
|
|
|
|
This object returns its RPC_SERVER_TRANSPORT_INFO.
|
|
|
|
--*/
|
|
{
|
|
return(RpcServerInfo);
|
|
}
|
|
|
|
NEW_SDICT(SERVER_LOADABLE_TRANSPORT);
|
|
SERVER_LOADABLE_TRANSPORT_DICT * SLoadedLoadableTransports;
|
|
|
|
|
|
RPC_SERVER_TRANSPORT_INFO *
|
|
LoadableTransportServerInfo (
|
|
IN RPC_CHAR * DllName,
|
|
IN RPC_CHAR * RpcProtocolSequence,
|
|
OUT RPC_STATUS * Status
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
We need to return the server information for the loadable transport
|
|
specified by the argument, DllName. This may mean that we need
|
|
to load the transport support dll.
|
|
|
|
Argument:
|
|
|
|
DllName - Supplies the name of the dll which we need to try and
|
|
load to get the appropriate loadable transport interface.
|
|
|
|
RpcProtocolSequence - Supplies the rpc protocol sequence for which
|
|
we are looking for the appropriate loadable transport interface.
|
|
|
|
Status - Returns the specific error code for failure to find/load
|
|
a loadable transport.
|
|
|
|
Return Value:
|
|
|
|
0 - If the specified transport interface can not be loaded for any
|
|
reason: does not exist, out of memory, version mismatch, etc.
|
|
|
|
Otherwise, a pointer to the server information for the requested
|
|
transport interface (loadable transport support) will be returned.
|
|
|
|
--*/
|
|
{
|
|
RPC_SERVER_TRANSPORT_INFO PAPI * RpcServerInfo;
|
|
SERVER_LOADABLE_TRANSPORT * ServerLoadableTransport;
|
|
DLL * LoadableTransportDll;
|
|
TRANS_SERVER_INIT_ROUTINE TransServerInitRoutine;
|
|
|
|
// To begin with, check to see if the transport is already loaded.
|
|
// If so, all we have got to do is to return a pointer to it.
|
|
|
|
RequestGlobalMutex();
|
|
|
|
SLoadedLoadableTransports->Reset();
|
|
while ((ServerLoadableTransport = SLoadedLoadableTransports->Next())
|
|
!= 0)
|
|
{
|
|
#if defined(NTENV) || defined(WIN96)
|
|
if (ServerLoadableTransport->IsTransportInterfaceSupported(
|
|
DllName, RpcProtocolSequence))
|
|
#else
|
|
if (ServerLoadableTransport->IsTransportInterfaceSupported(
|
|
DllName))
|
|
#endif
|
|
{
|
|
ClearGlobalMutex();
|
|
return(ServerLoadableTransport->InqRpcServerInfo());
|
|
}
|
|
}
|
|
|
|
// If we reach here, that means that we need to try and load the
|
|
// specified loadable transport DLL.
|
|
|
|
LoadableTransportDll = new DLL(DllName, Status);
|
|
if ( ( LoadableTransportDll == 0 )
|
|
||( *Status != RPC_S_OK ) )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete LoadableTransportDll;
|
|
if ( *Status != RPC_S_OUT_OF_MEMORY )
|
|
{
|
|
ASSERT( *Status == RPC_S_INVALID_ARG );
|
|
*Status = RPC_S_PROTSEQ_NOT_SUPPORTED;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
TransServerInitRoutine = (TRANS_SERVER_INIT_ROUTINE)
|
|
#if defined(NTENV) || defined(DOSWIN32RPC)
|
|
LoadableTransportDll->GetEntryPoint("TransportLoad");
|
|
#else // defined(NTENV) || defined(DOSWIN32RPC)
|
|
LoadableTransportDll->GetEntryPoint(
|
|
(unsigned char *)"TRANSPORTLOAD");
|
|
#endif // defined(NTENV) || defined(DOSWIN32RPC)
|
|
if ( TransServerInitRoutine == 0 )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete LoadableTransportDll;
|
|
*Status = RPC_S_PROTSEQ_NOT_SUPPORTED;
|
|
return(0);
|
|
}
|
|
|
|
RpcServerInfo = (*TransServerInitRoutine)(RpcProtocolSequence);
|
|
if ( RpcServerInfo == 0 )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete LoadableTransportDll;
|
|
*Status = RPC_S_PROTSEQ_NOT_SUPPORTED;
|
|
return(0);
|
|
}
|
|
if ( RpcServerInfo->TransInterfaceVersion
|
|
> RPC_TRANSPORT_INTERFACE_VERSION )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete LoadableTransportDll;
|
|
*Status = RPC_S_PROTSEQ_NOT_SUPPORTED;
|
|
return(0);
|
|
}
|
|
|
|
// When we reach here, we have successfully loaded and initialized
|
|
// the loadable transport DLL. Now we need to create the server
|
|
// loadable transport and stick it in the dictionary.
|
|
|
|
#if defined(NTENV) || defined(WIN96)
|
|
ServerLoadableTransport = new SERVER_LOADABLE_TRANSPORT(
|
|
RpcServerInfo, DllName, RpcProtocolSequence);
|
|
#else
|
|
ServerLoadableTransport = new SERVER_LOADABLE_TRANSPORT(
|
|
RpcServerInfo, DllName);
|
|
#endif
|
|
if ( ServerLoadableTransport == 0 )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete LoadableTransportDll;
|
|
*Status = RPC_S_OUT_OF_MEMORY;
|
|
return(0);
|
|
}
|
|
|
|
if ( SLoadedLoadableTransports->Insert(ServerLoadableTransport) == -1 )
|
|
{
|
|
ClearGlobalMutex();
|
|
delete ServerLoadableTransport;
|
|
delete LoadableTransportDll;
|
|
*Status = RPC_S_OUT_OF_MEMORY;
|
|
return(0);
|
|
}
|
|
|
|
ClearGlobalMutex();
|
|
|
|
return(ServerLoadableTransport->InqRpcServerInfo());
|
|
}
|
|
|
|
|
|
int
|
|
InitializeSTransports (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will get called once at dll initialization time. We
|
|
just have to perform the one time initialization of stuff we need
|
|
in this file.
|
|
|
|
Return Value:
|
|
|
|
Zero will be returned if initialization completes successfully;
|
|
otherwise, non-zero will be returned.
|
|
|
|
--*/
|
|
{
|
|
SLoadedLoadableTransports = new SERVER_LOADABLE_TRANSPORT_DICT;
|
|
if (SLoadedLoadableTransports == 0)
|
|
return(1);
|
|
return(0);
|
|
}
|
|
|