/*++ BUILD Version: 0009    // Increment this if a change has global effects
Copyright (c) 1987-1993  Microsoft Corporation

Module Name:

    smbcemid.c

Abstract:

    This module defines the routines for manipulating MIDs associated with SMB's

Notes:

--*/

#include "precomp.h"
#pragma hdrstop

RXDT_DefineCategory(SMBCEMID);

#define Dbg        (DEBUG_TRACE_SMBCEMID)


NTSTATUS
SmbCeAssociateExchangeWithMid(
   PSMBCEDB_SERVER_ENTRY pServerEntry,
   struct _SMB_EXCHANGE  *pExchange)
/*++

Routine Description:

   This routine associates an exchange with a MID

Arguments:

    pServerEntry - the servere entry

    pExchange    - the Exchange instance.

Return Value:

    STATUS_SUCCESS if successful, otherwise one of the following errors

Notes:

   If an asynchronous mechanism to acquire MID's is to be introduced this routine
   needs to be modified. Currently this routine does not return control till a
   MID is acquired or the exchange is aborted/terminated.

--*/
{
   NTSTATUS                 Status = STATUS_SUCCESS;
   PSMBCEDB_REQUEST_ENTRY   pRequestEntry;
   SMBCE_RESUMPTION_CONTEXT ResumptionContext;
   SMBCEDB_SERVER_TYPE      ServerType;

   ServerType = SmbCeGetServerType(pServerEntry);

   // Acquire the resource
   SmbCeAcquireSpinLock();

   // Attempt to allocate a MID only for FILE Servers. Mailslot servers do
   // not require a valid MID.

   if (ServerType != SMBCEDB_MAILSLOT_SERVER) {
      if (pServerEntry->pMidAtlas != NULL) {
         if (pExchange->SmbCeFlags & SMBCE_EXCHANGE_INDEFINITE_DELAY_IN_RESPONSE) {
            // This exchange response can be arbitrarily delayed. Ensure that
            // all the available MIDS are not tied up in such exchanges.

            if ((pServerEntry->pMidAtlas->NumberOfMidsInUse + 1) ==
                pServerEntry->pMidAtlas->MaximumNumberOfMids) {
               Status = STATUS_TOO_MANY_COMMANDS;
            }
         }

         if (Status == STATUS_SUCCESS) {
            Status = IfsMrxAssociateContextWithMid(
                              pServerEntry->pMidAtlas,
                              pExchange,
                              &pExchange->Mid);
         }
      } else {
         Status = STATUS_INVALID_PARAMETER;
      }
   }

   if (Status == STATUS_UNSUCCESSFUL) {
      // Allocate a new entry and add it to the list.
      pRequestEntry = (PSMBCEDB_REQUEST_ENTRY)SmbMmAllocateObject(SMBCEDB_OT_REQUEST);
      if (pRequestEntry != NULL) {
         // Enqueue the request entry.

         SmbCeInitializeResumptionContext(&ResumptionContext);

         pRequestEntry->MidRequest.Type               = ACQUIRE_MID_REQUEST;
         pRequestEntry->MidRequest.pExchange          = pExchange;
         pRequestEntry->MidRequest.pResumptionContext = &ResumptionContext;
         SmbCeAddRequestEntryLite(&pServerEntry->MidAssignmentRequests,pRequestEntry);
      } else {
         Status = STATUS_INSUFFICIENT_RESOURCES;
      }
   } else if (Status == STATUS_SUCCESS) {
      pExchange->SmbCeFlags |= SMBCE_EXCHANGE_MID_VALID;
   }

   // Release the resource
   SmbCeReleaseSpinLock();

   if (Status == STATUS_UNSUCCESSFUL) {
      //DbgPrint("***** Thread %lx Waiting for MID Resumption Context %lx*****\n",PsGetCurrentThread(),&ResumptionContext);
      SmbCeSuspend(&ResumptionContext);
      Status = ResumptionContext.Status;
      //DbgPrint("***** Thread %lx MID Wait Satisfied %lx *****\n",PsGetCurrentThread(),&ResumptionContext);
   }

   return Status;
}

struct _SMB_EXCHANGE *
SmbCeMapMidToExchange(
   PSMBCEDB_SERVER_ENTRY pServerEntry,
   SMB_MPX_ID            Mid)
/*++

Routine Description:

   This routine maps a given MID to the exchange associated with it

Arguments:

    pServerEntry - the servere entry

    Mid          - the mid to be mapped to an Exchange.

Return Value:

    a valid SMB_EXCHANGE instance if successful, otheriwse NULL.

--*/
{

   PSMB_EXCHANGE pExchange;

   // Acquire the resource
   SmbCeAcquireSpinLock();

   if (pServerEntry->pMidAtlas != NULL) {
      pExchange = IfsMrxMapMidToContext(
                        pServerEntry->pMidAtlas,
                        Mid);
   } else {
      pExchange = NULL;
   }

   // Release the resource
   SmbCeReleaseSpinLock();

   return pExchange;
}

NTSTATUS
SmbCeDisassociateMidFromExchange(
   PSMBCEDB_SERVER_ENTRY pServerEntry,
   struct _SMB_EXCHANGE  *pExchange)
/*++

Routine Description:

   This routine disassociates an exchange from the MID

Arguments:

    pServerEntry - the servere entry

    pExchange    - the exchange instance.

Return Value:

    a valid SMB_EXCHANGE instance if successful, otheriwse NULL.

Notes:

   If an asynchronous mechanism to acquire MID's is to be introduced this routine
   needs to be modified. This modification will also include posting requests
   for resumption of exchanges when invoked at DPC level.

--*/
{
   NTSTATUS               Status = STATUS_SUCCESS;
   SMBCEDB_SERVER_TYPE    ServerType;

   ServerType = SmbCeGetServerType(pServerEntry);

   if ((ServerType != SMBCEDB_MAILSLOT_SERVER) &&
       (pExchange->Mid != SMBCE_OPLOCK_RESPONSE_MID)) {
      PVOID                  pContext;
      PSMBCEDB_REQUEST_ENTRY pRequestEntry = NULL;

      // Acquire the resource
      SmbCeAcquireSpinLock();

      if (pExchange->SmbCeFlags & SMBCE_EXCHANGE_MID_VALID) {
         // Check if there are any pending MID assignment requests and transfer the MID
         // if one exists.
         pRequestEntry = SmbCeGetFirstRequestEntry(&pServerEntry->MidAssignmentRequests);
         if (pRequestEntry != NULL) {
            SmbCeRemoveRequestEntryLite(&pServerEntry->MidAssignmentRequests,pRequestEntry);
         }

         if (pServerEntry->pMidAtlas != NULL) {
            if (pRequestEntry != NULL) {
               Status = IfsMrxReassociateMid(
                                 pServerEntry->pMidAtlas,
                                 pExchange->Mid,
                                 pRequestEntry->MidRequest.pExchange);

               ASSERT(Status == STATUS_SUCCESS);

               pRequestEntry->MidRequest.pExchange->SmbCeFlags |= SMBCE_EXCHANGE_MID_VALID;
               pRequestEntry->MidRequest.pExchange->Mid = pExchange->Mid;
               pRequestEntry->MidRequest.pResumptionContext->Status = STATUS_SUCCESS;
            } else {
               Status = IfsMrxMapAndDissociateMidFromContext(
                                 pServerEntry->pMidAtlas,
                                 pExchange->Mid,
                                 &pContext);

               ASSERT(pContext == pExchange);
            }
         } else {
            Status = STATUS_INVALID_PARAMETER;
         }
      }

      // Release the resource
      SmbCeReleaseSpinLock();

      if (pRequestEntry != NULL) {
         // Signal the waiter for resumption
         //DbgPrint("***** Satisfying wait on MID Resumption Context %lx*****\n",pRequestEntry->MidRequest.pResumptionContext);
         SmbCeResume(pRequestEntry->MidRequest.pResumptionContext);

         SmbCeTearDownRequestEntry(pRequestEntry);
      }
   }

   pExchange->SmbCeFlags &= ~SMBCE_EXCHANGE_MID_VALID;

   return Status;
}

struct _SMB_EXCHANGE *
SmbCeGetExchangeAssociatedWithBuffer(
   PSMBCEDB_SERVER_ENTRY pServerEntry,
   PVOID                 pBuffer)
/*++

Routine Description:

   This routine gets the exchange associated with a buffer

Arguments:

    pServerEntry - the servere entry

    pBuffer      - the buffer instance.

Return Value:

    a valid SMB_EXCHANGE instance if successful, otheriwse NULL.

Notes:

   This routine and the routines that follow enable a pipelined reuse of MID's
   If a large buffer is to be copied then this can be done without hodling onto
   a MID. This improves the throughput between the client and the server. At the
   very least this mechanism ensures that the connection engine will not be the
   constraining factor in MID reuse.

--*/
{
   PSMBCEDB_REQUEST_ENTRY pRequestEntry;
   PSMB_EXCHANGE          pExchange = NULL;

   // Acquire the resource
   SmbCeAcquireSpinLock();

   // Walk through the list of requests maintained on this and remove the one
   // matching the cached buffer ptr with the ptr indicated
   pRequestEntry = SmbCeGetFirstRequestEntry(&pServerEntry->OutstandingRequests);
   while (pRequestEntry != NULL) {
      if ((pRequestEntry->GenericRequest.Type == COPY_DATA_REQUEST) &&
          (pRequestEntry->CopyDataRequest.pBuffer == pBuffer)) {
         pExchange = pRequestEntry->CopyDataRequest.pExchange;
         pRequestEntry->CopyDataRequest.pBuffer = NULL;
         break;
      }

      pRequestEntry = SmbCeGetNextRequestEntry(
                              &pServerEntry->OutstandingRequests,
                              pRequestEntry);
   }

   // Release the resource
   SmbCeReleaseSpinLock();

   return pExchange;
}

NTSTATUS
SmbCeAssociateBufferWithExchange(
   PSMBCEDB_SERVER_ENTRY  pServerEntry,
   struct _SMB_EXCHANGE * pExchange,
   PVOID                  pBuffer)
/*++

Routine Description:

   This routine establishes an association between an exchange and a copy data request
   buffer

Arguments:

    pServerEntry - the servere entry

    pBuffer      - the buffer instance.

Return Value:

    STATUS_SUCCESS if succesful

--*/
{
   NTSTATUS               Status = STATUS_SUCCESS;
   PSMBCEDB_REQUEST_ENTRY pRequestEntry;

   // Acquire the resource
   SmbCeAcquireSpinLock();

   Status = pServerEntry->ServerStatus;
   if (Status == STATUS_SUCCESS) {
      // Walk through the list of requests maintained on this and remove the one
      // matching the cached buffer ptr with the ptr indicated
      pRequestEntry = SmbCeGetFirstRequestEntry(&pServerEntry->OutstandingRequests);
      while (pRequestEntry != NULL) {
         if ((pRequestEntry->GenericRequest.Type == COPY_DATA_REQUEST) &&
             (pRequestEntry->CopyDataRequest.pBuffer == NULL)) {
            pRequestEntry->CopyDataRequest.pExchange = pExchange;
            pRequestEntry->CopyDataRequest.pBuffer = pBuffer;
            break;
         }
         pRequestEntry = SmbCeGetNextRequestEntry(&pServerEntry->OutstandingRequests,pRequestEntry);
      }
   }

   // Release the resource
   SmbCeReleaseSpinLock();

   if ((Status == STATUS_SUCCESS) &&
       (pRequestEntry == NULL)) {
      // Allocate a new entry and add it to the list.
      pRequestEntry = (PSMBCEDB_REQUEST_ENTRY)SmbMmAllocateObject(SMBCEDB_OT_REQUEST);
      if (pRequestEntry != NULL) {
         // Enqueue the request entry.
         pRequestEntry->CopyDataRequest.Type      = COPY_DATA_REQUEST;
         pRequestEntry->CopyDataRequest.pExchange = pExchange;
         pRequestEntry->CopyDataRequest.pBuffer   = pBuffer;

         // Acquire the resource
         SmbCeAcquireSpinLock();

         if ((Status = pServerEntry->ServerStatus) == STATUS_SUCCESS) {
            SmbCeAddRequestEntryLite(&pServerEntry->OutstandingRequests,pRequestEntry);
         }

         // Release the resource
         SmbCeReleaseSpinLock();

         if (Status != STATUS_SUCCESS) {
            SmbCeTearDownRequestEntry(pRequestEntry);
         }
      } else {
         Status = STATUS_INSUFFICIENT_RESOURCES;
      }
   }

   return Status;
}

VOID
SmbCePurgeBuffersAssociatedWithExchange(
   PSMBCEDB_SERVER_ENTRY  pServerEntry,
   struct _SMB_EXCHANGE * pExchange)
/*++

Routine Description:

   This routine purges all the copy data requests associated with an exchange.

Arguments:

    pServerEntry - the servere entry

    pExchange    - the exchange instance.

Notes:

   This mechanism of delaying the purging of requests associated with an exchange
   till it is discared is intended to solve the problem of repeated allocation/freeing
   of request entries. This rests on the assumption that there will not be too many
   copy data requests outstanding for any exchange. If evidence to the contrary is
   noticed this technique has to be modified.

--*/
{
   SMBCEDB_REQUESTS       ExchangeRequests;
   PSMBCEDB_REQUEST_ENTRY pRequestEntry;
   PSMBCEDB_REQUEST_ENTRY pNextRequestEntry;

   SmbCeInitializeRequests(&ExchangeRequests);

   // Acquire the resource
   SmbCeAcquireSpinLock();

   // Walk through the list of requests maintained on this and remove the one
   // matching the given exchange
   pRequestEntry = SmbCeGetFirstRequestEntry(&pServerEntry->OutstandingRequests);
   while (pRequestEntry != NULL) {
      pNextRequestEntry = SmbCeGetNextRequestEntry(&pServerEntry->OutstandingRequests,pRequestEntry);
      if (pRequestEntry->GenericRequest.pExchange == pExchange) {
         SmbCeRemoveRequestEntryLite(&pServerEntry->OutStandingRequests,pRequestEntry);
         SmbCeAddRequestEntryLite(&ExchangeRequests,pRequestEntry);
      }
      pRequestEntry = pNextRequestEntry;
   }

   // Release the resource
   SmbCeReleaseSpinLock();

   pRequestEntry = SmbCeGetFirstRequestEntry(&ExchangeRequests);
   while (pRequestEntry != NULL) {
      SmbCeRemoveRequestEntryLite(&ExchangeRequests,pRequestEntry);
      SmbCeTearDownRequestEntry(pRequestEntry);
      pRequestEntry = SmbCeGetFirstRequestEntry(&ExchangeRequests);
   }
}

