引言
Onvif规定,有些接口需要鉴权,有些节点不需要鉴权。那么,怎么知道哪些接口需要认证呢?
ONVIF哪些接口需要认证
在官网的ONVIF Core Specification文档中有详细的规定,如Version 16.12的版本为《ONVIF-Core-Specification-v1612.pdf》。在该文档的「Access classes for service requests」章节中有接口访问权限的相关规定,如下图所示。比如「PRE_AUTH」的规定是:The service shall not require user authentication,那非「PRE_AUTH」的就是都要认证的。
拿GetServices接口举个例子,在ONVIF Core Specification文档中找到GetServices接口定义(如下图所示),会有Access Class: PRE_AUTH的说明,表明客户端调用该接口时,不需要携带用户名、密码认证信息。
再看看GetDeviceInformation接口规定(如下图所示),Access Class: READ_SYSTEM,说明客户端调用该接口是需要进行认证。
实践
生成框架源码
1、编译gsoap,准备好环境
2、生成头文件
(1) 建立一个目录onvif-auth
,目录下有两个目录bin
和gsoap
(2) 编写脚本onvif_head.sh
,生成头文件
onvif-auth]$ ls
bin gsoap onvif_head.sh
onvif-auth]$ cat onvif_head.sh
#!/bin/bash
mkdir onvif_head
cd onvif_head
../bin/wsdl2h -o onvif.h -s -d -x -t ../gsoap/WS/typemap.dat \
http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl \
http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
onvif-auth]$ chmod 777 onvif_head.sh
onvif-auth]$ ./onvif_head.sh
Saving onvif.h
** The gSOAP WSDL/WADL/XSD processor for C and C++, wsdl2h release 2.8.117
** Copyright (C) 2000-2021 Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The wsdl2h tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
Reading type definitions from type map "../gsoap/WS/typemap.dat"
Connecting to 'http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl' to retrieve WSDL/WADL or XSD... connected, receiving...
Done reading 'http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl'
Connecting to 'http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl' to retrieve WSDL/WADL or XSD... connected, receiving...
Connecting to 'http://www.onvif.org/onvif/ver10/schema/onvif.xsd' to retrieve schema... connected, receiving...
Connecting to 'http://docs.oasis-open.org/wsn/b-2.xsd' to retrieve schema... connected, receiving...
Connecting to 'http://docs.oasis-open.org/wsrf/bf-2.xsd' to retrieve schema... connected, receiving...
Done reading 'http://docs.oasis-open.org/wsrf/bf-2.xsd'
Connecting to 'http://docs.oasis-open.org/wsn/t-1.xsd' to retrieve schema... connected, receiving...
Done reading 'http://docs.oasis-open.org/wsn/t-1.xsd'
Done reading 'http://docs.oasis-open.org/wsn/b-2.xsd'
Connecting to 'http://www.onvif.org/onvif/ver10/schema/common.xsd' to retrieve schema... connected, receiving...
Done reading 'http://www.onvif.org/onvif/ver10/schema/common.xsd'
Done reading 'http://www.onvif.org/onvif/ver10/schema/onvif.xsd'
Done reading 'http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl'
Note: option -p auto-enabled to generate wrappers for built-in types derived from xsd__anyType to support polymorphism in XML by (de)serializing any derived type of xsd:anyType (or xsd:anySimpleType) as elements annotated by xsi:type attributes in XML, use option -P to suppress and disable this feature
Warning: 2 service bindings found, but collected as one service (use option -Nname to produce a separate service for each binding)
To finalize code generation, execute:
> soapcpp2 onvif.h
Or to generate C++ proxy and service classes:
> soapcpp2 -j onvif.h
(3)修改上面生成的onvif.h
: 在onvif.h
头文件开头加入:
#import "wsse.h"
注意是import而不是include
打开wsse.h,可以看到相应依赖
注意:依赖一定要自己去看,否则会出现一大堆未定义引用。我在这里困扰了两天
错误: Cannot connect to https site: SSL/TLS support not enabled in this version
。 因为wsdl2h 编译时没有关闭了openssl,所以不能https下载: 将https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
,改成http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
就可以了
(2) 编写脚本gen_code.sh
, 根据上面生成的头文件生成源文件
onvif-auth]$ ls
bin gen_code.sh gsoap onvif_head onvif_head.sh
onvif-auth]$ cat gen_code.sh
#!/bin/bash
DIR=soap
mkdir $DIR
cd $DIR
../bin/soapcpp2 -2 -x -C ../onvif_head/onvif.h -L -I ../gsoap/import -I ../gsoap/
#----------执行-------------------
onvif-auth]$ chmod 777 gen_code.sh
onvif-auth]$ ./gen_code.sh
** The gSOAP code generator for C and C++, soapcpp2 release 2.8.117
** Copyright (C) 2000-2021, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The soapcpp2 tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
wsa5.h(280): *WARNING*: Duplicate declaration of 'SOAP_ENV__Fault' (already declared at line 268)
wsa5.h(290): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' already declared at wsa.h:278
soap12.h(54): *WARNING*: option -1 or -2 overrides SOAP-ENV namespace
soap12.h(55): *WARNING*: option -1 or -2 overrides SOAP-ENC namespace
Saving soapStub.h annotated copy of the source interface header file
Saving soapH.h serialization functions to #include in projects
Using wsdd service name: wsdd
Using wsdd service style: document
Using wsdd service encoding: literal
Using wsdd schema import namespace: http://schemas.xmlsoap.org/ws/2005/04/discovery
Saving wsdd.nsmap namespace mapping table
Using tdn service name: RemoteDiscoveryBinding
Using tdn service style: document
Using tdn service encoding: literal
Using tdn schema namespace: http://www.onvif.org/ver10/network/wsdl
Saving RemoteDiscoveryBinding.nsmap namespace mapping table
Using tds service name: DeviceBinding
Using tds service style: document
Using tds service encoding: literal
Using tds schema namespace: http://www.onvif.org/ver10/device/wsdl
Saving DeviceBinding.nsmap namespace mapping table
Saving soapClient.cpp client call stub functions
Saving soapC.cpp serialization functions
There were errors:
1 semantic error
3 warnings
可以看到,上面出现了错误:
wsa5.h(290): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' already declared at wsa.h:278
解决:打开 gsoap\import 路径下的wsa5.h, 将277行的SOAP_ENV__Fault结构体注释掉(改成其他名字亦可)
重新执行:
#----------执行-------------------
onvif-auth]$ chmod 777 gen_code.sh
onvif-auth]$ ./gen_code.sh
mkdir: 无法创建目录"soap": 文件已存在
** The gSOAP code generator for C and C++, soapcpp2 release 2.8.117
** Copyright (C) 2000-2021, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The soapcpp2 tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
soap12.h(54): *WARNING*: option -1 or -2 overrides SOAP-ENV namespace
soap12.h(55): *WARNING*: option -1 or -2 overrides SOAP-ENC namespace
Saving soapStub.h annotated copy of the source interface header file
Saving soapH.h serialization functions to #include in projects
Using wsdd service name: wsdd
Using wsdd service style: document
Using wsdd service encoding: literal
Using wsdd schema import namespace: http://schemas.xmlsoap.org/ws/2005/04/discovery
Saving wsdd.nsmap namespace mapping table
Using tdn service name: RemoteDiscoveryBinding
Using tdn service style: document
Using tdn service encoding: literal
Using tdn schema namespace: http://www.onvif.org/ver10/network/wsdl
Saving RemoteDiscoveryBinding.nsmap namespace mapping table
Using tds service name: DeviceBinding
Using tds service style: document
Using tds service encoding: literal
Using tds schema namespace: http://www.onvif.org/ver10/device/wsdl
Saving DeviceBinding.nsmap namespace mapping table
Saving soapClient.cpp client call stub functions
Saving soapC.cpp serialization functions
Compilation successful (2 warnings)
可以看到编译成功
编写代码
1、为了方便,我们将所有需要的代码放到同一个目录中。我们在刚才建立的onvif-auth
目录下创建一个application
目录,存放我们需要的所有代码(比如soap下生成的soapC.cpp、soapClient.cpp、soapH.h、soapStub.h、wsdd.nsmap;gsoap源码目录下的stdsoap2.cpp、stdsoap2.h,gsoap/plugin目录下的wsseapi.h、wsseapi.cpp、smdevp.h、smdevp.cpp、mecevp.cpp、mecevp.h、threads.cpp、threads.h、wsaapi.cpp、wsaapi.h等)。并创建一个main.cpp,如下(注意,如果只有.c没有.cpp的,那么就将其拷贝到application目录下,然后将尾部改成.cpp):
2、在application
目录创建一个CMakeLists.txt
文件,CMakeLists.txt
文件内容如下:
cmake_minimum_required(VERSION 3.16)
project(onvif-auth)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWITH_NONAMESPACES -DWITH_DOM -DWITH_OPENSSL")
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(/usr/include)
add_definitions("-Wall -g ")
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST} soapStub.h)
target_link_libraries(${PROJECT_NAME} -lcrypto -lssl)
main.cpp
内容如下:
#include <assert.h>
#include "soapH.h"
#include "wsdd.nsmap"
#include "soapStub.h"
#define SOAP_ASSERT assert
#define SOAP_DBGLOG printf
#define SOAP_DBGERR printf
#define SOAP_TO "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_ACTION "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"
#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702" // onvif规定的组播地址
#define SOAP_ITEM "" // 寻找的设备范围
#define SOAP_TYPES "dn:NetworkVideoTransmitter" // 寻找的设备类型
#define SOAP_SOCK_TIMEOUT (10) // socket超时时间(单秒秒)
void soap_perror(struct soap *soap, const char *str)
{
if (nullptr == str) {
SOAP_DBGERR("[soap] error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
} else {
SOAP_DBGERR("[soap] %s error: %d, %s, %s\n", str, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
}
}
void* ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{
void *p = nullptr;
if (n > 0) {
p = soap_malloc(soap, n);
SOAP_ASSERT(nullptr != p);
memset(p, 0x00 ,n);
}
return p;
}
struct soap *ONVIF_soap_new(int timeout)
{
struct soap *soap = nullptr; // soap环境变量
SOAP_ASSERT(nullptr != (soap = soap_new()));
soap_set_namespaces(soap, namespaces); // 设置soap的namespaces
soap->recv_timeout = timeout; // 设置超时(超过指定时间没有数据就退出)
soap->send_timeout = timeout;
soap->connect_timeout = timeout;
#if defined(__linux__) || defined(__linux) // 参考https://www.genivia.com/dev.html#client-c的修改:
soap->socket_flags = MSG_NOSIGNAL; // To prevent connection reset errors
#endif
soap_set_mode(soap, SOAP_C_UTFSTRING); // 设置为UTF-8编码,否则叠加中文OSD会乱码
return soap;
}
void ONVIF_soap_delete(struct soap *soap)
{
soap_destroy(soap); // remove deserialized class instances (C++ only)
soap_end(soap); // Clean up deserialized data (except class instances) and temporary data
soap_done(soap); // Reset, close communications, and remove callbacks
soap_free(soap); // Reset and deallocate the context created with soap_new or soap_copy
}
/************************************************************************
**函数:ONVIF_init_header
**功能:初始化soap描述消息头
**参数:
[in] soap - soap环境变量
**返回:无
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
struct SOAP_ENV__Header *header = nullptr;
SOAP_ASSERT(nullptr != soap);
header = (struct SOAP_ENV__Header *)ONVIF_soap_malloc(soap, sizeof(struct SOAP_ENV__Header));
soap_default_SOAP_ENV__Header(soap, header);
header->wsa__MessageID = "shsuishsihsishsishsuisbjshsusisuisusi";
header->wsa__To = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TO) + 1);
header->wsa__Action = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ACTION) + 1);
strcpy(header->wsa__To, SOAP_TO);
strcpy(header->wsa__Action, SOAP_ACTION);
soap->header = header;
}
/************************************************************************
**函数:ONVIF_init_ProbeType
**功能:初始化探测设备的范围和类型
**参数:
[in] soap - soap环境变量
[out] probe - 填充要探测的设备范围和类型
**返回:
0表明探测到,非0表明未探测到
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe)
{
struct wsdd__ScopesType *scope = nullptr; // 用于描述查找哪类的Web服务
SOAP_ASSERT(nullptr != soap);
SOAP_ASSERT(nullptr != probe);
scope = (struct wsdd__ScopesType *)ONVIF_soap_malloc(soap, sizeof(struct wsdd__ScopesType));
soap_default_wsdd__ScopesType(soap, scope); // 设置寻找设备的范围
scope->__item = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ITEM) + 1);
strcpy(scope->__item, SOAP_ITEM);
memset(probe, 0x00, sizeof(struct wsdd__ProbeType));
soap_default_wsdd__ProbeType(soap, probe);
probe->Scopes = scope;
probe->Types = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TYPES) + 1); // 设置寻找设备的类型
strcpy(probe->Types, SOAP_TYPES);
}
void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{
int i;
int result = 0;
unsigned int count = 0; // 搜索到的设备个数
struct soap *soap = nullptr; // soap环境变量
struct wsdd__ProbeType req; // 用于发送Probe消息
struct __wsdd__ProbeMatches rep; // 用于接收Probe应答
struct wsdd__ProbeMatchType *probeMatch;
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_init_header(soap); // 设置消息头描述
ONVIF_init_ProbeType(soap, &req); // 设置寻找的设备的范围和类型
result = soap_send___wsdd__Probe(soap, SOAP_MCAST_ADDR, nullptr, &req); // 向组播地址广播Probe消息
while (SOAP_OK == result) // 开始循环接收设备发送过来的消息
{
memset(&rep, 0x00, sizeof(rep));
result = soap_recv___wsdd__ProbeMatches(soap, &rep);
if (SOAP_OK == result) {
if (soap->error) {
soap_perror(soap, "ProbeMatches");
} else { // 成功接收到设备的应答消息
printf("__sizeProbeMatch:%d\n",rep.wsdd__ProbeMatches->__sizeProbeMatch);
if (nullptr != rep.wsdd__ProbeMatches) {
count += rep.wsdd__ProbeMatches->__sizeProbeMatch;
for(i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++) {
probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;
if (nullptr != cb) {
cb(probeMatch->XAddrs); // 使用设备服务地址执行函数回调
}
}
}
}
} else if (soap->error) {
break;
}
}
SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
}
#define SOAP_CHECK_ERROR(result, soap, str) \
do { \
if (SOAP_OK != (result) || SOAP_OK != (soap)->error) { \
soap_perror((soap), (str)); \
if (SOAP_OK == (result)) { \
(result) = (soap)->error; \
} \
goto EXIT; \
} \
} while (0)
#define USERNAME "admin"
#define PASSWORD "hik12345"
#include "wsseapi.h"
/************************************************************************
**函数:ONVIF_SetAuthInfo
**功能:设置认证信息
**参数:
[in] soap - soap环境变量
[in] username - 用户名
[in] password - 密码
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
int result = 0;
SOAP_ASSERT(nullptr != username);
SOAP_ASSERT(nullptr != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}
/************************************************************************
**函数:ONVIF_GetDeviceInformation
**功能:获取设备基本信息
**参数:
[in] DeviceXAddr - 设备服务地址
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
int result = 0;
struct soap *soap = nullptr;
_tds__GetDeviceInformation devinfo_req;
_tds__GetDeviceInformationResponse devinfo_resp;
SOAP_ASSERT(nullptr != DeviceXAddr);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, nullptr, &devinfo_req, devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
std::cout << " Manufacturer:\t" << devinfo_resp.Manufacturer << "\n";
std::cout << " Model:\t" << devinfo_resp.Model << "\n";
std::cout << " FirmwareVersion:\t" << devinfo_resp.FirmwareVersion << "\n";
std::cout << " SerialNumber:\t" << devinfo_resp.SerialNumber << "\n";
std::cout << " HardwareId:\t" << devinfo_resp.HardwareId << "\n";
EXIT:
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
void cb_discovery(char *DeviceXAddr)
{
ONVIF_GetDeviceInformation(DeviceXAddr);
}
int main(int argc, char **argv)
{
ONVIF_DetectDevice(cb_discovery);
return 0;
}
3、在application
创建一个build
目录,进入build
目录之后:
cmake ..
make
对应代码
因为鉴权比较麻烦,所以我上传了一个示例工程: 代码,可以零积分下载,需要自取