#include "precomp.h" DEBUG_FILEZONE(ZONE_T120_MCSNC); /* * user.cpp * * Copyright (c) 1993 - 1996 by DataBeam Corporation, Lexington, KY * * Abstract: * This is the implementation file for the User class. Objects of this * class represent the attachment between a user application and an MCS * domain. It "talks" to the application through an application interface * object, which is identified to it as a constructor parameter. Since * this class inherits from CommandTarget, it can talk to the domain * object using the MCS command language defined therein. The domain * object to which it must attach is another constructor parameter. * * When one of these objects is first created, it must register its * presence with both the application interface object above it, and the * domain object below it. To register with the application interface * object it sends it a registration message through the owner callback. * To register with the domain object, it issues an attach user request * on behalf of the application that created this attachment. * * This module contains code to perform three different tasks: accept * T.122 requests and responses from the user application and forward them * to the domain as MCS commands; accept MCS commands from the domain and * forward them to the application as T.122 primitives; and buffer those * indications and confirms until the controller allocates a time slice in * which to send them. * * T.122 requests and responses come from the application interface as * public member functions whose name is prefixed with "MCS" (for example, * "MCSChannelJoinRequest"). After validation, the equivalent MCS command * (whose name does NOT begin with "MCS") is sent to the domain object. * * MCS commands come from the domain object as public member functions that * are inherited from CommandTarget and overridden by this class. The * names of these functions are NOT prefixed with "MCS". Any MCS commands * that do not map to (or can be converted to) T.122 primitives are simply * not overridden. The default behavior of these functions ,as defined in * the CommandTarget class, is to return an error. * * Indication and confirm primitives are buffered by objects of this class * before being sent to the application. This allows the controller more * flexibility in the timing of events in the system. This is done by * allocating a structure to hold the information associated with the * primitive, and then putting a pointer to that structure into a linked * list. When the command comes to flush this message queue, the * primitives are sent to the application interface object through the * owner callback, and the structures are released. * * Private Instance Variables: * m_pDomain * This is a pointer to the domain, to which this user is (or wishes * to be) attached. * User_ID * This is the user ID assigned to this user attachment. This is * guaranteed to be unique ONLY within this domain. Note that a value * of 0 (zero) indicates that this user is not yet attached to the * domain. This is set by a successful attach user confirm, and the * user application should wait until that confirm is received before * trying to invoke any other MCS services. * Merge_In_Progress * This is a boolean flag that indicates whether or not the attached * Domain object is in the merge state. When in the merge state it * is invalid to send it any MCS commands. * Deletion_Pending * This is a boolean flag that indicates whether or not an internally * requested deletion is pending. This is used by the destructor to * determine if a deletion was requested by the object itself, or is * simply an asynchronous event. * Maximum_User_Data_Length * This is the maximum amount of user data that can be placed into * a single MCS PDU. This number is derived from the arbitrated * maximum MCS PDU size (minus enough space for overhead bytes). * * Private Member Functions: * ValidateUserRequest * This member function is called each time the user application makes * a request. It checks the current state of the system to see if * conditions are such that the request can be processed at the * current time. * PurgeMessageQueue * This member function walks through the current message queue, * freeing all resources held therein. * * Caveats: * None. * * Author: * James P. Galvin, Jr. */ #include "omcscode.h" #define USER_MSG_BASE WM_APP /* * bugbug: * The following constant is only used to cover a bug in NM 2.0 for backward * compatibility purposes. NM 2.0 can not accept MCS data PDUs with more than * 4096 bytes of user data. Because of the Max MCS PDU size we negotiate (4128), * even in NM 2.0, we should have been able to send 4120 bytes. But NM 2.0 chokes * in this case. * The constant should eliminated after NM 3.0. */ #define BER_PROTOCOL_EXTRA_OVERHEAD 24 /* * This is a global variable that has a pointer to the one MCS coder that * is instantiated by the MCS Controller. Most objects know in advance * whether they need to use the MCS or the GCC coder, so, they do not need * this pointer in their constructors. */ extern CMCSCoder *g_MCSCoder; // The external MCS Controller object extern PController g_pMCSController; // The global MCS Critical Section extern CRITICAL_SECTION g_MCS_Critical_Section; // The DLL's HINSTANCE extern HINSTANCE g_hDllInst; // Class name for windows used by MCS attachments. static char s_WindowClassName[CLASS_NAME_LENGTH]; // Initialization of the class's static variables. CTimerUserList2* User::s_pTimerUserList2 = NULL; HINSTANCE User::s_hInstance = NULL; /* * BOOL InitializeClass () * * Public, static * * Functional Description * * This function initializes the class's static variables. It is * called during the MCS Controller's construction. */ BOOL User::InitializeClass (void) { BOOL bReturnValue; WNDCLASS window_class; DBG_SAVE_FILE_LINE s_pTimerUserList2 = new CTimerUserList2(); bReturnValue = (s_pTimerUserList2 != NULL); if (bReturnValue) { // Construct the window class name wsprintf (s_WindowClassName, "MCS Window %x %x", GetCurrentProcessId(), GetTickCount()); /* * Fill out a window class structure in preparation for registering * the window with Windows. Note that since this is a hidden * window, most of the fields can be set to NULL or 0. */ ZeroMemory (&window_class, sizeof(WNDCLASS)); window_class.lpfnWndProc = (WNDPROC) UserWindowProc; window_class.hInstance = s_hInstance = g_hDllInst; window_class.lpszClassName = s_WindowClassName; /* * Register the class with Windows so that we can create a window * for use by this portal. */ if (RegisterClass (&window_class) == 0) { ERROR_OUT (("InitWindowPortals: window class registration failed. Error: %d", GetLastError())); bReturnValue = FALSE; } } else { ERROR_OUT(("User::InitializeClass: Failed to allocate timer dictionary.")); } return bReturnValue; } /* * void CleanupClass () * * Public, static * * Functional Description * * This function cleans up the class's static variables. It is * called when the MCS Controller is deleted. */ void User::CleanupClass (void) { delete s_pTimerUserList2; UnregisterClass (s_WindowClassName, s_hInstance); } /* * MCSError MCS_AttachRequest () * * Public * * Functional Description: * This API entry point is used to attach to an existing domain. Once * attached, a user application can utilize the services of MCS. When * a user application is through with MCS, it should detach from the domain * by calling MCSDetachUserRequest (see below). */ MCSError WINAPI MCS_AttachRequest (IMCSSap ** ppIMCSSap, DomainSelector domain_selector, UINT, // domain_selector_length MCSCallBack user_callback, PVoid user_defined, UINT flags) { MCSError return_value = MCS_NO_ERROR; AttachRequestInfo attach_request_info; PUser pUser; TRACE_OUT(("AttachUserRequest: beginning attachment process")); ASSERT (user_callback); // Initialize the interface ptr. *ppIMCSSap = NULL; /* * Pack the attach parameters into a structure since they will not fit * into the one parameter we have available in the owner callback. */ attach_request_info.domain_selector = (GCCConfID *) domain_selector; attach_request_info.ppuser = &pUser; /* * Enter the critical section which protects global data. */ EnterCriticalSection (& g_MCS_Critical_Section); if (g_pMCSController != NULL) { /* * Send an attach user request message to the controller through its * owner callback function. */ return_value = g_pMCSController->HandleAppletAttachUserRequest(&attach_request_info); if (return_value == (ULong) MCS_NO_ERROR) { // Set the returned interface ptr *ppIMCSSap = (IMCSSap *) pUser; /* * If the request was accepted, then register * the new user attachment. Note that there * is still no user ID associated with this * attachment, since the attach user confirm * has not yet been received. */ pUser->RegisterUserAttachment (user_callback, user_defined, flags); } } else { ERROR_OUT(("MCS_AttachRequest: MCS Provider is not initialized.")); return_value = MCS_NOT_INITIALIZED; } /* * Leave the critical section before returning. */ LeaveCriticalSection (& g_MCS_Critical_Section); return (return_value); } /* * User () * * Public * * Functional Description: * This is the constructor for the user class. It initializes all instance * variables (mostly with passed in information). It then registers its * presence with the application interface object, so that user requests * and responses will get here okay. Finally, it issues an attach user * request to the domain to start the attachment process. */ User::User (PDomain pDomain, PMCSError pError) : CAttachment(USER_ATTACHMENT), m_pDomain(pDomain), Deletion_Pending (FALSE), User_ID (0), Merge_In_Progress (FALSE), m_DataPktQueue(), m_PostMsgPendingQueue(), m_DataIndMemoryBuf2(), CRefCount(MAKE_STAMP_ID('U','s','e','r')) { DomainParameters domain_parameters; g_pMCSController->AddRef(); /* * We now need to create the window that the MCS Provider * will use to deliver MCS messages to the attachment. * These messages are indications and confirms. */ m_hWnd = CreateWindow (s_WindowClassName, NULL, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, g_hDllInst, NULL); if (m_hWnd != NULL) { /* * Call the domain object to find out the current domain parameters. * From this, set the maximum user data length appropriately. */ m_pDomain->GetDomainParameters (&domain_parameters, NULL, NULL); Maximum_User_Data_Length = domain_parameters.max_mcspdu_size - (MAXIMUM_PROTOCOL_OVERHEAD_MCS + BER_PROTOCOL_EXTRA_OVERHEAD); TRACE_OUT (("User::User: " "maximum user data length = %ld", Maximum_User_Data_Length)); /* * Use the specified domain parameters to set the type of encoding rules * to be used. */ ASSERT (domain_parameters.protocol_version == PROTOCOL_VERSION_PACKED); /* * Send an attach user request to the specified domain. */ m_pDomain->AttachUserRequest (this); *pError = MCS_NO_ERROR; } else { *pError = MCS_ALLOCATION_FAILURE; } } /* * ~User () * * Public * * Functional Description: * */ User::~User () { PDataPacket packet; while (NULL != (packet = m_PostMsgPendingQueue.Get())) { packet->Unlock(); } if (m_hWnd) { // Destroy the window; we do not need it anymore DestroyWindow (m_hWnd); } g_pMCSController->Release(); } /* * MCSError GetBuffer () * * Public * * Functional Description: * This function allocates an MCS buffer for a user attachment. * Because this function allocates a buffer for the user and a Memory * object that immediately precedes the buffer, after the user fills in * the buffer with data and gives it to MCS to send, it needs to specify the * right flags in the SendData request API. */ MCSError User::GetBuffer (UINT size, PVoid *pbuffer) { MCSError return_value; PMemory memory; EnterCriticalSection (& g_MCS_Critical_Section); /* * This request may be a retry from a previous request which * returned MCS_TRANSMIT_BUFFER_FULL. If so, delete the associated * buffer retry info structure since resource levels will be * checked in this function anyway. */ if (m_BufferRetryInfo != NULL) { KillTimer (NULL, m_BufferRetryInfo->timer_id); s_pTimerUserList2->Remove(m_BufferRetryInfo->timer_id); delete m_BufferRetryInfo; m_BufferRetryInfo = NULL; } // Allocate the memory DBG_SAVE_FILE_LINE memory = AllocateMemory (NULL, size + MAXIMUM_PROTOCOL_OVERHEAD, SEND_PRIORITY); LeaveCriticalSection (& g_MCS_Critical_Section); if (NULL != memory) { // the allocation succeeded. ASSERT ((PUChar) memory + sizeof(Memory) == memory->GetPointer()); *pbuffer = (PVoid) (memory->GetPointer() + MAXIMUM_PROTOCOL_OVERHEAD); return_value = MCS_NO_ERROR; } else { // the allocation failed. TRACE_OUT (("User::GetBuffer: Failed to allocate data buffer.")); CreateRetryTimer (size + MAXIMUM_PROTOCOL_OVERHEAD); return_value = MCS_TRANSMIT_BUFFER_FULL; } return (return_value); } /* * MCSError FreeBuffer () * * Public * * Functional Description: */ void User::FreeBuffer (PVoid buffer_ptr) { PMemory memory; ASSERT (m_fFreeDataIndBuffer == FALSE); /* * Attempt to find the buffer in the m_DataIndDictionary dictionary. * This is where irregular data indications go. */ if (NULL == (memory = m_DataIndMemoryBuf2.Remove(buffer_ptr))) { memory = GetMemoryObject(buffer_ptr); } // Free the memory. EnterCriticalSection (& g_MCS_Critical_Section); FreeMemory (memory); LeaveCriticalSection (& g_MCS_Critical_Section); } /* * Void CreateRetryTimer * * Private * * Functional Description * This functions creates a timer in response to a failure to * allocate memory for the send data that the user is trying to * send. The timer will fire off periodically so that this code * will remember to check the memory levels and provide an * MCS_TRANSMIT_BUFFER_AVAILABLE_INDICATION to the user. * * Return Value: * None. * * Side effects: * The timer is created. */ Void User::CreateRetryTimer (ULong size) { UINT_PTR timer_id; timer_id = SetTimer (NULL, 0, TIMER_PROCEDURE_TIMEOUT, (TIMERPROC) TimerProc); if (timer_id != 0) { DBG_SAVE_FILE_LINE m_BufferRetryInfo = new BufferRetryInfo; if (m_BufferRetryInfo != NULL) { m_BufferRetryInfo->user_data_length = size; m_BufferRetryInfo->timer_id = timer_id; s_pTimerUserList2->Append(timer_id, this); } else { ERROR_OUT (("User::CreateRetryTimer: Failed to allocate BufferRetryInfo struct.")); KillTimer (NULL, timer_id); } } else { /* * This is a bad error, The notification to the user when buffers * are available will be lost. Hopefully, the user will try again * later. */ WARNING_OUT(("User::CreateRetryTimer: Could not SetTimer.")); } } /* * MCSError ReleaseInterface () * * Public * * Functional Description: * This function is called when a user wishes to detach from the domain. * It kicks off the process of detaching, and seeing that this object * is properly deleted. */ MCSError User::ReleaseInterface () { CUidList deletion_list; MCSError return_value; EnterCriticalSection (& g_MCS_Critical_Section); /* * Check to see if there is a merge operation in progress before proceeding * with the request. */ if (Merge_In_Progress == FALSE) { /* * If deletion is not already pending, then it is necessary for us * to tell the domain that we are leaving. */ if (Deletion_Pending == FALSE) { /* * If we are already attached, user ID will not be 0, and we * should send a detach user request. If user ID IS 0, then we * are not yet attached to the domain, so a disconnect provider * ultimatum is used instead. */ if (User_ID != 0) { deletion_list.Append(User_ID); m_pDomain->DetachUserRequest (this, REASON_USER_REQUESTED, &deletion_list); User_ID = 0; } else m_pDomain->DisconnectProviderUltimatum (this, REASON_USER_REQUESTED); /* * Set the flag that will cause the object to be deleted during * the next call to FlushMessageQueue. */ Deletion_Pending = TRUE; } /* * Empty out the message queue (the application should receive no * messages once the attachment has been deleted). */ PurgeMessageQueue (); // Cleanup timers and retry structures; if (m_BufferRetryInfo != NULL) { s_pTimerUserList2->Remove(m_BufferRetryInfo->timer_id); KillTimer (NULL, m_BufferRetryInfo->timer_id); delete m_BufferRetryInfo; m_BufferRetryInfo = NULL; } return_value = MCS_NO_ERROR; // Release can release the MCS Controller, so, we have to exit the CS now. LeaveCriticalSection (& g_MCS_Critical_Section); /* * Release this object. Note that the object may be deleted * here, so, we should not access any member variables after this * call. */ Release(); } else { LeaveCriticalSection (& g_MCS_Critical_Section); /* * This operation could not be processed at this time due to a merge * operation in progress at the local provider. */ WARNING_OUT (("User::ReleaseInterface: " "merge in progress")); return_value = MCS_DOMAIN_MERGING; } return (return_value); } #define CHANNEL_JOIN 0 #define CHANNEL_LEAVE 1 #define CHANNEL_CONVENE 2 #define CHANNEL_DISBAND 3 /* * MCSError ChannelJLCD () * * Public * * Functional Description: * This function is called when the user application wishes to join/leave/convene/disband * a channel. If the user is attached to the domain, the request will be * repackaged as an MCS command and sent to the domain object. */ MCSError User::ChannelJLCD (int type, ChannelID channel_id) { MCSError return_value; EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { switch (type) { case CHANNEL_JOIN: m_pDomain->ChannelJoinRequest (this, User_ID, channel_id); break; case CHANNEL_LEAVE: { CChannelIDList deletion_list; deletion_list.Append(channel_id); m_pDomain->ChannelLeaveRequest (this, &deletion_list); } break; case CHANNEL_CONVENE: m_pDomain->ChannelConveneRequest (this, User_ID); break; case CHANNEL_DISBAND: m_pDomain->ChannelDisbandRequest (this, User_ID, channel_id); break; } } LeaveCriticalSection (& g_MCS_Critical_Section); return (return_value); } /* * MCSError ChannelJoin () * * Public * * Functional Description: * This function is called when the user application wishes to join a * channel. If the user is attached to the domain, the request will be * repackaged as an MCS command and sent to the domain object. */ MCSError User::ChannelJoin (ChannelID channel_id) { return (ChannelJLCD (CHANNEL_JOIN, channel_id)); } /* * MCSError ChannelLeave () * * Public * * Functional Description: * This function is called when the user application wishes to leave a * channel. If the user is attached to the domain, the request will be * repackaged as an MCS command and sent to the domain object. */ MCSError User::ChannelLeave (ChannelID channel_id) { return (ChannelJLCD (CHANNEL_LEAVE, channel_id)); } /* * MCSError ChannelConvene () * * Public * * Functional Description: * This function is called when the user application wishes to convene a * private channel. If the user is attached to the domain, the request * will be repackaged as an MCS command and sent to the domain object. */ MCSError User::ChannelConvene () { return (ChannelJLCD (CHANNEL_CONVENE, 0)); } /* * MCSError ChannelDisband () * * Public * * Functional Description: * This function is called when the user application wishes to disband a * private channel. If the user is attached to the domain, the request * will be repackaged as an MCS command and sent to the domain object. */ MCSError User::ChannelDisband ( ChannelID channel_id) { return (ChannelJLCD (CHANNEL_DISBAND, channel_id)); } /* * MCSError ChannelAdmit () * * Public * * Functional Description: * This function is called when the user application wishes to admit more * users to a private channel for which it is manager. If the user is * attached to the domain, the request will be repackaged as an MCS command * and sent to the domain object. */ MCSError User::ChannelAdmit ( ChannelID channel_id, PUserID user_id_list, UINT user_id_count) { UINT count; CUidList local_user_id_list; MCSError return_value = MCS_NO_ERROR; /* * Verify that the value of each user ID included in the user ID list is * a valid value. Otherwise, fail the call. */ for (count = 0; count < user_id_count; count++) { if (user_id_list[count] > 1000) { // add the UserID into the singly-linked list. local_user_id_list.Append(user_id_list[count]); } else { return_value = MCS_INVALID_PARAMETER; break; } } if (return_value == MCS_NO_ERROR) { EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { m_pDomain->ChannelAdmitRequest (this, User_ID, channel_id, &local_user_id_list); } LeaveCriticalSection (& g_MCS_Critical_Section); } return (return_value); } #ifdef USE_CHANNEL_EXPEL_REQUEST /* * MCSError MCSChannelExpelRequest () * * Public * * Functional Description: * This function is called when the user application wishes to expel * users from a private channel for which it is manager. If the user is * attached to the domain, the request will be repackaged as an MCS command * and sent to the domain object. */ MCSError User::ChannelExpel ( ChannelID channel_id, PMemory memory, UINT user_id_count) { UINT count; CUidList local_user_id_list; MCSError return_value; PUserID user_id_list = (PUserID) memory->GetPointer(); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { /* * Repack the user ID list into an S-list before sending it on. */ for (count=0; count < user_id_count; count++) local_user_id_list.append ((DWORD) user_id_list[count]); m_pDomain->ChannelExpelRequest (this, User_ID, channel_id, &local_user_id_list); } if (return_value != MCS_DOMAIN_MERGING) FreeMemory (memory); return (return_value); } #endif // USE_CHANNEL_EXPEL_REQUEST /* * MCSError SendData () * * Public * * Functional Description: * This function is called when the user application wishes to send data * on a channel. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. * * Note that this version of the send data request assumes that the user * data has not already been segmented. This is the function that * performs the segmentation. */ MCSError User::SendData (DataRequestType request_type, ChannelID channel_id, Priority priority, unsigned char * user_data, ULong user_data_length, SendDataFlags flags) { MCSError return_value = MCS_NO_ERROR; ULong i, request_count, user_packet_length; PDataPacket packet; ASN1choice_t choice; UINT type; PUChar data_ptr = user_data; PacketError packet_error; Segmentation segmentation; PMemory memory; PDataPacket *packets; /* * Calculate how many different MCS packets are going to be generated. * Remember that if the size of the request exceeds the maximum allowed * value, we will segment the data into multiple smaller pieces. */ request_count = ((user_data_length + (Maximum_User_Data_Length - 1)) / Maximum_User_Data_Length); /* * Allocate the array of PDataPackets, before we get the critical section. */ if (request_count == 1) { packets = &packet; packet = NULL; } else { DBG_SAVE_FILE_LINE packets = new PDataPacket[request_count]; if (packets == NULL) { ERROR_OUT (("User::SendData: Failed to allocate packet array.")); return_value = MCS_TRANSMIT_BUFFER_FULL; } else { ZeroMemory ((PVoid) packets, request_count * sizeof(PDataPacket)); } } if (MCS_NO_ERROR == return_value) { // Set the choice and type variables for all the DataPackets. if (NORMAL_SEND_DATA == request_type) { choice = SEND_DATA_REQUEST_CHOSEN; type = MCS_SEND_DATA_INDICATION; } else { choice = UNIFORM_SEND_DATA_REQUEST_CHOSEN; type = MCS_UNIFORM_SEND_DATA_INDICATION; } EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); /* * Check to see if there is a merge operation in progress before proceeding * with the request. */ if (MCS_NO_ERROR == return_value) { /* * This request may be a retry from a previous request which * returned MCS_TRANSMIT_BUFFER_FULL. If so, delete the associated * buffer retry info structure since resource levels will be * checked in this function anyway. */ if (m_BufferRetryInfo != NULL) { s_pTimerUserList2->Remove(m_BufferRetryInfo->timer_id); KillTimer (NULL, m_BufferRetryInfo->timer_id); delete m_BufferRetryInfo; m_BufferRetryInfo = NULL; } /* * Depending on the "flags" argument, we either have * to allocate the buffer space and copy the data into * it, or just create a Memory object for the supplied * buffer. */ if (flags != APP_ALLOCATION) { ASSERT (flags == MCS_ALLOCATION); /* * The buffer was allocated by MCS, thru an * MCSGetBufferRequest call. So, the Memory object * must preceed the buffer. */ memory = GetMemoryObject (user_data); ASSERT (SIGNATURE_MATCH(memory, MemorySignature)); } else memory = NULL; /* * We now attempt to allocate all data packets at once. * We need to do that before starting to send them, because * the request has to be totally successful or totally fail. * We can not succeed in sending a part of the request. */ for (i = 0; (ULong) i < request_count; i++) { // take care of segmentation flags if (i == 0) // first segment segmentation = SEGMENTATION_BEGIN; else segmentation = 0; if (i == request_count - 1) { // last segment segmentation |= SEGMENTATION_END; user_packet_length = user_data_length - (ULong)(data_ptr - user_data); } else { user_packet_length = Maximum_User_Data_Length; } // Now, create the new DataPacket. DBG_SAVE_FILE_LINE packets[i] = new DataPacket (choice, data_ptr, user_packet_length, (UINT) channel_id, priority, segmentation, (UINT) User_ID, flags, memory, &packet_error); // Make sure the allocation succeeded if ((packets[i] == NULL) || (packet_error != PACKET_NO_ERROR)) { /* * The allocation of the packet failed. We must therefore * return a failure to the user application. */ WARNING_OUT (("User::SendData: data packet allocation failed")); return_value = MCS_TRANSMIT_BUFFER_FULL; break; } // Adjust the user data ptr data_ptr += Maximum_User_Data_Length; } if (return_value == MCS_NO_ERROR) { // We now can send the data. // Forward all the data packets to the appropriate places. for (i = 0; i < request_count; i++) { /* * Send the successfully created packet to the domain * for processing. */ m_pDomain->SendDataRequest (this, (UINT) type, packets[i]); /* * Enable the packet to free itself. Note that it will not * actually do so until everyone that is using it is through * with it. Also, if nobody has locked it so far, * it will be deleted. */ packets[i]->Unlock (); } } else { // some packet allocation failed for (i = 0; i < request_count; i++) delete packets[i]; } } if (request_count > 1) delete [] packets; } if (MCS_TRANSMIT_BUFFER_FULL == return_value) { CreateRetryTimer(user_data_length + request_count * MAXIMUM_PROTOCOL_OVERHEAD); } else if (MCS_NO_ERROR == return_value) { FreeMemory (memory); } LeaveCriticalSection (& g_MCS_Critical_Section); return (return_value); } #define GRAB 0 #define INHIBIT 1 #define PLEASE 2 #define RELEASE 3 #define TEST 4 /* * MCSError TokenGIRPT () * * Public * * Functional Description: * This function is called when the user application wishes to grab/inhibit/request/release/test * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenGIRPT (int type, TokenID token_id) { MCSError return_value; EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { switch (type) { case GRAB: m_pDomain->TokenGrabRequest (this, User_ID, token_id); break; case INHIBIT: m_pDomain->TokenInhibitRequest (this, User_ID, token_id); break; case PLEASE: m_pDomain->TokenPleaseRequest (this, User_ID, token_id); break; case RELEASE: m_pDomain->TokenReleaseRequest (this, User_ID, token_id); break; case TEST: m_pDomain->TokenTestRequest (this, User_ID, token_id); break; } } LeaveCriticalSection (& g_MCS_Critical_Section); return (return_value); } /* * MCSError TokenGrab () * * Public * * Functional Description: * This function is called when the user application wishes to grab * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenGrab (TokenID token_id) { return (TokenGIRPT (GRAB, token_id)); } /* * MCSError TokenInhibit () * * Public * * Functional Description: * This function is called when the user application wishes to inhibit * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenInhibit (TokenID token_id) { return (TokenGIRPT (INHIBIT, token_id)); } /* * MCSError TokenGive () * * Public * * Functional Description: * This function is called when the user application wishes to give away * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenGive (TokenID token_id, UserID receiver_id) { MCSError return_value; TokenGiveRecord TokenGiveRec; if (receiver_id > 1000) { // Fill in the TokenGive command structure. TokenGiveRec.uidInitiator = User_ID; TokenGiveRec.token_id = token_id; TokenGiveRec.receiver_id = receiver_id; EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { m_pDomain->TokenGiveRequest (this, &TokenGiveRec); } LeaveCriticalSection (& g_MCS_Critical_Section); } else { ERROR_OUT(("User::TokenGive: Invalid UserID for receiver.")); return_value = MCS_INVALID_PARAMETER; } return (return_value); } /* * MCSError TokenGiveResponse () * * Public * * Functional Description: * This function is called when the user application wishes to respond to * a previously received token give indication. If the user is attached to * the domain, the request will be repackaged as an MCS command and sent to * the domain object. */ MCSError User::TokenGiveResponse (TokenID token_id, Result result) { MCSError return_value; EnterCriticalSection (& g_MCS_Critical_Section); /* * Verify that current conditions are appropriate for a request to be * accepted from a user attachment. */ return_value = ValidateUserRequest (); if (return_value == MCS_NO_ERROR) { m_pDomain->TokenGiveResponse (this, result, User_ID, token_id); } LeaveCriticalSection (& g_MCS_Critical_Section); return (return_value); } /* * MCSError TokenPlease () * * Public * * Functional Description: * This function is called when the user application wishes to be given * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenPlease (TokenID token_id) { return (TokenGIRPT (PLEASE, token_id)); } /* * MCSError TokenRelease () * * Public * * Functional Description: * This function is called when the user application wishes to release * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenRelease (TokenID token_id) { return (TokenGIRPT (RELEASE, token_id)); } /* * MCSError TokenTest () * * Public * * Functional Description: * This function is called when the user application wishes to test * a token. If the user is attached to the domain, the request will * be repackaged as an MCS command and sent to the domain object. */ MCSError User::TokenTest (TokenID token_id) { return (TokenGIRPT (TEST, token_id)); } /* * MCSError ValidateUserRequest () * * Private * * Functional Description: * This function is used to determine if it is valid to process an incoming * request at the current time. It checks several different conditions * to determine this, as follows: * * - If there is a merge in progress, then the request is not valid. * - If this user is not yet attached to a domain, then the request * is not valid. * - If there are not enough objects of the Memory, Packet, or UserMessage * class to handle a reasonable request, then the request is not valid. * * Note that the check on number of objects is not an absolute guarantee * that there will be enough to handle a given request, because a request * can result in MANY PDUs and user messages being generated. For example, * a single channel admit request can result in lots of channel admit * indications being sent. However, checking against a minimum number * of objects can reduce the possibility of failure to be astronomically * low. And remember, even if MCS runs out of something while processing * such a request, it WILL handle it properly (by cleanly destroying the * user attachment or MCS connection upon which the failure occurred). So * there is no chance of MCS crashing as a result of this. * * Caveats: * None. */ MCSError User::ValidateUserRequest () { MCSError return_value = MCS_NO_ERROR; /* * Check to see if there is a merge operation in progress. */ if (Merge_In_Progress == FALSE) { /* * Make sure the user is attached to the domain. */ if (User_ID == 0) { /* * The user is not yet attached to the domain. So fail the request * without passing it on to the domain object. */ TRACE_OUT (("User::ValidateUserRequest: user not attached")); return_value = MCS_USER_NOT_ATTACHED; } } else { /* * This operation could not be processed at this time due to a merge * operation in progress at the local provider. * * NOTE for JASPER: * Jasper probably will need to wait on an event handle here, which will be * set when the main MCS thread receives all the merging PDUs that get us out * of the merging state. Since the only MCS client for Jasper is the GCC, * it should be ok to block the client (GCC) while the merging goes on. */ WARNING_OUT (("User::ValidateUserRequest: merge in progress")); return_value = MCS_DOMAIN_MERGING; } return (return_value); } /* * Void RegisterUserAttachment () * * Public * * Functional Description: * This method registers a user attachment with the User object. */ void User::RegisterUserAttachment (MCSCallBack mcs_callback, PVoid user_defined, UINT flags) { TRACE_OUT (("User::RegisterUserAttachment: user handle = %p", this)); /* * Fill in all of the members of the User object. */ m_MCSCallback = mcs_callback; m_UserDefined = user_defined; m_BufferRetryInfo = NULL; m_fDisconnectInDataLoss = (flags & ATTACHMENT_DISCONNECT_IN_DATA_LOSS); m_fFreeDataIndBuffer = (flags & ATTACHMENT_MCS_FREES_DATA_IND_BUFFER); // Increase the ref count to indicate that the client is now using the object. AddRef(); } /* * Void SetDomainParameters () * * Public * * Functional Description: * This command is used to set the current value of the instance variable * that holds the maximum user data field length. */ void User::SetDomainParameters ( PDomainParameters domain_parameters) { /* * Set the maximum user data length instance variable to conform to the * maximum PDU size within the attached domain (minus some overhead to * allow for protocol bytes). */ Maximum_User_Data_Length = domain_parameters->max_mcspdu_size - (MAXIMUM_PROTOCOL_OVERHEAD_MCS + BER_PROTOCOL_EXTRA_OVERHEAD); TRACE_OUT (("User::SetDomainParameters: " "maximum user data length = %ld", Maximum_User_Data_Length)); /* * Use the specified domain parameters to set the type of encoding rules * to be used. */ ASSERT (domain_parameters->protocol_version == PROTOCOL_VERSION_PACKED); } /* * Void PurgeChannelsIndication () * * Public * * Functional Description: * This function is called during a domain merge operation when there is * a conflict in the use of channels. The former Top Provider responds * by issuing this command, which causes all users of the channel to be * expelled from it. Additionally, if the channel corresponds to a user * ID channel, that user is purged from the network. */ void User::PurgeChannelsIndication ( CUidList *purge_user_list, CChannelIDList *) { /* * Issue a DetachUserIndication to each user contained in the purge user * list. */ DetachUserIndication(REASON_PROVIDER_INITIATED, purge_user_list); } /* * Void DisconnectProviderUltimatum () * * Public * * Functional Description: * This function will be called when the domain determines the need to * tear down quickly. This call simulates the reception of a detach user * indication (if the user is already attached), or an unsuccessful * attach user confirm (if the user is not yet attached). In either * case, the user attachment will be eliminated by this call. */ void User::DisconnectProviderUltimatum ( Reason reason) { CUidList deletion_list; if (User_ID != 0) { /* * If the user is already attached, simulate a detach user indication * on the local user ID. */ deletion_list.Append(User_ID); DetachUserIndication(reason, &deletion_list); } else { /* * If the user is not yet attached, simulate an unsuccessful attach * user confirm. */ AttachUserConfirm(RESULT_UNSPECIFIED_FAILURE, 0); } } /* * Void AttachUserConfirm () * * Public * * Functional Description: * This function is called by the domain in response to the attach user * request that was sent by this object when it was first created. This * call will contain the result of that attachment operation. If the * result is successful, this call will also contain the user ID for this * attachment. */ void User::AttachUserConfirm ( Result result, UserID uidInitiator) { LPARAM parameter; if (Deletion_Pending == FALSE) { ASSERT (User_ID == 0); /* * If the result is successful, set the user ID of this user * object to indicate its new status. */ if (result == RESULT_SUCCESSFUL) User_ID = uidInitiator; else Deletion_Pending = TRUE; parameter = PACK_PARAMETER (uidInitiator, result); /* * Post the user message to the application. */ if (! PostMessage (m_hWnd, USER_MSG_BASE + MCS_ATTACH_USER_CONFIRM, (WPARAM) this, parameter)) { WARNING_OUT (("User::AttachUserConfirm: Failed to post msg to application. Error: %d", GetLastError())); if (result != RESULT_SUCCESSFUL) Release(); } } else { Release(); } } /* * Void DetachUserIndication () * * Public * * Functional Description: * This function is called by the domain whenever a user leaves the domain * (voluntarily or otherwise). Furthermore, if a user ID in the indication * is the same as the local user ID, then this user is being involuntarily * detached. */ Void User::DetachUserIndication ( Reason reason, CUidList *user_id_list) { UserID uid; LPARAM parameter; BOOL bPostMsgResult; if (Deletion_Pending == FALSE) { /* * Iterate through the list of users to be deleted. */ user_id_list->Reset(); while (NULL != (uid = user_id_list->Iterate())) { parameter = PACK_PARAMETER(uid, reason); /* * Post the user message to the application. */ bPostMsgResult = PostMessage (m_hWnd, USER_MSG_BASE + MCS_DETACH_USER_INDICATION, (WPARAM) this, parameter); if (! bPostMsgResult) { WARNING_OUT (("User::DetachUserIndication: Failed to post msg to application. Error: %d", GetLastError())); } /* * If this indication is deleting this user attachment, then * set the deletion pending flag, and break out of the loop. */ if (User_ID == uid) { m_originalUser_ID = User_ID; User_ID = 0; Deletion_Pending = TRUE; if (! bPostMsgResult) Release(); break; } } } else { /* * The user has already called ReleaseInterface(). If the * Indication is for this attachment, we have to release and * probably, delete the object. */ if (user_id_list->Find(User_ID)) { Release(); } } } /* * Void ChannelJLCDAEConfInd () * * Public * * Functional Description: * This function is called to post a channel confirm/indication message * to the user application. It handles ChannelJoinConfirms, * ChannelLeaveIndications, ChannelConveneConfirms, ChannelDisbandIndications * and ChannelExpelIndications. */ Void User::ChannelConfInd ( UINT type, ChannelID channel_id, UINT arg16) { LPARAM parameter; ASSERT (HIWORD(arg16) == 0); if (Deletion_Pending == FALSE) { parameter = PACK_PARAMETER (channel_id, arg16); /* * Post the user message to the application. */ if (! PostMessage (m_hWnd, USER_MSG_BASE + type, (WPARAM) this, parameter)) { WARNING_OUT (("User::ChannelConfInd: Failed to post msg to application. Type: %d. Error: %d", type, GetLastError())); } } } /* * Void ChannelJoinConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous channel * join request. This call contains the result of the join request, as * well as the channel that has just been joined. */ Void User::ChannelJoinConfirm ( Result result, UserID, ChannelID requested_id, ChannelID channel_id) { ChannelConfInd (MCS_CHANNEL_JOIN_CONFIRM, channel_id, (UINT) result); } /* * Void ChannelLeaveIndication () * * Public * * Functional Description: */ Void User::ChannelLeaveIndication ( Reason reason, ChannelID channel_id) { ChannelConfInd (MCS_CHANNEL_LEAVE_INDICATION, channel_id, (UINT) reason); } /* * Void ChannelConveneConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous channel * convene request. This call contains the result of the request, as * well as the channel that has just been convened. */ Void User::ChannelConveneConfirm ( Result result, UserID, ChannelID channel_id) { ChannelConfInd (MCS_CHANNEL_CONVENE_CONFIRM, channel_id, (UINT) result); } /* * Void ChannelDisbandIndication () * * Public * * Functional Description: * This function is called by the domain when MCS disbands an existing * private channel. */ Void User::ChannelDisbandIndication ( ChannelID channel_id) { ChannelConfInd (MCS_CHANNEL_DISBAND_INDICATION, channel_id, REASON_CHANNEL_PURGED); } /* * Void ChannelAdmitIndication () * * Public * * Functional Description: * This function is called by the domain when a user is admitted to a * private channel. */ Void User::ChannelAdmitIndication ( UserID uidInitiator, ChannelID channel_id, CUidList *) { ChannelConfInd (MCS_CHANNEL_ADMIT_INDICATION, channel_id, (UINT) uidInitiator); } /* * Void ChannelExpelIndication () * * Public * * Functional Description: * This function is called by the domain when a user is expelled from a * private channel. */ Void User::ChannelExpelIndication ( ChannelID channel_id, CUidList *) { ChannelConfInd (MCS_CHANNEL_EXPEL_INDICATION, channel_id, REASON_USER_REQUESTED); } /* * Void SendDataIndication () * * Public * * Functional Description: * This function is called by the domain when data needs to sent to the * user on a channel that the user has joined. */ Void User::SendDataIndication ( UINT message_type, PDataPacket packet) { if (Deletion_Pending == FALSE) { /* * Lock the packet object to indicate that we wish to have future * access to the decoded data that it contains. Then get the * address of the decoded data structure. */ packet->Lock (); packet->SetMessageType(message_type); // flush packets in the pending queue PDataPacket pkt; while (NULL != (pkt = m_PostMsgPendingQueue.PeekHead())) { if (::PostMessage(m_hWnd, USER_MSG_BASE + pkt->GetMessageType(), (WPARAM) this, (LPARAM) pkt)) { // remove the item just posted m_PostMsgPendingQueue.Get(); } else { // fail to post pending ones, just append the new one and bail out. m_PostMsgPendingQueue.Append(packet); return; } } /* * Post the user message to the application. */ if (! ::PostMessage(m_hWnd, USER_MSG_BASE + message_type, (WPARAM) this, (LPARAM) packet)) { // fail to post pending ones, just append the new one and bail out. m_PostMsgPendingQueue.Append(packet); return; } } } /* * Void TokenConfInd () * * Public * * Functional Description: * This function is called to post a token confirm/indication message * to the user application. */ Void User::TokenConfInd (UINT type, TokenID token_id, UINT arg16) { LPARAM parameter; ASSERT (HIWORD(arg16) == 0); if (Deletion_Pending == FALSE) { parameter = PACK_PARAMETER (token_id, arg16); /* * Post the user message to the application. */ if (! PostMessage (m_hWnd, USER_MSG_BASE + type, (WPARAM) this, parameter)) { WARNING_OUT (("User::TokenConfInd: Failed to post msg to application. Type: %d. Error: %d", type, GetLastError())); } } } /* * Void TokenGrabConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous token * grab request. This call contains the result of the grab request, as * well as the token that has just been grabbed. */ Void User::TokenGrabConfirm ( Result result, UserID, TokenID token_id, TokenStatus) { TokenConfInd (MCS_TOKEN_GRAB_CONFIRM, token_id, (UINT) result); } /* * Void TokenInhibitConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous token * inhibit request. This call contains the result of the inhibit request, * as well as the token that has just been inhibited. */ Void User::TokenInhibitConfirm ( Result result, UserID, TokenID token_id, TokenStatus) { TokenConfInd (MCS_TOKEN_INHIBIT_CONFIRM, token_id, (UINT) result); } /* * Void TokenGiveIndication () * * Public * * Functional Description: * This function is called by the domain when another user attempts to * give this user a token. */ Void User::TokenGiveIndication ( PTokenGiveRecord pTokenGiveRec) { TokenConfInd (MCS_TOKEN_GIVE_INDICATION, pTokenGiveRec->token_id, (UINT) pTokenGiveRec->uidInitiator); } /* * Void TokenGiveConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous token * give request. This call contains the result of the give request. */ Void User::TokenGiveConfirm ( Result result, UserID, TokenID token_id, TokenStatus) { TokenConfInd (MCS_TOKEN_GIVE_CONFIRM, token_id, (UINT) result); } /* * Void TokenPleaseIndication () * * Public * * Functional Description: * This function is called by the domain when a user somewhere in the * domain issues a token please request for a token that is currently * owned by this user. */ Void User::TokenPleaseIndication ( UserID uidInitiator, TokenID token_id) { TokenConfInd (MCS_TOKEN_PLEASE_INDICATION, token_id, (UINT) uidInitiator); } /* * Void TokenReleaseIndication () * * Public * * Functional Description: * This command is called when a token is being purged from the lower * domain after a new connection is established. It causes the indication * to be forwarded to the user application, letting it know that it no * longer owns the token. */ Void User::TokenReleaseIndication ( Reason reason, TokenID token_id) { TokenConfInd (MCS_TOKEN_RELEASE_INDICATION, token_id, (UINT) reason); } /* * Void TokenReleaseConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous token * release request. This call contains the result of the release request, * as well as the token that has just been released. */ Void User::TokenReleaseConfirm ( Result result, UserID, TokenID token_id, TokenStatus) { TokenConfInd (MCS_TOKEN_RELEASE_CONFIRM, token_id, (UINT) result); } /* * Void TokenTestConfirm () * * Public * * Functional Description: * This function is called by the domain in response to a previous token * test request. This call contains the result of the test request, * as well as the token that has just been tested. */ Void User::TokenTestConfirm ( UserID, TokenID token_id, TokenStatus token_status) { TokenConfInd (MCS_TOKEN_TEST_CONFIRM, token_id, (UINT) token_status); } /* * Void MergeDomainIndication () * * Public * * Functional Description: * This function is called by domain upon entering or leaving a domain * merger state. */ Void User::MergeDomainIndication ( MergeStatus merge_status) { if (Deletion_Pending == FALSE) { /* * If the merge operation is starting, set a boolean flag * indicating that this object should reject all user activity. * Otherwise, reset the flag. */ if (merge_status == MERGE_DOMAIN_IN_PROGRESS) { TRACE_OUT (("User::MergeDomainIndication: entering merge state")); Merge_In_Progress = TRUE; } else { TRACE_OUT (("User::MergeDomainIndication: leaving merge state")); Merge_In_Progress = FALSE; } } } /* * Void PurgeMessageQueue () * * Private * * Functional Description: * This function is called to purge all current entries from the message * queue, freeing up resources correctly (to prevent leaks). * * Formal Parameters: * None. * * Return Value: * None. * * Side Effects: * None. * * Caveats: * This function should only be called in the client's thread's context. */ Void User::PurgeMessageQueue () { MSG msg; PDataPacket packet; HWND hWnd; // First, unlock the packets in the list of pending data indications while (NULL != (packet = m_DataPktQueue.Get())) packet->Unlock(); // Keep a copy of the attachment's HWND to destroy it later. hWnd = m_hWnd; m_hWnd = NULL; /* * This loop calls PeekMessage to go through all the messages in the thread's * queue that were posted by the main MCS thread. It removes these * messages and frees the resources that they consume. */ while (PeekMessage (&msg, hWnd, USER_MSG_BASE, USER_MSG_BASE + MCS_LAST_USER_MESSAGE, PM_REMOVE)) { if (msg.message == WM_QUIT) { // Repost the quit PostQuitMessage (0); break; } /* * If this is a data indication message, we need to unlock * the packet associated with this message. */ else if ((msg.message == USER_MSG_BASE + MCS_SEND_DATA_INDICATION) || (msg.message == USER_MSG_BASE + MCS_UNIFORM_SEND_DATA_INDICATION)) { ((PDataPacket) msg.lParam)->Unlock (); } else if (((msg.message == USER_MSG_BASE + MCS_ATTACH_USER_CONFIRM) && ((Result) HIWORD(msg.lParam) != RESULT_SUCCESSFUL)) || ((msg.message == USER_MSG_BASE + MCS_DETACH_USER_INDICATION) && (m_originalUser_ID == (UserID) LOWORD(msg.lParam)))) { ASSERT (this == (PUser) msg.wParam); Release(); break; } } // Destroy the window; we do not need it anymore DestroyWindow (hWnd); } void User::IssueDataIndication ( UINT message_type, PDataPacket packet) { LPARAM parameter; PMemory memory; BOOL bIssueCallback = TRUE; BOOL bBufferInPacket = TRUE; PUChar data_ptr; SendDataIndicationPDU send_data_ind_pdu; switch (packet->GetSegmentation()) { case SEGMENTATION_BEGIN | SEGMENTATION_END: parameter = (LPARAM) &(((PDomainMCSPDU) (packet->GetDecodedData()))-> u.send_data_indication); data_ptr = packet->GetUserData(); memory = packet->GetMemory(); break; case SEGMENTATION_END: { /* * We now have to collect all the individual packets from m_DataPktQueue * that go with this MCS Data PDU and sent them as a single data indication * using a buffer large enough for all the data. */ /* * First, find out the size of the large buffer we need to allocate. * Note that we make a copy of the original m_DataPktList and operate * on the copy, since we need to remove items from the original list. */ CDataPktQueue PktQ(&m_DataPktQueue); UINT size; PDataPacket data_pkt; PUChar ptr; #ifdef DEBUG UINT uiCount = 0; #endif // DEBUG size = packet->GetUserDataLength(); PktQ.Reset(); while (NULL != (data_pkt = PktQ.Iterate())) { if (packet->Equivalent (data_pkt)) { #ifdef DEBUG if (uiCount == 0) { ASSERT (data_pkt->GetSegmentation() == SEGMENTATION_BEGIN); } else { ASSERT (data_pkt->GetSegmentation() == 0); } uiCount++; #endif // DEBUG size += data_pkt->GetUserDataLength(); // Remove from the original list, since we are processing the callback. m_DataPktQueue.Remove(data_pkt); } } // Allocate the memory we need. DBG_SAVE_FILE_LINE memory = AllocateMemory (NULL, size); if (memory != NULL) { bBufferInPacket = FALSE; // Copy the individual indications into the large buffer. data_ptr = ptr = memory->GetPointer(); PktQ.Reset(); /* * We need to enter the MCS critical section, because * we are unlocking packets. */ EnterCriticalSection (& g_MCS_Critical_Section); while (NULL != (data_pkt = PktQ.Iterate())) { if (packet->Equivalent (data_pkt)) { size = data_pkt->GetUserDataLength(); memcpy ((void *) ptr, (void *) data_pkt->GetUserData(), size); ptr += size; data_pkt->Unlock(); } } // Leave the MCS critical section LeaveCriticalSection (& g_MCS_Critical_Section); // Copy the last indication into the large buffer. memcpy ((void *) ptr, (void *) packet->GetUserData(), packet->GetUserDataLength()); /* * Prepare the SendDataIndicationPDU structure for the client. * Notice that we can use the first 8 bytes from the decoded * structure of the current "packet" to fill in the first bytes from * it. */ memcpy ((void *) &send_data_ind_pdu, (void *) &(((PDomainMCSPDU) (packet->GetDecodedData()))-> u.send_data_indication), 8); send_data_ind_pdu.segmentation = SEGMENTATION_BEGIN | SEGMENTATION_END; send_data_ind_pdu.user_data.length = memory->GetLength(); send_data_ind_pdu.user_data.value = data_ptr; parameter = (ULONG_PTR) &send_data_ind_pdu; } else { /* * We have failed to issue the data indication callback to the client. * The user attachment has been compromised. If the attachment can not * live with this loss, we have to detach them from the conference. */ ERROR_OUT (("User::IssueDataIndication: Memory allocation failed for segmented buffer of size %d.", size)); bIssueCallback = FALSE; // Clean up after the failure EnterCriticalSection (& g_MCS_Critical_Section); PktQ.Reset(); while (NULL != (data_pkt = PktQ.Iterate())) { if (m_fDisconnectInDataLoss || (packet->Equivalent (data_pkt))) { data_pkt->Unlock(); } } packet->Unlock(); LeaveCriticalSection (& g_MCS_Critical_Section); // Disconnect if the client wants us to. if (m_fDisconnectInDataLoss) { // Clear the list of the already-cleared pending packets. We will soon get a ReleaseInterface(). m_DataPktQueue.Clear(); ERROR_OUT(("User::IssueDataIndication: Disconnecting user because of data loss...")); /* * Send a detach user indication directly to the user application. * Note that this cannot go through the queue, due to the memory * failure. */ (*m_MCSCallback) (MCS_DETACH_USER_INDICATION, PACK_PARAMETER (User_ID, REASON_PROVIDER_INITIATED), m_UserDefined); } } break; } case SEGMENTATION_BEGIN: case 0: // Append the packet to the list of packets for send. m_DataPktQueue.Append(packet); bIssueCallback = FALSE; break; default: ASSERT (FALSE); ERROR_OUT(("User::IssueDataIndication: Processed packet with invalid segmentation field.")); break; } if (bIssueCallback) { /* * If the client has advised the server not to free the data, we have to * lock the buffer. */ if (m_fFreeDataIndBuffer == FALSE) { if (bBufferInPacket) LockMemory (memory); // Enter the data indication info in a dictionary, for the Free request. if (GetMemoryObject(data_ptr) != memory) { m_DataIndMemoryBuf2.Append((LPVOID) data_ptr, memory); } } /* * Issue the callback. The callee can not refuse to process this. */ (*m_MCSCallback) (message_type, parameter, m_UserDefined); /* * If the client has advised the server to free the data indication buffer * after delivering the callback, we must do so. */ if (m_fFreeDataIndBuffer) { if (bBufferInPacket == FALSE) FreeMemory (memory); } // To unlock a packet, we need to enter the MCS CS. EnterCriticalSection (& g_MCS_Critical_Section); packet->Unlock(); LeaveCriticalSection (& g_MCS_Critical_Section); } } /* * LRESULT UserWindowProc () * * Public * * Functional Description: * This is the window procedure that will be used by all internally * created windows. A hidden window is created internally when the * application attaches to an MCS domain. This technique insures * that callbacks are delivered to the owner in the same thread that * initially created the attachment. */ LRESULT CALLBACK UserWindowProc ( HWND window_handle, UINT message, WPARAM word_parameter, LPARAM long_parameter) { UINT mcs_message; //PDataPacket packet; PUser puser; if ((message >= USER_MSG_BASE) && (message < USER_MSG_BASE + MCS_LAST_USER_MESSAGE)) { // This is an MCS msg going to the user application. // Compute the MCS msg type mcs_message = message - USER_MSG_BASE; // Retrieve the pointer to the User (interface) object. puser = (PUser) word_parameter; if (NULL != puser) { /* * Find out whether this is a data indication. If it is, set the * packet variable. */ if ((mcs_message == MCS_SEND_DATA_INDICATION) || (mcs_message == MCS_UNIFORM_SEND_DATA_INDICATION)) { puser->IssueDataIndication (mcs_message, (PDataPacket) long_parameter); } else { /* * Issue the callback. Notice that the callee can not refuse * to process this. */ (*(puser->m_MCSCallback)) (mcs_message, long_parameter, puser->m_UserDefined); } /* * We may need to release the User object. This is the Server * side release. */ if (((mcs_message == MCS_ATTACH_USER_CONFIRM) && ((Result) HIWORD(long_parameter) != RESULT_SUCCESSFUL)) || ((mcs_message == MCS_DETACH_USER_INDICATION) && (puser->m_originalUser_ID == (UserID) LOWORD(long_parameter)))) { puser->Release(); } } else { ERROR_OUT(("UserWindowProc: null puser")); } return (0); } else { /* * Invoke the default window message handler to handle this * message. */ return (DefWindowProc (window_handle, message, word_parameter, long_parameter)); } } /* * Void CALLBACK TimerProc (HWND, UINT, UINT, DWORD * * Public * * Functional Description: * This is the timer procedure. Timer messages will be routed to this * function as a result of timer events which have been set up to recheck * resource levels. This would happen following a call to either * MCSSendDataRequest or MCSUniformSendDataRequest which resulted in a * return value of MCS_TRANSMIT_BUFFER_FULL. */ Void CALLBACK TimerProc (HWND, UINT, UINT timer_id, DWORD) { PUser puser; /* * Enter the critical section which protects global data. */ EnterCriticalSection (& g_MCS_Critical_Section); /* * First, we must find which user owns this timer. We will do this by * searching through the Static_User_List. */ if (NULL == (puser = User::s_pTimerUserList2->Find(timer_id))) { WARNING_OUT (("TimerProc: no user owns this timer - deleting timer")); KillTimer (NULL, timer_id); goto Bail; } /* * Make sure that this user is actively attached. If not, then kill the * timer and delete the user's buffer retry info structure. */ if ((puser->User_ID == 0) || puser->Deletion_Pending) { WARNING_OUT (("TimerProc: user is not attached - deleting timer")); goto CleanupBail; } /* * If we don't have retryinfo just get out of here. */ if(puser->m_BufferRetryInfo == NULL) { WARNING_OUT (("TimerProc: user does not have buffer retry info - deleting timer")); goto CleanupBail; } /* * We have identified a valid owner of this timer. * Verify that there is enough memory for the * required size before proceeding. Note that since there * can be multiple processes allocating from the same memory * at the same time, this call does not guarantee * that that the allocations will succeed. */ if (GetFreeMemory (SEND_PRIORITY) < puser->m_BufferRetryInfo->user_data_length) { TRACE_OUT (("TimerProc: not enough memory buffers of required size")); goto Bail; } /* * If the routine gets this far, then an adequate level of resources * now exists. */ /* * Issue an MCS_TRANSMIT_BUFFER_AVAILABLE_INDICATION to the user. */ TRACE_OUT(("TimerProc: Delivering MCS_TRANSMIT_BUFFER_AVAILABLE_INDICATION callback.")); // (*(puser->m_MCSCallback)) (MCS_TRANSMIT_BUFFER_AVAILABLE_INDICATION, // 0, puser->m_UserDefined); if(!PostMessage (puser->m_hWnd, USER_MSG_BASE + MCS_TRANSMIT_BUFFER_AVAILABLE_INDICATION,(WPARAM) puser, 0)) { ERROR_OUT (("TimerProc: Failed to post msg to application. Error: %d", GetLastError())); } CleanupBail: KillTimer (NULL, timer_id); delete puser->m_BufferRetryInfo; puser->m_BufferRetryInfo = NULL; User::s_pTimerUserList2->Remove(timer_id); Bail: // Leave the attachment's critical section LeaveCriticalSection (& g_MCS_Critical_Section); }