mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
support for latest hikivision ONVIF 19.12
This commit is contained in:
54
Device.go
54
Device.go
@@ -13,6 +13,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kerberos-io/onvif/networking"
|
||||
"github.com/kerberos-io/onvif/xsd/onvif"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
@@ -253,7 +254,7 @@ func (dev *Device) getEndpoint(endpoint string) (string, error) {
|
||||
|
||||
// CallMethod functions call an method, defined <method> struct.
|
||||
// You should use Authenticate method to call authorized requests.
|
||||
func (dev *Device) CallMethod(method interface{}) (*http.Response, error) {
|
||||
func (dev Device) CallMethod(method interface{}) (*http.Response, error) {
|
||||
pkgPath := strings.Split(reflect.TypeOf(method).PkgPath(), "/")
|
||||
pkg := strings.ToLower(pkgPath[len(pkgPath)-1])
|
||||
|
||||
@@ -261,11 +262,35 @@ func (dev *Device) CallMethod(method interface{}) (*http.Response, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestBody, err := xml.Marshal(method)
|
||||
return dev.callMethodDo(endpoint, method)
|
||||
}
|
||||
|
||||
// CallMethod functions call an method, defined <method> struct with authentication data
|
||||
func (dev Device) callMethodDo(endpoint string, method interface{}) (*http.Response, error) {
|
||||
output, err := xml.MarshalIndent(method, " ", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dev.SendSoap(endpoint, string(requestBody))
|
||||
|
||||
soap, err := dev.buildMethodSOAP(string(output))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
soap.AddRootNamespaces(Xlmns)
|
||||
soap.AddAction()
|
||||
|
||||
//Auth Handling
|
||||
if dev.params.Username != "" && dev.params.Password != "" {
|
||||
soap.AddWSSecurity(dev.params.Username, dev.params.Password)
|
||||
}
|
||||
|
||||
servResp, err := networking.SendSoap(dev.params.HttpClient, endpoint, soap.String())
|
||||
if err != nil {
|
||||
servResp, err = networking.SendSoapWithDigest(new(http.Client), endpoint, soap.String(), dev.params.Username, dev.params.Password)
|
||||
}
|
||||
|
||||
return servResp, err
|
||||
}
|
||||
|
||||
func (dev *Device) GetDeviceParams() DeviceParams {
|
||||
@@ -283,7 +308,7 @@ func (dev *Device) GetEndpointByRequestStruct(requestStruct interface{}) (string
|
||||
return endpoint, err
|
||||
}
|
||||
|
||||
func (dev *Device) SendSoap(endpoint string, xmlRequestBody string) (resp *http.Response, err error) {
|
||||
/*func (dev *Device) SendSoap(endpoint string, xmlRequestBody string) (resp *http.Response, err error) {
|
||||
soap := gosoap.NewEmptySOAP()
|
||||
soap.AddStringBodyContent(xmlRequestBody)
|
||||
soap.AddRootNamespaces(Xlmns)
|
||||
@@ -302,6 +327,27 @@ func (dev *Device) SendSoap(endpoint string, xmlRequestBody string) (resp *http.
|
||||
resp, err = dev.params.HttpClient.Do(req)
|
||||
}
|
||||
return resp, err
|
||||
}*/
|
||||
|
||||
// CallMethod functions call an method, defined <method> struct with authentication data
|
||||
func (dev Device) SendSoap(endpoint string, xmlRequestBody string) (*http.Response, error) {
|
||||
|
||||
soap := gosoap.NewEmptySOAP()
|
||||
soap.AddStringBodyContent(xmlRequestBody)
|
||||
soap.AddRootNamespaces(Xlmns)
|
||||
soap.AddAction()
|
||||
|
||||
//Auth Handling
|
||||
if dev.params.Username != "" && dev.params.Password != "" {
|
||||
soap.AddWSSecurity(dev.params.Username, dev.params.Password)
|
||||
}
|
||||
|
||||
servResp, err := networking.SendSoap(dev.params.HttpClient, endpoint, soap.String())
|
||||
if err != nil {
|
||||
servResp, err = networking.SendSoapWithDigest(new(http.Client), endpoint, soap.String(), dev.params.Username, dev.params.Password)
|
||||
}
|
||||
|
||||
return servResp, err
|
||||
}
|
||||
|
||||
func createHttpRequest(httpMethod string, endpoint string, soap string) (req *http.Request, err error) {
|
||||
|
||||
@@ -122,6 +122,8 @@ func callNecessaryMethod(serviceName, methodName, acceptedData, username, passwo
|
||||
switch strings.ToLower(serviceName) {
|
||||
case "device":
|
||||
methodStruct, err = getDeviceStructByName(methodName)
|
||||
case "deviceio":
|
||||
methodStruct, err = getDeviceStructByName(methodName)
|
||||
case "ptz":
|
||||
methodStruct, err = getPTZStructByName(methodName)
|
||||
case "media":
|
||||
@@ -150,7 +152,7 @@ func callNecessaryMethod(serviceName, methodName, acceptedData, username, passwo
|
||||
|
||||
servResp, err := networking.SendSoap(new(http.Client), endpoint, soap.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
servResp, err = networking.SendSoapWithDigest(new(http.Client), endpoint, soap.String(), username, password)
|
||||
}
|
||||
|
||||
rsp, err := ioutil.ReadAll(servResp.Body)
|
||||
|
||||
@@ -680,7 +680,7 @@ type GetRelayOutputs struct {
|
||||
}
|
||||
|
||||
type GetRelayOutputsResponse struct {
|
||||
RelayOutputs onvif.RelayOutput
|
||||
RelayOutputs []onvif.RelayOutput
|
||||
}
|
||||
|
||||
type GetDigitalInputs struct {
|
||||
@@ -688,7 +688,7 @@ type GetDigitalInputs struct {
|
||||
}
|
||||
|
||||
type GetDigitalInputsResponse struct {
|
||||
DigitalInputs onvif.DigitalInput
|
||||
DigitalInputs []onvif.DigitalInput
|
||||
}
|
||||
|
||||
type SetRelayOutputSettings struct {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -8,6 +8,8 @@ require (
|
||||
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/icholy/digest v0.1.23
|
||||
github.com/juju/errors v1.0.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/net v0.19.0
|
||||
)
|
||||
|
||||
12
go.sum
12
go.sum
@@ -29,16 +29,22 @@ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/icholy/digest v0.1.23 h1:4hX2pIloP0aDx7RJW0JewhPPy3R8kU+vWKdxPsCCGtY=
|
||||
github.com/icholy/digest v0.1.23/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM=
|
||||
github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
@@ -81,14 +87,14 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
2
names.go
2
names.go
@@ -75,7 +75,7 @@ const (
|
||||
GetNetworkProtocols = "GetNetworkProtocols"
|
||||
GetPkcs10Request = "GetPkcs10Request"
|
||||
GetRelayOutputs = "GetRelayOutputs"
|
||||
GetDigitalInputs = "GetDigitalInputs"
|
||||
GetDigitalInputs = "GetDigitalInputs"
|
||||
GetRemoteDiscoveryMode = "GetRemoteDiscoveryMode"
|
||||
GetRemoteUser = "GetRemoteUser"
|
||||
GetScopes = "GetScopes"
|
||||
|
||||
@@ -2,14 +2,87 @@ package networking
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/icholy/digest"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// SendSoap send soap message
|
||||
func SendSoap(httpClient *http.Client, endpoint, message string) (*http.Response, error) {
|
||||
resp, err := httpClient.Post(endpoint, "application/soap+xml; charset=utf-8", bytes.NewBufferString(message))
|
||||
if err != nil {
|
||||
return resp, err
|
||||
return resp, errors.Annotate(err, "Post")
|
||||
}
|
||||
|
||||
// if resp.StatusCode is 4xx,5xx, return error
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
return resp, errors.Errorf("Server error: %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func SendSoapWithDigest(httpClient *http.Client, endpoint, message, username, password string) (*http.Response, error) {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromString(message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := doc.FindElement("./Envelope/Header/Security")
|
||||
if e != nil {
|
||||
bodyTag := doc.Root().SelectElement("Header")
|
||||
bodyTag.RemoveChild(e)
|
||||
data, err := doc.WriteToString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message = data
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", endpoint, bytes.NewBufferString(message))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return resp, errors.Annotate(err, "Post with digest")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
wwwAuth := resp.Header.Get("WWW-Authenticate")
|
||||
chal, err := digest.ParseChallenge(wwwAuth)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("fail to parse challenge: %w", err)
|
||||
}
|
||||
|
||||
cred, err := digest.Digest(chal, digest.Options{
|
||||
Method: "POST",
|
||||
URI: req.URL.RequestURI(),
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("fail to build digest: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", cred.String())
|
||||
req.Body = io.NopCloser((bytes.NewBufferString(message)))
|
||||
resp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Post with digest")
|
||||
}
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
return resp, errors.Errorf("Post with digest error: %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
||||
@@ -1840,8 +1840,8 @@ type RelayOutputSettings struct {
|
||||
}
|
||||
|
||||
type DigitalInput struct {
|
||||
Token ReferenceToken `xml:"token,attr"`
|
||||
IdleState InputIdleState `xml:"IdleState,attr"`
|
||||
Token ReferenceToken `xml:"token,attr"`
|
||||
IdleState InputIdleState `xml:"IdleState,attr"`
|
||||
}
|
||||
|
||||
// TODO:enumeration
|
||||
|
||||
Reference in New Issue
Block a user