前面一篇已经介绍了客户端如何发些设备,下面这篇简单介绍下在发现设备后,如何通过ONVIF协议来获取设备的相关参数

ONVIF中不管是客户端还是设备端,最先实现的接口都是关于能力的那个接口,在客户端实现的函数名也就是[soap_call___tds__GetServiceCapabilities]通过获取的接口才知道设备具有那些能力,能够进行那些操作,服务端最基本的也需要实现这接口,让客户端知道设备支持那些基本操作。但是当设备端作了加密处理的话,即使你实现了这些接口,也不能正常获取到参数的,所以需要在获取设备参数之前,每次都需要作鉴权处理,这也是为什么我在前篇搜索的例程中把ONVIF_Initsoap这个接口函数里面的操作单独用一个函数接口来完成的原因。下面,我们就需要在这个接口函数里处理鉴权的问题了首先ONVIF鉴权是有两种方式的,一种是通过实现soap_wsse_add_UsernameTokenDigest函数,然后又实现soap_wsse_add_UsernameTokenText等一些复杂的函数接口,我第一次也是用这个鉴权方式做,但是太麻烦,而且最重要的是鉴权还是失败的,最后无奈网上各种问,终于发现了一个很简单直观的方法,虽然还是不明白为什么,但是确认达到了鉴权的效果了,在前篇中已经介绍过Onvif_Initsoap这个接口了鉴权的主要操作也是在这个接口里作的认证,具体鉴权实现如下:

  1. typedef struct
  2. {
  3. char username[64];
  4. char password[32];
  5. }UserInfo_S;
  6. static void ONVIF_GenrateDigest(unsigned char *pwddigest_out, unsigned char *pwd, char *nonc, char *time)
  7. {
  8. const unsigned char *tdist;
  9. unsigned char dist[1024] = {0};
  10. char tmp[1024] = {0};
  11. unsigned char bout[1024] = {0};
  12. strcpy(tmp,nonc);
  13. base64_64_to_bits((char*)bout, tmp);
  14. sprintf(tmp,"%s%s%s",bout,time,pwd);
  15. SHA1((const unsigned char*)tmp,strlen((const char*)tmp),dist);
  16. tdist = dist;
  17. memset(bout,0x0,1024);
  18. base64_bits_to_64(bout,tdist,(int)strlen((const char*)tdist));
  19. strcpy((char *)pwddigest_out,(const char*)bout);
  20. }
  21. //鉴权操作函数,以及上面的用到了openssl接口
  22. static struct soap* ONVIF_Initsoap(struct SOAP_ENV__Header *header, const char *was_To, const char *was_Action, int timeout, UserInfo_S *pUserInfo)
  23. {
  24. struct soap *soap = NULL;
  25. unsigned char macaddr[6];
  26. char _HwId[1024];
  27. unsigned int Flagrand;
  28. soap = soap_new();
  29. if(soap == NULL)
  30. {
  31. printf("[%d]soap = NULL\n", __LINE__);
  32. return NULL;
  33. }
  34. soap_set_namespaces( soap, namespaces);
  35. //超过5秒钟没有数据就退出
  36. if (timeout > 0)
  37. {
  38. soap->recv_timeout = timeout;
  39. soap->send_timeout = timeout;
  40. soap->connect_timeout = timeout;
  41. }
  42. else
  43. {
  44. //如果外部接口没有设备默认超时时间的话,我这里给了一个默认值10s
  45. soap->recv_timeout    = 10;
  46. soap->send_timeout    = 10;
  47. soap->connect_timeout = 10;
  48. }
  49. soap_default_SOAP_ENV__Header(soap, header);
  50. // 为了保证每次搜索的时候MessageID都是不相同的!因为简单,直接取了随机值
  51. srand((int)time(0));
  52. Flagrand = rand()%9000 + 1000; //保证四位整数
  53. macaddr[0] = 0x1; macaddr[1] = 0x2; macaddr[2] = 0x3; macaddr[3] = 0x4; macaddr[4] = 0x5; macaddr[5] = 0x6;
  54. sprintf(_HwId,"urn:uuid:%ud68a-1dd2-11b2-a105-%02X%02X%02X%02X%02X%02X",
  55. Flagrand, macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
  56. header->wsa__MessageID =(char *)malloc( 100);
  57. memset(header->wsa__MessageID, 0, 100);
  58. strncpy(header->wsa__MessageID, _HwId, strlen(_HwId));
  59. // 这里开始作鉴权处理了,如果有用户信息的话,就会处理鉴权问题
  60. //如果设备端不需要鉴权的话,在外层调用此接口的时候把User信息填空就可以了
  61. if( pUserInfo != NULL )
  62. {
  63. header->wsse__Security = (struct _wsse__Security *)malloc(sizeof(struct _wsse__Security));
  64. memset(header->wsse__Security, 0 , sizeof(struct _wsse__Security));
  65. header->wsse__Security->UsernameToken = (struct _wsse__UsernameToken *)calloc(1,sizeof(struct _wsse__UsernameToken));
  66. header->wsse__Security->UsernameToken->Username = (char *)malloc(64);
  67. memset(header->wsse__Security->UsernameToken->Username, '\0', 64);
  68. header->wsse__Security->UsernameToken->Nonce = (char*)malloc(64);
  69. memset(header->wsse__Security->UsernameToken->Nonce, '\0', 64);
  70. strcpy(header->wsse__Security->UsernameToken->Nonce,"LKqI6G/AikKCQrN0zqZFlg=="); //注意这里
  71. header->wsse__Security->UsernameToken->wsu__Created = (char*)malloc(64);
  72. memset(header->wsse__Security->UsernameToken->wsu__Created, '\0', 64);
  73. strcpy(header->wsse__Security->UsernameToken->wsu__Created,"2010-09-16T07:50:45Z");
  74. strcpy(header->wsse__Security->UsernameToken->Username, pUserInfo->username);
  75. header->wsse__Security->UsernameToken->Password = (struct _wsse__Password *)malloc(sizeof(struct _wsse__Password));
  76. header->wsse__Security->UsernameToken->Password->Type = (char*)malloc(128);
  77. memset(header->wsse__Security->UsernameToken->Password->Type, '\0', 128);
  78. strcpy(header->wsse__Security->UsernameToken->Password->Type,\
  79. "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
  80. header->wsse__Security->UsernameToken->Password->__item = (char*)malloc(128);
  81. ONVIF_GenrateDigest((unsigned char*)header->wsse__Security->UsernameToken->Password->__item,\
  82. (unsigned char*)pUserInfo->password,header->wsse__Security->UsernameToken->Nonce,header->wsse__Security->UsernameToken->wsu__Created);
  83. }
  84. if (was_Action != NULL)
  85. {
  86. header->wsa__Action =(char *)malloc(1024);
  87. memset(header->wsa__Action, '\0', 1024);
  88. strncpy(header->wsa__Action, was_Action, 1024);//"http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
  89. }
  90. if (was_To != NULL)
  91. {
  92. header->wsa__To =(char *)malloc(1024);
  93. memset(header->wsa__To, '\0', 1024);
  94. strncpy(header->wsa__To,  was_To, 1024);//"urn:schemas-xmlsoap-org:ws:2005:04:discovery";
  95. }
  96. soap->header = header;
  97. return soap;
  98. }

之所以把这些操作独立一个接口是因为每次在客户端获取参数之前都需要调用这个接口,需要是获取soap和就是鉴权操作了。实际上,整个鉴权的过程,通过这个来作是非常简单的,而且不需要那么多接口来越搞越乱,这是一个前辈指导我这么做的,但是具体为什么我也不清楚,如果有大神知道的话,可以交流下,不甚感激

下面我们就来看看鉴权的效果吧,看看获取这重要的一个接口,能力值获取的接口

实现如下:

  1. int ONVIF_Capabilities()  //获取设备能力接口
  2. {
  3. int retval = 0;
  4. struct soap *soap = NULL;
  5. struct _tds__GetCapabilities capa_req;
  6. struct _tds__GetCapabilitiesResponse capa_resp;
  7. struct SOAP_ENV__Header header;
  8. UserInfo_S stUserInfo;
  9. memset(&stUserInfo, 0, sizeof(UserInfo_S));
  10. //正确的用户名和错误的密码
  11. strcpy(stUserInfo.username, "admin");
  12. strcpy(stUserInfo.password, "admin");
  13. //此接口中作验证处理, 如果不需要验证的话,stUserInfo填空即可
  14. soap = ONVIF_Initsoap(&header, NULL, NULL, 5, &stUserInfo);
  15. char *soap_endpoint = (char *)malloc(256);
  16. memset(soap_endpoint, '\0', 256);
  17. //海康的设备,固定ip连接设备获取能力值 ,实际开发的时候,"172.18.14.22"地址以及80端口号需要填写在动态搜索到的具体信息
  18. sprintf(soap_endpoint, "http://%s:%d/onvif/device_service", "172.18.14.22", 80);
  19. capa_req.Category = (enum tt__CapabilityCategory *)soap_malloc(soap, sizeof(int));
  20. capa_req.__sizeCategory = 1;
  21. *(capa_req.Category) = (enum tt__CapabilityCategory)0;
  22. //此句也可以不要,因为在接口soap_call___tds__GetCapabilities中判断了,如果此值为NULL,则会给它赋值
  23. const char *soap_action = "http://www.onvif.org/ver10/device/wsdl/GetCapabilities";
  24. do
  25. {
  26. soap_call___tds__GetCapabilities(soap, soap_endpoint, soap_action, &capa_req, &capa_resp);
  27. if (soap->error)
  28. {
  29. printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
  30. retval = soap->error;
  31. break;
  32. }
  33. else   //获取参数成功
  34. {
  35. // 走到这里的时候,已经就是验证成功了,可以获取到参数了,
  36. // 在实际开发的时候,可以把capa_resp结构体的那些需要的值匹配到自己的私有协议中去,简单的赋值操作就好
  37. printf("[%s][%d] Get capabilities success !\n", __func__, __LINE__);
  38. }
  39. }while(0);
  40. free(soap_endpoint);
  41. soap_endpoint = NULL;
  42. soap_destroy(soap);
  43. return retval;
  44. }

我们知道,海康的设备用户名是"admin", 密码是"12345",所以上面第一次测试的话,我估计填写了错误的密码!

  1. //正确的用户名和错误的密码
  2. strcpy(stUserInfo.username, "admin");
  3. strcpy(stUserInfo.password, "admin");

  1. //正确的用户名和密码
  2. strcpy(stUserInfo.username, "admin");
  3. strcpy(stUserInfo.password, "12345");

上下两次结果对比很明显了,认证通过了,而且客户端也能正常获取能力值了!

如果顺利进行到这里了,客户端的参数部分开发基本也就差不多了,剩下的工作就是填充一些结构体以及复制一些代码工作了!

这里我说下自己在开发客户端的过程中的几个经验,后面也将继续完善客户端的代码:

1. 搜索固定端口3702,而且不可以跨网段的,虚拟机下也无法搜索,也能点对点的,也就是probe按钮操作。

2. 每次用soap_malloc分配新的空间的时候,一定记得清空操作,建议:宁愿多分配100个字节,也不要冒险纠结1个字节的!

3. 有些获取参数的或者设置参数的时候,需要填写对应的token,token也要不同的操作匹配不同的token值!profiles函数会告诉你所有操作的token值!

4 onvif开发是极其单调的工作,多是赋值粘贴的操作,请耐心点对待工作。

下面我添加下base64加密解密的两个接口函数!

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. static const char base64digits[] =
  4. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  5. #define BAD     -1
  6. static const signed char base64val[] = {
  7. BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
  8. BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
  9. BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD, 62, BAD,BAD,BAD, 63,
  10. 52, 53, 54, 55,  56, 57, 58, 59,  60, 61,BAD,BAD, BAD,BAD,BAD,BAD,
  11. BAD,  0,  1,  2,   3,  4,  5,  6,   7,  8,  9, 10,  11, 12, 13, 14,
  12. 15, 16, 17, 18,  19, 20, 21, 22,  23, 24, 25,BAD, BAD,BAD,BAD,BAD,
  13. BAD, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 36,  37, 38, 39, 40,
  14. 41, 42, 43, 44,  45, 46, 47, 48,  49, 50, 51,BAD, BAD,BAD,BAD,BAD
  15. };
  16. #define DECODE64(c)  (isascii(c) ? base64val[c] : BAD)
  17. void base64_bits_to_64(unsigned char *out, const unsigned char *in, int inlen)
  18. {
  19. for (; inlen >= 3; inlen -= 3)
  20. {
  21. *out++ = base64digits[in[0] >> 2];
  22. *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
  23. *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
  24. *out++ = base64digits[in[2] & 0x3f];
  25. in += 3;
  26. }
  27. if (inlen > 0)
  28. {
  29. unsigned char fragment;
  30. *out++ = base64digits[in[0] >> 2];
  31. fragment = (in[0] << 4) & 0x30;
  32. if (inlen > 1)
  33. fragment |= in[1] >> 4;
  34. *out++ = base64digits[fragment];
  35. *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c];
  36. *out++ = '=';
  37. }
  38. *out = '\0';
  39. }
  40. int base64_64_to_bits(char *out, const char *in)
  41. {
  42. int len = 0;
  43. register unsigned char digit1, digit2, digit3, digit4;
  44. if (in[0] == '+' && in[1] == ' ')
  45. in += 2;
  46. if (*in == '\r')
  47. return(0);
  48. do {
  49. digit1 = in[0];
  50. if (DECODE64(digit1) == BAD)
  51. return(-1);
  52. digit2 = in[1];
  53. if (DECODE64(digit2) == BAD)
  54. return(-1);
  55. digit3 = in[2];
  56. if (digit3 != '=' && DECODE64(digit3) == BAD)
  57. return(-1);
  58. digit4 = in[3];
  59. if (digit4 != '=' && DECODE64(digit4) == BAD)
  60. return(-1);
  61. in += 4;
  62. *out++ = (DECODE64(digit1) << 2) | (DECODE64(digit2) >> 4);
  63. ++len;
  64. if (digit3 != '=')
  65. {
  66. *out++ = ((DECODE64(digit2) << 4) & 0xf0) | (DECODE64(digit3) >> 2);
  67. ++len;
  68. if (digit4 != '=')
  69. {
  70. *out++ = ((DECODE64(digit3) << 6) & 0xc0) | DECODE64(digit4);
  71. ++len;
  72. }
  73. }
  74. } while (*in && *in != '\r' && digit4 != '=');
  75. return (len);
  76. }
  77. //简单的demo测试程序,实际可以不需要下面这个,只要上面两个函数接口就好!
  78. int main(void)
  79. {
  80. char p[] = "I Love You, Forever!";
  81. char test[48] = {0};
  82. base64_bits_to_64(test, p, sizeof("I Love You, Forever!"));
  83. printf("p = %s , test = %s \n", p, test);
  84. char a[48]= {0};
  85. base64_64_to_bits( a, test);
  86. printf("a = %s , test = %s \n", a, test);
  87. return 0;
  88. }

Onvif开发之客户端鉴权获取参数篇的更多相关文章

  1. EasyNVR摄像机网页H5全平台无插件直播流媒体播放服务二次开发之接口鉴权示例讲解

    背景需求 EasyNVR的使用者应该都清楚的了解到,EasyNVR一个强大的功能就是可以进行全平台的无插件直播.主要原因在于rtsp协议的视频流(默认是需要插件才可以播放的)经由EasyNVR处理可以 ...

  2. web开发常见的鉴权方式

    结合网上找的资料整理了一下,以下是web开发中常见的鉴权方法: 预备:一些基本的知识 RBAC(Role-Based Access Control)基于角色的权限访问控制(参考下面①的连接) l    ...

  3. SpringBoot 拦截器妙用,让你一个人开发整个系统的鉴权模块!

    我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复[资料],即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板. Han ...

  4. Vue-Router路由 Vue-CLI脚手架和模块化开发 之 使用路由对象获取参数

    使用路由对象$route获取参数: 1.params: 参数获取:使用$route.params获取参数: 参数传递: URL传参:例 <route-linke to : "/food ...

  5. Onvif开发之客户端搜索篇

    关于ONVIF的广播,有客户端搜索和服务端发现的区别:客户端向固定的网段和固定的端口发送广播消息,服务端在对应的端口回复广播请求消息本文首先介绍客户端如何进行广播的已经对广播回复的信息的基本处理. 客 ...

  6. 微信小程序云开发-云函数-云函数获取参数并实现运算

    1.编写加法运算的云函数addData 2.在本地小程序页面调用云函数

  7. ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)

    原文  http://blog.csdn.net/gubenpeiyuan/article/details/25618177   概要: 目前ONVIF协议家族设备已占据数字监控行业半壁江山以上,亲, ...

  8. 【视频开发】ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)

    转载地址:http://blog.csdn.net/gubenpeiyuan/article/details/25618177 概要:           目前ONVIF协议家族设备已占据数字监控行业 ...

  9. Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务:第一篇(内附开发 demo)

    简介 小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系. 系列 云原生 API 网关,gRPC-Gateway V2 初探 业务流程 官方开发接入文档 ...

随机推荐

  1. Android——PullToRefresh自动刷新

    需求:强制刷新 方法一: PullToRefreshListView本身提供了一个setRefreshing()接口,调用该接口会自动触发下拉刷新的操作(前提是支持下拉刷新).按照一般的操作我们直接在 ...

  2. vue中使用滚动效果

    new Vue({ el: '#app', data: function data() { return { bottom: false, beers: [] }; }, watch: { botto ...

  3. v2.0版本小程序开发心得(代码之外)

    总结一些代码之外的事情: 做优先该做的事情 分清主次,一天只有24小时,人的精力也是有限的,而且经常性的是,在某个时间爆发性的产生了大量的问题.这些问题集中的产生,需要一个一个的解决,但是人的精力是有 ...

  4. man帮助

    man命令是Linux下的帮助指令,通过man指令可以查看Linux中的指令帮助.配置文件帮助和编程帮助等信息.

  5. 紫书 例题 10-26 UVa 11440(欧拉函数+数论)

    这里用到了一些数论知识 首先素因子都大于M等价与M! 互质 然后又因为当k与M!互质且k>M!时当且仅当k mod M! 与M!互质(欧几里得算法的原理) 又因为N>=M, 所以N!为M! ...

  6. string.split 应用

    采用string.split将字符串依据分隔符,转换成字符串数组,生成的字符串数组中会包含空数组元素,需要通过StringSplitOptions.RemoveEmptyEntries参数选项去除. ...

  7. CMSIS-RTOS 信号量

    信号量Semaphores 和信号类似,信号量也是一种同步多个线程的方式,简单来讲,信号量就是装有一些令牌的容器.当一个线程在执行过程中,就可能遇到一个系统调用来获取信号量令牌,如果这个信号量包含多个 ...

  8. 【Educational Codeforces Round 37 A】 Water The Garden

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 记录下水龙头在哪些位置. 然后每秒钟把index-i和index+i改变状态一下就好(置1 [代码] #include <bi ...

  9. Get,Post和Head具体解释

    HTTP请求最经常使用的三个方法: (1)Get方法. 取回请求URL标志的不论什么信息,在浏览器的地址栏中输入网址的方式訪问网页时,浏览器採用GET方法向server获取资源. (2)Post方法. ...

  10. JAVA并发--volatile

    学过计算机组成原理的一定知道,为了解决内存速度跟不上CPU速度这个问题,在CPU的设计中加入了缓存机制,缓存的速度介于CPU和主存之间.在进行运算的时候,CPU将需要的数据映射一份在缓存中,然后直接操 ...