#include "precomp.h"
DEBUG_FILEZONE(ZONE_T120_MCSNC);
/*
 *	privchnl.cpp
 *
 *	Copyright (c) 1993 - 1995 by DataBeam Corporation, Lexington, KY
 *
 *	Abstract:
 *		This is the implementation file for the PrivateChannel class.  It
 *		contains the code that distinguishes this class from that of its parent,
 *		Channel.
 *
 *		This class maintains an authorized user list, and includes the code
 *		necessary to use that list.  No user will be allowed to join or send
 *		data on a private channel unless they are either the channel manager
 *		or an admitted user.
 *
 *	Private Instance Variables:
 *		m_uidChannelManager
 *			This is the User ID of the user that convened the private channel.
 *			Only this user is allowed to manipulate the authorized user list.
 *			When a private channel becomes invalid (as the result of a channel
 *			disband request or indication), this value will be set to 0.
 *		m_AuthorizedUserList
 *			This is a collection containing the user IDs of those users that
 *			have been admitted to the private channel by the channel manager.
 *			Other than the manager, these are the only users that are allowed
 *			to join or send data on the channel.  When a private channel becomes
 *			invalid (as the result of a channel disband request or indication),
 *			this list will be cleared.
 *		m_fDisbandRequestPending
 *			This is a boolean flag that gets set when a disband request is
 *			forwarded upward to the top provider.  This prevents this channel
 *			from issuing a disband indication to the channel manager when it
 *			comes back down the tree from the top provider.
 *
 *	Private Member Functions:
 *		ValidateUserID
 *			This member function is called to verify that a specified user ID
 *			corresponds to a valid user in the sub-tree of the local provider.
 *		BuildAttachmentLists
 *			This member function is called to build two lists of attachments
 *			from a master user ID list.  The first list contains all local
 *			attachments whose user ID is in the specified list.  The second
 *			list contains all remote attachments whose user ID is in the
 *			specified list.  These lists are used to issue various indications
 *			to specified users without sending any to the same attachment.
 *		BuildUserIDList
 *			This member function is called to build a list of users that lie
 *			in the direction of a specified attachment.  These lists are
 *			sent along with PDUs that require them.
 *
 *	Caveats:
 *		None.
 *
 *	Author:
 *		James P. Galvin, Jr.
 */

/*
 *	External Interfaces
 */

#include "privchnl.h"


/*
 *	PrivateChannel ()
 *
 *	Public
 *
 *	Functional Description:
 *		This is the primary constructor for PrivateChannel objects.  It creates
 *		an object with all instance variable initialized, but with no
 *		attachments (i.e. no users are joined to the channel automatically).
 *
 *		Note that most instance variable initialization is done by invoking the
 *		equivalent constructor in the base class.
 *
 *		Upon successful completion, a  channel convene confirm is automatically
 *		issued to the channel manager, if the channel manager is in the sub-tree
 *		of this provider.  Note that if the channel manager is NOT in this
 *		sub-tree, then this private channel object was probably created as the
 *		result of a channel admit indication, and no channel convene confirm
 *		will be issued.
 */
PrivateChannel::PrivateChannel (
		ChannelID			channel_id,
		UserID				channel_manager,
		PDomain             local_provider,
		PConnection         top_provider,
		CChannelList2      *channel_list,
		CAttachmentList    *attachment_list)
:
	Channel(channel_id, local_provider, top_provider, channel_list, attachment_list),
	m_AuthorizedUserList(),
	m_uidChannelManager(channel_manager),
	m_fDisbandRequestPending(FALSE)
{
	/*
	 *	Check to see if the channel manager lies in the sub-tree of this
	 *	provider.  If so, then this object was created as the result of a
	 *	channel convene request or confirm, and it is necessary to issue the
	 *	confirm toward that user.  If not, then this object was created as the
	 *	result of a channel admit indication, and it is not necessary to send
	 *	the channel convene confirm.
	 */
	if (ValidateUserID(m_uidChannelManager))
	{
		PChannel	lpChannel;
		/*
		 *	Determine which attachment leads to the channel manager by asking
		 *	the channel object corresponding to it.  Then issue the confirm
		 *	to that attachment.
		 */
		if (NULL != (lpChannel = m_pChannelList2->Find(m_uidChannelManager)))
		{
		    CAttachment *pAtt = lpChannel->GetAttachment();
		    if (pAtt)
		    {
    		    pAtt->ChannelConveneConfirm(RESULT_SUCCESSFUL,
    		                                m_uidChannelManager, channel_id);
            }
            else
            {
                ERROR_OUT(("PrivateChannel::PrivateChannel: null attachment"));
            }
		}
	}
}

/*
 *	PrivateChannel ()
 *
 *	Public
 *
 *	Functional Description:
 *		This is a secondary constructor that is only used during merge
 *		operations.  The intent of this constructor is to create an equivalent
 *		object without issuing any of the confirms.
 *
 *		Note that the additional constructor allows for the creator to specify
 *		that there is an attachment already joined to the channel upon creation.
 */
PrivateChannel::PrivateChannel (
		ChannelID			channel_id,
		UserID				channel_manager,
		PDomain             local_provider,
		PConnection         top_provider,
		CChannelList2      *channel_list,
		CAttachmentList    *attachment_list,
		CUidList           *admitted_list,
		PConnection         pConn)
:
	Channel(channel_id, local_provider, top_provider, channel_list, attachment_list, pConn),
	m_AuthorizedUserList(),
	m_uidChannelManager(channel_manager),
	m_fDisbandRequestPending(FALSE)
{
	UserID		uid;

	/*
	 *	Copy the initial contents of the admitted list into the authorized
	 *	user list.
	 */
	admitted_list->Reset();
	while (NULL != (uid = admitted_list->Iterate()))
	{
		m_AuthorizedUserList.Append(uid);
	}
}

/*
 *	~PrivateChannel ()
 *
 *	Public
 *
 *	Functional Description:
 *		This destructor walks through the admitted list, sending expel
 *		indications to any admitted users that are locally attached.  If the
 *		channel manager is locally attached, and this channel is being deleted
 *		a reason other than a previous disband request, then a disband
 *		indication will be sent to the channel manager.
 */
PrivateChannel::~PrivateChannel ()
{
	CAttachmentList         local_attachment_list;
	CAttachmentList         remote_attachment_list;
	CAttachment            *pAtt;
	CUidList                user_id_list;

	/*
	 *	Assemble lists of the attachments that lead to authorized users in
	 *	the sub-tree of this provider.
	 */
	BuildAttachmentLists (&m_AuthorizedUserList, &local_attachment_list,
			&remote_attachment_list);

	/*
	 *	For each local attachment, issue a channel expel indication letting the
	 *	user know that the channel is no longer valid.
	 */
	local_attachment_list.Reset();
	while (NULL != (pAtt = local_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of the users
		 *	that lie in the direction of that attachment.
		 */
		BuildUserIDList(&m_AuthorizedUserList, pAtt, &user_id_list);

		/*
		 *	Send the indication.
		 */
		pAtt->ChannelExpelIndication(Channel_ID, &user_id_list);
	}

	/*
	 *	If the channel manager is a locally attached user, then send it a
	 *	ChannelDisbandIndication informing it that the channel is no longer
	 *	valid.
	 */
	if ((m_fDisbandRequestPending == FALSE) && ValidateUserID(m_uidChannelManager))
	{
		PChannel		lpChannel;

		if (NULL != (lpChannel = m_pChannelList2->Find(m_uidChannelManager)))
		{
    		CAttachment *pAtt1 = lpChannel->GetAttachment();
    		if (m_pAttachmentList->Find(pAtt1) && pAtt1->IsUserAttachment())
    		{
    		    PUser pUser = (PUser) pAtt1;
    			pUser->ChannelDisbandIndication(Channel_ID);
    	    }
    	}
	}

	/*
	 *	Clear the lists associated with this object.  Note that this also
	 *	prevents the base class destructor from issuing ChannelLeaveIndications
	 *	to any local attachments in the joined attachment list (which would be
	 *	inappropriate).
	 */
	m_AuthorizedUserList.Clear();
	m_JoinedAttachmentList.Clear();
}

/*
 *	Channel_Type		GetChannelType ()
 *
 *	Public
 *
 *	Functional Description:
 *		Objects of this class are always private channels, so simply return
 *		PRIVATE_CHANNEL.
 */
Channel_Type		PrivateChannel::GetChannelType ()
{
	return (PRIVATE_CHANNEL);
}

/*
 *	BOOL    IsValid ()
 *
 *	Public
 *
 *	Functional Description:
 *		By convention, if the m_uidChannelManager is in the sub-tree of this
 *		provider OR if there are any users in the authorized user list, then
 *		the private channel is valid.  Otherwise it is not, and can be deleted
 *		by the domain object.
 */
BOOL    PrivateChannel::IsValid ()
{
	UserID			uid;
	CUidList		deletion_list;

	/*
	 *	Loop through the authorized user list making a list of those entries
	 *	that are no longer valid.
	 */
	m_AuthorizedUserList.Reset();
	while (NULL != (uid = m_AuthorizedUserList.Iterate()))
	{
		if (ValidateUserID(uid) == FALSE)
			deletion_list.Append(uid);
	}

	/*
	 *	Loop through the deletion list created above, deleting those user IDs
	 *	that are no longer valid.
	 */
	deletion_list.Reset();
	while (NULL != (uid = deletion_list.Iterate()))
	{
		m_AuthorizedUserList.Remove(uid);
	}

	/*
	 *	If this is the Top Provider, then the channel manager should ALWAYS be
	 *	in the sub-tree.  If it is not, then this indicates that the channel
	 *	manager has detached (willingly or otherwise).  When this happens it
	 *	is necessary to simulate a channel disband request (only if there are
	 *	other admitted users who need to receive a channel expel indication).
	 */
	if ((m_pConnToTopProvider == NULL) &&
			(ValidateUserID(m_uidChannelManager) == FALSE) &&
			(m_AuthorizedUserList.IsEmpty() == FALSE))
	{
		TRACE_OUT (("PrivateChannel::IsValid: "
				"simulating ChannelDisbandRequest"));
		ChannelDisbandRequest(NULL, m_uidChannelManager, Channel_ID);
	}

	/*
	 *	Check to see if the channel manager is in the sub-tree of this provider
	 *	or if the authorized user list is not empty.  If either is TRUE, then
	 *	then the channel is still valid.
	 */
	return (ValidateUserID(m_uidChannelManager) || (m_AuthorizedUserList.IsEmpty() == FALSE));
}

/*
 *	CAttachment *GetAttachment ()
 *
 *	Public
 *
 *	Functional Description:
 *		Return a pointer to the attachment leading to the channel manager.
 */
CAttachment *PrivateChannel::GetAttachment(void)
{
	if (ValidateUserID(m_uidChannelManager))
    {
		PChannel	lpChannel;
		if (NULL != (lpChannel = m_pChannelList2->Find(m_uidChannelManager)))
		{
            return lpChannel->GetAttachment();
        }
	}
	return NULL;
}

/*
 *	Void	IssueMergeRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		Issue a merge request for the information contained in this
 *		PrivateChannel object.
 */
Void	PrivateChannel::IssueMergeRequest ()
{
	ChannelAttributes		channel_attributes;
	CChannelAttributesList	merge_channel_list;
	CChannelIDList			purge_channel_list;

	if (m_pConnToTopProvider != NULL)
	{
		/*
		 *	Fill in the fields of the channel attributes structure so that it
		 *	accurately describes this channel.  Then put the structure into the
		 *	merge channel list.
		 */
		channel_attributes.channel_type = PRIVATE_CHANNEL;
		if (m_JoinedAttachmentList.IsEmpty() )
			channel_attributes.u.private_channel_attributes.joined = FALSE;
		else
			channel_attributes.u.private_channel_attributes.joined = TRUE;
		channel_attributes.u.private_channel_attributes.channel_id = Channel_ID;
		channel_attributes.u.private_channel_attributes.channel_manager = m_uidChannelManager;
		channel_attributes.u.private_channel_attributes.admitted_list =	&m_AuthorizedUserList;

		merge_channel_list.Append(&channel_attributes);

		/*
		 *	Send the merge request to the indicated provider.
		 */
		m_pConnToTopProvider->MergeChannelsRequest(&merge_channel_list, &purge_channel_list);
	}
}

/*
 *	Void	ChannelJoinRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		This function overrides the base class implementation.  The main
 *		difference is that this implementation only allows a user to join
 *		the private channel if it is either the channel manager or in the
 *		authorized user list.
 */
Void	PrivateChannel::ChannelJoinRequest (
				CAttachment        *pOrigAtt,
				UserID				uidInitiator,
				ChannelID			channel_id)
{
	/*
	 *	See if the requesting user is either the channel manager or in the
	 *	authorized user list.
	 */
	if ((uidInitiator == m_uidChannelManager) || m_AuthorizedUserList.Find(uidInitiator))
	{
		/*
		 *	See if anyone is currently joined to the channel in this sub-tree
		 */
		if (m_JoinedAttachmentList.IsEmpty())
		{
			/*
			 *	If this is the Top Provider, then this request can be handled
			 *	locally.
			 */
			if (IsTopProvider())
			{
				/*
				 *	There is no one in this sub-tree joined to the channel.  It
				 *	will therefore be necessary to add the originator to the
				 *	attachment list.
				 */
				TRACE_OUT (("PrivateChannel::ChannelJoinRequest: "
						"user %04X joining private channel = %04X",
						(UINT) uidInitiator, (UINT) Channel_ID));
				m_JoinedAttachmentList.Append(pOrigAtt);

				/*
				 *	Send a ChannelJoinConfirm downward to the originator.
				 */
				pOrigAtt->ChannelJoinConfirm(RESULT_SUCCESSFUL, uidInitiator, channel_id, Channel_ID);
			}
			else
			{
				/*
				 *	This is not the Top Provider.  Forward the join request
				 *	upward to the Top Provider.
				 */
				TRACE_OUT (("PrivateChannel::ChannelJoinRequest: "
						"forwarding join request to Top Provider"));
				m_pConnToTopProvider->ChannelJoinRequest(uidInitiator, Channel_ID);
			}
		}

		/*
		 *	There is at least one attachment joined to the channel, which means
		 *	that we do not have to forward the join request upward (even if
		 *	this is not the Top Provider).  Now check to see if the requesting
		 *	originator is already joined to the channel.
		 */
		else if (m_JoinedAttachmentList.Find(pOrigAtt) == FALSE)
		{
			/*
			 *	The originator is not yet joined to the channel, so add it to
			 *	the channel.
			 */
			TRACE_OUT (("PrivateChannel::ChannelJoinRequest: "
					"user %04X joining private channel = %04X",
					(UINT) uidInitiator, (UINT) Channel_ID));
			m_JoinedAttachmentList.Append(pOrigAtt);

			/*
			 *	Send a ChannelJoinConfirm downward to the originator.
			 */
			pOrigAtt->ChannelJoinConfirm(RESULT_SUCCESSFUL, uidInitiator, channel_id, Channel_ID);
		}

		else
		{
			/*
			 *	The originator is already joined to the channel.  Go ahead and
			 *	issue a successful channel join confirm.
			 */
			WARNING_OUT (("PrivateChannel::ChannelJoinRequest: "
					"already joined to channel"));
			pOrigAtt->ChannelJoinConfirm(RESULT_SUCCESSFUL, uidInitiator, channel_id, Channel_ID);
		}
	}
	else
	{
		/*
		 *	Someone is trying to join a private channel that they are not
		 *	admitted to.  Reject the request without further processing.
		 */
		WARNING_OUT (("PrivateChannel::ChannelJoinRequest: "
				"rejecting attempt to join private channel"));
		pOrigAtt->ChannelJoinConfirm(RESULT_NOT_ADMITTED, uidInitiator, channel_id, 0);
	}
}

/*
 *	Void	ChannelDisbandRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by a user that wishes to disband a
 *		private channel that it previously created.  If the requesting user is
 *		the private channel manager, then the request will be processed.  If
 *		this is not the Top Provider, the request will be forwarded upward.
 */
Void	PrivateChannel::ChannelDisbandRequest (
				CAttachment *,
				UserID				uidInitiator,
				ChannelID)
{
	CUidList				user_id_list;

	/*
	 *	Check to see if the requesting user is the channel manager.  Only
	 *	process the request if it is.
	 */
	if (uidInitiator == m_uidChannelManager)
	{
		/*
		 *	See if this is the Top Provider.  If it is, then the request can
		 *	be processed locally.  Otherwise, pass the request upward toward
		 *	the Top Provider.
		 */
		if (IsTopProvider())
		{
        	CAttachmentList     local_attachment_list;
        	CAttachmentList     remote_attachment_list;
        	CAttachment        *pAtt;

			TRACE_OUT (("PrivateChannel::ChannelDisbandRequest: "
					"disbanding channel = %04X", Channel_ID));

			/*
			 *	Go construct lists of the current unique local and remote
			 *	attachments.  These lists will be used to transmit the proper
			 *	channel expel and channel disband indications.
			 */
			BuildAttachmentLists (&m_AuthorizedUserList, &local_attachment_list,
					&remote_attachment_list);

			/*
			 *	It is also necessary to send the disband indication to the
			 *	channel manager, if it is valid and in the sub-tree of this
			 *	provider.  Determine what attachment leads to the channel
			 *	manager, and make sure that attachment is in the remote
			 *	attachment list, if valid.
			 */
			if (ValidateUserID(m_uidChannelManager))
			{
				PChannel		lpChannel;
				if (NULL != (lpChannel = m_pChannelList2->Find(m_uidChannelManager)))
                {
				    pAtt = lpChannel->GetAttachment();
				    if (m_pAttachmentList->Find(pAtt) && pAtt->IsConnAttachment())
				    {
					    if (remote_attachment_list.Find(pAtt) == FALSE)
					    {
						    remote_attachment_list.Append(pAtt);
						}
				    }
                }
                else
                {
                    ERROR_OUT(("PrivateChannel::ChannelDisbandRequest: can't locate channel"));
                }
			}

			/*
			 *	Loop through the local attachment list sending channel expel
			 *	indications to each attachment contained therein.
			 */
			local_attachment_list.Reset();
			while (NULL != (pAtt = local_attachment_list.Iterate()))
			{
				/*
				 *	Get the next attachment from the list and build a list of
				 *	the users that lie in the direction of that attachment.
				 */
				BuildUserIDList(&m_AuthorizedUserList, pAtt, &user_id_list);

				/*
				 *	Send the expel indication to the locally attached user.
				 */
				pAtt->ChannelExpelIndication(Channel_ID, &user_id_list);
			}

			/*
			 *	Loop through the remote attachment list sending channel disband
			 *	indications to each attachment contained therein.
			 */
			remote_attachment_list.Reset();
			while (NULL != (pAtt = remote_attachment_list.Iterate()))
			{
				/*
				 *	Send the disband indication to the remotely attached
				 *	provider.
				 */
				pAtt->ChannelDisbandIndication(Channel_ID);
			}

			/*
			 *	Set m_uidChannelManager to 0 and clear the authorized user list as
			 *	an indicator that this private channel object is no longer
			 *	valid, and cannot be used.  The next time the domain object
			 *	calls IsValid, it will return FALSE allowing the domain object
			 *	to delete this object.
			 */
			m_uidChannelManager = 0;
			m_AuthorizedUserList.Clear();
		}
		else
		{
			/*
			 *	Set a flag indicating that a disband request has been sent
			 *	upward.  This flag will be used to prevent a disband indication
			 *	from being sent to the channel manager as it flows back down
			 *	the domain tree.
			 */
			m_fDisbandRequestPending = TRUE;

			/*
			 *	This is not the Top Provider, so forward the request toward
			 *	the Top Provider.  This will result in a channel disband
			 *	indication at a future time.
			 */
			TRACE_OUT (("PrivateChannel::ChannelDisbandRequest: "
					"forwarding request to Top Provider"));
			m_pConnToTopProvider->ChannelDisbandRequest(uidInitiator, Channel_ID);
		}
	}
	else
	{
		/*
		 *	Someone is trying to disband a private channel that they are not
		 *	the channel manager for.  Ignore the request.
		 */
		WARNING_OUT (("PrivateChannel::ChannelDisbandRequest: "
				"ignoring request from non-channel manager"));
	}
}

/*
 *	Void	ChannelDisbandIndication ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by the Top Provider when it decides
 *		to delete a private channel from the domain.  It travels downward to
 *		all attachments and connections that contain an admitted user or the
 *		channel manager in their sub-tree.
 */
Void	PrivateChannel::ChannelDisbandIndication (
				ChannelID)
{
	CAttachmentList         local_attachment_list;
	CAttachmentList         remote_attachment_list;
	CAttachment            *pAtt;
	CUidList				user_id_list;

	TRACE_OUT (("PrivateChannel::ChannelDisbandIndication: "
			"disbanding channel = %04X", Channel_ID));

	/*
	 *	Build the lists of unique local and remote attachments.  These lists
	 *	will be used to issue the appropriate indications.
	 */
	BuildAttachmentLists (&m_AuthorizedUserList, &local_attachment_list,
			&remote_attachment_list);

	/*
	 *	It is also necessary to send the disband indication to the channel
	 *	manager, if it is valid and in the sub-tree of this provider.
	 *	Determine what attachment leads to the channel manager, and make sure
	 *	that attachment is in the remote attachment list, if valid.
	 */
	if (ValidateUserID(m_uidChannelManager))
	{
		PChannel		lpChannel;
		if (NULL != (lpChannel = m_pChannelList2->Find(m_uidChannelManager)))
        {
		    pAtt = lpChannel->GetAttachment();
		    if ((m_fDisbandRequestPending == FALSE) ||
			    (m_pAttachmentList->Find(pAtt) && pAtt->IsConnAttachment()))
			{
			    if (remote_attachment_list.Find(pAtt) == FALSE)
			    {
				    remote_attachment_list.Append(pAtt);
				}
		    }
        }
        else
        {
            ERROR_OUT(("PrivateChannel::ChannelDisbandIndication: can't locate channel"));
        }
    }

	/*
	 *	Loop through the local attachment list sending channel expel indications
	 *	to each attachment contained therein.
	 */
	local_attachment_list.Reset();
	while (NULL != (pAtt = local_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of
		 *	the users that lie in the direction of that attachment.
		 */
		BuildUserIDList(&m_AuthorizedUserList, pAtt, &user_id_list);

		/*
		 *	Send the expel indication to the locally attached user.
		 */
		pAtt->ChannelExpelIndication(Channel_ID, &user_id_list);
	}

	/*
	 *	Loop through the remote attachment list sending channel disband
	 *	indications to each attachment contained therein.
	 */
	remote_attachment_list.Reset();
	while (NULL != (pAtt = remote_attachment_list.Iterate()))
	{
		/*
		 *	Send the disband indication to the remotely attached provider.
		 */
		pAtt->ChannelDisbandIndication(Channel_ID);
	}

	/*
	 *	Set m_uidChannelManager to 0 and clear the authorized user list as an
	 *	indicator that this private channel object is no longer valid, and
	 *	cannot be used.  The next time the domain object calls IsValid, it will
	 *	return FALSE allowing the domain object to delete this object.
	 */
	m_uidChannelManager = 0;
	m_AuthorizedUserList.Clear();
}

/*
 *	Void	ChannelAdmitRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by the manager of a private channel
 *		when it wishes to expand the authorized user list of that channel.  If
 *		this is the Top Provider, then the request can be handled locally.
 *		Otherwise, it must be forwarded upward to the Top Provider.
 */
Void	PrivateChannel::ChannelAdmitRequest (
				CAttachment *,
				UserID				uidInitiator,
				ChannelID,
				CUidList           *user_id_list)
{
	UserID					uid;
	CUidList				admitted_id_list;
	CUidList				user_id_subset;

	/*
	 *	Check to see if the requesting user is the channel manager.  Only
	 *	process the request if it is.
	 */
	if (uidInitiator == m_uidChannelManager)
	{
		/*
		 *	See if this is the Top Provider.  If it is, then the request can
		 *	be processed locally.  Otherwise, pass the request upward toward
		 *	the Top Provider.
		 */
		if (IsTopProvider())
		{
        	CAttachmentList     local_attachment_list;
        	CAttachmentList     remote_attachment_list;
        	CAttachment        *pAtt;

			TRACE_OUT (("PrivateChannel::ChannelAdmitRequest: "
					"admitting users to channel = %04X", Channel_ID));

			/*
			 *	Iterate through the list of users to be admitted, adding all
			 *	valid users to the local authorized user list.
			 */
			user_id_list->Reset();
			while (NULL != (uid = user_id_list->Iterate()))
			{
				/*
				 *	Make sure that the user ID corresponds to a valid user in
				 *	the domain.
				 */
				if (ValidateUserID(uid))
				{
					/*
					 *	If the user is not already in the authorized user list,
					 *	then add it.
					 */
					if (m_AuthorizedUserList.Find(uid) == FALSE)
					{
						m_AuthorizedUserList.Append(uid);
						admitted_id_list.Append(uid);
					}
				}
			}

			/*
			 *	Build lists of unique attachments which can then be used to
			 *	issue the appropriate admit indications.  This prevents the
			 *	transmission of an admit indication to the same attachment more
			 *	than once.
			 */
			BuildAttachmentLists (&admitted_id_list, &local_attachment_list,
					&remote_attachment_list);

			/*
			 *	Iterate through the local attachment list issuing an admit
			 *	indication to each attachment contained therein.
			 */
			local_attachment_list.Reset();
			while (NULL != (pAtt = local_attachment_list.Iterate()))
			{
				/*
				 *	Get the next attachment from the list and build a list of
				 *	the users that lie in the direction of that attachment.
				 */
				BuildUserIDList(&admitted_id_list, pAtt, &user_id_subset);

				/*
				 *	Send the admit indication to the named attachment.
				 */
				pAtt->ChannelAdmitIndication(uidInitiator, Channel_ID, &user_id_subset);
			}

			/*
			 *	Iterate through the remote attachment list issuing an admit
			 *	indication to each attachment contained therein.
			 */
			remote_attachment_list.Reset();
			while (NULL != (pAtt = remote_attachment_list.Iterate()))
			{
				/*
				 *	Get the next attachment from the list and build a list of
				 *	the users that lie in the direction of that attachment.
				 */
				BuildUserIDList(&admitted_id_list, pAtt, &user_id_subset);

				/*
				 *	Send the admit indication to the named attachment.
				 */
				pAtt->ChannelAdmitIndication(uidInitiator, Channel_ID, &user_id_subset);
			}
		}
		else
		{
			/*
			 *	This is not the Top Provider, so forward the request toward
			 *	the Top Provider.  This will result in a channel admit
			 *	indication at a future time.
			 */
			TRACE_OUT (("PrivateChannel::ChannelAdmitRequest: "
					"forwarding request to Top Provider"));
			m_pConnToTopProvider->ChannelAdmitRequest(uidInitiator, Channel_ID, user_id_list);
		}
	}
	else
	{
		/*
		 *	Someone is trying to admit users to a private channel that they are
		 *	not the channel manager for.  Ignore the request.
		 */
		WARNING_OUT (("PrivateChannel::ChannelAdmitRequest: "
				"ignoring request from non-channel manager"));
	}
}

/*
 *	Void	ChannelAdmitIndication ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by the Top Provider when it receives
 *		a channel admit indication from the manager of a private channel.  This
 *		indication is broadcast downward to all providers that contain an
 *		admitted user somewhere in their sub-tree.  A side-effect of this
 *		indication is that a private channel will be created in the information
 *		base if one does not already exist.
 */
Void	PrivateChannel::ChannelAdmitIndication (
				PConnection,
				UserID				uidInitiator,
				ChannelID,
				CUidList           *user_id_list)
{
	UserID					uid;
	CUidList				admitted_id_list;
	CAttachmentList         local_attachment_list;
	CAttachmentList         remote_attachment_list;
	CAttachment            *pAtt;
	CUidList				user_id_subset;

	TRACE_OUT (("PrivateChannel::ChannelAdmitIndication: "
			"admitting users to channel = %04X", (UINT) Channel_ID));

	/*
	 *	Iterate through the list of users to be admitted, adding all
	 *	valid users to the local authorized user list.
	 */
	user_id_list->Reset();
	while (NULL != (uid = user_id_list->Iterate()))
	{
		/*
		 *	Make sure that the user ID corresponds to a valid user in
		 *	the domain.
		 */
		if (ValidateUserID(uid))
		{
			/*
			 *	If the user is not already in the authorized user list,
			 *	then add it.
			 */
			if (m_AuthorizedUserList.Find(uid) == FALSE)
			{
				m_AuthorizedUserList.Append(uid);
				admitted_id_list.Append(uid);
			}
		}
	}

	/*
	 *	Build lists of unique attachments which can then be used to
	 *	issue the appropriate admit indications.  This prevents the
	 *	transmission of an admit indication to the same attachment more
	 *	than once.
	 */
	BuildAttachmentLists (&admitted_id_list, &local_attachment_list,
			&remote_attachment_list);

	/*
	 *	Iterate through the local attachment list issuing an admit
	 *	indication to each attachment contained therein.
	 */
	local_attachment_list.Reset();
	while (NULL != (pAtt = local_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of
		 *	the users that lie in the direction of that attachment.
		 */
		BuildUserIDList(&admitted_id_list, pAtt, &user_id_subset);

		/*
		 *	Send the admit indication to the named attachment.
		 */
		pAtt->ChannelAdmitIndication(uidInitiator, Channel_ID, &user_id_subset);
	}

	/*
	 *	Iterate through the remote attachment list issuing an admit
	 *	indication to each attachment contained therein.
	 */
	remote_attachment_list.Reset();
	while (NULL != (pAtt = remote_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of
		 *	the users that lie in the direction of that attachment.
		 */
		BuildUserIDList(&admitted_id_list, pAtt, &user_id_subset);

		/*
		 *	Send the admit indication to the named attachment.
		 */
		pAtt->ChannelAdmitIndication(uidInitiator, Channel_ID, &user_id_subset);
	}
}

/*
 *	Void	ChannelExpelRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by the manager of a private channel
 *		when it wishes to shrink the authorized user list of that channel.  If
 *		the channel is in the local information base, the request is sent to it.
 *		Otherwise, the request is ignored.
 */
Void	PrivateChannel::ChannelExpelRequest (
				CAttachment *,
				UserID				uidInitiator,
				ChannelID,
				CUidList           *user_id_list)
{
	UserID  				uid;
	CUidList				expelled_id_list;
	CUidList				user_id_subset;

	/*
	 *	Check to see if the requesting user is the channel manager.  Only
	 *	process the request if it is.
	 */
	if (uidInitiator == m_uidChannelManager)
	{
		/*
		 *	See if this is the Top Provider.  If it is, then the request can
		 *	be processed locally.  Otherwise, pass the request upward toward
		 *	the Top Provider.
		 */
		if (m_pConnToTopProvider == NULL)
		{
        	CAttachmentList         local_attachment_list;
        	CAttachmentList         remote_attachment_list;
        	CAttachment            *pAtt;

			TRACE_OUT (("PrivateChannel::ChannelExpelRequest: "
					"expelling users from channel = %04X", Channel_ID));

			/*
			 *	Iterate through the list of users to be expelled, removing all
			 *	valid users from the local authorized user list.
			 */
			user_id_list->Reset();
			while (NULL != (uid = user_id_list->Iterate()))
			{
				/*
				 *	If the user is in the authorized user list, then remove it.
				 */
				if (m_AuthorizedUserList.Find(uid))
				{
					m_AuthorizedUserList.Remove(uid);
					expelled_id_list.Append(uid);
				}
			}

			/*
			 *	Build lists of unique attachments which can then be used to
			 *	issue the appropriate expel indications.  This prevents the
			 *	transmission of an expel indication to the same attachment more
			 *	than once.
			 */
			BuildAttachmentLists (&expelled_id_list, &local_attachment_list,
					&remote_attachment_list);

			/*
			 *	Iterate through the local attachment list issuing an expel
			 *	indication to each attachment contained therein.
			 */
			local_attachment_list.Reset();
			while (NULL != (pAtt = local_attachment_list.Iterate()))
			{
				/*
				 *	Get the next attachment from the list and build a list of
				 *	the users that lie in the direction of that attachment.
				 */
				BuildUserIDList(&expelled_id_list, pAtt, &user_id_subset);

				/*
				 *	Send the expel indication to the named attachment.
				 */
				pAtt->ChannelExpelIndication(Channel_ID, &user_id_subset);

				/*
				 *	Since this is a locally attached user, it is necessary to
				 *	simulate a channel leave request from the user, indicating
				 *	the fact that it can no longer use the channel.
				 */
				ChannelLeaveRequest(pAtt, (CChannelIDList *) &user_id_subset);
			}

			/*
			 *	Iterate through the remote attachment list issuing an expel
			 *	indication to each attachment contained therein.
			 */
			remote_attachment_list.Reset();
			while (NULL != (pAtt = remote_attachment_list.Iterate()))
			{
				/*
				 *	Get the next attachment from the list and build a list of
				 *	the users that lie in the direction of that attachment.
				 */
				BuildUserIDList(&expelled_id_list, pAtt, &user_id_subset);

				/*
				 *	Send the expel indication to the named attachment.
				 */
				pAtt->ChannelExpelIndication(Channel_ID, &user_id_subset);
			}
		}
		else
		{
			/*
			 *	This is not the Top Provider, so forward the request toward
			 *	the Top Provider.  This will result in a channel expel
			 *	indication at a future time.
			 */
			TRACE_OUT (("PrivateChannel::ChannelExpelRequest: "
					"forwarding request to Top Provider"));
			m_pConnToTopProvider->ChannelExpelRequest(uidInitiator, Channel_ID, user_id_list);
		}
	}
	else
	{
		/*
		 *	Someone is trying to admit users to a private channel that they are
		 *	not the channel manager for.  Ignore the request.
		 */
		WARNING_OUT (("PrivateChannel::ChannelExpelRequest: "
				"ignoring request from non-channel manager"));
	}
}

/*
 *	Void	ChannelExpelIndication ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by the Top Provider when it receives
 *		a request from the manager of a private channel to reduce the
 *		authorized user list.  It travels downward to all attachments and
 *		connections that contain an admitted user or the channel manager in
 *		their sub-tree.
 */
Void	PrivateChannel::ChannelExpelIndication (
				PConnection,
				ChannelID,
				CUidList           *user_id_list)
{
	UserID					uid;
	CUidList				expelled_id_list;
	CAttachmentList         local_attachment_list;
	CAttachmentList         remote_attachment_list;
	CAttachment            *pAtt;
	CUidList				user_id_subset;

	TRACE_OUT (("PrivateChannel::ChannelExpelIndication: "
			"expelling users from channel = %04X", Channel_ID));

	/*
	 *	Iterate through the list of users to be expelled, removing all
	 *	valid users from the local authorized user list.
	 */
	user_id_list->Reset();
	while (NULL != (uid = user_id_list->Iterate()))
	{
		/*
		 *	If the user is in the authorized user list, then remove it.
		 */
		if (m_AuthorizedUserList.Find(uid))
		{
			m_AuthorizedUserList.Remove(uid);
			expelled_id_list.Append(uid);
		}
	}

	/*
	 *	Build lists of unique attachments which can then be used to
	 *	issue the appropriate expel indications.  This prevents the
	 *	transmission of an expel indication to the same attachment more
	 *	than once.
	 */
	BuildAttachmentLists (&expelled_id_list, &local_attachment_list,
			&remote_attachment_list);

	/*
	 *	Iterate through the local attachment list issuing an expel
	 *	indication to each attachment contained therein.
	 */
	local_attachment_list.Reset();
	while (NULL != (pAtt = local_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of
		 *	the users that lie in the direction of that attachment.
		 */
		BuildUserIDList(&expelled_id_list, pAtt, &user_id_subset);

		/*
		 *	Send the expel indication to the named attachment.
		 */
		pAtt->ChannelExpelIndication(Channel_ID, &user_id_subset);

		/*
		 *	Since this is a locally attached user, it is necessary to
		 *	simulate a channel leave request from the user, indicating
		 *	the fact that it can no longer use the channel.
		 */
		ChannelLeaveRequest(pAtt, (CChannelIDList *) &user_id_subset);
	}

	/*
	 *	Iterate through the remote attachment list issuing an expel
	 *	indication to each attachment contained therein.
	 */
	remote_attachment_list.Reset();
	while (NULL != (pAtt = remote_attachment_list.Iterate()))
	{
		/*
		 *	Get the next attachment from the list and build a list of
		 *	the users that lie in the direction of that attachment.
		 */
		BuildUserIDList(&expelled_id_list, pAtt, &user_id_subset);

		/*
		 *	Send the expel indication to the named attachment.
		 */
		pAtt->ChannelExpelIndication(Channel_ID, &user_id_subset);
	}
}

/*
 *	Void	SendDataRequest ()
 *
 *	Public
 *
 *	Functional Description:
 *		This MCS command is initially sent by a user that wishes to send data
 *		to other users who are joined to a specified channel.  This routine
 *		is executed in the case that it is a private channel.  It verifies
 *		that the user is authorized to use the channel before allowing the data
 *		to be sent.
 */
Void	PrivateChannel::SendDataRequest (
				CAttachment        *pOrigAtt,
				UINT				type,
				PDataPacket			data_packet)
{
	UserID  uidInitiator;

	uidInitiator = data_packet->GetInitiator();
	if ((uidInitiator == m_uidChannelManager) || m_AuthorizedUserList.Find(uidInitiator))
	{
		/*
		 *	The channel usage is authorized, so forward the request to the
		 *	base class implementation for processing.
		 */
		Channel::SendDataRequest(pOrigAtt, type, data_packet);
	}
	else
	{
		/*
		 *	Someone is trying to send data on a private channel that they are
		 *	not authorized to use.  Ignore the request.
		 */
		WARNING_OUT (("PrivateChannel::SendDataRequest: "
				"ignoring request from non-authorized user"));
	}
}

/*
 *	BOOL    ValidateUserID ()
 *
 *	Private
 *
 *	Functional Description:
 *		This function is called whenever another member function of this class
 *		wants to check and see if a specified user is still valid in the
 *		domain channel list.
 *
 *	Formal Parameters:
 *		user_id (i)
 *			This is the ID of the user being checked out.
 *
 *	Return Value:
 *		TRUE if the user is valid.  FALSE otherwise.
 *
 *	Side Effects:
 *		None.
 */
BOOL    PrivateChannel::ValidateUserID (
					UserID			user_id)
{
	PChannel	channel;

	/*
	 *	First check to see if the user ID is in the channel list at all.  This
	 *	prevents an attempt to read an invalid entry from the dictionary.
	 */
	if (NULL != (channel = m_pChannelList2->Find(user_id)))
	{
		/*
		 *	We know that the ID is in the dictionary, but we don't know for sure
		 *	whether or not it is a user ID channel.  So check this.  If it is a
		 *	user channel, then set the valid flag to TRUE.
		 */
		if (channel->GetChannelType () == USER_CHANNEL)
			return TRUE;
	}

	return FALSE;
}

/*
 *	Void	BuildAttachmentLists ()
 *
 *	Private
 *
 *	Functional Description:
 *		This function is called upon to build a list of unique attachments that
 *		lead to the users in the specified list.  It builds two attachment
 *		lists.  The first has an entry for each unique local attachment.  The
 *		second for each remote attachment.  The key to each list is the
 *		attachment.
 *
 *	Formal Parameters:
 *		user_id_list (i)
 *			This is the list of users for which the list is to be built.
 *		local_attachment_list (i)
 *			This is the dictionary that is to contain the list of unique
 *			local attachments.
 *		remote_attachment_list (i)
 *			This is the dictionary that is to contain the list of unique
 *			remote attachments.
 *
 *	Return Value:
 *		None.
 *
 *	Side Effects:
 *		None.
 */
Void	PrivateChannel::BuildAttachmentLists (
				CUidList                *user_id_list,
				CAttachmentList         *local_attachment_list,
				CAttachmentList         *remote_attachment_list)
{
	UserID				uid;

	/*
	 *	Loop through the passed in user ID list building a dictionary of local
	 *	attachments (those leading to locally attached users) and a dictionary
	 *	of remote attachments (those leading to remotely connected providers).
	 *	These dictionaries will be used by this provider to issue various
	 *	indications downward, without sending multiple indications to the same
	 *	attachment.
	 */
	user_id_list->Reset();
	while (NULL != (uid = user_id_list->Iterate()))
	{
		/*
		 *	Check to see if the user ID refers to a valid user in the sub-tree
		 *	of this provider.
		 */
		if (ValidateUserID(uid))
		{
			PChannel		lpChannel;
			/*
			 *	Determine which attachment leads to the user in question.
			 */
			if (NULL != (lpChannel = m_pChannelList2->Find(uid)))
            {
			    CAttachment *pAtt = lpChannel->GetAttachment();
			    /*
			     *	This module builds separate lists for those users that are
			     *	attached locally and those attached remotely.
			     */
                if (m_pAttachmentList->Find(pAtt))
                {
			        if (pAtt->IsUserAttachment())
			        {
				        /*
				         *	This attachment is a local one (meaning that it leads to a
				         *	locally attached user, rather than another MCS provider).
				         *	Check to see if this attachment has already been put into
				         *	the dictionary while processing a previous user ID.
				         */
				        if (local_attachment_list->Find(pAtt) == FALSE)
					        local_attachment_list->Append(pAtt);
			        }
			        else
			        {
				        /*
				         *	This attachment is a remote one (meaning that it leads to
				         *	another MCS provider, rather than a locally attached user).
				         *	Check to see if this attachment has already been put into
				         *	the dictionary while processing a previous user ID.
				         */
				        if (remote_attachment_list->Find(pAtt) == FALSE)
					        remote_attachment_list->Append(pAtt);
			        }
                }
                else
                {
                    ERROR_OUT(("PrivateChannel::BuildAttachmentLists: can't find this attachment=0x%p", pAtt));
                }
            }
            else
            {
                ERROR_OUT(("PrivateChannel::BuildAttachmentLists: can't locate channel"));
            }
        }
		else
		{
			/*
			 *	This user ID does not correspond to a valid user in the sub-tree
			 *	of this provider.  Therefore, discard the ID.
			 */
			ERROR_OUT (("PrivateChannel::BuildAttachmentLists: "
					"ERROR - user ID not valid"));
		}
	}
}

/*
 *	Void	BuildUserIDList ()
 *
 *	Private
 *
 *	Functional Description:
 *		This function is called upon to build a list of all users in the
 *		specified list that are in the direction of the specified attachment.
 *
 *	Formal Parameters:
 *		user_id_list (i)
 *			This is the list of users for which the list is to be built.
 *		attachment (i)
 *			This is the attachment that the caller wishes to have a list of
 *			user IDs for.
 *		user_id_subset (o)
 *			This is the subset of the passed in user IDs that are in the
 *			direction of the specified attachment.
 *
 *	Return Value:
 *		None.
 *
 *	Side Effects:
 *		None.
 */
Void	PrivateChannel::BuildUserIDList (
				CUidList               *user_id_list,
				CAttachment            *pAtt,
				CUidList               *user_id_subset)
{
	UserID				uid;

	/*
	 *	Clear out the subset list, so that we start fresh.
	 */
	user_id_subset->Clear();

	/*
	 *	Loop through the specified user list, checking to see which users
	 *	lie in the direction of the specified attachment.
	 */
	user_id_list->Reset();
	while (NULL != (uid = user_id_list->Iterate()))
	{
		/*
		 *	Check to see if the user ID refers to a valid user in the sub-tree
		 *	of this provider.
		 */
		if (ValidateUserID(uid))
		{
			PChannel	lpChannel;
			/*
			 *	Check to see if this user is the direction of the specified
			 *	attachment.  If it is, then put it into the user ID subset that
			 *	we are building.
			 */
			if (NULL != (lpChannel = m_pChannelList2->Find(uid)))
            {
			    if (lpChannel->GetAttachment () == pAtt)
				    user_id_subset->Append(uid);
            }
            else
            {
                ERROR_OUT(("PrivateChannel::BuildUserIDList: can't locate channel"));
            }
		}
		else
		{
			/*
			 *	This user ID does not correspond to a valid user in the sub-tree
			 *	of this provider.  Therefore, discard the ID.
			 */
			ERROR_OUT (("PrivateChannel::BuildUserIDList: "
					"ERROR - user ID not valid"));
		}
	}
}