/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    ccmp.c

Abstract:

    Cluster Control Message Protocol code.

Author:

    Mike Massa (mikemas)           January 24, 1997

Revision History:

    Who         When        What
    --------    --------    ----------------------------------------------
    mikemas     01-24-97    created

Notes:

--*/

#include "precomp.h"
#pragma hdrstop
#include "ccmp.tmh"

#include <sspi.h>

#ifdef ALLOC_PRAGMA

#pragma alloc_text(INIT, CcmpLoad)
#pragma alloc_text(PAGE, CcmpUnload)

#endif // ALLOC_PRAGMA

//
// Local Data
//
PCN_RESOURCE_POOL  CcmpSendRequestPool = NULL;
PCN_RESOURCE_POOL  CcmpMcastHBSendRequestPool = NULL;
PCN_RESOURCE_POOL  CcmpReceiveRequestPool = NULL;


#define CCMP_SEND_REQUEST_POOL_DEPTH      5
#define CCMP_RECEIVE_REQUEST_POOL_DEPTH   2

typedef enum {
    CcmpInvalidMsgCode = 0
} CCMP_MSG_CODE;

//
// Packet header structures must be packed.
//
#include <packon.h>

typedef struct {
    ULONG     SeqNumber;
    ULONG     AckNumber;
} CCMP_HEARTBEAT_MSG, *PCCMP_HEARTBEAT_MSG;

typedef struct {
    ULONG     SeqNumber;
} CCMP_POISON_MSG, *PCCMP_POISON_MSG;

typedef struct {
    ULONG             NodeCount;
    CX_CLUSTERSCREEN  McastTargetNodes;
} CCMP_MCAST_HEARTBEAT_HEADER, *PCCMP_MCAST_HEARTBEAT_MSG;

typedef struct {
    UCHAR     Type;
    UCHAR     Code;
    USHORT    Checksum;

    union {
        CCMP_HEARTBEAT_MSG          Heartbeat;
        CCMP_POISON_MSG             Poison;
        CCMP_MCAST_HEARTBEAT_HEADER McastHeartbeat;
    } Message;

} CCMP_HEADER, *PCCMP_HEADER;

#include <packoff.h>


typedef struct {
    PCX_SEND_COMPLETE_ROUTINE     CompletionRoutine;
    PVOID                         CompletionContext;
    PVOID                         MessageData;
} CCMP_SEND_CONTEXT, *PCCMP_SEND_CONTEXT;

typedef struct {
    PCNP_NETWORK  Network;
    CL_NODE_ID    SourceNodeId;
    ULONG         TsduSize;
    ULONG         CnpReceiveFlags;
} CCMP_RECEIVE_CONTEXT, *PCCMP_RECEIVE_CONTEXT;

//
// Size of pre-allocated buffers for CCMP multicast heartbeats.
//
#define CCMP_MCAST_HEARTBEAT_PAYLOAD_PREALLOC(_NodeCount) \
    ((_NodeCount) * sizeof(CX_HB_NODE_INFO))
     
#define CCMP_MCAST_HEARTBEAT_PREALLOC(_NodeCount)         \
    (sizeof(CCMP_HEADER)                                  \
     + CCMP_MCAST_HEARTBEAT_PAYLOAD_PREALLOC(_NodeCount)  \
     )


//
// Security contexts.
//
// The heartbeat and poison packets are signed to detect tampering or
// spoofing.  The context is first established in user mode, then passed to
// clusnet and imported into the kernel security package.
//
// A node maintains an inbound and outbound based context with each node in
// the cluster. Hence, an array, indexed by Node Id, holds the data used to
// represent a context between this node and the specified node.
//
// The use of multiple, simultaneous security packages is supported on NT5. As
// of right now, the signature size can't be determined until the context has
// been generated. It's possible for the signature buffer size for the initial
// context to be smaller than the buffer size for subsequent
// contexts. RichardW is going to provide the ability to determine the
// signature size for a given package without having to generate a context.
//
// There are two scenarios where changing signature buffer size has an effect:
// 1) a mixed mode (SP4/NT5), 2 node cluster is using NTLM with a signature
// buffer size of 16 bytes. The SP4 node is upgraded to NT5. When the two
// nodes join, they will use kerberos which has a larger signature buffer size
// than NTLM but the 1st node has already allocated 16 b. signature
// buffers. This could be fixed by noting the change in buffer size and
// reallocating the lookaside list for the new size. This doesn't solve the
// problem with more than 2 nodes: 2) with > 2 node, mixed mode clusters, it's
// possible to have some nodes using NTLM and others using kerberos. If the
// max signature buffer can be determined before any contexts are generated
// then we'll allocated the largest buffer needed. If not, either multiple
// sets of signature buffers have to be maintained or the old, smaller buffer
// list is deallocated while a new, larger list is generated (in a
// synchronized fashion of course).
//

typedef struct _CLUSNET_SECURITY_DATA {
    CtxtHandle  Inbound;
    CtxtHandle  Outbound;
    ULONG       SignatureBufferSize;
} CLUSNET_SECURITY_DATA, * PCLUSNET_SECURITY_DATA;

//
// this array of structs holds the in/outbound contexts and the signature
// buffer size needed for communicating with the node indexed at this
// location. The index is based on internal (zero based) numbering.
//
CLUSNET_SECURITY_DATA SecurityContexts[ ClusterMinNodeId + ClusterDefaultMaxNodes ];

//
// the size of the signature buffers in the sig buffer lookaside list
//
ULONG AllocatedSignatureBufferSize = 0;

//
// the largest size of the signature buffers imported
//
ULONG MaxSignatureSize = 0;

CN_LOCK SecCtxtLock;

#define VALID_SSPI_HANDLE( _x )     ((_x).dwUpper != (ULONG_PTR)-1 && \
                                     (_x).dwLower != (ULONG_PTR)-1 )

#define INVALIDATE_SSPI_HANDLE( _x ) { \
        (_x).dwUpper = (ULONG_PTR)-1; \
        (_x).dwLower = (ULONG_PTR)-1; \
    }

//
// Lookaside list of signature data and its MDL
//

typedef struct _SIGNATURE_DATA {
    SINGLE_LIST_ENTRY Next;
    CN_SIGNATURE_FIELD
    PMDL SigMDL;
    UCHAR PacketSignature[0];
} SIGNATURE_DATA, *PSIGNATURE_DATA;

PNPAGED_LOOKASIDE_LIST SignatureLL;
#define CN_SIGNATURE_TAG    CN_POOL_TAG

//
// Routines exported within the Cluster Transport.
//
NTSTATUS
CcmpLoad(
    VOID
    )
{
    NTSTATUS   status;
    ULONG      i;

    IF_CNDBG(CN_DEBUG_INIT) {
        CNPRINT(("[CCMP] Loading...\n"));
    }

    CcmpSendRequestPool = CnpCreateSendRequestPool(
                              CNP_VERSION_UNICAST,
                              PROTOCOL_CCMP,
                              sizeof(CCMP_HEADER),
                              sizeof(CCMP_SEND_CONTEXT),
                              CCMP_SEND_REQUEST_POOL_DEPTH
                              );

    if (CcmpSendRequestPool == NULL) {
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    CcmpReceiveRequestPool = CnpCreateReceiveRequestPool(
                                 sizeof(CCMP_RECEIVE_CONTEXT),
                                 CCMP_RECEIVE_REQUEST_POOL_DEPTH
                                 );

    if (CcmpSendRequestPool == NULL) {
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    CcmpMcastHBSendRequestPool = 
        CnpCreateSendRequestPool(
            CNP_VERSION_MULTICAST,
            PROTOCOL_CCMP,
            (USHORT)CCMP_MCAST_HEARTBEAT_PREALLOC(ClusterDefaultMaxNodes),
            (USHORT)sizeof(CCMP_SEND_CONTEXT),
            CCMP_SEND_REQUEST_POOL_DEPTH
            );
    if (CcmpMcastHBSendRequestPool == NULL) {
        IF_CNDBG( CN_DEBUG_INIT )
            CNPRINT(("[CCMP]: no memory for mcast heartbeat "
                     "send request pool\n"));
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    //
    // initialize the individual client and server side security contexts
    //

    for ( i = ClusterMinNodeId; i <= ClusterDefaultMaxNodes; ++i ) {
        INVALIDATE_SSPI_HANDLE( SecurityContexts[ i ].Outbound );
        INVALIDATE_SSPI_HANDLE( SecurityContexts[ i ].Inbound );
        SecurityContexts[ i ].SignatureBufferSize = 0;
    }

    CnInitializeLock( &SecCtxtLock, CNP_SEC_CTXT_LOCK );

    SignatureLL = NULL;

    IF_CNDBG(CN_DEBUG_INIT) {
        CNPRINT(("[CCMP] Loaded.\n"));
    }

    return(STATUS_SUCCESS);

} // CcmpLoad


VOID
CcmpUnload(
    VOID
    )
{
    ULONG i;

    PAGED_CODE();


    IF_CNDBG(CN_DEBUG_INIT) {
        CNPRINT(("[CCMP] Unloading...\n"));
    }

    if (CcmpSendRequestPool != NULL) {
        CnpDeleteSendRequestPool(CcmpSendRequestPool);
        CcmpSendRequestPool = NULL;
    }

    if (CcmpMcastHBSendRequestPool != NULL) {
        CnpDeleteSendRequestPool(CcmpMcastHBSendRequestPool);
        CcmpMcastHBSendRequestPool = NULL;
    }

    if (CcmpReceiveRequestPool != NULL) {
        CnpDeleteReceiveRequestPool(CcmpReceiveRequestPool);
        CcmpReceiveRequestPool = NULL;
    }

    //
    // free Signature buffers and delete security contexts
    //

    if ( SignatureLL != NULL ) {

        ExDeleteNPagedLookasideList( SignatureLL );
        CnFreePool( SignatureLL );
        SignatureLL = NULL;
        AllocatedSignatureBufferSize = 0;
    }

    for ( i = ClusterMinNodeId; i <= ClusterDefaultMaxNodes; ++i ) {

        CxDeleteSecurityContext( i );
    }

    IF_CNDBG(CN_DEBUG_INIT) {
        CNPRINT(("[CCMP] Unload complete.\n"));
    }

    return;

}  // CcmpUnload

#ifdef MM_IN_CLUSNET
VOID
CcmpCompleteSendMembershipMsg(
    IN NTSTATUS           Status,
    IN ULONG              BytesSent,
    IN PCNP_SEND_REQUEST  SendRequest,
    IN PMDL               DataMdl,
    IN PIRP               Irp
    )
{
    PCCMP_SEND_CONTEXT  sendContext = SendRequest->UpperProtocolContext;

    CnAssert(DataMdl != NULL);

    if (NT_SUCCESS(Status)) {
        if (BytesSent >= sizeof(CCMP_HEADER)) {
            BytesSent -= sizeof(CCMP_HEADER);
        }
        else {
            BytesSent = 0;
            CnAssert(FALSE);
        }
        
        //
        // Update the Information field of the completed IRP to
        // reflect the actual bytes sent (adjusted for the CCMP
        // header).
        //
        Irp->IoStatus.Information = BytesSent;
    }
    else {
        CnAssert(BytesSent == 0);
    }

    //
    // Call the completion routine.
    //
    (*(sendContext->CompletionRoutine))(
        Status,
        BytesSent,
        sendContext->CompletionContext,
        sendContext->MessageData
        );

    //
    // Free the stuff we allocated.
    //
    IoFreeMdl(DataMdl);

    CnFreeResource((PCN_RESOURCE) SendRequest);

    return;

}  // CcmpCompleteSendMembershipMsg


NTSTATUS
CxSendMembershipMessage(
    IN CL_NODE_ID                  DestinationNodeId,
    IN PVOID                       MessageData,
    IN USHORT                      MessageDataLength,
    IN PCX_SEND_COMPLETE_ROUTINE   CompletionRoutine,
    IN PVOID                       CompletionContext   OPTIONAL
    )
{
    NTSTATUS            status;
    PCNP_SEND_REQUEST   sendRequest;
    PCCMP_HEADER        ccmpHeader;
    PMDL                dataMdl;
    PCCMP_SEND_CONTEXT  sendContext;


    CnAssert(MessageData != NULL);
    CnAssert(MessageDataLength > 0);

    dataMdl = IoAllocateMdl(
                  MessageData,
                  MessageDataLength,
                  FALSE,
                  FALSE,
                  NULL
                  );

    if (dataMdl != NULL) {
        MmBuildMdlForNonPagedPool(dataMdl);

        sendRequest = (PCNP_SEND_REQUEST) CnAllocateResource(
                                              CcmpSendRequestPool
                                              );

        if (sendRequest != NULL) {

            //
            // Fill in the CCMP header.
            //
            ccmpHeader = sendRequest->UpperProtocolHeader;
            RtlZeroMemory(ccmpHeader, sizeof(CCMP_HEADER));
            ccmpHeader->Type = CcmpMembershipMsgType;

            //
            // Fill in the caller portion of the CNP send request.
            //
            sendRequest->UpperProtocolIrp = NULL;
            sendRequest->CompletionRoutine = CcmpCompleteSendMembershipMsg;

            //
            // Fill in our own send context.
            //
            sendContext = sendRequest->UpperProtocolContext;
            sendContext->CompletionRoutine = CompletionRoutine;
            sendContext->CompletionContext = CompletionContext;
            sendContext->MessageData = MessageData;

            //
            // Send the message.
            //
            status = CnpSendPacket(
                         sendRequest,
                         DestinationNodeId,
                         dataMdl,
                         MessageDataLength,
                         FALSE,
                         ClusterAnyNetworkId
                         );

            return(status);
        }

        IoFreeMdl(dataMdl);
    }

    status = STATUS_INSUFFICIENT_RESOURCES;

    return(status);

}  // CxSendMembershipMessage
#endif // MM_IN_CLUSNET
 
VOID
CcmpCompleteSendHeartbeatMsg(
    IN     NTSTATUS           Status,
    IN OUT PULONG             BytesSent,
    IN     PCNP_SEND_REQUEST  SendRequest,
    IN     PMDL               DataMdl
    )
{
    PCCMP_HEADER        ccmpHeader = SendRequest->UpperProtocolHeader;
    PCNP_HEADER         cnpHeader = SendRequest->CnpHeader;
    PSIGNATURE_DATA     SigData;

    
    if (NT_SUCCESS(Status)) {
        MEMLOG(MemLogHBPacketSendComplete,
               CcmpHeartbeatMsgType,
               ccmpHeader->Message.Heartbeat.SeqNumber);
        
        CnTrace(CCMP_SEND_DETAIL, CcmpTraceSendHBComplete,
            "[CCMP] Send of heartbeat to node %u completed, seqno %u.",
            cnpHeader->DestinationAddress, // LOGULONG
            ccmpHeader->Message.Heartbeat.SeqNumber // LOGULONG
            );
    
        //
        // Strip the CCMP header off of the byte count
        //
        if (*BytesSent >= sizeof(CCMP_HEADER)) {
            *BytesSent -= sizeof(CCMP_HEADER);
        }
        else {
            *BytesSent = 0;
            CnAssert(FALSE);
        }
    }
    else {
        MEMLOG(MemLogPacketSendFailed,
               cnpHeader->DestinationAddress,
               Status);
        
        CnTrace(CCMP_SEND_ERROR, CcmpTraceSendHBFailedBelow,
            "[CCMP] Transport failed to send heartbeat to node %u, "
            "seqno %u, status %!status!.",
            cnpHeader->DestinationAddress, // LOGULONG
            ccmpHeader->Message.Heartbeat.SeqNumber, // LOGULONG
            Status // LOGSTATUS
            );

        CnAssert(*BytesSent == 0);
    }

    //
    // Strip the sig data off of the byte count and free it
    //
    CnAssert(DataMdl != NULL);

    SigData = CONTAINING_RECORD(
                  DataMdl->MappedSystemVa,
                  SIGNATURE_DATA,
                  PacketSignature
                  );

    if (NT_SUCCESS(Status)) {
        if (*BytesSent >= SigData->SigMDL->ByteCount) {
            *BytesSent -= SigData->SigMDL->ByteCount;
        } else {
            *BytesSent = 0;
            CnAssert(FALSE);
        }
    }

    // XXX: restore the original buffer size
    SigData->SigMDL->ByteCount = AllocatedSignatureBufferSize;

    ExFreeToNPagedLookasideList( SignatureLL, SigData );

    //
    // At this point BytesSent should be zero.
    //
    CnAssert(*BytesSent == 0);

    //
    // Free the send request.
    //
    CnFreeResource((PCN_RESOURCE) SendRequest);

    return;

}  // CcmpCompleteSendHeartbeatMsg


NTSTATUS
CxSendHeartBeatMessage(
    IN CL_NODE_ID                  DestinationNodeId,
    IN ULONG                       SeqNumber,
    IN ULONG                       AckNumber,
    IN CL_NETWORK_ID               NetworkId
    )
{
    NTSTATUS            status;
    PCNP_SEND_REQUEST   sendRequest;
    PCCMP_HEADER        ccmpHeader;
    SecBufferDesc       SignatureDescriptor;
    SecBuffer           SignatureSecBuffer[2];
    PSIGNATURE_DATA     SigData;
    CN_IRQL             SecContextIrql;
    PCLUSNET_SECURITY_DATA contextData = &SecurityContexts[ DestinationNodeId ];

    
    sendRequest = (PCNP_SEND_REQUEST) CnAllocateResource( CcmpSendRequestPool );

    if (sendRequest != NULL) {

        //
        // Fill in the CCMP header.
        //
        ccmpHeader = sendRequest->UpperProtocolHeader;
        RtlZeroMemory(ccmpHeader, sizeof(CCMP_HEADER));
        ccmpHeader->Type = CcmpHeartbeatMsgType;
        ccmpHeader->Message.Heartbeat.SeqNumber = SeqNumber;
        ccmpHeader->Message.Heartbeat.AckNumber = AckNumber;

        //
        // allocate a buffer and generate a signature. SignatureLL
        // will be NULL if security contexts have not yet been
        // imported.
        //

        if (SignatureLL != NULL) {
        
            SigData = ExAllocateFromNPagedLookasideList( SignatureLL );

            if (SigData != NULL) {

                //
                // acquire the lock on the security contexts and see if
                // we have a valid one with which to send this packet
                //

                CnAcquireLock( &SecCtxtLock, &SecContextIrql );

                if ( VALID_SSPI_HANDLE( contextData->Outbound )) {

                    //
                    // build a descriptor for the message and signature
                    //

                    SignatureDescriptor.cBuffers = 2;
                    SignatureDescriptor.pBuffers = SignatureSecBuffer;
                    SignatureDescriptor.ulVersion = SECBUFFER_VERSION;

                    SignatureSecBuffer[0].BufferType = SECBUFFER_DATA;
                    SignatureSecBuffer[0].cbBuffer = sizeof(CCMP_HEADER);
                    SignatureSecBuffer[0].pvBuffer = (PVOID)ccmpHeader;

                    SignatureSecBuffer[1].BufferType = SECBUFFER_TOKEN;
                    SignatureSecBuffer[1].cbBuffer = 
                        contextData->SignatureBufferSize;
                    SignatureSecBuffer[1].pvBuffer = 
                        SigData->PacketSignature;

                    status = MakeSignature(&contextData->Outbound,
                                           0,
                                           &SignatureDescriptor,
                                           0);
                    CnAssert( status == STATUS_SUCCESS );

                    CnReleaseLock( &SecCtxtLock, SecContextIrql );

                    if ( status == STATUS_SUCCESS ) {

                        //
                        // Fill in the caller portion of the CNP send request.
                        //
                        sendRequest->UpperProtocolIrp = NULL;
                        sendRequest->CompletionRoutine = 
                            CcmpCompleteSendHeartbeatMsg;

                        //
                        // Send the message.
                        //

                        MEMLOG( 
                            MemLogHBPacketSend, 
                            CcmpHeartbeatMsgType, 
                            SeqNumber
                            );

                        CnTrace(CCMP_SEND_DETAIL, CcmpTraceSendHB,
                            "[CCMP] Sending heartbeat to node %u "
                            "on network %u, seqno %u, ackno %u.",
                            DestinationNodeId, // LOGULONG
                            NetworkId, // LOGULONG
                            SeqNumber, // LOGULONG
                            AckNumber // LOGULONG
                            );

                        //
                        // XXX: adjust the MDL to reflect the true
                        // number of bytes in the signature buffer. This
                        // will go away when the max sig buffer size can
                        // be determined in user mode
                        //
                        SigData->SigMDL->ByteCount = 
                            contextData->SignatureBufferSize;

                        status = CnpSendPacket(
                                     sendRequest,
                                     DestinationNodeId,
                                     SigData->SigMDL,
                                     (USHORT)contextData->SignatureBufferSize,
                                     FALSE,
                                     NetworkId);

                        //
                        // CnpSendPacket is responsible for ensuring 
                        // that CcmpCompleteSendHeartbeatMsg is called (it 
                        // is stored in the send request data structure).
                        //
                    }
                } else {

                    CnReleaseLock( &SecCtxtLock, SecContextIrql );
                    ExFreeToNPagedLookasideList( SignatureLL, SigData );
                    CnFreeResource((PCN_RESOURCE) sendRequest);

                    status = STATUS_CLUSTER_NO_SECURITY_CONTEXT;
                }
            } else {

                CnFreeResource((PCN_RESOURCE) sendRequest);
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
        
        } else {

            CnFreeResource((PCN_RESOURCE) sendRequest);
            status = STATUS_CLUSTER_NO_SECURITY_CONTEXT;
        }

    } else {

        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    if (!NT_SUCCESS(status)) {
        CnTrace(CCMP_SEND_ERROR, CcmpTraceSendHBFailedInternal,
            "[CCMP] Failed to send heartbeat to node %u on net %u, "
            "seqno %u, status %!status!.",
            DestinationNodeId, // LOGULONG
            NetworkId, // LOGULONG
            SeqNumber, // LOGULONG
            status // LOGSTATUS
            );
    }

    return(status);

}  // CxSendHeartbeatMessage


VOID
CcmpCompleteSendMcastHeartbeatMsg(
    IN     NTSTATUS           Status,
    IN OUT PULONG             BytesSent,
    IN     PCNP_SEND_REQUEST  SendRequest,
    IN     PMDL               DataMdl
    )
{
    PCCMP_HEADER        ccmpHeader = SendRequest->UpperProtocolHeader;
    PCNP_HEADER         cnpHeader = SendRequest->CnpHeader;
    PCCMP_SEND_CONTEXT  sendContext = SendRequest->UpperProtocolContext;

    
    if (NT_SUCCESS(Status)) {

        MEMLOG(MemLogHBPacketSendComplete,
               CcmpMcastHeartbeatMsgType,
               0xFFFFFFFF);
        
        CnTrace(
            CCMP_SEND_DETAIL, CcmpTraceSendMcastHBComplete,
            "[CCMP] Send of multicast heartbeat "
            "on network id %u completed.",
            SendRequest->Network->Id // LOGULONG
            );
    
        //
        // Strip the CCMP header and multicast heartbeat payload 
        // off of the byte count. The size of the message sent was
        // saved in the send request data structure.
        //
        if (*BytesSent >= SendRequest->UpperProtocolHeaderLength) {
            *BytesSent -= SendRequest->UpperProtocolHeaderLength;
        }
        else {
            *BytesSent = 0;
            CnAssert(FALSE);
        }
    }
    else {
        MEMLOG(MemLogPacketSendFailed,
               cnpHeader->DestinationAddress,
               Status);
        
        CnTrace(
            CCMP_SEND_ERROR, CcmpTraceSendHBFailedBelow,
            "[CCMP] Transport failed to send multicast "
            "heartbeat on network id %u, status %!status!.",
            SendRequest->Network->Id, // LOGULONG
            Status // LOGSTATUS
            );

        CnAssert(*BytesSent == 0);
    }

    //
    // At this point BytesSent should be zero.
    //
    CnAssert(*BytesSent == 0);

    //
    // Call the completion routine if one was specified
    //
    if (sendContext->CompletionRoutine) {
        (*(sendContext->CompletionRoutine))(
            Status,
            *BytesSent,
            sendContext->CompletionContext,
            NULL
            );
    }

    //
    // Free the send request.
    //
    CnFreeResource((PCN_RESOURCE) SendRequest);

    return;

}  // CcmpCompleteSendHeartbeatMsg


NTSTATUS
CxSendMcastHeartBeatMessage(
    IN     CL_NETWORK_ID               NetworkId,
    IN     PVOID                       McastGroup,
    IN     CX_CLUSTERSCREEN            McastTargetNodes,
    IN     CX_HB_NODE_INFO             NodeInfo[],
    IN     PCX_SEND_COMPLETE_ROUTINE   CompletionRoutine,  OPTIONAL
    IN     PVOID                       CompletionContext   OPTIONAL
    )
/*++

Routine Description:

    Send a multicast heartbeat message. The mcast heartbeat is
    structured as follows:
    
        CCMP_HEADER
        
        CNP_MCAST_SIGNATURE (including signature buffer)
        
        CCMP_MCAST_HEARTBEAT_MESSAGE
        
Arguments:

    NetworkId - network to send mcast heartbeat

    McastGroup - contains data for the multicast group to
        which the message is to be sent
    
    McastTargetNodes - screen that indicates whether the 
        (internal) node id is a target of this multicast heartbeat.
    
    NodeInfo - vector, of size ClusterDefaultMaxNodes+ClusterMinNodeId, 
        of node info data structures indexed by dest node id
    
    CompletionRoutine - called in this routine if the request is
        not passed down to a lower level (in which case it will be
        called by this routine's completion routine)
        
    CompletionContext - context for CompletionRoutine
    
Return value:

    NTSTATUS
    
--*/
{
    NTSTATUS                        status = STATUS_HOST_UNREACHABLE;
    PCNP_SEND_REQUEST               sendRequest;
    PCCMP_HEADER                    ccmpHeader;
    PCCMP_SEND_CONTEXT              sendContext;
    CX_HB_NODE_INFO UNALIGNED     * payload;
    PVOID                           signHeaders[2];
    ULONG                           signHeaderLengths[2];
    ULONG                           sigLen;
    PCNP_MULTICAST_GROUP            mcastGroup;
    BOOLEAN                         pushedPacket = FALSE;


    mcastGroup = (PCNP_MULTICAST_GROUP) McastGroup;
    CnAssert(mcastGroup != NULL);

    sendRequest = (PCNP_SEND_REQUEST) CnAllocateResource( 
                                          CcmpMcastHBSendRequestPool
                                          );

    if (sendRequest != NULL) {

        //
        // Fill in the caller portion of the CNP send request.
        //
        sendRequest->UpperProtocolIrp = NULL;
        sendRequest->CompletionRoutine = CcmpCompleteSendMcastHeartbeatMsg;
        sendRequest->McastGroup = mcastGroup;

        //
        // Fill in our own send context.
        //
        sendContext = sendRequest->UpperProtocolContext;
        sendContext->CompletionRoutine = CompletionRoutine;
        sendContext->CompletionContext = CompletionContext;

        //
        // Fill in the CCMP header. 
        //
        ccmpHeader = sendRequest->UpperProtocolHeader;
        RtlZeroMemory(ccmpHeader, sizeof(CCMP_HEADER));
        ccmpHeader->Type = CcmpMcastHeartbeatMsgType;
        ccmpHeader->Message.McastHeartbeat.NodeCount = ClusterDefaultMaxNodes;
        ccmpHeader->Message.McastHeartbeat.McastTargetNodes = McastTargetNodes;

        //
        // Fill in the heartbeat data.
        //
        payload = (CX_HB_NODE_INFO UNALIGNED *)(ccmpHeader + 1);
        RtlCopyMemory(
            payload,
            &(NodeInfo[ClusterMinNodeId]),
            sizeof(*NodeInfo) * ClusterDefaultMaxNodes
            );

        //
        // Send the message.
        //

        MEMLOG( 
            MemLogHBPacketSend, 
            CcmpMcastHeartbeatMsgType, 
            0xFFFFFFFF
            );

        CnTrace(
            CCMP_SEND_DETAIL, CcmpTraceSendMcastHB,
            "[CCMP] Sending multicast heartbeat on network %u, "
            "node count %u, target mask %04X",
            NetworkId, // LOGULONG
            ClusterDefaultMaxNodes,  // LOGUSHORT
            McastTargetNodes.UlongScreen
            );

        status = CnpSendPacket(
                     sendRequest,
                     ClusterAnyNodeId,
                     NULL,
                     0,
                     FALSE,
                     NetworkId
                     );

        //
        // CnpSendPacket is responsible for ensuring 
        // that CcmpCompleteSendMcastHeartbeatMsg is called
        // (it is stored in the send request data structure).
        //

        pushedPacket = TRUE;


    } else {

        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    if (!NT_SUCCESS(status)) {
        CnTrace(CCMP_SEND_ERROR, CcmpTraceSendMcastHBFailedInternal,
            "[CCMP] Failed to send multicast heartbeat on net %u, "
            "status %!status!, pushedPacket = %!bool!.",
            NetworkId, // LOGULONG
            status, // LOGSTATUS
            pushedPacket
            );
    }

    //
    // If the request wasn't submitted to the next lower layer and
    // a completion routine was provided, call the completion
    // routine.
    //
    if (!pushedPacket && CompletionRoutine) {
        (*CompletionRoutine)(
            status,
            0,
            CompletionContext,
            NULL
            );
    }

    return(status);

} // CxSendMcastHeartBeatMessage


VOID
CcmpCompleteSendPoisonPkt(
    IN     NTSTATUS           Status,
    IN OUT PULONG             BytesSent,
    IN     PCNP_SEND_REQUEST  SendRequest,
    IN     PMDL               DataMdl
    )
{
    PCCMP_SEND_CONTEXT  sendContext = SendRequest->UpperProtocolContext;
    PSIGNATURE_DATA     SigData;
    PCNP_HEADER         cnpHeader = (PCNP_HEADER) SendRequest->CnpHeader;


    MEMLOG(MemLogHBPacketSendComplete,
           CcmpPoisonMsgType,
           ( sendContext->CompletionRoutine == NULL ));

    IF_CNDBG( CN_DEBUG_POISON | CN_DEBUG_CCMPSEND )
        CNPRINT(("[CCMP] Send of poison packet to node %u completed "
                 "with status %08x\n",
                 cnpHeader->DestinationAddress, Status));

    if (NT_SUCCESS(Status)) {
        
        CnTrace(CCMP_SEND_DETAIL, CcmpTraceSendPoisonComplete, 
            "[CCMP] Send of poison packet to node %u completed.",
            cnpHeader->DestinationAddress // LOGULONG
            );
    
        //
        // Strip the CCMP header off of the byte count
        //
        if (*BytesSent >= sizeof(CCMP_HEADER)) {
            *BytesSent -= sizeof(CCMP_HEADER);
        }
        else {
            *BytesSent = 0;
            CnAssert(FALSE);
        }

    } else {
        CnTrace(CCMP_SEND_ERROR, CcmpTraceSendPoisonFailedBelow, 
            "[CCMP] Transport failed to send poison packet to node %u, "
            "status %!status!.",
            cnpHeader->DestinationAddress, // LOGULONG
            Status // LOGSTATUS
            );
        
        CnAssert(*BytesSent == 0);
    }

    //
    // Strip the sig data off of the byte count and free it
    //
    CnAssert(DataMdl != NULL);

    SigData = CONTAINING_RECORD(
                  DataMdl->MappedSystemVa,
                  SIGNATURE_DATA,
                  PacketSignature
                  );

    if (NT_SUCCESS(Status)) {
        if (*BytesSent >= SigData->SigMDL->ByteCount) {
            *BytesSent -= SigData->SigMDL->ByteCount;
        } else {
            *BytesSent = 0;
            CnAssert(FALSE);
        }
    }

    // XXX: restore the original buffer size
    SigData->SigMDL->ByteCount = AllocatedSignatureBufferSize;

    ExFreeToNPagedLookasideList( SignatureLL, SigData );

    //
    // At this point BytesSent should be zero.
    //
    CnAssert(*BytesSent == 0);

    //
    // Call the completion routine if one was specified
    //
    if (sendContext->CompletionRoutine) {
        (*(sendContext->CompletionRoutine))(
            Status,
            *BytesSent,
            sendContext->CompletionContext,
            sendContext->MessageData
            );
    }

    //
    // Free the send request.
    //
    CnFreeResource((PCN_RESOURCE) SendRequest);

    return;

}  // CcmpCompleteSendPoisonPkt


VOID
CxSendPoisonPacket(
    IN CL_NODE_ID                  DestinationNodeId,
    IN PCX_SEND_COMPLETE_ROUTINE   CompletionRoutine,  OPTIONAL
    IN PVOID                       CompletionContext,  OPTIONAL
    IN PIRP                        Irp                 OPTIONAL
    )
{
    NTSTATUS     status;
    PCNP_NODE    node;


    node = CnpFindNode(DestinationNodeId);

    if (node == NULL) {
        if (CompletionRoutine) {
            (*CompletionRoutine)(
                STATUS_CLUSTER_NODE_NOT_FOUND,
                0,
                CompletionContext,
                NULL
                );
        }

        if (Irp) {
            Irp->IoStatus.Status = STATUS_CLUSTER_NODE_NOT_FOUND;
            Irp->IoStatus.Information = 0;
            
            IF_CNDBG( CN_DEBUG_POISON | CN_DEBUG_CCMPSEND )
                CNPRINT(("[CCMP] CxSendPoisonPacket completing IRP "
                         "%p with status %08x\n",
                         Irp, Irp->IoStatus.Status));

            IoCompleteRequest(Irp, IO_NO_INCREMENT);
        }
    }
    else {
        CcmpSendPoisonPacket(
            node,
            CompletionRoutine,
            CompletionContext,
            NULL,
            Irp
            );
    }

    return;

} // CxSendPoisonPacket


VOID
CcmpSendPoisonPacket(
    IN PCNP_NODE                   Node,
    IN PCX_SEND_COMPLETE_ROUTINE   CompletionRoutine,  OPTIONAL
    IN PVOID                       CompletionContext,  OPTIONAL
    IN PCNP_NETWORK                Network,            OPTIONAL
    IN PIRP                        Irp                 OPTIONAL
    )
/*++

Notes:

  Called with the node lock held. Returns with the node lock released.
  
  If this send request is not submitted to the next lower layer, 
  CompletionRoutine must be called (if it is not NULL).

--*/
{
    NTSTATUS                status;
    PCNP_SEND_REQUEST       sendRequest;
    PCCMP_HEADER            ccmpHeader;
    PCCMP_SEND_CONTEXT      sendContext;
    SecBufferDesc           SignatureDescriptor;
    SecBuffer               SignatureSecBuffer[2];
    PSIGNATURE_DATA         SigData;
    CN_IRQL                 SecContextIrql;
    SECURITY_STATUS         secStatus;
    PCNP_INTERFACE          interface;
    PCLUSNET_SECURITY_DATA  contextData = &SecurityContexts[Node->Id];
    CL_NETWORK_ID           networkId;
    CL_NODE_ID              nodeId = Node->Id;


    sendRequest = (PCNP_SEND_REQUEST) CnAllocateResource(CcmpSendRequestPool);

    if (sendRequest != NULL) {
        //
        // make sure we have an interface to send this on. We
        // could be shutting down and have dropped info out of
        // the database
        //
        if ( Network != NULL ) {
            PLIST_ENTRY  entry;

            //
            // we really want to send this packet over the indicated
            // network. walk the node's interface list matching the
            // supplied network id to the interface's network ID and
            // send the packet on that interface
            //

            for (entry = Node->InterfaceList.Flink;
                 entry != &(Node->InterfaceList);
                 entry = entry->Flink
                 )
                {
                    interface = CONTAINING_RECORD(entry,
                                                  CNP_INTERFACE,
                                                  NodeLinkage);

                    if ( interface->Network == Network ) {
                        break;
                    }
                }

            if ( entry == &Node->InterfaceList ) {
                interface = Node->CurrentInterface;
            }
        }
        else {
            interface = Node->CurrentInterface;
        }

        if ( interface != NULL ) {
            networkId = interface->Network->Id;

            //
            // Fill in the CCMP header.
            //
            ccmpHeader = sendRequest->UpperProtocolHeader;
            RtlZeroMemory(ccmpHeader, sizeof(CCMP_HEADER));
            ccmpHeader->Type = CcmpPoisonMsgType;
            ccmpHeader->Message.Poison.SeqNumber =
                ++(interface->SequenceToSend);

            CnReleaseLock( &Node->Lock, Node->Irql );

            //
            // Fill in the caller portion of the CNP send request.
            //
            sendRequest->UpperProtocolIrp = Irp;
            sendRequest->CompletionRoutine = CcmpCompleteSendPoisonPkt;

            //
            // Fill in our own send context.
            //
            sendContext = sendRequest->UpperProtocolContext;
            sendContext->CompletionRoutine = CompletionRoutine;
            sendContext->CompletionContext = CompletionContext;

            //
            // allocate a signature buffer and generate one. SignatureLL
            // will be NULL if security contexts have not yet been
            // imported.
            //

            if (SignatureLL != NULL) {

                SigData = ExAllocateFromNPagedLookasideList( SignatureLL );
                
                if (SigData != NULL) {

                    //
                    // acquire the lock on the security contexts and see if
                    // we have a valid one with which to send this packet
                    //

                    CnAcquireLock( &SecCtxtLock, &SecContextIrql );

                    if ( VALID_SSPI_HANDLE( contextData->Outbound )) {

                        //
                        // build a descriptor for the message and signature
                        //

                        SignatureDescriptor.cBuffers = 2;
                        SignatureDescriptor.pBuffers = SignatureSecBuffer;
                        SignatureDescriptor.ulVersion = SECBUFFER_VERSION;

                        SignatureSecBuffer[0].BufferType = SECBUFFER_DATA;
                        SignatureSecBuffer[0].cbBuffer = sizeof(CCMP_HEADER);
                        SignatureSecBuffer[0].pvBuffer = (PVOID)ccmpHeader;

                        SignatureSecBuffer[1].BufferType = SECBUFFER_TOKEN;
                        SignatureSecBuffer[1].cbBuffer =
                            contextData->SignatureBufferSize;
                        SignatureSecBuffer[1].pvBuffer = 
                            SigData->PacketSignature;

                        secStatus = MakeSignature(
                                        &contextData->Outbound,
                                        0,
                                        &SignatureDescriptor,
                                        0);
                        CnAssert( secStatus == STATUS_SUCCESS );

                        CnReleaseLock( &SecCtxtLock, SecContextIrql );

                        //
                        // no completion routine means this routine was called
                        // from the heartbeat dpc. We'll use that to 
                        // distinguish between that and clussvc calling for a 
                        // poison packet to be sent.
                        //

                        //
                        // WMI tracing prints the thread id,
                        // can figure out DPC or not on our own
                        //
                        CnTrace(CCMP_SEND_DETAIL, CcmpTraceSendPoison,
                            "[CCMP] Sending poison packet to node %u "
                            "on net %u.",
                            nodeId, // LOGULONG
                            networkId // LOGULONG
                            );

                        MEMLOG(MemLogHBPacketSend,
                               CcmpPoisonMsgType,
                               ( CompletionRoutine == NULL ));

                        //
                        // Send the message.
                        //
                        //
                        // XXX: adjust the MDL to reflect the true number of
                        // bytes in the signature buffer. This will go away 
                        // when the max sig buffer size can be determined in
                        // user mode
                        //
                        SigData->SigMDL->ByteCount =
                            contextData->SignatureBufferSize;

                        CnpSendPacket(
                            sendRequest,
                            nodeId,
                            SigData->SigMDL,
                            (USHORT)contextData->SignatureBufferSize,
                            FALSE,
                            networkId
                            );

                        //
                        // CnpSendPacket is responsible for ensuring 
                        // that CcmpCompleteSendPoisonPkt is called.
                        // CcmpCompleteSendPoisonPkt calls CompletionRoutine,
                        // which was a parameter to this routine.
                        //
                        return;

                    } else {

                        CnReleaseLock( &SecCtxtLock, SecContextIrql );
                        ExFreeToNPagedLookasideList( SignatureLL, SigData );
                        CnFreeResource((PCN_RESOURCE) sendRequest);

                        status = STATUS_CLUSTER_NO_SECURITY_CONTEXT;
                    }
                
                } else {

                    CnFreeResource((PCN_RESOURCE) sendRequest);
                    status = STATUS_INSUFFICIENT_RESOURCES;
                }
            } else {

                CnFreeResource((PCN_RESOURCE) sendRequest);
                status = STATUS_CLUSTER_NO_SECURITY_CONTEXT;
            }
        } else {
            CnReleaseLock( &Node->Lock, Node->Irql );
            CnFreeResource((PCN_RESOURCE) sendRequest);
            status = STATUS_CLUSTER_NETINTERFACE_NOT_FOUND;
        }
    } else {
        CnReleaseLock( &Node->Lock, Node->Irql );
        IF_CNDBG( CN_DEBUG_POISON )
            CNPRINT(("[CCMP] No send resources for SendPoisonPacket\n"));

        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    CnTrace(CCMP_SEND_ERROR, CcmpTraceSendPoisonFailedInternal,
        "[CCMP] Failed to send poison packet to node %u, status %!status!.",
        nodeId, // LOGULONG
        status // LOGSTATUS
        );

    //
    // The request to send a poison packet did not make it to the
    // next lower layer. If a completion routine was provided, 
    // call it now.
    //
    if (CompletionRoutine) {

        (*CompletionRoutine)(
            status,
            0,
            CompletionContext,
            NULL
            );
    }

    //
    // If an upper protocol IRP was provided, complete it now.
    //
    if (Irp) {

        IF_CNDBG( CN_DEBUG_POISON | CN_DEBUG_CCMPSEND )
            CNPRINT(("[CCMP] CcmpSendPoisonPacket completing IRP "
                     "%p with status %08x\n",
                     Irp, status));
        
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    return;

}  // CcmpSendPoisonPacket


VOID
CcmpProcessReceivePacket(
    IN  PCNP_NETWORK   Network,
    IN  CL_NODE_ID     SourceNodeId,
    IN  ULONG          CnpReceiveFlags,
    IN  ULONG          TsduSize,
    IN  PVOID          Tsdu
    )
{
    CCMP_HEADER UNALIGNED     * header = Tsdu;
    SECURITY_STATUS             SecStatus;
    CX_HB_NODE_INFO UNALIGNED * nodeInfo;


    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    CnAssert(TsduSize >= sizeof(CCMP_HEADER));

    //
    // adjust to point past CCMP header to message payload.
    //
    // For unicasts, the message payload is the Signature data.
    //
    // For multicasts, the signature was verified at the CNP level.
    //

    if (header->Type == CcmpMcastHeartbeatMsgType) {

        IF_CNDBG(CN_DEBUG_CCMPRECV) {
            CNPRINT(("[CCMP] Recv'd mcast packet from node %u "
                     "on network %u, node count %u, target "
                     "mask %04x, CNP flags %x.\n",
                     SourceNodeId, 
                     Network->Id,
                     header->Message.McastHeartbeat.NodeCount,
                     header->Message.McastHeartbeat.McastTargetNodes.UlongScreen,
                     CnpReceiveFlags
                     ));
        }

        //
        // Verify that the message was identified as a CNP multicast
        // and that the signature was verified.
        //
        if ((CnpReceiveFlags & 
             (CNP_RECV_FLAG_MULTICAST | CNP_RECV_FLAG_SIGNATURE_VERIFIED)
            ) != 
            (CNP_RECV_FLAG_MULTICAST | CNP_RECV_FLAG_SIGNATURE_VERIFIED)
           ) {
        
            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Dropping mcast packet from node %u "
                         "that was not identified as CNP multicast, "
                         "CNP flags %x.\n",
                         SourceNodeId, CnpReceiveFlags
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveNotVerified,
                "[CCMP] Dropping mcast packet from node %u "
                "that was not identified as CNP multicast, "
                "CNP flags %x.",
                SourceNodeId, CnpReceiveFlags
                );

            //
            // Drop it.
            //
            goto error_exit;            
        }

        //
        // Verify that the node count reported in the header is reasonable.
        // It must be compatible with our assumption that the entire 
        // cluster screen fits in one ULONG.
        //
        if (header->Message.McastHeartbeat.NodeCount >
            (sizeof(header->Message.McastHeartbeat.McastTargetNodes) * BYTEL)
            ) {
        
            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Recv'd mcast packet from node %u "
                         "with invalid node count %u, CNP flags %x.\n",
                         SourceNodeId,
                         header->Message.McastHeartbeat.NodeCount,
                         CnpReceiveFlags
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveNotTarget,
                "[CCMP] Recv'd mcast packet from node %u "
                "with invalid node count %u, CNP flags %x.",
                SourceNodeId,
                header->Message.McastHeartbeat.NodeCount,
                CnpReceiveFlags
                );

            //
            // Drop it.
            //
            goto error_exit;            
        }
        
        //
        // Verify that the packet contains data for this node.
        //
        if (!CnpClusterScreenMember(
                 header->Message.McastHeartbeat.McastTargetNodes.ClusterScreen,
                 INT_NODE(CnLocalNodeId)
                 )) {
            
            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Recv'd mcast packet from node %u "
                         "but node %u is not a target, CNP flags %x.\n",
                         SourceNodeId, CnLocalNodeId, CnpReceiveFlags
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveNotTarget,
                "[CCMP] Recv'd mcast packet from node %u "
                "but node %u is not a target, CNP flags %x.",
                SourceNodeId, CnLocalNodeId, CnpReceiveFlags
                );

            //
            // Drop it.
            //
            goto error_exit;            
        }

        nodeInfo = (CX_HB_NODE_INFO UNALIGNED *)((PUCHAR)Tsdu +
                                                 sizeof(CCMP_HEADER));

        SecStatus = SEC_E_OK;

    } else {

        SecBufferDesc            PacketDataDescriptor;
        SecBuffer                PacketData[3];
        ULONG                    fQOP;
        CN_IRQL                  SecContextIrql;
        PCLUSNET_SECURITY_DATA   contextData = &SecurityContexts[SourceNodeId];

        CnAssert(!(CnpReceiveFlags & CNP_RECV_FLAG_MULTICAST));
        CnAssert(!(CnpReceiveFlags & CNP_RECV_FLAG_SIGNATURE_VERIFIED));
        
        Tsdu = header + 1;
        TsduSize -= sizeof(CCMP_HEADER);

        //
        // Acquire the security context lock.
        //
        CnAcquireLock( &SecCtxtLock, &SecContextIrql );

        //
        // Verify that we have a valid context data.
        //
        if ( !VALID_SSPI_HANDLE( contextData->Inbound )) {

            CnReleaseLock( &SecCtxtLock, SecContextIrql );

            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Dropping packet - no security context "
                         "available for src node %u.\n",
                         SourceNodeId // LOGULONG
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveNoSecurityContext, 
                "[CCMP] Dropping packet - no security context available for "
                "src node %u.",
                SourceNodeId // LOGULONG
                );

            MEMLOG( MemLogNoSecurityContext, SourceNodeId, 0 );

            //
            // Drop it.
            //
            goto error_exit;
        } 
            
        //
        // Validate that the received signature size is expected.
        //
        if ( TsduSize < contextData->SignatureBufferSize ) {

            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Recv'd packet from node %u with "
                         "invalid signature buffer size %u.\n",
                         SourceNodeId,
                         TsduSize
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveBadSignatureSize,
                "[CCMP] Recv'd packet from node %u with invalid signature "
                "buffer size %u.",
                SourceNodeId, // LOGULONG
                TsduSize // LOGULONG
                );

            MEMLOG( MemLogSignatureSize, SourceNodeId, TsduSize );

            CnReleaseLock( &SecCtxtLock, SecContextIrql );

            //
            // Drop it.
            //
            goto error_exit;
        }

        //
        // Build the descriptors for the message and the
        // signature buffer
        //
        PacketDataDescriptor.cBuffers = 2;
        PacketDataDescriptor.pBuffers = PacketData;
        PacketDataDescriptor.ulVersion = SECBUFFER_VERSION;

        PacketData[0].BufferType = SECBUFFER_DATA;
        PacketData[0].cbBuffer = sizeof(CCMP_HEADER);
        PacketData[0].pvBuffer = (PVOID)header;

        PacketData[1].BufferType = SECBUFFER_TOKEN;
        PacketData[1].cbBuffer = contextData->SignatureBufferSize;
        PacketData[1].pvBuffer = (PVOID)Tsdu;

        //
        // Verify the signature of the packet.
        //
        SecStatus = VerifySignature(&contextData->Inbound,
                                    &PacketDataDescriptor,
                                    0,          // no sequence number
                                    &fQOP);     // Quality of protection

        //
        // Release the security context lock.
        //
        CnReleaseLock( &SecCtxtLock, SecContextIrql );
    }
    
    //
    // If the signature was verified, deliver the message.
    //
    if ( SecStatus == SEC_E_OK ) {

        if (header->Type == CcmpHeartbeatMsgType) {
            CnpReceiveHeartBeatMessage(Network,
                                       SourceNodeId,
                                       header->Message.Heartbeat.SeqNumber,
                                       header->Message.Heartbeat.AckNumber,
                                       FALSE);
        }
        else if (header->Type == CcmpMcastHeartbeatMsgType) {
            CnpReceiveHeartBeatMessage(
                Network,
                SourceNodeId,
                nodeInfo[INT_NODE(CnLocalNodeId)].SeqNumber,
                nodeInfo[INT_NODE(CnLocalNodeId)].AckNumber,
                (BOOLEAN)(CnpReceiveFlags & CNP_RECV_FLAG_CURRENT_MULTICAST_GROUP)
                );
        }
        else if (header->Type == CcmpPoisonMsgType) {
            CnpReceivePoisonPacket(Network,
                                   SourceNodeId,
                                   header->Message.Heartbeat.SeqNumber);
        }
#ifdef MM_IN_CLUSNET
        else if (header->Type == CcmpMembershipMsgType) {
            if (TsduSize > 0) {
                PVOID  messageBuffer = Tsdu;

                //
                // Copy the data if it is unaligned.
                //
                if ( (((ULONG) Tsdu) & 0x3) != 0 ) {
                    IF_CNDBG(CN_DEBUG_CCMPRECV) {
                        CNPRINT(("[CCMP] Copying misaligned membership packet\n"));
                    }

                    messageBuffer = CnAllocatePool(TsduSize);

                    if (messageBuffer != NULL) {
                        RtlMoveMemory(messageBuffer, Tsdu, TsduSize);
                    }
                }

                if (messageBuffer != NULL) {

                    CmmReceiveMessageHandler(SourceNodeId,
                                             messageBuffer,
                                             TsduSize);
                }

                if (messageBuffer != Tsdu) {
                    CnFreePool(messageBuffer);
                }
            }
        }
#endif // MM_IN_CLUSNET
        else {
            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Received packet with unknown "
                         "type %u from node %u, CNP flags %x.\n",
                         header->Type, 
                         SourceNodeId,
                         CnpReceiveFlags
                         ));
            }

            CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveInvalidType,
                "[CCMP] Received packet with unknown type %u from "
                "node %u, CNP flags %x.",
                header->Type, // LOGUCHAR
                SourceNodeId, // LOGULONG
                CnpReceiveFlags // LOGXLONG
                );
            CnAssert(FALSE);
        }
    } else {
        IF_CNDBG(CN_DEBUG_CCMPRECV) {
            CNPRINT(("[CCMP] Recv'd packet type %u with bad "
                     "signature from node %d, security status %08x, "
                     "CNP flags %x.\n",
                     header->Type, 
                     SourceNodeId, 
                     SecStatus,
                     CnpReceiveFlags
                     ));
        }

        CnTrace(CCMP_RECV_ERROR, CcmpTraceReceiveInvalidSignature,
            "[CCMP] Recv'd %!msgtype! packet with bad signature from node %d, "
            "security status %08x, CNP flags %x.",
            header->Type, // LOGMsgType
            SourceNodeId, // LOGULONG
            SecStatus, // LOGXLONG
            CnpReceiveFlags // LOGXLONG
            );

        MEMLOG( MemLogInvalidSignature, SourceNodeId, header->Type );
    }

error_exit:

    CnVerifyCpuLockMask(
                        0,                // Required
                        0xFFFFFFFF,       // Forbidden
                        0                 // Maximum
                        );

    return;

} // CcmpProcessReceivePacket


NTSTATUS
CcmpCompleteReceivePacket(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp,
    IN  PVOID           Context
    )
{
    PCNP_RECEIVE_REQUEST   request = Context;
    PCCMP_RECEIVE_CONTEXT  context = request->UpperProtocolContext;


    if (Irp->IoStatus.Status == STATUS_SUCCESS) {
        CnAssert(Irp->IoStatus.Information == context->TsduSize);

        CcmpProcessReceivePacket(
            context->Network,
            context->SourceNodeId,
            context->CnpReceiveFlags,
            (ULONG)Irp->IoStatus.Information,
            request->DataBuffer
            );
    }
    else {
        CnTrace(CCMP_RECV_ERROR, CcmpTraceCompleteReceiveFailed,
            "[CDP] Failed to fetch packet data, src node %u, "
            "CNP flags %x, status %!status!.",
            context->SourceNodeId, // LOGULONG
            context->CnpReceiveFlags, // LOGXLONG
            Irp->IoStatus.Status // LOGSTATUS
            );        
    }

    CnpFreeReceiveRequest(request);

    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    return(STATUS_MORE_PROCESSING_REQUIRED);

} // CcmpCompleteReceivePacket


NTSTATUS
CcmpReceivePacketHandler(
    IN  PCNP_NETWORK   Network,
    IN  CL_NODE_ID     SourceNodeId,
    IN  ULONG          CnpReceiveFlags,
    IN  ULONG          TdiReceiveDatagramFlags,
    IN  ULONG          BytesIndicated,
    IN  ULONG          BytesAvailable,
    OUT PULONG         BytesTaken,
    IN  PVOID          Tsdu,
    OUT PIRP *         Irp
    )
{
    NTSTATUS                 status;
    CCMP_HEADER UNALIGNED *  header = Tsdu;
    PCNP_RECEIVE_REQUEST     request;


    CnAssert(KeGetCurrentIrql() == DISPATCH_LEVEL);

    if (BytesIndicated >= sizeof(CCMP_HEADER)) {
        if (BytesIndicated == BytesAvailable) {

            CcmpProcessReceivePacket(
                Network,
                SourceNodeId,
                CnpReceiveFlags,
                BytesAvailable,
                Tsdu
                );

            *BytesTaken += BytesAvailable;
            *Irp = NULL;

            CnVerifyCpuLockMask(
                0,                // Required
                0xFFFFFFFF,       // Forbidden
                0                 // Maximum
                );

            return(STATUS_SUCCESS);
        }

        //
        // We need to fetch the rest of the packet before we
        // can process it.
        //
        // This message cannot be a CNP multicast, because 
        // the CNP layer could not have verified an incomplete 
        // message.
        //
        CnAssert(!(CnpReceiveFlags & CNP_RECV_FLAG_MULTICAST));
        CnAssert(!(CnpReceiveFlags & CNP_RECV_FLAG_SIGNATURE_VERIFIED));
        CnAssert(header->Type != CcmpMcastHeartbeatMsgType);

        request = CnpAllocateReceiveRequest(
                      CcmpReceiveRequestPool,
                      Network,
                      BytesAvailable,
                      CcmpCompleteReceivePacket
                      );

        if (request != NULL) {
            PCCMP_RECEIVE_CONTEXT  context = request->UpperProtocolContext;

            context->Network = Network;
            context->SourceNodeId = SourceNodeId;
            context->TsduSize = BytesAvailable;
            context->CnpReceiveFlags = CnpReceiveFlags;

            *Irp = request->Irp;

            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Fetching packet data, src node %u, "
                         "BI %u, BA %u, CNP flags %x.\n",
                         SourceNodeId, BytesIndicated, 
                         BytesAvailable, CnpReceiveFlags));

            }
            
            CnTrace(CCMP_RECV_DETAIL, CcmpTraceCompleteReceive,
                "[CCMP] Fetching packet data, src node %u, "
                "BI %u, BA %u, CNP flags %x.",
                SourceNodeId, // LOGULONG
                BytesIndicated, // LOGULONG
                BytesAvailable, // LOGULONG
                CnpReceiveFlags // LOGXLONG
                );        
            
            CnVerifyCpuLockMask(
                0,                // Required
                0xFFFFFFFF,       // Forbidden
                0                 // Maximum
                );

            return(STATUS_MORE_PROCESSING_REQUIRED);
        }
        else {
            IF_CNDBG(CN_DEBUG_CCMPRECV) {
                CNPRINT(("[CCMP] Dropped incoming packet - "
                         "out of resources, src node %u.\n",
                         SourceNodeId));

            }
            CnTrace(CCMP_RECV_ERROR, CcmpTraceDropReceiveOOR,
                "[CCMP] Dropped incoming packet - out of resources, "
                "src node %u.",
                SourceNodeId // LOGULONG
                );        
        }
    }
    else {
        IF_CNDBG(CN_DEBUG_CCMPRECV) {
            CNPRINT(("[CCMP] Dropped incoming runt packet, "
                     "src node %u, BI %u, BA %u, CNP flags %x.\n",
                     SourceNodeId, BytesIndicated, BytesAvailable,
                     CnpReceiveFlags));

        }
        CnTrace(CCMP_RECV_ERROR, CcmpTraceDropReceiveRunt,
            "[CCMP] Dropped incoming runt packet, src node %u, "
            "BI %u, BA %u, CNP flags %x.",
            SourceNodeId, // LOGULONG
            BytesIndicated, // LOGULONG
            BytesAvailable, // LOGULONG
            CnpReceiveFlags // LOGXLONG
            );        
    }

    //
    // Something went wrong. Drop the packet.
    //
    *BytesTaken += BytesAvailable;

    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    return(STATUS_SUCCESS);

}  // CcmpReceivePacketHandler

PVOID
SignatureAllocate(
    IN POOL_TYPE PoolType,
    IN SIZE_T NumberOfBytes,
    IN ULONG Tag
    )
{
    PSIGNATURE_DATA SignatureData;

    CnAssert( NumberOfBytes == ( sizeof(SIGNATURE_DATA) + AllocatedSignatureBufferSize ));

    //
    // allocate the space and then construct an MDL describing it
    //

    SignatureData = ExAllocatePoolWithTag( PoolType, NumberOfBytes, Tag );

    if ( SignatureData != NULL ) {

        SignatureData->SigMDL = IoAllocateMdl(SignatureData->PacketSignature,
                                              AllocatedSignatureBufferSize,
                                              FALSE,
                                              FALSE,
                                              NULL);

        if ( SignatureData->SigMDL != NULL ) {

            MmBuildMdlForNonPagedPool(SignatureData->SigMDL);
            CN_INIT_SIGNATURE( SignatureData, CN_SIGNATURE_TAG );
        } else {

            ExFreePool( SignatureData );
            SignatureData = NULL;
        }
    }

    return SignatureData;
}

VOID
SignatureFree(
    IN PVOID Buffer
    )
{
    PSIGNATURE_DATA SignatureData = (PSIGNATURE_DATA)Buffer;

    CN_ASSERT_SIGNATURE( SignatureData, CN_SIGNATURE_TAG );
    IoFreeMdl( SignatureData->SigMDL );

    ExFreePool( SignatureData );
}

VOID
CxDeleteSecurityContext(
    IN  CL_NODE_ID NodeId
    )

/*++

Routine Description:

    Delete the security context associated with the specified node

Arguments:

    NodeId - Id of the node blah blah blah

Return Value:

    None

--*/

{
    PCLUSNET_SECURITY_DATA contextData = &SecurityContexts[ NodeId ];

    if ( VALID_SSPI_HANDLE( contextData->Inbound )) {

        DeleteSecurityContext( &contextData->Inbound );
        INVALIDATE_SSPI_HANDLE( contextData->Inbound );
    }

    if ( VALID_SSPI_HANDLE( contextData->Outbound )) {

        DeleteSecurityContext( &contextData->Outbound );
        INVALIDATE_SSPI_HANDLE( contextData->Outbound );
    }
}


NTSTATUS
CxImportSecurityContext(
    IN  CL_NODE_ID NodeId,
    IN  PWCHAR PackageName,
    IN  ULONG PackageNameSize,
    IN  ULONG SignatureSize,
    IN  PVOID ServerContext,
    IN  PVOID ClientContext
    )

/*++

Routine Description:

    import a security context that was established in user mode into
    the kernel SSP. We are passed pointers to the structures in user
    mode, so they have be probed and used within try/except blocks.

Arguments:

    NodeId - # of node with which a security context was established

    PackageName - user process pointer to security package name

    PackageNameSize - length, in bytes, of PackageName

    SignatureSize - size, in bytes, needed for a Signature Buffer

    ServerContext - user process pointer to area that contains the
        SecBuffer for an inbound security context

    ClientContext - same as ServerContext, but for outbound security
        context

Return Value:

    STATUS_SUCCESS if everything worked ok, otherwise some error in issperr.h

--*/

{
    PSecBuffer      InboundSecBuffer = (PSecBuffer)ServerContext;
    PSecBuffer      OutboundSecBuffer = (PSecBuffer)ClientContext;

    PVOID           CapturedInboundSecData;
    ULONG           CapturedInboundSecDataSize;
    PVOID           CapturedOutboundSecData;
    ULONG           CapturedOutboundSecDataSize;

    CtxtHandle      InboundContext;
    CtxtHandle      OutboundContext;
    NTSTATUS        Status;

    PWCHAR          KPackageName = NULL;
    PSecBuffer      KInboundSecBuffer = NULL;
    PSecBuffer      KOutboundSecBuffer = NULL;
    PVOID           KInboundData = NULL;
    PVOID           KOutboundData = NULL;
    CN_IRQL         SecContextIrql;
    SECURITY_STRING PackageNameDesc;

    //
    // even though this routine is not marked pagable, make sure that we're
    // not running at raised IRQL since DeleteSecurityContext will puke.
    //
    PAGED_CODE();

    IF_CNDBG( CN_DEBUG_INIT )
        CNPRINT(("[CCMP]: Importing security contexts from %ws\n",
                 PackageName));

    if ( AllocatedSignatureBufferSize == 0 ) {
        //
        // first time in this routine, so create a lookaside list pool for
        // signature buffers and their MDLs
        //

        CnAssert( SignatureLL == NULL );
        SignatureLL = CnAllocatePool( sizeof( NPAGED_LOOKASIDE_LIST ));

        if ( SignatureLL != NULL ) {
            //
            // with the support of multiple packages, the only way to
            // determine the sig buffer size was after a context had been
            // generated. Knowing the max size of all sig buffers used by the
            // service before this routine is called will prevent having to
            // add a bunch of synchronization code that would allocate new
            // buffers and phase out the old buffer pool. on NT5, NTLM uses 16
            // bytes while kerberos uses 37b. We've asked security for a call
            // that will give us the max sig size for a set of packages but
            // that hasn't materialized, hence we force the sig buffer size to
            // something that will work for both NTLM and kerberos. But this
            // discussion is kinda moot since we don't use kerberos anyway on
            // NT5.
            //

//            AllocatedSignatureBufferSize = SignatureSize;
            AllocatedSignatureBufferSize = 64;

#if 0
            ExInitializeNPagedLookasideList(SignatureLL,
                                            SignatureAllocate,
                                            SignatureFree,
                                            0,
                                            sizeof( SIGNATURE_DATA ) + SignatureSize,
                                            CN_POOL_TAG,
                                            4);
#endif
            ExInitializeNPagedLookasideList(SignatureLL,
                                            SignatureAllocate,
                                            SignatureFree,
                                            0,
                                            sizeof( SIGNATURE_DATA ) + AllocatedSignatureBufferSize,
                                            CN_POOL_TAG,
                                            4);
        } else {
            IF_CNDBG( CN_DEBUG_INIT )
                CNPRINT(("[CCMP]: no memory for signature LL\n"));

            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto error_exit;
        }

    } else if ( SignatureSize > AllocatedSignatureBufferSize ) {

        //
        // the signature buffer is growing. the problem is that the lookaside
        // list is already in use by other nodes.
        //
        Status = STATUS_INVALID_PARAMETER;
        goto error_exit;
    }

    //
    // validate the pointers passed in as the SecBuffers
    //

    try {
        ProbeForRead( PackageName,
                      PackageNameSize,
                      sizeof( UCHAR ) );

        ProbeForRead( InboundSecBuffer,
                      sizeof( SecBuffer ),
                      sizeof( UCHAR ) );

        ProbeForRead( OutboundSecBuffer,
                      sizeof( SecBuffer ),
                      sizeof( UCHAR ) );

        //
        // made it this far; now capture the internal pointers and their
        // lengths. Probe the embedded pointers in the SecBuffers using the
        // captured data
        //
        CapturedInboundSecData = InboundSecBuffer->pvBuffer;
        CapturedInboundSecDataSize = InboundSecBuffer->cbBuffer;

        CapturedOutboundSecData = OutboundSecBuffer->pvBuffer;
        CapturedOutboundSecDataSize = OutboundSecBuffer->cbBuffer;

        ProbeForRead( CapturedInboundSecData,
                      CapturedInboundSecDataSize,
                      sizeof( UCHAR ) );

        ProbeForRead( CapturedOutboundSecData,
                      CapturedOutboundSecDataSize,
                      sizeof( UCHAR ) );

        //
        // make local copies of everything since security doesn't
        // handle accvios very well
        //

        KPackageName = CnAllocatePoolWithQuota( PackageNameSize );
        if ( KPackageName == NULL ) {
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }

        RtlCopyMemory( KPackageName, PackageName, PackageNameSize );

        KInboundSecBuffer = CnAllocatePoolWithQuota( sizeof( SecBuffer ));
        if ( KInboundSecBuffer == NULL ) {
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }
        *KInboundSecBuffer = *InboundSecBuffer;
        KInboundSecBuffer->cbBuffer = CapturedInboundSecDataSize;

        KOutboundSecBuffer = CnAllocatePoolWithQuota( sizeof( SecBuffer ));
        if ( KOutboundSecBuffer == NULL ) {
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }
        *KOutboundSecBuffer = *OutboundSecBuffer;
        KOutboundSecBuffer->cbBuffer = CapturedOutboundSecDataSize;

        KInboundData = CnAllocatePoolWithQuota( KInboundSecBuffer->cbBuffer );
        if ( KInboundData == NULL ) {
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }
        RtlCopyMemory( KInboundData, CapturedInboundSecData, CapturedInboundSecDataSize );
        KInboundSecBuffer->pvBuffer = KInboundData;

        KOutboundData = CnAllocatePoolWithQuota( KOutboundSecBuffer->cbBuffer );
        if ( KOutboundData == NULL ) {
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }
        RtlCopyMemory( KOutboundData, CapturedOutboundSecData, CapturedOutboundSecDataSize );
        KOutboundSecBuffer->pvBuffer = KOutboundData;

    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred while attempting to probe or copy
        // from one of the caller's parameters. Simply return an
        // appropriate error status code.
        //

        Status = GetExceptionCode();
        IF_CNDBG( CN_DEBUG_INIT )
            CNPRINT(("[CCMP]: Buffer probe failed %08X", Status ));

        goto error_exit;
    }

    //
    // import the data we were handed
    //

    RtlInitUnicodeString( &PackageNameDesc, KPackageName );

    Status = ImportSecurityContext(&PackageNameDesc,
                                   KInboundSecBuffer,
                                   NULL,
                                   &InboundContext);

    if ( NT_SUCCESS( Status )) {

        Status = ImportSecurityContext(&PackageNameDesc,
                                       KOutboundSecBuffer,
                                       NULL,
                                       &OutboundContext);

        if ( NT_SUCCESS( Status )) {
            CtxtHandle oldInbound;
            CtxtHandle oldOutbound;
            PCLUSNET_SECURITY_DATA contextData = &SecurityContexts[ NodeId ];

            INVALIDATE_SSPI_HANDLE( oldInbound );
            INVALIDATE_SSPI_HANDLE( oldOutbound );

            //
            // DeleteSecurityContext can't be called at raised IRQL so make
            // copies of the contexts to be deleted under the lock. After
            // releasing the lock, we can delete the old contexts.
            //

            CnAcquireLock( &SecCtxtLock, &SecContextIrql );

            if ( VALID_SSPI_HANDLE( contextData->Inbound )) {
                oldInbound = contextData->Inbound;
            }

            if ( VALID_SSPI_HANDLE( contextData->Outbound )) {
                oldOutbound = contextData->Outbound;
            }

            contextData->Inbound = InboundContext;
            contextData->Outbound = OutboundContext;
            contextData->SignatureBufferSize = SignatureSize;

            //
            // Update MaxSignatureSize -- the largest signature imported
            // so far.
            //
            if (SignatureSize > MaxSignatureSize) {
                MaxSignatureSize = SignatureSize;
            }

            CnReleaseLock( &SecCtxtLock, SecContextIrql );

            if ( VALID_SSPI_HANDLE( oldInbound )) {
                DeleteSecurityContext( &oldInbound );
            }

            if ( VALID_SSPI_HANDLE( oldOutbound )) {
                DeleteSecurityContext( &oldOutbound );
            }
        } else {
            IF_CNDBG( CN_DEBUG_INIT )
                CNPRINT(("[CCMP]: import of outbound security context failed  %08X\n", Status ));

            DeleteSecurityContext( &InboundContext );

            goto error_exit;
        }
    } else {
        IF_CNDBG( CN_DEBUG_INIT )
            CNPRINT(("[CCMP]: import of inbound security context failed %08X\n", Status ));
        goto error_exit;
    }

error_exit:

    //
    // Clean up allocations.
    //

    if ( KPackageName ) {
        CnFreePool( KPackageName );
    }

    if ( KInboundSecBuffer ) {
        CnFreePool( KInboundSecBuffer );
    }

    if ( KOutboundSecBuffer ) {
        CnFreePool( KOutboundSecBuffer );
    }

    if ( KInboundData ) {
        CnFreePool( KInboundData );
    }

    if ( KOutboundData ) {
        CnFreePool( KOutboundData );
    }

    if (NT_SUCCESS(Status)) {
        return Status;
    }

    //
    // The following is only executed in an error situation.
    //

    IF_CNDBG( CN_DEBUG_INIT ) {
        CNPRINT(("[CCMP]: CxImportSecurityContext returning %08X%\n", Status));
    }
    
    if (CcmpMcastHBSendRequestPool != NULL) {
        CnpDeleteSendRequestPool(CcmpMcastHBSendRequestPool);
        CcmpMcastHBSendRequestPool = NULL;
    }
    if (SignatureLL != NULL) {
        ExDeleteNPagedLookasideList(SignatureLL);
        CnFreePool(SignatureLL);
        SignatureLL = NULL;
    }

    return Status;

} // CxImportSecurityContext