/****************************************************************************
|
*
|
* Copyright (C) 2000-2001 RealNetworks, Inc. All rights reserved.
|
*
|
* This program is free software. It may be distributed under the terms
|
* in the file LICENSE, found in the top level of the source distribution.
|
*
|
*/
|
|
#include "RtspMsg.h"
|
|
#include <string.h> /* memset */
|
|
CAuthenticator::CAuthenticator()
|
{
|
fRealm = NULL;
|
fNonce = NULL;
|
fUsername = NULL;
|
fPassword = NULL;
|
fPasswordIsMD5 = false;
|
}
|
|
CAuthenticator::~CAuthenticator()
|
{
|
reset();
|
}
|
void CAuthenticator::reset()
|
{
|
resetRealmAndNonce();
|
resetUsernameAndPassword();
|
}
|
|
void CAuthenticator::setRealmAndNonce(char const* realm, char const* nonce)
|
{
|
resetRealmAndNonce();
|
assignRealmAndNonce(realm, nonce);
|
}
|
|
void CAuthenticator::setUsernameAndPassword(char const* username, char const* password, bool passwordIsMD5)
|
{
|
resetUsernameAndPassword();
|
assignUsernameAndPassword(username, password, passwordIsMD5);
|
}
|
|
void CAuthenticator::reclaimDigestResponse(char const* responseStr)
|
{
|
if (responseStr != NULL)
|
{
|
delete[](char*)responseStr;
|
responseStr = NULL;
|
}
|
}
|
|
void CAuthenticator::resetRealmAndNonce()
|
{
|
if (fRealm != NULL)
|
{
|
delete[] fRealm;
|
fRealm = NULL;
|
}
|
if (fNonce != NULL)
|
{
|
delete[] fNonce;
|
fNonce = NULL;
|
}
|
}
|
|
void CAuthenticator::resetUsernameAndPassword()
|
{
|
if (fUsername != NULL)
|
{
|
delete[] fUsername;
|
fUsername = NULL;
|
}
|
if (fPassword != NULL)
|
{
|
delete[] fPassword;
|
fPassword = NULL;
|
}
|
fPasswordIsMD5 = false;
|
}
|
|
void CAuthenticator::assignRealmAndNonce(char const* realm, char const* nonce)
|
{
|
if (realm != NULL && strlen(realm) > 0)
|
{
|
fRealm = strMyDup(realm);
|
}
|
if (nonce != NULL && strlen(nonce) > 0)
|
{
|
fNonce = strMyDup(nonce);
|
}
|
}
|
|
void CAuthenticator::assignUsernameAndPassword(char const* username, char const* password, bool passwordIsMD5)
|
{
|
if (username == NULL)
|
username = "";
|
if (password == NULL)
|
password = "";
|
|
fUsername = strMyDup(username);
|
fPassword = strMyDup(password);
|
fPasswordIsMD5 = passwordIsMD5;
|
}
|
|
char const* CAuthenticator::computeDigestResponse(char* cmd, char *url)
|
{
|
// The "response" field is computed as:
|
// md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<url>))
|
// or, if "fPasswordIsMD5" is True:
|
// md5(<password>:<nonce>:md5(<cmd>:<url>))
|
char *pAlgorithm = NULL;
|
char *pUsername = NULL;
|
char *pRealm = NULL;
|
char *pPasswd = NULL;
|
char *pNonce = NULL;
|
char *pNonceCount = NULL;
|
char *pCNonce = NULL;
|
char *pQop = NULL;
|
char *pMethod = NULL;
|
char *pUri = NULL;
|
char sNullAlg[] = ""; //"MD5";
|
char sNullQop[] = ""; //"auth";
|
//char SessionKey[MD5_SESSION_KEY_LEN+1] = {0};
|
char Response[MD5_RESPONSE_LEN] = {0};
|
|
char SessionKey[33] = {0};
|
if (fPasswordIsMD5)
|
{
|
strncpy(SessionKey, fPassword, 32);
|
SessionKey[32] = '\0'; // just in case
|
}
|
else
|
{
|
pUsername = unquote(fUsername);
|
pRealm = unquote(fRealm);
|
pPasswd = unquote(fPassword);
|
pNonce = unquote(fNonce);
|
pCNonce = unquote(sNullAlg);
|
pAlgorithm = unquote(sNullAlg);
|
//calculate session key
|
DigestCalcHA1(pAlgorithm, pUsername, pRealm, pPasswd, pNonce, pCNonce, SessionKey);
|
}
|
pQop = unquote(sNullQop);
|
pMethod = cmd;
|
pUri = url;
|
|
DigestCalcResponse(SessionKey, pNonce, pNonceCount, pCNonce, pQop, pMethod, pUri, (char*)"", Response);
|
|
char const* result = strMyDup(unquote(Response));
|
return result;
|
}
|
|
|
/**************************************
|
*
|
* CRtspHdr
|
*
|
**************************************/
|
|
CRtspHdr::CRtspHdr(const CString& strKey) : m_strKey(strKey)
|
{
|
//Empty
|
}
|
|
CRtspHdr::CRtspHdr(const CString& strKey, const CString& strVal) : m_strKey(strKey), m_strVal(strVal)
|
{
|
//Empty
|
}
|
|
const CString& CRtspHdr::GetKey(void) const
|
{
|
return m_strKey;
|
}
|
|
const CString& CRtspHdr::GetVal(void) const
|
{
|
return m_strVal;
|
}
|
|
void CRtspHdr::SetVal(const CString& strVal)
|
{
|
m_strVal = strVal;
|
}
|
|
/**************************************
|
*
|
* CRtspMsg
|
*
|
**************************************/
|
CRtspMsg::CRtspMsg(void) : m_nRtspVer(0), m_nSeq(0), m_nBufLen(0), m_pbuf(NULL)
|
{
|
//Empty
|
}
|
|
CRtspMsg::CRtspMsg(const CRtspMsg& other)
|
{
|
CRtspHdrList::ConstIterator itr(other.m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
m_listHdrs.InsertTail(new CRtspHdr(*pHdr) );
|
itr++;
|
}
|
|
m_nBufLen = other.m_nBufLen;
|
m_pbuf = NULL;
|
if (m_nBufLen)
|
{
|
m_pbuf = new Byte[m_nBufLen+1];
|
memcpy(m_pbuf, other.m_pbuf, m_nBufLen);
|
}
|
}
|
|
CRtspMsg::~CRtspMsg(void)
|
{
|
while (!m_listHdrs.IsEmpty() )
|
{
|
CRtspHdr* pHdr = m_listHdrs.RemoveHead();
|
delete pHdr;
|
}
|
|
delete[] m_pbuf; m_pbuf = NULL;
|
}
|
|
const CRtspMsg& CRtspMsg::operator=(const CRtspMsg& other)
|
{
|
m_nRtspVer = other.m_nRtspVer;
|
m_nSeq = other.m_nSeq;
|
|
while (!m_listHdrs.IsEmpty() )
|
{
|
CRtspHdr* pHdr = m_listHdrs.RemoveHead();
|
delete pHdr;
|
}
|
CRtspHdrList::ConstIterator itr(other.m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
m_listHdrs.InsertTail(new CRtspHdr(*pHdr) );
|
itr++;
|
}
|
|
m_nBufLen = other.m_nBufLen;
|
delete[] m_pbuf; m_pbuf = NULL;
|
if (m_nBufLen)
|
{
|
m_pbuf = new Byte[m_nBufLen+1];
|
memcpy(m_pbuf, other.m_pbuf, m_nBufLen);
|
}
|
|
return *this;
|
}
|
|
CRtspMsg::operator CPByte(void) const
|
{
|
return m_pbuf;
|
}
|
|
Byte CRtspMsg::operator[](UINT32 nPos) const
|
{
|
return GetAt(nPos);
|
}
|
|
RtspMsgType CRtspMsg::GetType(void) const
|
{
|
return RTSP_TYPE_NONE;
|
}
|
|
size_t CRtspMsg::GetHdrLen(void) const
|
{
|
size_t nLen = 0;
|
CRtspHdrList::ConstIterator itr(m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
nLen += (pHdr->GetKey().GetLength() + 2 + pHdr->GetVal().GetLength() + 2);
|
itr++;
|
}
|
|
return nLen;
|
}
|
|
|
|
size_t CRtspMsg::GetBufLen(void) const
|
{
|
return m_nBufLen;
|
}
|
|
Byte CRtspMsg::GetAt(UINT32 nPos) const
|
{
|
return m_pbuf[nPos];
|
}
|
|
void CRtspMsg::SetAt(UINT32 nPos, Byte by)
|
{
|
m_pbuf[nPos] = by;
|
}
|
|
void CRtspMsg::GetRtspVer(UINT32* puMajor, UINT32* puMinor) const
|
{
|
assert(puMajor && puMinor);
|
|
*puMajor = HIWORD(m_nRtspVer);
|
*puMinor = LOWORD(m_nRtspVer);
|
}
|
|
void CRtspMsg::SetRtspVer(UINT32 uMajor, UINT32 uMinor)
|
{
|
assert(uMajor < 10 && uMinor < 10);
|
|
m_nRtspVer = MAKEDWORD(uMajor,uMinor);
|
}
|
|
size_t CRtspMsg::GetHdrCount(void) const
|
{
|
return m_listHdrs.GetCount();
|
}
|
|
CString CRtspMsg::GetHdr(const CString& strKey) const
|
{
|
CString strVal;
|
|
CRtspHdrList::ConstIterator itr(m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
if (0 == strcasecmp(strKey, pHdr->GetKey() ) )
|
{
|
strVal = pHdr->GetVal();
|
break;
|
}
|
itr++;
|
}
|
|
return strVal;
|
}
|
|
CRtspHdr* CRtspMsg::GetHdr(UINT32 nIndex) const
|
{
|
CRtspHdrList::ConstIterator itr(m_listHdrs.Begin() );
|
for (UINT32 n = 0; n < nIndex; n++)
|
{
|
itr++;
|
}
|
return *itr;
|
}
|
|
void CRtspMsg::SetHdr(const CString& strKey, const CString& strVal)
|
{
|
CRtspHdrList::Iterator itr(m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
if (0 == strcasecmp(strKey, pHdr->GetKey() ) )
|
{
|
pHdr->SetVal(strVal);
|
return;
|
}
|
itr++;
|
}
|
m_listHdrs.InsertTail(new CRtspHdr(strKey, strVal) );
|
}
|
|
void CRtspMsg::SetHdr(const CRtspHdr& hdrNew)
|
{
|
CRtspHdrList::Iterator itr(m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
if (hdrNew.GetKey() == pHdr->GetKey() )
|
{
|
pHdr->SetVal(hdrNew.GetVal() );
|
return;
|
}
|
itr++;
|
}
|
m_listHdrs.InsertTail(new CRtspHdr(hdrNew) );
|
}
|
|
PByte CRtspMsg::GetBuf(void) const
|
{
|
return m_pbuf;
|
}
|
|
void CRtspMsg::SetBuf(CPByte buf, size_t nLen)
|
{
|
delete[] m_pbuf; m_pbuf = NULL;
|
m_nBufLen = nLen;
|
if (m_nBufLen)
|
{
|
m_pbuf = new Byte[m_nBufLen+1];
|
memcpy(m_pbuf, buf, m_nBufLen);
|
}
|
}
|
|
/**************************************
|
*
|
* CRtspRequestMsg
|
*
|
**************************************/
|
|
// These correspond with enum RtspVerb and must be sorted
|
static CPCHAR s_pVerbs[] = {
|
"-NONE-",
|
"ANNOUNCE",
|
"DESCRIBE",
|
"GET_PARAMETER",
|
"OPTIONS",
|
"PAUSE",
|
"PLAY",
|
"RECORD",
|
"REDIRECT",
|
"SETUP",
|
"SET_PARAMETER",
|
"TEARDOWN",
|
NULL
|
};
|
static const UINT32 s_nVerbs = sizeof(s_pVerbs)/sizeof(CPCHAR) - 1;
|
|
CRtspRequestMsg::CRtspRequestMsg(void) : CRtspMsg()
|
{
|
m_verb = VERB_NONE;
|
|
//Empty
|
}
|
|
CRtspRequestMsg::CRtspRequestMsg(const CRtspRequestMsg& other) : CRtspMsg(other)
|
{
|
m_verb = other.m_verb;
|
m_strUrl = other.m_strUrl;
|
|
UrlParam = other.UrlParam;
|
}
|
|
CRtspRequestMsg::~CRtspRequestMsg( void )
|
{
|
//Empty
|
}
|
|
const CRtspRequestMsg& CRtspRequestMsg::operator=(const CRtspRequestMsg& other)
|
{
|
m_verb = other.m_verb;
|
UrlParam = other.UrlParam;
|
m_strUrl = other.m_strUrl;
|
|
m_nRtspVer = other.m_nRtspVer;
|
m_nSeq = other.m_nSeq;
|
|
while (!m_listHdrs.IsEmpty() )
|
{
|
CRtspHdr* pHdr = m_listHdrs.RemoveHead();
|
delete pHdr;
|
}
|
CRtspHdrList::ConstIterator itr(other.m_listHdrs.Begin() );
|
while (itr)
|
{
|
CRtspHdr* pHdr = *itr;
|
m_listHdrs.InsertTail(new CRtspHdr(*pHdr) );
|
itr++;
|
}
|
|
m_nBufLen = other.m_nBufLen;
|
delete[] m_pbuf; m_pbuf = NULL;
|
if (m_nBufLen)
|
{
|
m_pbuf = new Byte[m_nBufLen+1];
|
memcpy(m_pbuf, other.m_pbuf, m_nBufLen);
|
}
|
|
return *this;
|
}
|
|
RtspMsgType CRtspRequestMsg::GetType( void ) const
|
{
|
return RTSP_TYPE_REQUEST;
|
}
|
|
RtspVerb CRtspRequestMsg::GetVerb( void ) const
|
{
|
return m_verb;
|
}
|
|
CPCHAR CRtspRequestMsg::GetVerbStr( void ) const
|
{
|
return s_pVerbs[m_verb];
|
}
|
|
void CRtspRequestMsg::SetVerb( RtspVerb verb )
|
{
|
assert( verb > VERB_NONE && verb < VERB_LAST );
|
|
m_verb = verb;
|
}
|
void CRtspRequestMsg::GetFileName(char filename[64])
|
{
|
char* url = (char*)(CPCHAR)m_strUrl;
|
char* temp;
|
|
filename[0] = 0;
|
|
temp = rindex(url,'/');
|
if(temp == NULL)
|
{
|
return;
|
}
|
if(strlen(temp) <= 1)
|
{
|
*temp = 0;
|
temp = rindex(url,'/');
|
if(temp == NULL)
|
{
|
return;
|
}
|
}
|
temp ++;
|
snprintf(filename,64,"%s",temp);
|
}
|
|
void CRtspRequestMsg::SetVerb( CPCHAR szVerb )
|
{
|
m_verb = VERB_NONE;
|
|
int hi = s_nVerbs;
|
int lo = -1;
|
int mid;
|
while( hi-lo > 1 )
|
{
|
mid = (hi+lo)/2;
|
if( strcmp( szVerb, s_pVerbs[mid] ) <= 0 )
|
hi = mid;
|
else
|
lo = mid;
|
}
|
if( 0 == strcmp( szVerb, s_pVerbs[hi] ) )
|
{
|
m_verb = (RtspVerb)hi;
|
}
|
|
//assert(VERB_NONE == m_verb);
|
}
|
|
CPCHAR CRtspRequestMsg::GetUrl( void ) const
|
{
|
return (CPCHAR)m_strUrl;
|
}
|
|
void CRtspRequestMsg::SetUrl( const CString& strUrl )
|
{
|
m_strUrl = strUrl;
|
}
|
|
void CRtspRequestMsg::ParserUri(void)
|
{
|
//For example: URL=rtsp://192.168.1.245:554/DevAor=100@192.168.1.245/Type=1/BeginTime=2011-3-16.13:06:12/EndTime=2011-3-16.13:06:23/StreamID=1
|
//URL=rtsp://172.30.12.93:8554/DevAor=44011188001310000001/StreamID=1
|
char* p = (char*)(CPCHAR)m_strUrl;
|
if ( ('\0' == *p) || (strcmp(p, "rtsp://") <= 0) )
|
{
|
printf("%s: url=<%s>, no \"rtsp://\" protocol header!\n", __FUNCTION__, p);
|
return;
|
}
|
|
p += 7; //7 = strlen("rtsp://");
|
while (*p != '\0')
|
{
|
if (*p == '/')
|
{
|
p++;
|
|
char buf1[64+1] = {0}, buf2[64+1] = {0}, buf3[64+1] = {0}, buf4[64+1] = {0}, buf5[64+1] = {0}, buf6[64+1] = {0}, buf7[64+1] = {0}, buf8[64+1] = {0};
|
sscanf(p, "%64[^/]/%64[^/]/%64[^/]/%64[^/]/%64[^/]/%64[^/]/%64[^/]/%64[^/]/", buf1, buf2, buf3, buf4, buf5, buf6, buf7, buf8);
|
if (strlen(buf1) > 0) //The URL is right if 4th parameter is no empty.
|
{
|
UrlParam.LineTo(buf1);
|
UrlParam.LineTo(buf2);
|
UrlParam.LineTo(buf3);
|
UrlParam.LineTo(buf4);
|
UrlParam.LineTo(buf5);
|
UrlParam.LineTo(buf6);
|
UrlParam.LineTo(buf7);
|
UrlParam.LineTo(buf8);
|
}
|
else
|
{
|
printf("%s: url=<%s>, some errors!\n", __FUNCTION__, (char*)(CPCHAR)m_strUrl);
|
}
|
|
break;
|
}
|
p++;
|
}
|
}
|
|
//Transfer protocol
|
SOCKETTYPE_E CRtspRequestMsg::ParserClientStreamTransType(void)
|
{
|
CString Transport = GetHdr("Transport");
|
char* p = strstr( (char *)(CPCHAR)Transport, "RTP/AVP/TCP");
|
if(p != NULL)
|
{
|
return SOCKETTYPE_TCPSERVER;
|
}
|
//UDP
|
return SOCKETTYPE_UDP;
|
}
|
|
bool CRtspRequestMsg::ParserClientAddr(char * ClientAddr)
|
{
|
if(ClientAddr == NULL)
|
{
|
return false;
|
}
|
CString Transport = GetHdr("Transport");
|
|
char* p = strstr( (char *)(CPCHAR)Transport, "destination=");
|
if(p == NULL)
|
{
|
return false;
|
}
|
p += strlen("destination=");
|
char * temp = strchr(p,';');
|
if(temp == NULL)
|
{
|
return false;
|
}
|
if(temp - p < 7 || temp - p > 15)//"1.1.1.1 255.255.255.255"
|
{
|
return false;
|
}
|
memcpy(ClientAddr,p,temp - p);
|
ClientAddr[temp - p] = 0;
|
|
return true;
|
}
|
|
UINT16 CRtspRequestMsg::ParserClientPort(void)
|
{
|
CString Transport = GetHdr("Transport");
|
UINT16 port = 0;
|
|
char* p = strstr( (char*)(CPCHAR)Transport, "client_port");
|
if (p != NULL)
|
{
|
p += 11; //11 = strlen(client_port=)
|
|
while ( (*p != '\0') && ( (*p == ' ') || (*p == '=') ) ) p++;
|
|
port = (*p != '\0') ? atoi(p) : 0;
|
}
|
|
return port;
|
}
|
|
bool CRtspRequestMsg::ParserClientMulticast(void)
|
{
|
CString Transport = GetHdr("Transport");
|
char* p = strstr( (char *)(CPCHAR)Transport, "multicast");
|
if(p != NULL)
|
{
|
return true;
|
}
|
|
return false;
|
}
|
|
UINT16 CRtspRequestMsg::ParserClientMulticastPort(void)
|
{
|
CString Transport = GetHdr("Transport");
|
UINT16 port = 0;
|
|
char* p = strstr( (char*)(CPCHAR)Transport, "port");
|
if (p != NULL)
|
{
|
p += 11; //11 = strlen(client_port=)
|
|
while ( (*p != '\0') && ( (*p == ' ') || (*p == '=') ) ) p++;
|
|
port = (*p != '\0') ? atoi(p) : 0;
|
}
|
|
return port;
|
}
|
|
/**************************************
|
*
|
* CRtspResponseMsg
|
*
|
**************************************/
|
|
struct StatusMapEntry {UINT32 nCode; CPCHAR szName;};
|
|
// These must be sorted
|
static StatusMapEntry s_mapStatus[] =
|
{
|
{ 100, "Continue" },
|
|
{ 200, "OK" },
|
{ 201, "Created" },
|
{ 250, "Low on Storage Space" },
|
|
{ 300, "Multiple Choices" },
|
{ 301, "Moved Permanently" },
|
{ 302, "Moved Temporarily" },
|
{ 303, "See Other" },
|
{ 304, "Not Modified" },
|
{ 305, "Use Proxy" },
|
|
{ 400, "Bad Request" },
|
{ 401, "Unauthorized" },
|
{ 402, "Payment Required" },
|
{ 403, "Forbidden" },
|
{ 404, "Not Found" },
|
{ 405, "Method Not Allowed" },
|
{ 406, "Not Acceptable" },
|
{ 407, "Proxy Authentication Required" },
|
{ 408, "Request Time-out" },
|
{ 410, "Gone" },
|
{ 411, "Length Required" },
|
{ 412, "Precondition Failed" },
|
{ 413, "Request Entity Too Large" },
|
{ 414, "Request-URI Too Large" },
|
{ 415, "Unsupported Media Type" },
|
{ 451, "Parameter Not Understood" },
|
{ 452, "Conference Not Found" },
|
{ 453, "Not Enough Bandwidth" },
|
{ 454, "Session Not Found" },
|
{ 455, "Method Not Valid in This State" },
|
{ 456, "Header Field Not Valid for Resource" },
|
{ 457, "Invalid Range" },
|
{ 458, "Parameter Is Read-Only" },
|
{ 459, "Aggregate operation not allowed" },
|
{ 460, "Only aggregate operation allowed" },
|
{ 461, "Unsupported transport" },
|
{ 462, "Destination unreachable" },
|
|
{ 500, "Internal Server Error" },
|
{ 501, "Not Implemented" },
|
{ 502, "Bad Gateway" },
|
{ 503, "Service Unavailable" },
|
{ 504, "Gateway Time-out" },
|
{ 505, "RTSP Version not supported" },
|
{ 551, "Option not supported" },
|
{ 0, NULL }
|
};
|
static const UINT32 s_nStatusEntries = sizeof(s_mapStatus)/sizeof(StatusMapEntry) - 1;
|
|
CRtspResponseMsg::CRtspResponseMsg(void) : CRtspMsg(), m_nCode(0)
|
{
|
//Empty
|
}
|
|
CRtspResponseMsg::CRtspResponseMsg(const CRtspResponseMsg& other) : CRtspMsg(other)
|
{
|
m_nCode = other.m_nCode;
|
m_strStatusMsg = other.m_strStatusMsg;
|
}
|
|
CRtspResponseMsg::~CRtspResponseMsg( void )
|
{
|
//Empty
|
}
|
|
const CRtspResponseMsg& CRtspResponseMsg::operator=(const CRtspResponseMsg& other)
|
{
|
m_nCode = other.m_nCode;
|
m_strStatusMsg = other.m_strStatusMsg;
|
|
return *this;
|
}
|
|
RtspMsgType CRtspResponseMsg::GetType(void) const
|
{
|
return RTSP_TYPE_RESPONSE;
|
}
|
|
UINT32 CRtspResponseMsg::GetStatusCode(void) const
|
{
|
return m_nCode;
|
}
|
|
const CString& CRtspResponseMsg::GetStatusMsg(void) const
|
{
|
return m_strStatusMsg;
|
}
|
|
void CRtspResponseMsg::SetStatus( UINT32 nCode, CPCHAR szMsg /* = NULL */ )
|
{
|
assert( nCode >= 100 && nCode <= 999 );
|
|
m_nCode = nCode;
|
if( !szMsg )
|
{
|
szMsg = "Unknown";
|
|
int hi = s_nStatusEntries;
|
int lo = -1;
|
int mid;
|
while( hi-lo > 1 )
|
{
|
mid = (hi+lo)/2;
|
if( nCode <= s_mapStatus[mid].nCode )
|
hi = mid;
|
else
|
lo = mid;
|
}
|
if( nCode == s_mapStatus[hi].nCode )
|
{
|
szMsg = s_mapStatus[hi].szName;
|
}
|
}
|
m_strStatusMsg = szMsg;
|
}
|