国标GBT28181协议的用户注册时候,需要用户名密码认证,其本质是使用 http digest的算法,
http digest算法,在RFC2617 [HTTP Authentication: Basic and Digest Access Authentication]文档里面有详细的描述,并且有实现代码,
具体请参考我的另外一篇博客,[MD5 SHA1 UUID 算法的C语言实现]
国标GBT28181注册流程如下,[GBT 28181-2011安全防范视频监控联网系统信息传输、交换、控制技术要求,第9.1.2.1 基本注册]
我使用 eXosip2 库,实现了国标的注册代码的客户端与服务端,代码如下,请参考,
服务器端注册代码,
#include
#include
#include
#include
#include //INADDR_ANY
#include
//MD5
#include "HTTPDigest.h"
char *pcPassword = "12345";
char *pcRealm = "640001";
char *pcNonce = "6fe9ba44a76be22a";
static void Register401Unauthorized(struct eXosip_t * peCtx,eXosip_event_t *je)
{
int iReturnCode = 0;
osip_message_t * pSRegister = NULL;
osip_www_authenticate_t * header = NULL;
osip_www_authenticate_init(&header);
osip_www_authenticate_set_auth_type (header,osip_strdup("Digest"));
osip_www_authenticate_set_realm(header,osip_enquote(pcRealm));
osip_www_authenticate_set_nonce(header,osip_enquote(pcNonce));
char *pDest = NULL;
osip_www_authenticate_to_str(header,&pDest);
iReturnCode = eXosip_message_build_answer (peCtx,je->tid,401,&pSRegister);
if ( iReturnCode == 0 && pSRegister != NULL )
{
osip_message_set_www_authenticate(pSRegister,pDest);
osip_message_set_content_type(pSRegister,"Application/MANSCDP+xml");
eXosip_lock(peCtx);
eXosip_message_send_answer (peCtx,je->tid,401,pSRegister);
eXosip_unlock(peCtx);
}
osip_www_authenticate_free(header);
osip_free(pDest);
}
static void SelfCalculateResponse(char *pcUsername,char *pcURI,char *pcMethod,HASHHEX pcResponse)
{
//MD5 计算
HASHHEX HA1;
DigestCalcHA1("REGISTER",pcUsername,pcRealm,pcPassword,pcNonce,NULL,HA1);
HASHHEX Response;
//在下面这个函数里面,已经计算了 H(A2),所以不需要自己计算 H(A2)
DigestCalcResponse(HA1,pcNonce,NULL,NULL,NULL,0,pcMethod,pcURI,NULL,Response);
memcpy(pcResponse,Response,HASHHEXLEN+1);
}
static void RegisterSuccess(struct eXosip_t * peCtx,eXosip_event_t *je)
{
int iReturnCode = 0 ;
osip_message_t * pSRegister = NULL;
iReturnCode = eXosip_message_build_answer (peCtx,je->tid,200,&pSRegister);
if ( iReturnCode == 0 && pSRegister != NULL )
{
eXosip_lock(peCtx);
eXosip_message_send_answer (peCtx,je->tid,200,pSRegister);
eXosip_unlock(peCtx);
}
}
static void RegisterFailed(struct eXosip_t * peCtx,eXosip_event_t *je)
{
int iReturnCode = 0 ;
osip_message_t * pSRegister = NULL;
iReturnCode = eXosip_message_build_answer (peCtx,je->tid,401,&pSRegister);
if ( iReturnCode == 0 && pSRegister != NULL )
{
eXosip_lock(peCtx);
eXosip_message_send_answer (peCtx,je->tid,401,pSRegister);
eXosip_unlock(peCtx);
}
}
static void *MainProcess(void * pvSClientGB)
{
struct eXosip_t * peCtx = (struct eXosip_t *)pvSClientGB;
for(;;)
{
eXosip_event_t *je = NULL;
je = eXosip_event_wait (peCtx,0,4);
if (je == NULL)
{
osip_usleep(10000);
continue;
}
switch (je->type)
{
case EXOSIP_MESSAGE_NEW:
{
//处理注册消息
if ( MSG_IS_REGISTER(je->request) )
{
//提取出各个字段值,进行 MD5 计算
osip_authorization_t * Sdest = NULL;
osip_message_get_authorization(je->request,0,&Sdest);
if ( Sdest != NULL )
{
char *pcMethod = je->request->sip_method;
char *pAlgorithm = osip_strdup_without_quote(Sdest->algorithm);
char *pUsername = NULL;
if ( Sdest->username != NULL )
{
pUsername = osip_strdup_without_quote(Sdest->username);
}
char *pRealm = NULL;
if ( Sdest->realm != NULL )
{
pRealm = osip_strdup_without_quote(Sdest->realm);
}
char *pNonce = NULL;
if ( Sdest->nonce != NULL )
{
pNonce = osip_strdup_without_quote(Sdest->nonce);
}
char *pNonce_count = NULL;
if ( Sdest->nonce_count != NULL)
{
pNonce_count = osip_strdup_without_quote(Sdest->nonce_count);
}
char *pUri = NULL;
if ( Sdest->uri != NULL )
{
pUri = osip_strdup_without_quote(Sdest->uri);
}
//需要去掉两端多余的引号
HASHHEX HA1;
DigestCalcHA1(pAlgorithm,pUsername,pRealm,pcPassword,pNonce,
pNonce_count, HA1);
HASHHEX Response;
HASHHEX HA2="";
//在下面这个函数里面,已经计算了 H(A2),所以不需要自己计算 H(A2)
DigestCalcResponse(HA1,pNonce,pNonce_count,Sdest->cnonce,Sdest->message_qop,0,
pcMethod,pUri,HA2,Response);
//Authenticate
char acResponse[HASHHEXLEN];
SelfCalculateResponse(pUsername,pUri,pcMethod,acResponse);
if ( memcmp(acResponse,Response,HASHHEXLEN) == 0 )
{
//发送注册成功的消息
RegisterSuccess(peCtx,je);
fprintf(stderr,"Register Success!!\n");
}
else //认证失败
{
RegisterFailed(peCtx,je);
}
osip_free(pAlgorithm);
osip_free(pUsername);
osip_free(pRealm);
osip_free(pNonce);
osip_free(pNonce_count);
osip_free(pUri);
}
else //认证失败
{
Register401Unauthorized(peCtx,je);
}
}
}
break;
default:
{
}
break;
}
eXosip_event_free(je);
}
return NULL;
}
int main()
{
struct eXosip_t *eCtx;
eCtx = eXosip_malloc();
int iReturnCode = 0;
iReturnCode = eXosip_init (eCtx);
if (iReturnCode != OSIP_SUCCESS )
{
printf ("Can't initialize eXosip!\n");
return -1;
}
else
{
printf ("eXosip_init successfully!\n");
}
iReturnCode = eXosip_listen_addr (eCtx,IPPROTO_UDP, NULL,5060, AF_INET, 0);
if ( iReturnCode != OSIP_SUCCESS )
{
printf ("eXosip_listen_addr error!\n");
return -1;
}
MainProcess(eCtx);
eXosip_quit(eCtx);
osip_free(eCtx);
eCtx = NULL;
return 0;
}
客户端注册代码,
#include
#include
#include
#include
#include //INADDR_ANY
#include
char *pcServerID = "34010000002000000001";
char *pcServerIP = "192.168.120.107";
int iServerPort = 5060;
char *pcClientID = "34010000001310000001";
int iClientPort = 5062;
char *pcPassword = "12345";
int g_Rid = 0;
/**
* @brief 注册 向上级服务器注册
* @param pSClientGB
* @return
*/
#define GBT28181_REGISTER_SUCCESS 0
#define GBT28181_REGISTER_FAILURE -1
#define GBT28181_REGISTER_TIMEOUT -2
int GBT28181_Register(struct eXosip_t * peCtx)
{
int i = 0;
int iRid = 0;
osip_message_t * reg = NULL;
char acClientURI[128];
char acServerURI[128];
char acDeiveID10Bit[11];
memset(acDeiveID10Bit,0,11);
strncpy(acDeiveID10Bit,pcClientID,10);
sprintf(acClientURI,"sip:%s@%s:%d",pcClientID,acDeiveID10Bit,iClientPort);
sprintf(acServerURI,"sip:%s@%s:%d",pcServerID,pcServerIP,iServerPort);
iRid = eXosip_register_build_initial_register(peCtx,acClientURI,acServerURI,NULL,3600,®);
if (iRid < 0)
{
return -1;
}
g_Rid = iRid;
i = eXosip_register_send_register (peCtx,iRid,reg);
if ( i != OSIP_SUCCESS )
{
return GBT28181_REGISTER_TIMEOUT;
}
else
{
}
return GBT28181_REGISTER_SUCCESS;
}
static void *MainProcess(void * pvSClientGB)
{
struct eXosip_t * peCtx = (struct eXosip_t *)pvSClientGB;
int iHasStartFlag = 0;
for(;;)
{
eXosip_event_t *je = NULL;
je = eXosip_event_wait (peCtx,0,4);
if ( iHasStartFlag == 0 )
{
iHasStartFlag = 1;
}
if ( iHasStartFlag == 1 )
{
//Send Register to Server
GBT28181_Register(peCtx);
iHasStartFlag = 2;
}
if (je == NULL)
{
osip_usleep(10000);
continue;
}
switch (je->type)
{
case EXOSIP_REGISTRATION_SUCCESS:
{
if ( je->response != NULL )
{
osip_header_t *Date = NULL;
osip_message_header_get_byname (je->response,"Date",0,&Date);
if ( (Date != NULL) && (Date->hvalue != NULL) )
{
char *pcDate = Date->hvalue;
//HandleTiming(pSClientGB,pcDate); //处理校时指令
fprintf(stderr,"Register Success!!\n");
}
}
}
break;
case EXOSIP_REGISTRATION_FAILURE:
{
//发送带用户名密码的注册消息
if ( je->response != NULL )
{
if(je->response->status_code == 401 || je->response->status_code == 407)
{
eXosip_lock(peCtx);
eXosip_add_authentication_info (peCtx,pcClientID,pcClientID,pcPassword,"MD5",NULL);//向eXosip提供本机的认证信息
eXosip_unlock(peCtx);
osip_message_t * reg = NULL;
eXosip_lock(peCtx);
eXosip_register_build_register (peCtx,g_Rid,3600,®);
eXosip_register_send_register (peCtx,g_Rid,reg);
eXosip_unlock(peCtx);
}
}
}
}
eXosip_event_free(je);
}
return NULL;
}
int main()
{
struct eXosip_t *eCtx;
eCtx = eXosip_malloc();
int iReturnCode = 0;
iReturnCode = eXosip_init (eCtx);
if (iReturnCode != OSIP_SUCCESS )
{
printf ("Can't initialize eXosip!\n");
return -1;
}
else
{
printf ("eXosip_init successfully!\n");
}
iReturnCode = eXosip_listen_addr (eCtx,IPPROTO_UDP, NULL,iClientPort, AF_INET, 0);
if ( iReturnCode != OSIP_SUCCESS )
{
printf ("eXosip_listen_addr error!\n");
return -1;
}
MainProcess(eCtx);
eXosip_quit(eCtx);
osip_free(eCtx);
eCtx = NULL;
return 0;
}
非常需要注意的是,内存泄漏,osip2 库与 eXosip2 库使用时,有很多地方都需要注意内存是否自动回收,若没有回收,需要自己手动回收内存。
比如上面代码中的函数,
osip_strdup_without_quote()
osip_www_authenticate_to_str()
库内部都没有回收内存,需要手动释放内存。内存泄漏检测,有一个 valgrind 工具,感觉挺好用,推荐!!
我在资源里面,上传了本代码的工程,编写好了makefile,可在 linux 下可以直接编译运行。请参考。