Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1849 lines
40 KiB

// File: call.cpp
#include "precomp.h"
#include "resource.h"
#include "call.h"
#include "dlgcall.h"
#include "confapi.h"
#include "popupmsg.h"
#include "passdlg.h"
#include "chcondlg.h"
#include "dshowdlg.h"
#include "conf.h"
#include "calllog.h"
#include "rostinfo.h"
#include "..\..\core\cncodes.h" // for CN_* codes
#include <inodecnt.h> // for UI_RC_...
#include "cr.h" // for CreateConfRoomWindow, UpdateUI
#include "confroom.h"
#include "confman.h"
#include "NmLdap.h"
#include "nmremote.h"
#include <tsecctrl.h>
#include "ConfPolicies.h"
#include "StatBar.h"
#include "certui.h"
#include "cmd.h"
#include "callto.h"
#include "dlgacd.h"
// External SDK stuff...
#include "NmCall.h"
#include "NmApp.h"
COBLIST * g_pCallList = NULL; // Global list of calls in progress
extern INmSysInfo2 * g_pNmSysInfo;
static HRESULT OnUIRemotePassword(BSTR bstrConference, BSTR *pbstrPassword, LPCTSTR pCertText, BOOL fIsService);
extern BOOL FRejectIncomingCalls(void);
extern BOOL FIsConfRoomClosing(void);
extern GUID g_csguidSecurity;
extern GUID g_csguidMeetingSettings;
/* C C A L L */
/*-------------------------------------------------------------------------
%%Function: CCall
-------------------------------------------------------------------------*/
CCall::CCall(LPCTSTR pszCallTo, LPCTSTR pszDisplayName, NM_ADDR_TYPE nmAddrType, BOOL bAddToMru, BOOL fIncoming) :
RefCount(NULL),
m_fIncoming (fIncoming),
m_pszDisplayName (PszAlloc(pszDisplayName)),
m_pszCallTo (PszAlloc(pszCallTo)),
m_nmAddrType (nmAddrType),
m_bAddToMru (bAddToMru),
m_fSelectedConference (FALSE),
m_pDlgCall (NULL),
m_pInternalICall(NULL),
m_pos (NULL),
m_dwCookie (0),
m_ppm (NULL),
m_fInRespond (FALSE)
{
DbgMsg(iZONE_OBJECTS, "Obj: %08X created CCall", this);
if (FEmptySz(m_pszDisplayName))
{
delete m_pszDisplayName;
// Default to "another person" if no name available in the call data
m_pszDisplayName = PszLoadString(IDS_UNKNOWN_PERSON);
}
m_dwTick = ::GetTickCount();
DbgMsgCall("CCall: %08X Created Name=[%s] CallTo=[%s]",
this, m_pszDisplayName, m_pszCallTo ? m_pszCallTo : _TEXT("<NULL>"));
// add it to the global call list
if (NULL == g_pCallList)
{
g_pCallList = new COBLIST;
if (NULL == g_pCallList)
{
ERROR_OUT(("CCall::CCall - unable to allocate g_pCallList"));
return;
}
}
m_pos = g_pCallList->AddTail(this);
}
CCall::~CCall()
{
DBGENTRY(CCall::~CCall);
RemoveFromList();
delete m_pszDisplayName;
delete m_pszCallTo;
if(m_pInternalICall)
{
m_pInternalICall->Release();
m_pInternalICall = NULL;
}
DbgMsg(iZONE_OBJECTS, "Obj: %08X destroyed CCall", this);
DBGEXIT(CCall::~CCall);
}
/* S E T N M C A L L */
/*-------------------------------------------------------------------------
%%Function: SetNmCall
-------------------------------------------------------------------------*/
VOID CCall::SetNmCall(INmCall * pCall)
{
ASSERT(NULL != pCall);
ASSERT((!m_pInternalICall) || (m_pInternalICall == pCall));
if(!m_pInternalICall)
{
pCall->AddRef();
m_pInternalICall = pCall;
}
if(!m_dwCookie)
{
NmAdvise(m_pInternalICall, this, IID_INmCallNotify2, &m_dwCookie);
}
Update();
}
BOOL CCall::RemoveFromList(void)
{
// Remove the call from the global list
if (NULL == m_pos)
return FALSE;
ASSERT(NULL != g_pCallList);
CCall * pCall = (CCall *) g_pCallList->RemoveAt(m_pos);
ASSERT(this == pCall);
m_pos = NULL;
if(m_pInternalICall)
{
NmUnadvise(m_pInternalICall, IID_INmCallNotify2, m_dwCookie);
m_dwCookie = NULL;
}
return TRUE;
}
VOID CCall::Cancel(BOOL fDisplayCancelMsg)
{
if (!FComplete())
{
if (fDisplayCancelMsg & !FIncoming())
{
DisplayPopup(); // Inform the user with a small popup message
}
if (m_pInternalICall)
{
m_pInternalICall->Cancel();
}
Update();
}
}
///////////////////////////////////////////////////////////////////////////
// IUnknown methods
STDMETHODIMP_(ULONG) CCall::AddRef(void)
{
return RefCount::AddRef();
}
STDMETHODIMP_(ULONG) CCall::Release(void)
{
return RefCount::Release();
}
STDMETHODIMP CCall::QueryInterface(REFIID riid, PVOID *ppv)
{
HRESULT hr = S_OK;
if ((riid == IID_INmCallNotify2) || (riid == IID_INmCallNotify) || (riid == IID_IUnknown))
{
*ppv = (INmCallNotify2 *)this;
ApiDebugMsg(("CCall::QueryInterface()"));
}
else
{
hr = E_NOINTERFACE;
*ppv = NULL;
ApiDebugMsg(("CCall::QueryInterface(): Called on unknown interface."));
}
if (S_OK == hr)
{
AddRef();
}
return hr;
}
///////////////////////////////////////////////////////////////////////////
// INmCallNotify methods
STDMETHODIMP CCall::NmUI(CONFN uNotify)
{
return S_OK;
}
STDMETHODIMP CCall::StateChanged(NM_CALL_STATE uState)
{
Update();
return S_OK;
}
STDMETHODIMP CCall::Failed(ULONG uError)
{
DbgMsgCall("CCall: %08X Failed uError=%d", this, uError);
return S_OK;
}
STDMETHODIMP CCall::Accepted(INmConference *pConference)
{
DbgMsgCall("CCall: %08X Accepted pConference=0x%08X", this, pConference);
return S_OK;
}
VOID CCall::ShowProgress(BOOL fShow)
{
if (NULL == m_pDlgCall)
return;
ShowWindow(m_pDlgCall->GetHwnd(), fShow ? SW_SHOWNORMAL : SW_HIDE);
}
VOID CCall::RemoveProgress(void)
{
if (NULL == m_pDlgCall)
return;
m_pDlgCall->Destroy();
m_pDlgCall->Release();
m_pDlgCall = NULL;
}
/* C A L L E R R O R */
/*-------------------------------------------------------------------------
%%Function: CallError
-------------------------------------------------------------------------*/
STDMETHODIMP CCall::CallError(UINT cns)
{
UINT ids = 0;
ShowProgress(FALSE);
ASSERT(m_pInternalICall != NULL);
DbgMsgCall("CCall: %08X CallError cns=%08X", this, cns);
// Translate cns to normal error message
switch (cns)
{
case CN_RC_NAME_RESOLUTION_FAILED:
ids = IDS_RESOLVE_FAILED;
break;
case CN_RC_CONNECT_FAILED:
case CN_RC_AUDIO_CONNECT_FAILED:
ids = IDS_COULD_NOT_CONNECT;
break;
case CN_RC_CONNECT_REMOTE_NO_SECURITY:
ids = IDS_CONNECT_REMOTE_NO_SECURITY;
break;
case CN_RC_CONNECT_REMOTE_DOWNLEVEL_SECURITY:
ids = IDS_CONNECT_REMOTE_DOWNLEVEL_SECURITY;
break;
case CN_RC_CONNECT_AUTHENTICATION_FAILED:
ids = IDS_CONNECT_AUTHENTICATION_FAILED;
break;
case CN_RC_SECURITY_FAILED:
ids = IDS_CONNECT_SECURITY_FAILED;
break;
case CN_RC_CONNECT_REMOTE_REQUIRE_SECURITY:
ids = IDS_CONNECT_REMOTE_REQUIRE_SECURITY;
break;
case CN_RC_CONFERENCE_JOIN_DENIED:
ids = IDS_JOIN_DENIED;
break;
case CN_RC_CONFERENCE_INVITE_DENIED:
ids = IDS_INVITE_DENIED;
break;
case CN_RC_INVITE_DENIED_REMOTE_IN_CONF:
ids = IDS_INVITE_DENIED_REMOTE_CONF;
break;
case CN_RC_CONFERENCE_DOES_NOT_EXIST:
ids = m_fSelectedConference ?
IDS_CONFERENCE_DOES_NOT_EXIST : IDS_CONFERENCE_ENDED_BEFORE_JOIN;
break;
case CN_RC_CONFERENCE_ENDED_BEFORE_JOIN:
// REVIEW: This is no longer sent?
ids = IDS_CONFERENCE_ENDED_BEFORE_JOIN;
break;
case CN_RC_AUDIO_NOT_AVAILABLE:
ids = IDS_AUDIO_NOT_AVAILABLE;
break;
case CN_RC_AUDIO_FAILED_AFTER_DATA:
ids = IDS_AUDIO_FAILED_AFTER_DATA;
break;
case CN_RC_AUDIO_IN_USE_REMOTE_AFTER_DATA:
ids = IDS_AUDIO_IN_USE_REMOTE_AFTER_DATA;
break;
case CN_RC_AUDIO_IN_USE_REMOTE:
ids = IDS_AUDIO_IN_USE_REMOTE;
break;
case CN_RC_AUDIO_IN_USE_LOCAL_AFTER_DATA:
ids = IDS_AUDIO_IN_USE_LOCAL_AFTER_DATA;
break;
case CN_RC_AUDIO_IN_USE_LOCAL:
ids = IDS_AUDIO_IN_USE_LOCAL;
break;
case CN_RC_CANT_INVITE_MCU:
ids = IDS_CANT_INVITE_MCU;
break;
case CN_RC_REMOTE_PLACING_CALL:
ids = IDS_REMOTE_PLACING_CALL;
break;
case CN_RC_TRANSPORT_FAILURE:
ids = IDS_TRANSPORT_UNAVAILABLE;
break;
case CN_RC_CANT_JOIN_ALREADY_IN_CALL:
ids = IDS_INCALL_JOIN_FAILED;
break;
case CN_RC_CONFERENCE_ENDED_BEFORE_ACCEPTED:
ids = IDS_INVITE_CONF_ENDED;
break;
case CN_RC_GK_CALLEE_NOT_REGISTERED:
ids = IDS_GK_CALLEE_NOT_REGISTERED;
break;
case CN_RC_GK_TIMEOUT:
ids = IDS_GK_TIMEOUT;
break;
case CN_RC_GK_REJECTED:
ids = IDS_GK_REJECTED;
break;
case CN_RC_GK_NOT_REGISTERED:
ids = IDS_GK_NOT_REGISTERED;
break;
default:
return S_FALSE;
} /* switch (cns) */
DisplayMsgIdsParam(ids, m_pszDisplayName);
return S_OK;
}
STDMETHODIMP CCall::RemoteConference(BOOL fMCU, BSTR *pwszConfNames, BSTR *pbstrConfToJoin)
{
return OnUIRemoteConference(fMCU, (PWSTR *)pwszConfNames, pbstrConfToJoin);
}
STDMETHODIMP CCall::RemotePassword(BSTR bstrConference, BSTR *pbstrPassword, PBYTE pb, DWORD cb, BOOL fIsService)
{
TCHAR* pLastCertText = NULL;
if (NULL != pb) {
ASSERT(cb > 0);
if (!(pLastCertText = FormatCert(pb, cb)))
{
ERROR_OUT(("FormatCert failed"));
}
}
ShowProgress(FALSE);
HRESULT hr = OnUIRemotePassword(bstrConference, pbstrPassword, pLastCertText, fIsService);
if (pLastCertText) delete pLastCertText;
return hr;
}
NM_CALL_STATE CCall::GetState()
{
NM_CALL_STATE callState = NM_CALL_INVALID;
if(m_pInternalICall)
{
m_pInternalICall->GetState(&callState);
}
return callState;
}
/* U P D A T E */
/*-------------------------------------------------------------------------
%%Function: Update
Update the cached information about the call
-------------------------------------------------------------------------*/
VOID CCall::Update(void)
{
DBGENTRY(CCall::Update);
NM_CALL_STATE callState = GetState();;
switch (callState)
{
case NM_CALL_CANCELED:
case NM_CALL_ACCEPTED:
case NM_CALL_REJECTED:
// Remove the call from the global list because we'll never get
// any more notifications for this.
if (RemoveFromList())
{
if (FIncoming())
{
if ((NM_CALL_CANCELED == callState) && (NULL != m_ppm))
{
//
// if m_fInRespond is set then we've already
// dismissed the dialog and the call is being
// cancelled because we discovered the underlying
// connection is gone.
//
if ( !m_fInRespond )
{
delete m_ppm;
m_ppm = NULL;
// Release the lock added by OnRing
Release();
}
}
LogCall(NM_CALL_ACCEPTED == callState);
}
else
{
RemoveProgress();
if (NM_CALL_ACCEPTED == callState)
{
if(m_bAddToMru)
{
CAcdMru CallList;
CallList.AddEntry(m_pszDisplayName, m_pszCallTo, m_nmAddrType);
CallList.Save();
}
}
}
// Release the initial lock on this object
Release();
}
break;
case NM_CALL_RING:
OnRing();
break;
case NM_CALL_SEARCH:
ASSERT(NULL == m_pDlgCall);
m_pDlgCall = new CDlgCall(this);
break;
case NM_CALL_WAIT:
if (NULL != m_pDlgCall)
{
m_pDlgCall->OnStateChange();
}
break;
default:
ERROR_OUT(("CCall::Update: Unknown state %08X", callState));
case NM_CALL_INVALID:
case NM_CALL_INIT:
break;
}
::UpdateUI(CRUI_CALLANIM | CRUI_TOOLBAR | CRUI_STATUSBAR);
DBGEXIT(CCall::Update);
}
/* F C O M P L E T E */
/*-------------------------------------------------------------------------
%%Function: FComplete
Return TRUE if the call has completed
-------------------------------------------------------------------------*/
BOOL CCall::FComplete(void)
{
switch (GetState())
{
case NM_CALL_ACCEPTED:
case NM_CALL_REJECTED:
case NM_CALL_CANCELED:
return TRUE;
case NM_CALL_INVALID:
case NM_CALL_INIT:
case NM_CALL_RING:
case NM_CALL_SEARCH:
case NM_CALL_WAIT:
default:
return FALSE;
}
}
/* P O P U P M S G R I N G I N G C A L L B A C K */
/*-------------------------------------------------------------------------
%%Function: CCall::PopupMsgRingingCallback
-------------------------------------------------------------------------*/
VOID CALLBACK CCall::PopupMsgRingingCallback(LPVOID pContext, DWORD dwFlags)
{
CCall *pCall = (CCall *) pContext;
ASSERT(NULL != pCall);
DbgMsgCall("CCall: %08X Responding from invite popup - result is 0x%08X", pCall, dwFlags);
DWORD dwCLEF;
if(!( PMF_KILLED & dwFlags ))
{
dwCLEF = (PMF_OK & dwFlags) ? CLEF_ACCEPTED : CLEF_REJECTED;
if (PMF_TIMEOUT & dwFlags)
{
dwCLEF |= CLEF_TIMED_OUT;
}
pCall->RespondToRinging(dwCLEF);
}
if(pCall->m_ppm)
{
// pop up message will be destroyed after callback returns
pCall->m_ppm = NULL;
// Release the lock added by OnRing
pCall->Release();
}
}
/* O N R I N G */
/*-------------------------------------------------------------------------
%%Function: OnRing
Handling an incoming call that just started to "ring".
-------------------------------------------------------------------------*/
VOID CCall::OnRing(void)
{
DbgMsgCall("CCall: %08X OnRing", this);
if (FRejectIncomingCalls())
{
// Respond negatively
WARNING_OUT(("Rejecting invite - not listening or sys pol disabled"));
RespondToRinging(CLEF_REJECTED);
return;
}
if( ConfPolicies::IsAutoAcceptCallsEnabled() && !_Module.InitControlMode())
{
// Respond with success
RespondToRinging(CLEF_ACCEPTED | CLEF_AUTO_ACCEPTED);
return;
}
if(!_Module.InitControlMode())
{
// Display a message for the user
TCHAR szFormatBuf[MAX_PATH];
TCHAR szMsgBuf[MAX_PATH];
if (FLoadString(IDS_INVITE_PERMISSION, szFormatBuf, CCHMAX(szFormatBuf)))
{
TCHAR szName[MAX_PATH];
LPTSTR psz = m_pszDisplayName;
if (FEmptySz(psz))
{
// The name string is blank, so fill it in with a default:
::LoadString(::GetInstanceHandle(), IDS_UNKNOWN_PERSON,
szName, CCHMAX(szName));
psz = szName;
}
wsprintf(szMsgBuf, szFormatBuf, psz);
}
ASSERT(NULL == m_ppm);
m_ppm = new CPopupMsg(PopupMsgRingingCallback, this);
if (NULL != m_ppm)
{
RegEntry re(UI_KEY, HKEY_CURRENT_USER);
UINT uTime = re.GetNumber(REGVAL_RING_TIMEOUT, DEFAULT_RING_TIMEOUT) * 1000;
AddRef(); // Released in PopupMsgRingingCallback
m_ppm->CreateDlg(szMsgBuf, TRUE, MAKEINTRESOURCE(IDI_CONFROOM),
::GetInstanceHandle(), IDS_INVITE_SOUND, uTime);
}
}
}
/* R E S P O N D T O R I N G I N G */
/*-------------------------------------------------------------------------
%%Function: RespondToRinging
-------------------------------------------------------------------------*/
BOOL CCall::RespondToRinging(DWORD dwCLEF)
{
BOOL fAccept = FALSE;
m_fInRespond = TRUE;
if (NM_CALL_RING == GetState())
{
if (!FIsConfRoomClosing() && (CLEF_ACCEPTED & dwCLEF))
{
fAccept = TRUE;
if(_Module.IsUIActive())
{
CConfRoom * pcr = ::GetConfRoom();
ASSERT(pcr);
pcr->BringToFront();
}
}
CNmCallObj::StateChanged(m_pInternalICall, fAccept ? NM_CALL_ACCEPTED : NM_CALL_REJECTED);
if (fAccept)
{
m_pInternalICall->Accept();
}
else
{
m_pInternalICall->Reject();
}
}
else
{
CallError(CN_RC_CONFERENCE_ENDED_BEFORE_ACCEPTED);
}
m_fInRespond = FALSE;
return fAccept;
}
/* D I S P L A Y P O P U P */
/*-------------------------------------------------------------------------
%%Function: DisplayPopup
-------------------------------------------------------------------------*/
VOID CCall::DisplayPopup(void)
{
CPopupMsg* ppm = new CPopupMsg(NULL);
if (NULL == ppm)
return;
TCHAR szMsg[MAX_PATH*2];
if (FLoadString1(IDS_CALL_CANCELED_FORMAT, szMsg, m_pszDisplayName))
{
ppm->Create(szMsg, FALSE, MAKEINTRESOURCE(IDI_CONFROOM),
::GetInstanceHandle(), IDS_PERSON_LEFT_SOUND,
ROSTER_TIP_TIMEOUT);
}
else
{
delete ppm;
}
}
/* P L A C E C A L L */
/*-------------------------------------------------------------------------
%%Function: PlaceCall
Place an outgoing call.
-------------------------------------------------------------------------*/
HRESULT
CCall::PlaceCall
(
DWORD dwFlags,
NM_ADDR_TYPE addrType,
const TCHAR * const setupAddress,
const TCHAR * const destinationAddress,
const TCHAR * const alias,
const TCHAR * const url,
const TCHAR * const conference,
const TCHAR * const password,
const TCHAR * const userData
){
DBGENTRY(CCall::PlaceCall);
HRESULT hr = E_FAIL;
ASSERT(m_pInternalICall == NULL);
INmManager2 *pNmMgr = CConfMan::GetNmManager();
ASSERT (NULL != pNmMgr);
hr = pNmMgr->CallEx( &m_pInternalICall,
dwFlags,
addrType,
CComBSTR( GetPszName() ),
CComBSTR( setupAddress ),
CComBSTR( destinationAddress ),
CComBSTR( alias ),
CComBSTR( url ),
CComBSTR( conference ),
CComBSTR( password ),
CComBSTR( userData ) );
if(m_pInternalICall && (CRPCF_JOIN & dwFlags) )
{
SetSelectedConference();
}
// Force an update of the status bar, animation, etc.
::UpdateUI(CRUI_DEFAULT);
pNmMgr->Release();
TRACE_OUT(("CCall::PlaceCall(%s) result=%08X", m_pszCallTo? m_pszCallTo: g_szEmpty, hr));
DBGEXIT_HR(CCall::PlaceCall, hr);
return hr;
}
VOID CCall::LogCall(BOOL fAccepted)
{
LPCTSTR pcszName = GetPszName();
TCHAR szName[MAX_PATH];
if (FEmptySz(pcszName))
{
if (FLoadString(IDS_UNKNOWN_PERSON, szName, CCHMAX(szName)))
pcszName = szName;
}
LOGHDR logHdr;
CRosterInfo ri;
LPBYTE pb;
ULONG cb;
LPBYTE pbCert = NULL;
ULONG cbCert = 0;
if (SUCCEEDED(GetINmCall()->GetUserData(g_csguidRostInfo, &pb, &cb)))
{
ri.Load(pb);
}
GetINmCall()->GetUserData(g_csguidSecurity, &pbCert, &cbCert);
DWORD dwCLEF = fAccepted ? CLEF_ACCEPTED : CLEF_REJECTED;
if (ri.IsEmpty())
{
// No caller data - not NetMeeting
dwCLEF |= CLEF_NO_CALL;
}
if (pbCert)
{
ASSERT(cbCert);
dwCLEF |= CLEF_SECURE;
}
ZeroMemory(&logHdr, sizeof(LOGHDR));
logHdr.dwCLEF = dwCLEF;
if (NULL != ::GetIncomingCallLog() )
{
// Write the data to the log file
::GetIncomingCallLog()->AddCall(pcszName, &logHdr, &ri, pbCert, cbCert);
}
}
/* C R E A T E I N C O M I N G C A L L */
/*-------------------------------------------------------------------------
%%Function: CreateIncomingCall
Create an CCall object for the incoming call.
-------------------------------------------------------------------------*/
CCall * CreateIncomingCall(INmCall * pNmCall)
{
HRESULT hr;
BSTR bstr;
LPTSTR pszName = NULL;
LPTSTR pszAddr = NULL;
NM_ADDR_TYPE addrType = NM_ADDR_UNKNOWN;
ASSERT(NULL != pNmCall);
// Get the display name
hr = pNmCall->GetName(&bstr);
if (SUCCEEDED(hr))
{
hr = BSTR_to_LPTSTR(&pszName, bstr);
SysFreeString(bstr);
}
// Get the address and type
hr = pNmCall->GetAddr(&bstr, &addrType);
if (SUCCEEDED(hr))
{
hr = BSTR_to_LPTSTR(&pszAddr, bstr);
SysFreeString(bstr);
}
CCall * pCall = new CCall(pszAddr, pszName, NM_ADDR_CALLTO, FALSE, TRUE /* fIncoming */);
delete pszName;
delete pszAddr;
return pCall;
}
///////////////////////////////////////////////////////////////////////////
// Global Functions
/* C A L L F R O M N M C A L L */
/*-------------------------------------------------------------------------
%%Function: CallFromNmCall
-------------------------------------------------------------------------*/
CCall * CallFromNmCall(INmCall * pNmCall)
{
if (NULL == g_pCallList)
return NULL;
POSITION pos = g_pCallList->GetHeadPosition();
while (pos)
{
CCall * pCall = (CCall *) g_pCallList->GetNext(pos);
ASSERT(NULL != pCall);
if (pNmCall == pCall->GetINmCall())
{
return pCall;
}
}
// no matching call?
return NULL;
}
/* F I S C A L L I N P R O G R E S S */
/*-------------------------------------------------------------------------
%%Function: FIsCallInProgress
Return TRUE if there is an incoming or outgoing call in progress.
-------------------------------------------------------------------------*/
BOOL FIsCallInProgress(void)
{
if (NULL == g_pCallList)
return FALSE;
return !g_pCallList->IsEmpty();
}
/* G E T L A S T O U T G O I N G C A L L */
/*-------------------------------------------------------------------------
%%Function: GetLastOutgoingCall
-------------------------------------------------------------------------*/
CCall * GetLastOutgoingCall(void)
{
if (NULL == g_pCallList)
return NULL;
CCall * pCall = NULL;
POSITION pos = g_pCallList->GetHeadPosition();
while (pos)
{
CCall * pCallTemp = (CCall *) g_pCallList->GetNext(pos);
ASSERT(NULL != pCallTemp);
if (!pCallTemp->FIncoming())
{
pCall = pCallTemp;
}
}
return pCall;
}
/* G E T C A L L S T A T U S */
/*-------------------------------------------------------------------------
%%Function: GetCallStatus
Check the current call status and return a string for the status bar.
Return 0 if no call information is available
-------------------------------------------------------------------------*/
DWORD GetCallStatus(LPTSTR pszStatus, int cchMax, UINT * puID)
{
ASSERT(NULL != pszStatus);
ASSERT(NULL != puID);
ASSERT(cchMax > 0);
*pszStatus = _T('\0');
*puID = 0;
CCall *pCall = GetLastOutgoingCall();
if (NULL == pCall)
return 0; // not in a call
// Use the status info from the most recent connection attempt:
switch (pCall->GetState())
{
case NM_CALL_INIT:
{
*puID = IDS_STATUS_SETTING_UP;
break;
}
case NM_CALL_SEARCH:
{
*puID = IDS_STATUS_FINDING;
break;
}
case NM_CALL_WAIT:
{
*puID = IDS_STATUS_WAITING;
break;
}
default:
{
// unknown/useless call state
return 0;
}
} /* switch */
if (FEmptySz(pCall->GetPszName()))
{
return 0;
}
if (!FLoadString1(*puID, pszStatus, pCall->GetPszName()))
{
return 0;
}
return pCall->GetTickCount();
}
///////////////////////////////////////////////////////////////////////////
// TODO: Replace these with real connection points
/* O N U I C A L L C R E A T E D */
/*-------------------------------------------------------------------------
%%Function: OnUICallCreated
-------------------------------------------------------------------------*/
HRESULT OnUICallCreated(INmCall *pNmCall)
{
CCall * pCall;
// Notify the API
if (S_OK == pNmCall->IsIncoming())
{
pCall = CreateIncomingCall(pNmCall);
if (NULL == pCall)
{
return S_FALSE;
}
}
else
{
pCall = CallFromNmCall(pNmCall);
if (NULL == pCall)
{
WARNING_OUT(("OnUiCallCreated: Unable to find outgoing call=%08X", pNmCall));
return S_FALSE;
}
}
pCall->SetNmCall(pNmCall);
return S_OK;
}
HRESULT OnUIRemotePassword(BSTR bstrConference, BSTR * pbstrPassword, LPCTSTR pCertText, BOOL fIsService)
{
HRESULT hr = S_FALSE;
LPTSTR szName;
hr = BSTR_to_LPTSTR (&szName, bstrConference);
if (SUCCEEDED(hr))
{
CPasswordDlg dlgPw(::GetMainWindow(), szName, pCertText, fIsService);
// Free resources
//
delete (szName);
if (IDOK == dlgPw.DoModal())
{
TRACE_OUT(("password dialog complete (OK pressed)"));
LPTSTR_to_BSTR(pbstrPassword, dlgPw.GetPassword());
hr = S_OK;
}
}
return hr;
}
HRESULT CCall::OnUIRemoteConference(BOOL fMCU, PWSTR* pwszConfNames, BSTR *pbstrConfToJoin)
{
HRESULT hr = S_FALSE;
ShowProgress(FALSE);
// We bring up the "choose a conference" dialog
// when calling an MCU or another node with more than
// one "listed" conference
CChooseConfDlg dlgChoose(::GetMainWindow(), pwszConfNames);
if (IDOK == dlgChoose.DoModal())
{
TRACE_OUT(("choose conference dialog complete (OK pressed)"));
LPTSTR_to_BSTR(pbstrConfToJoin, dlgChoose.GetName());
hr = S_OK;
m_fSelectedConference = TRUE;
}
ShowProgress(TRUE);
return hr;
}
/* F R E E C A L L L I S T */
/*-------------------------------------------------------------------------
%%Function: FreeCallList
Free any remaining calls
-------------------------------------------------------------------------*/
VOID FreeCallList(void)
{
if (NULL == g_pCallList)
return;
while (!g_pCallList->IsEmpty())
{
CCall * pCall = (CCall *) g_pCallList->GetHead();
WARNING_OUT(("FreeCallList: Orphan call=%08X", pCall));
pCall->RemoveFromList();
pCall->Release();
}
delete g_pCallList;
g_pCallList = NULL;
}
/* C A N C E L A L L O U T G O I N G C A L L S */
/*-------------------------------------------------------------------------
%%Function: CancelAllOutgoingCalls
-------------------------------------------------------------------------*/
VOID CancelAllOutgoingCalls(void)
{
if (NULL == g_pCallList)
return;
POSITION pos = g_pCallList->GetHeadPosition();
while (pos)
{
CCall * pCall = (CCall *) g_pCallList->GetNext(pos);
ASSERT(NULL != pCall);
if (!pCall->FIncoming())
{
// Cancel will release the call object.
// Ensure that there is at least one reference.
pCall->AddRef();
pCall->Cancel(TRUE);
pCall->Release();
}
}
}
/* C A N C E L A L L C A L L S */
/*-------------------------------------------------------------------------
%%Function: CancelAllCalls
-------------------------------------------------------------------------*/
VOID CancelAllCalls(void)
{
if (NULL == g_pCallList)
return;
POSITION pos = g_pCallList->GetHeadPosition();
while (pos)
{
CCall * pCall = (CCall *) g_pCallList->GetNext(pos);
ASSERT(NULL != pCall);
// Cancel will release the call object.
// Ensure that there is at least one reference.
pCall->AddRef();
pCall->Cancel(TRUE);
pCall->Release();
}
}
///////////////////////////////////////////////////////////////////////////
// IP Utilities
/* F L O C A L I P A D D R E S S */
/*-------------------------------------------------------------------------
%%Function: FLocalIpAddress
Return TRUE if the parameter matches the local IP address.
-------------------------------------------------------------------------*/
BOOL FLocalIpAddress(DWORD dwIP)
{
if (dwIP == 0x0100007F)
{
WARNING_OUT(("t-bkrav is trying to call himself"));
return TRUE;
}
// Get own host name
TCHAR sz[MAX_PATH];
if (0 != gethostname(sz, CCHMAX(sz)))
{
WARNING_OUT(("FLocalIpAddress: gethostname failed? err=%s", PszWSALastError()));
return FALSE;
}
HOSTENT * pHostInfo = gethostbyname(sz);
if (NULL == pHostInfo)
{
WARNING_OUT(("FLocalIpAddress: gethostbyname failed? err=%s", PszWSALastError()));
return FALSE;
}
return (dwIP == *(DWORD *) pHostInfo->h_addr);
}
/* F I P A D D R E S S */
/*-------------------------------------------------------------------------
%%Function: FIpAddress
Return TRUE if the string is in the form: a.b.c.d
where a,b,c,d < 256.
Note that inet_addr returns success on strings like "55534" and "3102.550"
FUTURE: Return the converted DWORD
-------------------------------------------------------------------------*/
BOOL FIpAddress(LPCTSTR pcsz)
{
TCHAR ch;
int cPeriods = 0;
int uVal = 0;
ASSERT(NULL != pcsz);
while (_T('\0') != (ch = *pcsz++))
{
switch (ch)
{
case _T('0'):
case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
uVal = (uVal *= 10) + (ch - _T('0'));
if (uVal > 255)
return FALSE;
break;
case _T('.'):
cPeriods++;
uVal = 0;
break;
default:
return FALSE;
} /* switch (ch) */
}
return (3 == cPeriods);
}
VOID DisplayCallError(HRESULT hr, LPCTSTR pcszName)
{
int ids;
WARNING_OUT(("DisplayCallError pcsz=[%s] err=%s", pcszName, PszHResult(hr)));
switch (hr)
{
case S_OK:
case S_FALSE:
return; // no error
default:
case E_FAIL:
WARNING_OUT(("DisplayCallError - message is not very informative. HRESULT=%08X", hr));
// fall thru to IDS_RESOLVE_FAILED
case NM_CALLERR_NAME_RESOLUTION:
ids = IDS_RESOLVE_FAILED;
break;
case NM_CALLERR_NOT_INITIALIZED:
case NM_CALLERR_NOT_FOUND:
ids = IDS_COULD_NOT_INVITE;
break;
case NM_CALLERR_LOOPBACK:
ids = IDS_CALL_LOOPBACK;
break;
case NM_CALLERR_ALREADY_CALLING:
ids = IDS_ALREADY_CALLING;
break;
case E_OUTOFMEMORY:
ids = IDS_ULSLOGON_OUTOFMEMORY;
break;
case NM_CALLERR_INVALID_PHONE_NUMBER:
ids = IDS_CALLERR_E_BAD_PHONE_NUMBER;
break;
case NM_CALLERR_NO_PHONE_SUPPORT:
ids = IDS_CALLERR_E_NO_PHONE_SUPPORT;
break;
case NM_CALLERR_INVALID_IPADDRESS:
ids = IDS_CALLERR_E_BAD_IPADDRESS;
break;
case NM_CALLERR_HOST_RESOLUTION_FAILED:
ids = IDS_CALLERR_E_BAD_HOSTNAME;
break;
case NM_CALLERR_NO_ILS:
ids = IDS_CALLERR_E_NO_ILS;
break;
case NM_CALLERR_ILS_RESOLUTION_FAILED:
ids = IDS_CALLERR_E_ILS_RESOLUTION_FAILED;
break;
case NM_CALLERR_NO_ADDRESS:
ids = IDS_CALLERR_E_NO_ADDRESS;
break;
case NM_CALLERR_INVALID_ADDRESS:
ids = IDS_CALLERR_E_INVALID_ADDRESS;
break;
case NM_CALLERR_NO_GATEKEEPER:
ids = IDS_CALLERR_E_NO_GATEKEEPER;
break;
case NM_CALLERR_NOT_REGISTERED:
ids = IDS_GK_NOT_REGISTERED;
break;
case NM_CALLERR_NO_GATEWAY:
ids = IDS_CALLERR_E_NO_GATEWAY;
break;
case NM_CALLERR_PARAM_ERROR:
ids = IDS_CALLERR_E_PARAM_ERROR;
break;
case NM_CALLERR_SECURITY_MISMATCH:
ids = IDS_CALLERR_E_SECURITY_MISMATCH;
break;
case NM_CALLERR_UNESCAPE_ERROR:
ids = IDS_CALLERR_E_UNESCAPE_ERROR;
break;
case NM_CALLERR_IN_CONFERENCE:
ids = IDS_INVITE_DENIED_REMOTE_CONF;
break;
}
DisplayMsgIdsParam(ids, pcszName);
}
///////////////////////////////////////////////////////////////////////
// Gateway utility routines
/* G E T D E F A U L T G A T E W A Y */
/*-------------------------------------------------------------------------
%%Function: GetDefaultGateway
-------------------------------------------------------------------------*/
int GetDefaultGateway(LPTSTR psz, UINT cchMax)
{
RegEntry re(CONFERENCING_KEY, HKEY_CURRENT_USER);
// Make sure it's enabled
if (0 == re.GetNumber(REGVAL_USE_H323_GATEWAY, DEFAULT_USE_H323_GATEWAY))
{
SetEmptySz(psz);
return 0;
}
lstrcpyn(psz, re.GetString(REGVAL_H323_GATEWAY), cchMax);
return lstrlen(psz);
}
BOOL FH323GatewayEnabled(VOID)
{
if (!::FIsAudioAllowed())
return FALSE;
TCHAR sz[MAX_PATH];
return 0 != GetDefaultGateway(sz, CCHMAX(sz));
}
/* C R E A T E G A T E W A Y A D D R E S S */
/*-------------------------------------------------------------------------
%%Function: CreateGatewayAddress
Create a gateway address in the form: gateway/address
e.g. "157.59.0.40/65000"
-------------------------------------------------------------------------*/
HRESULT CreateGatewayAddress(LPTSTR pszResult, UINT cchMax, LPCTSTR pszAddr)
{
int cch = GetDefaultGateway(pszResult, cchMax);
if (0 == cch)
return E_FAIL;
if (cchMax <= (UINT) (cch + 1 + lstrlen(pszAddr)))
return E_FAIL;
*(pszResult+cch) = _T('/');
pszResult += cch+1;
lstrcpy(pszResult, pszAddr);
return S_OK;
}
///////////////////////////////////////////////////////////////////////
// Gatekeeper utility routines
NM_GK_STATE g_GkLogonState = NM_GK_NOT_IN_GK_MODE;
BOOL FGkEnabled(VOID)
{
return ( ConfPolicies::CallingMode_GateKeeper == ConfPolicies::GetCallingMode() );
}
inline bool ISE164CHAR(TCHAR digit)
{
if ((digit >= '0') && (digit <= '9'))
{
return true;
}
if ((digit == '#') || (digit == '*'))
{
return true;
}
return false;
}
// removes non E-164 chars from a phone number string
int CleanupE164String(LPTSTR szPhoneNumber)
{
int nLength;
int nIndex, nIndexWrite;
if ((szPhoneNumber == NULL) || (szPhoneNumber[0] == '\0'))
{
return 0;
}
nIndexWrite = 0;
nLength = lstrlen(szPhoneNumber);
for (nIndex = 0; nIndex < nLength; nIndex++)
{
if (ISE164CHAR(szPhoneNumber[nIndex]))
{
if (nIndex != nIndexWrite)
{
szPhoneNumber[nIndexWrite] = szPhoneNumber[nIndex];
}
nIndexWrite++;
}
}
szPhoneNumber[nIndexWrite] = '\0';
return nIndexWrite; // length of the new string
}
// removes non E-164 & non-comma chars from a phone number string
int CleanupE164StringEx(LPTSTR szPhoneNumber)
{
int nLength;
int nIndex, nIndexWrite;
if ((szPhoneNumber == NULL) || (szPhoneNumber[0] == '\0'))
{
return 0;
}
nIndexWrite = 0;
nLength = lstrlen(szPhoneNumber);
for (nIndex = 0; nIndex < nLength; nIndex++)
{
if (ISE164CHAR(szPhoneNumber[nIndex]) || (szPhoneNumber[nIndex] == ',') )
{
if (nIndex != nIndexWrite)
{
szPhoneNumber[nIndexWrite] = szPhoneNumber[nIndex];
}
nIndexWrite++;
}
}
szPhoneNumber[nIndexWrite] = '\0';
return nIndexWrite; // length of the new string
}
static bool _CanLogonToGk()
{
return (NULL != g_pNmSysInfo) &&
FGkEnabled() &&
( ( NM_GK_IDLE == g_GkLogonState ) || ( NM_GK_NOT_IN_GK_MODE == g_GkLogonState ) );
}
void GkLogon(void)
{
if(_CanLogonToGk())
{
// In case the logon fails, we set this to idle
SetGkLogonState(NM_GK_IDLE);
RegEntry reConf(CONFERENCING_KEY, HKEY_CURRENT_USER);
LPCTSTR pszServer = reConf.GetString(REGVAL_GK_SERVER);
g_pCCallto->SetGatekeeperName( pszServer );
RegEntry reULS(ISAPI_CLIENT_KEY, HKEY_CURRENT_USER);
LPTSTR pszAliasID = NULL;
LPTSTR pszAliasE164 = NULL;
ConfPolicies::eGKAddressingMode mode = ConfPolicies::GKAddressing_Invalid;
mode = ConfPolicies::GetGKAddressingMode();
if( (ConfPolicies::GKAddressing_PhoneNum == mode) || (ConfPolicies::GKAddressing_Both == mode) )
{
pszAliasE164 = PszAlloc(reULS.GetString( REGVAL_ULS_PHONENUM_NAME ));
CleanupE164String(pszAliasE164);
}
if( (ConfPolicies::GKAddressing_Account == mode) || (ConfPolicies::GKAddressing_Both == mode) )
{
pszAliasID = PszAlloc(reULS.GetString( REGVAL_ULS_GK_ACCOUNT ));
}
HRESULT hr = g_pNmSysInfo->GkLogon(CComBSTR(pszServer),
CComBSTR(pszAliasID ? pszAliasID : g_szEmpty),
CComBSTR(pszAliasE164 ? pszAliasE164 : g_szEmpty));
delete pszAliasID;
delete pszAliasE164;
if( SUCCEEDED( hr ) )
{
SetGkLogonState(NM_GK_LOGGING_ON);
}
else
{
PostConfMsgBox(IDS_ERR_GK_NOT_FOUND);
}
}
}
void GkLogoff(void)
{
if (NULL != g_pNmSysInfo)
{
g_pNmSysInfo->GkLogoff();
}
SetGkLogonState( NM_GK_IDLE );
}
bool IsGatekeeperLoggedOn(void)
{
return ( NM_GK_LOGGED_ON == g_GkLogonState );
}
bool IsGatekeeperLoggingOn(void)
{
return ( NM_GK_LOGGING_ON == g_GkLogonState );
}
void SetGkLogonState( NM_GK_STATE state )
{
if( FGkEnabled() )
{
if( g_GkLogonState != state )
{
// Set the new state
g_GkLogonState = state;
}
}
else
{
// We are not in GK mode anymore
g_GkLogonState = NM_GK_NOT_IN_GK_MODE;
}
::UpdateUI(CRUI_STATUSBAR, TRUE);
g_pCCallto->SetGatekeeperEnabled( IsGatekeeperLoggedOn() || IsGatekeeperLoggingOn() );
}
CCallResolver::CCallResolver(LPCTSTR pszAddr, NM_ADDR_TYPE addrType) :
m_pszAddr(PszAlloc(pszAddr)),
m_pszAddrIP(NULL),
m_addrType(addrType)
{
}
CCallResolver::~CCallResolver()
{
delete m_pszAddr;
delete m_pszAddrIP;
}
///////////////////////////////////////////////////////////////////////////
// Name Resolution
HRESULT CCallResolver::CheckHostEnt(HOSTENT * pHostInfo)
{
// Only expecting IP addresses..
if ((AF_INET != pHostInfo->h_addrtype) || (sizeof(DWORD) != pHostInfo->h_length))
{
WARNING_OUT(("CCallResolver: %08X CheckHostEnt - address type=%d",this, pHostInfo->h_addrtype));
return E_FAIL;
}
struct in_addr inAddr;
inAddr.s_addr = *((DWORD *)pHostInfo->h_addr);
if (FLocalIpAddress(inAddr.s_addr))
{
WARNING_OUT(("CCallResolver: %08X CheckHostEnt - Attempted to call local machine", this));
return NM_CALLERR_LOOPBACK;
}
m_pszAddrIP = PszAlloc(inet_ntoa(inAddr));
if (NULL == m_pszAddrIP)
return E_OUTOFMEMORY;
return S_OK;
}
HRESULT CCallResolver::ResolveMachineName(LPCTSTR pcszAddr)
{
TCHAR szOem[MAX_PATH];
lstrcpyn(szOem, pcszAddr, CCHMAX(szOem));
CharUpper(szOem);
CharToOem(szOem, szOem);
HOSTENT * pHostInfo = gethostbyname(szOem);
if (NULL == pHostInfo)
{
WARNING_OUT(("CCallResolver: %08X ResolveMachineName(%s) gethostbyname failed. err=%s",
this, szOem, PszWSALastError()));
return NM_CALLERR_NAME_RESOLUTION;
}
return CheckHostEnt(pHostInfo);
}
HRESULT CCallResolver::ResolveUlsName(LPCTSTR pcszAddr)
{
TCHAR szIP[MAX_PATH];
TCHAR szServer[MAX_PATH];
LPCTSTR pcsz = ExtractServerName(pcszAddr, szServer, CCHMAX(szServer));
if (pcsz == pcszAddr)
return NM_CALLERR_NAME_RESOLUTION;
HRESULT hr = S_OK;
if( SUCCEEDED( hr = CNmLDAP::ResolveUser( pcsz, szServer, szIP, CCHMAX( szIP ) ) ) )
{
hr = ResolveIpName( szIP );
}
return hr;
}
HRESULT CCallResolver::ResolveIpName(LPCTSTR pcszAddr)
{
DWORD dwIP = inet_addr(pcszAddr);
if (INADDR_NONE == dwIP)
return NM_CALLERR_NAME_RESOLUTION;
char * pAddr = (char *) &dwIP;
HOSTENT hostInfo;
ClearStruct(&hostInfo);
hostInfo.h_addrtype = AF_INET;
hostInfo.h_length = sizeof(DWORD);
hostInfo.h_addr_list = &pAddr;
return CheckHostEnt(&hostInfo);
}
HRESULT CCallResolver::ResolveGateway(LPCTSTR pcszAddr)
{
TCHAR szGateway[MAX_PATH];
LPCTSTR pchSlash = _StrChr(pcszAddr, _T('/'));
if (NULL == pchSlash)
{
WARNING_OUT(("CCallResolver: %08X ResolveGateway(%s) no separator?", this, pcszAddr));
return NM_CALLERR_NAME_RESOLUTION;
}
lstrcpyn(szGateway, pcszAddr, (int)(1 + (pchSlash-pcszAddr)));
return ResolveIpName(szGateway);
}
/* R E S O L V E */
/*-------------------------------------------------------------------------
%%Function: Resolve
Attempt to resolve the string into a standard IP address.
-------------------------------------------------------------------------*/
HRESULT CCallResolver::Resolve()
{
DBGENTRY(CCallResolver::Resolve);
HRESULT hr = E_FAIL;
switch (m_addrType)
{
case NM_ADDR_UNKNOWN:
{
if (NULL != _StrChr(m_pszAddr, _T('/')))
{
if(SUCCEEDED(hr = ResolveUlsName(m_pszAddr)))
{
m_addrType = NM_ADDR_ULS;
}
break;
}
if (FIpAddress(m_pszAddr))
{
if(SUCCEEDED(hr = ResolveIpName(m_pszAddr)))
{
m_addrType = NM_ADDR_IP;
}
break;
}
if(SUCCEEDED(hr = ResolveMachineName(m_pszAddr)))
{
m_addrType = NM_ADDR_MACHINENAME;
}
break;
}
case NM_ADDR_H323_GATEWAY:
{
LPTSTR pch = (LPTSTR) _StrChr(m_pszAddr, _T('/'));
if (NULL != pch)
{
// Address is in the format: Gateway/address
// e.g. "157.59.0.40/65000" or "efusion/65000"
*pch = _T('\0');
pch++;
hr = ResolveIpName(m_pszAddr);
if (FAILED(hr))
{
hr = ResolveMachineName(m_pszAddr);
if (FAILED(hr))
{
break;
}
}
LPTSTR pszNumber = PszAlloc(pch);
delete m_pszAddr;
m_pszAddr = pszNumber;
}
else
{
TCHAR sz[MAX_PATH];
if (0 == GetDefaultGateway(sz, CCHMAX(sz)))
{
hr = E_FAIL;
break;
}
hr = ResolveIpName(sz);
if (FAILED(hr))
{
hr = ResolveMachineName(sz);
if (FAILED(hr))
{
break;
}
}
}
hr = FEmptySz(m_pszAddr) ? E_INVALIDARG : S_OK;
break;
}
case NM_ADDR_ULS:
// Make sure the address is prefixed with an ILS server
if (NULL == _StrChr(m_pszAddr, _T('/')))
{
TCHAR szAddr[CCHMAXSZ_ADDRESS];
if (!FCreateIlsName(szAddr, NULL, m_pszAddr, CCHMAX(szAddr)))
{
hr = E_FAIL;
break;
}
delete m_pszAddr;
m_pszAddr = PszAlloc(szAddr);
}
hr = ResolveUlsName(m_pszAddr);
break;
case NM_ADDR_IP:
hr = ResolveIpName(m_pszAddr);
if (FAILED(hr) && (hr != NM_CALLERR_LOOPBACK) )
{
hr = ResolveMachineName(m_pszAddr);
}
break;
case NM_ADDR_MACHINENAME:
hr = ResolveMachineName(m_pszAddr);
break;
case NM_ADDR_ALIAS_ID:
case NM_ADDR_ALIAS_E164:
case NM_ADDR_T120_TRANSPORT:
hr = FEmptySz(m_pszAddr) ? E_INVALIDARG : S_OK;
break;
default:
WARNING_OUT(("Resolve: Unsupported address type %d", m_addrType));
ASSERT(E_FAIL == hr);
break;
} /* switch (addrType) */
WARNING_OUT(("CCallResolver::Resolve(%d,%s) result=%08X", m_addrType, m_pszAddrIP ? m_pszAddrIP : m_pszAddr, hr));
DBGEXIT_HR(CCallResolver::Resolve, hr);
return hr;
}