windows-nt/Source/XPSP1/NT/enduser/netmeeting/t120/mst120/user.cpp
2020-09-26 16:20:57 +08:00

2357 lines
64 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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);
}