前面一篇已经介绍了客户端如何发些设备,下面这篇简单介绍下在发现设备后,如何通过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. 机器学习规则:ML工程最佳实践----rules_of_ml section 2【翻译】

    作者:黄永刚 ML Phase II: 特征工程 第一阶段介绍了机器学习的一个周期,为学习系统获取训练数据,通过有趣的引导设计指标,创建一个服务框架.在有了一个完整系统之后,就进入了第一阶段. 第二阶 ...

  2. c# IndexOf()用法

    IndexOf()用法 查找字符串中字符或者字符串首次出现的位置,返回的是索引值; str1.indexOf('字');//查找“字”在字符串中首次出现的索引值 str1.indexOf(" ...

  3. linux上将另一个文件内容快速写入正在编辑的文件内

    一.我们看到 www 目录下有两个文件.like.php 内有一行字母,而 loo.php 内什么也没有. 二 .我们来编辑 loo.php. 三.用下面的命令将 like.php 的内容复制到 lo ...

  4. yii2.0中使用jquery

    我们都知道 yii 框架是组件式开发的,使用 jquery 也是非常简单的.只需要注册一下就可以使用非常简单的 jquery 代码了! <?php $this->beginBlock('s ...

  5. 【agc004f】Namori Grundy

    那个问一下有人可以解释以下这个做法嘛,看不太懂QwQ~ Description 有一个n个点n条边的有向图,点的编号为从1到n. 给出一个数组p,表明有(p1,1),(p2,2),…,(pn,n)这n ...

  6. js cookie 页面倒计时

    疯了啦 写了一篇没有保存需求:页面倒计时 只从第一次加购开始公共方法cookie的设置 获取function getCookie(c_name){ if (document.cookie.length ...

  7. 【ICM Technex 2018 and Codeforces Round #463 (Div. 1 + Div. 2, combined) B】Recursive Queries

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 写个记忆化搜索. 接近O(n)的复杂度吧 [代码] #include <bits/stdc++.h> using nam ...

  8. Ehcache学习总结(2)--Ehcache整合spring配置

    首先需要的maven依赖为: [html] view plain copy <!--ehcache--> <dependency> <groupId>com.goo ...

  9. HDFS文件系统上传时序图 PB级文件存储时序图

    自己设计的时序图. 来自为知笔记(Wiz)

  10. Spring Tool Suit安装virgo server插件、virgo的下载

    virgo-tomcat原先是Spring DM Server,后来转eclipse社区维护 安装教程:http://osgi.com.cn/article/7289514 virgo-tomcat各 ...