feat: add benchmarks and improve XML request handling for device security operations

- Introduced benchmarks for various device security operations including GetRemoteUser, SetRemoteUser, and IP address filter management.
- Enhanced XML request handling by defining common structures for IP address filters to improve code efficiency and readability.
- Updated existing methods to utilize the new SOAP client creation method for better credential management.
- Pre-allocated slices in XML request structures to optimize performance during serialization.
This commit is contained in:
0x524a
2025-12-03 10:20:34 -05:00
parent df3cdfb5ab
commit 21646af4ca
2 changed files with 434 additions and 253 deletions
+148 -230
View File
@@ -8,14 +8,73 @@ import (
"github.com/0x524a/onvif-go/internal/soap" "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. // GetRemoteUser returns the configured remote user.
func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) { func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) {
type GetRemoteUser struct { type getRemoteUserRequest struct {
XMLName xml.Name `xml:"tds:GetRemoteUser"` XMLName xml.Name `xml:"tds:GetRemoteUser"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetRemoteUserResponse struct { type getRemoteUserResponse struct {
XMLName xml.Name `xml:"GetRemoteUserResponse"` XMLName xml.Name `xml:"GetRemoteUserResponse"`
RemoteUser *struct { RemoteUser *struct {
Username string `xml:"Username"` Username string `xml:"Username"`
@@ -24,16 +83,12 @@ func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) {
} `xml:"RemoteUser"` } `xml:"RemoteUser"`
} }
req := GetRemoteUser{ req := getRemoteUserRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetRemoteUserResponse var resp getRemoteUserResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetRemoteUser failed: %w", err) 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. // SetRemoteUser sets the remote user.
func (c *Client) SetRemoteUser(ctx context.Context, remoteUser *RemoteUser) error { func (c *Client) SetRemoteUser(ctx context.Context, remoteUser *RemoteUser) error {
type SetRemoteUser struct { type remoteUserXML struct {
XMLName xml.Name `xml:"tds:SetRemoteUser"`
Xmlns string `xml:"xmlns:tds,attr"`
RemoteUser *struct {
Username string `xml:"tds:Username"` Username string `xml:"tds:Username"`
Password string `xml:"tds:Password,omitempty"` Password string `xml:"tds:Password,omitempty"`
UseDerivedPassword bool `xml:"tds:UseDerivedPassword"` UseDerivedPassword bool `xml:"tds:UseDerivedPassword"`
} `xml:"tds:RemoteUser,omitempty"`
} }
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, Xmlns: deviceNamespace,
} }
if remoteUser != nil { if remoteUser != nil {
req.RemoteUser = &struct { req.RemoteUser = &remoteUserXML{
Username string `xml:"tds:Username"`
Password string `xml:"tds:Password,omitempty"`
UseDerivedPassword bool `xml:"tds:UseDerivedPassword"`
}{
Username: remoteUser.Username, Username: remoteUser.Username,
Password: remoteUser.Password, Password: remoteUser.Password,
UseDerivedPassword: remoteUser.UseDerivedPassword, UseDerivedPassword: remoteUser.UseDerivedPassword,
} }
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetRemoteUser failed: %w", err) 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. // GetIPAddressFilter gets the IP address filter settings from a device.
func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, error) { func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, error) {
type GetIPAddressFilter struct { type getIPAddressFilterRequest struct {
XMLName xml.Name `xml:"tds:GetIPAddressFilter"` XMLName xml.Name `xml:"tds:GetIPAddressFilter"`
Xmlns string `xml:"xmlns:tds,attr"` 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"` XMLName xml.Name `xml:"GetIPAddressFilterResponse"`
IPAddressFilter struct { IPAddressFilter struct {
Type string `xml:"Type"` Type string `xml:"Type"`
IPv4Address []struct { IPv4Address []prefixedAddressXML `xml:"IPv4Address"`
Address string `xml:"Address"` IPv6Address []prefixedAddressXML `xml:"IPv6Address"`
PrefixLength int `xml:"PrefixLength"`
} `xml:"IPv4Address"`
IPv6Address []struct {
Address string `xml:"Address"`
PrefixLength int `xml:"PrefixLength"`
} `xml:"IPv6Address"`
} `xml:"IPAddressFilter"` } `xml:"IPAddressFilter"`
} }
req := GetIPAddressFilter{ req := getIPAddressFilterRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetIPAddressFilterResponse var resp getIPAddressFilterResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetIPAddressFilter failed: %w", err) return nil, fmt.Errorf("GetIPAddressFilter failed: %w", err)
} }
@@ -125,70 +170,44 @@ func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, erro
Type: IPAddressFilterType(resp.IPAddressFilter.Type), Type: IPAddressFilterType(resp.IPAddressFilter.Type),
} }
// 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 { for _, addr := range resp.IPAddressFilter.IPv4Address {
filter.IPv4Address = append(filter.IPv4Address, PrefixedIPv4Address{ filter.IPv4Address = append(filter.IPv4Address, PrefixedIPv4Address{
Address: addr.Address, Address: addr.Address,
PrefixLength: addr.PrefixLength, PrefixLength: addr.PrefixLength,
}) })
} }
}
if len(resp.IPAddressFilter.IPv6Address) > 0 {
filter.IPv6Address = make([]PrefixedIPv6Address, 0, len(resp.IPAddressFilter.IPv6Address))
for _, addr := range resp.IPAddressFilter.IPv6Address { for _, addr := range resp.IPAddressFilter.IPv6Address {
filter.IPv6Address = append(filter.IPv6Address, PrefixedIPv6Address{ filter.IPv6Address = append(filter.IPv6Address, PrefixedIPv6Address{
Address: addr.Address, Address: addr.Address,
PrefixLength: addr.PrefixLength, PrefixLength: addr.PrefixLength,
}) })
} }
}
return filter, nil return filter, nil
} }
// SetIPAddressFilter sets the IP address filter settings on a device. // SetIPAddressFilter sets the IP address filter settings on a device.
func (c *Client) SetIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { func (c *Client) SetIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error {
type SetIPAddressFilter struct { type setIPAddressFilterRequest struct {
XMLName xml.Name `xml:"tds:SetIPAddressFilter"` XMLName xml.Name `xml:"tds:SetIPAddressFilter"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
IPAddressFilter struct { IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"`
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"`
} }
req := SetIPAddressFilter{ req := setIPAddressFilterRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} IPAddressFilter: buildIPAddressFilterRequest(filter),
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,
})
} }
for _, addr := range filter.IPv6Address { if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
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 {
return fmt.Errorf("SetIPAddressFilter failed: %w", err) 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. // AddIPAddressFilter adds an IP filter address to a device.
func (c *Client) AddIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { func (c *Client) AddIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error {
type AddIPAddressFilter struct { type addIPAddressFilterRequest struct {
XMLName xml.Name `xml:"tds:AddIPAddressFilter"` XMLName xml.Name `xml:"tds:AddIPAddressFilter"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
IPAddressFilter struct { IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"`
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"`
} }
req := AddIPAddressFilter{ req := addIPAddressFilterRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} IPAddressFilter: buildIPAddressFilterRequest(filter),
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,
})
} }
for _, addr := range filter.IPv6Address { if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
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 {
return fmt.Errorf("AddIPAddressFilter failed: %w", err) 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. // RemoveIPAddressFilter deletes an IP filter address from a device.
func (c *Client) RemoveIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error { func (c *Client) RemoveIPAddressFilter(ctx context.Context, filter *IPAddressFilter) error {
type RemoveIPAddressFilter struct { type removeIPAddressFilterRequest struct {
XMLName xml.Name `xml:"tds:RemoveIPAddressFilter"` XMLName xml.Name `xml:"tds:RemoveIPAddressFilter"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
IPAddressFilter struct { IPAddressFilter ipAddressFilterRequest `xml:"tds:IPAddressFilter"`
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"`
} }
req := RemoveIPAddressFilter{ req := removeIPAddressFilterRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} IPAddressFilter: buildIPAddressFilterRequest(filter),
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,
})
} }
for _, addr := range filter.IPv6Address { if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
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 {
return fmt.Errorf("RemoveIPAddressFilter failed: %w", err) 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. // GetZeroConfiguration gets the zero-configuration from a device.
func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfiguration, error) { func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfiguration, error) {
type GetZeroConfiguration struct { type getZeroConfigurationRequest struct {
XMLName xml.Name `xml:"tds:GetZeroConfiguration"` XMLName xml.Name `xml:"tds:GetZeroConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetZeroConfigurationResponse struct { type getZeroConfigurationResponse struct {
XMLName xml.Name `xml:"GetZeroConfigurationResponse"` XMLName xml.Name `xml:"GetZeroConfigurationResponse"`
ZeroConfiguration struct { ZeroConfiguration struct {
InterfaceToken string `xml:"InterfaceToken"` InterfaceToken string `xml:"InterfaceToken"`
@@ -317,16 +270,12 @@ func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfigur
} `xml:"ZeroConfiguration"` } `xml:"ZeroConfiguration"`
} }
req := GetZeroConfiguration{ req := getZeroConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetZeroConfigurationResponse var resp getZeroConfigurationResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetZeroConfiguration failed: %w", err) 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. // SetZeroConfiguration sets the zero-configuration.
func (c *Client) SetZeroConfiguration(ctx context.Context, interfaceToken string, enabled bool) error { func (c *Client) SetZeroConfiguration(ctx context.Context, interfaceToken string, enabled bool) error {
type SetZeroConfiguration struct { type setZeroConfigurationRequest struct {
XMLName xml.Name `xml:"tds:SetZeroConfiguration"` XMLName xml.Name `xml:"tds:SetZeroConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
InterfaceToken string `xml:"tds:InterfaceToken"` InterfaceToken string `xml:"tds:InterfaceToken"`
Enabled bool `xml:"tds:Enabled"` Enabled bool `xml:"tds:Enabled"`
} }
req := SetZeroConfiguration{ req := setZeroConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
InterfaceToken: interfaceToken, InterfaceToken: interfaceToken,
Enabled: enabled, Enabled: enabled,
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetZeroConfiguration failed: %w", err) 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. // GetDynamicDNS gets the dynamic DNS settings from a device.
func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, error) { func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, error) {
type GetDynamicDNS struct { type getDynamicDNSRequest struct {
XMLName xml.Name `xml:"tds:GetDynamicDNS"` XMLName xml.Name `xml:"tds:GetDynamicDNS"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetDynamicDNSResponse struct { type getDynamicDNSResponse struct {
XMLName xml.Name `xml:"GetDynamicDNSResponse"` XMLName xml.Name `xml:"GetDynamicDNSResponse"`
DynamicDNSInformation struct { DynamicDNSInformation struct {
Type string `xml:"Type"` Type string `xml:"Type"`
@@ -378,16 +324,12 @@ func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, err
} `xml:"DynamicDNSInformation"` } `xml:"DynamicDNSInformation"`
} }
req := GetDynamicDNS{ req := getDynamicDNSRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetDynamicDNSResponse var resp getDynamicDNSResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetDynamicDNS failed: %w", err) 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. // SetDynamicDNS sets the dynamic DNS settings on a device.
func (c *Client) SetDynamicDNS(ctx context.Context, dnsType DynamicDNSType, name string) error { func (c *Client) SetDynamicDNS(ctx context.Context, dnsType DynamicDNSType, name string) error {
type SetDynamicDNS struct { type setDynamicDNSRequest struct {
XMLName xml.Name `xml:"tds:SetDynamicDNS"` XMLName xml.Name `xml:"tds:SetDynamicDNS"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
Type DynamicDNSType `xml:"tds:Type"` Type DynamicDNSType `xml:"tds:Type"`
Name string `xml:"tds:Name,omitempty"` Name string `xml:"tds:Name,omitempty"`
} }
req := SetDynamicDNS{ req := setDynamicDNSRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
Type: dnsType, Type: dnsType,
Name: name, Name: name,
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetDynamicDNS failed: %w", err) 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. // GetPasswordComplexityConfiguration retrieves the current password complexity configuration settings.
func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*PasswordComplexityConfiguration, error) { func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*PasswordComplexityConfiguration, error) {
type GetPasswordComplexityConfiguration struct { type getPasswordComplexityConfigurationRequest struct {
XMLName xml.Name `xml:"tds:GetPasswordComplexityConfiguration"` XMLName xml.Name `xml:"tds:GetPasswordComplexityConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetPasswordComplexityConfigurationResponse struct { type getPasswordComplexityConfigurationResponse struct {
XMLName xml.Name `xml:"GetPasswordComplexityConfigurationResponse"` XMLName xml.Name `xml:"GetPasswordComplexityConfigurationResponse"`
MinLen int `xml:"MinLen"` MinLen int `xml:"MinLen"`
Uppercase int `xml:"Uppercase"` Uppercase int `xml:"Uppercase"`
@@ -440,16 +379,12 @@ func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*Passw
PolicyConfigurationLocked bool `xml:"PolicyConfigurationLocked"` PolicyConfigurationLocked bool `xml:"PolicyConfigurationLocked"`
} }
req := GetPasswordComplexityConfiguration{ req := getPasswordComplexityConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetPasswordComplexityConfigurationResponse var resp getPasswordComplexityConfigurationResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetPasswordComplexityConfiguration failed: %w", err) return nil, fmt.Errorf("GetPasswordComplexityConfiguration failed: %w", err)
} }
@@ -468,7 +403,7 @@ func (c *Client) SetPasswordComplexityConfiguration(
ctx context.Context, ctx context.Context,
config *PasswordComplexityConfiguration, config *PasswordComplexityConfiguration,
) error { ) error {
type SetPasswordComplexityConfiguration struct { type setPasswordComplexityConfigurationRequest struct {
XMLName xml.Name `xml:"tds:SetPasswordComplexityConfiguration"` XMLName xml.Name `xml:"tds:SetPasswordComplexityConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
MinLen int `xml:"tds:MinLen,omitempty"` MinLen int `xml:"tds:MinLen,omitempty"`
@@ -479,7 +414,7 @@ func (c *Client) SetPasswordComplexityConfiguration(
PolicyConfigurationLocked bool `xml:"tds:PolicyConfigurationLocked,omitempty"` PolicyConfigurationLocked bool `xml:"tds:PolicyConfigurationLocked,omitempty"`
} }
req := SetPasswordComplexityConfiguration{ req := setPasswordComplexityConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
MinLen: config.MinLen, MinLen: config.MinLen,
Uppercase: config.Uppercase, Uppercase: config.Uppercase,
@@ -489,10 +424,7 @@ func (c *Client) SetPasswordComplexityConfiguration(
PolicyConfigurationLocked: config.PolicyConfigurationLocked, PolicyConfigurationLocked: config.PolicyConfigurationLocked,
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetPasswordComplexityConfiguration failed: %w", err) return fmt.Errorf("SetPasswordComplexityConfiguration failed: %w", err)
} }
@@ -501,27 +433,23 @@ func (c *Client) SetPasswordComplexityConfiguration(
// GetPasswordHistoryConfiguration retrieves the current password history configuration settings. // GetPasswordHistoryConfiguration retrieves the current password history configuration settings.
func (c *Client) GetPasswordHistoryConfiguration(ctx context.Context) (*PasswordHistoryConfiguration, error) { func (c *Client) GetPasswordHistoryConfiguration(ctx context.Context) (*PasswordHistoryConfiguration, error) {
type GetPasswordHistoryConfiguration struct { type getPasswordHistoryConfigurationRequest struct {
XMLName xml.Name `xml:"tds:GetPasswordHistoryConfiguration"` XMLName xml.Name `xml:"tds:GetPasswordHistoryConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetPasswordHistoryConfigurationResponse struct { type getPasswordHistoryConfigurationResponse struct {
XMLName xml.Name `xml:"GetPasswordHistoryConfigurationResponse"` XMLName xml.Name `xml:"GetPasswordHistoryConfigurationResponse"`
Enabled bool `xml:"Enabled"` Enabled bool `xml:"Enabled"`
Length int `xml:"Length"` Length int `xml:"Length"`
} }
req := GetPasswordHistoryConfiguration{ req := getPasswordHistoryConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetPasswordHistoryConfigurationResponse var resp getPasswordHistoryConfigurationResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetPasswordHistoryConfiguration failed: %w", err) 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. // SetPasswordHistoryConfiguration allows setting of the password history configuration.
func (c *Client) SetPasswordHistoryConfiguration(ctx context.Context, config *PasswordHistoryConfiguration) error { func (c *Client) SetPasswordHistoryConfiguration(ctx context.Context, config *PasswordHistoryConfiguration) error {
type SetPasswordHistoryConfiguration struct { type setPasswordHistoryConfigurationRequest struct {
XMLName xml.Name `xml:"tds:SetPasswordHistoryConfiguration"` XMLName xml.Name `xml:"tds:SetPasswordHistoryConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
Enabled bool `xml:"tds:Enabled"` Enabled bool `xml:"tds:Enabled"`
Length int `xml:"tds:Length"` Length int `xml:"tds:Length"`
} }
req := SetPasswordHistoryConfiguration{ req := setPasswordHistoryConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
Enabled: config.Enabled, Enabled: config.Enabled,
Length: config.Length, Length: config.Length,
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetPasswordHistoryConfiguration failed: %w", err) 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. // GetAuthFailureWarningConfiguration retrieves the current authentication failure warning configuration.
func (c *Client) GetAuthFailureWarningConfiguration(ctx context.Context) (*AuthFailureWarningConfiguration, error) { func (c *Client) GetAuthFailureWarningConfiguration(ctx context.Context) (*AuthFailureWarningConfiguration, error) {
type GetAuthFailureWarningConfiguration struct { type getAuthFailureWarningConfigurationRequest struct {
XMLName xml.Name `xml:"tds:GetAuthFailureWarningConfiguration"` XMLName xml.Name `xml:"tds:GetAuthFailureWarningConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetAuthFailureWarningConfigurationResponse struct { type getAuthFailureWarningConfigurationResponse struct {
XMLName xml.Name `xml:"GetAuthFailureWarningConfigurationResponse"` XMLName xml.Name `xml:"GetAuthFailureWarningConfigurationResponse"`
Enabled bool `xml:"Enabled"` Enabled bool `xml:"Enabled"`
MonitorPeriod int `xml:"MonitorPeriod"` MonitorPeriod int `xml:"MonitorPeriod"`
MaxAuthFailures int `xml:"MaxAuthFailures"` MaxAuthFailures int `xml:"MaxAuthFailures"`
} }
req := GetAuthFailureWarningConfiguration{ req := getAuthFailureWarningConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var resp GetAuthFailureWarningConfigurationResponse var resp getAuthFailureWarningConfigurationResponse
if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, &resp); err != nil {
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetAuthFailureWarningConfiguration failed: %w", err) return nil, fmt.Errorf("GetAuthFailureWarningConfiguration failed: %w", err)
} }
@@ -595,7 +516,7 @@ func (c *Client) SetAuthFailureWarningConfiguration(
ctx context.Context, ctx context.Context,
config *AuthFailureWarningConfiguration, config *AuthFailureWarningConfiguration,
) error { ) error {
type SetAuthFailureWarningConfiguration struct { type setAuthFailureWarningConfigurationRequest struct {
XMLName xml.Name `xml:"tds:SetAuthFailureWarningConfiguration"` XMLName xml.Name `xml:"tds:SetAuthFailureWarningConfiguration"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
Enabled bool `xml:"tds:Enabled"` Enabled bool `xml:"tds:Enabled"`
@@ -603,17 +524,14 @@ func (c *Client) SetAuthFailureWarningConfiguration(
MaxAuthFailures int `xml:"tds:MaxAuthFailures"` MaxAuthFailures int `xml:"tds:MaxAuthFailures"`
} }
req := SetAuthFailureWarningConfiguration{ req := setAuthFailureWarningConfigurationRequest{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
Enabled: config.Enabled, Enabled: config.Enabled,
MonitorPeriod: config.MonitorPeriod, MonitorPeriod: config.MonitorPeriod,
MaxAuthFailures: config.MaxAuthFailures, MaxAuthFailures: config.MaxAuthFailures,
} }
username, password := c.GetCredentials() if err := c.newSOAPClient().Call(ctx, c.endpoint, "", req, nil); err != nil {
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetAuthFailureWarningConfiguration failed: %w", err) return fmt.Errorf("SetAuthFailureWarningConfiguration failed: %w", err)
} }
+263
View File
@@ -521,3 +521,266 @@ func TestIPAddressFilterTypeConstants(t *testing.T) {
t.Errorf("IPAddressFilterDeny should be 'Deny', got %s", IPAddressFilterDeny) 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)
}
}