UsernameToken 【转】

原文:http://idior.cnblogs.com/articles/381534.html

使用用户名和密码来验证用户的身份是最普通也最常见的方法,虽然在安全性方面也比较弱,由于其运用的广泛性还是成为了WS-Security目前所支持的Security Token之一。其原理非常简单,用户在发送请求的时候,在Soap head中加入自己的用户名以及密码,接受请求的Service通过之前与Client建立的共享密码来验证密码的合法性从而实现鉴别用户的功能。
不过实际运用起来就不能考虑的那么简单了,该方法主要存在两个问题: 1. 在SOAP包中传输密码怎么保证密码的安全性? 2. 怎么从用户名密码中获得签名和加密所需要的密钥?

针对第一个问题有三种解决方案: 1. 使用运输层的安全协议(如SSL)来保证明文密码的安全性。 2. 对明文密码做摘要后再传送给Service。 3. 利用从密码派生出来的密钥来代替直接使用密码来实现身份鉴别。

第一种方法采用了WS-Security结合运输层的安全协议(SSL)来保证密码的安全,在本系列一开始的文章已经描述过运输层安全协议的缺点,所以在此不对该方法做详细介绍。

第二种方法类似于HTTP Digest Authentication,先来看一段使用该方法的示例:
<wsse:UsernameToken>     <wsse:Username>NNK</wsse:Username>     <wsse:Password Type="...#PasswordDigest">          weYI3nXd8LjMNVksCKFV8t3rgHh3Rw==     </wsse:Password>     <wsse:Nonce>WScqanjCEAC4mQoBE07sAQ==</wsse:Nonce>     <wsu:Created>2003-07-16T01:24:32Z</wsu:Created> </wsse:UsernameToken> 从中看出这里使用了PasswordDigest类型的Password,从Password的内容也可以看出这里没有使用明文密码的形式。另外还多出了wsse:Nonce和wsu:Created两个元素。 其中Password的内容的计算公式如下: Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
读者可能比较奇怪wsse:Nonce和wsu:Created这两个元素的作用。为什么不直接SHA-1(password) ? 这样做是为了避免重放(Replay)攻击。假设Alice以摘要的形式向Service发送了密码,如果Bob此时截获了Alice发送的密码摘要,然后再用它向Service发送请求,那么Service将误认为Bob也是合法用户。当我们加入Nonce和Created元素之后,Service可以检查收到的消息中的Nonce是否已经收到过了,或者在一段时间(5min)内是否收到了相同用户名密码,从而避免重放攻击的危险。不过使用PasswordDigest方式要求Service必须拥有密码的明文形式,也就是说Service可以看到每个用户的密码,这对用户来说增加了风险。因为通常情况下用户的密码是以hash的形式保存在Service端的,从而保证用户的信息不被泄漏。 尽管通过PasswordDigest可以避免密码的明文传播,而且通过引入wsse:Nonce和wsu:Created可以避免重放攻击的危险,但是如果Bob能够把传送中的密码摘要完全的拦截下来(使它无法传送到Service),然后利用拦截下来的密码去冒充Alice去请求Service,那么Service将束手无策。为此,我们引入了第三种方法。

第三种方法和Kerberos协议中KDC向Client传送TGT的方式类似。 我们可以看出前两种方式用户都将自己的密码发送给Service用于身份鉴别,难道为了证明自己的身份就必须把密钥(这里是密码)直接告诉别人吗?其实问题的关键在于Client能向Service证明它拥有只有C与S知道的密钥。而证明拥有的最直接方法就是告诉对方这个密钥,然后由Service比较这个密钥是否和它所知道的密钥一致,从而鉴别用户的身份。但是这种方法如前所述存在多种缺陷。既然仅仅需要Client证明它知道这个密钥,那么Client可以用这个密钥对一段消息做一个签名,然后将消息和签名同时发送给Service,Service用它所知道的Client的密钥也对同样的消息做一次签名,通过比较两个签名是否一致就可以确认Client是否真的拥有它的密钥。同样通过加密的方法Client也可以向Service证明自己是否真的拥有密钥(因为只有C与S密钥一致Service才能解密出用C密钥加密的消息)。这样一旦Client在消息中加入自己的一些特有信息(比如IP),即便Bob截获了消息但是由于他并不知道真正的密钥,看不到那些特有信息,也就无法冒充Alice。

通过这种间接证明拥有密钥的方法,我们同时解决了文章一开始提出的第二个问题: 怎么从用户名密码中获得签名和加密所需要的密钥?
只要对密码做一些处理就可以从中派生出密钥。当然为了安全起见我们希望每次派生出来的密钥都不一样,这样就可以避免多次使用同一密钥而导致密钥被破解。下面就是WS-Security对密钥派生的元素定义:
<wsse:UsernameToken wsse:Id=”…”>     <wsse:Username>…</wsse:Username>     <wsse11:Salt>…</wsse11:Salt>     <wsse11:Iteration>…</wsse11:Iteration> </wsse:UsernameToken> 其中Salt是导致密钥变化的因子,Iteration是密钥派生时Hash的次数。 密码的派生公式如下: K1 = SHA1( password + Salt)  K2 = SHA1( K1)    … Kn = SHA1 ( Kn-1)

可以看到此时在UsernameToken已经不再包含Password元素,因为Client将通过使用从Password派生出密钥做签名做加密的方式来证明它拥有密钥,从而证明自己的身份。

由此看出第三种办法相对来说安全性大大提高了,但是在实际应用中以上介绍的三种的方法都不被推荐使用。 第三种方法仍旧存在以下两个缺陷: 1. 直接使用密码派生密钥,同以往临时产生的会话密钥相比,密码一旦破解,所有由改密码派生的密钥也被破解。由于密码长期不变, 那么随后所有使用该密码加密的消息都没有安全性可言。而且该密码可能还被用于Client与其他Service的交互,那么被破解后带来的损失就大多了。 2. 用户密码必须以明文形式保存在Service端。

因此,在微软的WSE对安全的默认支持方式中采用了UsernameToken和Service端Certification的组合的方式来表示Security Token。下图就是WSE中已经实现的UsernameForCertificate对SOAP Envelop的扩展结构。
             

从中可以看到SOAP Head中的wsse:UsernameToken已经被加密,被xenc:EncryptedData所替代,查看其Token Reference发现加密使用的Key来自xenc:EncryptedKey。如果你完整阅读了本系列的文章,你将不会对它太陌生,在XML Encryption中曾经对它的来由做了详细介绍。

      

Note由于使用对称密钥加密效率高,所以通常会使用对称密钥来加密数据,但是如何让消息的接受也获得对称密钥则成了一个问题。消息发送方不可能将对称密钥也随消息传递给消息接收方,此时利用非对称密钥来实现加密所用的对称密钥的传递成为了一个比较好的选择。EncrptedKey就是实现此种功能的扩展元素。

Client随机产生了一个对称密钥并用它来加密和签名SOAP Envelop中的其他元素(如UsernameToken),然后通过使用Service(消息接受方)的公钥(由于是公钥可以方便获取)来加密该对称密钥,以保证只有Service能够获得Client随机产生的对称密钥,从而达到验证消息完整性,解密数据以及鉴别用户身份的目的。以下是采用这种方式保证安全的SOAP Envelop的示例:
<?xml version="1.0" encoding="utf-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"     xmlns:ds="http://www.w3.org/2000/09/xmldsig#"     xmlns:xenc="http://www.w3.org/2001/04/xmlenc"     xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility">     <SOAP-ENV:Header>         <wsse:Security             xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/secext">             <wsse:UsernameToken>                 <wsse:Username>HotelService</wsse:Username>                 <wsse:Password>myword</wsse:Password>                                       </wsse:UsernameToken>             <xenc:EncryptedKey wsu:id="userSysmetricKey">                 <xenc:EncryptionMethod                     Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>                  <ds:KeyInfo>                        <wsse:SecurityTokenReference>                            <wsse:KeyIdentifier                               ValueType="...oasis-wss-soap-message-security-1.1#ThumbPrintSHA1">                                     LKiQ/CmFrJDJqCLFcjlhIsmZ/+0=                            </wsse:KeyIdentifier>                         </wsse:SecurityTokenReference>                     </ds:KeyInfo>                 <xenc:CipherData>                     <xenc:CipherValue>G2wDCq24FsgBGerE...</xenc:CipherValue>                 </xenc:CipherData>                 <xenc:ReferenceList>                     <xenc:DataReference URI="#DiscountResponse"/>                 </xenc:ReferenceList>             </xenc:EncryptedKey>             <ds:Signature>                 <ds:SignedInfo>                     <ds:CanonicalizationMethod                         Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>                     <ds:SignatureMethod                         Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>                     <ds:Reference URI="#DiscountedBookingForPartnersResponse">                         <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>                         <ds:DigestValue>JwFsd3eQc0iXlJm5PkLh7...</ds:DigestValue>                     </ds:Reference>                 </ds:SignedInfo>                 <ds:SignatureValue>BSxlJbSiFdm5Plhk...</ds:SignatureValue>                     <ds:KeyInfo>                                             <wsse:SecurityTokenReference>                                <wsse:Reference URI="#userSysmetricKey"                             ValueType="...oasis-wss-soap-message-security-1.1#EncryptedKey"/>                           </wsse:SecurityTokenReference>                     </ds:KeyInfo>             </ds:Signature>         </wsse:Security>     </SOAP-ENV:Header>     <SOAP-ENV:Body wsu:Id="DiscountedBookingForPartnersResponse">         <s:GetSpecialDiscountedBookingForPartnersResponse                 xmlns:s="http://www.MyHotel.com/partnerservice ">             <xenc:EncryptedData                 wsu:Id="DiscountResponse"                 type="http://www.w3.org/2001/04/xmlenc#Element">                 <xenc:EncryptionMethod                     Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc "/>                 <CipherData>                 <CipherValue>XD6sFa0DrWsHdehrHdhcW0x...</CipherValue>                 </CipherData>             </xenc:EncryptedData>         </s:GetSpecialDiscountedBookingForPartnersResponse>     </SOAP-ENV:Body> </SOAP-ENV:Envelope>     

       

Note以上介绍的方法UsernameForCertificate仅被WS-Security1.1支持。因为在1.1中才支持使用对称密钥签名。

如此,之前所提到的问题都可以解决了,让我们回顾一下: Q: 在SOAP包中传输密码怎么保证密码的安全性? A: 使用密钥加密,只有接受方能解密密码。

Q: 怎么从用户名密码中获得签名和加密所需要的密钥? A:  随机产生密钥,并通过接受方的公钥加密,保证密钥不被别人所知。

Q: 如何避免重放攻击? A: 由于其他人无法获得密钥,所以即便截获消息也无法冒充。

Q: 直接使用密码派生密钥被破解了怎么办? A: 密钥不再从密码派生,而是每次随机产生,即便被破解也不会影响其他的消息和其他的服务。

Q: 用户密码必须以明文形式保存在Service端? A: 由于密码被加密而不是做摘要所以不需要Service拥有明文密码。
应用场景: B2C网上购物,每个用户都有各自的用户名密码,而且可以方便的获得Server端的Certification。(比如Amazon)
参考资料: OASIS Kerberos Token Profile 1.1 Protect Your Web Services Through The Extensible Policy Framework In WSE 3.0

转载于:https://www.cnblogs.com/yf2011/p/5493063.html

onvif_passthrough:/****************************************************************************** * Copyright (c) 2018-2018 TP-Link Systems Inc. * * Filename: imaging.c * Version: 1.0 * Description: Imaging 请求中,与透传相关的指令处理接口 * Author: yexuelin<yexuelin@tp-link.com.cn> * Date: 2019-03-14 ******************************************************************************/ #include <stdio.h> #include "onvif_passthrough.h" #include "libds.h" #include "soap_auth.h" #include "soap_pack.h" #include "soap_timg.h" #include "soap_parse.h" #include "soap_tptz.h" #define MAX_INTEGER_STR_LEN 32 /*最大整数字符串长度*/ #define IMAGE_STR "image" #define COMMON_STR "common" #define START_FOCUS_STR "start_focus" #define STOP_FOCUS_STR "stop_focus" #define START_IRIS_STR "start_iris" #define VELOCITY_STR "velocity" #define TIMG_IMG_SETTING "timg:ImagingSettings" #define TIMG_GET_IMG_SETTINGS "timg:GetImagingSettings" #define TIMG_SET_IMG_SETTINGS "timg:SetImagingSettings" #define TIMG_GET_SETTING_RSP "timg:GetImagingSettingsResponse" #define TIMG_SET_SETTING_RSP "timg:SetImagingSettingsResponse" #define TIMG_MOVE "timg:Move" #define TIMG_STOP "timg:Stop" #define TIMG_MOVE_RSP "timg:MoveResponse" #define TIMG_STOP_RSP "timg:StopResponse" /* 功能:构建一个获取图像设置的JSON请求对象。 输入:SOAP_CONTEXT指针(包含请求信息),输出参数json_req(用于返回构建的JSON对象),模块名和节名。 输出:返回OK或ERROR。 过程: a. 进行鉴权(检查是否有操作员权限)。 b. 创建JSON对象,添加方法为"get",并添加指定的模块和节。 c. 将构建的JSON对象通过json_req返回。*/ LOCAL S32 get_json_settings(SOAP_CONTEXT *soap, JSON_OBJPTR *json_req, char *module_name, char *sec_name) { JSON_OBJPTR json_data = NULL; JSON_OBJPTR json_sec = NULL; if (soap == NULL || json_req == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ONVIF_ERROR("Auth failed\n"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); soap->error = 400; return ERROR; } json_data = jso_new_obj(); json_sec = jso_new_obj(); if (json_data == NULL || json_sec == NULL) { ONVIF_WARN("jso_new_obj error."); jso_free_obj(json_data); jso_free_obj(json_sec); return ERROR; } jso_add_string(json_data, DS_METHOD_STR, METHOD_GET_STR); jso_obj_add(json_data, module_name, json_sec); jso_add_string(json_sec, DS_SECTION_STR, sec_name); *json_req = json_data; return OK; } /*------------------------- 获取图像参数 ----------------------------------*/ /* 功能:生成获取图像设置的SOAP响应(XML格式)。 输入:xml_buf(输出缓冲区),data(未使用)。 输出:返回OK或ERROR。 过程: a. 从设备服务(ds_read)读取当前的图像通用设置(亮度、饱和度、对比度、锐度)和模块规格(判断是否支持光圈优先)。 b. 开始构建XML响应,按照ONVIF标准格式输出各个图像参数。 c. 如果设备支持光圈优先(iris_first),则输出曝光模式(手动、自动或光圈优先)和当前的光圈值。 d. 如果设备支持变焦(通过tptz_get_local_capability判断),则输出聚焦相关的参数(自动对焦模式、默认速度、近限和远限)。*/ LOCAL S32 soap_out_timg_get_settings_rsp(ONVIF_BUF *xml_buf, void *data) { IMAGE_COMMON image_common; MODULE_SPEC module_spec; struct onvif_ptz_capability *ptz_capability = NULL; const char *tag_str[] = {TT_BRIGHTNESS_STR, TT_COLORSATURATION_STR, TT_CONTRAST_STR, TT_SHARPNESS_STR}; U8 *value[] = {&image_common.luma, &image_common.saturation, &image_common.contrast, &image_common.sharpness}; U32 i; if (NULL == xml_buf) { ONVIF_WARN("Input NULL."); return ERROR; } memset(&image_common, 0, sizeof(IMAGE_COMMON)); if (0 == ds_read(IMAGE_COMMON_PATH, &image_common, sizeof(IMAGE_COMMON))) { ONVIF_ERROR("ds_read %s fail.", IMAGE_COMMON_PATH); return ERROR; } memset(&module_spec, 0, sizeof(MODULE_SPEC)); if (0 == ds_read(MODULE_SPEC_PATH, &module_spec, sizeof(MODULE_SPEC))) { ONVIF_ERROR("ds_read %s fail.", MODULE_SPEC_PATH); return ERROR; } SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, TIMG_GET_SETTING_RSP, NULL)); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, TIMG_IMG_SETTING, NULL)); for (i = 0; i < sizeof(tag_str) / sizeof(char*); ++i) { SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, tag_str[i], *value[i], NULL)); } if (module_spec.iris_first[0] == '1') { /* tt:Exposure begin */ SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Exposure", NULL)); /* tt:Mode */ switch (image_common.exp_type) { case EXP_TYPE_MANUAL: SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Mode", "MANUAL", NULL)); break; case EXP_TYPE_AUTO: SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Mode", "AUTO", NULL)); break; case EXP_TYPE_IRIS_FIRST: SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Mode", "IRIS_FIRST", NULL)); break; default: ONVIF_WARN("Unsupported expore mode."); return ERROR; } /* tt:Iris */ SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Iris", image_common.iris_level, NULL)); /* tt:Exposure end */ SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Exposure")); } if (tptz_get_local_capability(&ptz_capability) && ptz_capability->zoom_valid) { /*FOCUS begin */ SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Focus", NULL)); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:AutoFocusModes", "AUTO", NULL)); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:AutoFocusModes", "MANUAL", NULL)); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:DefaultSpeed", NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Min", 1, NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Max", 1, NULL)); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:DefaultSpeed")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:NearLimit", NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Min", 10, NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Max", 2000, NULL)); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:NearLimit")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:FarLimit", NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Min", 0, NULL)); SOAP_IF_FAIL_RET(soap_element_int_type(xml_buf, "tt:Max", 0, NULL)); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:FarLimit")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Focus")); /*FOCUS end */ } SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, TIMG_IMG_SETTING)); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, TIMG_GET_SETTING_RSP)); return OK; } /* 功能:将获取图像设置的SOAP请求(XML)换为内部JSON请求对象。 输入:SOAP_CONTEXT指针,输出参数json_req(返回构建的JSON对象)。 输出:返回OK或ERROR。 过程: a. 鉴权。 b. 解析SOAP请求中的VideoSourceToken(视频源令牌),检查是否为"raw_vs1"(目前只支持这个)。 c. 调用get_json_settings函数构建JSON请求。*/ LOCAL S32 timg_get_settings_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJPTR *json_req) { S32 request_len = 0; S32 ch = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; char charbuf[LEN_INFO] = {0}; if (soap == NULL || json_req == NULL) { ONVIF_TRACE("soap == NULL."); return ERROR; } ONVIF_TRACE("timg:GetImagingSettings"); /* 需确认是否支持 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ONVIF_TRACE("Auth failed\n"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); soap->error = 400; return ERROR; } /* 分析 GetVideoEncoderConfigurationOptions 请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { request_len = soap->request_end - soap->request_begin; if (request_len < 0) { ONVIF_TRACE("request_len < 0."); return ERROR; } xml_str = xml_start = soap->request_begin; p = (char **)&xml_str; } else { ONVIF_TRACE("no content"); return ERROR; } while (((*p) - xml_start) < request_len) { if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) { return ERROR; } if (charbuf[0] == '/') { continue; } if (TRUE == soap_match_tag(charbuf, "VideoSourceToken")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf))) { return ERROR; } ONVIF_TRACE("VideoSourceToken: %s", charbuf); if (strcmp(charbuf, "raw_vs1")) { SOAP_IF_FAIL_RET(soap_fault(soap, "SOAP-ENV:Sender", "ter:InvalidArgVal", "ter:SettingsInvalid", "error data")); soap->error = SOAP_FAULT; return ERROR; } continue; } } return get_json_settings(soap, json_req, IMAGE_STR, COMMON_STR); } /* 功能:将内部处理后的JSON响应换为SOAP响应(XML)。 输入:SOAP_CONTEXT指针,json_rsp(内部处理返回的JSON响应)。 输出:返回OK或ERROR。 过程:调用soap_generate_xml函数,使用soap_out_timg_get_settings_rsp函数生成XML。*/ LOCAL S32 timg_get_settings_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJPTR json_rsp) { if (soap == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } return soap_generate_xml((p_out_fun)(soap_out_timg_get_settings_rsp), soap, NULL); } /*------------------------- 配置图像参数 ----------------------------------*/ /* 功能:生成设置图像设置的SOAP响应(XML)。 输入:xml_buf(输出缓冲区),data(未使用)。 输出:返回OK或ERROR。 过程:生成一个空的timg:SetImagingSettingsResponse标签。*/ LOCAL S32 soap_out_set_timg_rsp(ONVIF_BUF *xml_buf, void *data) { if (NULL == xml_buf) { ONVIF_WARN("Input NULL."); return ERROR; } SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, TIMG_SET_SETTING_RSP, NULL)); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, TIMG_SET_SETTING_RSP)); return OK; } #if 0 /*------------------------- 开始聚焦 ----------------------------------*/ LOCAL S32 soap_out_timg_move_rsp(ONVIF_BUF *xml_buf, void *data) { if (NULL == xml_buf) { ONVIF_WARN("Input NULL."); return ERROR; } /* timg:MoveResponse */ SOAP_IF_FAIL_RET(soap_element(xml_buf, TIMG_MOVE_RSP, NULL, NULL)); return OK; } /*------------------------- 停止聚焦 ----------------------------------*/ LOCAL S32 soap_out_timg_stop_rsp(ONVIF_BUF *xml_buf, void *data) { if (NULL == xml_buf) { ONVIF_WARN("Input NULL."); return ERROR; } /* timg:StopResponse */ SOAP_IF_FAIL_RET(soap_element(xml_buf, TIMG_STOP_RSP, NULL, NULL)); return OK; } #endif LOCAL S32 timg_set_imaging_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJPTR *json_req) { S32 request_len = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; S32 ch = 0; char charbuf[LEN_INFO * 2] = {0}; JSON_OBJPTR json_data = NULL; JSON_OBJPTR json_sec = NULL; JSON_OBJPTR json_seg = NULL; const char *tag_str[] = {BRIGHTNESS_STR, COLORSATURATION_STR, CONTRAST_STR, SHARPNESS_STR}; const char *jso_str[] = {LUMA_JSO_STR, SATURATION_JSO_STR, CONTRAST_JSO_STR, SHARPNESS_JSO_STR}; U32 min[] = {LUMA_MIN, SATURATION_MIN, CONTRAST_MIN, SHARPNESS_MIN}; U32 max[] = {LUMA_MAX, SATURATION_MAX, CONTRAST_MAX, SHARPNESS_MAX}; U32 i; JSON_OBJPTR json_data_iris = NULL; JSON_OBJPTR json_sec_iris = NULL; JSON_OBJPTR json_seg_iris = NULL; int iris_to_set = 0; IMAGE_COMMON image_common; MODULE_SPEC module_spec; JSON_OBJ *rsp_obj = NULL; if (soap == NULL || json_req == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ONVIF_WARN("Auth failed"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); soap->error = 400; return ERROR; } /* 分析SetImage请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { request_len = soap->request_end - soap->request_begin; if (request_len < 0) { ONVIF_WARN("Invalid request"); return ERROR; } xml_str = soap->request_begin; xml_start = soap->request_begin; p = (char **)&xml_str; } else { ONVIF_ERROR("soap request content is NULL."); return ERROR; } json_data = jso_new_obj(); json_sec = jso_new_obj(); json_seg = jso_new_obj(); if (NULL == json_data || NULL == json_sec || NULL == json_seg) { ONVIF_WARN("jso_new_obj error."); goto err_out; } while (((*p) - xml_start) < request_len) { if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) { goto err_out; } if (charbuf[0] == '/') { continue; } if (TRUE == soap_match_tag(charbuf, "VideoSourceToken")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf))) { goto err_out; } ONVIF_TRACE("VideoSourceToken: %s", charbuf); if (strcmp(charbuf, "raw_vs1")) { SOAP_IF_FAIL_RET(soap_fault(soap, "SOAP-ENV:Sender", "ter:InvalidArgVal", "ter:SettingsInvalid", "error data")); soap->error = SOAP_FAULT; goto err_out; } continue; } for (i = 0; i < sizeof(tag_str) / sizeof(char*); ++i) { if (TRUE != soap_match_tag(charbuf, tag_str[i])) { continue; } if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf)) || 0 != jso_add_int(json_seg, jso_str[i], atoi(charbuf))) { goto err_out; } if((atoi(charbuf) < min[i]) || (atoi(charbuf) > max[i])) { SOAP_IF_FAIL_RET(soap_fault(soap, "SOAP-ENV:Sender", "ter:InvalidArgVal", "ter:SettingsInvalid", "error data")); soap->error = SOAP_FAULT; goto err_out; } break; } if (TRUE == soap_match_tag(charbuf, "Iris")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf))) { goto err_out; } ONVIF_TRACE("Iris: %s", charbuf); iris_to_set = atoi(charbuf); continue; } } jso_obj_add(json_sec, COMMON_STR, json_seg); jso_obj_add(json_data, IMAGE_STR, json_sec); jso_add_string(json_data, DS_METHOD_STR, METHOD_SET_STR); *json_req = json_data; memset(&module_spec, 0, sizeof(MODULE_SPEC)); if (0 == ds_read(MODULE_SPEC_PATH, &module_spec, sizeof(MODULE_SPEC))) { ONVIF_ERROR("ds_read %s fail.", MODULE_SPEC_PATH); goto err_out; } if (module_spec.iris_first[0] == '1') { memset(&image_common, 0, sizeof(IMAGE_COMMON)); if (0 == ds_read(IMAGE_COMMON_PATH, &image_common, sizeof(IMAGE_COMMON))) { ONVIF_ERROR("ds_read %s fail.", IMAGE_COMMON_PATH); goto err_out; } json_data_iris = jso_new_obj(); json_sec_iris = jso_new_obj(); json_seg_iris = jso_new_obj(); if (NULL == json_data_iris || NULL == json_sec_iris || NULL == json_seg_iris) { ONVIF_WARN("jso_new_obj error."); goto err_out; } if (iris_to_set > image_common.iris_level) { jso_add_string(json_seg_iris, VELOCITY_STR, "0.1"); } else if (iris_to_set < image_common.iris_level) { jso_add_string(json_seg_iris, VELOCITY_STR, "-0.1"); } else { ONVIF_WARN("iris_to_set is the same as DUT."); goto err_out; } jso_obj_add(json_sec_iris, START_IRIS_STR, json_seg_iris); jso_obj_add(json_data_iris, IMAGE_STR, json_sec_iris); jso_add_string(json_data_iris, DS_METHOD_STR, METHOD_DO_STR); if (OK != onvif_passthrough(soap, json_data_iris, &rsp_obj)) { ONVIF_WARN("passthrough error."); goto err_out; } jso_free_obj(json_data_iris); jso_free_obj(rsp_obj); } return OK; err_out: jso_free_obj(json_data); jso_free_obj(json_sec); jso_free_obj(json_seg); jso_free_obj(json_data_iris); jso_free_obj(json_sec_iris); jso_free_obj(json_seg_iris); jso_free_obj(rsp_obj); return ERROR; } LOCAL S32 timg_set_imaging_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJPTR json_rsp) { if (soap == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } return soap_generate_xml((p_out_fun)(soap_out_set_timg_rsp), soap, NULL); } #if 0 LOCAL S32 timg_move_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJPTR *json_req) { S32 request_len = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; S32 ch = 0; char charbuf[LEN_INFO] = {0}; char vs_token[LEN_INFO] = {0}; JSON_OBJPTR json_data = NULL; JSON_OBJPTR json_sec = NULL; JSON_OBJPTR json_seg = NULL; if (soap == NULL || json_req == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } /* 判断是否支持ptz和zoom,不支持直接返回 */ if (!is_ptz_support() || !is_ptz_3D_zoom_support()) { ONVIF_WARN("not support ptz or zoom."); return ERROR; } /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ONVIF_WARN("Auth failed"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); soap->error = 400; return ERROR; } /* 需要分析 timg:Move请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { request_len = soap->request_end - soap->request_begin; if (request_len < 0) { ONVIF_TRACE("request_len < 0."); return ERROR; } xml_str = xml_start = soap->request_begin; p = (char **)&xml_str; } else { ONVIF_ERROR("soap request content is NULL."); return ERROR; } json_data = jso_new_obj(); json_sec = jso_new_obj(); json_seg = jso_new_obj(); if (NULL == json_data || NULL == json_sec || NULL == json_seg) { ONVIF_WARN("jso_new_obj error."); goto err_out; } while (((*p) - xml_start) < request_len) { if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) { ONVIF_ERROR("soap_get_tag failed"); goto err_out; } if (charbuf[0] == '/') { continue; } if (TRUE == soap_match_tag(charbuf, "VideoSourceToken")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf))) { goto err_out; } ONVIF_TRACE("VideoSourceToken: %s", charbuf); snprintf(vs_token, LEN_INFO, "%s", charbuf); continue; } if (TRUE == soap_match_tag(charbuf, "Speed")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf)) || 0 != jso_add_string(json_seg, VELOCITY_STR, charbuf)) { goto err_out; } ONVIF_TRACE("Speed: %s", charbuf); } } if (0 != strcmp("raw_vs1", vs_token)) { soap->error = SOAP_FAULT; goto err_out; } jso_obj_add(json_sec, START_FOCUS_STR, json_seg); jso_obj_add(json_data, IMAGE_STR, json_sec); jso_add_string(json_data, DS_METHOD_STR, METHOD_DO_STR); *json_req = json_data; return OK; err_out: jso_free_obj(json_data); jso_free_obj(json_sec); jso_free_obj(json_seg); return ERROR; } LOCAL S32 timg_move_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJPTR json_rsp) { if (soap == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } return soap_generate_xml((p_out_fun)(soap_out_timg_move_rsp), soap, NULL); } LOCAL S32 timg_stop_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJPTR *json_req) { S32 request_len = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; S32 ch = 0; char charbuf[LEN_INFO] = {0}; char vs_token[LEN_INFO] = {0}; JSON_OBJPTR json_data = NULL; JSON_OBJPTR json_sec = NULL; JSON_OBJPTR json_seg = NULL; if (soap == NULL || json_req == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } /* 判断是否支持ptz和zoom,不支持直接返回 */ if (!is_ptz_support() || !is_ptz_3D_zoom_support()) { ONVIF_WARN("not support ptz or zoom."); return ERROR; } /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ONVIF_WARN("Auth failed"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); soap->error = 400; return ERROR; } /* 需要分析 timg:Move请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { request_len = soap->request_end - soap->request_begin; if (request_len < 0) { ONVIF_TRACE("request_len < 0."); return ERROR; } xml_str = xml_start = soap->request_begin; p = (char **)&xml_str; } else { ONVIF_ERROR("soap request content is NULL."); return ERROR; } json_data = jso_new_obj(); json_sec = jso_new_obj(); json_seg = jso_new_obj(); if (NULL == json_data || NULL == json_sec || NULL == json_seg) { ONVIF_WARN("jso_new_obj error."); goto err_out; } while (((*p) - xml_start) < request_len) { if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) { ONVIF_ERROR("soap_get_tag failed"); goto err_out; } if (charbuf[0] == '/') { continue; } if (TRUE == soap_match_tag(charbuf, "VideoSourceToken")) { if (OK != soap_parse_element_value(xml_start, request_len, p, ch, charbuf, sizeof(charbuf))) { goto err_out; } ONVIF_TRACE("VideoSourceToken: %s", charbuf); snprintf(vs_token, LEN_INFO, "%s", charbuf); break; } } if (0 != strcmp("raw_vs1", vs_token)) { soap->error = SOAP_FAULT; goto err_out; } jso_obj_add(json_sec, STOP_FOCUS_STR, json_seg); jso_obj_add(json_data, IMAGE_STR, json_sec); jso_add_string(json_data, DS_METHOD_STR, METHOD_DO_STR); *json_req = json_data; return OK; err_out: jso_free_obj(json_data); jso_free_obj(json_sec); jso_free_obj(json_seg); return ERROR; } LOCAL S32 timg_stop_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJPTR json_rsp) { if (soap == NULL) { ONVIF_WARN("soap == NULL."); return ERROR; } return soap_generate_xml((p_out_fun)(soap_out_timg_stop_rsp), soap, NULL); } #endif void imaging_passthrough_init() { onvif_passthrough_handle_add(TIMG_GET_IMG_SETTINGS, timg_get_settings_xml_to_json, timg_get_settings_json_to_xml); onvif_passthrough_handle_add(TIMG_SET_IMG_SETTINGS, timg_set_imaging_xml_to_json, timg_set_imaging_json_to_xml); /* 调焦相关,目前不支持 */ #if 0 onvif_passthrough_handle_add(TIMG_MOVE, timg_move_xml_to_json, timg_move_json_to_xml); onvif_passthrough_handle_add(TIMG_STOP, timg_stop_xml_to_json, timg_stop_json_to_xml); #endif } /****************************************************************************** 模块流程: 系统初始化时调用imaging_passthrough_init,将本模块的处理函数注册到全局透传路由表。 当收到SOAP请求时,透传框架(onvif_serve_passthrough)会匹配请求标签(如timg:GetImagingSettings)。 匹配成功后,调用注册的xml_to_json函数(如timg_get_settings_xml_to_json)将SOAP请求换为内部JSON请求。 调用onvif_passthrough函数(核心业务处理)执行JSON指令,得到JSON响应。 调用注册的json_to_xml函数(如timg_get_settings_json_to_xml)将JSON响应换为SOAP响应。 将SOAP响应返回给客户端。 对外接口: 本模块对外提供的唯一接口是imaging_passthrough_init,它被系统初始化函数调用,用于注册本模块的处理函数。 ******************************************************************************/ /****************************************************************************** * Copyright (c) 2018-2018 TP-Link Systems Inc. * * Filename: md_active_cells.c * Version: 1.0 * Description: 移动侦测区域计算的相关接口头文件 * Author: dengpeng<dengpeng@tp-link.com.cn> * Date: 2019-03-15 ******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <math.h> #include "onvif_passthrough.h" #include "packbits.h" #include <json/json.h> #include <json/json_object_private.h> #include "md_active_cells.h" /**************************************************************************** * Function : json_parse * Description: 从json对象解析section子对象 * Input : json_recv : json对象 mod_name : 模块名称 sub_mod_name : section名称 * Output : N/A * Return : 解析结果 ****************************************************************************/ json_object* json_parse(json_object* json_recv, char* mod_name, char* sub_mod_name) { json_object* json_data = NULL; if (json_recv == NULL || mod_name == NULL || sub_mod_name == NULL) { return NULL; } /* !!!json_object_object_get返回的指针不能手动释放!!! */ json_data = json_object_object_get(json_recv, mod_name); if (NULL == json_data) { ONVIF_ERROR("get %s mod error", mod_name); return NULL; } json_data = json_object_object_get(json_data, sub_mod_name); if (NULL == json_data) { ONVIF_ERROR("get %s subMode error", sub_mod_name); return NULL; } return json_data; } #define UP_GET_CELL_IDX(total, cell_cnt, v) \ ({ \ int i = 0; \ int idx = 1; \ float gap = 0.0; \ for (i = (cell_cnt); i > 0; i--) \ { \ gap = (((float)total) * (i - 1)) / (cell_cnt); \ if ((v) > gap || fabs((v) - gap) <= 1e-5) \ { \ idx = i; \ break; \ } \ } \ idx; \ }) #define DOWN_GET_CELL_IDX(total, cell_cnt, v) \ ({ \ int i = 0; \ int idx = cell_cnt; \ float gap = 0.0; \ for (i = 1; i <= (cell_cnt); i++) \ { \ gap = (((float)total) * i) / (cell_cnt); \ if ((v) < gap || fabs((v) - gap) <= 1e-5) \ { \ idx = i; \ break; \ } \ } \ idx; \ }) /**************************************************************************** * Function : md_get_active_cells * Description: 读取移动侦测区域信息,映射到22*18的网格中,将映射后的 * 的网格使用packbits和base64算法编码 * Input : SLP协议定义的区域信息 * Output : N/A * Return : base64编码的区域信息 ****************************************************************************/ char *md_get_active_cells_alloc(json_object* region_info) { int i = 0; int c = 0; int r = 0; int region_cnt = 0; int x = 0; int y = 0; int height = 0; int width = 0; int left_col = 0; int right_col = 0; int top_row = 0; int bottom_row = 0; int byte_idx = 0; int bit_idx = 0; int packbit_len = 0; json_object* jso_opt = NULL; json_object** module_obj = NULL; unsigned char bit_arr[(CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) / BITS_IN_BYTE] = {0}; unsigned char packbit_arr[((CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) * 2) / BITS_IN_BYTE] = {0}; char *base64_buf = NULL; if (NULL == region_info) { ONVIF_ERROR("NULL == region_info"); return NULL; } memset(bit_arr, 0, sizeof(bit_arr)); region_cnt = jso_array_length(region_info); for (i = 0; i < region_cnt; i++) { jso_opt = jso_array_get_idx(region_info, i); /* 取region_info_i下一级的内容 */ module_obj = NULL; module_obj = jso_next_sub_obj(jso_opt, NULL, NULL); if (NULL == module_obj) { ONVIF_ERROR("NULL == module_obj"); return NULL; } jso_opt = *module_obj; jso_obj_get_int(jso_opt, "x_coor", &x); jso_obj_get_int(jso_opt, "y_coor", &y); jso_obj_get_int(jso_opt, "height", &height); jso_obj_get_int(jso_opt, "width", &width); left_col = UP_GET_CELL_IDX(SLP_REGION_MAX_WIDTH, CELL_LAYOUT_COLS, x); right_col = DOWN_GET_CELL_IDX(SLP_REGION_MAX_WIDTH, CELL_LAYOUT_COLS, x + width); top_row = UP_GET_CELL_IDX(SLP_REGION_MAX_HEIGHT, CELL_LAYOUT_ROWS, y); bottom_row = DOWN_GET_CELL_IDX(SLP_REGION_MAX_HEIGHT, CELL_LAYOUT_ROWS, y + height); for (r = top_row; r <= bottom_row; r++) { for (c = left_col; c <= right_col; c++) { byte_idx = ((r - 1) * CELL_LAYOUT_COLS + c - 1) / BITS_IN_BYTE; bit_idx = BITS_IN_BYTE - 1 - ((r - 1) * CELL_LAYOUT_COLS + c - 1) % BITS_IN_BYTE; bit_arr[byte_idx] |= (0x1<<bit_idx); } } } packbit_len = tiff6_PackBits(bit_arr, sizeof(bit_arr) / sizeof(bit_arr[0]), packbit_arr); onvif_base64_encode_alloc((char *)packbit_arr, packbit_len, &base64_buf); return base64_buf; } /**************************************************************************** * Function : md_get_active_cells_alloc_no_json * Description: 通过ds_read读取移动侦测区域信息,映射到22*18的网格中,将映射后的 * 的网格使用packbits和base64算法编码 * Input : SLP协议定义的区域信息 * Output : N/A * Return : base64编码的区域信息 ****************************************************************************/ char *md_get_active_cells_alloc_no_json() { int i = 0; int c = 0; int r = 0; int x = 0; int y = 0; int height = 0; int width = 0; int left_col = 0; int right_col = 0; int top_row = 0; int bottom_row = 0; int byte_idx = 0; int bit_idx = 0; int packbit_len = 0; DEVICE_INFO device_info; char path[DATA_PATH_SIZE] = {0}; MOTION_DETECT_REGION_INFO region = {0}; unsigned char bit_arr[(CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) / BITS_IN_BYTE] = {0}; unsigned char packbit_arr[((CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) * 2) / BITS_IN_BYTE] = {0}; char *base64_buf = NULL; if (0 == ds_read(DEVICE_INFO_PATH, (U8 *)&device_info, sizeof(DEVICE_INFO))) { ONVIF_DEBUG("read DEVICE_INFO fail."); return NULL; } for (i = 0; i < device_info.md_reg_num; i++) { snprintf(path, DATA_PATH_SIZE, "/motion_detection/%s%d", MOTION_DETECT_REGION_PREFIX, i + 1); if (0 == ds_read((const char*)path, &region, sizeof(MOTION_DETECT_REGION_INFO))) { ONVIF_TRACE("read %s fail.", path); continue; } x = region.x_coor; y = region.y_coor; height = region.height; width = region.width; left_col = UP_GET_CELL_IDX(SLP_REGION_MAX_WIDTH, CELL_LAYOUT_COLS, x); right_col = DOWN_GET_CELL_IDX(SLP_REGION_MAX_WIDTH, CELL_LAYOUT_COLS, x + width); top_row = UP_GET_CELL_IDX(SLP_REGION_MAX_HEIGHT, CELL_LAYOUT_ROWS, y); bottom_row = DOWN_GET_CELL_IDX(SLP_REGION_MAX_HEIGHT, CELL_LAYOUT_ROWS, y + height); for (r = top_row; r <= bottom_row; r++) { for (c = left_col; c <= right_col; c++) { byte_idx = ((r - 1) * CELL_LAYOUT_COLS + c - 1) / BITS_IN_BYTE; bit_idx = BITS_IN_BYTE - 1 - ((r - 1) * CELL_LAYOUT_COLS + c - 1) % BITS_IN_BYTE; bit_arr[byte_idx] |= (0x1<<bit_idx); } } } packbit_len = tiff6_PackBits(bit_arr, sizeof(bit_arr) / sizeof(bit_arr[0]), packbit_arr); onvif_base64_encode_alloc((char *)packbit_arr, packbit_len, &base64_buf); return base64_buf; } /**************************************************************************** * Function : md_parse_active_cells_alloc * Description: 将onvif标准格式的区域信息换为SLP私有协议的json串 * 其中,该函数会申请内存用于存储json命令,需要调用者释放该内存 * Input : active_cells: onvif标准定义的的区域信息(base64Binary) * Output : json_set_region: SLP协议设置区域的json串命令 * Return : 0-success, -1-error ****************************************************************************/ int md_parse_active_cells_alloc(const char *active_cells, char **json_set_region) { int ret = -1; int found; int byte_len = 0; int region_cnt = 0; int r, c, byte_idx, bit_idx, mask; // 遍历整个bitmask int sub_r, sub_c, sub_byte_idx, sub_bit_idx, sub_mask; // 从新矩阵左上角开始遍历bitmask int top_left_row, top_left_col, right_col, bottom_row; // 最大矩形区域的左上角及右下角 int top_left_x, top_left_y, right_bottom_x, right_bottom_y; // 矩形映射为SLP协议的坐标值 size_t inlen = 0; size_t packbit_len = 0; float cell_width = (float)SLP_REGION_MAX_WIDTH / CELL_LAYOUT_COLS; float cell_height = (float)SLP_REGION_MAX_HEIGHT / CELL_LAYOUT_ROWS; char *packbit_buf = NULL; unsigned char byte_buf[(CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) / BITS_IN_BYTE] = {0}; unsigned char cell_visited[(CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) / BITS_IN_BYTE] = {0}; json_object *json_root = NULL; json_object *json_module = NULL; json_object *json_action = NULL; json_object *json_param = NULL; json_object *json_region = NULL; int array_len = (CELL_LAYOUT_ROWS * CELL_LAYOUT_COLS + BITS_IN_BYTE) / BITS_IN_BYTE; char *cmd = NULL; if (!active_cells || !json_set_region) { ret = -1; goto exit; } inlen = strlen(active_cells); memset(byte_buf, 0, sizeof(byte_buf)); memset(cell_visited, 0, sizeof(cell_visited)); json_root = jso_new_obj(); if (NULL == json_root) { ONVIF_ERROR("NULL == json_root"); ret = -1; goto exit; } jso_add_string(json_root, "method", "do"); json_module = jso_new_obj(); if (NULL == json_module) { ONVIF_ERROR("NULL == json_module"); ret = -1; goto free_json_obj; } jso_obj_add(json_root, "motion_detection", json_module); json_action = jso_new_obj(); if (NULL == json_action) { ONVIF_ERROR("NULL == json_action"); ret = -1; goto free_json_obj; } jso_obj_add(json_module, "add_md_regions", json_action); json_param = jso_new_array();//注意,创建数组不能用jso_new_obj,在添加元素时会失败 if (NULL == json_param) { ONVIF_ERROR(""); ret = -1; goto free_json_obj; } jso_obj_add(json_action, "region_info", json_param); ret = onvif_base64_decode_alloc(active_cells, inlen, &packbit_buf, &packbit_len); if (ret == FALSE || (ret == TRUE && !packbit_buf)) { ONVIF_ERROR("ret[%d], packbuf[%p]", ret, packbit_buf); ret = -1; goto free_json_obj; } byte_len = tiff6_unPackBits(packbit_buf, packbit_len, byte_buf, array_len); if (byte_len < 0) { ret = -1; goto free_packbit_buf; } // 遍历整个bitmask for (r = 1; r <= CELL_LAYOUT_ROWS; r++) { if (region_cnt >= SLP_REGION_MAX_NUM) { break; } for (c = 1; c <= CELL_LAYOUT_COLS; c++) { if (region_cnt >= SLP_REGION_MAX_NUM) { break; } byte_idx = ((r - 1) * CELL_LAYOUT_COLS + c - 1) / BITS_IN_BYTE; bit_idx = BITS_IN_BYTE - 1 - ((r - 1) * CELL_LAYOUT_COLS + c - 1) % BITS_IN_BYTE; mask = 0x1<<bit_idx; if (cell_visited[byte_idx] & mask) { continue; } // find a new region if (byte_buf[byte_idx] & mask) { top_left_row = r; top_left_col = c; bottom_row = top_left_row; right_col = CELL_LAYOUT_COLS; found = 0; // 从新矩阵左上角开始遍历bitmask for (sub_r = top_left_row; sub_r <= CELL_LAYOUT_ROWS; sub_r++) { // 找到最大矩形 if (found) { break; } for (sub_c = top_left_col; sub_c <= right_col; sub_c++) { sub_byte_idx = ((sub_r - 1) * CELL_LAYOUT_COLS + sub_c - 1) / BITS_IN_BYTE; sub_bit_idx = BITS_IN_BYTE - 1 - ((sub_r - 1) * CELL_LAYOUT_COLS + sub_c - 1) % BITS_IN_BYTE; sub_mask = 0x1<<sub_bit_idx; // blank cell if (!(byte_buf[sub_byte_idx] & sub_mask)) { // 第一列出现空cell,已找到最大矩形 if (sub_c == top_left_col) { found = 1; bottom_row = sub_r - 1; } else { right_col = (sub_c - 1) < right_col ? (sub_c - 1) : right_col; } break; } } } // 到达最底部第一列都没有空的cell if (!found) { bottom_row = CELL_LAYOUT_ROWS; } if (region_cnt < SLP_REGION_MAX_NUM) { // 设置visited mask for (sub_r = top_left_row; sub_r <= bottom_row; sub_r++) { for (sub_c = top_left_col; sub_c <= right_col; sub_c++) { sub_byte_idx = ((sub_r - 1) * CELL_LAYOUT_COLS + sub_c - 1) / BITS_IN_BYTE; sub_bit_idx = BITS_IN_BYTE - 1 - ((sub_r - 1) * CELL_LAYOUT_COLS + sub_c - 1) % BITS_IN_BYTE; sub_mask = 0x1<<sub_bit_idx; cell_visited[sub_byte_idx] |= sub_mask; } } top_left_x = (int)ceilf(cell_width * (top_left_col - 1)); top_left_y = (int)ceilf(cell_height * (top_left_row - 1)); right_bottom_x = (int)floorf(cell_width * right_col); right_bottom_y = (int)floorf(cell_height * bottom_row); json_region = jso_new_obj(); if (NULL == json_region) { ONVIF_ERROR("NULL == json_region"); ret = -1; goto free_packbit_buf; } jso_add_string_from_int(json_region, "x_coor", top_left_x); jso_add_string_from_int(json_region, "y_coor", top_left_y); jso_add_string_from_int(json_region, "width", right_bottom_x - top_left_x); jso_add_string_from_int(json_region, "height", right_bottom_y - top_left_y); jso_array_add(json_param, json_region); region_cnt++; } } } } cmd = (char *)json_object_to_json_string(json_root); *json_set_region = (char *)malloc((strlen(cmd) + 1) * sizeof(char)); if (!*json_set_region) { ONVIF_TRACE("Malloc fail"); ret = -1; goto free_packbit_buf; } strcpy(*json_set_region, cmd); ret = 0; free_packbit_buf: if (packbit_buf) { free(packbit_buf); packbit_buf = NULL; } free_json_obj: jso_free_obj(json_root); exit: return ret; } /* 输入:设置移动侦测区域的字符串(可以是base64编码的,也可以是slp协议的json字符串), * 返回:slp协议的json对象指针,失败返回NULL */ json_object * parse_modify_rules_value(char *md_active_cells) { MOTION_DETECT motion_detect; int md_on = FALSE; int region_cnt = 0; char *json_set_region_str = NULL; json_object *json_md_set = NULL; json_object *json_sub_module = NULL; json_object *json_region = NULL; if (NULL == md_active_cells) { ONVIF_ERROR("ptr is NULL"); return NULL; } memset(&motion_detect, 0, sizeof(MOTION_DETECT)); if (0 == ds_read(MOTION_DETECT_PATH, &motion_detect, sizeof(MOTION_DETECT))) { ONVIF_ERROR("ds_read return 0"); goto error_out; } md_on = motion_detect.enabled; // md_active_cells是slp接口的json字符串 if (md_active_cells) { /* '{' 是非base64编码字符,且合法的json串中都会包含该符号 * 如果activeCell的值包括'{',则其不是base64编码,按私有协议处理 * 如果activeCell的值不包括'{',则按照onvif标准解析 */ // 如果md_active_cells 是json格式的字符串串,则解析该串并构造slp接口json对象 if (strstr(md_active_cells, "{")) { json_md_set = jso_from_string(md_active_cells); if (NULL == json_md_set) { ONVIF_TRACE("Error parsing json"); goto error_out; } if (FALSE == md_on) { ONVIF_TRACE("Motion_detect.enable is FALSE"); return json_md_set; } json_sub_module = jso_obj_get(json_md_set, "motion_detection"); if (NULL == json_sub_module) { ONVIF_TRACE("Error parsing json"); goto error_out; } json_sub_module = jso_obj_get(json_sub_module, "add_md_regions"); if (NULL == json_sub_module) { ONVIF_TRACE("Error parsing json"); goto error_out; } json_sub_module = jso_obj_get(json_sub_module, "region_info"); if (NULL == json_sub_module) { ONVIF_TRACE("Error parsing json"); goto error_out; } /* 获取数组元素个数:即区域个数 */ region_cnt = jso_array_length(json_sub_module); if (region_cnt == 0) { // 如果region_info的值为空,则构造默认的值 json_region = jso_new_obj();//jsonRegion不是数组,是数组的元素 if (NULL == json_region) { ONVIF_TRACE("Ptr is NULL"); goto error_out; } jso_add_string_from_int(json_region, "x_coor", 0); jso_add_string_from_int(json_region, "y_coor", 0); jso_add_string_from_int(json_region, "width", SLP_REGION_MAX_WIDTH); jso_add_string_from_int(json_region, "height", SLP_REGION_MAX_HEIGHT); jso_array_add(json_sub_module, json_region); return json_md_set; } else/* 区域个数不为0,直接返回 */ { return json_md_set; } } else/* 如果md_active_cells不是json串,而是base64Binary的字符串 */ { /* 根据onvif标准定义的的区域信息(base64Binary)-md_active_cells */ /* 获取SLP协议设置区域的json串命令-json_set_region_str */ if (0 != md_parse_active_cells_alloc(md_active_cells, &json_set_region_str)) { ONVIF_TRACE("Decode base64 error"); goto error_out; } json_md_set = jso_from_string(json_set_region_str); if (NULL == json_md_set) { ONVIF_TRACE("Error parsing json"); goto error_out; } return json_md_set; } } error_out: jso_free_obj(json_md_set); ONVIF_FREE(json_set_region_str); return NULL; } /****************************************************************************** * Copyright (c) 2018-2018 TP-Link Systems Inc. * * Filename: onvif_passthrough.c * Version: 1.0 * Description: 将配置参数写入内存及FLASH的相关接口 * Author: liyijie<liyijie@tp-link.com.cn> * Date: 2019-02-01 ******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "onvif_passthrough.h" #include "soap_parse.h" #include "libds.h" #define PASSTHROUGH_TAG_NUM 128 /*最大支持的透传标签数量*/ typedef struct _PASSTHROUGH_HANDLE { char tag[LEN_TAG]; /*标签名(如"tds:GetDeviceInformation")*/ S32 (*xml_to_json)(SOAP_CONTEXT *soap, JSON_OBJ **req_obj); /*XMLJSON函数指针*/ S32 (*json_to_xml)(SOAP_CONTEXT *soap, JSON_OBJ *rsp_obj); /*JSONXML函数指针*/ }PASSTHROUGH_HANDLE; LOCAL PASSTHROUGH_HANDLE g_passthrough_handle[PASSTHROUGH_TAG_NUM]; /*全局透传处理函数表*/ /****************************************************************************** * 函数名称: onvif_passthrough() * 函数描述: 执行json指令 * 输 入: params_obj -- 输入的json指令 * 输 出: rsp_obj -- 应答json object * 返 回 值: ERROR/OK ******************************************************************************/ S32 onvif_passthrough(SOAP_CONTEXT *soap, JSON_OBJ *req_obj, JSON_OBJ **rsp_obj) { DS_HANDLE_CONTEXT context; if (NULL == req_obj || rsp_obj == NULL) { ONVIF_TRACE("Params_obj is NULL."); return ERROR; } memset(&context, 0, sizeof(DS_HANDLE_CONTEXT)); /* 运行该透传接口。*/ ONVIF_TRACE("request:%s\n", json_object_get_string(req_obj)); context.req_obj = req_obj; if (OK != ds_parse(&context)) { ONVIF_TRACE("Parse method failed."); return ERROR; } context.group_mask = ROOT_MASK; ds_handle(&context); ONVIF_TRACE("Ds_handle OK."); *rsp_obj = context.res_obj; ONVIF_TRACE("response:%s\n", json_object_get_string(*rsp_obj)); return OK; } /****************************************************************************** * 函数名称: onvif_serve_passthrough() * 函数描述: onvif透传请求处理入口 * 输 入: soap -- soap结构体地址 * 输 出: N/A * 返 回 值: PASSTHROUGH_RET * 流 程:标签路由匹配:根据SOAP请求中的标签(如tds:GetDeviceInformation)查找对应的换函数。 XML->JSON换:调用注册的xml_to_json函数解析SOAP请求体,生成JSON请求对象。 业务处理:调用onvif_passthrough()执行实际业务逻辑(通过设备服务库)。 JSON->XML换:将业务返回的JSON对象换成SOAP响应XML。 资源清理:释放临时创建的JSON对象。 ******************************************************************************/ PASSTHROUGH_RET onvif_serve_passthrough(SOAP_CONTEXT *soap) { U32 index = 0; PASSTHROUGH_RET ret = PASSTHROUGH_OK; JSON_OBJ *req_obj = NULL; JSON_OBJ *rsp_obj = NULL; if (soap == NULL || soap->tag[0] == '\0') { return PASSTHROUGH_ERROR; } for (index = 0; index < PASSTHROUGH_TAG_NUM; index++) { if (NULL == g_passthrough_handle[index].xml_to_json || NULL == g_passthrough_handle[index].json_to_xml) { ret = PASSTHROUGH_NOT_MATCH; break; } if (soap_match_tag(soap->tag, g_passthrough_handle[index].tag)) { if (OK != g_passthrough_handle[index].xml_to_json(soap, &req_obj) || OK != onvif_passthrough(soap, req_obj, &rsp_obj) || OK != g_passthrough_handle[index].json_to_xml(soap, rsp_obj)) { ret = PASSTHROUGH_SOAP_FAULT; break; } break; } } if (index >= PASSTHROUGH_TAG_NUM) { ret = PASSTHROUGH_NOT_MATCH; } jso_free_obj(req_obj); jso_free_obj(rsp_obj); return ret; } /****************************************************************************** * 函数名称: soap_tag_handle_add() * 函数描述: 注册onvif透传请求处理函数 * 输 入: tag -- 请求tag * handle -- 处理函数 * 输 出: N/A * 返 回 值: ERROR/OK ******************************************************************************/ void onvif_passthrough_handle_add(char *tag, void *xml_to_json, void *json_to_xml) { U32 index = 0; if ((NULL == tag) || (strlen(tag) >= 64) || (NULL == xml_to_json) || (NULL == json_to_xml)) { ONVIF_ERROR("soap handle add error, invalid arg."); return; } while ((index < PASSTHROUGH_TAG_NUM) && (NULL != g_passthrough_handle[index].xml_to_json) && (NULL != g_passthrough_handle[index].json_to_xml)) { index++; } if (index >= PASSTHROUGH_TAG_NUM) { ONVIF_ERROR("soap handle add error, max support %d tags.", PASSTHROUGH_TAG_NUM); return; } snprintf(g_passthrough_handle[index].tag, sizeof(g_passthrough_handle[index].tag), "%s", tag); g_passthrough_handle[index].xml_to_json = xml_to_json; g_passthrough_handle[index].json_to_xml = json_to_xml; return; } /****************************************************************************** * 函数名称: onvif_passthrough_handle_init() * 函数描述: 初始化onvif透传请求处理函数 * 输 入: N/A * 输 出: N/A * 返 回 值: ERROR/OK ******************************************************************************/ void onvif_passthrough_handle_init() { U32 index = 0; for (index = 0; index < PASSTHROUGH_TAG_NUM; index++) { memset(g_passthrough_handle[index].tag, 0, sizeof(g_passthrough_handle[index].tag)); g_passthrough_handle[index].xml_to_json = NULL; g_passthrough_handle[index].json_to_xml = NULL; } tds_passthrough_init(); imaging_passthrough_init(); tan_passthrough_init(); trt_passthrough_init(); tptz_passthrough_init(); tr2_passthrough_init(); return; } /* 标签路由匹配:当SOAP请求到来时,根据请求中的标签(如"tds:GetDeviceInformation")在全局处理函数表g_passthrough_handle中查找对应的处理函数。 XML->JSON换:如果找到匹配的标签,调用注册的xml_to_json函数,将SOAP请求XML换为JSON对象。 业务处理:调用onvif_passthrough()函数,它进一步调用ds_parse()和ds_handle()来执行实际的业务逻辑,并将结果返回为JSON对象。 JSON->XML换:然后调用注册的json_to_xml函数,将JSON响应对象换回SOAP响应XML。 资源清理:释放临时创建的JSON对象,避免内存泄漏。 *//****************************************************************************** * Copyright (c) 2015-2018 TP-Link Systems Inc. * * 文件名称: packbits.c * 版 本: 1.0 * 摘 要: compress/decompress image with packbits * 作 者: wupimin <wupimin@tp-link.com.cn> * 创建时间: 2018-02-23 ******************************************************************************/ #include <stdio.h> #include <string.h> #include "packbits.h" static signed char* Pack_init(unsigned char Pack[], unsigned char Byte); static signed char* Pack_byte(signed char* Count, unsigned char Byte); static unsigned char* End_byte(signed char* Count); static int unPack_count(char Pack[], int count); int tiff6_PackBits(unsigned char array[], int count, unsigned char Pack[]) { int i = 0; signed char* Count = Pack_init(Pack, array[i]); i++; for (; i < count; i++) { Count = Pack_byte(Count, array[i]); } unsigned char* End = End_byte(Count); *End = '\0'; return (End - Pack); } int tiff6_unPackBits(char Pack[], int count, unsigned char array[], int array_len) { if (!Pack) { return -1; } if (!array) { return unPack_count(Pack, count); } int nRes = 0; signed char* Count = (signed char*)Pack; while ((char*)Count < (Pack+count)) { int c = *Count; if (c<0) { int n = (1-c); if (nRes + n <= array_len) { memset(&(array[nRes]), Count[1], n); nRes += n; } else { return -1; } } else { int n = (1+c); if (nRes + n <= array_len) { memcpy(&(array[nRes]), &Count[1], n); nRes += n; } else { return -1; } } Count = (signed char*)End_byte(Count); } return nRes; } int unPack_count(char Pack[], int count) { int nRes = 0; signed char* Count = (signed char*)Pack; while ((char*)Count < (Pack+count)) { int c = *Count; if (c<0) { nRes += (1-c); } else { nRes += (1+c); } Count = (signed char*)End_byte(Count); } return nRes; } signed char* Pack_init( unsigned char Pack[], unsigned char Byte ) { signed char* Cnt = (signed char*)Pack; *Cnt = 0; Pack[1] = Byte; return Cnt; } unsigned char* End_byte( signed char* Count ) { unsigned char* Pack = (unsigned char*)(Count+1); signed char c = *Count; if (c >0) { Pack = &(Pack[c+1]); } else { Pack = &(Pack[1]); } return Pack; } signed char* Pack_byte( signed char* Count, unsigned char Byte ) { signed char c = *Count; unsigned char* End = End_byte(Count); if (127 == c || c == -127) { return Pack_init(End, Byte); } else { unsigned char* Pack = End-1; if (*(Pack) == Byte) { if (c >0) { (*Count) = c-1; Count = Pack_byte(Pack_init(Pack, Byte), Byte); } else { (*Count)--; } } else { if (c >= 0) { *End = Byte; (*Count)++; } else { Count = Pack_init(End, Byte); } } } return Count; }
最新发布
09-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值