Skip to content

Commit

Permalink
add time sync functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Amin Nami committed Apr 9, 2023
1 parent 5c2fea3 commit e617b6a
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 50 deletions.
96 changes: 69 additions & 27 deletions Device.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package onvif

import (
"context"
"encoding/xml"
"errors"
"github.com/AminN77/customGoOnvif/sdk"
"io/ioutil"
"github.com/juju/errors"
"io"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"time"

"github.com/AminN77/customGoOnvif/device"
"github.com/AminN77/customGoOnvif/gosoap"
Expand Down Expand Up @@ -78,9 +77,10 @@ type DeviceInfo struct {
// struct represents an abstract ONVIF device.
// It contains methods, which helps to communicate with ONVIF device
type Device struct {
params DeviceParams
endpoints map[string]string
info DeviceInfo
params DeviceParams
endpoints map[string]string
info DeviceInfo
timeOffset gosoap.TimeOffset
}

type DeviceParams struct {
Expand All @@ -95,13 +95,13 @@ func (dev *Device) GetServices() map[string]string {
return dev.endpoints
}

// GetServices return available endpoints
// GetDeviceInfo return available infos
func (dev *Device) GetDeviceInfo() DeviceInfo {
return dev.info
}

func readResponse(resp *http.Response) string {
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -145,7 +145,7 @@ func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) ([]Dev
func (dev *Device) getSupportedServices(resp *http.Response) {
doc := etree.NewDocument()

data, _ := ioutil.ReadAll(resp.Body)
data, _ := io.ReadAll(resp.Body)

if err := doc.ReadFromBytes(data); err != nil {
//log.Println(err.Error())
Expand All @@ -155,8 +155,9 @@ func (dev *Device) getSupportedServices(resp *http.Response) {
for _, j := range services {
dev.addEndpoint(j.Parent().Tag, j.Text())
}
extension_services := doc.FindElements("./Envelope/Body/GetCapabilitiesResponse/Capabilities/Extension/*/XAddr")
for _, j := range extension_services {

extensionServices := doc.FindElements("./Envelope/Body/GetCapabilitiesResponse/Capabilities/Extension/*/XAddr")
for _, j := range extensionServices {
dev.addEndpoint(j.Parent().Tag, j.Text())
}
}
Expand Down Expand Up @@ -184,30 +185,60 @@ func NewDevice(params DeviceParams) (*Device, error) {
return dev, nil
}

func NewDeviceWithGetTime(params DeviceParams) (*Device, device.GetSystemDateAndTimeResponse, error) {
type Envelope struct {
XMLName xml.Name `xml:"Envelope"`
//Text string `xml:",chardata"`
//Sc string `xml:"sc,attr"`
//S string `xml:"s,attr"`
//Tt string `xml:"tt,attr"`
//Tds string `xml:"tds,attr"`
Header string `xml:"Header"`
Body device.GetSystemDateAndTimeResponseNew `xml:"Body"`
}

func NewDeviceWithGetTime(params DeviceParams) (*Device, error) {
dev := new(Device)
dev.params = params
dev.params.Username = ""
dev.params.Password = ""
dev.endpoints = make(map[string]string)
dev.addEndpoint("Device", "http://"+dev.params.Xaddr+"/onvif/device_service")
type Envelope struct {
Header struct{}
Body struct {
GetSystemDateAndTimeResponse device.GetSystemDateAndTimeResponse
}
}
var reply Envelope

//var reply Envelope
if dev.params.HttpClient == nil {
dev.params.HttpClient = new(http.Client)
}

getDateTime := device.GetSystemDateAndTime{}
httpReply, err := dev.CallMethod(getDateTime)
if err != nil {
return nil, reply.Body.GetSystemDateAndTimeResponse, errors.New("camera is not available")

ct := time.Now().UTC()
if httpReply, err := dev.CallMethod(getDateTime); err != nil {
return dev, errors.Annotate(err, "call")
} else {
err = sdk.ReadAndParse(context.Background(), httpReply, &reply, "GetSystemDateAndTime")
return dev, reply.Body.GetSystemDateAndTimeResponse, nil
bo := &Envelope{}
b, err := io.ReadAll(httpReply.Body)
err = xml.Unmarshal(b, bo)

st := time.Date(
bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Date.Year,
time.Month(bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Date.Month),
bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Date.Day,
bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Time.Hour,
bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Time.Minute,
bo.Body.GetSystemDateAndTimeResponse.SystemDateAndTime.UTCDateTime.Time.Second,
0,
time.UTC,
)

dev.timeOffset = *timeOffsetCalculator(st, ct)
dev.params.Username = params.Username
dev.params.Password = params.Password

getCapabilities := device.GetCapabilities{Category: "All"}
resp, err := dev.CallMethod(getCapabilities)
dev.getSupportedServices(resp)

return dev, errors.Annotate(err, "reply")
}
}

Expand Down Expand Up @@ -267,7 +298,7 @@ func (dev Device) getEndpoint(endpoint string) (string, error) {
return endpointURL, errors.New("target endpoint service not found")
}

// CallMethod functions call an method, defined <method> struct.
// CallMethod functions call a method, defined <method> struct.
// You should use Authenticate method to call authorized requests.
func (dev Device) CallMethod(method interface{}) (*http.Response, error) {
pkgPath := strings.Split(reflect.TypeOf(method).PkgPath(), "/")
Expand Down Expand Up @@ -297,8 +328,19 @@ func (dev Device) callMethodDo(endpoint string, method interface{}) (*http.Respo

//Auth Handling
if dev.params.Username != "" && dev.params.Password != "" {
soap.AddWSSecurity(dev.params.Username, dev.params.Password)
soap.AddWSSecurity(dev.params.Username, dev.params.Password, dev.timeOffset)
}

return networking.SendSoap(dev.params.HttpClient, endpoint, soap.String())
}

// utility functions
func timeOffsetCalculator(st time.Time, ct time.Time) *gosoap.TimeOffset {
td := ((st.Hour() - ct.Hour()) * 3_600) + ((st.Minute() - ct.Minute()) * 60) + (st.Second() - ct.Second())
return &gosoap.TimeOffset{
Year: st.Year(),
Month: st.Month(),
Day: st.Day(),
TimeDiff: td,
}
}
40 changes: 24 additions & 16 deletions gosoap/soap-builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ package gosoap
import (
"encoding/xml"
"log"
"time"

"github.com/beevik/etree"
)

//SoapMessage type from string
type TimeOffset struct {
Year int
Month time.Month
Day int
TimeDiff int
}

// SoapMessage type from string
type SoapMessage string

// NewEmptySOAP return new SoapMessage
Expand All @@ -20,7 +28,7 @@ func NewEmptySOAP() SoapMessage {
return SoapMessage(res)
}

//NewSOAP Get a new soap message
// NewSOAP Get a new soap message
func NewSOAP(headContent []*etree.Element, bodyContent []*etree.Element, namespaces map[string]string) SoapMessage {
doc := buildSoapRoot()
//doc.IndentTabs()
Expand All @@ -34,7 +42,7 @@ func (msg SoapMessage) String() string {
return string(msg)
}

//StringIndent handle indent
// StringIndent handle indent
func (msg SoapMessage) StringIndent() string {
doc := etree.NewDocument()

Expand All @@ -48,7 +56,7 @@ func (msg SoapMessage) StringIndent() string {
return res
}

//Body return body from Envelope
// Body return body from Envelope
func (msg SoapMessage) Body() string {

doc := etree.NewDocument()
Expand All @@ -65,7 +73,7 @@ func (msg SoapMessage) Body() string {
return res
}

//AddStringBodyContent for Envelope
// AddStringBodyContent for Envelope
func (msg *SoapMessage) AddStringBodyContent(data string) {
doc := etree.NewDocument()

Expand All @@ -89,7 +97,7 @@ func (msg *SoapMessage) AddStringBodyContent(data string) {
*msg = SoapMessage(res)
}

//AddBodyContent for Envelope
// AddBodyContent for Envelope
func (msg *SoapMessage) AddBodyContent(element *etree.Element) {
doc := etree.NewDocument()
if err := doc.ReadFromString(msg.String()); err != nil {
Expand All @@ -105,7 +113,7 @@ func (msg *SoapMessage) AddBodyContent(element *etree.Element) {
*msg = SoapMessage(res)
}

//AddBodyContents for Envelope body
// AddBodyContents for Envelope body
func (msg *SoapMessage) AddBodyContents(elements []*etree.Element) {
doc := etree.NewDocument()
if err := doc.ReadFromString(msg.String()); err != nil {
Expand All @@ -126,7 +134,7 @@ func (msg *SoapMessage) AddBodyContents(elements []*etree.Element) {
*msg = SoapMessage(res)
}

//AddStringHeaderContent for Envelope body
// AddStringHeaderContent for Envelope body
func (msg *SoapMessage) AddStringHeaderContent(data string) error {
doc := etree.NewDocument()

Expand Down Expand Up @@ -154,7 +162,7 @@ func (msg *SoapMessage) AddStringHeaderContent(data string) error {
return nil
}

//AddHeaderContent for Envelope body
// AddHeaderContent for Envelope body
func (msg *SoapMessage) AddHeaderContent(element *etree.Element) {
doc := etree.NewDocument()
if err := doc.ReadFromString(msg.String()); err != nil {
Expand All @@ -170,7 +178,7 @@ func (msg *SoapMessage) AddHeaderContent(element *etree.Element) {
*msg = SoapMessage(res)
}

//AddHeaderContents for Envelope body
// AddHeaderContents for Envelope body
func (msg *SoapMessage) AddHeaderContents(elements []*etree.Element) {
doc := etree.NewDocument()
if err := doc.ReadFromString(msg.String()); err != nil {
Expand All @@ -191,7 +199,7 @@ func (msg *SoapMessage) AddHeaderContents(elements []*etree.Element) {
*msg = SoapMessage(res)
}

//AddRootNamespace for Envelope body
// AddRootNamespace for Envelope body
func (msg *SoapMessage) AddRootNamespace(key, value string) {
doc := etree.NewDocument()
if err := doc.ReadFromString(msg.String()); err != nil {
Expand All @@ -204,7 +212,7 @@ func (msg *SoapMessage) AddRootNamespace(key, value string) {
*msg = SoapMessage(res)
}

//AddRootNamespaces for Envelope body
// AddRootNamespaces for Envelope body
func (msg *SoapMessage) AddRootNamespaces(namespaces map[string]string) {
for key, value := range namespaces {
msg.AddRootNamespace(key, value)
Expand Down Expand Up @@ -242,16 +250,16 @@ func buildSoapRoot() *etree.Document {
return doc
}

//AddWSSecurity Header for soapMessage
func (msg *SoapMessage) AddWSSecurity(username, password string) {
// AddWSSecurity Header for soapMessage
func (msg *SoapMessage) AddWSSecurity(username, password string, offset TimeOffset) {
//doc := etree.NewDocument()
//if err := doc.ReadFromString(msg.String()); err != nil {
// log.Println(err.Error())
//}
/*
Getting an WS-Security struct representation
*/
auth := NewSecurity(username, password)
auth := NewSecurity(username, password, offset)

/*
Adding WS-Security namespaces to root element of SOAP message
Expand All @@ -276,7 +284,7 @@ func (msg *SoapMessage) AddWSSecurity(username, password string) {
//*msg = SoapMessage(res)
}

//AddAction Header handling for soapMessage
// AddAction Header handling for soapMessage
func (msg *SoapMessage) AddAction() {

doc := etree.NewDocument()
Expand Down
23 changes: 16 additions & 7 deletions gosoap/ws-security.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import (
"github.com/elgs/gostrgen"
)

/*************************
/*
************************
WS-Security types
*************************/
************************
*/
const (
passwordType = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
encodingType = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
)

//Security type :XMLName xml.Name `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
// Security type :XMLName xml.Name `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
type Security struct {
//XMLName xml.Name `xml:"wsse:Security"`
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
Expand Down Expand Up @@ -55,14 +59,14 @@ type wsAuth struct {
</Security>
*/

//NewSecurity get a new security
func NewSecurity(username, passwd string) Security {
// NewSecurity get a new security
func NewSecurity(username, passwd string, offset TimeOffset) Security {
/** Generating Nonce sequence **/
charsToGenerate := 32
charSet := gostrgen.Lower | gostrgen.Digit

nonceSeq, _ := gostrgen.RandGen(charsToGenerate, charSet, "", "")
created := time.Now().UTC().Format(time.RFC3339Nano)
created := calculateCreateTime(offset).Format(time.RFC3339Nano)
auth := Security{
Auth: wsAuth{
Username: username,
Expand All @@ -81,7 +85,7 @@ func NewSecurity(username, passwd string) Security {
return auth
}

//Digest = B64ENCODE( SHA1( B64DECODE( Nonce ) + Date + Password ) )
// Digest = B64ENCODE( SHA1( B64DECODE( Nonce ) + Date + Password ) )
func generateToken(Username string, Nonce string, Created string, Password string) string {
sDec, _ := base64.StdEncoding.DecodeString(Nonce)

Expand All @@ -90,3 +94,8 @@ func generateToken(Username string, Nonce string, Created string, Password strin

return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}

func calculateCreateTime(offset TimeOffset) time.Time {
t := time.Now().UTC()
return time.Date(offset.Year, offset.Month, offset.Day, t.Hour(), t.Minute(), t.Second(), 0, time.UTC).Add(time.Duration(offset.TimeDiff) * time.Second)
}

0 comments on commit e617b6a

Please sign in to comment.