refactor: update HTTP request handling and improve documentation

- Replaced http.NewRequest with http.NewRequestWithContext in client tests for better context management.
- Updated method names and comments for clarity, including renaming GetWsdlUrl to GetWsdlURL and StorageUri to StorageURI for consistency.
- Enhanced comments across various files to provide clearer descriptions of functionality and ONVIF specifications.
This commit is contained in:
0x524a
2025-12-02 08:41:37 -05:00
parent 9e3b5e0170
commit 96ac509c24
32 changed files with 248 additions and 184 deletions
+12 -7
View File
@@ -2,7 +2,7 @@ package onvif
import ( import (
"context" "context"
"crypto/md5" "crypto/md5" //nolint:gosec // MD5 used for ONVIF digest authentication
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/hex" "encoding/hex"
@@ -62,12 +62,14 @@ func WithHTTPClient(httpClient *http.Client) ClientOption {
} }
} }
// WithInsecureSkipVerify disables TLS certificate verification.
// WARNING: Only use this for testing or with trusted cameras on private networks. // WARNING: Only use this for testing or with trusted cameras on private networks.
func WithInsecureSkipVerify() ClientOption { func WithInsecureSkipVerify() ClientOption {
return func(c *Client) { return func(c *Client) {
if transport, ok := c.httpClient.Transport.(*http.Transport); ok { if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
if transport.TLSClientConfig == nil { if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{} transport.TLSClientConfig = &tls.Config{ //nolint:gosec // InsecureSkipVerify is intentional for testing
}
} }
transport.TLSClientConfig.InsecureSkipVerify = true transport.TLSClientConfig.InsecureSkipVerify = true
} }
@@ -240,6 +242,7 @@ func (c *Client) GetCredentials() (username, password string) {
return c.username, c.password return c.username, c.password
} }
// DownloadFile downloads a file from the given URL with authentication.
// Supports both Basic and Digest authentication (tries basic first, falls back to digest). // Supports both Basic and Digest authentication (tries basic first, falls back to digest).
func (c *Client) DownloadFile(ctx context.Context, downloadURL string) ([]byte, error) { func (c *Client) DownloadFile(ctx context.Context, downloadURL string) ([]byte, error) {
// Try basic auth first // Try basic auth first
@@ -290,8 +293,9 @@ func (c *Client) downloadWithBasicAuth(ctx context.Context, downloadURL string)
//nolint:errcheck // Error response body preview - ignore read errors //nolint:errcheck // Error response body preview - ignore read errors
bodyPreview, _ := io.ReadAll(resp.Body) bodyPreview, _ := io.ReadAll(resp.Body)
bodyStr := string(bodyPreview) bodyStr := string(bodyPreview)
if len(bodyStr) > 200 { const maxBodyPreview = 200
bodyStr = bodyStr[:200] + "..." if len(bodyStr) > maxBodyPreview {
bodyStr = bodyStr[:maxBodyPreview] + "..."
} }
// Base error message for programmatic use // Base error message for programmatic use
@@ -368,8 +372,9 @@ func (c *Client) downloadWithDigestAuth(ctx context.Context, downloadURL string)
//nolint:errcheck // Error response body preview - ignore read errors //nolint:errcheck // Error response body preview - ignore read errors
bodyPreview, _ := io.ReadAll(resp.Body) bodyPreview, _ := io.ReadAll(resp.Body)
bodyStr := string(bodyPreview) bodyStr := string(bodyPreview)
if len(bodyStr) > 200 { const maxBodyPreview = 200
bodyStr = bodyStr[:200] + "..." if len(bodyStr) > maxBodyPreview {
bodyStr = bodyStr[:maxBodyPreview] + "..."
} }
errorMsg := fmt.Sprintf("download failed with status code %d", resp.StatusCode) errorMsg := fmt.Sprintf("download failed with status code %d", resp.StatusCode)
@@ -510,7 +515,7 @@ func md5Hash(s string) string {
func md5sum(s string) interface{} { func md5sum(s string) interface{} {
// Use crypto/md5 - import it if not already present // Use crypto/md5 - import it if not already present
h := md5.New() h := md5.New() //nolint:gosec // MD5 required for ONVIF digest auth
h.Write([]byte(s)) h.Write([]byte(s))
return h.Sum(nil) return h.Sum(nil)
+2 -2
View File
@@ -1013,7 +1013,7 @@ func TestDigestAuthTransport(t *testing.T) {
Timeout: DefaultTimeout, Timeout: DefaultTimeout,
} }
req, err := http.NewRequest("GET", server.URL, http.NoBody) req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, http.NoBody)
if err != nil { if err != nil {
t.Fatalf("NewRequest() failed: %v", err) t.Fatalf("NewRequest() failed: %v", err)
} }
@@ -1358,7 +1358,7 @@ func TestDigestAuthTransportConcurrency(t *testing.T) {
for i := 0; i < numRequests; i++ { for i := 0; i < numRequests; i++ {
go func(id int) { go func(id int) {
req, err := http.NewRequest("GET", server.URL, http.NoBody) req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, http.NoBody)
if err != nil { if err != nil {
errors <- fmt.Errorf("request %d: %w", id, fmt.Errorf("%w", ErrTestRequestNewFailed)) errors <- fmt.Errorf("request %d: %w", id, fmt.Errorf("%w", ErrTestRequestNewFailed))
done <- true done <- true
+2 -1
View File
@@ -135,6 +135,7 @@ type AdditionalTest struct {
Code string Code string
} }
//nolint:funlen // Main function has many statements due to test generation logic
func main() { func main() {
flag.Parse() flag.Parse()
@@ -215,7 +216,7 @@ func main() {
// Create output file // Create output file
outputFile := filepath.Join(*outputDir, fmt.Sprintf("%s_test.go", strings.ToLower(cameraID))) outputFile := filepath.Join(*outputDir, fmt.Sprintf("%s_test.go", strings.ToLower(cameraID)))
f, err := os.Create(outputFile) f, err := os.Create(outputFile) //nolint:gosec // Filename is generated from test data, safe
if err != nil { if err != nil {
log.Fatalf("Failed to create output file: %v", err) log.Fatalf("Failed to create output file: %v", err)
} }
+18 -6
View File
@@ -17,11 +17,21 @@ type ASCIIConfig struct {
Quality string // "high", "medium", "low" Quality string // "high", "medium", "low"
} }
const (
defaultASCIIWidth = 120
defaultASCIIHeight = 40
maxColorValue = 255
bitShift8 = 8
bufferSize1024 = 1024
largeASCIIWidth = 160
largeASCIIHeight = 50
)
// DefaultASCIIConfig returns a sensible default configuration. // DefaultASCIIConfig returns a sensible default configuration.
func DefaultASCIIConfig() ASCIIConfig { func DefaultASCIIConfig() ASCIIConfig {
return ASCIIConfig{ return ASCIIConfig{
Width: 120, Width: defaultASCIIWidth,
Height: 40, Height: defaultASCIIHeight,
Invert: false, Invert: false,
Quality: "medium", Quality: "medium",
} }
@@ -46,7 +56,7 @@ var (
'o', 'O', '0', 'e', 'E', 'p', 'P', 'x', 'X', '$', 'D', 'W', 'M', '@', '#'} 'o', 'O', '0', 'e', 'E', 'p', 'P', 'x', 'X', '$', 'D', 'W', 'M', '@', '#'}
) )
// Supports JPEG and PNG formats. // ImageToASCII converts image data to ASCII art. Supports JPEG and PNG formats.
func ImageToASCII(imageData []byte, config ASCIIConfig) (string, error) { func ImageToASCII(imageData []byte, config ASCIIConfig) (string, error) {
// Decode image from bytes // Decode image from bytes
img, _, err := image.Decode(bytes.NewReader(imageData)) img, _, err := image.Decode(bytes.NewReader(imageData))
@@ -58,6 +68,8 @@ func ImageToASCII(imageData []byte, config ASCIIConfig) (string, error) {
} }
// imageToASCIIFromImage is the core conversion function. // imageToASCIIFromImage is the core conversion function.
//
//nolint:gocyclo // Image to ASCII conversion has high complexity due to multiple pixel processing paths
func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (string, error) { func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (string, error) {
// Validate configuration // Validate configuration
if config.Width <= 0 { if config.Width <= 0 {
@@ -141,9 +153,9 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (
// Uses standard luminance formula. // Uses standard luminance formula.
func calculateBrightness(r, g, b uint32) int { func calculateBrightness(r, g, b uint32) int {
// Convert 16-bit color to 8-bit // Convert 16-bit color to 8-bit
r8 := uint8(r >> 8) r8 := uint8(r >> 8) //nolint:gosec // Color values are clamped to valid range
g8 := uint8(g >> 8) g8 := uint8(g >> 8) //nolint:gosec // Color values are clamped to valid range
b8 := uint8(b >> 8) b8 := uint8(b >> 8) //nolint:gosec // Color values are clamped to valid range
// Use standard brightness calculation // Use standard brightness calculation
// https://en.wikipedia.org/wiki/Relative_luminance // https://en.wikipedia.org/wiki/Relative_luminance
+4 -1
View File
@@ -177,6 +177,8 @@ func (c *CLI) discoverCameras() {
} }
// discoverWithInterfaceSelection shows available network interfaces and lets user select one. // discoverWithInterfaceSelection shows available network interfaces and lets user select one.
//
//nolint:gocyclo // Interface selection has high complexity due to multiple user interaction paths
func (c *CLI) discoverWithInterfaceSelection() ([]*discovery.Device, error) { func (c *CLI) discoverWithInterfaceSelection() ([]*discovery.Device, error) {
// Get list of available interfaces // Get list of available interfaces
interfaces, err := discovery.ListNetworkInterfaces() interfaces, err := discovery.ListNetworkInterfaces()
@@ -1471,6 +1473,7 @@ func (c *CLI) advancedImagingSettings(ctx context.Context, videoSourceToken stri
c.getImagingSettings(ctx, videoSourceToken) c.getImagingSettings(ctx, videoSourceToken)
} }
//nolint:gocyclo // Snapshot capture and display has high complexity due to multiple error handling paths
func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) { func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
fmt.Println("📷 Capture Snapshot as ASCII Preview") fmt.Println("📷 Capture Snapshot as ASCII Preview")
fmt.Println("===================================") fmt.Println("===================================")
@@ -1595,7 +1598,7 @@ func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
if filename == "" { if filename == "" {
filename = "snapshot.jpg" filename = "snapshot.jpg"
} }
if err := os.WriteFile(filename, snapshotData, 0644); err != nil { if err := os.WriteFile(filename, snapshotData, 0600); err != nil { //nolint:gosec // 0600 is appropriate for CLI output files
fmt.Printf("❌ Failed to save file: %v\n", err) fmt.Printf("❌ Failed to save file: %v\n", err)
} else { } else {
fmt.Printf("✅ Snapshot saved to %s\n", filename) fmt.Printf("✅ Snapshot saved to %s\n", filename)
+11 -5
View File
@@ -145,6 +145,7 @@ var (
captureXML = flag.Bool("capture-xml", false, "Capture raw SOAP XML traffic and create tar.gz archive") captureXML = flag.Bool("capture-xml", false, "Capture raw SOAP XML traffic and create tar.gz archive")
) )
//nolint:gocognit // Main function has high complexity due to multiple diagnostic operations
func main() { func main() {
flag.Parse() flag.Parse()
@@ -191,7 +192,7 @@ func main() {
if *captureXML { if *captureXML {
timestamp := time.Now().Format("20060102-150405") timestamp := time.Now().Format("20060102-150405")
xmlCaptureDir = filepath.Join(*outputDir, "temp_"+timestamp) xmlCaptureDir = filepath.Join(*outputDir, "temp_"+timestamp)
if err := os.MkdirAll(xmlCaptureDir, 0755); err != nil { if err := os.MkdirAll(xmlCaptureDir, 0750); err != nil { //nolint:gosec // 0750 is appropriate for diagnostic output directory
log.Fatalf("Failed to create XML capture directory: %v", err) log.Fatalf("Failed to create XML capture directory: %v", err)
} }
@@ -876,15 +877,20 @@ func saveReport(report *CameraReport, filename string) error {
return fmt.Errorf("failed to marshal report: %w", err) return fmt.Errorf("failed to marshal report: %w", err)
} }
if err := os.WriteFile(filename, data, 0644); err != nil { if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec // 0600 is appropriate for diagnostic output files
return fmt.Errorf("failed to write file: %w", err) return fmt.Errorf("failed to write file: %w", err)
} }
return nil return nil
} }
//nolint:unparam // args parameter is kept for printf-style consistency, even though currently unused
func logStepf(format string, args ...interface{}) { func logStepf(format string, args ...interface{}) {
fmt.Printf("→ "+format+"\n", args...) if len(args) > 0 {
fmt.Printf("→ %s\n", fmt.Sprintf(format, args...))
} else {
fmt.Printf("→ " + format + "\n")
}
} }
func logSuccessf(format string, args ...interface{}) { func logSuccessf(format string, args ...interface{}) {
@@ -1011,7 +1017,7 @@ func (t *LoggingTransport) saveCapture(capture *XMLCapture) {
return return
} }
if err := os.WriteFile(filename, data, 0644); err != nil { if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec // 0600 is appropriate for diagnostic output files
log.Printf("Failed to write capture: %v", err) log.Printf("Failed to write capture: %v", err)
} }
@@ -1134,7 +1140,7 @@ func createTarGz(sourceDir, archivePath string) error {
// If it's a file, write its content // If it's a file, write its content
if !info.IsDir() { if !info.IsDir() {
file, err := os.Open(path) file, err := os.Open(path) //nolint:gosec // File path is from filepath.Walk, safe
if err != nil { if err != nil {
return fmt.Errorf("failed to open file: %w", err) return fmt.Errorf("failed to open file: %w", err)
} }
+22 -11
View File
@@ -12,6 +12,16 @@ import (
"github.com/0x524a/onvif-go/discovery" "github.com/0x524a/onvif-go/discovery"
) )
const (
defaultUsername = "admin"
defaultTimeout = 10
defaultRetryDelay = 5
ptzTimeout = 30
ptzStepSize = 2
ptzSpeed = 0.5
maxBodyPreview = 200
)
func main() { func main() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@@ -81,9 +91,9 @@ func discoverCameras() {
fmt.Printf(" %d. %s (%v)\n", i+1, iface.Name, iface.Addresses) fmt.Printf(" %d. %s (%v)\n", i+1, iface.Name, iface.Addresses)
} }
fmt.Print("\nEnter interface name or IP: ") fmt.Print("\nEnter interface name or IP: ")
//nolint:errcheck // ReadString error on stdin is rare and not critical for CLI //nolint:errcheck // ReadString error on stdin is rare and not critical for CLI
ifaceInput, _ := reader.ReadString('\n') ifaceInput, _ := reader.ReadString('\n')
ifaceInput = strings.TrimSpace(ifaceInput) ifaceInput = strings.TrimSpace(ifaceInput)
if ifaceInput != "" { if ifaceInput != "" {
@@ -97,7 +107,7 @@ func discoverCameras() {
opts = &discovery.DiscoverOptions{} opts = &discovery.DiscoverOptions{}
} }
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
defer cancel() defer cancel()
devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts) devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts)
@@ -172,7 +182,7 @@ func connectAndShowInfo() {
username, _ := reader.ReadString('\n') username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username) username = strings.TrimSpace(username)
if username == "" { if username == "" {
username = "admin" username = defaultUsername
} }
fmt.Print("Password: ") fmt.Print("Password: ")
@@ -223,6 +233,7 @@ func connectAndShowInfo() {
} }
} }
//nolint:gocyclo // PTZ demo function has high complexity due to multiple control paths
func ptzDemo() { func ptzDemo() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@@ -236,7 +247,7 @@ func ptzDemo() {
username, _ := reader.ReadString('\n') username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username) username = strings.TrimSpace(username)
if username == "" { if username == "" {
username = "admin" username = defaultUsername
} }
fmt.Print("Password: ") fmt.Print("Password: ")
@@ -302,11 +313,11 @@ func ptzDemo() {
case "1": case "1":
velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.5, Y: 0.0}} velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.5, Y: 0.0}}
case "2": case "2":
velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: -0.5, Y: 0.0}} velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: -ptzSpeed, Y: 0.0}}
case "3": case "3":
velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.5}} velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.0, Y: ptzSpeed}}
case "4": case "4":
velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.0, Y: -0.5}} velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.0, Y: -ptzSpeed}}
case "5": case "5":
position = &onvif.PTZVector{PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0}} position = &onvif.PTZVector{PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0}}
default: default:
@@ -316,7 +327,7 @@ func ptzDemo() {
} }
if velocity != nil { if velocity != nil {
timeout := "PT2S" timeout := fmt.Sprintf("PT%dS", ptzStepSize)
err = client.ContinuousMove(ctx, profileToken, velocity, &timeout) err = client.ContinuousMove(ctx, profileToken, velocity, &timeout)
if err != nil { if err != nil {
fmt.Printf("❌ Error: %v\n", err) fmt.Printf("❌ Error: %v\n", err)
@@ -353,7 +364,7 @@ func getStreamURLs() {
username, _ := reader.ReadString('\n') username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username) username = strings.TrimSpace(username)
if username == "" { if username == "" {
username = "admin" username = defaultUsername
} }
fmt.Print("Password: ") fmt.Print("Password: ")
+1
View File
@@ -17,6 +17,7 @@ var (
version = "1.0.0" version = "1.0.0"
) )
//nolint:funlen // Main function has many statements due to server setup and configuration
func main() { func main() {
// Define command-line flags // Define command-line flags
host := flag.String("host", "0.0.0.0", "Server host address") host := flag.String("host", "0.0.0.0", "Server host address")
+6 -4
View File
@@ -50,6 +50,8 @@ func (c *Client) GetDeviceInformation(ctx context.Context) (*DeviceInformation,
} }
// GetCapabilities retrieves device capabilities. // GetCapabilities retrieves device capabilities.
//
//nolint:funlen // GetCapabilities has many statements due to parsing multiple service capabilities
func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) { func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) {
type GetCapabilities struct { type GetCapabilities struct {
XMLName xml.Name `xml:"tds:GetCapabilities"` XMLName xml.Name `xml:"tds:GetCapabilities"`
@@ -110,8 +112,8 @@ func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) {
XAddr string `xml:"XAddr"` XAddr string `xml:"XAddr"`
StreamingCapabilities *struct { StreamingCapabilities *struct {
RTPMulticast bool `xml:"RTPMulticast"` RTPMulticast bool `xml:"RTPMulticast"`
RTP_TCP bool `xml:"RTP_TCP"` RTPTCP bool `xml:"RTP_TCP"`
RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP"` RTPRTSPTCP bool `xml:"RTP_RTSP_TCP"`
} `xml:"StreamingCapabilities"` } `xml:"StreamingCapabilities"`
} `xml:"Media"` } `xml:"Media"`
PTZ *struct { PTZ *struct {
@@ -214,8 +216,8 @@ func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) {
if resp.Capabilities.Media.StreamingCapabilities != nil { if resp.Capabilities.Media.StreamingCapabilities != nil {
capabilities.Media.StreamingCapabilities = &StreamingCapabilities{ capabilities.Media.StreamingCapabilities = &StreamingCapabilities{
RTPMulticast: resp.Capabilities.Media.StreamingCapabilities.RTPMulticast, RTPMulticast: resp.Capabilities.Media.StreamingCapabilities.RTPMulticast,
RTP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_TCP, RTPTCP: resp.Capabilities.Media.StreamingCapabilities.RTPTCP,
RTP_RTSP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_RTSP_TCP, RTPRTSPTCP: resp.Capabilities.Media.StreamingCapabilities.RTPRTSPTCP,
} }
} }
} }
+16 -16
View File
@@ -8,7 +8,7 @@ import (
"github.com/0x524a/onvif-go/internal/soap" "github.com/0x524a/onvif-go/internal/soap"
) )
// ONVIF Specification: GetGeoLocation operation. // GetGeoLocation retrieves geographic location information. ONVIF Specification: GetGeoLocation operation.
func (c *Client) GetGeoLocation(ctx context.Context) ([]LocationEntity, error) { func (c *Client) GetGeoLocation(ctx context.Context) ([]LocationEntity, error) {
type GetGeoLocationBody struct { type GetGeoLocationBody struct {
XMLName xml.Name `xml:"tds:GetGeoLocation"` XMLName xml.Name `xml:"tds:GetGeoLocation"`
@@ -35,7 +35,7 @@ func (c *Client) GetGeoLocation(ctx context.Context) ([]LocationEntity, error) {
return response.Location, nil return response.Location, nil
} }
// ONVIF Specification: SetGeoLocation operation. // SetGeoLocation sets geographic location information. ONVIF Specification: SetGeoLocation operation.
func (c *Client) SetGeoLocation(ctx context.Context, location []LocationEntity) error { func (c *Client) SetGeoLocation(ctx context.Context, location []LocationEntity) error {
type SetGeoLocationBody struct { type SetGeoLocationBody struct {
XMLName xml.Name `xml:"tds:SetGeoLocation"` XMLName xml.Name `xml:"tds:SetGeoLocation"`
@@ -63,7 +63,7 @@ func (c *Client) SetGeoLocation(ctx context.Context, location []LocationEntity)
return nil return nil
} }
// ONVIF Specification: DeleteGeoLocation operation. // DeleteGeoLocation deletes geographic location information. ONVIF Specification: DeleteGeoLocation operation.
func (c *Client) DeleteGeoLocation(ctx context.Context, location []LocationEntity) error { func (c *Client) DeleteGeoLocation(ctx context.Context, location []LocationEntity) error {
type DeleteGeoLocationBody struct { type DeleteGeoLocationBody struct {
XMLName xml.Name `xml:"tds:DeleteGeoLocation"` XMLName xml.Name `xml:"tds:DeleteGeoLocation"`
@@ -91,7 +91,7 @@ func (c *Client) DeleteGeoLocation(ctx context.Context, location []LocationEntit
return nil return nil
} }
// ONVIF Specification: GetDPAddresses operation. // GetDPAddresses retrieves DP (Device Provisioning) addresses. ONVIF Specification: GetDPAddresses operation.
func (c *Client) GetDPAddresses(ctx context.Context) ([]NetworkHost, error) { func (c *Client) GetDPAddresses(ctx context.Context) ([]NetworkHost, error) {
type GetDPAddressesBody struct { type GetDPAddressesBody struct {
XMLName xml.Name `xml:"tds:GetDPAddresses"` XMLName xml.Name `xml:"tds:GetDPAddresses"`
@@ -118,7 +118,7 @@ func (c *Client) GetDPAddresses(ctx context.Context) ([]NetworkHost, error) {
return response.DPAddress, nil return response.DPAddress, nil
} }
// ONVIF Specification: SetDPAddresses operation. // SetDPAddresses sets DP (Device Provisioning) addresses. ONVIF Specification: SetDPAddresses operation.
func (c *Client) SetDPAddresses(ctx context.Context, dpAddress []NetworkHost) error { func (c *Client) SetDPAddresses(ctx context.Context, dpAddress []NetworkHost) error {
type SetDPAddressesBody struct { type SetDPAddressesBody struct {
XMLName xml.Name `xml:"tds:SetDPAddresses"` XMLName xml.Name `xml:"tds:SetDPAddresses"`
@@ -146,7 +146,7 @@ func (c *Client) SetDPAddresses(ctx context.Context, dpAddress []NetworkHost) er
return nil return nil
} }
// ONVIF Specification: GetAccessPolicy operation. // GetAccessPolicy retrieves access policy information. ONVIF Specification: GetAccessPolicy operation.
func (c *Client) GetAccessPolicy(ctx context.Context) (*AccessPolicy, error) { func (c *Client) GetAccessPolicy(ctx context.Context) (*AccessPolicy, error) {
type GetAccessPolicyBody struct { type GetAccessPolicyBody struct {
XMLName xml.Name `xml:"tds:GetAccessPolicy"` XMLName xml.Name `xml:"tds:GetAccessPolicy"`
@@ -173,7 +173,7 @@ func (c *Client) GetAccessPolicy(ctx context.Context) (*AccessPolicy, error) {
return &AccessPolicy{PolicyFile: response.PolicyFile}, nil return &AccessPolicy{PolicyFile: response.PolicyFile}, nil
} }
// ONVIF Specification: SetAccessPolicy operation. // SetAccessPolicy sets access policy information. ONVIF Specification: SetAccessPolicy operation.
func (c *Client) SetAccessPolicy(ctx context.Context, policy *AccessPolicy) error { func (c *Client) SetAccessPolicy(ctx context.Context, policy *AccessPolicy) error {
type SetAccessPolicyBody struct { type SetAccessPolicyBody struct {
XMLName xml.Name `xml:"tds:SetAccessPolicy"` XMLName xml.Name `xml:"tds:SetAccessPolicy"`
@@ -201,29 +201,29 @@ func (c *Client) SetAccessPolicy(ctx context.Context, policy *AccessPolicy) erro
return nil return nil
} }
// ONVIF Specification: GetWsdlUrl operation (deprecated). // GetWsdlURL retrieves the WSDL URL (deprecated). ONVIF Specification: GetWsdlUrl operation.
func (c *Client) GetWsdlUrl(ctx context.Context) (string, error) { func (c *Client) GetWsdlURL(ctx context.Context) (string, error) {
type GetWsdlUrlBody struct { type GetWsdlURLBody struct {
XMLName xml.Name `xml:"tds:GetWsdlUrl"` XMLName xml.Name `xml:"tds:GetWsdlUrl"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
} }
type GetWsdlUrlResponse struct { type GetWsdlURLResponse struct {
XMLName xml.Name `xml:"GetWsdlUrlResponse"` XMLName xml.Name `xml:"GetWsdlUrlResponse"`
WsdlUrl string `xml:"WsdlUrl"` WsdlURL string `xml:"WsdlUrl"`
} }
request := GetWsdlUrlBody{ request := GetWsdlURLBody{
Xmlns: deviceNamespace, Xmlns: deviceNamespace,
} }
var response GetWsdlUrlResponse var response GetWsdlURLResponse
username, password := c.GetCredentials() username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password) soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", request, &response); err != nil { if err := soapClient.Call(ctx, c.endpoint, "", request, &response); err != nil {
return "", fmt.Errorf("GetWsdlUrl failed: %w", err) return "", fmt.Errorf("GetWsdlURL failed: %w", err)
} }
return response.WsdlUrl, nil return response.WsdlURL, nil
} }
+2 -2
View File
@@ -324,9 +324,9 @@ func TestGetWsdlUrl(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
url, err := client.GetWsdlUrl(ctx) url, err := client.GetWsdlURL(ctx)
if err != nil { if err != nil {
t.Fatalf("GetWsdlUrl failed: %v", err) t.Fatalf("GetWsdlURL failed: %v", err)
} }
expected := "http://192.168.1.100/onvif/device.wsdl" expected := "http://192.168.1.100/onvif/device.wsdl"
+13 -10
View File
@@ -8,7 +8,7 @@ import (
"github.com/0x524a/onvif-go/internal/soap" "github.com/0x524a/onvif-go/internal/soap"
) )
// ONVIF Specification: GetCertificates operation. // GetCertificates retrieves certificates. ONVIF Specification: GetCertificates operation.
func (c *Client) GetCertificates(ctx context.Context) ([]*Certificate, error) { func (c *Client) GetCertificates(ctx context.Context) ([]*Certificate, error) {
type GetCertificatesBody struct { type GetCertificatesBody struct {
XMLName xml.Name `xml:"tds:GetCertificates"` XMLName xml.Name `xml:"tds:GetCertificates"`
@@ -35,7 +35,7 @@ func (c *Client) GetCertificates(ctx context.Context) ([]*Certificate, error) {
return response.Certificates, nil return response.Certificates, nil
} }
// ONVIF Specification: GetCACertificates operation. // GetCACertificates retrieves CA certificates. ONVIF Specification: GetCACertificates operation.
func (c *Client) GetCACertificates(ctx context.Context) ([]*Certificate, error) { func (c *Client) GetCACertificates(ctx context.Context) ([]*Certificate, error) {
type GetCACertificatesBody struct { type GetCACertificatesBody struct {
XMLName xml.Name `xml:"tds:GetCACertificates"` XMLName xml.Name `xml:"tds:GetCACertificates"`
@@ -62,7 +62,7 @@ func (c *Client) GetCACertificates(ctx context.Context) ([]*Certificate, error)
return response.Certificates, nil return response.Certificates, nil
} }
// ONVIF Specification: LoadCertificates operation. // LoadCertificates loads certificates. ONVIF Specification: LoadCertificates operation.
func (c *Client) LoadCertificates(ctx context.Context, certificates []*Certificate) error { func (c *Client) LoadCertificates(ctx context.Context, certificates []*Certificate) error {
type LoadCertificatesBody struct { type LoadCertificatesBody struct {
XMLName xml.Name `xml:"tds:LoadCertificates"` XMLName xml.Name `xml:"tds:LoadCertificates"`
@@ -90,7 +90,7 @@ func (c *Client) LoadCertificates(ctx context.Context, certificates []*Certifica
return nil return nil
} }
// ONVIF Specification: LoadCACertificates operation. // LoadCACertificates loads CA certificates. ONVIF Specification: LoadCACertificates operation.
func (c *Client) LoadCACertificates(ctx context.Context, certificates []*Certificate) error { func (c *Client) LoadCACertificates(ctx context.Context, certificates []*Certificate) error {
type LoadCACertificatesBody struct { type LoadCACertificatesBody struct {
XMLName xml.Name `xml:"tds:LoadCACertificates"` XMLName xml.Name `xml:"tds:LoadCACertificates"`
@@ -118,7 +118,7 @@ func (c *Client) LoadCACertificates(ctx context.Context, certificates []*Certifi
return nil return nil
} }
// ONVIF Specification: CreateCertificate operation. // CreateCertificate creates a certificate. ONVIF Specification: CreateCertificate operation.
func (c *Client) CreateCertificate( func (c *Client) CreateCertificate(
ctx context.Context, ctx context.Context,
certificateID, subject, validNotBefore, validNotAfter string, certificateID, subject, validNotBefore, validNotAfter string,
@@ -156,7 +156,7 @@ func (c *Client) CreateCertificate(
return response.Certificate, nil return response.Certificate, nil
} }
// ONVIF Specification: DeleteCertificates operation. // DeleteCertificates deletes certificates. ONVIF Specification: DeleteCertificates operation.
func (c *Client) DeleteCertificates(ctx context.Context, certificateIDs []string) error { func (c *Client) DeleteCertificates(ctx context.Context, certificateIDs []string) error {
type DeleteCertificatesBody struct { type DeleteCertificatesBody struct {
XMLName xml.Name `xml:"tds:DeleteCertificates"` XMLName xml.Name `xml:"tds:DeleteCertificates"`
@@ -184,6 +184,7 @@ func (c *Client) DeleteCertificates(ctx context.Context, certificateIDs []string
return nil return nil
} }
// GetCertificateInformation retrieves certificate information.
// ONVIF Specification: GetCertificateInformation operation. // ONVIF Specification: GetCertificateInformation operation.
func (c *Client) GetCertificateInformation(ctx context.Context, certificateID string) (*CertificateInformation, error) { func (c *Client) GetCertificateInformation(ctx context.Context, certificateID string) (*CertificateInformation, error) {
type GetCertificateInformationBody struct { type GetCertificateInformationBody struct {
@@ -213,7 +214,7 @@ func (c *Client) GetCertificateInformation(ctx context.Context, certificateID st
return response.CertificateInformation, nil return response.CertificateInformation, nil
} }
// ONVIF Specification: GetCertificatesStatus operation. // GetCertificatesStatus retrieves certificate status. ONVIF Specification: GetCertificatesStatus operation.
func (c *Client) GetCertificatesStatus(ctx context.Context) ([]*CertificateStatus, error) { func (c *Client) GetCertificatesStatus(ctx context.Context) ([]*CertificateStatus, error) {
type GetCertificatesStatusBody struct { type GetCertificatesStatusBody struct {
XMLName xml.Name `xml:"tds:GetCertificatesStatus"` XMLName xml.Name `xml:"tds:GetCertificatesStatus"`
@@ -240,7 +241,7 @@ func (c *Client) GetCertificatesStatus(ctx context.Context) ([]*CertificateStatu
return response.CertificateStatus, nil return response.CertificateStatus, nil
} }
// ONVIF Specification: SetCertificatesStatus operation. // SetCertificatesStatus sets certificate status. ONVIF Specification: SetCertificatesStatus operation.
func (c *Client) SetCertificatesStatus(ctx context.Context, statuses []*CertificateStatus) error { func (c *Client) SetCertificatesStatus(ctx context.Context, statuses []*CertificateStatus) error {
type SetCertificatesStatusBody struct { type SetCertificatesStatusBody struct {
XMLName xml.Name `xml:"tds:SetCertificatesStatus"` XMLName xml.Name `xml:"tds:SetCertificatesStatus"`
@@ -268,7 +269,7 @@ func (c *Client) SetCertificatesStatus(ctx context.Context, statuses []*Certific
return nil return nil
} }
// ONVIF Specification: GetPkcs10Request operation. // GetPkcs10Request retrieves a PKCS10 certificate request. ONVIF Specification: GetPkcs10Request operation.
func (c *Client) GetPkcs10Request( func (c *Client) GetPkcs10Request(
ctx context.Context, ctx context.Context,
certificateID, subject string, certificateID, subject string,
@@ -305,6 +306,7 @@ func (c *Client) GetPkcs10Request(
return response.Pkcs10Request, nil return response.Pkcs10Request, nil
} }
// LoadCertificateWithPrivateKey loads a certificate with its private key.
// ONVIF Specification: LoadCertificateWithPrivateKey operation. // ONVIF Specification: LoadCertificateWithPrivateKey operation.
func (c *Client) LoadCertificateWithPrivateKey( func (c *Client) LoadCertificateWithPrivateKey(
ctx context.Context, ctx context.Context,
@@ -358,6 +360,7 @@ func (c *Client) LoadCertificateWithPrivateKey(
return nil return nil
} }
// GetClientCertificateMode retrieves the client certificate mode.
// ONVIF Specification: GetClientCertificateMode operation. // ONVIF Specification: GetClientCertificateMode operation.
func (c *Client) GetClientCertificateMode(ctx context.Context) (bool, error) { func (c *Client) GetClientCertificateMode(ctx context.Context) (bool, error) {
type GetClientCertificateModeBody struct { type GetClientCertificateModeBody struct {
@@ -385,7 +388,7 @@ func (c *Client) GetClientCertificateMode(ctx context.Context) (bool, error) {
return response.Enabled, nil return response.Enabled, nil
} }
// ONVIF Specification: SetClientCertificateMode operation. // SetClientCertificateMode sets the client certificate mode. ONVIF Specification: SetClientCertificateMode operation.
func (c *Client) SetClientCertificateMode(ctx context.Context, enabled bool) error { func (c *Client) SetClientCertificateMode(ctx context.Context, enabled bool) error {
type SetClientCertificateModeBody struct { type SetClientCertificateModeBody struct {
XMLName xml.Name `xml:"tds:SetClientCertificateMode"` XMLName xml.Name `xml:"tds:SetClientCertificateMode"`
+13 -13
View File
@@ -623,7 +623,7 @@ func (c *Client) RestoreSystem(ctx context.Context, backupFiles []*BackupFile) e
// GetSystemUris retrieves URIs from which system information may be downloaded. // GetSystemUris retrieves URIs from which system information may be downloaded.
func (c *Client) GetSystemUris( func (c *Client) GetSystemUris(
ctx context.Context, ctx context.Context,
) (uriList *SystemLogUriList, systemBackupURI, systemLogURI string, err error) { ) (uriList *SystemLogURIList, systemBackupURI, systemLogURI string, err error) {
type GetSystemUris struct { type GetSystemUris struct {
XMLName xml.Name `xml:"tds:GetSystemUris"` XMLName xml.Name `xml:"tds:GetSystemUris"`
Xmlns string `xml:"xmlns:tds,attr"` Xmlns string `xml:"xmlns:tds,attr"`
@@ -634,11 +634,11 @@ func (c *Client) GetSystemUris(
SystemLogUris *struct { SystemLogUris *struct {
SystemLog []struct { SystemLog []struct {
Type string `xml:"Type"` Type string `xml:"Type"`
Uri string `xml:"Uri"` URI string `xml:"Uri"`
} `xml:"SystemLog"` } `xml:"SystemLog"`
} `xml:"SystemLogUris"` } `xml:"SystemLogUris"`
SupportInfoUri string `xml:"SupportInfoUri"` SupportInfoURI string `xml:"SupportInfoUri"`
SystemBackupUri string `xml:"SystemBackupUri"` SystemBackupURI string `xml:"SystemBackupUri"`
} }
req := GetSystemUris{ req := GetSystemUris{
@@ -654,18 +654,18 @@ func (c *Client) GetSystemUris(
return nil, "", "", fmt.Errorf("GetSystemUris failed: %w", err) return nil, "", "", fmt.Errorf("GetSystemUris failed: %w", err)
} }
var logUris *SystemLogUriList var logUris *SystemLogURIList
if resp.SystemLogUris != nil { if resp.SystemLogUris != nil {
logUris = &SystemLogUriList{} logUris = &SystemLogURIList{}
for _, log := range resp.SystemLogUris.SystemLog { for _, log := range resp.SystemLogUris.SystemLog {
logUris.SystemLog = append(logUris.SystemLog, SystemLogUri{ logUris.SystemLog = append(logUris.SystemLog, SystemLogURI{
Type: SystemLogType(log.Type), Type: SystemLogType(log.Type),
Uri: log.Uri, URI: log.URI,
}) })
} }
} }
return logUris, resp.SupportInfoUri, resp.SystemBackupUri, nil return logUris, resp.SupportInfoURI, resp.SystemBackupURI, nil
} }
// GetSystemSupportInformation gets arbitrary device diagnostics information. // GetSystemSupportInformation gets arbitrary device diagnostics information.
@@ -745,7 +745,7 @@ func (c *Client) StartFirmwareUpgrade(
type StartFirmwareUpgradeResponse struct { type StartFirmwareUpgradeResponse struct {
XMLName xml.Name `xml:"StartFirmwareUpgradeResponse"` XMLName xml.Name `xml:"StartFirmwareUpgradeResponse"`
UploadUri string `xml:"UploadUri"` UploadURI string `xml:"UploadUri"`
UploadDelay string `xml:"UploadDelay"` UploadDelay string `xml:"UploadDelay"`
ExpectedDownTime string `xml:"ExpectedDownTime"` ExpectedDownTime string `xml:"ExpectedDownTime"`
} }
@@ -763,7 +763,7 @@ func (c *Client) StartFirmwareUpgrade(
return "", "", "", fmt.Errorf("StartFirmwareUpgrade failed: %w", err) return "", "", "", fmt.Errorf("StartFirmwareUpgrade failed: %w", err)
} }
return resp.UploadUri, resp.UploadDelay, resp.ExpectedDownTime, nil return resp.UploadURI, resp.UploadDelay, resp.ExpectedDownTime, nil
} }
// StartSystemRestore initiates a system restore from backed up configuration data. // StartSystemRestore initiates a system restore from backed up configuration data.
@@ -775,7 +775,7 @@ func (c *Client) StartSystemRestore(ctx context.Context) (uploadURI, expectedDow
type StartSystemRestoreResponse struct { type StartSystemRestoreResponse struct {
XMLName xml.Name `xml:"StartSystemRestoreResponse"` XMLName xml.Name `xml:"StartSystemRestoreResponse"`
UploadUri string `xml:"UploadUri"` UploadURI string `xml:"UploadUri"`
ExpectedDownTime string `xml:"ExpectedDownTime"` ExpectedDownTime string `xml:"ExpectedDownTime"`
} }
@@ -792,5 +792,5 @@ func (c *Client) StartSystemRestore(ctx context.Context) (uploadURI, expectedDow
return "", "", fmt.Errorf("StartSystemRestore failed: %w", err) return "", "", fmt.Errorf("StartSystemRestore failed: %w", err)
} }
return resp.UploadUri, resp.ExpectedDownTime, nil return resp.UploadURI, resp.ExpectedDownTime, nil
} }
+3 -3
View File
@@ -345,13 +345,13 @@ func TestStartFirmwareUpgrade(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
uploadUri, delay, downtime, err := client.StartFirmwareUpgrade(ctx) uploadURI, delay, downtime, err := client.StartFirmwareUpgrade(ctx)
if err != nil { if err != nil {
t.Fatalf("StartFirmwareUpgrade failed: %v", err) t.Fatalf("StartFirmwareUpgrade failed: %v", err)
} }
if uploadUri != "http://192.168.1.100/upload" { if uploadURI != "http://192.168.1.100/upload" {
t.Errorf("Expected upload URI http://192.168.1.100/upload, got %s", uploadUri) t.Errorf("Expected upload URI http://192.168.1.100/upload, got %s", uploadURI)
} }
if delay != "PT5S" { if delay != "PT5S" {
+6 -4
View File
@@ -8,7 +8,7 @@ import (
"github.com/0x524a/onvif-go/internal/soap" "github.com/0x524a/onvif-go/internal/soap"
) )
// ONVIF Specification: GetStorageConfigurations operation. // GetStorageConfigurations retrieves storage configurations. ONVIF Specification: GetStorageConfigurations operation.
func (c *Client) GetStorageConfigurations(ctx context.Context) ([]*StorageConfiguration, error) { func (c *Client) GetStorageConfigurations(ctx context.Context) ([]*StorageConfiguration, error) {
type GetStorageConfigurationsBody struct { type GetStorageConfigurationsBody struct {
XMLName xml.Name `xml:"tds:GetStorageConfigurations"` XMLName xml.Name `xml:"tds:GetStorageConfigurations"`
@@ -35,7 +35,7 @@ func (c *Client) GetStorageConfigurations(ctx context.Context) ([]*StorageConfig
return response.StorageConfigurations, nil return response.StorageConfigurations, nil
} }
// ONVIF Specification: GetStorageConfiguration operation. // GetStorageConfiguration retrieves a storage configuration. ONVIF Specification: GetStorageConfiguration operation.
func (c *Client) GetStorageConfiguration(ctx context.Context, token string) (*StorageConfiguration, error) { func (c *Client) GetStorageConfiguration(ctx context.Context, token string) (*StorageConfiguration, error) {
type GetStorageConfigurationBody struct { type GetStorageConfigurationBody struct {
XMLName xml.Name `xml:"tds:GetStorageConfiguration"` XMLName xml.Name `xml:"tds:GetStorageConfiguration"`
@@ -64,6 +64,7 @@ func (c *Client) GetStorageConfiguration(ctx context.Context, token string) (*St
return response.StorageConfiguration, nil return response.StorageConfiguration, nil
} }
// CreateStorageConfiguration creates a storage configuration.
// ONVIF Specification: CreateStorageConfiguration operation. // ONVIF Specification: CreateStorageConfiguration operation.
func (c *Client) CreateStorageConfiguration(ctx context.Context, config *StorageConfiguration) (string, error) { func (c *Client) CreateStorageConfiguration(ctx context.Context, config *StorageConfiguration) (string, error) {
type CreateStorageConfigurationBody struct { type CreateStorageConfigurationBody struct {
@@ -93,7 +94,7 @@ func (c *Client) CreateStorageConfiguration(ctx context.Context, config *Storage
return response.Token, nil return response.Token, nil
} }
// ONVIF Specification: SetStorageConfiguration operation. // SetStorageConfiguration sets a storage configuration. ONVIF Specification: SetStorageConfiguration operation.
func (c *Client) SetStorageConfiguration(ctx context.Context, config *StorageConfiguration) error { func (c *Client) SetStorageConfiguration(ctx context.Context, config *StorageConfiguration) error {
type SetStorageConfigurationBody struct { type SetStorageConfigurationBody struct {
XMLName xml.Name `xml:"tds:SetStorageConfiguration"` XMLName xml.Name `xml:"tds:SetStorageConfiguration"`
@@ -121,6 +122,7 @@ func (c *Client) SetStorageConfiguration(ctx context.Context, config *StorageCon
return nil return nil
} }
// DeleteStorageConfiguration deletes a storage configuration.
// ONVIF Specification: DeleteStorageConfiguration operation. // ONVIF Specification: DeleteStorageConfiguration operation.
func (c *Client) DeleteStorageConfiguration(ctx context.Context, token string) error { func (c *Client) DeleteStorageConfiguration(ctx context.Context, token string) error {
type DeleteStorageConfigurationBody struct { type DeleteStorageConfigurationBody struct {
@@ -149,7 +151,7 @@ func (c *Client) DeleteStorageConfiguration(ctx context.Context, token string) e
return nil return nil
} }
// ONVIF Specification: SetHashingAlgorithm operation. // SetHashingAlgorithm sets the hashing algorithm. ONVIF Specification: SetHashingAlgorithm operation.
func (c *Client) SetHashingAlgorithm(ctx context.Context, algorithm string) error { func (c *Client) SetHashingAlgorithm(ctx context.Context, algorithm string) error {
type SetHashingAlgorithmBody struct { type SetHashingAlgorithmBody struct {
XMLName xml.Name `xml:"tds:SetHashingAlgorithm"` XMLName xml.Name `xml:"tds:SetHashingAlgorithm"`
+6 -6
View File
@@ -147,8 +147,8 @@ func TestGetStorageConfigurations(t *testing.T) {
t.Errorf("Expected second config token 'storage-002', got '%s'", configs[1].Token) t.Errorf("Expected second config token 'storage-002', got '%s'", configs[1].Token)
} }
if configs[1].Data.StorageUri != "cifs://nas.local/recordings" { if configs[1].Data.StorageURI != "cifs://nas.local/recordings" {
t.Errorf("Expected second config URI 'cifs://nas.local/recordings', got '%s'", configs[1].Data.StorageUri) t.Errorf("Expected second config URI 'cifs://nas.local/recordings', got '%s'", configs[1].Data.StorageURI)
} }
} }
@@ -175,8 +175,8 @@ func TestGetStorageConfiguration(t *testing.T) {
t.Errorf("Expected config path '/var/media/storage1', got '%s'", config.Data.LocalPath) t.Errorf("Expected config path '/var/media/storage1', got '%s'", config.Data.LocalPath)
} }
if config.Data.StorageUri != "file:///var/media/storage1" { if config.Data.StorageURI != "file:///var/media/storage1" {
t.Errorf("Expected config URI 'file:///var/media/storage1', got '%s'", config.Data.StorageUri) t.Errorf("Expected config URI 'file:///var/media/storage1', got '%s'", config.Data.StorageURI)
} }
if config.Data.Type != "NFS" { if config.Data.Type != "NFS" {
@@ -198,7 +198,7 @@ func TestCreateStorageConfiguration(t *testing.T) {
Token: "storage-new", Token: "storage-new",
Data: StorageConfigurationData{ Data: StorageConfigurationData{
LocalPath: "/var/media/storage3", LocalPath: "/var/media/storage3",
StorageUri: "file:///var/media/storage3", StorageURI: "file:///var/media/storage3",
Type: "Local", Type: "Local",
}, },
} }
@@ -227,7 +227,7 @@ func TestSetStorageConfiguration(t *testing.T) {
Token: "storage-001", Token: "storage-001",
Data: StorageConfigurationData{ Data: StorageConfigurationData{
LocalPath: "/var/media/updated", LocalPath: "/var/media/updated",
StorageUri: "file:///var/media/updated", StorageURI: "file:///var/media/updated",
Type: "NFS", Type: "NFS",
}, },
} }
+8 -7
View File
@@ -8,7 +8,7 @@ import (
"github.com/0x524a/onvif-go/internal/soap" "github.com/0x524a/onvif-go/internal/soap"
) )
// ONVIF Specification: GetDot11Capabilities operation. // GetDot11Capabilities retrieves 802.11 capabilities. ONVIF Specification: GetDot11Capabilities operation.
func (c *Client) GetDot11Capabilities(ctx context.Context) (*Dot11Capabilities, error) { func (c *Client) GetDot11Capabilities(ctx context.Context) (*Dot11Capabilities, error) {
type GetDot11CapabilitiesBody struct { type GetDot11CapabilitiesBody struct {
XMLName xml.Name `xml:"tds:GetDot11Capabilities"` XMLName xml.Name `xml:"tds:GetDot11Capabilities"`
@@ -35,7 +35,7 @@ func (c *Client) GetDot11Capabilities(ctx context.Context) (*Dot11Capabilities,
return response.Capabilities, nil return response.Capabilities, nil
} }
// ONVIF Specification: GetDot11Status operation. // GetDot11Status retrieves 802.11 status. ONVIF Specification: GetDot11Status operation.
func (c *Client) GetDot11Status(ctx context.Context, interfaceToken string) (*Dot11Status, error) { func (c *Client) GetDot11Status(ctx context.Context, interfaceToken string) (*Dot11Status, error) {
type GetDot11StatusBody struct { type GetDot11StatusBody struct {
XMLName xml.Name `xml:"tds:GetDot11Status"` XMLName xml.Name `xml:"tds:GetDot11Status"`
@@ -64,7 +64,7 @@ func (c *Client) GetDot11Status(ctx context.Context, interfaceToken string) (*Do
return response.Status, nil return response.Status, nil
} }
// ONVIF Specification: GetDot1XConfiguration operation. // GetDot1XConfiguration retrieves an 802.1X configuration. ONVIF Specification: GetDot1XConfiguration operation.
func (c *Client) GetDot1XConfiguration(ctx context.Context, configToken string) (*Dot1XConfiguration, error) { func (c *Client) GetDot1XConfiguration(ctx context.Context, configToken string) (*Dot1XConfiguration, error) {
type GetDot1XConfigurationBody struct { type GetDot1XConfigurationBody struct {
XMLName xml.Name `xml:"tds:GetDot1XConfiguration"` XMLName xml.Name `xml:"tds:GetDot1XConfiguration"`
@@ -93,7 +93,7 @@ func (c *Client) GetDot1XConfiguration(ctx context.Context, configToken string)
return response.Dot1XConfiguration, nil return response.Dot1XConfiguration, nil
} }
// ONVIF Specification: GetDot1XConfigurations operation. // GetDot1XConfigurations retrieves all 802.1X configurations. ONVIF Specification: GetDot1XConfigurations operation.
func (c *Client) GetDot1XConfigurations(ctx context.Context) ([]*Dot1XConfiguration, error) { func (c *Client) GetDot1XConfigurations(ctx context.Context) ([]*Dot1XConfiguration, error) {
type GetDot1XConfigurationsBody struct { type GetDot1XConfigurationsBody struct {
XMLName xml.Name `xml:"tds:GetDot1XConfigurations"` XMLName xml.Name `xml:"tds:GetDot1XConfigurations"`
@@ -120,7 +120,7 @@ func (c *Client) GetDot1XConfigurations(ctx context.Context) ([]*Dot1XConfigurat
return response.Dot1XConfiguration, nil return response.Dot1XConfiguration, nil
} }
// ONVIF Specification: SetDot1XConfiguration operation. // SetDot1XConfiguration sets an 802.1X configuration. ONVIF Specification: SetDot1XConfiguration operation.
func (c *Client) SetDot1XConfiguration(ctx context.Context, config *Dot1XConfiguration) error { func (c *Client) SetDot1XConfiguration(ctx context.Context, config *Dot1XConfiguration) error {
type SetDot1XConfigurationBody struct { type SetDot1XConfigurationBody struct {
XMLName xml.Name `xml:"tds:SetDot1XConfiguration"` XMLName xml.Name `xml:"tds:SetDot1XConfiguration"`
@@ -148,7 +148,7 @@ func (c *Client) SetDot1XConfiguration(ctx context.Context, config *Dot1XConfigu
return nil return nil
} }
// ONVIF Specification: CreateDot1XConfiguration operation. // CreateDot1XConfiguration creates an 802.1X configuration. ONVIF Specification: CreateDot1XConfiguration operation.
func (c *Client) CreateDot1XConfiguration(ctx context.Context, config *Dot1XConfiguration) error { func (c *Client) CreateDot1XConfiguration(ctx context.Context, config *Dot1XConfiguration) error {
type CreateDot1XConfigurationBody struct { type CreateDot1XConfigurationBody struct {
XMLName xml.Name `xml:"tds:CreateDot1XConfiguration"` XMLName xml.Name `xml:"tds:CreateDot1XConfiguration"`
@@ -176,7 +176,7 @@ func (c *Client) CreateDot1XConfiguration(ctx context.Context, config *Dot1XConf
return nil return nil
} }
// ONVIF Specification: DeleteDot1XConfiguration operation. // DeleteDot1XConfiguration deletes an 802.1X configuration. ONVIF Specification: DeleteDot1XConfiguration operation.
func (c *Client) DeleteDot1XConfiguration(ctx context.Context, configToken string) error { func (c *Client) DeleteDot1XConfiguration(ctx context.Context, configToken string) error {
type DeleteDot1XConfigurationBody struct { type DeleteDot1XConfigurationBody struct {
XMLName xml.Name `xml:"tds:DeleteDot1XConfiguration"` XMLName xml.Name `xml:"tds:DeleteDot1XConfiguration"`
@@ -204,6 +204,7 @@ func (c *Client) DeleteDot1XConfiguration(ctx context.Context, configToken strin
return nil return nil
} }
// ScanAvailableDot11Networks scans for available 802.11 networks.
// ONVIF Specification: ScanAvailableDot11Networks operation. // ONVIF Specification: ScanAvailableDot11Networks operation.
func (c *Client) ScanAvailableDot11Networks( func (c *Client) ScanAvailableDot11Networks(
ctx context.Context, ctx context.Context,
+10 -4
View File
@@ -14,6 +14,9 @@ import (
const ( const (
// WS-Discovery multicast address. // WS-Discovery multicast address.
multicastAddr = "239.255.255.250:3702" multicastAddr = "239.255.255.250:3702"
// UUID generation constants.
uuidMod1000 = 1000
uuidMod10000 = 10000
// WS-Discovery probe message. // WS-Discovery probe message.
probeTemplate = `<?xml version="1.0" encoding="UTF-8"?> probeTemplate = `<?xml version="1.0" encoding="UTF-8"?>
@@ -136,7 +139,8 @@ func DiscoverWithOptions(ctx context.Context, timeout time.Duration, opts *Disco
// Collect responses // Collect responses
devices := make(map[string]*Device) devices := make(map[string]*Device)
buffer := make([]byte, 8192) const maxUDPPacketSize = 8192
buffer := make([]byte, maxUDPPacketSize)
// Read responses until timeout or context cancellation // Read responses until timeout or context cancellation
for { for {
@@ -225,12 +229,14 @@ func generateUUID() string {
return fmt.Sprintf("%d-%d-%d-%d-%d", return fmt.Sprintf("%d-%d-%d-%d-%d",
time.Now().UnixNano(), time.Now().UnixNano(),
time.Now().Unix(), time.Now().Unix(),
time.Now().UnixNano()%1000, time.Now().UnixNano()%uuidMod1000,
time.Now().Unix()%1000, time.Now().Unix()%uuidMod1000,
time.Now().UnixNano()%10000) time.Now().UnixNano()%uuidMod10000)
} }
// resolveNetworkInterface resolves a network interface by name or IP address. // resolveNetworkInterface resolves a network interface by name or IP address.
//
//nolint:gocognit // Network interface resolution has high complexity due to multiple validation paths
func resolveNetworkInterface(ifaceSpec string) (*net.Interface, error) { func resolveNetworkInterface(ifaceSpec string) (*net.Interface, error) {
// Try to get interface by name (e.g., "eth0", "wlan0") // Try to get interface by name (e.g., "eth0", "wlan0")
if iface, err := net.InterfaceByName(ifaceSpec); err == nil { if iface, err := net.InterfaceByName(ifaceSpec); err == nil {
+2
View File
@@ -12,6 +12,8 @@ import (
const imagingNamespace = "http://www.onvif.org/ver20/imaging/wsdl" const imagingNamespace = "http://www.onvif.org/ver20/imaging/wsdl"
// GetImagingSettings retrieves imaging settings for a video source. // GetImagingSettings retrieves imaging settings for a video source.
//
//nolint:funlen // GetImagingSettings has many statements due to parsing complex imaging settings
func (c *Client) GetImagingSettings(ctx context.Context, videoSourceToken string) (*ImagingSettings, error) { func (c *Client) GetImagingSettings(ctx context.Context, videoSourceToken string) (*ImagingSettings, error) {
endpoint := c.imagingEndpoint endpoint := c.imagingEndpoint
if endpoint == "" { if endpoint == "" {
+6 -5
View File
@@ -5,7 +5,7 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1" //nolint:gosec // SHA1 used for ONVIF digest authentication
"encoding/base64" "encoding/base64"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
@@ -42,14 +42,14 @@ type Fault struct {
// Security represents WS-Security header. // Security represents WS-Security header.
type Security struct { type Security struct {
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"` XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"` MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"` UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"`
} }
// UsernameToken represents a WS-Security username token. // UsernameToken represents a WS-Security username token.
type UsernameToken struct { type UsernameToken struct {
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"` XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
Username string `xml:"Username"` Username string `xml:"Username"`
Password Password `xml:"Password"` Password Password `xml:"Password"`
Nonce Nonce `xml:"Nonce"` Nonce Nonce `xml:"Nonce"`
@@ -195,7 +195,8 @@ func (c *Client) Call(ctx context.Context, endpoint, action string, request, res
// createSecurityHeader creates a WS-Security header with username token digest. // createSecurityHeader creates a WS-Security header with username token digest.
func (c *Client) createSecurityHeader() *Security { func (c *Client) createSecurityHeader() *Security {
// Generate nonce // Generate nonce
nonceBytes := make([]byte, 16) const nonceSize = 16
nonceBytes := make([]byte, nonceSize)
//nolint:errcheck // rand.Read always returns len(nonceBytes), nil for sufficient entropy //nolint:errcheck // rand.Read always returns len(nonceBytes), nil for sufficient entropy
_, _ = rand.Read(nonceBytes) _, _ = rand.Read(nonceBytes)
nonce := base64.StdEncoding.EncodeToString(nonceBytes) nonce := base64.StdEncoding.EncodeToString(nonceBytes)
@@ -204,7 +205,7 @@ func (c *Client) createSecurityHeader() *Security {
created := time.Now().UTC().Format(time.RFC3339) created := time.Now().UTC().Format(time.RFC3339)
// Calculate password digest: Base64(SHA1(nonce + created + password)) // Calculate password digest: Base64(SHA1(nonce + created + password))
hash := sha1.New() hash := sha1.New() //nolint:gosec // SHA1 required for ONVIF digest auth
hash.Write(nonceBytes) hash.Write(nonceBytes)
hash.Write([]byte(created)) hash.Write([]byte(created))
hash.Write([]byte(c.password)) hash.Write([]byte(c.password))
+30 -26
View File
@@ -28,6 +28,8 @@ func (c *Client) getMediaSoapClient() *soap.Client {
} }
// GetProfiles retrieves all media profiles. // GetProfiles retrieves all media profiles.
//
//nolint:funlen // GetProfiles has many statements due to parsing complex profile structures
func (c *Client) GetProfiles(ctx context.Context) ([]*Profile, error) { func (c *Client) GetProfiles(ctx context.Context) ([]*Profile, error) {
endpoint := c.mediaEndpoint endpoint := c.mediaEndpoint
if endpoint == "" { if endpoint == "" {
@@ -163,7 +165,7 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU
endpoint = c.endpoint endpoint = c.endpoint
} }
type GetStreamUri struct { type GetStreamURI struct {
XMLName xml.Name `xml:"trt:GetStreamUri"` XMLName xml.Name `xml:"trt:GetStreamUri"`
Xmlns string `xml:"xmlns:trt,attr"` Xmlns string `xml:"xmlns:trt,attr"`
Xmlnst string `xml:"xmlns:tt,attr"` Xmlnst string `xml:"xmlns:tt,attr"`
@@ -176,17 +178,17 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU
ProfileToken string `xml:"trt:ProfileToken"` ProfileToken string `xml:"trt:ProfileToken"`
} }
type GetStreamUriResponse struct { type GetStreamURIResponse struct {
XMLName xml.Name `xml:"GetStreamUriResponse"` XMLName xml.Name `xml:"GetStreamUriResponse"`
MediaUri struct { MediaURI struct {
Uri string `xml:"Uri"` URI string `xml:"Uri"`
InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"`
InvalidAfterReboot bool `xml:"InvalidAfterReboot"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"`
Timeout string `xml:"Timeout"` Timeout string `xml:"Timeout"`
} `xml:"MediaUri"` } `xml:"MediaUri"`
} }
req := GetStreamUri{ req := GetStreamURI{
Xmlns: mediaNamespace, Xmlns: mediaNamespace,
Xmlnst: "http://www.onvif.org/ver10/schema", Xmlnst: "http://www.onvif.org/ver10/schema",
ProfileToken: profileToken, ProfileToken: profileToken,
@@ -194,19 +196,19 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU
req.StreamSetup.Stream = "RTP-Unicast" req.StreamSetup.Stream = "RTP-Unicast"
req.StreamSetup.Transport.Protocol = "RTSP" req.StreamSetup.Transport.Protocol = "RTSP"
var resp GetStreamUriResponse var resp GetStreamURIResponse
username, password := c.GetCredentials() username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password) soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetStreamUri failed: %w", err) return nil, fmt.Errorf("GetStreamURI failed: %w", err)
} }
return &MediaURI{ return &MediaURI{
URI: resp.MediaUri.Uri, URI: resp.MediaURI.URI,
InvalidAfterConnect: resp.MediaUri.InvalidAfterConnect, InvalidAfterConnect: resp.MediaURI.InvalidAfterConnect,
InvalidAfterReboot: resp.MediaUri.InvalidAfterReboot, InvalidAfterReboot: resp.MediaURI.InvalidAfterReboot,
}, nil }, nil
} }
@@ -217,40 +219,40 @@ func (c *Client) GetSnapshotURI(ctx context.Context, profileToken string) (*Medi
endpoint = c.endpoint endpoint = c.endpoint
} }
type GetSnapshotUri struct { type GetSnapshotURI struct {
XMLName xml.Name `xml:"trt:GetSnapshotUri"` XMLName xml.Name `xml:"trt:GetSnapshotUri"`
Xmlns string `xml:"xmlns:trt,attr"` Xmlns string `xml:"xmlns:trt,attr"`
ProfileToken string `xml:"trt:ProfileToken"` ProfileToken string `xml:"trt:ProfileToken"`
} }
type GetSnapshotUriResponse struct { type GetSnapshotURIResponse struct {
XMLName xml.Name `xml:"GetSnapshotUriResponse"` XMLName xml.Name `xml:"GetSnapshotUriResponse"`
MediaUri struct { MediaURI struct {
Uri string `xml:"Uri"` URI string `xml:"Uri"`
InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"`
InvalidAfterReboot bool `xml:"InvalidAfterReboot"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"`
Timeout string `xml:"Timeout"` Timeout string `xml:"Timeout"`
} `xml:"MediaUri"` } `xml:"MediaUri"`
} }
req := GetSnapshotUri{ req := GetSnapshotURI{
Xmlns: mediaNamespace, Xmlns: mediaNamespace,
ProfileToken: profileToken, ProfileToken: profileToken,
} }
var resp GetSnapshotUriResponse var resp GetSnapshotURIResponse
username, password := c.GetCredentials() username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password) soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetSnapshotUri failed: %w", err) return nil, fmt.Errorf("GetSnapshotURI failed: %w", err)
} }
return &MediaURI{ return &MediaURI{
URI: resp.MediaUri.Uri, URI: resp.MediaURI.URI,
InvalidAfterConnect: resp.MediaUri.InvalidAfterConnect, InvalidAfterConnect: resp.MediaURI.InvalidAfterConnect,
InvalidAfterReboot: resp.MediaUri.InvalidAfterReboot, InvalidAfterReboot: resp.MediaURI.InvalidAfterReboot,
}, nil }, nil
} }
@@ -637,7 +639,7 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService
type GetServiceCapabilitiesResponse struct { type GetServiceCapabilitiesResponse struct {
XMLName xml.Name `xml:"GetServiceCapabilitiesResponse"` XMLName xml.Name `xml:"GetServiceCapabilitiesResponse"`
Capabilities struct { Capabilities struct {
SnapshotUri bool `xml:"SnapshotUri,attr"` SnapshotURI bool `xml:"SnapshotUri,attr"`
Rotation bool `xml:"Rotation,attr"` Rotation bool `xml:"Rotation,attr"`
VideoSourceMode bool `xml:"VideoSourceMode,attr"` VideoSourceMode bool `xml:"VideoSourceMode,attr"`
OSD bool `xml:"OSD,attr"` OSD bool `xml:"OSD,attr"`
@@ -648,8 +650,8 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService
} `xml:"ProfileCapabilities"` } `xml:"ProfileCapabilities"`
StreamingCapabilities *struct { StreamingCapabilities *struct {
RTPMulticast bool `xml:"RTPMulticast,attr"` RTPMulticast bool `xml:"RTPMulticast,attr"`
RTP_TCP bool `xml:"RTP_TCP,attr"` RTPTCP bool `xml:"RTP_TCP,attr"`
RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP,attr"` RTPRTSPTCP bool `xml:"RTP_RTSP_TCP,attr"`
} `xml:"StreamingCapabilities"` } `xml:"StreamingCapabilities"`
} `xml:"Capabilities"` } `xml:"Capabilities"`
} }
@@ -668,7 +670,7 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService
} }
caps := &MediaServiceCapabilities{ caps := &MediaServiceCapabilities{
SnapshotUri: resp.Capabilities.SnapshotUri, SnapshotURI: resp.Capabilities.SnapshotURI,
Rotation: resp.Capabilities.Rotation, Rotation: resp.Capabilities.Rotation,
VideoSourceMode: resp.Capabilities.VideoSourceMode, VideoSourceMode: resp.Capabilities.VideoSourceMode,
OSD: resp.Capabilities.OSD, OSD: resp.Capabilities.OSD,
@@ -682,14 +684,16 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService
if resp.Capabilities.StreamingCapabilities != nil { if resp.Capabilities.StreamingCapabilities != nil {
caps.RTPMulticast = resp.Capabilities.StreamingCapabilities.RTPMulticast caps.RTPMulticast = resp.Capabilities.StreamingCapabilities.RTPMulticast
caps.RTP_TCP = resp.Capabilities.StreamingCapabilities.RTP_TCP caps.RTPTCP = resp.Capabilities.StreamingCapabilities.RTPTCP
caps.RTP_RTSP_TCP = resp.Capabilities.StreamingCapabilities.RTP_RTSP_TCP caps.RTPRTSPTCP = resp.Capabilities.StreamingCapabilities.RTPRTSPTCP
} }
return caps, nil return caps, nil
} }
// GetVideoEncoderConfigurationOptions retrieves available options for video encoder configuration. // GetVideoEncoderConfigurationOptions retrieves available options for video encoder configuration.
//
//nolint:funlen // GetVideoEncoderConfigurationOptions has many statements due to parsing complex encoder options
func (c *Client) GetVideoEncoderConfigurationOptions(ctx context.Context, configurationToken string) (*VideoEncoderConfigurationOptions, error) { func (c *Client) GetVideoEncoderConfigurationOptions(ctx context.Context, configurationToken string) (*VideoEncoderConfigurationOptions, error) {
endpoint := c.mediaEndpoint endpoint := c.mediaEndpoint
if endpoint == "" { if endpoint == "" {
+4 -4
View File
@@ -74,11 +74,11 @@ func TestGetMediaServiceCapabilities_Bosch(t *testing.T) {
if !capabilities.RTPMulticast { if !capabilities.RTPMulticast {
t.Error("Expected RTPMulticast=true (Bosch FLEXIDOME)") t.Error("Expected RTPMulticast=true (Bosch FLEXIDOME)")
} }
if !capabilities.RTP_RTSP_TCP { if !capabilities.RTPRTSPTCP {
t.Error("Expected RTP_RTSP_TCP=true (Bosch FLEXIDOME)") t.Error("Expected RTPRTSPTCP=true (Bosch FLEXIDOME)")
} }
if capabilities.SnapshotUri { if capabilities.SnapshotURI {
t.Error("Expected SnapshotUri=false (Bosch FLEXIDOME)") t.Error("Expected SnapshotURI=false (Bosch FLEXIDOME)")
} }
if !capabilities.Rotation { if !capabilities.Rotation {
t.Error("Expected Rotation=true (Bosch FLEXIDOME)") t.Error("Expected Rotation=true (Bosch FLEXIDOME)")
+2 -2
View File
@@ -467,8 +467,8 @@ func TestGetMediaServiceCapabilities(t *testing.T) {
t.Fatalf("GetMediaServiceCapabilities() failed: %v", err) t.Fatalf("GetMediaServiceCapabilities() failed: %v", err)
} }
if !caps.SnapshotUri { if !caps.SnapshotURI {
t.Error("Expected SnapshotUri to be true") t.Error("Expected SnapshotURI to be true")
} }
if caps.MaximumNumberOfProfiles != 10 { if caps.MaximumNumberOfProfiles != 10 {
+6 -6
View File
@@ -17,7 +17,7 @@ type GetDeviceInformationResponse struct {
Model string `xml:"Model"` Model string `xml:"Model"`
FirmwareVersion string `xml:"FirmwareVersion"` FirmwareVersion string `xml:"FirmwareVersion"`
SerialNumber string `xml:"SerialNumber"` SerialNumber string `xml:"SerialNumber"`
HardwareId string `xml:"HardwareId"` HardwareID string `xml:"HardwareId"`
} }
// GetCapabilitiesResponse represents GetCapabilities response. // GetCapabilitiesResponse represents GetCapabilities response.
@@ -110,8 +110,8 @@ type MediaCapabilities struct {
// StreamingCapabilities represents streaming capabilities. // StreamingCapabilities represents streaming capabilities.
type StreamingCapabilities struct { type StreamingCapabilities struct {
RTPMulticast bool `xml:"RTPMulticast,attr"` RTPMulticast bool `xml:"RTPMulticast,attr"`
RTP_TCP bool `xml:"RTP_TCP,attr"` RTPTCP bool `xml:"RTP_TCP,attr"`
RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP,attr"` RTPRTSPTCP bool `xml:"RTP_RTSP_TCP,attr"`
} }
// PTZCapabilities represents PTZ service capabilities. // PTZCapabilities represents PTZ service capabilities.
@@ -153,7 +153,7 @@ func (s *Server) HandleGetDeviceInformation(body interface{}) (interface{}, erro
Model: s.config.DeviceInfo.Model, Model: s.config.DeviceInfo.Model,
FirmwareVersion: s.config.DeviceInfo.FirmwareVersion, FirmwareVersion: s.config.DeviceInfo.FirmwareVersion,
SerialNumber: s.config.DeviceInfo.SerialNumber, SerialNumber: s.config.DeviceInfo.SerialNumber,
HardwareId: s.config.DeviceInfo.HardwareID, HardwareID: s.config.DeviceInfo.HardwareID,
}, nil }, nil
} }
@@ -204,8 +204,8 @@ func (s *Server) HandleGetCapabilities(body interface{}) (interface{}, error) {
XAddr: baseURL + "/media_service", XAddr: baseURL + "/media_service",
StreamingCapabilities: &StreamingCapabilities{ StreamingCapabilities: &StreamingCapabilities{
RTPMulticast: false, RTPMulticast: false,
RTP_TCP: true, RTPTCP: true,
RTP_RTSP_TCP: true, RTPRTSPTCP: true,
}, },
}, },
} }
+10 -10
View File
@@ -28,7 +28,7 @@ func TestHandleGetDeviceInformation(t *testing.T) {
{"Model", deviceResp.Model, config.DeviceInfo.Model}, {"Model", deviceResp.Model, config.DeviceInfo.Model},
{"FirmwareVersion", deviceResp.FirmwareVersion, config.DeviceInfo.FirmwareVersion}, {"FirmwareVersion", deviceResp.FirmwareVersion, config.DeviceInfo.FirmwareVersion},
{"SerialNumber", deviceResp.SerialNumber, config.DeviceInfo.SerialNumber}, {"SerialNumber", deviceResp.SerialNumber, config.DeviceInfo.SerialNumber},
{"HardwareId", deviceResp.HardwareId, config.DeviceInfo.HardwareID}, {"HardwareID", deviceResp.HardwareID, config.DeviceInfo.HardwareID},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -162,7 +162,7 @@ func TestGetDeviceInformationResponseXML(t *testing.T) {
Model: "TestModel", Model: "TestModel",
FirmwareVersion: "1.0.0", FirmwareVersion: "1.0.0",
SerialNumber: "SN123", SerialNumber: "SN123",
HardwareId: "HW001", HardwareID: "HW001",
} }
// Marshal to XML // Marshal to XML
@@ -209,8 +209,8 @@ func TestCapabilitiesStructure(t *testing.T) {
XAddr: "http://localhost:8080/onvif/media_service", XAddr: "http://localhost:8080/onvif/media_service",
StreamingCapabilities: &StreamingCapabilities{ StreamingCapabilities: &StreamingCapabilities{
RTPMulticast: true, RTPMulticast: true,
RTP_TCP: true, RTPTCP: true,
RTP_RTSP_TCP: true, RTPRTSPTCP: true,
}, },
}, },
} }
@@ -239,8 +239,8 @@ func TestMediaCapabilitiesStructure(t *testing.T) {
XAddr: "http://localhost:8080/onvif/media_service", XAddr: "http://localhost:8080/onvif/media_service",
StreamingCapabilities: &StreamingCapabilities{ StreamingCapabilities: &StreamingCapabilities{
RTPMulticast: true, RTPMulticast: true,
RTP_TCP: true, RTPTCP: true,
RTP_RTSP_TCP: true, RTPRTSPTCP: true,
}, },
} }
@@ -251,10 +251,10 @@ func TestMediaCapabilitiesStructure(t *testing.T) {
if !caps.StreamingCapabilities.RTPMulticast { if !caps.StreamingCapabilities.RTPMulticast {
t.Error("RTP Multicast should be supported") t.Error("RTP Multicast should be supported")
} }
if !caps.StreamingCapabilities.RTP_TCP { if !caps.StreamingCapabilities.RTPTCP {
t.Error("RTP TCP should be supported") t.Error("RTP TCP should be supported")
} }
if !caps.StreamingCapabilities.RTP_RTSP_TCP { if !caps.StreamingCapabilities.RTPRTSPTCP {
t.Error("RTSP should be supported") t.Error("RTSP should be supported")
} }
} }
@@ -368,8 +368,8 @@ func TestGetCapabilitiesResponse(t *testing.T) {
XAddr: "http://localhost:8080/media", XAddr: "http://localhost:8080/media",
StreamingCapabilities: &StreamingCapabilities{ StreamingCapabilities: &StreamingCapabilities{
RTPMulticast: true, RTPMulticast: true,
RTP_TCP: true, RTPTCP: true,
RTP_RTSP_TCP: true, RTPRTSPTCP: true,
}, },
}, },
} }
+2
View File
@@ -266,6 +266,8 @@ func (s *Server) HandleGetImagingSettings(body interface{}) (interface{}, error)
} }
// HandleSetImagingSettings handles SetImagingSettings request. // HandleSetImagingSettings handles SetImagingSettings request.
//
//nolint:gocyclo // SetImagingSettings has high complexity due to multiple validation and update paths
func (s *Server) HandleSetImagingSettings(body interface{}) (interface{}, error) { func (s *Server) HandleSetImagingSettings(body interface{}) (interface{}, error) {
var req SetImagingSettingsRequest var req SetImagingSettingsRequest
if err := unmarshalBody(body, &req); err != nil { if err := unmarshalBody(body, &req); err != nil {
+9 -9
View File
@@ -138,12 +138,12 @@ type IPAddress struct {
// GetStreamURIResponse represents GetStreamURI response. // GetStreamURIResponse represents GetStreamURI response.
type GetStreamURIResponse struct { type GetStreamURIResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetStreamURIResponse"` XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetStreamURIResponse"`
MediaUri MediaUri `xml:"MediaUri"` MediaURI MediaURI `xml:"MediaUri"`
} }
// MediaUri represents a media URI. // MediaURI represents a media URI.
type MediaUri struct { type MediaURI struct {
Uri string `xml:"Uri"` URI string `xml:"Uri"`
InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"`
InvalidAfterReboot bool `xml:"InvalidAfterReboot"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"`
Timeout string `xml:"Timeout"` Timeout string `xml:"Timeout"`
@@ -152,7 +152,7 @@ type MediaUri struct {
// GetSnapshotURIResponse represents GetSnapshotURI response. // GetSnapshotURIResponse represents GetSnapshotURI response.
type GetSnapshotURIResponse struct { type GetSnapshotURIResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURIResponse"` XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURIResponse"`
MediaUri MediaUri `xml:"MediaUri"` MediaURI MediaURI `xml:"MediaUri"`
} }
// GetVideoSourcesResponse represents GetVideoSources response. // GetVideoSourcesResponse represents GetVideoSources response.
@@ -287,8 +287,8 @@ func (s *Server) HandleGetStreamURI(body interface{}) (interface{}, error) {
} }
return &GetStreamURIResponse{ return &GetStreamURIResponse{
MediaUri: MediaUri{ MediaURI: MediaURI{
Uri: uri, URI: uri,
InvalidAfterConnect: false, InvalidAfterConnect: false,
InvalidAfterReboot: true, InvalidAfterReboot: true,
Timeout: "PT60S", Timeout: "PT60S",
@@ -333,8 +333,8 @@ func (s *Server) HandleGetSnapshotURI(body interface{}) (interface{}, error) {
host, s.config.Port, s.config.BasePath, req.ProfileToken) host, s.config.Port, s.config.BasePath, req.ProfileToken)
return &GetSnapshotURIResponse{ return &GetSnapshotURIResponse{
MediaUri: MediaUri{ MediaURI: MediaURI{
Uri: uri, URI: uri,
InvalidAfterConnect: false, InvalidAfterConnect: false,
InvalidAfterReboot: true, InvalidAfterReboot: true,
Timeout: "PT5S", Timeout: "PT5S",
+4 -4
View File
@@ -52,15 +52,15 @@ func TestHandleGetStreamURI(t *testing.T) {
t.Fatalf("Response is not GetStreamURIResponse, got %T", resp) t.Fatalf("Response is not GetStreamURIResponse, got %T", resp)
} }
if streamResp.MediaUri.Uri == "" { if streamResp.MediaURI.URI == "" {
t.Error("Stream URI is empty") t.Error("Stream URI is empty")
return return
} }
// URI should contain stream path // URI should contain stream path
if !contains(streamResp.MediaUri.Uri, "rtsp://") { if !contains(streamResp.MediaURI.URI, "rtsp://") {
t.Errorf("Invalid stream URI format: %s", streamResp.MediaUri.Uri) t.Errorf("Invalid stream URI format: %s", streamResp.MediaURI.URI)
} }
} }
@@ -80,7 +80,7 @@ func TestHandleGetSnapshotURI(t *testing.T) {
t.Fatalf("Response is not GetSnapshotURIResponse, got %T", resp) t.Fatalf("Response is not GetSnapshotURIResponse, got %T", resp)
} }
if snapResp.MediaUri.Uri == "" { if snapResp.MediaURI.URI == "" {
t.Error("Snapshot URI is empty") t.Error("Snapshot URI is empty")
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@ package soap
import ( import (
"bytes" "bytes"
"crypto/sha1" "crypto/sha1" //nolint:gosec // SHA1 used for ONVIF digest authentication
"encoding/base64" "encoding/base64"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
@@ -123,7 +123,7 @@ func (h *Handler) authenticate(envelope *originsoap.Envelope) bool {
} }
// Calculate expected digest // Calculate expected digest
hash := sha1.New() hash := sha1.New() //nolint:gosec // SHA1 required for ONVIF digest auth
hash.Write(nonce) hash.Write(nonce)
hash.Write([]byte(token.Created)) hash.Write([]byte(token.Created))
hash.Write([]byte(h.password)) hash.Write([]byte(h.password))
+2
View File
@@ -228,6 +228,8 @@ type WDRSettings struct {
} }
// DefaultConfig returns a default server configuration with a multi-lens camera setup. // DefaultConfig returns a default server configuration with a multi-lens camera setup.
//
//nolint:funlen // DefaultConfig has many statements due to comprehensive default configuration
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
Host: "0.0.0.0", Host: "0.0.0.0",
+1 -1
View File
@@ -35,7 +35,7 @@ type CameraCapture struct {
// LoadCaptureFromArchive loads all captured exchanges from a tar.gz archive. // LoadCaptureFromArchive loads all captured exchanges from a tar.gz archive.
func LoadCaptureFromArchive(archivePath string) (*CameraCapture, error) { func LoadCaptureFromArchive(archivePath string) (*CameraCapture, error) {
file, err := os.Open(archivePath) file, err := os.Open(archivePath) //nolint:gosec // File path is from test data, safe
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open archive: %w", err) return nil, fmt.Errorf("failed to open archive: %w", err)
} }
+13 -13
View File
@@ -106,12 +106,12 @@ type SecurityCapabilities struct {
// StreamingCapabilities represents streaming capabilities. // StreamingCapabilities represents streaming capabilities.
type StreamingCapabilities struct { type StreamingCapabilities struct {
RTPMulticast bool RTPMulticast bool
RTP_TCP bool RTPTCP bool
RTP_RTSP_TCP bool RTPRTSPTCP bool
Extension *StreamingCapabilitiesExtension Extension *StreamingCapabilitiesExtension
} }
// Extension types. // CapabilitiesExtension represents extension types for capabilities.
type CapabilitiesExtension struct{} type CapabilitiesExtension struct{}
type NetworkCapabilitiesExtension struct{} type NetworkCapabilitiesExtension struct{}
type SystemCapabilitiesExtension struct{} type SystemCapabilitiesExtension struct{}
@@ -324,7 +324,7 @@ type ProfileExtension struct{}
// MediaServiceCapabilities represents media service capabilities. // MediaServiceCapabilities represents media service capabilities.
type MediaServiceCapabilities struct { type MediaServiceCapabilities struct {
SnapshotUri bool SnapshotURI bool
Rotation bool Rotation bool
VideoSourceMode bool VideoSourceMode bool
OSD bool OSD bool
@@ -332,8 +332,8 @@ type MediaServiceCapabilities struct {
EXICompression bool EXICompression bool
MaximumNumberOfProfiles int MaximumNumberOfProfiles int
RTPMulticast bool RTPMulticast bool
RTP_TCP bool RTPTCP bool
RTP_RTSP_TCP bool RTPRTSPTCP bool
} }
// VideoEncoderConfigurationOptions represents available options for video encoder configuration. // VideoEncoderConfigurationOptions represents available options for video encoder configuration.
@@ -995,15 +995,15 @@ type SupportInformation struct {
String string String string
} }
// SystemLogUriList represents system log URIs. // SystemLogURIList represents system log URIs.
type SystemLogUriList struct { type SystemLogURIList struct {
SystemLog []SystemLogUri SystemLog []SystemLogURI
} }
// SystemLogUri represents system log URI. // SystemLogURI represents system log URI.
type SystemLogUri struct { type SystemLogURI struct {
Type SystemLogType Type SystemLogType
Uri string URI string
} }
// NetworkZeroConfiguration represents zero-configuration. // NetworkZeroConfiguration represents zero-configuration.
@@ -1187,7 +1187,7 @@ type StorageConfiguration struct {
type StorageConfigurationData struct { type StorageConfigurationData struct {
Type string Type string
LocalPath string LocalPath string
StorageUri string StorageURI string
User *UserCredential User *UserCredential
CertPathValidationPolicyID string CertPathValidationPolicyID string
} }