diff --git a/COMPREHENSIVE_TEST_SUMMARY.md b/COMPREHENSIVE_TEST_SUMMARY.md index acc72d2..1150b8f 100644 --- a/COMPREHENSIVE_TEST_SUMMARY.md +++ b/COMPREHENSIVE_TEST_SUMMARY.md @@ -302,3 +302,4 @@ The library provides **complete coverage** of all essential ONVIF Media and Devi *Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)* + diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md index 31bdd8c..2006cce 100644 --- a/IMPLEMENTATION_COMPLETE.md +++ b/IMPLEMENTATION_COMPLETE.md @@ -101,3 +101,4 @@ New types added to `types.go`: *Total Operations: 79/79 (100%)* + diff --git a/IMPLEMENTATION_STATUS.md b/IMPLEMENTATION_STATUS.md index 771d05f..7fb747e 100644 --- a/IMPLEMENTATION_STATUS.md +++ b/IMPLEMENTATION_STATUS.md @@ -168,3 +168,4 @@ The library provides **complete coverage** of all essential ONVIF operations req *Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)* + diff --git a/MEDIA_OPERATIONS_ANALYSIS.md b/MEDIA_OPERATIONS_ANALYSIS.md index ea32b84..46b96b4 100644 --- a/MEDIA_OPERATIONS_ANALYSIS.md +++ b/MEDIA_OPERATIONS_ANALYSIS.md @@ -229,3 +229,4 @@ The missing operations are primarily **optional discovery and management operati *Last Updated: December 1, 2025* + diff --git a/MEDIA_WSDL_OPERATIONS_ANALYSIS.md b/MEDIA_WSDL_OPERATIONS_ANALYSIS.md index 8c68567..0c8f830 100644 --- a/MEDIA_WSDL_OPERATIONS_ANALYSIS.md +++ b/MEDIA_WSDL_OPERATIONS_ANALYSIS.md @@ -209,3 +209,4 @@ Implement Video Analytics and Audio Decoder operations if needed for specific us *Last Updated: December 2, 2025* + diff --git a/client.go b/client.go index 68f7a12..1221cb8 100644 --- a/client.go +++ b/client.go @@ -506,15 +506,10 @@ func extractParam(authHeader, param string) string { } func md5Hash(s string) string { - return fmt.Sprintf("%x", md5sum(s)) -} - -func md5sum(s string) interface{} { - // Use crypto/md5 - import it if not already present h := md5.New() //nolint:gosec // MD5 required for ONVIF digest auth h.Write([]byte(s)) - return h.Sum(nil) + return hex.EncodeToString(h.Sum(nil)) } // generateNonce generates a cryptographically secure random nonce for digest authentication. diff --git a/device_security.go b/device_security.go index ea421ff..08a1b92 100644 --- a/device_security.go +++ b/device_security.go @@ -8,14 +8,73 @@ import ( "github.com/0x524a/onvif-go/internal/soap" ) +// Common XML request/response types for device security operations. +// These are defined at package level to avoid repeated inline struct definitions. + +// ipAddressFilterRequest is the common structure for IP address filter SOAP requests. +type ipAddressFilterRequest struct { + Type string `xml:"tds:Type"` + IPv4Address []prefixedIPv4AddressXML `xml:"tds:IPv4Address,omitempty"` + IPv6Address []prefixedIPv6AddressXML `xml:"tds:IPv6Address,omitempty"` +} + +// prefixedIPv4AddressXML is the XML representation of a prefixed IPv4 address. +type prefixedIPv4AddressXML struct { + Address string `xml:"tds:Address"` + PrefixLength int `xml:"tds:PrefixLength"` +} + +// prefixedIPv6AddressXML is the XML representation of a prefixed IPv6 address. +type prefixedIPv6AddressXML struct { + Address string `xml:"tds:Address"` + PrefixLength int `xml:"tds:PrefixLength"` +} + +// buildIPAddressFilterRequest converts an IPAddressFilter to the XML request format. +// Pre-allocates slices for efficiency when the source length is known. +func buildIPAddressFilterRequest(filter *IPAddressFilter) ipAddressFilterRequest { + req := ipAddressFilterRequest{ + Type: string(filter.Type), + } + + // Pre-allocate slices with known capacity + if len(filter.IPv4Address) > 0 { + req.IPv4Address = make([]prefixedIPv4AddressXML, 0, len(filter.IPv4Address)) + for _, addr := range filter.IPv4Address { + req.IPv4Address = append(req.IPv4Address, prefixedIPv4AddressXML{ + Address: addr.Address, + PrefixLength: addr.PrefixLength, + }) + } + } + + if len(filter.IPv6Address) > 0 { + req.IPv6Address = make([]prefixedIPv6AddressXML, 0, len(filter.IPv6Address)) + for _, addr := range filter.IPv6Address { + req.IPv6Address = append(req.IPv6Address, prefixedIPv6AddressXML{ + Address: addr.Address, + PrefixLength: addr.PrefixLength, + }) + } + } + + return req +} + +// newSOAPClient creates a SOAP client with the current client credentials. +func (c *Client) newSOAPClient() *soap.Client { + username, password := c.GetCredentials() + return soap.NewClient(c.httpClient, username, password) +} + // GetRemoteUser returns the configured remote user. func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) { - type GetRemoteUser struct { + type getRemoteUserRequest struct { XMLName xml.Name `xml:"tds:GetRemoteUser"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetRemoteUserResponse struct { + type getRemoteUserResponse struct { XMLName xml.Name `xml:"GetRemoteUserResponse"` RemoteUser *struct { Username string `xml:"Username"` @@ -24,16 +83,12 @@ func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) { } `xml:"RemoteUser"` } - req := GetRemoteUser{ + req := getRemoteUserRequest{ Xmlns: deviceNamespace, } - var resp GetRemoteUserResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getRemoteUserResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetRemoteUser failed: %w", err) } @@ -50,36 +105,31 @@ func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) { // SetRemoteUser sets the remote user. func (c *Client) SetRemoteUser(ctx context.Context, remoteUser *RemoteUser) error { - type SetRemoteUser struct { - XMLName xml.Name `xml:"tds:SetRemoteUser"` - Xmlns string `xml:"xmlns:tds,attr"` - RemoteUser *struct { - Username string `xml:"tds:Username"` - Password string `xml:"tds:Password,omitempty"` - UseDerivedPassword bool `xml:"tds:UseDerivedPassword"` - } `xml:"tds:RemoteUser,omitempty"` + type remoteUserXML struct { + Username string `xml:"tds:Username"` + Password string `xml:"tds:Password,omitempty"` + UseDerivedPassword bool `xml:"tds:UseDerivedPassword"` } - req := SetRemoteUser{ + type setRemoteUserRequest struct { + XMLName xml.Name `xml:"tds:SetRemoteUser"` + Xmlns string `xml:"xmlns:tds,attr"` + RemoteUser *remoteUserXML `xml:"tds:RemoteUser,omitempty"` + } + + req := setRemoteUserRequest{ Xmlns: deviceNamespace, } if remoteUser != nil { - req.RemoteUser = &struct { - Username string `xml:"tds:Username"` - Password string `xml:"tds:Password,omitempty"` - UseDerivedPassword bool `xml:"tds:UseDerivedPassword"` - }{ + req.RemoteUser = &remoteUserXML{ Username: remoteUser.Username, Password: remoteUser.Password, UseDerivedPassword: remoteUser.UseDerivedPassword, } } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetRemoteUser failed: %w", err) } @@ -88,36 +138,31 @@ func (c *Client) SetRemoteUser(ctx context.Context, remoteUser *RemoteUser) erro // GetIPAddressFilter gets the IP address filter settings from a device. func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, error) { - type GetIPAddressFilter struct { + type getIPAddressFilterRequest struct { XMLName xml.Name `xml:"tds:GetIPAddressFilter"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetIPAddressFilterResponse struct { + type prefixedAddressXML struct { + Address string `xml:"Address"` + PrefixLength int `xml:"PrefixLength"` + } + + type getIPAddressFilterResponse struct { XMLName xml.Name `xml:"GetIPAddressFilterResponse"` IPAddressFilter struct { - Type string `xml:"Type"` - IPv4Address []struct { - Address string `xml:"Address"` - PrefixLength int `xml:"PrefixLength"` - } `xml:"IPv4Address"` - IPv6Address []struct { - Address string `xml:"Address"` - PrefixLength int `xml:"PrefixLength"` - } `xml:"IPv6Address"` + Type string `xml:"Type"` + IPv4Address []prefixedAddressXML `xml:"IPv4Address"` + IPv6Address []prefixedAddressXML `xml:"IPv6Address"` } `xml:"IPAddressFilter"` } - req := GetIPAddressFilter{ + req := getIPAddressFilterRequest{ Xmlns: deviceNamespace, } - var resp GetIPAddressFilterResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getIPAddressFilterResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetIPAddressFilter failed: %w", err) } @@ -125,18 +170,25 @@ func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, erro Type: IPAddressFilterType(resp.IPAddressFilter.Type), } - for _, addr := range resp.IPAddressFilter.IPv4Address { - filter.IPv4Address = append(filter.IPv4Address, PrefixedIPv4Address{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) + // Pre-allocate slices with known capacity + if len(resp.IPAddressFilter.IPv4Address) > 0 { + filter.IPv4Address = make([]PrefixedIPv4Address, 0, len(resp.IPAddressFilter.IPv4Address)) + for _, addr := range resp.IPAddressFilter.IPv4Address { + filter.IPv4Address = append(filter.IPv4Address, PrefixedIPv4Address{ + Address: addr.Address, + PrefixLength: addr.PrefixLength, + }) + } } - for _, addr := range resp.IPAddressFilter.IPv6Address { - filter.IPv6Address = append(filter.IPv6Address, PrefixedIPv6Address{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) + if len(resp.IPAddressFilter.IPv6Address) > 0 { + filter.IPv6Address = make([]PrefixedIPv6Address, 0, len(resp.IPAddressFilter.IPv6Address)) + for _, addr := range resp.IPAddressFilter.IPv6Address { + filter.IPv6Address = append(filter.IPv6Address, PrefixedIPv6Address{ + Address: addr.Address, + PrefixLength: addr.PrefixLength, + }) + } } return filter, nil @@ -144,51 +196,18 @@ func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, erro // SetIPAddressFilter sets the IP address filter settings on a device. func (c *Client) SetIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { - type SetIPAddressFilter struct { - XMLName xml.Name `xml:"tds:SetIPAddressFilter"` - Xmlns string `xml:"xmlns:tds,attr"` - IPAddressFilter struct { - Type string `xml:"tds:Type"` - IPv4Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv4Address,omitempty"` - IPv6Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv6Address,omitempty"` - } `xml:"tds:IPAddressFilter"` + type setIPAddressFilterRequest struct { + XMLName xml.Name `xml:"tds:SetIPAddressFilter"` + Xmlns string `xml:"xmlns:tds,attr"` + IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"` } - req := SetIPAddressFilter{ - Xmlns: deviceNamespace, - } - req.IPAddressFilter.Type = string(filter.Type) - - for _, addr := range filter.IPv4Address { - req.IPAddressFilter.IPv4Address = append(req.IPAddressFilter.IPv4Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) + req := setIPAddressFilterRequest{ + Xmlns: deviceNamespace, + IPAddressFilter: buildIPAddressFilterRequest(filter), } - for _, addr := range filter.IPv6Address { - req.IPAddressFilter.IPv6Address = append(req.IPAddressFilter.IPv6Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) - } - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetIPAddressFilter failed: %w", err) } @@ -197,51 +216,18 @@ func (c *Client) SetIPAddressFilter(ctx context.Context, filter *IPAddressFilter // AddIPAddressFilter adds an IP filter address to a device. func (c *Client) AddIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { - type AddIPAddressFilter struct { - XMLName xml.Name `xml:"tds:AddIPAddressFilter"` - Xmlns string `xml:"xmlns:tds,attr"` - IPAddressFilter struct { - Type string `xml:"tds:Type"` - IPv4Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv4Address,omitempty"` - IPv6Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv6Address,omitempty"` - } `xml:"tds:IPAddressFilter"` + type addIPAddressFilterRequest struct { + XMLName xml.Name `xml:"tds:AddIPAddressFilter"` + Xmlns string `xml:"xmlns:tds,attr"` + IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"` } - req := AddIPAddressFilter{ - Xmlns: deviceNamespace, - } - req.IPAddressFilter.Type = string(filter.Type) - - for _, addr := range filter.IPv4Address { - req.IPAddressFilter.IPv4Address = append(req.IPAddressFilter.IPv4Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) + req := addIPAddressFilterRequest{ + Xmlns: deviceNamespace, + IPAddressFilter: buildIPAddressFilterRequest(filter), } - for _, addr := range filter.IPv6Address { - req.IPAddressFilter.IPv6Address = append(req.IPAddressFilter.IPv6Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) - } - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("AddIPAddressFilter failed: %w", err) } @@ -250,51 +236,18 @@ func (c *Client) AddIPAddressFilter(ctx context.Context, filter *IPAddressFilter // RemoveIPAddressFilter deletes an IP filter address from a device. func (c *Client) RemoveIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { - type RemoveIPAddressFilter struct { - XMLName xml.Name `xml:"tds:RemoveIPAddressFilter"` - Xmlns string `xml:"xmlns:tds,attr"` - IPAddressFilter struct { - Type string `xml:"tds:Type"` - IPv4Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv4Address,omitempty"` - IPv6Address []struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - } `xml:"tds:IPv6Address,omitempty"` - } `xml:"tds:IPAddressFilter"` + type removeIPAddressFilterRequest struct { + XMLName xml.Name `xml:"tds:RemoveIPAddressFilter"` + Xmlns string `xml:"xmlns:tds,attr"` + IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"` } - req := RemoveIPAddressFilter{ - Xmlns: deviceNamespace, - } - req.IPAddressFilter.Type = string(filter.Type) - - for _, addr := range filter.IPv4Address { - req.IPAddressFilter.IPv4Address = append(req.IPAddressFilter.IPv4Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) + req := removeIPAddressFilterRequest{ + Xmlns: deviceNamespace, + IPAddressFilter: buildIPAddressFilterRequest(filter), } - for _, addr := range filter.IPv6Address { - req.IPAddressFilter.IPv6Address = append(req.IPAddressFilter.IPv6Address, struct { - Address string `xml:"tds:Address"` - PrefixLength int `xml:"tds:PrefixLength"` - }{ - Address: addr.Address, - PrefixLength: addr.PrefixLength, - }) - } - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("RemoveIPAddressFilter failed: %w", err) } @@ -303,12 +256,12 @@ func (c *Client) RemoveIPAddressFilter(ctx context.Context, filter *IPAddressFil // GetZeroConfiguration gets the zero-configuration from a device. func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfiguration, error) { - type GetZeroConfiguration struct { + type getZeroConfigurationRequest struct { XMLName xml.Name `xml:"tds:GetZeroConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetZeroConfigurationResponse struct { + type getZeroConfigurationResponse struct { XMLName xml.Name `xml:"GetZeroConfigurationResponse"` ZeroConfiguration struct { InterfaceToken string `xml:"InterfaceToken"` @@ -317,16 +270,12 @@ func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfigur } `xml:"ZeroConfiguration"` } - req := GetZeroConfiguration{ + req := getZeroConfigurationRequest{ Xmlns: deviceNamespace, } - var resp GetZeroConfigurationResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getZeroConfigurationResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetZeroConfiguration failed: %w", err) } @@ -339,23 +288,20 @@ func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfigur // SetZeroConfiguration sets the zero-configuration. func (c *Client) SetZeroConfiguration(ctx context.Context, interfaceToken string, enabled bool) error { - type SetZeroConfiguration struct { + type setZeroConfigurationRequest struct { XMLName xml.Name `xml:"tds:SetZeroConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` InterfaceToken string `xml:"tds:InterfaceToken"` Enabled bool `xml:"tds:Enabled"` } - req := SetZeroConfiguration{ + req := setZeroConfigurationRequest{ Xmlns: deviceNamespace, InterfaceToken: interfaceToken, Enabled: enabled, } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetZeroConfiguration failed: %w", err) } @@ -364,12 +310,12 @@ func (c *Client) SetZeroConfiguration(ctx context.Context, interfaceToken string // GetDynamicDNS gets the dynamic DNS settings from a device. func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, error) { - type GetDynamicDNS struct { + type getDynamicDNSRequest struct { XMLName xml.Name `xml:"tds:GetDynamicDNS"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetDynamicDNSResponse struct { + type getDynamicDNSResponse struct { XMLName xml.Name `xml:"GetDynamicDNSResponse"` DynamicDNSInformation struct { Type string `xml:"Type"` @@ -378,16 +324,12 @@ func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, err } `xml:"DynamicDNSInformation"` } - req := GetDynamicDNS{ + req := getDynamicDNSRequest{ Xmlns: deviceNamespace, } - var resp GetDynamicDNSResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getDynamicDNSResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetDynamicDNS failed: %w", err) } @@ -400,23 +342,20 @@ func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, err // SetDynamicDNS sets the dynamic DNS settings on a device. func (c *Client) SetDynamicDNS(ctx context.Context, dnsType DynamicDNSType, name string) error { - type SetDynamicDNS struct { + type setDynamicDNSRequest struct { XMLName xml.Name `xml:"tds:SetDynamicDNS"` Xmlns string `xml:"xmlns:tds,attr"` Type DynamicDNSType `xml:"tds:Type"` Name string `xml:"tds:Name,omitempty"` } - req := SetDynamicDNS{ + req := setDynamicDNSRequest{ Xmlns: deviceNamespace, Type: dnsType, Name: name, } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetDynamicDNS failed: %w", err) } @@ -425,12 +364,12 @@ func (c *Client) SetDynamicDNS(ctx context.Context, dnsType DynamicDNSType, name // GetPasswordComplexityConfiguration retrieves the current password complexity configuration settings. func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*PasswordComplexityConfiguration, error) { - type GetPasswordComplexityConfiguration struct { + type getPasswordComplexityConfigurationRequest struct { XMLName xml.Name `xml:"tds:GetPasswordComplexityConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetPasswordComplexityConfigurationResponse struct { + type getPasswordComplexityConfigurationResponse struct { XMLName xml.Name `xml:"GetPasswordComplexityConfigurationResponse"` MinLen int `xml:"MinLen"` Uppercase int `xml:"Uppercase"` @@ -440,16 +379,12 @@ func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*Passw PolicyConfigurationLocked bool `xml:"PolicyConfigurationLocked"` } - req := GetPasswordComplexityConfiguration{ + req := getPasswordComplexityConfigurationRequest{ Xmlns: deviceNamespace, } - var resp GetPasswordComplexityConfigurationResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getPasswordComplexityConfigurationResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetPasswordComplexityConfiguration failed: %w", err) } @@ -468,7 +403,7 @@ func (c *Client) SetPasswordComplexityConfiguration( ctx context.Context, config *PasswordComplexityConfiguration, ) error { - type SetPasswordComplexityConfiguration struct { + type setPasswordComplexityConfigurationRequest struct { XMLName xml.Name `xml:"tds:SetPasswordComplexityConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` MinLen int `xml:"tds:MinLen,omitempty"` @@ -479,7 +414,7 @@ func (c *Client) SetPasswordComplexityConfiguration( PolicyConfigurationLocked bool `xml:"tds:PolicyConfigurationLocked,omitempty"` } - req := SetPasswordComplexityConfiguration{ + req := setPasswordComplexityConfigurationRequest{ Xmlns: deviceNamespace, MinLen: config.MinLen, Uppercase: config.Uppercase, @@ -489,10 +424,7 @@ func (c *Client) SetPasswordComplexityConfiguration( PolicyConfigurationLocked: config.PolicyConfigurationLocked, } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetPasswordComplexityConfiguration failed: %w", err) } @@ -501,27 +433,23 @@ func (c *Client) SetPasswordComplexityConfiguration( // GetPasswordHistoryConfiguration retrieves the current password history configuration settings. func (c *Client) GetPasswordHistoryConfiguration(ctx context.Context) (*PasswordHistoryConfiguration, error) { - type GetPasswordHistoryConfiguration struct { + type getPasswordHistoryConfigurationRequest struct { XMLName xml.Name `xml:"tds:GetPasswordHistoryConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetPasswordHistoryConfigurationResponse struct { + type getPasswordHistoryConfigurationResponse struct { XMLName xml.Name `xml:"GetPasswordHistoryConfigurationResponse"` Enabled bool `xml:"Enabled"` Length int `xml:"Length"` } - req := GetPasswordHistoryConfiguration{ + req := getPasswordHistoryConfigurationRequest{ Xmlns: deviceNamespace, } - var resp GetPasswordHistoryConfigurationResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getPasswordHistoryConfigurationResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetPasswordHistoryConfiguration failed: %w", err) } @@ -533,23 +461,20 @@ func (c *Client) GetPasswordHistoryConfiguration(ctx context.Context) (*Password // SetPasswordHistoryConfiguration allows setting of the password history configuration. func (c *Client) SetPasswordHistoryConfiguration(ctx context.Context, config *PasswordHistoryConfiguration) error { - type SetPasswordHistoryConfiguration struct { + type setPasswordHistoryConfigurationRequest struct { XMLName xml.Name `xml:"tds:SetPasswordHistoryConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` Enabled bool `xml:"tds:Enabled"` Length int `xml:"tds:Length"` } - req := SetPasswordHistoryConfiguration{ + req := setPasswordHistoryConfigurationRequest{ Xmlns: deviceNamespace, Enabled: config.Enabled, Length: config.Length, } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetPasswordHistoryConfiguration failed: %w", err) } @@ -558,28 +483,24 @@ func (c *Client) SetPasswordHistoryConfiguration(ctx context.Context, config *Pa // GetAuthFailureWarningConfiguration retrieves the current authentication failure warning configuration. func (c *Client) GetAuthFailureWarningConfiguration(ctx context.Context) (*AuthFailureWarningConfiguration, error) { - type GetAuthFailureWarningConfiguration struct { + type getAuthFailureWarningConfigurationRequest struct { XMLName xml.Name `xml:"tds:GetAuthFailureWarningConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetAuthFailureWarningConfigurationResponse struct { + type getAuthFailureWarningConfigurationResponse struct { XMLName xml.Name `xml:"GetAuthFailureWarningConfigurationResponse"` Enabled bool `xml:"Enabled"` MonitorPeriod int `xml:"MonitorPeriod"` MaxAuthFailures int `xml:"MaxAuthFailures"` } - req := GetAuthFailureWarningConfiguration{ + req := getAuthFailureWarningConfigurationRequest{ Xmlns: deviceNamespace, } - var resp GetAuthFailureWarningConfigurationResponse - - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil { + var resp getAuthFailureWarningConfigurationResponse + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetAuthFailureWarningConfiguration failed: %w", err) } @@ -595,7 +516,7 @@ func (c *Client) SetAuthFailureWarningConfiguration( ctx context.Context, config *AuthFailureWarningConfiguration, ) error { - type SetAuthFailureWarningConfiguration struct { + type setAuthFailureWarningConfigurationRequest struct { XMLName xml.Name `xml:"tds:SetAuthFailureWarningConfiguration"` Xmlns string `xml:"xmlns:tds,attr"` Enabled bool `xml:"tds:Enabled"` @@ -603,17 +524,14 @@ func (c *Client) SetAuthFailureWarningConfiguration( MaxAuthFailures int `xml:"tds:MaxAuthFailures"` } - req := SetAuthFailureWarningConfiguration{ + req := setAuthFailureWarningConfigurationRequest{ Xmlns: deviceNamespace, Enabled: config.Enabled, MonitorPeriod: config.MonitorPeriod, MaxAuthFailures: config.MaxAuthFailures, } - username, password := c.GetCredentials() - soapClient := soap.NewClient(c.httpClient, username, password) - - if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil { + if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil { return fmt.Errorf("SetAuthFailureWarningConfiguration failed: %w", err) } diff --git a/device_security_test.go b/device_security_test.go index b35c326..bb378b0 100644 --- a/device_security_test.go +++ b/device_security_test.go @@ -521,3 +521,266 @@ func TestIPAddressFilterTypeConstants(t *testing.T) { t.Errorf("IPAddressFilterDeny should be 'Deny', got %s", IPAddressFilterDeny) } } + +// Benchmarks for device security operations. + +func BenchmarkGetRemoteUser(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetRemoteUser(ctx) + } +} + +func BenchmarkSetRemoteUser(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + remoteUser := &RemoteUser{ + Username: "test_user", + Password: "password123", + UseDerivedPassword: true, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetRemoteUser(ctx, remoteUser) + } +} + +func BenchmarkGetIPAddressFilter(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetIPAddressFilter(ctx) + } +} + +func BenchmarkSetIPAddressFilter(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + filter := &IPAddressFilter{ + Type: IPAddressFilterAllow, + IPv4Address: []PrefixedIPv4Address{ + {Address: "192.168.1.0", PrefixLength: 24}, + {Address: "10.0.0.0", PrefixLength: 8}, + }, + IPv6Address: []PrefixedIPv6Address{ + {Address: "fe80::", PrefixLength: 64}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetIPAddressFilter(ctx, filter) + } +} + +func BenchmarkAddIPAddressFilter(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + filter := &IPAddressFilter{ + Type: IPAddressFilterAllow, + IPv4Address: []PrefixedIPv4Address{ + {Address: "172.16.0.0", PrefixLength: 12}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.AddIPAddressFilter(ctx, filter) + } +} + +func BenchmarkRemoveIPAddressFilter(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + filter := &IPAddressFilter{ + Type: IPAddressFilterAllow, + IPv4Address: []PrefixedIPv4Address{ + {Address: "172.16.0.0", PrefixLength: 12}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.RemoveIPAddressFilter(ctx, filter) + } +} + +func BenchmarkGetZeroConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetZeroConfiguration(ctx) + } +} + +func BenchmarkSetZeroConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetZeroConfiguration(ctx, "eth0", true) + } +} + +func BenchmarkGetPasswordComplexityConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetPasswordComplexityConfiguration(ctx) + } +} + +func BenchmarkSetPasswordComplexityConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + config := &PasswordComplexityConfiguration{ + MinLen: 10, + Uppercase: 2, + Number: 2, + SpecialChars: 1, + BlockUsernameOccurrence: true, + PolicyConfigurationLocked: false, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetPasswordComplexityConfiguration(ctx, config) + } +} + +func BenchmarkGetPasswordHistoryConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetPasswordHistoryConfiguration(ctx) + } +} + +func BenchmarkSetPasswordHistoryConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + config := &PasswordHistoryConfiguration{ + Enabled: true, + Length: 10, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetPasswordHistoryConfiguration(ctx, config) + } +} + +func BenchmarkGetAuthFailureWarningConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = client.GetAuthFailureWarningConfiguration(ctx) + } +} + +func BenchmarkSetAuthFailureWarningConfiguration(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + config := &AuthFailureWarningConfiguration{ + Enabled: true, + MonitorPeriod: 120, + MaxAuthFailures: 3, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetAuthFailureWarningConfiguration(ctx, config) + } +} + +// BenchmarkIPAddressFilterWithManyAddresses tests performance with larger address lists. +func BenchmarkIPAddressFilterWithManyAddresses(b *testing.B) { + server := newMockDeviceSecurityServer() + defer server.Close() + + client, _ := NewClient(server.URL) + ctx := context.Background() + + // Create filter with many addresses to test pre-allocation efficiency + filter := &IPAddressFilter{ + Type: IPAddressFilterAllow, + IPv4Address: make([]PrefixedIPv4Address, 100), + IPv6Address: make([]PrefixedIPv6Address, 50), + } + + for i := 0; i < 100; i++ { + filter.IPv4Address[i] = PrefixedIPv4Address{ + Address: "192.168.1.0", + PrefixLength: 24, + } + } + + for i := 0; i < 50; i++ { + filter.IPv6Address[i] = PrefixedIPv6Address{ + Address: "fe80::", + PrefixLength: 64, + } + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = client.SetIPAddressFilter(ctx, filter) + } +} diff --git a/discovery/discovery.go b/discovery/discovery.go index bb7b8ac..dc52c69 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -227,12 +227,16 @@ func deviceMapToSlice(m map[string]*Device) []*Device { // generateUUID generates a simple UUID (not cryptographically secure). func generateUUID() string { + now := time.Now() + nanos := now.UnixNano() + secs := now.Unix() + return fmt.Sprintf("%d-%d-%d-%d-%d", - time.Now().UnixNano(), - time.Now().Unix(), - time.Now().UnixNano()%uuidMod1000, - time.Now().Unix()%uuidMod1000, - time.Now().UnixNano()%uuidMod10000) + nanos, + secs, + nanos%uuidMod1000, + secs%uuidMod1000, + nanos%uuidMod10000) } // resolveNetworkInterface resolves a network interface by name or IP address. diff --git a/event.go b/event.go index 85809fc..d54ba07 100644 --- a/event.go +++ b/event.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "errors" "fmt" + "strings" "time" "github.com/0x524a/onvif-go/internal/soap" @@ -751,28 +752,5 @@ func splitSpaceSeparated(s string) []string { return nil } - var result []string - - start := 0 - inWord := false - - for i, r := range s { - if r == ' ' || r == '\t' { - if inWord { - result = append(result, s[start:i]) - inWord = false - } - } else { - if !inWord { - start = i - inWord = true - } - } - } - - if inWord { - result = append(result, s[start:]) - } - - return result + return strings.Fields(s) } diff --git a/media.go b/media.go index 8f72318..0ce23d7 100644 --- a/media.go +++ b/media.go @@ -20,21 +20,11 @@ func (c *Client) getMediaEndpoint() string { return c.endpoint } -// getMediaSoapClient creates a new SOAP client for media operations. -func (c *Client) getMediaSoapClient() *soap.Client { - username, password := c.GetCredentials() - - return soap.NewClient(c.httpClient, username, password) -} - // GetProfiles retrieves all media profiles. // //nolint:funlen // GetProfiles has many statements due to parsing complex profile structures func (c *Client) GetProfiles(ctx context.Context) ([]*Profile, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetProfiles struct { XMLName xml.Name `xml:"trt:GetProfiles"` @@ -160,10 +150,7 @@ func (c *Client) GetProfiles(ctx context.Context) ([]*Profile, error) { // GetStreamURI retrieves the stream URI for a profile. func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaURI, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetStreamURI struct { XMLName xml.Name `xml:"trt:GetStreamUri"` @@ -214,10 +201,7 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU // GetSnapshotURI retrieves the snapshot URI for a profile. func (c *Client) GetSnapshotURI(ctx context.Context, profileToken string) (*MediaURI, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetSnapshotURI struct { XMLName xml.Name `xml:"trt:GetSnapshotUri"` @@ -261,10 +245,7 @@ func (c *Client) GetVideoEncoderConfiguration( ctx context.Context, configurationToken string, ) (*VideoEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoEncoderConfiguration struct { XMLName xml.Name `xml:"trt:GetVideoEncoderConfiguration"` @@ -334,10 +315,7 @@ func (c *Client) GetVideoEncoderConfiguration( // GetVideoSources retrieves all video sources. func (c *Client) GetVideoSources(ctx context.Context) ([]*VideoSource, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoSources struct { XMLName xml.Name `xml:"trt:GetVideoSources"` @@ -386,10 +364,7 @@ func (c *Client) GetVideoSources(ctx context.Context) ([]*VideoSource, error) { // GetAudioSources retrieves all audio sources. func (c *Client) GetAudioSources(ctx context.Context) ([]*AudioSource, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioSources struct { XMLName xml.Name `xml:"trt:GetAudioSources"` @@ -430,10 +405,7 @@ func (c *Client) GetAudioSources(ctx context.Context) ([]*AudioSource, error) { // GetAudioOutputs retrieves all audio outputs. func (c *Client) GetAudioOutputs(ctx context.Context) ([]*AudioOutput, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioOutputs struct { XMLName xml.Name `xml:"trt:GetAudioOutputs"` @@ -472,10 +444,7 @@ func (c *Client) GetAudioOutputs(ctx context.Context) ([]*AudioOutput, error) { // CreateProfile creates a new media profile. func (c *Client) CreateProfile(ctx context.Context, name, token string) (*Profile, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type CreateProfile struct { XMLName xml.Name `xml:"trt:CreateProfile"` @@ -517,10 +486,7 @@ func (c *Client) CreateProfile(ctx context.Context, name, token string) (*Profil // DeleteProfile deletes a media profile. func (c *Client) DeleteProfile(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type DeleteProfile struct { XMLName xml.Name `xml:"trt:DeleteProfile"` @@ -549,10 +515,7 @@ func (c *Client) SetVideoEncoderConfiguration( config *VideoEncoderConfiguration, forcePersistence bool, ) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetVideoEncoderConfiguration struct { XMLName xml.Name `xml:"trt:SetVideoEncoderConfiguration"` @@ -626,10 +589,7 @@ func (c *Client) SetVideoEncoderConfiguration( // GetMediaServiceCapabilities retrieves media service capabilities. func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaServiceCapabilities, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetServiceCapabilities struct { XMLName xml.Name `xml:"trt:GetServiceCapabilities"` @@ -697,10 +657,7 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService func (c *Client) GetVideoEncoderConfigurationOptions( ctx context.Context, configurationToken string, ) (*VideoEncoderConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoEncoderConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetVideoEncoderConfigurationOptions"` @@ -839,10 +796,7 @@ func (c *Client) GetAudioEncoderConfiguration( ctx context.Context, configurationToken string, ) (*AudioEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioEncoderConfiguration struct { XMLName xml.Name `xml:"trt:GetAudioEncoderConfiguration"` @@ -920,10 +874,7 @@ func (c *Client) SetAudioEncoderConfiguration( config *AudioEncoderConfiguration, forcePersistence bool, ) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetAudioEncoderConfiguration struct { XMLName xml.Name `xml:"trt:SetAudioEncoderConfiguration"` @@ -1011,10 +962,7 @@ func (c *Client) GetMetadataConfiguration( ctx context.Context, configurationToken string, ) (*MetadataConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetMetadataConfiguration struct { XMLName xml.Name `xml:"trt:GetMetadataConfiguration"` @@ -1104,10 +1052,7 @@ func (c *Client) SetMetadataConfiguration( config *MetadataConfiguration, forcePersistence bool, ) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetMetadataConfiguration struct { XMLName xml.Name `xml:"trt:SetMetadataConfiguration"` @@ -1203,10 +1148,7 @@ func (c *Client) SetMetadataConfiguration( // GetVideoSourceModes retrieves available video source modes. func (c *Client) GetVideoSourceModes(ctx context.Context, videoSourceToken string) ([]*VideoSourceMode, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoSourceModes struct { XMLName xml.Name `xml:"trt:GetVideoSourceModes"` @@ -1257,10 +1199,7 @@ func (c *Client) GetVideoSourceModes(ctx context.Context, videoSourceToken strin // SetVideoSourceMode sets the video source mode. func (c *Client) SetVideoSourceMode(ctx context.Context, videoSourceToken, modeToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetVideoSourceMode struct { XMLName xml.Name `xml:"trt:SetVideoSourceMode"` @@ -1287,10 +1226,7 @@ func (c *Client) SetVideoSourceMode(ctx context.Context, videoSourceToken, modeT // SetSynchronizationPoint sets a synchronization point for the stream. func (c *Client) SetSynchronizationPoint(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetSynchronizationPoint struct { XMLName xml.Name `xml:"trt:SetSynchronizationPoint"` @@ -1315,10 +1251,7 @@ func (c *Client) SetSynchronizationPoint(ctx context.Context, profileToken strin // GetOSDs retrieves all OSD configurations. func (c *Client) GetOSDs(ctx context.Context, configurationToken string) ([]*OSDConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetOSDs struct { XMLName xml.Name `xml:"trt:GetOSDs"` @@ -1361,10 +1294,7 @@ func (c *Client) GetOSDs(ctx context.Context, configurationToken string) ([]*OSD // GetOSD retrieves a specific OSD configuration. func (c *Client) GetOSD(ctx context.Context, osdToken string) (*OSDConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetOSD struct { XMLName xml.Name `xml:"trt:GetOSD"` @@ -1400,10 +1330,7 @@ func (c *Client) GetOSD(ctx context.Context, osdToken string) (*OSDConfiguration // SetOSD sets OSD configuration. func (c *Client) SetOSD(ctx context.Context, osd *OSDConfiguration) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetOSD struct { XMLName xml.Name `xml:"trt:SetOSD"` @@ -1436,10 +1363,7 @@ func (c *Client) CreateOSD( videoSourceConfigurationToken string, osd *OSDConfiguration, ) (*OSDConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type CreateOSD struct { XMLName xml.Name `xml:"trt:CreateOSD"` @@ -1483,10 +1407,7 @@ func (c *Client) CreateOSD( // DeleteOSD deletes an OSD configuration. func (c *Client) DeleteOSD(ctx context.Context, osdToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type DeleteOSD struct { XMLName xml.Name `xml:"trt:DeleteOSD"` @@ -1511,10 +1432,7 @@ func (c *Client) DeleteOSD(ctx context.Context, osdToken string) error { // StartMulticastStreaming starts multicast streaming. func (c *Client) StartMulticastStreaming(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type StartMulticastStreaming struct { XMLName xml.Name `xml:"trt:StartMulticastStreaming"` @@ -1539,10 +1457,7 @@ func (c *Client) StartMulticastStreaming(ctx context.Context, profileToken strin // StopMulticastStreaming stops multicast streaming. func (c *Client) StopMulticastStreaming(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type StopMulticastStreaming struct { XMLName xml.Name `xml:"trt:StopMulticastStreaming"` @@ -1567,10 +1482,7 @@ func (c *Client) StopMulticastStreaming(ctx context.Context, profileToken string // GetProfile retrieves a specific media profile. func (c *Client) GetProfile(ctx context.Context, profileToken string) (*Profile, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetProfile struct { XMLName xml.Name `xml:"trt:GetProfile"` @@ -1608,10 +1520,7 @@ func (c *Client) GetProfile(ctx context.Context, profileToken string) (*Profile, // SetProfile sets profile configuration. func (c *Client) SetProfile(ctx context.Context, profile *Profile) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetProfile struct { XMLName xml.Name `xml:"trt:SetProfile"` @@ -1642,10 +1551,7 @@ func (c *Client) SetProfile(ctx context.Context, profile *Profile) error { // AddVideoEncoderConfiguration adds video encoder configuration to a profile. func (c *Client) AddVideoEncoderConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddVideoEncoderConfiguration struct { XMLName xml.Name `xml:"trt:AddVideoEncoderConfiguration"` @@ -1672,10 +1578,7 @@ func (c *Client) AddVideoEncoderConfiguration(ctx context.Context, profileToken, // RemoveVideoEncoderConfiguration removes video encoder configuration from a profile. func (c *Client) RemoveVideoEncoderConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveVideoEncoderConfiguration struct { XMLName xml.Name `xml:"trt:RemoveVideoEncoderConfiguration"` @@ -1700,10 +1603,7 @@ func (c *Client) RemoveVideoEncoderConfiguration(ctx context.Context, profileTok // AddAudioEncoderConfiguration adds audio encoder configuration to a profile. func (c *Client) AddAudioEncoderConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddAudioEncoderConfiguration struct { XMLName xml.Name `xml:"trt:AddAudioEncoderConfiguration"` @@ -1730,10 +1630,7 @@ func (c *Client) AddAudioEncoderConfiguration(ctx context.Context, profileToken, // RemoveAudioEncoderConfiguration removes audio encoder configuration from a profile. func (c *Client) RemoveAudioEncoderConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveAudioEncoderConfiguration struct { XMLName xml.Name `xml:"trt:RemoveAudioEncoderConfiguration"` @@ -1758,10 +1655,7 @@ func (c *Client) RemoveAudioEncoderConfiguration(ctx context.Context, profileTok // AddAudioSourceConfiguration adds audio source configuration to a profile. func (c *Client) AddAudioSourceConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddAudioSourceConfiguration struct { XMLName xml.Name `xml:"trt:AddAudioSourceConfiguration"` @@ -1788,10 +1682,7 @@ func (c *Client) AddAudioSourceConfiguration(ctx context.Context, profileToken, // RemoveAudioSourceConfiguration removes audio source configuration from a profile. func (c *Client) RemoveAudioSourceConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveAudioSourceConfiguration struct { XMLName xml.Name `xml:"trt:RemoveAudioSourceConfiguration"` @@ -1816,10 +1707,7 @@ func (c *Client) RemoveAudioSourceConfiguration(ctx context.Context, profileToke // AddVideoSourceConfiguration adds video source configuration to a profile. func (c *Client) AddVideoSourceConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddVideoSourceConfiguration struct { XMLName xml.Name `xml:"trt:AddVideoSourceConfiguration"` @@ -1846,10 +1734,7 @@ func (c *Client) AddVideoSourceConfiguration(ctx context.Context, profileToken, // RemoveVideoSourceConfiguration removes video source configuration from a profile. func (c *Client) RemoveVideoSourceConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveVideoSourceConfiguration struct { XMLName xml.Name `xml:"trt:RemoveVideoSourceConfiguration"` @@ -1874,10 +1759,7 @@ func (c *Client) RemoveVideoSourceConfiguration(ctx context.Context, profileToke // AddPTZConfiguration adds PTZ configuration to a profile. func (c *Client) AddPTZConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddPTZConfiguration struct { XMLName xml.Name `xml:"trt:AddPTZConfiguration"` @@ -1904,10 +1786,7 @@ func (c *Client) AddPTZConfiguration(ctx context.Context, profileToken, configur // RemovePTZConfiguration removes PTZ configuration from a profile. func (c *Client) RemovePTZConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemovePTZConfiguration struct { XMLName xml.Name `xml:"trt:RemovePTZConfiguration"` @@ -1932,10 +1811,7 @@ func (c *Client) RemovePTZConfiguration(ctx context.Context, profileToken string // AddMetadataConfiguration adds metadata configuration to a profile. func (c *Client) AddMetadataConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddMetadataConfiguration struct { XMLName xml.Name `xml:"trt:AddMetadataConfiguration"` @@ -1962,10 +1838,7 @@ func (c *Client) AddMetadataConfiguration(ctx context.Context, profileToken, con // RemoveMetadataConfiguration removes metadata configuration from a profile. func (c *Client) RemoveMetadataConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveMetadataConfiguration struct { XMLName xml.Name `xml:"trt:RemoveMetadataConfiguration"` @@ -1993,10 +1866,7 @@ func (c *Client) GetAudioEncoderConfigurationOptions( ctx context.Context, configurationToken, profileToken string, ) (*AudioEncoderConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioEncoderConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetAudioEncoderConfigurationOptions"` @@ -2045,10 +1915,7 @@ func (c *Client) GetMetadataConfigurationOptions( ctx context.Context, configurationToken, profileToken string, ) (*MetadataConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetMetadataConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetMetadataConfigurationOptions"` @@ -2124,7 +1991,9 @@ func (c *Client) GetAudioOutputConfiguration(ctx context.Context, configurationT } var resp GetAudioOutputConfigurationResponse - soapClient := c.getMediaSoapClient() + + username, password := c.GetCredentials() + soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetAudioOutputConfiguration failed: %w", err) @@ -2140,10 +2009,7 @@ func (c *Client) GetAudioOutputConfiguration(ctx context.Context, configurationT // SetAudioOutputConfiguration sets audio output configuration. func (c *Client) SetAudioOutputConfiguration(ctx context.Context, config *AudioOutputConfiguration, forcePersistence bool) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetAudioOutputConfiguration struct { XMLName xml.Name `xml:"trt:SetAudioOutputConfiguration"` @@ -2184,10 +2050,7 @@ func (c *Client) GetAudioOutputConfigurationOptions( ctx context.Context, configurationToken string, ) (*AudioOutputConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioOutputConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetAudioOutputConfigurationOptions"` @@ -2228,10 +2091,7 @@ func (c *Client) GetAudioDecoderConfigurationOptions( ctx context.Context, configurationToken string, ) (*AudioDecoderConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioDecoderConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetAudioDecoderConfigurationOptions"` @@ -2297,10 +2157,7 @@ func (c *Client) GetGuaranteedNumberOfVideoEncoderInstances( ctx context.Context, configurationToken string, ) (*GuaranteedNumberOfVideoEncoderInstances, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetGuaranteedNumberOfVideoEncoderInstances struct { XMLName xml.Name `xml:"trt:GetGuaranteedNumberOfVideoEncoderInstances"` @@ -2340,10 +2197,7 @@ func (c *Client) GetGuaranteedNumberOfVideoEncoderInstances( // GetOSDOptions retrieves available options for OSD configuration. func (c *Client) GetOSDOptions(ctx context.Context, configurationToken string) (*OSDConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetOSDOptions struct { XMLName xml.Name `xml:"trt:GetOSDOptions"` @@ -2381,10 +2235,7 @@ func (c *Client) GetOSDOptions(ctx context.Context, configurationToken string) ( // GetVideoSourceConfigurations retrieves all video source configurations. func (c *Client) GetVideoSourceConfigurations(ctx context.Context) ([]*VideoSourceConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoSourceConfigurations struct { XMLName xml.Name `xml:"trt:GetVideoSourceConfigurations"` @@ -2444,10 +2295,7 @@ func (c *Client) GetVideoSourceConfigurations(ctx context.Context) ([]*VideoSour // GetAudioSourceConfigurations retrieves all audio source configurations. func (c *Client) GetAudioSourceConfigurations(ctx context.Context) ([]*AudioSourceConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioSourceConfigurations struct { XMLName xml.Name `xml:"trt:GetAudioSourceConfigurations"` @@ -2492,10 +2340,7 @@ func (c *Client) GetAudioSourceConfigurations(ctx context.Context) ([]*AudioSour // GetVideoEncoderConfigurations retrieves all video encoder configurations. func (c *Client) GetVideoEncoderConfigurations(ctx context.Context) ([]*VideoEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoEncoderConfigurations struct { XMLName xml.Name `xml:"trt:GetVideoEncoderConfigurations"` @@ -2616,10 +2461,7 @@ func (c *Client) GetVideoEncoderConfigurations(ctx context.Context) ([]*VideoEnc // GetAudioEncoderConfigurations retrieves all audio encoder configurations. func (c *Client) GetAudioEncoderConfigurations(ctx context.Context) ([]*AudioEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioEncoderConfigurations struct { XMLName xml.Name `xml:"trt:GetAudioEncoderConfigurations"` @@ -2699,10 +2541,7 @@ func (c *Client) GetVideoSourceConfiguration( ctx context.Context, configurationToken string, ) (*VideoSourceConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoSourceConfiguration struct { XMLName xml.Name `xml:"trt:GetVideoSourceConfiguration"` @@ -2785,7 +2624,9 @@ func (c *Client) GetAudioSourceConfiguration(ctx context.Context, configurationT } var resp GetAudioSourceConfigurationResponse - soapClient := c.getMediaSoapClient() + + username, password := c.GetCredentials() + soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { return nil, fmt.Errorf("GetAudioSourceConfiguration failed: %w", err) @@ -2804,10 +2645,7 @@ func (c *Client) GetVideoSourceConfigurationOptions( ctx context.Context, configurationToken, profileToken string, ) (*VideoSourceConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoSourceConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetVideoSourceConfigurationOptions"` @@ -2867,10 +2705,7 @@ func (c *Client) GetAudioSourceConfigurationOptions( ctx context.Context, configurationToken, profileToken string, ) (*AudioSourceConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioSourceConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetAudioSourceConfigurationOptions"` @@ -2916,10 +2751,7 @@ func (c *Client) SetVideoSourceConfiguration( config *VideoSourceConfiguration, forcePersistence bool, ) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetVideoSourceConfiguration struct { XMLName xml.Name `xml:"trt:SetVideoSourceConfiguration"` @@ -2977,10 +2809,7 @@ func (c *Client) SetVideoSourceConfiguration( // SetAudioSourceConfiguration sets audio source configuration. func (c *Client) SetAudioSourceConfiguration(ctx context.Context, config *AudioSourceConfiguration, forcePersistence bool) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetAudioSourceConfiguration struct { XMLName xml.Name `xml:"trt:SetAudioSourceConfiguration"` @@ -3021,10 +2850,7 @@ func (c *Client) GetCompatibleVideoEncoderConfigurations( ctx context.Context, profileToken string, ) ([]*VideoEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleVideoEncoderConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleVideoEncoderConfigurations"` @@ -3102,10 +2928,7 @@ func (c *Client) GetCompatibleVideoSourceConfigurations( ctx context.Context, profileToken string, ) ([]*VideoSourceConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleVideoSourceConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleVideoSourceConfigurations"` @@ -3170,10 +2993,7 @@ func (c *Client) GetCompatibleAudioEncoderConfigurations( ctx context.Context, profileToken string, ) ([]*AudioEncoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleAudioEncoderConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleAudioEncoderConfigurations"` @@ -3224,10 +3044,7 @@ func (c *Client) GetCompatibleAudioEncoderConfigurations( // GetCompatibleAudioSourceConfigurations retrieves compatible audio source configurations for a profile. func (c *Client) GetCompatibleAudioSourceConfigurations(ctx context.Context, profileToken string) ([]*AudioSourceConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleAudioSourceConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleAudioSourceConfigurations"` @@ -3274,10 +3091,7 @@ func (c *Client) GetCompatibleAudioSourceConfigurations(ctx context.Context, pro // GetCompatiblePTZConfigurations retrieves compatible PTZ configurations for a profile. func (c *Client) GetCompatiblePTZConfigurations(ctx context.Context, profileToken string) ([]*PTZConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatiblePTZConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatiblePTZConfigurations"` @@ -3324,10 +3138,7 @@ func (c *Client) GetCompatiblePTZConfigurations(ctx context.Context, profileToke // GetCompatibleMetadataConfigurations retrieves compatible metadata configurations for a profile. func (c *Client) GetCompatibleMetadataConfigurations(ctx context.Context, profileToken string) ([]*MetadataConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleMetadataConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleMetadataConfigurations"` @@ -3374,10 +3185,7 @@ func (c *Client) GetCompatibleMetadataConfigurations(ctx context.Context, profil // GetCompatibleAudioOutputConfigurations retrieves compatible audio output configurations for a profile. func (c *Client) GetCompatibleAudioOutputConfigurations(ctx context.Context, profileToken string) ([]*AudioOutputConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleAudioOutputConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleAudioOutputConfigurations"` @@ -3424,10 +3232,7 @@ func (c *Client) GetCompatibleAudioOutputConfigurations(ctx context.Context, pro // GetCompatibleAudioDecoderConfigurations retrieves compatible audio decoder configurations for a profile. func (c *Client) GetCompatibleAudioDecoderConfigurations(ctx context.Context, profileToken string) ([]*AudioDecoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleAudioDecoderConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleAudioDecoderConfigurations"` @@ -3472,10 +3277,7 @@ func (c *Client) GetCompatibleAudioDecoderConfigurations(ctx context.Context, pr // GetMetadataConfigurations retrieves all metadata configurations. func (c *Client) GetMetadataConfigurations(ctx context.Context) ([]*MetadataConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetMetadataConfigurations struct { XMLName xml.Name `xml:"trt:GetMetadataConfigurations"` @@ -3520,10 +3322,7 @@ func (c *Client) GetMetadataConfigurations(ctx context.Context) ([]*MetadataConf // GetAudioOutputConfigurations retrieves all audio output configurations. func (c *Client) GetAudioOutputConfigurations(ctx context.Context) ([]*AudioOutputConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioOutputConfigurations struct { XMLName xml.Name `xml:"trt:GetAudioOutputConfigurations"` @@ -3568,10 +3367,7 @@ func (c *Client) GetAudioOutputConfigurations(ctx context.Context) ([]*AudioOutp // GetAudioDecoderConfigurations retrieves all audio decoder configurations. func (c *Client) GetAudioDecoderConfigurations(ctx context.Context) ([]*AudioDecoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioDecoderConfigurations struct { XMLName xml.Name `xml:"trt:GetAudioDecoderConfigurations"` @@ -3617,10 +3413,7 @@ func (c *Client) GetAudioDecoderConfiguration( ctx context.Context, configurationToken string, ) (*AudioDecoderConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetAudioDecoderConfiguration struct { XMLName xml.Name `xml:"trt:GetAudioDecoderConfiguration"` @@ -3660,10 +3453,7 @@ func (c *Client) GetAudioDecoderConfiguration( // SetAudioDecoderConfiguration sets audio decoder configuration. func (c *Client) SetAudioDecoderConfiguration(ctx context.Context, config *AudioDecoderConfiguration, forcePersistence bool) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetAudioDecoderConfiguration struct { XMLName xml.Name `xml:"trt:SetAudioDecoderConfiguration"` @@ -3699,10 +3489,7 @@ func (c *Client) SetAudioDecoderConfiguration(ctx context.Context, config *Audio // GetVideoAnalyticsConfigurations retrieves all video analytics configurations. func (c *Client) GetVideoAnalyticsConfigurations(ctx context.Context) ([]*VideoAnalyticsConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoAnalyticsConfigurations struct { XMLName xml.Name `xml:"trt:GetVideoAnalyticsConfigurations"` @@ -3748,10 +3535,7 @@ func (c *Client) GetVideoAnalyticsConfiguration( ctx context.Context, configurationToken string, ) (*VideoAnalyticsConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoAnalyticsConfiguration struct { XMLName xml.Name `xml:"trt:GetVideoAnalyticsConfiguration"` @@ -3791,10 +3575,7 @@ func (c *Client) GetVideoAnalyticsConfiguration( // GetCompatibleVideoAnalyticsConfigurations retrieves compatible video analytics configurations for a profile. func (c *Client) GetCompatibleVideoAnalyticsConfigurations(ctx context.Context, profileToken string) ([]*VideoAnalyticsConfiguration, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetCompatibleVideoAnalyticsConfigurations struct { XMLName xml.Name `xml:"trt:GetCompatibleVideoAnalyticsConfigurations"` @@ -3839,10 +3620,7 @@ func (c *Client) GetCompatibleVideoAnalyticsConfigurations(ctx context.Context, // SetVideoAnalyticsConfiguration sets video analytics configuration. func (c *Client) SetVideoAnalyticsConfiguration(ctx context.Context, config *VideoAnalyticsConfiguration, forcePersistence bool) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type SetVideoAnalyticsConfiguration struct { XMLName xml.Name `xml:"trt:SetVideoAnalyticsConfiguration"` @@ -3881,10 +3659,7 @@ func (c *Client) GetVideoAnalyticsConfigurationOptions( ctx context.Context, configurationToken, profileToken string, ) (*VideoAnalyticsConfigurationOptions, error) { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type GetVideoAnalyticsConfigurationOptions struct { XMLName xml.Name `xml:"trt:GetVideoAnalyticsConfigurationOptions"` @@ -3922,10 +3697,7 @@ func (c *Client) GetVideoAnalyticsConfigurationOptions( // AddVideoAnalyticsConfiguration adds a video analytics configuration to a profile. func (c *Client) AddVideoAnalyticsConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddVideoAnalyticsConfiguration struct { XMLName xml.Name `xml:"trt:AddVideoAnalyticsConfiguration"` @@ -3952,10 +3724,7 @@ func (c *Client) AddVideoAnalyticsConfiguration(ctx context.Context, profileToke // RemoveVideoAnalyticsConfiguration removes a video analytics configuration from a profile. func (c *Client) RemoveVideoAnalyticsConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveVideoAnalyticsConfiguration struct { XMLName xml.Name `xml:"trt:RemoveVideoAnalyticsConfiguration"` @@ -3980,10 +3749,7 @@ func (c *Client) RemoveVideoAnalyticsConfiguration(ctx context.Context, profileT // AddAudioOutputConfiguration adds an audio output configuration to a profile. func (c *Client) AddAudioOutputConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddAudioOutputConfiguration struct { XMLName xml.Name `xml:"trt:AddAudioOutputConfiguration"` @@ -4010,10 +3776,7 @@ func (c *Client) AddAudioOutputConfiguration(ctx context.Context, profileToken, // RemoveAudioOutputConfiguration removes an audio output configuration from a profile. func (c *Client) RemoveAudioOutputConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveAudioOutputConfiguration struct { XMLName xml.Name `xml:"trt:RemoveAudioOutputConfiguration"` @@ -4038,10 +3801,7 @@ func (c *Client) RemoveAudioOutputConfiguration(ctx context.Context, profileToke // AddAudioDecoderConfiguration adds an audio decoder configuration to a profile. func (c *Client) AddAudioDecoderConfiguration(ctx context.Context, profileToken, configurationToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type AddAudioDecoderConfiguration struct { XMLName xml.Name `xml:"trt:AddAudioDecoderConfiguration"` @@ -4068,10 +3828,7 @@ func (c *Client) AddAudioDecoderConfiguration(ctx context.Context, profileToken, // RemoveAudioDecoderConfiguration removes an audio decoder configuration from a profile. func (c *Client) RemoveAudioDecoderConfiguration(ctx context.Context, profileToken string) error { - endpoint := c.mediaEndpoint - if endpoint == "" { - endpoint = c.endpoint - } + endpoint := c.getMediaEndpoint() type RemoveAudioDecoderConfiguration struct { XMLName xml.Name `xml:"trt:RemoveAudioDecoderConfiguration"` diff --git a/ptz.go b/ptz.go index 9b82f23..4d9e099 100644 --- a/ptz.go +++ b/ptz.go @@ -11,6 +11,61 @@ import ( // PTZ service namespace. const ptzNamespace = "http://www.onvif.org/ver20/ptz/wsdl" +// ptzPanTiltXML is a shared type for PTZ pan/tilt XML serialization. +type ptzPanTiltXML struct { + X float64 `xml:"x,attr"` + Y float64 `xml:"y,attr"` + Space string `xml:"space,attr,omitempty"` +} + +// ptzZoomXML is a shared type for PTZ zoom XML serialization. +type ptzZoomXML struct { + X float64 `xml:"x,attr"` + Space string `xml:"space,attr,omitempty"` +} + +// ptzVectorXML is a shared type for PTZ position/velocity XML serialization. +type ptzVectorXML struct { + PanTilt *ptzPanTiltXML `xml:"PanTilt,omitempty"` + Zoom *ptzZoomXML `xml:"Zoom,omitempty"` +} + +// ptzSpeedXML is a shared type for PTZ speed XML serialization. +type ptzSpeedXML struct { + PanTilt *ptzPanTiltXML `xml:"PanTilt,omitempty"` + Zoom *ptzZoomXML `xml:"Zoom,omitempty"` +} + +// convertToPTZVectorXML converts PTZVector to XML struct. +func convertToPTZVectorXML(v *PTZVector) *ptzVectorXML { + if v == nil { + return nil + } + result := &ptzVectorXML{} + if v.PanTilt != nil { + result.PanTilt = &ptzPanTiltXML{X: v.PanTilt.X, Y: v.PanTilt.Y, Space: v.PanTilt.Space} + } + if v.Zoom != nil { + result.Zoom = &ptzZoomXML{X: v.Zoom.X, Space: v.Zoom.Space} + } + return result +} + +// convertToPTZSpeedXML converts PTZSpeed to XML struct. +func convertToPTZSpeedXML(s *PTZSpeed) *ptzSpeedXML { + if s == nil { + return nil + } + result := &ptzSpeedXML{} + if s.PanTilt != nil { + result.PanTilt = &ptzPanTiltXML{X: s.PanTilt.X, Y: s.PanTilt.Y, Space: s.PanTilt.Space} + } + if s.Zoom != nil { + result.Zoom = &ptzZoomXML{X: s.Zoom.X, Space: s.Zoom.Space} + } + return result +} + // ContinuousMove starts continuous PTZ movement. func (c *Client) ContinuousMove(ctx context.Context, profileToken string, velocity *PTZSpeed, timeout *string) error { endpoint := c.ptzEndpoint @@ -19,65 +74,20 @@ func (c *Client) ContinuousMove(ctx context.Context, profileToken string, veloci } type ContinuousMove struct { - XMLName xml.Name `xml:"tptz:ContinuousMove"` - Xmlns string `xml:"xmlns:tptz,attr"` - ProfileToken string `xml:"tptz:ProfileToken"` - Velocity *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Velocity"` - Timeout *string `xml:"tptz:Timeout,omitempty"` + XMLName xml.Name `xml:"tptz:ContinuousMove"` + Xmlns string `xml:"xmlns:tptz,attr"` + ProfileToken string `xml:"tptz:ProfileToken"` + Velocity *ptzSpeedXML `xml:"tptz:Velocity"` + Timeout *string `xml:"tptz:Timeout,omitempty"` } req := ContinuousMove{ Xmlns: ptzNamespace, ProfileToken: profileToken, + Velocity: convertToPTZSpeedXML(velocity), Timeout: timeout, } - if velocity != nil { - req.Velocity = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if velocity.PanTilt != nil { - req.Velocity.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: velocity.PanTilt.X, - Y: velocity.PanTilt.Y, - Space: velocity.PanTilt.Space, - } - } - - if velocity.Zoom != nil { - req.Velocity.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: velocity.Zoom.X, - Space: velocity.Zoom.Space, - } - } - } - username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) @@ -96,108 +106,18 @@ func (c *Client) AbsoluteMove(ctx context.Context, profileToken string, position } type AbsoluteMove struct { - XMLName xml.Name `xml:"tptz:AbsoluteMove"` - Xmlns string `xml:"xmlns:tptz,attr"` - ProfileToken string `xml:"tptz:ProfileToken"` - Position *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Position"` - Speed *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Speed,omitempty"` + XMLName xml.Name `xml:"tptz:AbsoluteMove"` + Xmlns string `xml:"xmlns:tptz,attr"` + ProfileToken string `xml:"tptz:ProfileToken"` + Position *ptzVectorXML `xml:"tptz:Position"` + Speed *ptzSpeedXML `xml:"tptz:Speed,omitempty"` } req := AbsoluteMove{ Xmlns: ptzNamespace, ProfileToken: profileToken, - } - - if position != nil { - req.Position = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if position.PanTilt != nil { - req.Position.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: position.PanTilt.X, - Y: position.PanTilt.Y, - Space: position.PanTilt.Space, - } - } - - if position.Zoom != nil { - req.Position.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: position.Zoom.X, - Space: position.Zoom.Space, - } - } - } - - if speed != nil { - req.Speed = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if speed.PanTilt != nil { - req.Speed.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.PanTilt.X, - Y: speed.PanTilt.Y, - Space: speed.PanTilt.Space, - } - } - - if speed.Zoom != nil { - req.Speed.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.Zoom.X, - Space: speed.Zoom.Space, - } - } + Position: convertToPTZVectorXML(position), + Speed: convertToPTZSpeedXML(speed), } username, password := c.GetCredentials() @@ -218,108 +138,18 @@ func (c *Client) RelativeMove(ctx context.Context, profileToken string, translat } type RelativeMove struct { - XMLName xml.Name `xml:"tptz:RelativeMove"` - Xmlns string `xml:"xmlns:tptz,attr"` - ProfileToken string `xml:"tptz:ProfileToken"` - Translation *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Translation"` - Speed *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Speed,omitempty"` + XMLName xml.Name `xml:"tptz:RelativeMove"` + Xmlns string `xml:"xmlns:tptz,attr"` + ProfileToken string `xml:"tptz:ProfileToken"` + Translation *ptzVectorXML `xml:"tptz:Translation"` + Speed *ptzSpeedXML `xml:"tptz:Speed,omitempty"` } req := RelativeMove{ Xmlns: ptzNamespace, ProfileToken: profileToken, - } - - if translation != nil { - req.Translation = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if translation.PanTilt != nil { - req.Translation.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: translation.PanTilt.X, - Y: translation.PanTilt.Y, - Space: translation.PanTilt.Space, - } - } - - if translation.Zoom != nil { - req.Translation.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: translation.Zoom.X, - Space: translation.Zoom.Space, - } - } - } - - if speed != nil { - req.Speed = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if speed.PanTilt != nil { - req.Speed.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.PanTilt.X, - Y: speed.PanTilt.Y, - Space: speed.PanTilt.Space, - } - } - - if speed.Zoom != nil { - req.Speed.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.Zoom.X, - Space: speed.Zoom.Space, - } - } + Translation: convertToPTZVectorXML(translation), + Speed: convertToPTZSpeedXML(speed), } username, password := c.GetCredentials() @@ -534,63 +364,18 @@ func (c *Client) GotoPreset(ctx context.Context, profileToken, presetToken strin } type GotoPreset struct { - XMLName xml.Name `xml:"tptz:GotoPreset"` - Xmlns string `xml:"xmlns:tptz,attr"` - ProfileToken string `xml:"tptz:ProfileToken"` - PresetToken string `xml:"tptz:PresetToken"` - Speed *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Speed,omitempty"` + XMLName xml.Name `xml:"tptz:GotoPreset"` + Xmlns string `xml:"xmlns:tptz,attr"` + ProfileToken string `xml:"tptz:ProfileToken"` + PresetToken string `xml:"tptz:PresetToken"` + Speed *ptzSpeedXML `xml:"tptz:Speed,omitempty"` } req := GotoPreset{ Xmlns: ptzNamespace, ProfileToken: profileToken, PresetToken: presetToken, - } - - if speed != nil { - req.Speed = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if speed.PanTilt != nil { - req.Speed.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.PanTilt.X, - Y: speed.PanTilt.Y, - Space: speed.PanTilt.Space, - } - } - - if speed.Zoom != nil { - req.Speed.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.Zoom.X, - Space: speed.Zoom.Space, - } - } + Speed: convertToPTZSpeedXML(speed), } username, password := c.GetCredentials() @@ -685,61 +470,16 @@ func (c *Client) GotoHomePosition(ctx context.Context, profileToken string, spee } type GotoHomePosition struct { - XMLName xml.Name `xml:"tptz:GotoHomePosition"` - Xmlns string `xml:"xmlns:tptz,attr"` - ProfileToken string `xml:"tptz:ProfileToken"` - Speed *struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - } `xml:"tptz:Speed,omitempty"` + XMLName xml.Name `xml:"tptz:GotoHomePosition"` + Xmlns string `xml:"xmlns:tptz,attr"` + ProfileToken string `xml:"tptz:ProfileToken"` + Speed *ptzSpeedXML `xml:"tptz:Speed,omitempty"` } req := GotoHomePosition{ Xmlns: ptzNamespace, ProfileToken: profileToken, - } - - if speed != nil { - req.Speed = &struct { - PanTilt *struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"PanTilt,omitempty"` - Zoom *struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - } `xml:"Zoom,omitempty"` - }{} - - if speed.PanTilt != nil { - req.Speed.PanTilt = &struct { - X float64 `xml:"x,attr"` - Y float64 `xml:"y,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.PanTilt.X, - Y: speed.PanTilt.Y, - Space: speed.PanTilt.Space, - } - } - - if speed.Zoom != nil { - req.Speed.Zoom = &struct { - X float64 `xml:"x,attr"` - Space string `xml:"space,attr,omitempty"` - }{ - X: speed.Zoom.X, - Space: speed.Zoom.Space, - } - } + Speed: convertToPTZSpeedXML(speed), } username, password := c.GetCredentials()