前面一篇已经介绍了客户端如何发些设备,下面这篇简单介绍下在发现设备后,如何通过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. bootstrap如何自定义5列

    废话少说,先上代码: <!DOCTYPE html><html> <head> <meta charset="utf-8"> < ...

  2. TCP学习前的准备——可靠数据传输协议

    由于传输层所依赖的网络层是不可靠的,通过逐渐考虑实际情况不断引入新技术来实现可靠数据传输. 完全可信的信道 有比特差错的信道 新的协议功能: 1.    差错检测:检验和 2.    接收方反馈:序号 ...

  3. cygwin下调用make出现的奇怪现象

    <lenovo@root 11:48:03> /cygdrive/d/liuhang/GitHub/rpi_linux/linux$make help 1 [main] make 4472 ...

  4. C++输入流

    输出流基本概念  输出流定义在头文件中.大部分程序都会包含头文件,这个头文件又包含了输入流和输出流头文件.头文件还声明了标准控制台输出流cout.  使用输出流的最简单的方法是使用<<运算 ...

  5. Unity Shader (四)顶点程序示例

    1.在顶点函数中实现凸起效果 Shader "Custom/Example" { properties { _R(,))= //圆的半径,也是凸起的范围 _OX(,))= //x轴 ...

  6. 【Round #36 (Div. 2 only) B】Safe Spots

    [题目链接]:https://csacademy.com/contest/round-36/task/safe-spots/ [题意] 给你n个数字构成的序列; 每个位置上的数都由0和1组成; 对于每 ...

  7. 【转】 基于C#.NET的高端智能化网络爬虫

    [转] 基于C#.NET的高端智能化网络爬虫 前两天朋友发给我了一篇文章,是携程网反爬虫组的技术经理写的,大概讲的是如何用他的超高智商通过(挑衅.怜悯.嘲讽.猥琐)的方式来完美碾压爬虫开发者.今天我就 ...

  8. Linux下无需输入password自己主动登陆sshserver方法

    用OpenSSH在linux下登陆sshserver时.每次都提示要输入password,并且使用vim 的netrw插件编辑远程文件时每次改动后保存都要输password,很麻烦. 查看了netrw ...

  9. scikit-learn:3.2. Grid Search: Searching for estimator parameters

    參考:http://scikit-learn.org/stable/modules/grid_search.html GridSearchCV通过(蛮力)搜索參数空间(參数的全部可能组合).寻找最好的 ...

  10. 八款常用的 Python GUI 开发框架推荐

    作为Python开发者,你迟早都会用到图形用户界面来开发应用.本文将推荐一些 Python GUI 框架,希望对你有所帮助,如果你有其他更好的选择,欢迎在评论区留言. Python 的 UI 开发工具 ...