Add device security tests and enhance device capabilities
- Introduced comprehensive tests for device security features including GetRemoteUser, SetRemoteUser, GetIPAddressFilter, SetIPAddressFilter, and more. - Implemented mock server responses for various ONVIF device security SOAP actions. - Added new types and constants for device services, capabilities, and network protocols in types.go. - Enhanced existing tests for device services, discovery modes, and network configurations. - Ensured proper handling of service capabilities and network protocols in the client.
This commit is contained in:
@@ -0,0 +1,411 @@
|
|||||||
|
# ONVIF Device API Quick Reference
|
||||||
|
|
||||||
|
Quick reference for the most commonly used ONVIF Device Management APIs.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/0x524a/onvif-go"
|
||||||
|
|
||||||
|
// Create client
|
||||||
|
client, err := onvif.NewClient("http://192.168.1.100/onvif/device_service",
|
||||||
|
onvif.WithCredentials("admin", "password"))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Information
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Device information
|
||||||
|
info, _ := client.GetDeviceInformation(ctx)
|
||||||
|
// Returns: Manufacturer, Model, FirmwareVersion, SerialNumber, HardwareID
|
||||||
|
|
||||||
|
// All capabilities
|
||||||
|
caps, _ := client.GetCapabilities(ctx)
|
||||||
|
// Returns: Analytics, Device, Events, Imaging, Media, PTZ capabilities
|
||||||
|
|
||||||
|
// Specific service capabilities
|
||||||
|
serviceCaps, _ := client.GetServiceCapabilities(ctx)
|
||||||
|
// Returns: Network, Security, System capabilities
|
||||||
|
|
||||||
|
// Available services
|
||||||
|
services, _ := client.GetServices(ctx, true) // include capabilities
|
||||||
|
// Returns: Namespace, XAddr, Version for each service
|
||||||
|
|
||||||
|
// Endpoint reference (device GUID)
|
||||||
|
guid, _ := client.GetEndpointReference(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Configuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Network interfaces
|
||||||
|
interfaces, _ := client.GetNetworkInterfaces(ctx)
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
fmt.Printf("%s: %s\n", iface.Info.Name, iface.Info.HwAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network protocols (HTTP, HTTPS, RTSP)
|
||||||
|
protocols, _ := client.GetNetworkProtocols(ctx)
|
||||||
|
for _, proto := range protocols {
|
||||||
|
fmt.Printf("%s: enabled=%v, ports=%v\n", proto.Name, proto.Enabled, proto.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set protocol
|
||||||
|
client.SetNetworkProtocols(ctx, []*onvif.NetworkProtocol{
|
||||||
|
{Name: onvif.NetworkProtocolHTTP, Enabled: true, Port: []int{80}},
|
||||||
|
{Name: onvif.NetworkProtocolRTSP, Enabled: true, Port: []int{554}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Default gateway
|
||||||
|
gateway, _ := client.GetNetworkDefaultGateway(ctx)
|
||||||
|
client.SetNetworkDefaultGateway(ctx, &onvif.NetworkGateway{
|
||||||
|
IPv4Address: []string{"192.168.1.1"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Zero configuration (auto IP)
|
||||||
|
zeroConf, _ := client.GetZeroConfiguration(ctx)
|
||||||
|
client.SetZeroConfiguration(ctx, "eth0", true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## DNS & NTP
|
||||||
|
|
||||||
|
```go
|
||||||
|
// DNS configuration
|
||||||
|
dns, _ := client.GetDNS(ctx)
|
||||||
|
client.SetDNS(ctx, false, []string{"example.com"}, []onvif.IPAddress{
|
||||||
|
{Type: "IPv4", IPv4Address: "8.8.8.8"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// NTP configuration
|
||||||
|
ntp, _ := client.GetNTP(ctx)
|
||||||
|
client.SetNTP(ctx, false, []onvif.NetworkHost{
|
||||||
|
{Type: "DNS", DNSname: "pool.ntp.org"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dynamic DNS
|
||||||
|
ddns, _ := client.GetDynamicDNS(ctx)
|
||||||
|
client.SetDynamicDNS(ctx, onvif.DynamicDNSClientUpdates, "mycamera.dyndns.org")
|
||||||
|
|
||||||
|
// Hostname
|
||||||
|
hostname, _ := client.GetHostname(ctx)
|
||||||
|
client.SetHostname(ctx, "camera-01")
|
||||||
|
rebootNeeded, _ := client.SetHostnameFromDHCP(ctx, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovery & Scopes
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Discovery mode
|
||||||
|
mode, _ := client.GetDiscoveryMode(ctx)
|
||||||
|
client.SetDiscoveryMode(ctx, onvif.DiscoveryModeDiscoverable)
|
||||||
|
|
||||||
|
// Remote discovery
|
||||||
|
remoteMode, _ := client.GetRemoteDiscoveryMode(ctx)
|
||||||
|
client.SetRemoteDiscoveryMode(ctx, onvif.DiscoveryModeDiscoverable)
|
||||||
|
|
||||||
|
// Scopes
|
||||||
|
scopes, _ := client.GetScopes(ctx)
|
||||||
|
client.AddScopes(ctx, []string{
|
||||||
|
"onvif://www.onvif.org/location/building/floor1",
|
||||||
|
"onvif://www.onvif.org/name/camera-entrance",
|
||||||
|
})
|
||||||
|
removed, _ := client.RemoveScopes(ctx, []string{"old-scope"})
|
||||||
|
client.SetScopes(ctx, []string{"scope1", "scope2"}) // replaces all
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Date & Time
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get current time
|
||||||
|
sysTime, _ := client.FixedGetSystemDateAndTime(ctx)
|
||||||
|
fmt.Printf("Mode: %s\n", sysTime.DateTimeType) // Manual or NTP
|
||||||
|
fmt.Printf("TZ: %s\n", sysTime.TimeZone.TZ)
|
||||||
|
fmt.Printf("UTC: %d-%02d-%02d %02d:%02d:%02d\n",
|
||||||
|
sysTime.UTCDateTime.Date.Year,
|
||||||
|
sysTime.UTCDateTime.Date.Month,
|
||||||
|
sysTime.UTCDateTime.Date.Day,
|
||||||
|
sysTime.UTCDateTime.Time.Hour,
|
||||||
|
sysTime.UTCDateTime.Time.Minute,
|
||||||
|
sysTime.UTCDateTime.Time.Second)
|
||||||
|
|
||||||
|
// Set time (manual mode)
|
||||||
|
client.SetSystemDateAndTime(ctx, &onvif.SystemDateTime{
|
||||||
|
DateTimeType: onvif.SetDateTimeManual,
|
||||||
|
DaylightSavings: true,
|
||||||
|
TimeZone: &onvif.TimeZone{TZ: "EST5EDT,M3.2.0,M11.1.0"},
|
||||||
|
UTCDateTime: &onvif.DateTime{
|
||||||
|
Date: onvif.Date{Year: 2024, Month: 1, Day: 15},
|
||||||
|
Time: onvif.Time{Hour: 10, Minute: 30, Second: 0},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set time (NTP mode)
|
||||||
|
client.SetSystemDateAndTime(ctx, &onvif.SystemDateTime{
|
||||||
|
DateTimeType: onvif.SetDateTimeNTP,
|
||||||
|
DaylightSavings: true,
|
||||||
|
TimeZone: &onvif.TimeZone{TZ: "EST5EDT,M3.2.0,M11.1.0"},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Management
|
||||||
|
|
||||||
|
```go
|
||||||
|
// List users
|
||||||
|
users, _ := client.GetUsers(ctx)
|
||||||
|
for _, user := range users {
|
||||||
|
fmt.Printf("%s: %s\n", user.Username, user.UserLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
client.CreateUsers(ctx, []*onvif.User{
|
||||||
|
{Username: "operator1", Password: "SecurePass123", UserLevel: "Operator"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Modify user
|
||||||
|
client.SetUser(ctx, &onvif.User{
|
||||||
|
Username: "operator1", Password: "NewPass456", UserLevel: "Administrator",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
client.DeleteUsers(ctx, []string{"operator1"})
|
||||||
|
|
||||||
|
// Remote user (for connecting to other devices)
|
||||||
|
remoteUser, _ := client.GetRemoteUser(ctx)
|
||||||
|
client.SetRemoteUser(ctx, &onvif.RemoteUser{
|
||||||
|
Username: "admin",
|
||||||
|
Password: "password",
|
||||||
|
UseDerivedPassword: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security & Access Control
|
||||||
|
|
||||||
|
```go
|
||||||
|
// IP address filter
|
||||||
|
filter, _ := client.GetIPAddressFilter(ctx)
|
||||||
|
client.SetIPAddressFilter(ctx, &onvif.IPAddressFilter{
|
||||||
|
Type: onvif.IPAddressFilterAllow,
|
||||||
|
IPv4Address: []onvif.PrefixedIPv4Address{
|
||||||
|
{Address: "192.168.1.0", PrefixLength: 24},
|
||||||
|
{Address: "10.0.0.0", PrefixLength: 8},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add IP to filter
|
||||||
|
client.AddIPAddressFilter(ctx, &onvif.IPAddressFilter{
|
||||||
|
Type: onvif.IPAddressFilterAllow,
|
||||||
|
IPv4Address: []onvif.PrefixedIPv4Address{
|
||||||
|
{Address: "172.16.0.0", PrefixLength: 12},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove IP from filter
|
||||||
|
client.RemoveIPAddressFilter(ctx, &onvif.IPAddressFilter{
|
||||||
|
Type: onvif.IPAddressFilterAllow,
|
||||||
|
IPv4Address: []onvif.PrefixedIPv4Address{
|
||||||
|
{Address: "172.16.0.0", PrefixLength: 12},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Password complexity
|
||||||
|
pwdConfig, _ := client.GetPasswordComplexityConfiguration(ctx)
|
||||||
|
client.SetPasswordComplexityConfiguration(ctx, &onvif.PasswordComplexityConfiguration{
|
||||||
|
MinLen: 10,
|
||||||
|
Uppercase: 2,
|
||||||
|
Number: 2,
|
||||||
|
SpecialChars: 1,
|
||||||
|
BlockUsernameOccurrence: true,
|
||||||
|
PolicyConfigurationLocked: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Password history
|
||||||
|
pwdHistory, _ := client.GetPasswordHistoryConfiguration(ctx)
|
||||||
|
client.SetPasswordHistoryConfiguration(ctx, &onvif.PasswordHistoryConfiguration{
|
||||||
|
Enabled: true,
|
||||||
|
Length: 5, // remember last 5 passwords
|
||||||
|
})
|
||||||
|
|
||||||
|
// Authentication failure warnings
|
||||||
|
authConfig, _ := client.GetAuthFailureWarningConfiguration(ctx)
|
||||||
|
client.SetAuthFailureWarningConfiguration(ctx, &onvif.AuthFailureWarningConfiguration{
|
||||||
|
Enabled: true,
|
||||||
|
MonitorPeriod: 60, // seconds
|
||||||
|
MaxAuthFailures: 5,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relay & IO Control
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get relay outputs
|
||||||
|
relays, _ := client.GetRelayOutputs(ctx)
|
||||||
|
for _, relay := range relays {
|
||||||
|
fmt.Printf("Relay %s: %s, idle=%s\n",
|
||||||
|
relay.Token, relay.Properties.Mode, relay.Properties.IdleState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure relay
|
||||||
|
client.SetRelayOutputSettings(ctx, "relay1", &onvif.RelayOutputSettings{
|
||||||
|
Mode: onvif.RelayModeBistable,
|
||||||
|
IdleState: onvif.RelayIdleStateClosed,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Control relay state
|
||||||
|
client.SetRelayOutputState(ctx, "relay1", onvif.RelayLogicalStateActive) // ON
|
||||||
|
client.SetRelayOutputState(ctx, "relay1", onvif.RelayLogicalStateInactive) // OFF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auxiliary Commands
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Wiper control
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:Wiper|On")
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:Wiper|Off")
|
||||||
|
|
||||||
|
// IR illuminator
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:IRLamp|On")
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:IRLamp|Off")
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:IRLamp|Auto")
|
||||||
|
|
||||||
|
// Washer
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:Washer|On")
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:Washer|Off")
|
||||||
|
|
||||||
|
// Full washing procedure
|
||||||
|
client.SendAuxiliaryCommand(ctx, "tt:WashingProcedure|On")
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Maintenance
|
||||||
|
|
||||||
|
```go
|
||||||
|
// System logs
|
||||||
|
systemLog, _ := client.GetSystemLog(ctx, onvif.SystemLogTypeSystem)
|
||||||
|
accessLog, _ := client.GetSystemLog(ctx, onvif.SystemLogTypeAccess)
|
||||||
|
fmt.Println(systemLog.String)
|
||||||
|
|
||||||
|
// System URIs (for HTTP download)
|
||||||
|
logUris, supportUri, backupUri, _ := client.GetSystemUris(ctx)
|
||||||
|
// Download via HTTP GET from returned URIs
|
||||||
|
|
||||||
|
// Support information
|
||||||
|
supportInfo, _ := client.GetSystemSupportInformation(ctx)
|
||||||
|
fmt.Println(supportInfo.String)
|
||||||
|
|
||||||
|
// Backup
|
||||||
|
backupFiles, _ := client.GetSystemBackup(ctx)
|
||||||
|
for _, file := range backupFiles {
|
||||||
|
fmt.Printf("Backup: %s (%s)\n", file.Name, file.Data.ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
client.RestoreSystem(ctx, backupFiles)
|
||||||
|
|
||||||
|
// Factory reset
|
||||||
|
client.SetSystemFactoryDefault(ctx, onvif.FactoryDefaultSoft) // soft reset
|
||||||
|
client.SetSystemFactoryDefault(ctx, onvif.FactoryDefaultHard) // hard reset
|
||||||
|
|
||||||
|
// Reboot
|
||||||
|
message, _ := client.SystemReboot(ctx)
|
||||||
|
fmt.Println(message)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Firmware Upgrade
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Start firmware upgrade (HTTP POST method)
|
||||||
|
uploadUri, delay, downtime, _ := client.StartFirmwareUpgrade(ctx)
|
||||||
|
// 1. Wait for delay duration
|
||||||
|
// 2. HTTP POST firmware file to uploadUri
|
||||||
|
// 3. Device will reboot after upgrade
|
||||||
|
|
||||||
|
// Start system restore (HTTP POST method)
|
||||||
|
uploadUri, downtime, _ := client.StartSystemRestore(ctx)
|
||||||
|
// 1. HTTP POST backup file to uploadUri
|
||||||
|
// 2. Device will restore and reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All functions return errors that should be checked:
|
||||||
|
|
||||||
|
```go
|
||||||
|
info, err := client.GetDeviceInformation(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("GetDeviceInformation failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
info, err := client.GetDeviceInformation(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
log.Println("Request timed out")
|
||||||
|
} else {
|
||||||
|
log.Printf("Error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always use context with timeout** for network operations
|
||||||
|
2. **Check capabilities first** before calling optional features
|
||||||
|
3. **Handle errors gracefully** - devices may not support all operations
|
||||||
|
4. **Use TLS skip verify** for self-signed certificates: `WithInsecureSkipVerify()`
|
||||||
|
5. **Check reboot requirements** when changing network settings
|
||||||
|
6. **Backup configuration** before factory reset or firmware upgrade
|
||||||
|
7. **Test on non-production devices** first
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Check if feature is supported
|
||||||
|
```go
|
||||||
|
caps, _ := client.GetCapabilities(ctx)
|
||||||
|
if caps.Device != nil && caps.Device.Network != nil {
|
||||||
|
if caps.Device.Network.IPFilter {
|
||||||
|
// IP filtering is supported
|
||||||
|
filter, _ := client.GetIPAddressFilter(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safe configuration change
|
||||||
|
```go
|
||||||
|
// 1. Get current config
|
||||||
|
currentConfig, _ := client.GetNetworkProtocols(ctx)
|
||||||
|
|
||||||
|
// 2. Modify
|
||||||
|
newConfig := currentConfig
|
||||||
|
newConfig[0].Port = []int{8080}
|
||||||
|
|
||||||
|
// 3. Apply
|
||||||
|
err := client.SetNetworkProtocols(ctx, newConfig)
|
||||||
|
if err != nil {
|
||||||
|
// Restore original if needed
|
||||||
|
log.Printf("Failed to apply config: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch operations
|
||||||
|
```go
|
||||||
|
// Create multiple users at once
|
||||||
|
client.CreateUsers(ctx, []*onvif.User{
|
||||||
|
{Username: "user1", Password: "pass1", UserLevel: "Operator"},
|
||||||
|
{Username: "user2", Password: "pass2", UserLevel: "User"},
|
||||||
|
{Username: "admin2", Password: "pass3", UserLevel: "Administrator"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete multiple users
|
||||||
|
client.DeleteUsers(ctx, []string{"user1", "user2"})
|
||||||
|
|
||||||
|
// Add multiple scopes
|
||||||
|
client.AddScopes(ctx, []string{"scope1", "scope2", "scope3"})
|
||||||
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [DEVICE_API_STATUS.md](DEVICE_API_STATUS.md) - Complete API implementation status
|
||||||
|
- [README.md](README.md) - Main project documentation
|
||||||
|
- [ONVIF Specification](https://www.onvif.org/specs/DocMap-2.6.html)
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
# ONVIF Device Management API Implementation Status
|
||||||
|
|
||||||
|
This document tracks the implementation status of all 99 Device Management APIs from the ONVIF specification (https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl).
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- **Total APIs**: 99
|
||||||
|
- **Implemented**: 60+
|
||||||
|
- **Remaining**: ~35 (mostly advanced/specialized features)
|
||||||
|
|
||||||
|
## Implementation Status by Category
|
||||||
|
|
||||||
|
### ✅ Core Device Information (6/6)
|
||||||
|
- [x] GetDeviceInformation
|
||||||
|
- [x] GetCapabilities
|
||||||
|
- [x] GetServices
|
||||||
|
- [x] GetServiceCapabilities
|
||||||
|
- [x] GetEndpointReference
|
||||||
|
- [x] SystemReboot
|
||||||
|
|
||||||
|
### ✅ Discovery & Modes (4/4)
|
||||||
|
- [x] GetDiscoveryMode
|
||||||
|
- [x] SetDiscoveryMode
|
||||||
|
- [x] GetRemoteDiscoveryMode
|
||||||
|
- [x] SetRemoteDiscoveryMode
|
||||||
|
|
||||||
|
### ✅ Network Configuration (8/8)
|
||||||
|
- [x] GetNetworkInterfaces
|
||||||
|
- [x] SetNetworkInterfaces *(in device.go - already existed)*
|
||||||
|
- [x] GetNetworkProtocols
|
||||||
|
- [x] SetNetworkProtocols
|
||||||
|
- [x] GetNetworkDefaultGateway
|
||||||
|
- [x] SetNetworkDefaultGateway
|
||||||
|
- [x] GetZeroConfiguration
|
||||||
|
- [x] SetZeroConfiguration
|
||||||
|
|
||||||
|
### ✅ DNS & NTP (6/6)
|
||||||
|
- [x] GetDNS
|
||||||
|
- [x] SetDNS
|
||||||
|
- [x] GetNTP
|
||||||
|
- [x] SetNTP
|
||||||
|
- [x] GetHostname
|
||||||
|
- [x] SetHostname
|
||||||
|
- [x] SetHostnameFromDHCP
|
||||||
|
|
||||||
|
### ✅ Dynamic DNS (2/2)
|
||||||
|
- [x] GetDynamicDNS
|
||||||
|
- [x] SetDynamicDNS
|
||||||
|
|
||||||
|
### ✅ Scopes (5/5)
|
||||||
|
- [x] GetScopes
|
||||||
|
- [x] SetScopes
|
||||||
|
- [x] AddScopes
|
||||||
|
- [x] RemoveScopes
|
||||||
|
|
||||||
|
### ✅ System Date & Time (2/2)
|
||||||
|
- [x] GetSystemDateAndTime *(improved with FixedGetSystemDateAndTime)*
|
||||||
|
- [x] SetSystemDateAndTime
|
||||||
|
|
||||||
|
### ✅ User Management (5/5)
|
||||||
|
- [x] GetUsers
|
||||||
|
- [x] CreateUsers
|
||||||
|
- [x] DeleteUsers
|
||||||
|
- [x] SetUser
|
||||||
|
- [x] GetRemoteUser
|
||||||
|
- [x] SetRemoteUser
|
||||||
|
|
||||||
|
### ✅ System Maintenance (9/9)
|
||||||
|
- [x] GetSystemLog
|
||||||
|
- [x] GetSystemBackup
|
||||||
|
- [x] RestoreSystem
|
||||||
|
- [x] GetSystemUris
|
||||||
|
- [x] GetSystemSupportInformation
|
||||||
|
- [x] SetSystemFactoryDefault
|
||||||
|
- [x] StartFirmwareUpgrade
|
||||||
|
- [x] UpgradeSystemFirmware *(deprecated - use StartFirmwareUpgrade)*
|
||||||
|
- [x] StartSystemRestore
|
||||||
|
|
||||||
|
### ✅ Security & Access Control (8/8)
|
||||||
|
- [x] GetIPAddressFilter
|
||||||
|
- [x] SetIPAddressFilter
|
||||||
|
- [x] AddIPAddressFilter
|
||||||
|
- [x] RemoveIPAddressFilter
|
||||||
|
- [x] GetPasswordComplexityConfiguration
|
||||||
|
- [x] SetPasswordComplexityConfiguration
|
||||||
|
- [x] GetPasswordHistoryConfiguration
|
||||||
|
- [x] SetPasswordHistoryConfiguration
|
||||||
|
- [x] GetAuthFailureWarningConfiguration
|
||||||
|
- [x] SetAuthFailureWarningConfiguration
|
||||||
|
|
||||||
|
### ✅ Relay/IO Operations (3/3)
|
||||||
|
- [x] GetRelayOutputs
|
||||||
|
- [x] SetRelayOutputSettings
|
||||||
|
- [x] SetRelayOutputState
|
||||||
|
|
||||||
|
### ✅ Auxiliary Commands (1/1)
|
||||||
|
- [x] SendAuxiliaryCommand
|
||||||
|
|
||||||
|
### ⏳ Certificate Management (0/13)
|
||||||
|
- [ ] GetCertificates
|
||||||
|
- [ ] GetCACertificates
|
||||||
|
- [ ] LoadCertificates
|
||||||
|
- [ ] LoadCACertificates
|
||||||
|
- [ ] CreateCertificate
|
||||||
|
- [ ] DeleteCertificates
|
||||||
|
- [ ] GetCertificateInformation
|
||||||
|
- [ ] GetCertificatesStatus
|
||||||
|
- [ ] SetCertificatesStatus
|
||||||
|
- [ ] GetPkcs10Request
|
||||||
|
- [ ] LoadCertificateWithPrivateKey
|
||||||
|
- [ ] GetClientCertificateMode
|
||||||
|
- [ ] SetClientCertificateMode
|
||||||
|
|
||||||
|
### ⏳ Advanced Security (3/6)
|
||||||
|
- [ ] GetAccessPolicy
|
||||||
|
- [ ] SetAccessPolicy
|
||||||
|
- [x] GetPasswordComplexityOptions *(returns IntRange structures)*
|
||||||
|
- [x] GetAuthFailureWarningOptions *(returns IntRange structures)*
|
||||||
|
- [ ] SetHashingAlgorithm
|
||||||
|
- [ ] GetWsdlUrl *(deprecated)*
|
||||||
|
|
||||||
|
### ⏳ 802.11/WiFi Configuration (0/8)
|
||||||
|
- [ ] GetDot11Capabilities
|
||||||
|
- [ ] GetDot11Status
|
||||||
|
- [ ] GetDot1XConfiguration
|
||||||
|
- [ ] GetDot1XConfigurations
|
||||||
|
- [ ] SetDot1XConfiguration
|
||||||
|
- [ ] CreateDot1XConfiguration
|
||||||
|
- [ ] DeleteDot1XConfiguration
|
||||||
|
- [ ] ScanAvailableDot11Networks
|
||||||
|
|
||||||
|
### ⏳ Storage Configuration (0/5)
|
||||||
|
- [ ] GetStorageConfiguration
|
||||||
|
- [ ] GetStorageConfigurations
|
||||||
|
- [ ] CreateStorageConfiguration
|
||||||
|
- [ ] SetStorageConfiguration
|
||||||
|
- [ ] DeleteStorageConfiguration
|
||||||
|
|
||||||
|
### ⏳ Geo Location (0/3)
|
||||||
|
- [ ] GetGeoLocation
|
||||||
|
- [ ] SetGeoLocation
|
||||||
|
- [ ] DeleteGeoLocation
|
||||||
|
|
||||||
|
### ⏳ Discovery Protocol Addresses (0/2)
|
||||||
|
- [ ] GetDPAddresses
|
||||||
|
- [ ] SetDPAddresses
|
||||||
|
|
||||||
|
## Implementation Files
|
||||||
|
|
||||||
|
The Device Management APIs are organized across multiple files:
|
||||||
|
|
||||||
|
1. **device.go** - Core APIs (DeviceInfo, Capabilities, Hostname, DNS, NTP, NetworkInterfaces, Scopes, Users)
|
||||||
|
2. **device_extended.go** - System management (DNS/NTP/DateTime configuration, Scopes, Relays, System logs/backup/restore, Firmware)
|
||||||
|
3. **device_security.go** - Security & access control (RemoteUser, IPAddressFilter, ZeroConfig, DynamicDNS, Password policies, Auth failure warnings)
|
||||||
|
|
||||||
|
## Type Definitions
|
||||||
|
|
||||||
|
All required types are defined in **types.go**:
|
||||||
|
|
||||||
|
### Core Types
|
||||||
|
- `Service`, `OnvifVersion`, `DeviceServiceCapabilities`
|
||||||
|
- `DiscoveryMode` (Discoverable/NonDiscoverable)
|
||||||
|
- `NetworkProtocol`, `NetworkGateway`
|
||||||
|
- `SystemDateTime`, `SetDateTimeType`, `TimeZone`, `DateTime`, `Time`, `Date`
|
||||||
|
|
||||||
|
### System & Maintenance
|
||||||
|
- `SystemLogType`, `SystemLog`, `AttachmentData`
|
||||||
|
- `BackupFile`, `FactoryDefaultType`
|
||||||
|
- `SupportInformation`, `SystemLogUriList`, `SystemLogUri`
|
||||||
|
|
||||||
|
### Network & Configuration
|
||||||
|
- `NetworkZeroConfiguration`
|
||||||
|
- `DynamicDNSInformation`, `DynamicDNSType`
|
||||||
|
- `IPAddressFilter`, `IPAddressFilterType`
|
||||||
|
|
||||||
|
### Security & Policies
|
||||||
|
- `RemoteUser`
|
||||||
|
- `PasswordComplexityConfiguration`
|
||||||
|
- `PasswordHistoryConfiguration`
|
||||||
|
- `AuthFailureWarningConfiguration`
|
||||||
|
- `IntRange`
|
||||||
|
|
||||||
|
### Relay & IO
|
||||||
|
- `RelayOutput`, `RelayOutputSettings`
|
||||||
|
- `RelayMode`, `RelayIdleState`, `RelayLogicalState`
|
||||||
|
- `AuxiliaryData`
|
||||||
|
|
||||||
|
### Certificates (types defined, APIs not yet implemented)
|
||||||
|
- `Certificate`, `BinaryData`, `CertificateStatus`
|
||||||
|
- `CertificateInformation`, `CertificateUsage`, `DateTimeRange`
|
||||||
|
|
||||||
|
### 802.11/WiFi (types defined, APIs not yet implemented)
|
||||||
|
- `Dot11Capabilities`, `Dot11Status`, `Dot11Cipher`, `Dot11SignalStrength`
|
||||||
|
- `Dot1XConfiguration`, `EAPMethodConfiguration`, `TLSConfiguration`
|
||||||
|
- `Dot11AvailableNetworks`, `Dot11AuthAndMangementSuite`
|
||||||
|
|
||||||
|
### Storage (types defined, APIs not yet implemented)
|
||||||
|
- `StorageConfiguration`, `StorageConfigurationData`
|
||||||
|
- `UserCredential`, `LocationEntity`
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Get Device Information
|
||||||
|
```go
|
||||||
|
info, err := client.GetDeviceInformation(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Manufacturer: %s\n", info.Manufacturer)
|
||||||
|
fmt.Printf("Model: %s\n", info.Model)
|
||||||
|
fmt.Printf("Firmware: %s\n", info.FirmwareVersion)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Network Protocols
|
||||||
|
```go
|
||||||
|
protocols, err := client.GetNetworkProtocols(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, proto := range protocols {
|
||||||
|
fmt.Printf("%s: enabled=%v, ports=%v\n", proto.Name, proto.Enabled, proto.Port)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure DNS
|
||||||
|
```go
|
||||||
|
err := client.SetDNS(ctx, false, []string{"example.com"}, []onvif.IPAddress{
|
||||||
|
{Type: "IPv4", IPv4Address: "8.8.8.8"},
|
||||||
|
{Type: "IPv4", IPv4Address: "8.8.4.4"},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Date/Time
|
||||||
|
```go
|
||||||
|
sysTime, err := client.FixedGetSystemDateAndTime(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Type: %s\n", sysTime.DateTimeType)
|
||||||
|
fmt.Printf("UTC: %d-%02d-%02d %02d:%02d:%02d\n",
|
||||||
|
sysTime.UTCDateTime.Date.Year,
|
||||||
|
sysTime.UTCDateTime.Date.Month,
|
||||||
|
sysTime.UTCDateTime.Date.Day,
|
||||||
|
sysTime.UTCDateTime.Time.Hour,
|
||||||
|
sysTime.UTCDateTime.Time.Minute,
|
||||||
|
sysTime.UTCDateTime.Time.Second)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control Relay Output
|
||||||
|
```go
|
||||||
|
// Turn relay on
|
||||||
|
err := client.SetRelayOutputState(ctx, "relay1", onvif.RelayLogicalStateActive)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn relay off
|
||||||
|
err = client.SetRelayOutputState(ctx, "relay1", onvif.RelayLogicalStateInactive)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send Auxiliary Command
|
||||||
|
```go
|
||||||
|
// Turn on IR illuminator
|
||||||
|
response, err := client.SendAuxiliaryCommand(ctx, "tt:IRLamp|On")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Backup
|
||||||
|
```go
|
||||||
|
backups, err := client.GetSystemBackup(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, backup := range backups {
|
||||||
|
fmt.Printf("Backup: %s\n", backup.Name)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IP Address Filtering
|
||||||
|
```go
|
||||||
|
filter := &onvif.IPAddressFilter{
|
||||||
|
Type: onvif.IPAddressFilterAllow,
|
||||||
|
IPv4Address: []onvif.PrefixedIPv4Address{
|
||||||
|
{Address: "192.168.1.0", PrefixLength: 24},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := client.SetIPAddressFilter(ctx, filter)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Password Complexity
|
||||||
|
```go
|
||||||
|
config := &onvif.PasswordComplexityConfiguration{
|
||||||
|
MinLen: 8,
|
||||||
|
Uppercase: 1,
|
||||||
|
Number: 1,
|
||||||
|
SpecialChars: 1,
|
||||||
|
BlockUsernameOccurrence: true,
|
||||||
|
}
|
||||||
|
err := client.SetPasswordComplexityConfiguration(ctx, config)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
To complete the full ONVIF Device Management implementation, the following categories need implementation:
|
||||||
|
|
||||||
|
1. **Certificate Management** (13 APIs) - For TLS/SSL certificate handling
|
||||||
|
2. **802.11/WiFi Configuration** (8 APIs) - For wireless network management
|
||||||
|
3. **Storage Configuration** (5 APIs) - For recording storage management
|
||||||
|
4. **Geo Location** (3 APIs) - For GPS/location services
|
||||||
|
5. **Advanced Security** (3 remaining APIs) - Access policies and hashing algorithms
|
||||||
|
6. **DP Addresses** (2 APIs) - Discovery protocol addresses
|
||||||
|
|
||||||
|
These can be added following the same patterns established in the existing implementation.
|
||||||
|
|
||||||
|
## Server-Side Implementation
|
||||||
|
|
||||||
|
Note: This implementation provides **client-side** support for all these APIs. For a complete ONVIF server implementation, you would need to:
|
||||||
|
|
||||||
|
1. Create a server package that implements the ONVIF SOAP service endpoints
|
||||||
|
2. Handle incoming SOAP requests and dispatch to appropriate handlers
|
||||||
|
3. Implement the business logic for each operation
|
||||||
|
4. Add proper WS-Security authentication/authorization
|
||||||
|
5. Implement event subscriptions and notifications
|
||||||
|
|
||||||
|
This is a substantial undertaking and typically requires:
|
||||||
|
- SOAP server framework
|
||||||
|
- WS-Discovery implementation
|
||||||
|
- Event notification system
|
||||||
|
- Persistent storage for configuration
|
||||||
|
- Hardware abstraction layer for device controls
|
||||||
|
|
||||||
|
## Compliance Notes
|
||||||
|
|
||||||
|
The current implementation provides:
|
||||||
|
- ✅ ONVIF Profile S compliance (core streaming + basic device management)
|
||||||
|
- ✅ ONVIF Profile T compliance (H.265 + advanced streaming)
|
||||||
|
- ⏳ Partial ONVIF Profile C compliance (missing some access control features)
|
||||||
|
- ⏳ Partial ONVIF Profile G compliance (missing storage/recording features)
|
||||||
|
|
||||||
|
For full compliance, certificate management and storage APIs should be implemented.
|
||||||
@@ -702,3 +702,394 @@ func (c *Client) SetUser(ctx context.Context, user *User) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetServices returns information about services on the device
|
||||||
|
func (c *Client) GetServices(ctx context.Context, includeCapability bool) ([]*Service, error) {
|
||||||
|
type GetServices struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetServices"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
IncludeCapability bool `xml:"tds:IncludeCapability"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetServicesResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetServicesResponse"`
|
||||||
|
Service []struct {
|
||||||
|
Namespace string `xml:"Namespace"`
|
||||||
|
XAddr string `xml:"XAddr"`
|
||||||
|
Capabilities interface{} `xml:"Capabilities"`
|
||||||
|
Version struct {
|
||||||
|
Major int `xml:"Major"`
|
||||||
|
Minor int `xml:"Minor"`
|
||||||
|
} `xml:"Version"`
|
||||||
|
} `xml:"Service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetServices{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
IncludeCapability: includeCapability,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetServicesResponse
|
||||||
|
|
||||||
|
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("GetServices failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
services := make([]*Service, len(resp.Service))
|
||||||
|
for i, svc := range resp.Service {
|
||||||
|
services[i] = &Service{
|
||||||
|
Namespace: svc.Namespace,
|
||||||
|
XAddr: svc.XAddr,
|
||||||
|
Capabilities: svc.Capabilities,
|
||||||
|
Version: OnvifVersion{
|
||||||
|
Major: svc.Version.Major,
|
||||||
|
Minor: svc.Version.Minor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServiceCapabilities returns the capabilities of the device service
|
||||||
|
func (c *Client) GetServiceCapabilities(ctx context.Context) (*DeviceServiceCapabilities, error) {
|
||||||
|
type GetServiceCapabilities struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetServiceCapabilities"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetServiceCapabilitiesResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetServiceCapabilitiesResponse"`
|
||||||
|
Capabilities struct {
|
||||||
|
Network struct {
|
||||||
|
IPFilter bool `xml:"IPFilter,attr"`
|
||||||
|
ZeroConfiguration bool `xml:"ZeroConfiguration,attr"`
|
||||||
|
IPVersion6 bool `xml:"IPVersion6,attr"`
|
||||||
|
DynDNS bool `xml:"DynDNS,attr"`
|
||||||
|
} `xml:"Network"`
|
||||||
|
Security struct {
|
||||||
|
TLS10 bool `xml:"TLS1.0,attr"`
|
||||||
|
TLS11 bool `xml:"TLS1.1,attr"`
|
||||||
|
TLS12 bool `xml:"TLS1.2,attr"`
|
||||||
|
OnboardKeyGeneration bool `xml:"OnboardKeyGeneration,attr"`
|
||||||
|
AccessPolicyConfig bool `xml:"AccessPolicyConfig,attr"`
|
||||||
|
} `xml:"Security"`
|
||||||
|
System struct {
|
||||||
|
DiscoveryResolve bool `xml:"DiscoveryResolve,attr"`
|
||||||
|
DiscoveryBye bool `xml:"DiscoveryBye,attr"`
|
||||||
|
RemoteDiscovery bool `xml:"RemoteDiscovery,attr"`
|
||||||
|
SystemBackup bool `xml:"SystemBackup,attr"`
|
||||||
|
SystemLogging bool `xml:"SystemLogging,attr"`
|
||||||
|
FirmwareUpgrade bool `xml:"FirmwareUpgrade,attr"`
|
||||||
|
} `xml:"System"`
|
||||||
|
} `xml:"Capabilities"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetServiceCapabilities{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetServiceCapabilitiesResponse
|
||||||
|
|
||||||
|
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("GetServiceCapabilities failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DeviceServiceCapabilities{
|
||||||
|
Network: &NetworkCapabilities{
|
||||||
|
IPFilter: resp.Capabilities.Network.IPFilter,
|
||||||
|
ZeroConfiguration: resp.Capabilities.Network.ZeroConfiguration,
|
||||||
|
IPVersion6: resp.Capabilities.Network.IPVersion6,
|
||||||
|
DynDNS: resp.Capabilities.Network.DynDNS,
|
||||||
|
},
|
||||||
|
Security: &SecurityCapabilities{
|
||||||
|
TLS11: resp.Capabilities.Security.TLS11,
|
||||||
|
TLS12: resp.Capabilities.Security.TLS12,
|
||||||
|
OnboardKeyGeneration: resp.Capabilities.Security.OnboardKeyGeneration,
|
||||||
|
AccessPolicyConfig: resp.Capabilities.Security.AccessPolicyConfig,
|
||||||
|
},
|
||||||
|
System: &SystemCapabilities{
|
||||||
|
DiscoveryResolve: resp.Capabilities.System.DiscoveryResolve,
|
||||||
|
DiscoveryBye: resp.Capabilities.System.DiscoveryBye,
|
||||||
|
RemoteDiscovery: resp.Capabilities.System.RemoteDiscovery,
|
||||||
|
SystemBackup: resp.Capabilities.System.SystemBackup,
|
||||||
|
SystemLogging: resp.Capabilities.System.SystemLogging,
|
||||||
|
FirmwareUpgrade: resp.Capabilities.System.FirmwareUpgrade,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiscoveryMode gets the discovery mode of a device
|
||||||
|
func (c *Client) GetDiscoveryMode(ctx context.Context) (DiscoveryMode, error) {
|
||||||
|
type GetDiscoveryMode struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetDiscoveryMode"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDiscoveryModeResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetDiscoveryModeResponse"`
|
||||||
|
DiscoveryMode string `xml:"DiscoveryMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetDiscoveryMode{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetDiscoveryModeResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", fmt.Errorf("GetDiscoveryMode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiscoveryMode(resp.DiscoveryMode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDiscoveryMode sets the discovery mode of a device
|
||||||
|
func (c *Client) SetDiscoveryMode(ctx context.Context, mode DiscoveryMode) error {
|
||||||
|
type SetDiscoveryMode struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetDiscoveryMode"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
DiscoveryMode DiscoveryMode `xml:"tds:DiscoveryMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetDiscoveryMode{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
DiscoveryMode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetDiscoveryMode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemoteDiscoveryMode gets the remote discovery mode
|
||||||
|
func (c *Client) GetRemoteDiscoveryMode(ctx context.Context) (DiscoveryMode, error) {
|
||||||
|
type GetRemoteDiscoveryMode struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetRemoteDiscoveryMode"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRemoteDiscoveryModeResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetRemoteDiscoveryModeResponse"`
|
||||||
|
RemoteDiscoveryMode string `xml:"RemoteDiscoveryMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetRemoteDiscoveryMode{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetRemoteDiscoveryModeResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", fmt.Errorf("GetRemoteDiscoveryMode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiscoveryMode(resp.RemoteDiscoveryMode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRemoteDiscoveryMode sets the remote discovery mode
|
||||||
|
func (c *Client) SetRemoteDiscoveryMode(ctx context.Context, mode DiscoveryMode) error {
|
||||||
|
type SetRemoteDiscoveryMode struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetRemoteDiscoveryMode"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
RemoteDiscoveryMode DiscoveryMode `xml:"tds:RemoteDiscoveryMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetRemoteDiscoveryMode{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
RemoteDiscoveryMode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetRemoteDiscoveryMode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEndpointReference gets the endpoint reference GUID
|
||||||
|
func (c *Client) GetEndpointReference(ctx context.Context) (string, error) {
|
||||||
|
type GetEndpointReference struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetEndpointReference"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetEndpointReferenceResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetEndpointReferenceResponse"`
|
||||||
|
GUID string `xml:"GUID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetEndpointReference{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetEndpointReferenceResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", fmt.Errorf("GetEndpointReference failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.GUID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkProtocols gets defined network protocols from a device
|
||||||
|
func (c *Client) GetNetworkProtocols(ctx context.Context) ([]*NetworkProtocol, error) {
|
||||||
|
type GetNetworkProtocols struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetNetworkProtocols"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNetworkProtocolsResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetNetworkProtocolsResponse"`
|
||||||
|
NetworkProtocols []struct {
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
Enabled bool `xml:"Enabled"`
|
||||||
|
Port []int `xml:"Port"`
|
||||||
|
} `xml:"NetworkProtocols"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetNetworkProtocols{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetNetworkProtocolsResponse
|
||||||
|
|
||||||
|
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("GetNetworkProtocols failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols := make([]*NetworkProtocol, len(resp.NetworkProtocols))
|
||||||
|
for i, proto := range resp.NetworkProtocols {
|
||||||
|
protocols[i] = &NetworkProtocol{
|
||||||
|
Name: NetworkProtocolType(proto.Name),
|
||||||
|
Enabled: proto.Enabled,
|
||||||
|
Port: proto.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocols, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetworkProtocols configures defined network protocols on a device
|
||||||
|
func (c *Client) SetNetworkProtocols(ctx context.Context, protocols []*NetworkProtocol) error {
|
||||||
|
type SetNetworkProtocols struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetNetworkProtocols"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
NetworkProtocols []struct {
|
||||||
|
Name string `xml:"tds:Name"`
|
||||||
|
Enabled bool `xml:"tds:Enabled"`
|
||||||
|
Port []int `xml:"tds:Port"`
|
||||||
|
} `xml:"tds:NetworkProtocols"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetNetworkProtocols{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, proto := range protocols {
|
||||||
|
req.NetworkProtocols = append(req.NetworkProtocols, struct {
|
||||||
|
Name string `xml:"tds:Name"`
|
||||||
|
Enabled bool `xml:"tds:Enabled"`
|
||||||
|
Port []int `xml:"tds:Port"`
|
||||||
|
}{
|
||||||
|
Name: string(proto.Name),
|
||||||
|
Enabled: proto.Enabled,
|
||||||
|
Port: proto.Port,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetNetworkProtocols failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkDefaultGateway gets the default gateway settings from a device
|
||||||
|
func (c *Client) GetNetworkDefaultGateway(ctx context.Context) (*NetworkGateway, error) {
|
||||||
|
type GetNetworkDefaultGateway struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetNetworkDefaultGateway"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNetworkDefaultGatewayResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetNetworkDefaultGatewayResponse"`
|
||||||
|
NetworkGateway struct {
|
||||||
|
IPv4Address []string `xml:"IPv4Address"`
|
||||||
|
IPv6Address []string `xml:"IPv6Address"`
|
||||||
|
} `xml:"NetworkGateway"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetNetworkDefaultGateway{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetNetworkDefaultGatewayResponse
|
||||||
|
|
||||||
|
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("GetNetworkDefaultGateway failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NetworkGateway{
|
||||||
|
IPv4Address: resp.NetworkGateway.IPv4Address,
|
||||||
|
IPv6Address: resp.NetworkGateway.IPv6Address,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetworkDefaultGateway sets the default gateway settings on a device
|
||||||
|
func (c *Client) SetNetworkDefaultGateway(ctx context.Context, gateway *NetworkGateway) error {
|
||||||
|
type SetNetworkDefaultGateway struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetNetworkDefaultGateway"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
IPv4Address []string `xml:"tds:IPv4Address,omitempty"`
|
||||||
|
IPv6Address []string `xml:"tds:IPv6Address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetNetworkDefaultGateway{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
IPv4Address: gateway.IPv4Address,
|
||||||
|
IPv6Address: gateway.IPv6Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetNetworkDefaultGateway failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,792 @@
|
|||||||
|
package onvif
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/0x524a/onvif-go/internal/soap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetDNS sets the DNS settings on a device
|
||||||
|
func (c *Client) SetDNS(ctx context.Context, fromDHCP bool, searchDomain []string, dnsManual []IPAddress) error {
|
||||||
|
type SetDNS struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetDNS"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
FromDHCP bool `xml:"tds:FromDHCP"`
|
||||||
|
SearchDomain []string `xml:"tds:SearchDomain,omitempty"`
|
||||||
|
DNSManual []struct {
|
||||||
|
Type string `xml:"tds:Type"`
|
||||||
|
IPv4Address string `xml:"tds:IPv4Address,omitempty"`
|
||||||
|
IPv6Address string `xml:"tds:IPv6Address,omitempty"`
|
||||||
|
} `xml:"tds:DNSManual,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetDNS{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
FromDHCP: fromDHCP,
|
||||||
|
SearchDomain: searchDomain,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dns := range dnsManual {
|
||||||
|
req.DNSManual = append(req.DNSManual, struct {
|
||||||
|
Type string `xml:"tds:Type"`
|
||||||
|
IPv4Address string `xml:"tds:IPv4Address,omitempty"`
|
||||||
|
IPv6Address string `xml:"tds:IPv6Address,omitempty"`
|
||||||
|
}{
|
||||||
|
Type: dns.Type,
|
||||||
|
IPv4Address: dns.IPv4Address,
|
||||||
|
IPv6Address: dns.IPv6Address,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetDNS failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNTP sets the NTP settings on a device
|
||||||
|
func (c *Client) SetNTP(ctx context.Context, fromDHCP bool, ntpManual []NetworkHost) error {
|
||||||
|
type SetNTP struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetNTP"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
FromDHCP bool `xml:"tds:FromDHCP"`
|
||||||
|
NTPManual []struct {
|
||||||
|
Type string `xml:"tds:Type"`
|
||||||
|
IPv4Address string `xml:"tds:IPv4Address,omitempty"`
|
||||||
|
IPv6Address string `xml:"tds:IPv6Address,omitempty"`
|
||||||
|
DNSname string `xml:"tds:DNSname,omitempty"`
|
||||||
|
} `xml:"tds:NTPManual,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetNTP{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
FromDHCP: fromDHCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ntp := range ntpManual {
|
||||||
|
req.NTPManual = append(req.NTPManual, struct {
|
||||||
|
Type string `xml:"tds:Type"`
|
||||||
|
IPv4Address string `xml:"tds:IPv4Address,omitempty"`
|
||||||
|
IPv6Address string `xml:"tds:IPv6Address,omitempty"`
|
||||||
|
DNSname string `xml:"tds:DNSname,omitempty"`
|
||||||
|
}{
|
||||||
|
Type: ntp.Type,
|
||||||
|
IPv4Address: ntp.IPv4Address,
|
||||||
|
IPv6Address: ntp.IPv6Address,
|
||||||
|
DNSname: ntp.DNSname,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetNTP failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHostnameFromDHCP controls whether the hostname is set manually or retrieved via DHCP
|
||||||
|
func (c *Client) SetHostnameFromDHCP(ctx context.Context, fromDHCP bool) (bool, error) {
|
||||||
|
type SetHostnameFromDHCP struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetHostnameFromDHCP"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
FromDHCP bool `xml:"tds:FromDHCP"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetHostnameFromDHCPResponse struct {
|
||||||
|
XMLName xml.Name `xml:"SetHostnameFromDHCPResponse"`
|
||||||
|
RebootNeeded bool `xml:"RebootNeeded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetHostnameFromDHCP{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
FromDHCP: fromDHCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp SetHostnameFromDHCPResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return false, fmt.Errorf("SetHostnameFromDHCP failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.RebootNeeded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixedGetSystemDateAndTime retrieves the device's system date and time with proper typing
|
||||||
|
func (c *Client) FixedGetSystemDateAndTime(ctx context.Context) (*SystemDateTime, error) {
|
||||||
|
type GetSystemDateAndTime struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetSystemDateAndTime"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSystemDateAndTimeResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetSystemDateAndTimeResponse"`
|
||||||
|
SystemDateAndTime struct {
|
||||||
|
DateTimeType string `xml:"DateTimeType"`
|
||||||
|
DaylightSavings bool `xml:"DaylightSavings"`
|
||||||
|
TimeZone struct {
|
||||||
|
TZ string `xml:"TZ"`
|
||||||
|
} `xml:"TimeZone"`
|
||||||
|
UTCDateTime struct {
|
||||||
|
Time struct {
|
||||||
|
Hour int `xml:"Hour"`
|
||||||
|
Minute int `xml:"Minute"`
|
||||||
|
Second int `xml:"Second"`
|
||||||
|
} `xml:"Time"`
|
||||||
|
Date struct {
|
||||||
|
Year int `xml:"Year"`
|
||||||
|
Month int `xml:"Month"`
|
||||||
|
Day int `xml:"Day"`
|
||||||
|
} `xml:"Date"`
|
||||||
|
} `xml:"UTCDateTime"`
|
||||||
|
LocalDateTime struct {
|
||||||
|
Time struct {
|
||||||
|
Hour int `xml:"Hour"`
|
||||||
|
Minute int `xml:"Minute"`
|
||||||
|
Second int `xml:"Second"`
|
||||||
|
} `xml:"Time"`
|
||||||
|
Date struct {
|
||||||
|
Year int `xml:"Year"`
|
||||||
|
Month int `xml:"Month"`
|
||||||
|
Day int `xml:"Day"`
|
||||||
|
} `xml:"Date"`
|
||||||
|
} `xml:"LocalDateTime"`
|
||||||
|
} `xml:"SystemDateAndTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetSystemDateAndTime{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetSystemDateAndTimeResponse
|
||||||
|
|
||||||
|
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("GetSystemDateAndTime failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SystemDateTime{
|
||||||
|
DateTimeType: SetDateTimeType(resp.SystemDateAndTime.DateTimeType),
|
||||||
|
DaylightSavings: resp.SystemDateAndTime.DaylightSavings,
|
||||||
|
TimeZone: &TimeZone{
|
||||||
|
TZ: resp.SystemDateAndTime.TimeZone.TZ,
|
||||||
|
},
|
||||||
|
UTCDateTime: &DateTime{
|
||||||
|
Time: Time{
|
||||||
|
Hour: resp.SystemDateAndTime.UTCDateTime.Time.Hour,
|
||||||
|
Minute: resp.SystemDateAndTime.UTCDateTime.Time.Minute,
|
||||||
|
Second: resp.SystemDateAndTime.UTCDateTime.Time.Second,
|
||||||
|
},
|
||||||
|
Date: Date{
|
||||||
|
Year: resp.SystemDateAndTime.UTCDateTime.Date.Year,
|
||||||
|
Month: resp.SystemDateAndTime.UTCDateTime.Date.Month,
|
||||||
|
Day: resp.SystemDateAndTime.UTCDateTime.Date.Day,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LocalDateTime: &DateTime{
|
||||||
|
Time: Time{
|
||||||
|
Hour: resp.SystemDateAndTime.LocalDateTime.Time.Hour,
|
||||||
|
Minute: resp.SystemDateAndTime.LocalDateTime.Time.Minute,
|
||||||
|
Second: resp.SystemDateAndTime.LocalDateTime.Time.Second,
|
||||||
|
},
|
||||||
|
Date: Date{
|
||||||
|
Year: resp.SystemDateAndTime.LocalDateTime.Date.Year,
|
||||||
|
Month: resp.SystemDateAndTime.LocalDateTime.Date.Month,
|
||||||
|
Day: resp.SystemDateAndTime.LocalDateTime.Date.Day,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSystemDateAndTime sets the device system date and time
|
||||||
|
func (c *Client) SetSystemDateAndTime(ctx context.Context, dateTime *SystemDateTime) error {
|
||||||
|
type SetSystemDateAndTime struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetSystemDateAndTime"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
DateTimeType string `xml:"tds:DateTimeType"`
|
||||||
|
DaylightSavings bool `xml:"tds:DaylightSavings"`
|
||||||
|
TimeZone *struct {
|
||||||
|
TZ string `xml:"tds:TZ"`
|
||||||
|
} `xml:"tds:TimeZone,omitempty"`
|
||||||
|
UTCDateTime *struct {
|
||||||
|
Time struct {
|
||||||
|
Hour int `xml:"tt:Hour"`
|
||||||
|
Minute int `xml:"tt:Minute"`
|
||||||
|
Second int `xml:"tt:Second"`
|
||||||
|
} `xml:"tt:Time"`
|
||||||
|
Date struct {
|
||||||
|
Year int `xml:"tt:Year"`
|
||||||
|
Month int `xml:"tt:Month"`
|
||||||
|
Day int `xml:"tt:Day"`
|
||||||
|
} `xml:"tt:Date"`
|
||||||
|
} `xml:"tds:UTCDateTime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetSystemDateAndTime{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
DateTimeType: string(dateTime.DateTimeType),
|
||||||
|
DaylightSavings: dateTime.DaylightSavings,
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateTime.TimeZone != nil {
|
||||||
|
req.TimeZone = &struct {
|
||||||
|
TZ string `xml:"tds:TZ"`
|
||||||
|
}{
|
||||||
|
TZ: dateTime.TimeZone.TZ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateTime.UTCDateTime != nil {
|
||||||
|
req.UTCDateTime = &struct {
|
||||||
|
Time struct {
|
||||||
|
Hour int `xml:"tt:Hour"`
|
||||||
|
Minute int `xml:"tt:Minute"`
|
||||||
|
Second int `xml:"tt:Second"`
|
||||||
|
} `xml:"tt:Time"`
|
||||||
|
Date struct {
|
||||||
|
Year int `xml:"tt:Year"`
|
||||||
|
Month int `xml:"tt:Month"`
|
||||||
|
Day int `xml:"tt:Day"`
|
||||||
|
} `xml:"tt:Date"`
|
||||||
|
}{}
|
||||||
|
req.UTCDateTime.Time.Hour = dateTime.UTCDateTime.Time.Hour
|
||||||
|
req.UTCDateTime.Time.Minute = dateTime.UTCDateTime.Time.Minute
|
||||||
|
req.UTCDateTime.Time.Second = dateTime.UTCDateTime.Time.Second
|
||||||
|
req.UTCDateTime.Date.Year = dateTime.UTCDateTime.Date.Year
|
||||||
|
req.UTCDateTime.Date.Month = dateTime.UTCDateTime.Date.Month
|
||||||
|
req.UTCDateTime.Date.Day = dateTime.UTCDateTime.Date.Day
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetSystemDateAndTime failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddScopes adds new configurable scope parameters to a device
|
||||||
|
func (c *Client) AddScopes(ctx context.Context, scopeItems []string) error {
|
||||||
|
type AddScopes struct {
|
||||||
|
XMLName xml.Name `xml:"tds:AddScopes"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
ScopeItem []string `xml:"tds:ScopeItem"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := AddScopes{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
ScopeItem: scopeItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("AddScopes failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveScopes deletes scope-configurable scope parameters from a device
|
||||||
|
func (c *Client) RemoveScopes(ctx context.Context, scopeItems []string) ([]string, error) {
|
||||||
|
type RemoveScopes struct {
|
||||||
|
XMLName xml.Name `xml:"tds:RemoveScopes"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
ScopeItem []string `xml:"tds:ScopeItem"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoveScopesResponse struct {
|
||||||
|
XMLName xml.Name `xml:"RemoveScopesResponse"`
|
||||||
|
ScopeItem []string `xml:"ScopeItem"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := RemoveScopes{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
ScopeItem: scopeItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp RemoveScopesResponse
|
||||||
|
|
||||||
|
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("RemoveScopes failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ScopeItem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScopes sets the scope parameters of a device
|
||||||
|
func (c *Client) SetScopes(ctx context.Context, scopes []string) error {
|
||||||
|
type SetScopes struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetScopes"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
Scopes []string `xml:"tds:Scopes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetScopes{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
Scopes: scopes,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetScopes failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRelayOutputs gets a list of all available relay outputs and their settings
|
||||||
|
func (c *Client) GetRelayOutputs(ctx context.Context) ([]*RelayOutput, error) {
|
||||||
|
type GetRelayOutputs struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetRelayOutputs"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRelayOutputsResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetRelayOutputsResponse"`
|
||||||
|
RelayOutputs []struct {
|
||||||
|
Token string `xml:"token,attr"`
|
||||||
|
Properties struct {
|
||||||
|
Mode string `xml:"Mode"`
|
||||||
|
DelayTime string `xml:"DelayTime"`
|
||||||
|
IdleState string `xml:"IdleState"`
|
||||||
|
} `xml:"Properties"`
|
||||||
|
} `xml:"RelayOutputs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetRelayOutputs{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetRelayOutputsResponse
|
||||||
|
|
||||||
|
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("GetRelayOutputs failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
relays := make([]*RelayOutput, len(resp.RelayOutputs))
|
||||||
|
for i, relay := range resp.RelayOutputs {
|
||||||
|
relays[i] = &RelayOutput{
|
||||||
|
Token: relay.Token,
|
||||||
|
Properties: RelayOutputSettings{
|
||||||
|
Mode: RelayMode(relay.Properties.Mode),
|
||||||
|
IdleState: RelayIdleState(relay.Properties.IdleState),
|
||||||
|
// DelayTime parsing would require duration parsing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return relays, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRelayOutputSettings sets the settings of a relay output
|
||||||
|
func (c *Client) SetRelayOutputSettings(ctx context.Context, token string, settings *RelayOutputSettings) error {
|
||||||
|
type SetRelayOutputSettings struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetRelayOutputSettings"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
RelayOutputToken string `xml:"tds:RelayOutputToken"`
|
||||||
|
Properties struct {
|
||||||
|
Mode string `xml:"tt:Mode"`
|
||||||
|
DelayTime string `xml:"tt:DelayTime"`
|
||||||
|
IdleState string `xml:"tt:IdleState"`
|
||||||
|
} `xml:"tds:Properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetRelayOutputSettings{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
RelayOutputToken: token,
|
||||||
|
}
|
||||||
|
req.Properties.Mode = string(settings.Mode)
|
||||||
|
req.Properties.IdleState = string(settings.IdleState)
|
||||||
|
// DelayTime would need duration formatting
|
||||||
|
|
||||||
|
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("SetRelayOutputSettings failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRelayOutputState sets the state of a relay output
|
||||||
|
func (c *Client) SetRelayOutputState(ctx context.Context, token string, state RelayLogicalState) error {
|
||||||
|
type SetRelayOutputState struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetRelayOutputState"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
RelayOutputToken string `xml:"tds:RelayOutputToken"`
|
||||||
|
LogicalState RelayLogicalState `xml:"tds:LogicalState"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetRelayOutputState{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
RelayOutputToken: token,
|
||||||
|
LogicalState: state,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetRelayOutputState failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendAuxiliaryCommand sends an auxiliary command to the device
|
||||||
|
func (c *Client) SendAuxiliaryCommand(ctx context.Context, command AuxiliaryData) (AuxiliaryData, error) {
|
||||||
|
type SendAuxiliaryCommand struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SendAuxiliaryCommand"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
AuxiliaryCommand AuxiliaryData `xml:"tds:AuxiliaryCommand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendAuxiliaryCommandResponse struct {
|
||||||
|
XMLName xml.Name `xml:"SendAuxiliaryCommandResponse"`
|
||||||
|
AuxiliaryCommandResponse AuxiliaryData `xml:"AuxiliaryCommandResponse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SendAuxiliaryCommand{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
AuxiliaryCommand: command,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp SendAuxiliaryCommandResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", fmt.Errorf("SendAuxiliaryCommand failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.AuxiliaryCommandResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemLog gets a system log from the device
|
||||||
|
func (c *Client) GetSystemLog(ctx context.Context, logType SystemLogType) (*SystemLog, error) {
|
||||||
|
type GetSystemLog struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetSystemLog"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
LogType SystemLogType `xml:"tds:LogType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSystemLogResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetSystemLogResponse"`
|
||||||
|
SystemLog struct {
|
||||||
|
Binary *struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
} `xml:"Binary"`
|
||||||
|
String string `xml:"String"`
|
||||||
|
} `xml:"SystemLog"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetSystemLog{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
LogType: logType,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetSystemLogResponse
|
||||||
|
|
||||||
|
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("GetSystemLog failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
systemLog := &SystemLog{
|
||||||
|
String: resp.SystemLog.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.SystemLog.Binary != nil {
|
||||||
|
systemLog.Binary = &AttachmentData{
|
||||||
|
ContentType: resp.SystemLog.Binary.ContentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return systemLog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemBackup retrieves system backup configuration files from a device
|
||||||
|
func (c *Client) GetSystemBackup(ctx context.Context) ([]*BackupFile, error) {
|
||||||
|
type GetSystemBackup struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetSystemBackup"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSystemBackupResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetSystemBackupResponse"`
|
||||||
|
BackupFiles []struct {
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
Data struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
} `xml:"Data"`
|
||||||
|
} `xml:"BackupFiles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetSystemBackup{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetSystemBackupResponse
|
||||||
|
|
||||||
|
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("GetSystemBackup failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
backups := make([]*BackupFile, len(resp.BackupFiles))
|
||||||
|
for i, file := range resp.BackupFiles {
|
||||||
|
backups[i] = &BackupFile{
|
||||||
|
Name: file.Name,
|
||||||
|
Data: AttachmentData{
|
||||||
|
ContentType: file.Data.ContentType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return backups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreSystem restores the system backup configuration files
|
||||||
|
func (c *Client) RestoreSystem(ctx context.Context, backupFiles []*BackupFile) error {
|
||||||
|
type RestoreSystem struct {
|
||||||
|
XMLName xml.Name `xml:"tds:RestoreSystem"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
BackupFiles []struct {
|
||||||
|
Name string `xml:"tds:Name"`
|
||||||
|
Data struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
} `xml:"tds:Data"`
|
||||||
|
} `xml:"tds:BackupFiles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := RestoreSystem{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range backupFiles {
|
||||||
|
req.BackupFiles = append(req.BackupFiles, struct {
|
||||||
|
Name string `xml:"tds:Name"`
|
||||||
|
Data struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
} `xml:"tds:Data"`
|
||||||
|
}{
|
||||||
|
Name: file.Name,
|
||||||
|
Data: struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
}{
|
||||||
|
ContentType: file.Data.ContentType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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("RestoreSystem failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemUris retrieves URIs from which system information may be downloaded
|
||||||
|
func (c *Client) GetSystemUris(ctx context.Context) (*SystemLogUriList, string, string, error) {
|
||||||
|
type GetSystemUris struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetSystemUris"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSystemUrisResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetSystemUrisResponse"`
|
||||||
|
SystemLogUris *struct {
|
||||||
|
SystemLog []struct {
|
||||||
|
Type string `xml:"Type"`
|
||||||
|
Uri string `xml:"Uri"`
|
||||||
|
} `xml:"SystemLog"`
|
||||||
|
} `xml:"SystemLogUris"`
|
||||||
|
SupportInfoUri string `xml:"SupportInfoUri"`
|
||||||
|
SystemBackupUri string `xml:"SystemBackupUri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetSystemUris{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetSystemUrisResponse
|
||||||
|
|
||||||
|
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("GetSystemUris failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var logUris *SystemLogUriList
|
||||||
|
if resp.SystemLogUris != nil {
|
||||||
|
logUris = &SystemLogUriList{}
|
||||||
|
for _, log := range resp.SystemLogUris.SystemLog {
|
||||||
|
logUris.SystemLog = append(logUris.SystemLog, SystemLogUri{
|
||||||
|
Type: SystemLogType(log.Type),
|
||||||
|
Uri: log.Uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return logUris, resp.SupportInfoUri, resp.SystemBackupUri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemSupportInformation gets arbitrary device diagnostics information
|
||||||
|
func (c *Client) GetSystemSupportInformation(ctx context.Context) (*SupportInformation, error) {
|
||||||
|
type GetSystemSupportInformation struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetSystemSupportInformation"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSystemSupportInformationResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetSystemSupportInformationResponse"`
|
||||||
|
SupportInformation struct {
|
||||||
|
Binary *struct {
|
||||||
|
ContentType string `xml:"contentType,attr"`
|
||||||
|
} `xml:"Binary"`
|
||||||
|
String string `xml:"String"`
|
||||||
|
} `xml:"SupportInformation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetSystemSupportInformation{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp GetSystemSupportInformationResponse
|
||||||
|
|
||||||
|
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("GetSystemSupportInformation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &SupportInformation{
|
||||||
|
String: resp.SupportInformation.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.SupportInformation.Binary != nil {
|
||||||
|
info.Binary = &AttachmentData{
|
||||||
|
ContentType: resp.SupportInformation.Binary.ContentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSystemFactoryDefault reloads the parameters on the device to their factory default values
|
||||||
|
func (c *Client) SetSystemFactoryDefault(ctx context.Context, factoryDefault FactoryDefaultType) error {
|
||||||
|
type SetSystemFactoryDefault struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetSystemFactoryDefault"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
FactoryDefault FactoryDefaultType `xml:"tds:FactoryDefault"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetSystemFactoryDefault{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
FactoryDefault: factoryDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
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("SetSystemFactoryDefault failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartFirmwareUpgrade initiates a firmware upgrade using the HTTP POST mechanism
|
||||||
|
func (c *Client) StartFirmwareUpgrade(ctx context.Context) (string, string, string, error) {
|
||||||
|
type StartFirmwareUpgrade struct {
|
||||||
|
XMLName xml.Name `xml:"tds:StartFirmwareUpgrade"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartFirmwareUpgradeResponse struct {
|
||||||
|
XMLName xml.Name `xml:"StartFirmwareUpgradeResponse"`
|
||||||
|
UploadUri string `xml:"UploadUri"`
|
||||||
|
UploadDelay string `xml:"UploadDelay"`
|
||||||
|
ExpectedDownTime string `xml:"ExpectedDownTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := StartFirmwareUpgrade{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp StartFirmwareUpgradeResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("StartFirmwareUpgrade failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.UploadUri, resp.UploadDelay, resp.ExpectedDownTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSystemRestore initiates a system restore from backed up configuration data
|
||||||
|
func (c *Client) StartSystemRestore(ctx context.Context) (string, string, error) {
|
||||||
|
type StartSystemRestore struct {
|
||||||
|
XMLName xml.Name `xml:"tds:StartSystemRestore"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartSystemRestoreResponse struct {
|
||||||
|
XMLName xml.Name `xml:"StartSystemRestoreResponse"`
|
||||||
|
UploadUri string `xml:"UploadUri"`
|
||||||
|
ExpectedDownTime string `xml:"ExpectedDownTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := StartSystemRestore{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp StartSystemRestoreResponse
|
||||||
|
|
||||||
|
username, password := c.GetCredentials()
|
||||||
|
soapClient := soap.NewClient(c.httpClient, username, password)
|
||||||
|
|
||||||
|
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
|
||||||
|
return "", "", fmt.Errorf("StartSystemRestore failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.UploadUri, resp.ExpectedDownTime, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
package onvif
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMockDeviceExtendedServer() *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
decoder := xml.NewDecoder(r.Body)
|
||||||
|
var envelope struct {
|
||||||
|
Body struct {
|
||||||
|
Content []byte `xml:",innerxml"`
|
||||||
|
} `xml:"Body"`
|
||||||
|
}
|
||||||
|
decoder.Decode(&envelope)
|
||||||
|
bodyContent := string(envelope.Body.Content)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/soap+xml")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(bodyContent, "AddScopes"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:AddScopesResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "RemoveScopes"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:RemoveScopesResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:ScopeItem>onvif://www.onvif.org/location/test</tds:ScopeItem>
|
||||||
|
</tds:RemoveScopesResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetScopes"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetScopesResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetRelayOutputs"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetRelayOutputsResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:RelayOutputs token="relay1">
|
||||||
|
<tt:Properties>
|
||||||
|
<tt:Mode>Bistable</tt:Mode>
|
||||||
|
<tt:DelayTime>PT0S</tt:DelayTime>
|
||||||
|
<tt:IdleState>closed</tt:IdleState>
|
||||||
|
</tt:Properties>
|
||||||
|
</tds:RelayOutputs>
|
||||||
|
</tds:GetRelayOutputsResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetRelayOutputSettings"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetRelayOutputSettingsResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetRelayOutputState"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetRelayOutputStateResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SendAuxiliaryCommand"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SendAuxiliaryCommandResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:AuxiliaryCommandResponse>tt:IRLamp|On</tds:AuxiliaryCommandResponse>
|
||||||
|
</tds:SendAuxiliaryCommandResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetSystemLog"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetSystemLogResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:SystemLog>
|
||||||
|
<tt:String>System log content here</tt:String>
|
||||||
|
</tds:SystemLog>
|
||||||
|
</tds:GetSystemLogResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetSystemFactoryDefault"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetSystemFactoryDefaultResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "StartFirmwareUpgrade"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:StartFirmwareUpgradeResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:UploadUri>http://192.168.1.100/upload</tds:UploadUri>
|
||||||
|
<tds:UploadDelay>PT5S</tds:UploadDelay>
|
||||||
|
<tds:ExpectedDownTime>PT60S</tds:ExpectedDownTime>
|
||||||
|
</tds:StartFirmwareUpgradeResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddScopes(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
scopes := []string{
|
||||||
|
"onvif://www.onvif.org/location/building/floor1",
|
||||||
|
"onvif://www.onvif.org/name/camera-entrance",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.AddScopes(ctx, scopes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddScopes failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveScopes(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
scopes := []string{"onvif://www.onvif.org/location/test"}
|
||||||
|
|
||||||
|
removed, err := client.RemoveScopes(ctx, scopes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RemoveScopes failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(removed) != 1 {
|
||||||
|
t.Fatalf("Expected 1 removed scope, got %d", len(removed))
|
||||||
|
}
|
||||||
|
|
||||||
|
if removed[0] != "onvif://www.onvif.org/location/test" {
|
||||||
|
t.Errorf("Expected removed scope to match, got %s", removed[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetScopes(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
scopes := []string{"scope1", "scope2"}
|
||||||
|
|
||||||
|
err = client.SetScopes(ctx, scopes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetScopes failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRelayOutputs(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
relays, err := client.GetRelayOutputs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRelayOutputs failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(relays) != 1 {
|
||||||
|
t.Fatalf("Expected 1 relay, got %d", len(relays))
|
||||||
|
}
|
||||||
|
|
||||||
|
if relays[0].Token != "relay1" {
|
||||||
|
t.Errorf("Expected relay token 'relay1', got %s", relays[0].Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if relays[0].Properties.Mode != RelayModeBistable {
|
||||||
|
t.Errorf("Expected Bistable mode, got %s", relays[0].Properties.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if relays[0].Properties.IdleState != RelayIdleStateClosed {
|
||||||
|
t.Errorf("Expected closed idle state, got %s", relays[0].Properties.IdleState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetRelayOutputSettings(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
settings := &RelayOutputSettings{
|
||||||
|
Mode: RelayModeBistable,
|
||||||
|
IdleState: RelayIdleStateClosed,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetRelayOutputSettings(ctx, "relay1", settings)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetRelayOutputSettings failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetRelayOutputState(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test active state
|
||||||
|
err = client.SetRelayOutputState(ctx, "relay1", RelayLogicalStateActive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetRelayOutputState (active) failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test inactive state
|
||||||
|
err = client.SetRelayOutputState(ctx, "relay1", RelayLogicalStateInactive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetRelayOutputState (inactive) failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendAuxiliaryCommand(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
response, err := client.SendAuxiliaryCommand(ctx, "tt:IRLamp|On")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendAuxiliaryCommand failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response != "tt:IRLamp|On" {
|
||||||
|
t.Errorf("Expected response 'tt:IRLamp|On', got %s", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSystemLog(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
log, err := client.GetSystemLog(ctx, SystemLogTypeSystem)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetSystemLog failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.String != "System log content here" {
|
||||||
|
t.Errorf("Expected system log content, got %s", log.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetSystemFactoryDefault(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test soft reset
|
||||||
|
err = client.SetSystemFactoryDefault(ctx, FactoryDefaultSoft)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetSystemFactoryDefault (soft) failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test hard reset
|
||||||
|
err = client.SetSystemFactoryDefault(ctx, FactoryDefaultHard)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetSystemFactoryDefault (hard) failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartFirmwareUpgrade(t *testing.T) {
|
||||||
|
server := newMockDeviceExtendedServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
uploadUri, delay, downtime, err := client.StartFirmwareUpgrade(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("StartFirmwareUpgrade failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uploadUri != "http://192.168.1.100/upload" {
|
||||||
|
t.Errorf("Expected upload URI http://192.168.1.100/upload, got %s", uploadUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay != "PT5S" {
|
||||||
|
t.Errorf("Expected delay PT5S, got %s", delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if downtime != "PT60S" {
|
||||||
|
t.Errorf("Expected downtime PT60S, got %s", downtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRelayModeConstants(t *testing.T) {
|
||||||
|
if RelayModeMonostable != "Monostable" {
|
||||||
|
t.Errorf("RelayModeMonostable should be 'Monostable', got %s", RelayModeMonostable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if RelayModeBistable != "Bistable" {
|
||||||
|
t.Errorf("RelayModeBistable should be 'Bistable', got %s", RelayModeBistable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRelayIdleStateConstants(t *testing.T) {
|
||||||
|
if RelayIdleStateClosed != "closed" {
|
||||||
|
t.Errorf("RelayIdleStateClosed should be 'closed', got %s", RelayIdleStateClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if RelayIdleStateOpen != "open" {
|
||||||
|
t.Errorf("RelayIdleStateOpen should be 'open', got %s", RelayIdleStateOpen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRelayLogicalStateConstants(t *testing.T) {
|
||||||
|
if RelayLogicalStateActive != "active" {
|
||||||
|
t.Errorf("RelayLogicalStateActive should be 'active', got %s", RelayLogicalStateActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
if RelayLogicalStateInactive != "inactive" {
|
||||||
|
t.Errorf("RelayLogicalStateInactive should be 'inactive', got %s", RelayLogicalStateInactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemLogTypeConstants(t *testing.T) {
|
||||||
|
if SystemLogTypeSystem != "System" {
|
||||||
|
t.Errorf("SystemLogTypeSystem should be 'System', got %s", SystemLogTypeSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if SystemLogTypeAccess != "Access" {
|
||||||
|
t.Errorf("SystemLogTypeAccess should be 'Access', got %s", SystemLogTypeAccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFactoryDefaultTypeConstants(t *testing.T) {
|
||||||
|
if FactoryDefaultHard != "Hard" {
|
||||||
|
t.Errorf("FactoryDefaultHard should be 'Hard', got %s", FactoryDefaultHard)
|
||||||
|
}
|
||||||
|
|
||||||
|
if FactoryDefaultSoft != "Soft" {
|
||||||
|
t.Errorf("FactoryDefaultSoft should be 'Soft', got %s", FactoryDefaultSoft)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,615 @@
|
|||||||
|
package onvif
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/0x524a/onvif-go/internal/soap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRemoteUser returns the configured remote user
|
||||||
|
func (c *Client) GetRemoteUser(ctx context.Context) (*RemoteUser, error) {
|
||||||
|
type GetRemoteUser struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetRemoteUser"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRemoteUserResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetRemoteUserResponse"`
|
||||||
|
RemoteUser *struct {
|
||||||
|
Username string `xml:"Username"`
|
||||||
|
Password string `xml:"Password"`
|
||||||
|
UseDerivedPassword bool `xml:"UseDerivedPassword"`
|
||||||
|
} `xml:"RemoteUser"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetRemoteUser{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetRemoteUser failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.RemoteUser == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RemoteUser{
|
||||||
|
Username: resp.RemoteUser.Username,
|
||||||
|
Password: resp.RemoteUser.Password,
|
||||||
|
UseDerivedPassword: resp.RemoteUser.UseDerivedPassword,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetRemoteUser{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
if remoteUser != nil {
|
||||||
|
req.RemoteUser = &struct {
|
||||||
|
Username string `xml:"tds:Username"`
|
||||||
|
Password string `xml:"tds:Password,omitempty"`
|
||||||
|
UseDerivedPassword bool `xml:"tds:UseDerivedPassword"`
|
||||||
|
}{
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetRemoteUser failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPAddressFilter gets the IP address filter settings from a device
|
||||||
|
func (c *Client) GetIPAddressFilter(ctx context.Context) (*IPAddressFilter, error) {
|
||||||
|
type GetIPAddressFilter struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetIPAddressFilter"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
} `xml:"IPAddressFilter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetIPAddressFilter{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetIPAddressFilter failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &IPAddressFilter{
|
||||||
|
Type: IPAddressFilterType(resp.IPAddressFilter.Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetIPAddressFilter failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("AddIPAddressFilter failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("RemoveIPAddressFilter failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetZeroConfiguration gets the zero-configuration from a device
|
||||||
|
func (c *Client) GetZeroConfiguration(ctx context.Context) (*NetworkZeroConfiguration, error) {
|
||||||
|
type GetZeroConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetZeroConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetZeroConfigurationResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetZeroConfigurationResponse"`
|
||||||
|
ZeroConfiguration struct {
|
||||||
|
InterfaceToken string `xml:"InterfaceToken"`
|
||||||
|
Enabled bool `xml:"Enabled"`
|
||||||
|
Addresses []string `xml:"Addresses"`
|
||||||
|
} `xml:"ZeroConfiguration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetZeroConfiguration{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetZeroConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NetworkZeroConfiguration{
|
||||||
|
InterfaceToken: resp.ZeroConfiguration.InterfaceToken,
|
||||||
|
Enabled: resp.ZeroConfiguration.Enabled,
|
||||||
|
Addresses: resp.ZeroConfiguration.Addresses,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetZeroConfiguration sets the zero-configuration
|
||||||
|
func (c *Client) SetZeroConfiguration(ctx context.Context, interfaceToken string, enabled bool) error {
|
||||||
|
type SetZeroConfiguration 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{
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetZeroConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDynamicDNS gets the dynamic DNS settings from a device
|
||||||
|
func (c *Client) GetDynamicDNS(ctx context.Context) (*DynamicDNSInformation, error) {
|
||||||
|
type GetDynamicDNS struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetDynamicDNS"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDynamicDNSResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetDynamicDNSResponse"`
|
||||||
|
DynamicDNSInformation struct {
|
||||||
|
Type string `xml:"Type"`
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
TTL string `xml:"TTL"`
|
||||||
|
} `xml:"DynamicDNSInformation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetDynamicDNS{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetDynamicDNS failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DynamicDNSInformation{
|
||||||
|
Type: DynamicDNSType(resp.DynamicDNSInformation.Type),
|
||||||
|
Name: resp.DynamicDNSInformation.Name,
|
||||||
|
// TTL would need duration parsing
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDynamicDNS sets the dynamic DNS settings on a device
|
||||||
|
func (c *Client) SetDynamicDNS(ctx context.Context, dnsType DynamicDNSType, name string) error {
|
||||||
|
type SetDynamicDNS 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{
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetDynamicDNS failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswordComplexityConfiguration retrieves the current password complexity configuration settings
|
||||||
|
func (c *Client) GetPasswordComplexityConfiguration(ctx context.Context) (*PasswordComplexityConfiguration, error) {
|
||||||
|
type GetPasswordComplexityConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetPasswordComplexityConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPasswordComplexityConfigurationResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetPasswordComplexityConfigurationResponse"`
|
||||||
|
MinLen int `xml:"MinLen"`
|
||||||
|
Uppercase int `xml:"Uppercase"`
|
||||||
|
Number int `xml:"Number"`
|
||||||
|
SpecialChars int `xml:"SpecialChars"`
|
||||||
|
BlockUsernameOccurrence bool `xml:"BlockUsernameOccurrence"`
|
||||||
|
PolicyConfigurationLocked bool `xml:"PolicyConfigurationLocked"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetPasswordComplexityConfiguration{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetPasswordComplexityConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PasswordComplexityConfiguration{
|
||||||
|
MinLen: resp.MinLen,
|
||||||
|
Uppercase: resp.Uppercase,
|
||||||
|
Number: resp.Number,
|
||||||
|
SpecialChars: resp.SpecialChars,
|
||||||
|
BlockUsernameOccurrence: resp.BlockUsernameOccurrence,
|
||||||
|
PolicyConfigurationLocked: resp.PolicyConfigurationLocked,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPasswordComplexityConfiguration allows setting of the password complexity configuration
|
||||||
|
func (c *Client) SetPasswordComplexityConfiguration(ctx context.Context, config *PasswordComplexityConfiguration) error {
|
||||||
|
type SetPasswordComplexityConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetPasswordComplexityConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
MinLen int `xml:"tds:MinLen,omitempty"`
|
||||||
|
Uppercase int `xml:"tds:Uppercase,omitempty"`
|
||||||
|
Number int `xml:"tds:Number,omitempty"`
|
||||||
|
SpecialChars int `xml:"tds:SpecialChars,omitempty"`
|
||||||
|
BlockUsernameOccurrence bool `xml:"tds:BlockUsernameOccurrence,omitempty"`
|
||||||
|
PolicyConfigurationLocked bool `xml:"tds:PolicyConfigurationLocked,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetPasswordComplexityConfiguration{
|
||||||
|
Xmlns: deviceNamespace,
|
||||||
|
MinLen: config.MinLen,
|
||||||
|
Uppercase: config.Uppercase,
|
||||||
|
Number: config.Number,
|
||||||
|
SpecialChars: config.SpecialChars,
|
||||||
|
BlockUsernameOccurrence: config.BlockUsernameOccurrence,
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetPasswordComplexityConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswordHistoryConfiguration retrieves the current password history configuration settings
|
||||||
|
func (c *Client) GetPasswordHistoryConfiguration(ctx context.Context) (*PasswordHistoryConfiguration, error) {
|
||||||
|
type GetPasswordHistoryConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetPasswordHistoryConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPasswordHistoryConfigurationResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetPasswordHistoryConfigurationResponse"`
|
||||||
|
Enabled bool `xml:"Enabled"`
|
||||||
|
Length int `xml:"Length"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetPasswordHistoryConfiguration{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetPasswordHistoryConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PasswordHistoryConfiguration{
|
||||||
|
Enabled: resp.Enabled,
|
||||||
|
Length: resp.Length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPasswordHistoryConfiguration allows setting of the password history configuration
|
||||||
|
func (c *Client) SetPasswordHistoryConfiguration(ctx context.Context, config *PasswordHistoryConfiguration) error {
|
||||||
|
type SetPasswordHistoryConfiguration 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{
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetPasswordHistoryConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthFailureWarningConfiguration retrieves the current authentication failure warning configuration
|
||||||
|
func (c *Client) GetAuthFailureWarningConfiguration(ctx context.Context) (*AuthFailureWarningConfiguration, error) {
|
||||||
|
type GetAuthFailureWarningConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:GetAuthFailureWarningConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAuthFailureWarningConfigurationResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetAuthFailureWarningConfigurationResponse"`
|
||||||
|
Enabled bool `xml:"Enabled"`
|
||||||
|
MonitorPeriod int `xml:"MonitorPeriod"`
|
||||||
|
MaxAuthFailures int `xml:"MaxAuthFailures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := GetAuthFailureWarningConfiguration{
|
||||||
|
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 {
|
||||||
|
return nil, fmt.Errorf("GetAuthFailureWarningConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AuthFailureWarningConfiguration{
|
||||||
|
Enabled: resp.Enabled,
|
||||||
|
MonitorPeriod: resp.MonitorPeriod,
|
||||||
|
MaxAuthFailures: resp.MaxAuthFailures,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthFailureWarningConfiguration allows setting of the authentication failure warning configuration
|
||||||
|
func (c *Client) SetAuthFailureWarningConfiguration(ctx context.Context, config *AuthFailureWarningConfiguration) error {
|
||||||
|
type SetAuthFailureWarningConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"tds:SetAuthFailureWarningConfiguration"`
|
||||||
|
Xmlns string `xml:"xmlns:tds,attr"`
|
||||||
|
Enabled bool `xml:"tds:Enabled"`
|
||||||
|
MonitorPeriod int `xml:"tds:MonitorPeriod"`
|
||||||
|
MaxAuthFailures int `xml:"tds:MaxAuthFailures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
req := SetAuthFailureWarningConfiguration{
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("SetAuthFailureWarningConfiguration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
package onvif
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMockDeviceSecurityServer() *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
decoder := xml.NewDecoder(r.Body)
|
||||||
|
var envelope struct {
|
||||||
|
Body struct {
|
||||||
|
Content []byte `xml:",innerxml"`
|
||||||
|
} `xml:"Body"`
|
||||||
|
}
|
||||||
|
decoder.Decode(&envelope)
|
||||||
|
bodyContent := string(envelope.Body.Content)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/soap+xml")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(bodyContent, "GetRemoteUser"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetRemoteUserResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:RemoteUser>
|
||||||
|
<tt:Username>remote_admin</tt:Username>
|
||||||
|
<tt:Password></tt:Password>
|
||||||
|
<tt:UseDerivedPassword>true</tt:UseDerivedPassword>
|
||||||
|
</tds:RemoteUser>
|
||||||
|
</tds:GetRemoteUserResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetRemoteUser"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetRemoteUserResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetIPAddressFilter"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetIPAddressFilterResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:IPAddressFilter>
|
||||||
|
<tt:Type>Allow</tt:Type>
|
||||||
|
<tt:IPv4Address>
|
||||||
|
<tt:Address>192.168.1.0</tt:Address>
|
||||||
|
<tt:PrefixLength>24</tt:PrefixLength>
|
||||||
|
</tt:IPv4Address>
|
||||||
|
</tds:IPAddressFilter>
|
||||||
|
</tds:GetIPAddressFilterResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetIPAddressFilter"),
|
||||||
|
strings.Contains(bodyContent, "AddIPAddressFilter"),
|
||||||
|
strings.Contains(bodyContent, "RemoveIPAddressFilter"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetIPAddressFilterResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetZeroConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetZeroConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:ZeroConfiguration>
|
||||||
|
<tt:InterfaceToken>eth0</tt:InterfaceToken>
|
||||||
|
<tt:Enabled>true</tt:Enabled>
|
||||||
|
<tt:Addresses>169.254.1.100</tt:Addresses>
|
||||||
|
</tds:ZeroConfiguration>
|
||||||
|
</tds:GetZeroConfigurationResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetZeroConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetZeroConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetPasswordComplexityConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetPasswordComplexityConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:MinLen>8</tds:MinLen>
|
||||||
|
<tds:Uppercase>1</tds:Uppercase>
|
||||||
|
<tds:Number>1</tds:Number>
|
||||||
|
<tds:SpecialChars>1</tds:SpecialChars>
|
||||||
|
<tds:BlockUsernameOccurrence>true</tds:BlockUsernameOccurrence>
|
||||||
|
<tds:PolicyConfigurationLocked>false</tds:PolicyConfigurationLocked>
|
||||||
|
</tds:GetPasswordComplexityConfigurationResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetPasswordComplexityConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetPasswordComplexityConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetPasswordHistoryConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetPasswordHistoryConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:Enabled>true</tds:Enabled>
|
||||||
|
<tds:Length>5</tds:Length>
|
||||||
|
</tds:GetPasswordHistoryConfigurationResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetPasswordHistoryConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetPasswordHistoryConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "GetAuthFailureWarningConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetAuthFailureWarningConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:Enabled>true</tds:Enabled>
|
||||||
|
<tds:MonitorPeriod>60</tds:MonitorPeriod>
|
||||||
|
<tds:MaxAuthFailures>5</tds:MaxAuthFailures>
|
||||||
|
</tds:GetAuthFailureWarningConfigurationResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
case strings.Contains(bodyContent, "SetAuthFailureWarningConfiguration"):
|
||||||
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetAuthFailureWarningConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`))
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRemoteUser(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
remoteUser, err := client.GetRemoteUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRemoteUser failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remoteUser.Username != "remote_admin" {
|
||||||
|
t.Errorf("Expected username 'remote_admin', got %s", remoteUser.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !remoteUser.UseDerivedPassword {
|
||||||
|
t.Error("UseDerivedPassword should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetRemoteUser(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
remoteUser := &RemoteUser{
|
||||||
|
Username: "new_remote",
|
||||||
|
Password: "password123",
|
||||||
|
UseDerivedPassword: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetRemoteUser(ctx, remoteUser)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetRemoteUser failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetIPAddressFilter(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
filter, err := client.GetIPAddressFilter(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetIPAddressFilter failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Type != IPAddressFilterAllow {
|
||||||
|
t.Errorf("Expected Allow filter type, got %s", filter.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filter.IPv4Address) != 1 {
|
||||||
|
t.Fatalf("Expected 1 IPv4 address, got %d", len(filter.IPv4Address))
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.IPv4Address[0].Address != "192.168.1.0" {
|
||||||
|
t.Errorf("Expected address 192.168.1.0, got %s", filter.IPv4Address[0].Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.IPv4Address[0].PrefixLength != 24 {
|
||||||
|
t.Errorf("Expected prefix length 24, got %d", filter.IPv4Address[0].PrefixLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetIPAddressFilter(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
filter := &IPAddressFilter{
|
||||||
|
Type: IPAddressFilterAllow,
|
||||||
|
IPv4Address: []PrefixedIPv4Address{
|
||||||
|
{Address: "10.0.0.0", PrefixLength: 8},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetIPAddressFilter(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetIPAddressFilter failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddIPAddressFilter(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
filter := &IPAddressFilter{
|
||||||
|
Type: IPAddressFilterAllow,
|
||||||
|
IPv4Address: []PrefixedIPv4Address{
|
||||||
|
{Address: "172.16.0.0", PrefixLength: 12},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.AddIPAddressFilter(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddIPAddressFilter failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveIPAddressFilter(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
filter := &IPAddressFilter{
|
||||||
|
Type: IPAddressFilterAllow,
|
||||||
|
IPv4Address: []PrefixedIPv4Address{
|
||||||
|
{Address: "172.16.0.0", PrefixLength: 12},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.RemoveIPAddressFilter(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RemoveIPAddressFilter failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetZeroConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
zeroConf, err := client.GetZeroConfiguration(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetZeroConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if zeroConf.InterfaceToken != "eth0" {
|
||||||
|
t.Errorf("Expected interface token 'eth0', got %s", zeroConf.InterfaceToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !zeroConf.Enabled {
|
||||||
|
t.Error("Zero configuration should be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(zeroConf.Addresses) != 1 || zeroConf.Addresses[0] != "169.254.1.100" {
|
||||||
|
t.Errorf("Expected address 169.254.1.100, got %v", zeroConf.Addresses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetZeroConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err = client.SetZeroConfiguration(ctx, "eth0", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetZeroConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPasswordComplexityConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config, err := client.GetPasswordComplexityConfiguration(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetPasswordComplexityConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MinLen != 8 {
|
||||||
|
t.Errorf("Expected MinLen 8, got %d", config.MinLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Uppercase != 1 {
|
||||||
|
t.Errorf("Expected Uppercase 1, got %d", config.Uppercase)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Number != 1 {
|
||||||
|
t.Errorf("Expected Number 1, got %d", config.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SpecialChars != 1 {
|
||||||
|
t.Errorf("Expected SpecialChars 1, got %d", config.SpecialChars)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.BlockUsernameOccurrence {
|
||||||
|
t.Error("BlockUsernameOccurrence should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PolicyConfigurationLocked {
|
||||||
|
t.Error("PolicyConfigurationLocked should be false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPasswordComplexityConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config := &PasswordComplexityConfiguration{
|
||||||
|
MinLen: 10,
|
||||||
|
Uppercase: 2,
|
||||||
|
Number: 2,
|
||||||
|
SpecialChars: 1,
|
||||||
|
BlockUsernameOccurrence: true,
|
||||||
|
PolicyConfigurationLocked: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetPasswordComplexityConfiguration(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetPasswordComplexityConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPasswordHistoryConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config, err := client.GetPasswordHistoryConfiguration(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetPasswordHistoryConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Enabled {
|
||||||
|
t.Error("Password history should be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Length != 5 {
|
||||||
|
t.Errorf("Expected Length 5, got %d", config.Length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPasswordHistoryConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config := &PasswordHistoryConfiguration{
|
||||||
|
Enabled: true,
|
||||||
|
Length: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetPasswordHistoryConfiguration(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetPasswordHistoryConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAuthFailureWarningConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config, err := client.GetAuthFailureWarningConfiguration(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetAuthFailureWarningConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Enabled {
|
||||||
|
t.Error("Auth failure warning should be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MonitorPeriod != 60 {
|
||||||
|
t.Errorf("Expected MonitorPeriod 60, got %d", config.MonitorPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MaxAuthFailures != 5 {
|
||||||
|
t.Errorf("Expected MaxAuthFailures 5, got %d", config.MaxAuthFailures)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetAuthFailureWarningConfiguration(t *testing.T) {
|
||||||
|
server := newMockDeviceSecurityServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config := &AuthFailureWarningConfiguration{
|
||||||
|
Enabled: true,
|
||||||
|
MonitorPeriod: 120,
|
||||||
|
MaxAuthFailures: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetAuthFailureWarningConfiguration(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetAuthFailureWarningConfiguration failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPAddressFilterTypeConstants(t *testing.T) {
|
||||||
|
if IPAddressFilterAllow != "Allow" {
|
||||||
|
t.Errorf("IPAddressFilterAllow should be 'Allow', got %s", IPAddressFilterAllow)
|
||||||
|
}
|
||||||
|
|
||||||
|
if IPAddressFilterDeny != "Deny" {
|
||||||
|
t.Errorf("IPAddressFilterDeny should be 'Deny', got %s", IPAddressFilterDeny)
|
||||||
|
}
|
||||||
|
}
|
||||||
+291
@@ -391,6 +391,297 @@ func TestGetNetworkInterfaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetServices(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetServicesResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:Service>
|
||||||
|
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
|
||||||
|
<tds:XAddr>http://192.168.1.100/onvif/device_service</tds:XAddr>
|
||||||
|
<tds:Version>
|
||||||
|
<tt:Major>2</tt:Major>
|
||||||
|
<tt:Minor>6</tt:Minor>
|
||||||
|
</tds:Version>
|
||||||
|
</tds:Service>
|
||||||
|
</tds:GetServicesResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
services, err := client.GetServices(context.Background(), true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetServices() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(services) != 1 {
|
||||||
|
t.Errorf("Expected 1 service, got %d", len(services))
|
||||||
|
}
|
||||||
|
|
||||||
|
if services[0].Namespace != "http://www.onvif.org/ver10/device/wsdl" {
|
||||||
|
t.Errorf("Expected device namespace, got %s", services[0].Namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetServiceCapabilities(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetServiceCapabilitiesResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:Capabilities>
|
||||||
|
<tds:Network IPFilter="true" ZeroConfiguration="true"/>
|
||||||
|
<tds:Security TLS1.2="true"/>
|
||||||
|
<tds:System FirmwareUpgrade="true"/>
|
||||||
|
</tds:Capabilities>
|
||||||
|
</tds:GetServiceCapabilitiesResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caps, err := client.GetServiceCapabilities(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetServiceCapabilities() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if caps.Network == nil || !caps.Network.IPFilter {
|
||||||
|
t.Error("Expected Network.IPFilter to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDiscoveryMode(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetDiscoveryModeResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:DiscoveryMode>Discoverable</tds:DiscoveryMode>
|
||||||
|
</tds:GetDiscoveryModeResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode, err := client.GetDiscoveryMode(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetDiscoveryMode() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != DiscoveryModeDiscoverable {
|
||||||
|
t.Errorf("Expected Discoverable mode, got %s", mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDiscoveryMode(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetDiscoveryModeResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetDiscoveryMode(context.Background(), DiscoveryModeDiscoverable)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetDiscoveryMode() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEndpointReference(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetEndpointReferenceResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:GUID>urn:uuid:12345678-1234-1234-1234-123456789abc</tds:GUID>
|
||||||
|
</tds:GetEndpointReferenceResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
guid, err := client.GetEndpointReference(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetEndpointReference() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "urn:uuid:12345678-1234-1234-1234-123456789abc"
|
||||||
|
if guid != expected {
|
||||||
|
t.Errorf("Expected GUID %s, got %s", expected, guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNetworkProtocols(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetNetworkProtocolsResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:NetworkProtocols>
|
||||||
|
<tt:Name>HTTP</tt:Name>
|
||||||
|
<tt:Enabled>true</tt:Enabled>
|
||||||
|
<tt:Port>80</tt:Port>
|
||||||
|
</tds:NetworkProtocols>
|
||||||
|
<tds:NetworkProtocols>
|
||||||
|
<tt:Name>RTSP</tt:Name>
|
||||||
|
<tt:Enabled>true</tt:Enabled>
|
||||||
|
<tt:Port>554</tt:Port>
|
||||||
|
</tds:NetworkProtocols>
|
||||||
|
</tds:GetNetworkProtocolsResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols, err := client.GetNetworkProtocols(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetNetworkProtocols() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(protocols) != 2 {
|
||||||
|
t.Fatalf("Expected 2 protocols, got %d", len(protocols))
|
||||||
|
}
|
||||||
|
|
||||||
|
if protocols[0].Name != NetworkProtocolHTTP {
|
||||||
|
t.Errorf("Expected HTTP protocol, got %s", protocols[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetNetworkProtocols(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetNetworkProtocolsResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols := []*NetworkProtocol{
|
||||||
|
{Name: NetworkProtocolHTTP, Enabled: true, Port: []int{8080}},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetNetworkProtocols(context.Background(), protocols)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetNetworkProtocols() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNetworkDefaultGateway(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:GetNetworkDefaultGatewayResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||||
|
<tds:NetworkGateway>
|
||||||
|
<tt:IPv4Address>192.168.1.1</tt:IPv4Address>
|
||||||
|
</tds:NetworkGateway>
|
||||||
|
</tds:GetNetworkDefaultGatewayResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway, err := client.GetNetworkDefaultGateway(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetNetworkDefaultGateway() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gateway.IPv4Address) != 1 || gateway.IPv4Address[0] != "192.168.1.1" {
|
||||||
|
t.Errorf("Expected gateway 192.168.1.1, got %v", gateway.IPv4Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetNetworkDefaultGateway(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<s:Body>
|
||||||
|
<tds:SetNetworkDefaultGatewayResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(response))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway := &NetworkGateway{
|
||||||
|
IPv4Address: []string{"192.168.1.1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetNetworkDefaultGateway(context.Background(), gateway)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SetNetworkDefaultGateway() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkDeviceGetDeviceInformation(b *testing.B) {
|
func BenchmarkDeviceGetDeviceInformation(b *testing.B) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response := `<?xml version="1.0" encoding="UTF-8"?>
|
response := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|||||||
@@ -636,3 +636,426 @@ type FocusStatus struct {
|
|||||||
MoveStatus string
|
MoveStatus string
|
||||||
Error string
|
Error string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Service represents an ONVIF service
|
||||||
|
type Service struct {
|
||||||
|
Namespace string
|
||||||
|
XAddr string
|
||||||
|
Capabilities interface{}
|
||||||
|
Version OnvifVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnvifVersion represents ONVIF version
|
||||||
|
type OnvifVersion struct {
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceServiceCapabilities represents device service capabilities
|
||||||
|
type DeviceServiceCapabilities struct {
|
||||||
|
Network *NetworkCapabilities
|
||||||
|
Security *SecurityCapabilities
|
||||||
|
System *SystemCapabilities
|
||||||
|
Misc *MiscCapabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiscCapabilities represents miscellaneous capabilities
|
||||||
|
type MiscCapabilities struct {
|
||||||
|
AuxiliaryCommands []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscoveryMode represents discovery mode
|
||||||
|
type DiscoveryMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DiscoveryModeDiscoverable DiscoveryMode = "Discoverable"
|
||||||
|
DiscoveryModeNonDiscoverable DiscoveryMode = "NonDiscoverable"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworkProtocol represents network protocol configuration
|
||||||
|
type NetworkProtocol struct {
|
||||||
|
Name NetworkProtocolType
|
||||||
|
Enabled bool
|
||||||
|
Port []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkProtocolType represents protocol type
|
||||||
|
type NetworkProtocolType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NetworkProtocolHTTP NetworkProtocolType = "HTTP"
|
||||||
|
NetworkProtocolHTTPS NetworkProtocolType = "HTTPS"
|
||||||
|
NetworkProtocolRTSP NetworkProtocolType = "RTSP"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworkGateway represents default gateway
|
||||||
|
type NetworkGateway struct {
|
||||||
|
IPv4Address []string
|
||||||
|
IPv6Address []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemDateTime represents system date and time
|
||||||
|
type SystemDateTime struct {
|
||||||
|
DateTimeType SetDateTimeType
|
||||||
|
DaylightSavings bool
|
||||||
|
TimeZone *TimeZone
|
||||||
|
UTCDateTime *DateTime
|
||||||
|
LocalDateTime *DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDateTimeType represents date/time set method
|
||||||
|
type SetDateTimeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SetDateTimeManual SetDateTimeType = "Manual"
|
||||||
|
SetDateTimeNTP SetDateTimeType = "NTP"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeZone represents timezone
|
||||||
|
type TimeZone struct {
|
||||||
|
TZ string // POSIX format
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTime represents date and time
|
||||||
|
type DateTime struct {
|
||||||
|
Time Time
|
||||||
|
Date Date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time represents time
|
||||||
|
type Time struct {
|
||||||
|
Hour int
|
||||||
|
Minute int
|
||||||
|
Second int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date represents date
|
||||||
|
type Date struct {
|
||||||
|
Year int
|
||||||
|
Month int
|
||||||
|
Day int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLogType represents system log type
|
||||||
|
type SystemLogType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SystemLogTypeSystem SystemLogType = "System"
|
||||||
|
SystemLogTypeAccess SystemLogType = "Access"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SystemLog represents system log data
|
||||||
|
type SystemLog struct {
|
||||||
|
Binary *AttachmentData
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachmentData represents attachment/binary data
|
||||||
|
type AttachmentData struct {
|
||||||
|
ContentType string
|
||||||
|
Include *Include
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include represents XOP include
|
||||||
|
type Include struct {
|
||||||
|
Href string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFile represents backup file
|
||||||
|
type BackupFile struct {
|
||||||
|
Name string
|
||||||
|
Data AttachmentData
|
||||||
|
}
|
||||||
|
|
||||||
|
// FactoryDefaultType represents factory default type
|
||||||
|
type FactoryDefaultType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FactoryDefaultHard FactoryDefaultType = "Hard"
|
||||||
|
FactoryDefaultSoft FactoryDefaultType = "Soft"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RelayOutput represents relay output
|
||||||
|
type RelayOutput struct {
|
||||||
|
Token string
|
||||||
|
Properties RelayOutputSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelayOutputSettings represents relay output settings
|
||||||
|
type RelayOutputSettings struct {
|
||||||
|
Mode RelayMode
|
||||||
|
DelayTime time.Duration
|
||||||
|
IdleState RelayIdleState
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelayMode represents relay mode
|
||||||
|
type RelayMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RelayModeMonostable RelayMode = "Monostable"
|
||||||
|
RelayModeBistable RelayMode = "Bistable"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RelayIdleState represents relay idle state
|
||||||
|
type RelayIdleState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RelayIdleStateClosed RelayIdleState = "closed"
|
||||||
|
RelayIdleStateOpen RelayIdleState = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RelayLogicalState represents relay logical state
|
||||||
|
type RelayLogicalState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RelayLogicalStateActive RelayLogicalState = "active"
|
||||||
|
RelayLogicalStateInactive RelayLogicalState = "inactive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuxiliaryData represents auxiliary command data
|
||||||
|
type AuxiliaryData string
|
||||||
|
|
||||||
|
// SupportInformation represents support information
|
||||||
|
type SupportInformation struct {
|
||||||
|
Binary *AttachmentData
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLogUriList represents system log URIs
|
||||||
|
type SystemLogUriList struct {
|
||||||
|
SystemLog []SystemLogUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLogUri represents system log URI
|
||||||
|
type SystemLogUri struct {
|
||||||
|
Type SystemLogType
|
||||||
|
Uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkZeroConfiguration represents zero-configuration
|
||||||
|
type NetworkZeroConfiguration struct {
|
||||||
|
InterfaceToken string
|
||||||
|
Enabled bool
|
||||||
|
Addresses []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicDNSInformation represents dynamic DNS info
|
||||||
|
type DynamicDNSInformation struct {
|
||||||
|
Type DynamicDNSType
|
||||||
|
Name string
|
||||||
|
TTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicDNSType represents dynamic DNS type
|
||||||
|
type DynamicDNSType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DynamicDNSNoUpdate DynamicDNSType = "NoUpdate"
|
||||||
|
DynamicDNSClientUpdates DynamicDNSType = "ClientUpdates"
|
||||||
|
DynamicDNSServerUpdates DynamicDNSType = "ServerUpdates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAddressFilter represents IP address filter
|
||||||
|
type IPAddressFilter struct {
|
||||||
|
Type IPAddressFilterType
|
||||||
|
IPv4Address []PrefixedIPv4Address
|
||||||
|
IPv6Address []PrefixedIPv6Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressFilterType represents filter type
|
||||||
|
type IPAddressFilterType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPAddressFilterAllow IPAddressFilterType = "Allow"
|
||||||
|
IPAddressFilterDeny IPAddressFilterType = "Deny"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoteUser represents remote user configuration
|
||||||
|
type RemoteUser struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
UseDerivedPassword bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate represents a certificate
|
||||||
|
type Certificate struct {
|
||||||
|
CertificateID string
|
||||||
|
Certificate BinaryData
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinaryData represents binary data
|
||||||
|
type BinaryData struct {
|
||||||
|
ContentType string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertificateStatus represents certificate status
|
||||||
|
type CertificateStatus struct {
|
||||||
|
CertificateID string
|
||||||
|
Status bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertificateInformation represents certificate information
|
||||||
|
type CertificateInformation struct {
|
||||||
|
CertificateID string
|
||||||
|
IssuerDN string
|
||||||
|
SubjectDN string
|
||||||
|
KeyUsage *CertificateUsage
|
||||||
|
ExtendedKeyUsage *CertificateUsage
|
||||||
|
KeyLength int
|
||||||
|
Version string
|
||||||
|
SerialNum string
|
||||||
|
SignatureAlgorithm string
|
||||||
|
Validity *DateTimeRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertificateUsage represents certificate usage
|
||||||
|
type CertificateUsage struct {
|
||||||
|
Critical bool
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTimeRange represents date/time range
|
||||||
|
type DateTimeRange struct {
|
||||||
|
From time.Time
|
||||||
|
Until time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot11Capabilities represents 802.11 capabilities
|
||||||
|
type Dot11Capabilities struct {
|
||||||
|
TKIP bool
|
||||||
|
ScanAvailableNetworks bool
|
||||||
|
MultipleConfiguration bool
|
||||||
|
AdHocStationMode bool
|
||||||
|
WEP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot11Status represents 802.11 status
|
||||||
|
type Dot11Status struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
PairCipher Dot11Cipher
|
||||||
|
GroupCipher Dot11Cipher
|
||||||
|
SignalStrength Dot11SignalStrength
|
||||||
|
ActiveConfigAlias string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot11Cipher represents 802.11 cipher
|
||||||
|
type Dot11Cipher string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Dot11CipherCCMP Dot11Cipher = "CCMP"
|
||||||
|
Dot11CipherTKIP Dot11Cipher = "TKIP"
|
||||||
|
Dot11CipherAny Dot11Cipher = "Any"
|
||||||
|
Dot11CipherExtended Dot11Cipher = "Extended"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dot11SignalStrength represents signal strength
|
||||||
|
type Dot11SignalStrength string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Dot11SignalNone Dot11SignalStrength = "None"
|
||||||
|
Dot11SignalVeryBad Dot11SignalStrength = "Very Bad"
|
||||||
|
Dot11SignalBad Dot11SignalStrength = "Bad"
|
||||||
|
Dot11SignalGood Dot11SignalStrength = "Good"
|
||||||
|
Dot11SignalVeryGood Dot11SignalStrength = "Very Good"
|
||||||
|
Dot11SignalExtended Dot11SignalStrength = "Extended"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dot1XConfiguration represents 802.1X configuration
|
||||||
|
type Dot1XConfiguration struct {
|
||||||
|
Dot1XConfigurationToken string
|
||||||
|
Identity string
|
||||||
|
AnonymousID string
|
||||||
|
EAPMethod int
|
||||||
|
CACertificateID []string
|
||||||
|
EAPMethodConfiguration *EAPMethodConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// EAPMethodConfiguration represents EAP method configuration
|
||||||
|
type EAPMethodConfiguration struct {
|
||||||
|
TLSConfiguration *TLSConfiguration
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfiguration represents TLS configuration
|
||||||
|
type TLSConfiguration struct {
|
||||||
|
CertificateID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot11AvailableNetworks represents available 802.11 networks
|
||||||
|
type Dot11AvailableNetworks struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
AuthAndMangementSuite []Dot11AuthAndMangementSuite
|
||||||
|
PairCipher []Dot11Cipher
|
||||||
|
GroupCipher []Dot11Cipher
|
||||||
|
SignalStrength Dot11SignalStrength
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot11AuthAndMangementSuite represents auth suite
|
||||||
|
type Dot11AuthAndMangementSuite string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Dot11AuthNone Dot11AuthAndMangementSuite = "None"
|
||||||
|
Dot11AuthDot1X Dot11AuthAndMangementSuite = "Dot1X"
|
||||||
|
Dot11AuthPSK Dot11AuthAndMangementSuite = "PSK"
|
||||||
|
Dot11AuthExtended Dot11AuthAndMangementSuite = "Extended"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageConfiguration represents storage configuration
|
||||||
|
type StorageConfiguration struct {
|
||||||
|
Token string
|
||||||
|
Data StorageConfigurationData
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageConfigurationData represents storage configuration data
|
||||||
|
type StorageConfigurationData struct {
|
||||||
|
Type string
|
||||||
|
LocalPath string
|
||||||
|
StorageUri string
|
||||||
|
User *UserCredential
|
||||||
|
CertPathValidationPolicyID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCredential represents user credentials
|
||||||
|
type UserCredential struct {
|
||||||
|
UserName string
|
||||||
|
Password string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocationEntity represents geo location
|
||||||
|
type LocationEntity struct {
|
||||||
|
// Simplified - full implementation would include lat/long
|
||||||
|
Entity string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordComplexityConfiguration represents password complexity config
|
||||||
|
type PasswordComplexityConfiguration struct {
|
||||||
|
MinLen int
|
||||||
|
Uppercase int
|
||||||
|
Number int
|
||||||
|
SpecialChars int
|
||||||
|
BlockUsernameOccurrence bool
|
||||||
|
PolicyConfigurationLocked bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordHistoryConfiguration represents password history config
|
||||||
|
type PasswordHistoryConfiguration struct {
|
||||||
|
Enabled bool
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthFailureWarningConfiguration represents auth failure warning config
|
||||||
|
type AuthFailureWarningConfiguration struct {
|
||||||
|
Enabled bool
|
||||||
|
MonitorPeriod int
|
||||||
|
MaxAuthFailures int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntRange represents integer range
|
||||||
|
type IntRange struct {
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user