/****************************************************************************/ // mcsapi.c // // TS RDPWSX MCS user-mode multiplexing layer code. // // Copyright (C) 1997-2000 Microsoft Corporation /****************************************************************************/ #include "precomp.h" #pragma hdrstop #include "mcsmux.h" /* * Defines */ #define DefaultNumDomains 10 #define DefaultNumChannels 3 #define MinDomainControlMarker 0xFFFFFFF0 #define ReadChannelRequestMarker 0xFFFFFFFD #define CloseChannelRequestMarker 0xFFFFFFFE #define ExitIoThreadMarker 0xFFFFFFFF // Defines the maximum number of threads that can be waiting on the global // I/O completion port. 0 means the same as the number of processors in // the system. #define MaxIoPortThreads 0 /* * DLL globals */ static PVOID g_NCUserDefined = NULL; static DWORD g_IoThreadID = 0; static HANDLE g_hIoPort = NULL, g_hIoThread = NULL, g_hIoThreadEndEvent = NULL; static OVERLAPPED g_NCOverlapped; static MCSNodeControllerCallback g_NCCallback = NULL; // Externs global to all sources. BOOL g_bInitialized = FALSE; #ifdef MCS_FUTURE CRITICAL_SECTION g_csGlobalListLock; SList g_UserList; SList g_ConnList; #endif /* * Local function prototypes. */ MCSError SendConnectProviderResponse(Domain *, ConnectionHandle, MCSResult, BYTE *, unsigned); /* * Initialization done at RDPWSX.dll load. */ BOOL MCSDLLInit(void) { return TRUE; } /* * Cleanup done at RDPWSX.dll unload. */ void MCSDllCleanup(void) { // Cleanup all resources. MCSCleanup() must handle abnormal // terminations where MCSCleanup() was not properly called // by the node controller. MCSCleanup(); } /* * Utility functions. */ void DestroyUserInfo(UserInformation *pUserInfo) { MCSChannel *pMCSChannel; SListResetIteration(&pUserInfo->JoinedChannelList); while (SListIterate(&pUserInfo->JoinedChannelList, (unsigned *)&pMCSChannel, &pMCSChannel)) Free(pMCSChannel); SListDestroy(&pUserInfo->JoinedChannelList); } // Perform common domain destruction. Used in multiple places in code. void DestroyDomain(Domain *pDomain) { // Fill the to-be-freed Domain with recognizable trash // and then release it back to the heap DeleteCriticalSection(&pDomain->csLock); Free(pDomain); } /* * Handles a connect-provider indication ChanelInput coming from kernel mode. */ void HandleConnectProviderIndication( Domain *pDomain, unsigned BytesTransferred, ConnectProviderIndicationIoctl *pCPin) { MCSError MCSErr; Connection *pConn; ConnectProviderIndication CP; if (BytesTransferred != sizeof(ConnectProviderIndicationIoctl)) { ErrOutIca1(pDomain->hIca, "HandleConnectProvInd(): Wrong size data " "received (%d), ignoring", BytesTransferred); return; } ASSERT(pCPin->UserDataLength <= MaxGCCConnectDataLength); if (pDomain->State != Dom_Unconnected) { ErrOutIca(pDomain->hIca, "HandleConnectProvInd(): Connect received " "unexpectedly, ignoring"); return; } // Generate a new Connection for user mode. Associate it with the domain. // This allows the ConnectProviderResponse() to find the domain again. // TODO FUTURE: We use a static Connection embedded in the Domain since // we are currently a single-connection system. Change this in the // future for multiple-connection system. pConn = &pDomain->MainConn; pConn->pDomain = pDomain; pConn->hConnKernel = pCPin->hConn; #ifdef MCS_Future pConn = Malloc(sizeof(Connection)); if (pConn == NULL) { ErrOutIca(pDomain->hIca, "HandleConnectProvInd(): Could not " "create Connection"); // Send error PDU back. MCSErr = SendConnectProviderResponse(pDomain, pCPin->hConn, RESULT_UNSPECIFIED_FAILURE, NULL, 0); // We cannot do much more error handling if this does not work. ASSERT(MCSErr == MCS_NO_ERROR); return; } EnterCriticalSection(&g_csGlobalListLock); if (!SListAppend(&g_ConnList, (unsigned)pConn, pDomain)) { LeaveCriticalSection(&g_csGlobalListLock); ErrOutIca(pDomain->hIca, "ConnectProvInd: Could not " "add hConn to global list"); // Send error PDU back. MCSErr = SendConnectProviderResponse(pDomain, pCPin->hConn, RESULT_UNSPECIFIED_FAILURE, NULL, 0); // We cannot do much more error handling if this does not work. ASSERT(MCSErr == MCS_NO_ERROR); return; } LeaveCriticalSection(&g_csGlobalListLock); #endif // Store information away for future use. pDomain->DomParams = pCPin->DomainParams; // Prepare a ConnectProviderIndication for sending up to // the node controller. CP.hConnection = pConn; CP.bUpwardConnection = pCPin->bUpwardConnection; CP.DomainParams = pCPin->DomainParams; CP.UserDataLength = pCPin->UserDataLength; if (CP.UserDataLength == 0) CP.pUserData = NULL; else CP.pUserData = pCPin->UserData; //TODO FUTURE: This is a hack, assumes only one connection // per domain. This is used in DisconnectProviderInd // to get the user mode hConn for the domain. pDomain->hConn = pConn; // Set state to pending response. pDomain->State = Dom_PendingCPResponse; // Call the node controller callback. TraceOutIca(pDomain->hIca, "MCS_CONNECT_PROV_IND received, calling node " "ctrl callback"); ASSERT(g_NCCallback != NULL); g_NCCallback(pDomain, MCS_CONNECT_PROVIDER_INDICATION, &CP, pDomain->NCUserDefined); } /* * Handles a disconnect-provider indication ChanelInput coming from kernel * mode. */ void HandleDisconnectProviderIndication( Domain *pDomain, unsigned BytesTransferred, DisconnectProviderIndicationIoctl *pDPin) { Domain *pDomainConn; Connection *pConn; DisconnectProviderIndication DP; if (BytesTransferred != sizeof(DisconnectProviderIndicationIoctl)) { ErrOutIca1(pDomain->hIca, "HandleDiscProvInd(): Wrong size data " "received (%d), ignoring", BytesTransferred); return; } #ifdef MCS_Future // Remove the connection from the connection list, destroy. EnterCriticalSection(&g_csGlobalListLock); SListResetIteration(&g_ConnList); while (SListIterate(&g_ConnList, (unsigned *)&pConn, &pDomainConn)) { if (pConn->hConnKernel == pDPin->hConn) { ASSERT(pDomainConn == pDomain); SListRemove(&g_ConnList, (unsigned)pConn, NULL); // TODO FUTURE: This was removed to work with the statically // allocated Connection object contained in the Domain. // Restore if moving to a multiple-connection system. Free(pConn); break; } } LeaveCriticalSection(&g_csGlobalListLock); #endif // We are now no longer connected. pDomain->hConn = NULL; pDomain->State = Dom_Unconnected; // Prepare a DisconnectProviderIndication for sending up // to node controller. DP.hDomain = pDomain; //TODO FUTURE: This hack assumes only one connection per // domain. DP.hConnection = pDomain->hConn; DP.Reason = pDPin->Reason; // Call the node controller callback. TraceOutIca(pDomain->hIca, "MCS_DISCONNECT_PROV_IND received, calling " "node ctrl callback"); ASSERT(g_NCCallback != NULL); g_NCCallback(pDomain, MCS_DISCONNECT_PROVIDER_INDICATION, &DP, pDomain->NCUserDefined); } /* * Takes a reference count on a domain */ VOID MCSReferenceDomain(Domain *pDomain) { if (InterlockedIncrement(&pDomain->RefCount) <= 0) ASSERT(0); } /* * Releases domain resources if the reference count goes to zero */ VOID MCSDereferenceDomain(Domain *pDomain) { ASSERT(pDomain->RefCount > 0); // Don't delete the domain unless everyone is done with it. This means // GCC has let it go, and there are no pending I/O's for it. if (InterlockedDecrement(&pDomain->RefCount) == 0) { DestroyDomain(pDomain); } } /* * Closes the domain channel. Does nothing if the channel is already closed */ VOID MCSChannelClose(Domain *pDomain) { // Note that the channel is disconnected, and then // close the ica channel if it is still open pDomain->bPortDisconnected = TRUE; if (pDomain->hIcaT120Channel != NULL) { //TraceOutIca(pDomain->hIca, "MCSChannelClose(): Closing " // "T120 ICA channel"); CancelIo(pDomain->hIcaT120Channel); IcaChannelClose(pDomain->hIcaT120Channel); pDomain->hIcaT120Channel = NULL; } } /* * Initiates port disconnection */ NTSTATUS MCSDisconnectPort(Domain *pDomain, MCSReason Reason) { NTSTATUS ntStatus = STATUS_SUCCESS; DisconnectProviderRequestIoctl DPrq; // Send special disconnect-provider request to kernel to trigger // sending detach-user requests to local attachments with their own // UserIDs, signaling that the domain is going away. if (!pDomain->bPortDisconnected) { DPrq.Header.Type = MCS_DISCONNECT_PROVIDER_REQUEST; DPrq.Header.hUser = NULL; // Special meaning node controller. DPrq.hConn = NULL; // Special meaning last local connection. DPrq.Reason = Reason; // Call kernel mode ntStatus = IcaStackIoControl(pDomain->hIcaStack, IOCTL_T120_REQUEST, &DPrq, sizeof(DPrq), NULL, 0, NULL); } // Queue a channel close request to the IoThreadFunc() to cancel the I/O // since GCC is done with it. This is necessary as the I/O must // be canceled from the same thread that initially issued it. MCSReferenceDomain(pDomain); PostQueuedCompletionStatus(g_hIoPort, CloseChannelRequestMarker, (ULONG_PTR)pDomain, NULL); return ntStatus; } /* * Handles port data and reissues the read */ VOID MCSPortData(Domain *pDomain, DWORD BytesTransferred) { IoctlHeader *pHeader; EnterCriticalSection(&pDomain->csLock); // If real data has been received on the channel instead of a queued domain // control message, then process the data. if (BytesTransferred < MinDomainControlMarker) { // We only do callbacks if MCSDeleteDomain() was not called. if (pDomain->bDeleteDomainCalled == FALSE) { // Decode the ChannelInput and make the callback. pHeader = (IoctlHeader *)pDomain->InputBuf; switch (pHeader->Type) { case MCS_CONNECT_PROVIDER_INDICATION: ASSERT(pHeader->hUser == NULL); HandleConnectProviderIndication(pDomain, BytesTransferred, (ConnectProviderIndicationIoctl *)pHeader); break; case MCS_DISCONNECT_PROVIDER_INDICATION: ASSERT(pHeader->hUser == NULL); HandleDisconnectProviderIndication(pDomain, BytesTransferred, (DisconnectProviderIndicationIoctl *)pHeader); MCSChannelClose(pDomain); break; default: //TODO FUTURE: Handle other MCS indications/confirms. ErrOutIca2(pDomain->hIca, "IoThreadFunc(): Unknown " "node controller ioctl %d received for " "domain %X", pHeader->Type, pDomain); break; } // Set the message number to an invalid value. // This makes sure that any improperly-received dequeued // messages that do not bring data with them will, when // reusing pDomain->InputBuf, fall to the default part of // the switch statement and throw an error. pHeader->Type = 0xFFFFFFFF; } } // Else, a special domain control request has been queued to the I/O port else { switch (BytesTransferred) { case ReadChannelRequestMarker : break; case CloseChannelRequestMarker : MCSChannelClose(pDomain); break; default: ErrOutIca2(pDomain->hIca, "MCSPortData: Unknown domain control " "for Domain(%lx), code(%lx)", pDomain, BytesTransferred); } } // Issue a new read to catch the next indication/confirm. Overlapped // I/O reads will return as "unsuccessful" if there is no data already // waiting to be read so check for that status specifically. if (pDomain->hIcaT120Channel) { if (ReadFile(pDomain->hIcaT120Channel, pDomain->InputBuf, DefaultInputBufSize, NULL, &pDomain->Overlapped) || (GetLastError() == ERROR_IO_PENDING)) MCSReferenceDomain(pDomain); else { // Warning only. This should occur only when the ICA stack // is being torn down without our direct knowledge. In that // case, we simply keep running until we are torn down at the // user mode level. Take off a ref count since there is no // longer a pending I/O. WarnOutIca2(pDomain->hIca, "IoThreadFunc(): Could not perform " "ReadFile, pDomain=%X, rc=%X", pDomain, GetLastError()); } } // Release the lock on the domain LeaveCriticalSection(&pDomain->csLock); // drop the reference count since we just completed processing MCSDereferenceDomain(pDomain); } /* * Param is the handle for the I/O completion port to wait on. * Completion key of ExitIoThreadMarker tells the thread to exit. * On exit we set an event to indicate we are done. This must be done instead * of relying solely on the thread's handle signaling in the case where * the unload is occurring because of abnormal termination, and Cleanup() * does not get called normally but instead from within DLL_PROCESS_DETACH. * The call to DllEntryPoint prevents the thread handle from signaling, * so we would end up in a race condition. A parallel event handle can be * signaled correctly in all cases. */ DWORD WINAPI IoThreadFunc(void *Param) { BOOL bSuccess; DWORD BytesTransferred; Domain *pDomain; OVERLAPPED *pOverlapped; ASSERT(Param != NULL); for (;;) { // Wait for a port completion status pDomain = NULL; pOverlapped = NULL; bSuccess = GetQueuedCompletionStatus((HANDLE)Param, &BytesTransferred, (ULONG_PTR *)&pDomain, &pOverlapped, INFINITE); // Check for failed dequeue, at which point pDomain is not valid. if (!bSuccess && (pOverlapped == NULL)) { continue; } // If the pDomain is ExitIoThreadMarker then we are // shutting down and being unloaded if (pDomain == (Domain *)(DWORD_PTR)ExitIoThreadMarker) break; // We have successfully received a completion status. Either it is a // domain control request or user data if (bSuccess) MCSPortData(pDomain, BytesTransferred); // Else a cancel or abort I/O has occurred. Release a ref count since // an I/O is no longer pending. else MCSDereferenceDomain(pDomain); } ASSERT(g_hIoThreadEndEvent); SetEvent(g_hIoThreadEndEvent); return 0; } /* * MUX-only non-MCS primitive function to allow ICA code to inject a new entry * into the MUX-internal domain/stack database. */ MCSError APIENTRY MCSCreateDomain( HANDLE hIca, HANDLE hIcaStack, void *pContext, DomainHandle *phDomain) { NTSTATUS status; Domain *pDomain; NTSTATUS Status; IoctlHeader StartIoctl; CheckInitialized("CreateDomain()"); // Init the receiver's data to NULL to ensure a fault if they skip the // error code. *phDomain = NULL; pDomain = Malloc(sizeof(Domain)); if (pDomain != NULL) { // Create and enter the locking critical section. status = RtlInitializeCriticalSection(&pDomain->csLock); if (status == STATUS_SUCCESS) { EnterCriticalSection(&pDomain->csLock); #if MCS_Future pDomain->SelLen = 0; #endif pDomain->hIca = hIca; pDomain->hIcaStack = hIcaStack; pDomain->NCUserDefined = pContext; pDomain->State = Dom_Unconnected; pDomain->Overlapped.hEvent = NULL; pDomain->Overlapped.Offset = pDomain->Overlapped.OffsetHigh = 0; pDomain->RefCount = 0; // Take a reference count for the caller of this function (GCC). MCSReferenceDomain(pDomain); // Open T.120 ICA virtual channel. Status = IcaChannelOpen(hIca, Channel_Virtual, Virtual_T120, &pDomain->hIcaT120Channel); if (!NT_SUCCESS(Status)) { ErrOutIca(hIca, "CreateDomain: Error opening virtual channel"); goto PostInitCS; } // Add the hIcaT120 Channel to the handles associated with the // main I/O completion port. We use pDomain as the completion key // so we can find it during callback processing. if (CreateIoCompletionPort(pDomain->hIcaT120Channel, g_hIoPort, (ULONG_PTR)pDomain, MaxIoPortThreads) == NULL) { ErrOutIca(hIca, "CreateDomain(): Could not add ICA channel to " "I/O completion port"); goto PostChannel; } // Tell kernel mode to start the MCS I/O. StartIoctl.hUser = NULL; StartIoctl.Type = MCS_T120_START; Status = IcaStackIoControl(hIcaStack, IOCTL_T120_REQUEST, &StartIoctl, sizeof(StartIoctl), NULL, 0, NULL); if (!NT_SUCCESS(Status)) { ErrOutIca(hIca, "Could not start kernel T120 I/O"); goto PostChannel; } // Set the domain handle for use by GCC *phDomain = pDomain; // Send a message to the IoThreadFunc to initiate the read on // the channel. We do this so that the same thread is always // responsible for all I/O operations. This is important for // CancelIo. Take another ref count since an I/O result is // pending for the completion port now. MCSReferenceDomain(pDomain); PostQueuedCompletionStatus(g_hIoPort, ReadChannelRequestMarker, (ULONG_PTR)pDomain, NULL); // Leave the Domain locking critical section. LeaveCriticalSection(&pDomain->csLock); } else { ErrOutIca(hIca, "CreateDomain: Error initialize pDomain->csLock"); goto PostAlloc; } } else { ErrOutIca(hIca, "CreateDomain(): Error allocating a new domain"); return MCS_ALLOCATION_FAILURE; } return MCS_NO_ERROR; // Error handling PostChannel: IcaChannelClose(pDomain->hIcaT120Channel); PostInitCS: LeaveCriticalSection(&pDomain->csLock); DeleteCriticalSection(&pDomain->csLock); PostAlloc: Free(pDomain); return MCS_ALLOCATION_FAILURE; } /* * Signals that an hIca is no longer valid. * Tears down the associated domain, including sending detach-user * indications to remaining local attachments. */ MCSError APIENTRY MCSDeleteDomain( HANDLE hIca, DomainHandle hDomain, MCSReason Reason) { Domain *pDomain, pDomainConn; NTSTATUS Status; MCSError MCSErr; Connection *pConn; pDomain = (Domain *)hDomain; TraceOutIca(hIca, "DeleteDomain() entry"); // Gain access to the domain. Prevents collision with other API functions // and concurrent callbacks. EnterCriticalSection(&pDomain->csLock); // This API should not be called more than once per domain. if (pDomain->bDeleteDomainCalled) { ASSERT(!pDomain->bDeleteDomainCalled); MCSErr = MCS_INVALID_PARAMETER; LeaveCriticalSection(&pDomain->csLock); goto PostLockDomain; } #ifdef MCS_Future // If we are still connected find and remove remaining connections // which refer to this domain. if (pDomain->State != Dom_Unconnected) { //TODO FUTURE: This assumes only one connection per domain. EnterCriticalSection(&g_csGlobalListLock); SListRemove(&g_ConnList, (unsigned)pDomain->hConn, (void **)&pDomainConn); LeaveCriticalSection(&g_csGlobalListLock); // TODO FUTURE: This was removed to work with the statically // allocated Connection object contained in the Domain. // Restore if moving to a multiple-connection system. if (pDomainConn != NULL) Free(pConn); } #endif pDomain->hConn = NULL; pDomain->State = Dom_Unconnected; //TODO: Implement detach-user indications for local attachments. // Queue the "Destroy this domain!" request. This allows the domain // free code to be in only one place and the IoPort queue will // serialize this request along with closed virtual channel indications. pDomain->bDeleteDomainCalled = TRUE; MCSDisconnectPort(pDomain, Reason); // Drop a ref count because GCC is done with the domain. This count was // originally incremented for GCC in MCSCreateDomain(). LeaveCriticalSection(&pDomain->csLock); MCSDereferenceDomain(pDomain); MCSErr = MCS_NO_ERROR; PostLockDomain: return MCSErr; } /* * Called by node controller to initialize DLL. */ MCSError APIENTRY MCSInitialize(MCSNodeControllerCallback Callback) { NTSTATUS status; #if DBG if (g_bInitialized) { ErrOut("Initialize() called when already initialized"); return MCS_ALREADY_INITIALIZED; } #endif // Initialize node controller specific information. g_NCCallback = Callback; #ifdef MCS_FUTURE // Initialize global MCS lists. status = RtlInitializeCriticalSection(&g_csGlobalListLock); if (status != STATUS_SUCCESS) { ErrOut("MCSInitialize: Error initialize g_csGlobalListLock"); return MCS_ALLOCATION_FAILURE; } EnterCriticalSection(&g_csGlobalListLock); SListInit(&g_UserList, DefaultNumDomains); SListInit(&g_ConnList, DefaultNumDomains); LeaveCriticalSection(&g_csGlobalListLock); #endif // Create I/O completion port, not associated with any requests. g_hIoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, MaxIoPortThreads); if (g_hIoPort == NULL) { ErrOut1("IO completion port not created (rc = %ld)", GetLastError()); return MCS_ALLOCATION_FAILURE; } // Create I/O port listening thread, starts waiting immediately. g_hIoThread = CreateThread(NULL, 0, IoThreadFunc, g_hIoPort, 0, &g_IoThreadID); if (g_hIoThread == NULL) { ErrOut1("IO thread not created (rc = %ld)", GetLastError()); return MCS_ALLOCATION_FAILURE; } // Increase the priority of the IoThread to increase connect performance. SetThreadPriority(g_hIoThread, THREAD_PRIORITY_HIGHEST); g_bInitialized = TRUE; return MCS_NO_ERROR; } /* * Called by the node controller or DllEntryPoint() to shut down the DLL. */ MCSError APIENTRY MCSCleanup(void) { DWORD WaitResult; Domain *pDomain; Connection *pConn; UserInformation *pUserInfo; CheckInitialized("Cleanup()"); if (!g_bInitialized) return MCS_NO_ERROR; // Kill I/O completion port, thread(s) waiting on it. g_hIoThreadEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL); PostQueuedCompletionStatus(g_hIoPort, 0, ExitIoThreadMarker, NULL); WaitResult = WaitForSingleObject(g_hIoThreadEndEvent, INFINITE); CloseHandle(g_hIoThreadEndEvent); CloseHandle(g_hIoThread); CloseHandle(g_hIoPort); // Cleanup node controller-specific information. g_NCCallback = NULL; g_NCUserDefined = NULL; /* * Clean up global MCS lists. */ #ifdef MCS_Future EnterCriticalSection(&g_csGlobalListLock); // Kill remaining users. SListResetIteration(&g_UserList); while (SListIterate(&g_UserList, (unsigned *)&pUserInfo, NULL)) { DestroyUserInfo(pUserInfo); Free(pUserInfo); } SListDestroy(&g_UserList); // Kill remaining connections. SListResetIteration(&g_ConnList); // TODO FUTURE: Removed to work with statically allocated Connection // contained in Domain. Restore fo multiple-connection system. while (SListIterate(&g_ConnList, (unsigned *)&pConn, NULL)) Free(pConn); SListDestroy(&g_ConnList); LeaveCriticalSection(&g_csGlobalListLock); DeleteCriticalSection(&g_csGlobalListLock); #endif g_bInitialized = FALSE; return MCS_NO_ERROR; } /* * Response function for an MCS_CONNECT_PROVIDER_INDICATION callback. Result * values are as defined by T.125. */ MCSError APIENTRY MCSConnectProviderResponse( DomainHandle hDomain, MCSResult Result, BYTE *pUserData, unsigned UserDataLen) { Domain *pDomain; MCSError MCSErr; ConnectProviderResponseIoctl CPrs; CheckInitialized("ConnectProvResponse()"); pDomain = (Domain *) hDomain; // Lock the domain. EnterCriticalSection(&pDomain->csLock); // It is possible for us to call this function in the wrong state, e.g. // if we have gotten a disconnect very soon after processing a // connection, and have transitioned to State_Unconnected. if (pDomain->State != Dom_PendingCPResponse) { TraceOutIca(pDomain->hIca, "ConnectProvderResp(): in wrong state, " "ignoring call"); MCSErr = MCS_NO_ERROR; goto PostLockDomain; } // TODO FUTURE: We are assuming an hConn in the Domain which is a hack // only for a single-connection system. MCSErr = SendConnectProviderResponse(pDomain, ((Connection *)pDomain->hConn)->hConnKernel, Result, pUserData, UserDataLen); if (MCSErr == MCS_NO_ERROR) { if (Result == RESULT_SUCCESSFUL) pDomain->State = Dom_Connected; else pDomain->State = Dom_Rejected; } PostLockDomain: // Release the domain. LeaveCriticalSection(&pDomain->csLock); return MCSErr; } // Utility function called internally as well as part of // MCSConnectProviderResponse(). Domain must be locked before calling. MCSError SendConnectProviderResponse( Domain *pDomain, ConnectionHandle hConnKernel, MCSResult Result, BYTE *pUserData, unsigned UserDataLen) { NTSTATUS Status; ConnectProviderResponseIoctl CPrs; // Transfer params. CPrs.Header.hUser = NULL; // Special value meaning node controller. CPrs.Header.Type = MCS_CONNECT_PROVIDER_RESPONSE; CPrs.hConn = hConnKernel; CPrs.Result = Result; CPrs.UserDataLength = UserDataLen; // Points to user data. if (UserDataLen) CPrs.pUserData = pUserData; // Call kernel mode. No callback is expected. Status = IcaStackIoControl(pDomain->hIcaStack, IOCTL_T120_REQUEST, &CPrs, sizeof(ConnectProviderResponseIoctl), NULL, 0, NULL); if (!NT_SUCCESS(Status)) { ErrOutIca(pDomain->hIca, "ConnectProvResponse(): Stack ioctl failed"); return MCS_ALLOCATION_FAILURE; } TraceOutIca(pDomain->hIca, "Sent MCS_CONNECT_PROVIDER_RESPONSE"); return MCS_NO_ERROR; } /* * Terminates an existing MCS connection or aborts a creation in-progress. */ MCSError APIENTRY MCSDisconnectProviderRequest( HANDLE hIca, ConnectionHandle hConn, MCSReason Reason) { Domain *pDomain; NTSTATUS Status; MCSError MCSErr; Connection *pConn; DisconnectProviderRequestIoctl DPrq; CheckInitialized("DisconnectProviderReq()"); pConn = (Connection *)hConn; pDomain = pConn->pDomain; // Lock the domain. EnterCriticalSection(&pDomain->csLock); // Transfer params. DPrq.Header.hUser = NULL; // Special meaning node controller. DPrq.Header.Type = MCS_DISCONNECT_PROVIDER_REQUEST; DPrq.hConn = pConn->hConnKernel; DPrq.Reason = Reason; // TODO FUTURE: Assumes only one connection. pDomain->hConn = NULL; pDomain->State = Dom_Unconnected; // TODO FUTURE: We do not deallocate the Connection. Unnecessary for now // since we are using a static Connection in the Domain, but will be an // issue when mallocing the Connection objects. #ifdef MCS_Future EnterCriticalSection(&g_csGlobalListLock); SListRemove(&g_ConnList, (unsigned)pConn, NULL); LeaveCriticalSection(&g_csGlobalListLock); Free(pConn); #endif // Call kernel mode. No callback is expected. Status = IcaStackIoControl(pDomain->hIcaStack, IOCTL_T120_REQUEST, &DPrq, sizeof(DPrq), NULL, 0, NULL); if (!NT_SUCCESS(Status)) { ErrOutIca(hIca, "DisconnectProvRequest(): Stack ioctl failed"); MCSErr = MCS_ALLOCATION_FAILURE; goto PostLockDomain; } TraceOutIca(hIca, "Sent MCS_DISCONNECT_PROVIDER_REQUEST"); MCSErr = MCS_NO_ERROR; PostLockDomain: // Release the domain. LeaveCriticalSection(&pDomain->csLock); return MCSErr; } /* * * CODE PAST THIS POINT IS FOR REFERENCE PURPOSES ONLY. IT IS NOT PART OF * THE PRODUCT. * */ #if MCS_Future /* * Connects a local domain to a remote domain, merging the two domains. */ MCSError APIENTRY MCSConnectProviderRequest( DomainSelector CallingDom, unsigned CallingDomLen, DomainSelector CalledDom, unsigned CalledDomLen, BOOL bUpwardConnection, PDomainParameters pDomParams, BYTE *UserData, unsigned UserDataLen, DomainHandle *phDomain, ConnectionHandle *phConn) { CheckInitialized("ConnectProvReq()"); // Create new domain or look up already-created one. // call to kernel mode to set up the connection. ErrOut("ConnectProviderRequest: Not implemented"); return MCS_COMMAND_NOT_SUPPORTED; } #endif // MCS_Future #if MCS_Future /* * Attaches a user SAP to an existing domain. */ MCSError APIENTRY MCSAttachUserRequest( DomainHandle hDomain, MCSUserCallback UserCallback, MCSSendDataCallback SDCallback, void *UserDefined, UserHandle *phUser, unsigned *pMaxSendSize, BOOLEAN *pbCompleted) { Domain *pDomain; unsigned i, Err, OutBufSize, BytesReturned; NTSTATUS ntStatus; UserInformation *pUserInfo; AttachUserReturnIoctl AUrt; AttachUserRequestIoctl AUrq; CheckInitialized("AttachUserReq()"); pDomain = (Domain *)hDomain; *pbCompleted = FALSE; // We must by this time have an hICA assigned to the domain. ASSERT(pDomain->hIca != NULL); // Make a new user-mode user information struct. *phUser = pUserInfo = Malloc(sizeof(UserInformation)); if (pUserInfo == NULL) { ErrOutIca(pDomain->hIca, "AttachUserReq(): Alloc failure for " "user info"); return MCS_ALLOCATION_FAILURE; } // Fill in the struct pUserInfo->Callback = UserCallback; pUserInfo->SDCallback = SDCallback; pUserInfo->UserDefined = UserDefined; pUserInfo->State = User_AttachConfirmPending; pUserInfo->hUserKernel = NULL; // Don't yet have kernel hUser assignment. pUserInfo->UserID = 0; // Don't yet have kernel UserID assignment. pUserInfo->pDomain = pDomain; SListInit(&pUserInfo->JoinedChannelList, DefaultNumChannels); // Transfer params for kernel-mode call. AUrq.Header.hUser = NULL; AUrq.Header.Type = MCS_ATTACH_USER_REQUEST; AUrq.UserDefined = UserDefined; // Add the user to the global UserInfo list. EnterCriticalSection(&g_csGlobalListLock); if (!SListAppend(&g_UserList, (unsigned)pUserInfo, pDomain)) { ErrOutIca(pDomain->hIca, "AttachUserReq(): Could not add user to " "user list"); AUrt.MCSErr = MCS_ALLOCATION_FAILURE; LeaveCriticalSection(&g_csGlobalListLock); goto PostAlloc; } LeaveCriticalSection(&g_csGlobalListLock); // Issue the T120 request to kernel mode. ntStatus = IcaStackIoControl(pDomain->hIcaStack, IOCTL_T120_REQUEST, &AUrq, sizeof(AUrq), &AUrt, sizeof(AUrt), &BytesReturned); if (!NT_SUCCESS(ntStatus)) { ErrOutIca(pDomain->hIca, "AttachUserRequest(): T120 request failed"); AUrt.MCSErr = MCS_ALLOCATION_FAILURE; goto PostAddList; } if (AUrt.MCSErr != MCS_NO_ERROR) goto PostAddList; pUserInfo->hUserKernel = AUrt.hUser; *phUser = pUserInfo; *pMaxSendSize = AUrt.MaxSendSize; pUserInfo->MaxSendSize = AUrt.MaxSendSize; if (AUrt.bCompleted) { pUserInfo->UserID = AUrt.UserID; *pbCompleted = TRUE; } return MCS_NO_ERROR; // Error handling. PostAddList: EnterCriticalSection(&g_csGlobalListLock); SListRemove(&g_UserList, (unsigned)pUserInfo, NULL); LeaveCriticalSection(&g_csGlobalListLock); PostAlloc: SListDestroy(&pUserInfo->JoinedChannelList); Free(pUserInfo); return AUrt.MCSErr; } #endif // MCS_Future #if MCS_Future UserID MCSGetUserIDFromHandle(UserHandle hUser) { return ((UserInformation *)hUser)->UserID; } #endif // MCS_Future #if MCS_Future /* * Detach a previously created user attachment from a domain. */ MCSError APIENTRY MCSDetachUserRequest(UserHandle hUser) { Domain *pDomain; NTSTATUS Status; unsigned BytesReturned; MCSError MCSErr; MCSChannel *pMCSChannel; UserInformation *pUserInfo; DetachUserRequestIoctl DUrq; CheckInitialized("DetachUserReq()"); pUserInfo = (UserInformation *)hUser; // Fill in detach-user request for sending to kernel mode. DUrq.Header.Type = MCS_DETACH_USER_REQUEST; DUrq.Header.hUser = pUserInfo->hUserKernel; // Call down to kernel mode, using the user attachment channel. // Issue the T120 request to kernel mode. Status = IcaStackIoControl(pUserInfo->pDomain->hIcaStack, IOCTL_T120_REQUEST, &DUrq, sizeof(DUrq), &MCSErr, sizeof(MCSErr), &BytesReturned); if (!NT_SUCCESS(Status)) { ErrOutIca(pDomain->hIca, "DetachUserRequest(): T120 request failed"); return MCS_USER_NOT_ATTACHED; } if (MCSErr != MCS_NO_ERROR) return MCSErr; // Remove the hUser from the User list. EnterCriticalSection(&g_csGlobalListLock); SListRemove(&g_UserList, (unsigned)hUser, &pDomain); LeaveCriticalSection(&g_csGlobalListLock); if (pDomain == NULL) return MCS_NO_SUCH_USER; // Clean up contents of pUserInfo then free. DestroyUserInfo(pUserInfo); Free(pUserInfo); return MCS_NO_ERROR; } #endif // MCS_Future #if MCS_Future /* * Join a data channel. Once joined, an attachment can receive data sent on * that channel. Static channels are in range 1..1000 and can be joined by * any user. Dynamic channels are in range 1001..65535 and cannot be joined * unless they are convened by a user and the convenor allows this user * to be admitted, or the dynamic channel is a previously assigned channel * requested by a user attachment calling JoinRequest() with a channel ID * of 0. */ MCSError APIENTRY MCSChannelJoinRequest( UserHandle hUser, ChannelID ChannelID, ChannelHandle *phChannel, BOOLEAN *pbCompleted) { unsigned Err, BytesReturned; NTSTATUS Status; MCSChannel *pMCSChannel; UserInformation *pUserInfo; ChannelJoinReturnIoctl CJrt; ChannelJoinRequestIoctl CJrq; CheckInitialized("ChannelJoinReq()"); pUserInfo = (UserInformation *)hUser; *pbCompleted = FALSE; // Alloc a new user-mode channel struct. pMCSChannel = Malloc(sizeof(MCSChannel)); if (pMCSChannel == NULL) { ErrOutIca(pUserInfo->pDomain->hIca, "ChannelJoinReq(): " "Could not alloc a user-mode channel"); return MCS_ALLOCATION_FAILURE; } pMCSChannel->hChannelKernel = NULL; pMCSChannel->ChannelID = 0; // Add channel to user list of joined channels. if (!SListAppend(&pUserInfo->JoinedChannelList, (unsigned)pMCSChannel, pMCSChannel)) { ErrOutIca(pUserInfo->pDomain->hIca, "ChannelJoinReq(): " "Could not add channel to user mode user list"); CJrt.MCSErr = MCS_ALLOCATION_FAILURE; goto PostAlloc; } // Transfer params for kernel mode call. CJrq.Header.hUser = pUserInfo->hUserKernel; CJrq.Header.Type = MCS_CHANNEL_JOIN_REQUEST; CJrq.ChannelID = ChannelID; // Issue the T120 request to kernel mode Status = IcaStackIoControl(pUserInfo->pDomain->hIcaStack, IOCTL_T120_REQUEST, &CJrq, sizeof(CJrq), &CJrt, sizeof(CJrt), &BytesReturned); if (!NT_SUCCESS(Status)) { ErrOutIca1(pUserInfo->pDomain->hIca, "ChannelJoinReq(): " "T120 request failed (%ld)", Status); CJrt.MCSErr = MCS_ALLOCATION_FAILURE; goto PostAddList; } if (CJrt.MCSErr != MCS_NO_ERROR) goto PostAddList; if (CJrt.bCompleted) { // Fill in the user-mode channel info. pMCSChannel->hChannelKernel = CJrt.hChannel; pMCSChannel->ChannelID = CJrt.ChannelID; *phChannel = pMCSChannel; *pbCompleted = TRUE; } return MCS_NO_ERROR; // Error handling. PostAddList: SListRemove(&pUserInfo->JoinedChannelList, (unsigned)pMCSChannel, NULL); PostAlloc: Free(pMCSChannel); return CJrt.MCSErr; } #endif // MCS_Future #if MCS_Future /* * Leave a data channel previously joined. Once unjoined, the user attachment * does not receive data from that channel. */ MCSError APIENTRY MCSChannelLeaveRequest( UserHandle hUser, ChannelHandle hChannel) { unsigned BytesReturned; MCSError MCSErr; NTSTATUS Status; MCSChannel *pMCSChannel; UserInformation *pUserInfo; ChannelLeaveRequestIoctl CLrq; CheckInitialized("ChannelLeaveReq()"); pUserInfo = (UserInformation *)hUser; #if DBG // Check that the indicated channel is actually present. if (!SListGetByKey(&pUserInfo->JoinedChannelList, (unsigned)hChannel, &pMCSChannel)) { ErrOutIca(pUserInfo->pDomain->hIca, "ChannelLeaveReq(): " "Given hChannel not present!"); return MCS_NO_SUCH_CHANNEL; } #endif pMCSChannel = (MCSChannel *)hChannel; // Transfer params. CLrq.Header.Type = MCS_CHANNEL_LEAVE_REQUEST; CLrq.Header.hUser = pUserInfo->hUserKernel; CLrq.hChannel = pMCSChannel->hChannelKernel; // Issue the T120 request to kernel mode Status = IcaStackIoControl(pUserInfo->pDomain->hIcaStack, IOCTL_T120_REQUEST, &CLrq, sizeof(CLrq), &MCSErr, sizeof(MCSErr), &BytesReturned); if (!NT_SUCCESS(Status)) { ErrOutIca1(pUserInfo->pDomain->hIca, "ChannelLeaveReq(): " "T120 request failed (%ld)", Status); return MCS_ALLOCATION_FAILURE; } if (MCSErr != MCS_NO_ERROR) return MCSErr; // Remove the user-mode channel from the user list and destroy. SListRemove(&pUserInfo->JoinedChannelList, (unsigned)pMCSChannel, NULL); Free(pMCSChannel); return MCS_NO_ERROR; } #endif // MCS_Future /* * Allocates a SendData buffer. This must be done by MCS to ensure high * performance operation without memcpy()'s. */ #if MCS_Future MCSError APIENTRY MCSGetBufferRequest( UserHandle hUser, unsigned Size, void **ppBuffer) { BYTE *pBuf; CheckInitialized("GetBufferReq()"); // Leave sizeof(MCSSendDataRequestIoctl) in front of the block to bypass // memcpy()'s during SendData. pBuf = Malloc(sizeof(SendDataRequestIoctl) + Size); if (pBuf == NULL) { ErrOut("GetBufferReq(): Malloc failed"); return MCS_ALLOCATION_FAILURE; } *ppBuffer = pBuf + sizeof(SendDataRequestIoctl); return MCS_NO_ERROR; } #endif // MCS_Future /* * Free a buffer allocated using GetBufferRequest(). This should only need to * be done in the case where a buffer was allocated but never used. * SendDataReq() automatically frees the buffer used before it returns. */ #if MCS_Future MCSError APIENTRY MCSFreeBufferRequest(UserHandle hUser, void *pBuffer) { CheckInitialized("FreeBufReq()"); ASSERT(pBuffer != NULL); // Reverse the process done in GetBufferReq() above. Free((BYTE *)pBuffer - sizeof(SendDataRequestIoctl)); return MCS_NO_ERROR; } #endif // MCS_Future #if MCS_Future /* * Sends data on a channel. The channel need not have been joined before * sending. The pData[] buffers must have been generated from successful * calls to MCSGetBufferRequest(). After this call the pData[] are invalid; * MCS will free them, and assign NULL to the contents in pData[]. */ MCSError APIENTRY MCSSendDataRequest( UserHandle hUser, DataRequestType RequestType, ChannelHandle hChannel, ChannelID ChannelID, MCSPriority Priority, Segmentation Segmentation, BYTE *pData, unsigned DataLength) { unsigned BytesReturned; NTSTATUS Status; MCSError MCSErr; MCSChannel *pMCSChannel; UserInformation *pUserInfo; SendDataRequestIoctl *pSDrq; CheckInitialized("SendDataReq()"); ASSERT(pData != NULL); pUserInfo = (UserInformation *)hUser; #if DBG // Check against maximum send size allowed. if (DataLength > pUserInfo->MaxSendSize) { ErrOutIca(pUserInfo->pDomain->hIca, "SendDataReq(): Send size " "exceeds negotiated domain maximum"); return MCS_SEND_SIZE_TOO_LARGE; } #endif // Inbound buffer was allocated by GetBufferReq() with // sizeof(MCSSendDataRequestIoctl) bytes at the beginning. pSDrq = (SendDataRequestIoctl *)(pData - sizeof(SendDataRequestIoctl)); if (hChannel == NULL) { // The user requested a send to a channel that it has not joined. ASSERT(ChannelID >= 1 && ChannelID <= 65535); // Forward the channel number to kernel mode for handling. pSDrq->hChannel = NULL; pSDrq->ChannelID = ChannelID; } else { #if DBG // Check that the indicated channel is actually present. if (!SListGetByKey(&pUserInfo->JoinedChannelList, (unsigned)hChannel, &pMCSChannel)) { ErrOutIca(pUserInfo->pDomain->hIca, "SendDataReq(): " "Given hChannel not joined by user!"); return MCS_NO_SUCH_CHANNEL; } #endif pMCSChannel = (MCSChannel *)hChannel; pSDrq->hChannel = pMCSChannel->hChannelKernel; pSDrq->ChannelID = 0; } // Fill out data for sending to kernel mode. pSDrq->Header.Type = (RequestType == NORMAL_SEND_DATA ? MCS_SEND_DATA_REQUEST : MCS_UNIFORM_SEND_DATA_REQUEST); pSDrq->Header.hUser = pUserInfo->hUserKernel; pSDrq->RequestType = RequestType; pSDrq->Priority = Priority; pSDrq->Segmentation = Segmentation; pSDrq->DataLength = DataLength; // Issue the T120 request to kernel mode. Status = IcaStackIoControl(pUserInfo->pDomain->hIcaStack, IOCTL_T120_REQUEST, (BYTE *)pSDrq, sizeof(SendDataRequestIoctl) + DataLength, &MCSErr, sizeof(MCSErr), &BytesReturned); if (!NT_SUCCESS(Status)) { ErrOutIca1(pUserInfo->pDomain->hIca, "MCSSendDataReq(): " "T120 request failed (%ld)", Status); return MCS_ALLOCATION_FAILURE; } if (MCSErr != MCS_NO_ERROR) return MCSErr; // Buffer sent to kernel mode is copied as necessary. We free // the memory after we are done. MCSFreeBufferRequest(hUser, pData); return MCS_NO_ERROR; } #endif // MCS_Future