diff --git a/client.go b/client.go index 3d23aae..7077823 100644 --- a/client.go +++ b/client.go @@ -2,7 +2,7 @@ package onvif import ( "context" - "crypto/md5" + "crypto/md5" //nolint:gosec // MD5 used for ONVIF digest authentication "crypto/rand" "crypto/tls" "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. func WithInsecureSkipVerify() ClientOption { return func(c *Client) { if transport, ok := c.httpClient.Transport.(*http.Transport); ok { if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} + transport.TLSClientConfig = &tls.Config{ //nolint:gosec // InsecureSkipVerify is intentional for testing + } } transport.TLSClientConfig.InsecureSkipVerify = true } @@ -240,6 +242,7 @@ func (c *Client) GetCredentials() (username, password string) { 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). func (c *Client) DownloadFile(ctx context.Context, downloadURL string) ([]byte, error) { // 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 bodyPreview, _ := io.ReadAll(resp.Body) bodyStr := string(bodyPreview) - if len(bodyStr) > 200 { - bodyStr = bodyStr[:200] + "..." + const maxBodyPreview = 200 + if len(bodyStr) > maxBodyPreview { + bodyStr = bodyStr[:maxBodyPreview] + "..." } // 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 bodyPreview, _ := io.ReadAll(resp.Body) bodyStr := string(bodyPreview) - if len(bodyStr) > 200 { - bodyStr = bodyStr[:200] + "..." + const maxBodyPreview = 200 + if len(bodyStr) > maxBodyPreview { + bodyStr = bodyStr[:maxBodyPreview] + "..." } 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{} { // 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)) return h.Sum(nil) diff --git a/client_test.go b/client_test.go index 9fcf386..b305a81 100644 --- a/client_test.go +++ b/client_test.go @@ -1013,7 +1013,7 @@ func TestDigestAuthTransport(t *testing.T) { 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 { t.Fatalf("NewRequest() failed: %v", err) } @@ -1358,7 +1358,7 @@ func TestDigestAuthTransportConcurrency(t *testing.T) { for i := 0; i < numRequests; i++ { 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 { errors <- fmt.Errorf("request %d: %w", id, fmt.Errorf("%w", ErrTestRequestNewFailed)) done <- true diff --git a/cmd/generate-tests/main.go b/cmd/generate-tests/main.go index 8f2c82a..c0da36c 100644 --- a/cmd/generate-tests/main.go +++ b/cmd/generate-tests/main.go @@ -135,6 +135,7 @@ type AdditionalTest struct { Code string } +//nolint:funlen // Main function has many statements due to test generation logic func main() { flag.Parse() @@ -215,7 +216,7 @@ func main() { // Create output file 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 { log.Fatalf("Failed to create output file: %v", err) } diff --git a/cmd/onvif-cli/ascii.go b/cmd/onvif-cli/ascii.go index 373fb35..43a9d58 100644 --- a/cmd/onvif-cli/ascii.go +++ b/cmd/onvif-cli/ascii.go @@ -17,11 +17,21 @@ type ASCIIConfig struct { 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. func DefaultASCIIConfig() ASCIIConfig { return ASCIIConfig{ - Width: 120, - Height: 40, + Width: defaultASCIIWidth, + Height: defaultASCIIHeight, Invert: false, Quality: "medium", } @@ -46,7 +56,7 @@ var ( '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) { // Decode image from bytes 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. +// +//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) { // Validate configuration if config.Width <= 0 { @@ -141,9 +153,9 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) ( // Uses standard luminance formula. func calculateBrightness(r, g, b uint32) int { // Convert 16-bit color to 8-bit - r8 := uint8(r >> 8) - g8 := uint8(g >> 8) - b8 := uint8(b >> 8) + r8 := uint8(r >> 8) //nolint:gosec // Color values are clamped to valid range + g8 := uint8(g >> 8) //nolint:gosec // Color values are clamped to valid range + b8 := uint8(b >> 8) //nolint:gosec // Color values are clamped to valid range // Use standard brightness calculation // https://en.wikipedia.org/wiki/Relative_luminance diff --git a/cmd/onvif-cli/main.go b/cmd/onvif-cli/main.go index 1e0a977..0933cdd 100644 --- a/cmd/onvif-cli/main.go +++ b/cmd/onvif-cli/main.go @@ -177,6 +177,8 @@ func (c *CLI) discoverCameras() { } // 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) { // Get list of available interfaces interfaces, err := discovery.ListNetworkInterfaces() @@ -1471,6 +1473,7 @@ func (c *CLI) advancedImagingSettings(ctx context.Context, videoSourceToken stri 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) { fmt.Println("📷 Capture Snapshot as ASCII Preview") fmt.Println("===================================") @@ -1595,7 +1598,7 @@ func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) { if filename == "" { 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) } else { fmt.Printf("✅ Snapshot saved to %s\n", filename) diff --git a/cmd/onvif-diagnostics/main.go b/cmd/onvif-diagnostics/main.go index 8cfed6c..4b60506 100644 --- a/cmd/onvif-diagnostics/main.go +++ b/cmd/onvif-diagnostics/main.go @@ -145,6 +145,7 @@ var ( 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() { flag.Parse() @@ -191,7 +192,7 @@ func main() { if *captureXML { timestamp := time.Now().Format("20060102-150405") 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) } @@ -876,15 +877,20 @@ func saveReport(report *CameraReport, filename string) error { 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 nil } +//nolint:unparam // args parameter is kept for printf-style consistency, even though currently unused 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{}) { @@ -1011,7 +1017,7 @@ func (t *LoggingTransport) saveCapture(capture *XMLCapture) { 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) } @@ -1134,7 +1140,7 @@ func createTarGz(sourceDir, archivePath string) error { // If it's a file, write its content 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 { return fmt.Errorf("failed to open file: %w", err) } diff --git a/cmd/onvif-quick/main.go b/cmd/onvif-quick/main.go index adcea91..cb85e9a 100644 --- a/cmd/onvif-quick/main.go +++ b/cmd/onvif-quick/main.go @@ -12,6 +12,16 @@ import ( "github.com/0x524a/onvif-go/discovery" ) +const ( + defaultUsername = "admin" + defaultTimeout = 10 + defaultRetryDelay = 5 + ptzTimeout = 30 + ptzStepSize = 2 + ptzSpeed = 0.5 + maxBodyPreview = 200 +) + func main() { reader := bufio.NewReader(os.Stdin) @@ -81,9 +91,9 @@ func discoverCameras() { fmt.Printf(" %d. %s (%v)\n", i+1, iface.Name, iface.Addresses) } - fmt.Print("\nEnter interface name or IP: ") - //nolint:errcheck // ReadString error on stdin is rare and not critical for CLI - ifaceInput, _ := reader.ReadString('\n') + fmt.Print("\nEnter interface name or IP: ") + //nolint:errcheck // ReadString error on stdin is rare and not critical for CLI + ifaceInput, _ := reader.ReadString('\n') ifaceInput = strings.TrimSpace(ifaceInput) if ifaceInput != "" { @@ -97,7 +107,7 @@ func discoverCameras() { opts = &discovery.DiscoverOptions{} } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second) defer cancel() devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts) @@ -172,7 +182,7 @@ func connectAndShowInfo() { username, _ := reader.ReadString('\n') username = strings.TrimSpace(username) if username == "" { - username = "admin" + username = defaultUsername } fmt.Print("Password: ") @@ -223,6 +233,7 @@ func connectAndShowInfo() { } } +//nolint:gocyclo // PTZ demo function has high complexity due to multiple control paths func ptzDemo() { reader := bufio.NewReader(os.Stdin) @@ -236,7 +247,7 @@ func ptzDemo() { username, _ := reader.ReadString('\n') username = strings.TrimSpace(username) if username == "" { - username = "admin" + username = defaultUsername } fmt.Print("Password: ") @@ -302,11 +313,11 @@ func ptzDemo() { case "1": velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.5, Y: 0.0}} 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": - 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": - 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": position = &onvif.PTZVector{PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0}} default: @@ -316,7 +327,7 @@ func ptzDemo() { } if velocity != nil { - timeout := "PT2S" + timeout := fmt.Sprintf("PT%dS", ptzStepSize) err = client.ContinuousMove(ctx, profileToken, velocity, &timeout) if err != nil { fmt.Printf("❌ Error: %v\n", err) @@ -353,7 +364,7 @@ func getStreamURLs() { username, _ := reader.ReadString('\n') username = strings.TrimSpace(username) if username == "" { - username = "admin" + username = defaultUsername } fmt.Print("Password: ") diff --git a/cmd/onvif-server/main.go b/cmd/onvif-server/main.go index b884f62..24e8d7a 100644 --- a/cmd/onvif-server/main.go +++ b/cmd/onvif-server/main.go @@ -17,6 +17,7 @@ var ( version = "1.0.0" ) +//nolint:funlen // Main function has many statements due to server setup and configuration func main() { // Define command-line flags host := flag.String("host", "0.0.0.0", "Server host address") diff --git a/device.go b/device.go index 9cc9efc..066b068 100644 --- a/device.go +++ b/device.go @@ -50,6 +50,8 @@ func (c *Client) GetDeviceInformation(ctx context.Context) (*DeviceInformation, } // 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) { type GetCapabilities struct { XMLName xml.Name `xml:"tds:GetCapabilities"` @@ -110,8 +112,8 @@ func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) { XAddr string `xml:"XAddr"` StreamingCapabilities *struct { RTPMulticast bool `xml:"RTPMulticast"` - RTP_TCP bool `xml:"RTP_TCP"` - RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP"` + RTPTCP bool `xml:"RTP_TCP"` + RTPRTSPTCP bool `xml:"RTP_RTSP_TCP"` } `xml:"StreamingCapabilities"` } `xml:"Media"` PTZ *struct { @@ -214,8 +216,8 @@ func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) { if resp.Capabilities.Media.StreamingCapabilities != nil { capabilities.Media.StreamingCapabilities = &StreamingCapabilities{ RTPMulticast: resp.Capabilities.Media.StreamingCapabilities.RTPMulticast, - RTP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_TCP, - RTP_RTSP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_RTSP_TCP, + RTPTCP: resp.Capabilities.Media.StreamingCapabilities.RTPTCP, + RTPRTSPTCP: resp.Capabilities.Media.StreamingCapabilities.RTPRTSPTCP, } } } diff --git a/device_additional.go b/device_additional.go index 0dd1e84..57ea0dd 100644 --- a/device_additional.go +++ b/device_additional.go @@ -8,7 +8,7 @@ import ( "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) { type GetGeoLocationBody struct { XMLName xml.Name `xml:"tds:GetGeoLocation"` @@ -35,7 +35,7 @@ func (c *Client) GetGeoLocation(ctx context.Context) ([]LocationEntity, error) { 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 { type SetGeoLocationBody struct { XMLName xml.Name `xml:"tds:SetGeoLocation"` @@ -63,7 +63,7 @@ func (c *Client) SetGeoLocation(ctx context.Context, location []LocationEntity) 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 { type DeleteGeoLocationBody struct { XMLName xml.Name `xml:"tds:DeleteGeoLocation"` @@ -91,7 +91,7 @@ func (c *Client) DeleteGeoLocation(ctx context.Context, location []LocationEntit 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) { type GetDPAddressesBody struct { XMLName xml.Name `xml:"tds:GetDPAddresses"` @@ -118,7 +118,7 @@ func (c *Client) GetDPAddresses(ctx context.Context) ([]NetworkHost, error) { 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 { type SetDPAddressesBody struct { XMLName xml.Name `xml:"tds:SetDPAddresses"` @@ -146,7 +146,7 @@ func (c *Client) SetDPAddresses(ctx context.Context, dpAddress []NetworkHost) er return nil } -// ONVIF Specification: GetAccessPolicy operation. +// GetAccessPolicy retrieves access policy information. ONVIF Specification: GetAccessPolicy operation. func (c *Client) GetAccessPolicy(ctx context.Context) (*AccessPolicy, error) { type GetAccessPolicyBody struct { 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 } -// ONVIF Specification: SetAccessPolicy operation. +// SetAccessPolicy sets access policy information. ONVIF Specification: SetAccessPolicy operation. func (c *Client) SetAccessPolicy(ctx context.Context, policy *AccessPolicy) error { type SetAccessPolicyBody struct { XMLName xml.Name `xml:"tds:SetAccessPolicy"` @@ -201,29 +201,29 @@ func (c *Client) SetAccessPolicy(ctx context.Context, policy *AccessPolicy) erro return nil } -// ONVIF Specification: GetWsdlUrl operation (deprecated). -func (c *Client) GetWsdlUrl(ctx context.Context) (string, error) { - type GetWsdlUrlBody struct { +// GetWsdlURL retrieves the WSDL URL (deprecated). ONVIF Specification: GetWsdlUrl operation. +func (c *Client) GetWsdlURL(ctx context.Context) (string, error) { + type GetWsdlURLBody struct { XMLName xml.Name `xml:"tds:GetWsdlUrl"` Xmlns string `xml:"xmlns:tds,attr"` } - type GetWsdlUrlResponse struct { + type GetWsdlURLResponse struct { XMLName xml.Name `xml:"GetWsdlUrlResponse"` - WsdlUrl string `xml:"WsdlUrl"` + WsdlURL string `xml:"WsdlUrl"` } - request := GetWsdlUrlBody{ + request := GetWsdlURLBody{ Xmlns: deviceNamespace, } - var response GetWsdlUrlResponse + var response GetWsdlURLResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) 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 } diff --git a/device_additional_test.go b/device_additional_test.go index 201e458..21bb322 100644 --- a/device_additional_test.go +++ b/device_additional_test.go @@ -324,9 +324,9 @@ func TestGetWsdlUrl(t *testing.T) { } ctx := context.Background() - url, err := client.GetWsdlUrl(ctx) + url, err := client.GetWsdlURL(ctx) if err != nil { - t.Fatalf("GetWsdlUrl failed: %v", err) + t.Fatalf("GetWsdlURL failed: %v", err) } expected := "http://192.168.1.100/onvif/device.wsdl" diff --git a/device_certificates.go b/device_certificates.go index 24e8bf5..bec28b4 100644 --- a/device_certificates.go +++ b/device_certificates.go @@ -8,7 +8,7 @@ import ( "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) { type GetCertificatesBody struct { XMLName xml.Name `xml:"tds:GetCertificates"` @@ -35,7 +35,7 @@ func (c *Client) GetCertificates(ctx context.Context) ([]*Certificate, error) { 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) { type GetCACertificatesBody struct { XMLName xml.Name `xml:"tds:GetCACertificates"` @@ -62,7 +62,7 @@ func (c *Client) GetCACertificates(ctx context.Context) ([]*Certificate, error) 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 { type LoadCertificatesBody struct { XMLName xml.Name `xml:"tds:LoadCertificates"` @@ -90,7 +90,7 @@ func (c *Client) LoadCertificates(ctx context.Context, certificates []*Certifica return nil } -// ONVIF Specification: LoadCACertificates operation. +// LoadCACertificates loads CA certificates. ONVIF Specification: LoadCACertificates operation. func (c *Client) LoadCACertificates(ctx context.Context, certificates []*Certificate) error { type LoadCACertificatesBody struct { XMLName xml.Name `xml:"tds:LoadCACertificates"` @@ -118,7 +118,7 @@ func (c *Client) LoadCACertificates(ctx context.Context, certificates []*Certifi return nil } -// ONVIF Specification: CreateCertificate operation. +// CreateCertificate creates a certificate. ONVIF Specification: CreateCertificate operation. func (c *Client) CreateCertificate( ctx context.Context, certificateID, subject, validNotBefore, validNotAfter string, @@ -156,7 +156,7 @@ func (c *Client) CreateCertificate( 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 { type DeleteCertificatesBody struct { XMLName xml.Name `xml:"tds:DeleteCertificates"` @@ -184,6 +184,7 @@ func (c *Client) DeleteCertificates(ctx context.Context, certificateIDs []string return nil } +// GetCertificateInformation retrieves certificate information. // ONVIF Specification: GetCertificateInformation operation. func (c *Client) GetCertificateInformation(ctx context.Context, certificateID string) (*CertificateInformation, error) { type GetCertificateInformationBody struct { @@ -213,7 +214,7 @@ func (c *Client) GetCertificateInformation(ctx context.Context, certificateID st 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) { type GetCertificatesStatusBody struct { XMLName xml.Name `xml:"tds:GetCertificatesStatus"` @@ -240,7 +241,7 @@ func (c *Client) GetCertificatesStatus(ctx context.Context) ([]*CertificateStatu 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 { type SetCertificatesStatusBody struct { XMLName xml.Name `xml:"tds:SetCertificatesStatus"` @@ -268,7 +269,7 @@ func (c *Client) SetCertificatesStatus(ctx context.Context, statuses []*Certific return nil } -// ONVIF Specification: GetPkcs10Request operation. +// GetPkcs10Request retrieves a PKCS10 certificate request. ONVIF Specification: GetPkcs10Request operation. func (c *Client) GetPkcs10Request( ctx context.Context, certificateID, subject string, @@ -305,6 +306,7 @@ func (c *Client) GetPkcs10Request( return response.Pkcs10Request, nil } +// LoadCertificateWithPrivateKey loads a certificate with its private key. // ONVIF Specification: LoadCertificateWithPrivateKey operation. func (c *Client) LoadCertificateWithPrivateKey( ctx context.Context, @@ -358,6 +360,7 @@ func (c *Client) LoadCertificateWithPrivateKey( return nil } +// GetClientCertificateMode retrieves the client certificate mode. // ONVIF Specification: GetClientCertificateMode operation. func (c *Client) GetClientCertificateMode(ctx context.Context) (bool, error) { type GetClientCertificateModeBody struct { @@ -385,7 +388,7 @@ func (c *Client) GetClientCertificateMode(ctx context.Context) (bool, error) { 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 { type SetClientCertificateModeBody struct { XMLName xml.Name `xml:"tds:SetClientCertificateMode"` diff --git a/device_extended.go b/device_extended.go index 7f1bf4e..54ec900 100644 --- a/device_extended.go +++ b/device_extended.go @@ -623,7 +623,7 @@ func (c *Client) RestoreSystem(ctx context.Context, backupFiles []*BackupFile) e // GetSystemUris retrieves URIs from which system information may be downloaded. func (c *Client) GetSystemUris( ctx context.Context, -) (uriList *SystemLogUriList, systemBackupURI, systemLogURI string, err error) { +) (uriList *SystemLogURIList, systemBackupURI, systemLogURI string, err error) { type GetSystemUris struct { XMLName xml.Name `xml:"tds:GetSystemUris"` Xmlns string `xml:"xmlns:tds,attr"` @@ -634,11 +634,11 @@ func (c *Client) GetSystemUris( SystemLogUris *struct { SystemLog []struct { Type string `xml:"Type"` - Uri string `xml:"Uri"` + URI string `xml:"Uri"` } `xml:"SystemLog"` } `xml:"SystemLogUris"` - SupportInfoUri string `xml:"SupportInfoUri"` - SystemBackupUri string `xml:"SystemBackupUri"` + SupportInfoURI string `xml:"SupportInfoUri"` + SystemBackupURI string `xml:"SystemBackupUri"` } req := GetSystemUris{ @@ -654,18 +654,18 @@ func (c *Client) GetSystemUris( return nil, "", "", fmt.Errorf("GetSystemUris failed: %w", err) } - var logUris *SystemLogUriList + var logUris *SystemLogURIList if resp.SystemLogUris != nil { - logUris = &SystemLogUriList{} + logUris = &SystemLogURIList{} for _, log := range resp.SystemLogUris.SystemLog { - logUris.SystemLog = append(logUris.SystemLog, SystemLogUri{ + logUris.SystemLog = append(logUris.SystemLog, SystemLogURI{ 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. @@ -745,7 +745,7 @@ func (c *Client) StartFirmwareUpgrade( type StartFirmwareUpgradeResponse struct { XMLName xml.Name `xml:"StartFirmwareUpgradeResponse"` - UploadUri string `xml:"UploadUri"` + UploadURI string `xml:"UploadUri"` UploadDelay string `xml:"UploadDelay"` ExpectedDownTime string `xml:"ExpectedDownTime"` } @@ -763,7 +763,7 @@ func (c *Client) StartFirmwareUpgrade( 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. @@ -775,7 +775,7 @@ func (c *Client) StartSystemRestore(ctx context.Context) (uploadURI, expectedDow type StartSystemRestoreResponse struct { XMLName xml.Name `xml:"StartSystemRestoreResponse"` - UploadUri string `xml:"UploadUri"` + UploadURI string `xml:"UploadUri"` 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 resp.UploadUri, resp.ExpectedDownTime, nil + return resp.UploadURI, resp.ExpectedDownTime, nil } diff --git a/device_extended_test.go b/device_extended_test.go index 6c70be5..bf2e63a 100644 --- a/device_extended_test.go +++ b/device_extended_test.go @@ -345,13 +345,13 @@ func TestStartFirmwareUpgrade(t *testing.T) { } ctx := context.Background() - uploadUri, delay, downtime, err := client.StartFirmwareUpgrade(ctx) + 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 uploadURI != "http://192.168.1.100/upload" { + t.Errorf("Expected upload URI http://192.168.1.100/upload, got %s", uploadURI) } if delay != "PT5S" { diff --git a/device_storage.go b/device_storage.go index ffafb3c..1d74d45 100644 --- a/device_storage.go +++ b/device_storage.go @@ -8,7 +8,7 @@ import ( "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) { type GetStorageConfigurationsBody struct { XMLName xml.Name `xml:"tds:GetStorageConfigurations"` @@ -35,7 +35,7 @@ func (c *Client) GetStorageConfigurations(ctx context.Context) ([]*StorageConfig 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) { type GetStorageConfigurationBody struct { XMLName xml.Name `xml:"tds:GetStorageConfiguration"` @@ -64,6 +64,7 @@ func (c *Client) GetStorageConfiguration(ctx context.Context, token string) (*St return response.StorageConfiguration, nil } +// CreateStorageConfiguration creates a storage configuration. // ONVIF Specification: CreateStorageConfiguration operation. func (c *Client) CreateStorageConfiguration(ctx context.Context, config *StorageConfiguration) (string, error) { type CreateStorageConfigurationBody struct { @@ -93,7 +94,7 @@ func (c *Client) CreateStorageConfiguration(ctx context.Context, config *Storage 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 { type SetStorageConfigurationBody struct { XMLName xml.Name `xml:"tds:SetStorageConfiguration"` @@ -121,6 +122,7 @@ func (c *Client) SetStorageConfiguration(ctx context.Context, config *StorageCon return nil } +// DeleteStorageConfiguration deletes a storage configuration. // ONVIF Specification: DeleteStorageConfiguration operation. func (c *Client) DeleteStorageConfiguration(ctx context.Context, token string) error { type DeleteStorageConfigurationBody struct { @@ -149,7 +151,7 @@ func (c *Client) DeleteStorageConfiguration(ctx context.Context, token string) e 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 { type SetHashingAlgorithmBody struct { XMLName xml.Name `xml:"tds:SetHashingAlgorithm"` diff --git a/device_storage_test.go b/device_storage_test.go index 56cd320..5c81e37 100644 --- a/device_storage_test.go +++ b/device_storage_test.go @@ -147,8 +147,8 @@ func TestGetStorageConfigurations(t *testing.T) { t.Errorf("Expected second config token 'storage-002', got '%s'", configs[1].Token) } - 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) + 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) } } @@ -175,8 +175,8 @@ func TestGetStorageConfiguration(t *testing.T) { t.Errorf("Expected config path '/var/media/storage1', got '%s'", config.Data.LocalPath) } - if config.Data.StorageUri != "file:///var/media/storage1" { - t.Errorf("Expected config URI 'file:///var/media/storage1', got '%s'", config.Data.StorageUri) + if config.Data.StorageURI != "file:///var/media/storage1" { + t.Errorf("Expected config URI 'file:///var/media/storage1', got '%s'", config.Data.StorageURI) } if config.Data.Type != "NFS" { @@ -198,7 +198,7 @@ func TestCreateStorageConfiguration(t *testing.T) { Token: "storage-new", Data: StorageConfigurationData{ LocalPath: "/var/media/storage3", - StorageUri: "file:///var/media/storage3", + StorageURI: "file:///var/media/storage3", Type: "Local", }, } @@ -227,7 +227,7 @@ func TestSetStorageConfiguration(t *testing.T) { Token: "storage-001", Data: StorageConfigurationData{ LocalPath: "/var/media/updated", - StorageUri: "file:///var/media/updated", + StorageURI: "file:///var/media/updated", Type: "NFS", }, } diff --git a/device_wifi.go b/device_wifi.go index e4d58ff..d4cf6c3 100644 --- a/device_wifi.go +++ b/device_wifi.go @@ -8,7 +8,7 @@ import ( "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) { type GetDot11CapabilitiesBody struct { XMLName xml.Name `xml:"tds:GetDot11Capabilities"` @@ -35,7 +35,7 @@ func (c *Client) GetDot11Capabilities(ctx context.Context) (*Dot11Capabilities, 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) { type GetDot11StatusBody struct { XMLName xml.Name `xml:"tds:GetDot11Status"` @@ -64,7 +64,7 @@ func (c *Client) GetDot11Status(ctx context.Context, interfaceToken string) (*Do 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) { type GetDot1XConfigurationBody struct { XMLName xml.Name `xml:"tds:GetDot1XConfiguration"` @@ -93,7 +93,7 @@ func (c *Client) GetDot1XConfiguration(ctx context.Context, configToken string) 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) { type GetDot1XConfigurationsBody struct { XMLName xml.Name `xml:"tds:GetDot1XConfigurations"` @@ -120,7 +120,7 @@ func (c *Client) GetDot1XConfigurations(ctx context.Context) ([]*Dot1XConfigurat 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 { type SetDot1XConfigurationBody struct { XMLName xml.Name `xml:"tds:SetDot1XConfiguration"` @@ -148,7 +148,7 @@ func (c *Client) SetDot1XConfiguration(ctx context.Context, config *Dot1XConfigu 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 { type CreateDot1XConfigurationBody struct { XMLName xml.Name `xml:"tds:CreateDot1XConfiguration"` @@ -176,7 +176,7 @@ func (c *Client) CreateDot1XConfiguration(ctx context.Context, config *Dot1XConf 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 { type DeleteDot1XConfigurationBody struct { XMLName xml.Name `xml:"tds:DeleteDot1XConfiguration"` @@ -204,6 +204,7 @@ func (c *Client) DeleteDot1XConfiguration(ctx context.Context, configToken strin return nil } +// ScanAvailableDot11Networks scans for available 802.11 networks. // ONVIF Specification: ScanAvailableDot11Networks operation. func (c *Client) ScanAvailableDot11Networks( ctx context.Context, diff --git a/discovery/discovery.go b/discovery/discovery.go index 4a78451..820e655 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -14,6 +14,9 @@ import ( const ( // WS-Discovery multicast address. multicastAddr = "239.255.255.250:3702" + // UUID generation constants. + uuidMod1000 = 1000 + uuidMod10000 = 10000 // WS-Discovery probe message. probeTemplate = ` @@ -136,7 +139,8 @@ func DiscoverWithOptions(ctx context.Context, timeout time.Duration, opts *Disco // Collect responses devices := make(map[string]*Device) - buffer := make([]byte, 8192) + const maxUDPPacketSize = 8192 + buffer := make([]byte, maxUDPPacketSize) // Read responses until timeout or context cancellation for { @@ -225,12 +229,14 @@ func generateUUID() string { return fmt.Sprintf("%d-%d-%d-%d-%d", time.Now().UnixNano(), time.Now().Unix(), - time.Now().UnixNano()%1000, - time.Now().Unix()%1000, - time.Now().UnixNano()%10000) + time.Now().UnixNano()%uuidMod1000, + time.Now().Unix()%uuidMod1000, + time.Now().UnixNano()%uuidMod10000) } // 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) { // Try to get interface by name (e.g., "eth0", "wlan0") if iface, err := net.InterfaceByName(ifaceSpec); err == nil { diff --git a/imaging.go b/imaging.go index 58270a8..c44cc9b 100644 --- a/imaging.go +++ b/imaging.go @@ -12,6 +12,8 @@ import ( const imagingNamespace = "http://www.onvif.org/ver20/imaging/wsdl" // 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) { endpoint := c.imagingEndpoint if endpoint == "" { diff --git a/internal/soap/soap.go b/internal/soap/soap.go index 0d9160d..7179c0d 100644 --- a/internal/soap/soap.go +++ b/internal/soap/soap.go @@ -5,7 +5,7 @@ import ( "bytes" "context" "crypto/rand" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // SHA1 used for ONVIF digest authentication "encoding/base64" "encoding/xml" "fmt" @@ -42,14 +42,14 @@ type Fault struct { // Security represents WS-Security header. 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"` UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"` } // UsernameToken represents a WS-Security username token. 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"` Password Password `xml:"Password"` 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. func (c *Client) createSecurityHeader() *Security { // 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 _, _ = rand.Read(nonceBytes) nonce := base64.StdEncoding.EncodeToString(nonceBytes) @@ -204,7 +205,7 @@ func (c *Client) createSecurityHeader() *Security { created := time.Now().UTC().Format(time.RFC3339) // 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([]byte(created)) hash.Write([]byte(c.password)) diff --git a/media.go b/media.go index 450c0b9..f940bec 100644 --- a/media.go +++ b/media.go @@ -28,6 +28,8 @@ func (c *Client) getMediaSoapClient() *soap.Client { } // GetProfiles retrieves all media profiles. +// +//nolint:funlen // GetProfiles has many statements due to parsing complex profile structures func (c *Client) GetProfiles(ctx context.Context) ([]*Profile, error) { endpoint := c.mediaEndpoint if endpoint == "" { @@ -163,7 +165,7 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU endpoint = c.endpoint } - type GetStreamUri struct { + type GetStreamURI struct { XMLName xml.Name `xml:"trt:GetStreamUri"` Xmlns string `xml:"xmlns:trt,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"` } - type GetStreamUriResponse struct { + type GetStreamURIResponse struct { XMLName xml.Name `xml:"GetStreamUriResponse"` - MediaUri struct { - Uri string `xml:"Uri"` + MediaURI struct { + URI string `xml:"Uri"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"` Timeout string `xml:"Timeout"` } `xml:"MediaUri"` } - req := GetStreamUri{ + req := GetStreamURI{ Xmlns: mediaNamespace, Xmlnst: "http://www.onvif.org/ver10/schema", ProfileToken: profileToken, @@ -194,19 +196,19 @@ func (c *Client) GetStreamURI(ctx context.Context, profileToken string) (*MediaU req.StreamSetup.Stream = "RTP-Unicast" req.StreamSetup.Transport.Protocol = "RTSP" - var resp GetStreamUriResponse + var resp GetStreamURIResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { - return nil, fmt.Errorf("GetStreamUri failed: %w", err) + return nil, fmt.Errorf("GetStreamURI failed: %w", err) } return &MediaURI{ - URI: resp.MediaUri.Uri, - InvalidAfterConnect: resp.MediaUri.InvalidAfterConnect, - InvalidAfterReboot: resp.MediaUri.InvalidAfterReboot, + URI: resp.MediaURI.URI, + InvalidAfterConnect: resp.MediaURI.InvalidAfterConnect, + InvalidAfterReboot: resp.MediaURI.InvalidAfterReboot, }, nil } @@ -217,40 +219,40 @@ func (c *Client) GetSnapshotURI(ctx context.Context, profileToken string) (*Medi endpoint = c.endpoint } - type GetSnapshotUri struct { + type GetSnapshotURI struct { XMLName xml.Name `xml:"trt:GetSnapshotUri"` Xmlns string `xml:"xmlns:trt,attr"` ProfileToken string `xml:"trt:ProfileToken"` } - type GetSnapshotUriResponse struct { + type GetSnapshotURIResponse struct { XMLName xml.Name `xml:"GetSnapshotUriResponse"` - MediaUri struct { - Uri string `xml:"Uri"` + MediaURI struct { + URI string `xml:"Uri"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"` Timeout string `xml:"Timeout"` } `xml:"MediaUri"` } - req := GetSnapshotUri{ + req := GetSnapshotURI{ Xmlns: mediaNamespace, ProfileToken: profileToken, } - var resp GetSnapshotUriResponse + var resp GetSnapshotURIResponse username, password := c.GetCredentials() soapClient := soap.NewClient(c.httpClient, username, password) if err := soapClient.Call(ctx, endpoint, "", req, &resp); err != nil { - return nil, fmt.Errorf("GetSnapshotUri failed: %w", err) + return nil, fmt.Errorf("GetSnapshotURI failed: %w", err) } return &MediaURI{ - URI: resp.MediaUri.Uri, - InvalidAfterConnect: resp.MediaUri.InvalidAfterConnect, - InvalidAfterReboot: resp.MediaUri.InvalidAfterReboot, + URI: resp.MediaURI.URI, + InvalidAfterConnect: resp.MediaURI.InvalidAfterConnect, + InvalidAfterReboot: resp.MediaURI.InvalidAfterReboot, }, nil } @@ -637,7 +639,7 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService type GetServiceCapabilitiesResponse struct { XMLName xml.Name `xml:"GetServiceCapabilitiesResponse"` Capabilities struct { - SnapshotUri bool `xml:"SnapshotUri,attr"` + SnapshotURI bool `xml:"SnapshotUri,attr"` Rotation bool `xml:"Rotation,attr"` VideoSourceMode bool `xml:"VideoSourceMode,attr"` OSD bool `xml:"OSD,attr"` @@ -648,8 +650,8 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService } `xml:"ProfileCapabilities"` StreamingCapabilities *struct { RTPMulticast bool `xml:"RTPMulticast,attr"` - RTP_TCP bool `xml:"RTP_TCP,attr"` - RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP,attr"` + RTPTCP bool `xml:"RTP_TCP,attr"` + RTPRTSPTCP bool `xml:"RTP_RTSP_TCP,attr"` } `xml:"StreamingCapabilities"` } `xml:"Capabilities"` } @@ -668,7 +670,7 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService } caps := &MediaServiceCapabilities{ - SnapshotUri: resp.Capabilities.SnapshotUri, + SnapshotURI: resp.Capabilities.SnapshotURI, Rotation: resp.Capabilities.Rotation, VideoSourceMode: resp.Capabilities.VideoSourceMode, OSD: resp.Capabilities.OSD, @@ -682,14 +684,16 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService if resp.Capabilities.StreamingCapabilities != nil { caps.RTPMulticast = resp.Capabilities.StreamingCapabilities.RTPMulticast - caps.RTP_TCP = resp.Capabilities.StreamingCapabilities.RTP_TCP - caps.RTP_RTSP_TCP = resp.Capabilities.StreamingCapabilities.RTP_RTSP_TCP + caps.RTPTCP = resp.Capabilities.StreamingCapabilities.RTPTCP + caps.RTPRTSPTCP = resp.Capabilities.StreamingCapabilities.RTPRTSPTCP } return caps, nil } // 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) { endpoint := c.mediaEndpoint if endpoint == "" { diff --git a/media_real_camera_test.go b/media_real_camera_test.go index 84c1bc2..91028e3 100644 --- a/media_real_camera_test.go +++ b/media_real_camera_test.go @@ -74,11 +74,11 @@ func TestGetMediaServiceCapabilities_Bosch(t *testing.T) { if !capabilities.RTPMulticast { t.Error("Expected RTPMulticast=true (Bosch FLEXIDOME)") } - if !capabilities.RTP_RTSP_TCP { - t.Error("Expected RTP_RTSP_TCP=true (Bosch FLEXIDOME)") + if !capabilities.RTPRTSPTCP { + t.Error("Expected RTPRTSPTCP=true (Bosch FLEXIDOME)") } - if capabilities.SnapshotUri { - t.Error("Expected SnapshotUri=false (Bosch FLEXIDOME)") + if capabilities.SnapshotURI { + t.Error("Expected SnapshotURI=false (Bosch FLEXIDOME)") } if !capabilities.Rotation { t.Error("Expected Rotation=true (Bosch FLEXIDOME)") diff --git a/media_test.go b/media_test.go index e7c2189..e83562a 100644 --- a/media_test.go +++ b/media_test.go @@ -467,8 +467,8 @@ func TestGetMediaServiceCapabilities(t *testing.T) { t.Fatalf("GetMediaServiceCapabilities() failed: %v", err) } - if !caps.SnapshotUri { - t.Error("Expected SnapshotUri to be true") + if !caps.SnapshotURI { + t.Error("Expected SnapshotURI to be true") } if caps.MaximumNumberOfProfiles != 10 { diff --git a/server/device.go b/server/device.go index 44d659c..67ae0e3 100644 --- a/server/device.go +++ b/server/device.go @@ -17,7 +17,7 @@ type GetDeviceInformationResponse struct { Model string `xml:"Model"` FirmwareVersion string `xml:"FirmwareVersion"` SerialNumber string `xml:"SerialNumber"` - HardwareId string `xml:"HardwareId"` + HardwareID string `xml:"HardwareId"` } // GetCapabilitiesResponse represents GetCapabilities response. @@ -110,8 +110,8 @@ type MediaCapabilities struct { // StreamingCapabilities represents streaming capabilities. type StreamingCapabilities struct { RTPMulticast bool `xml:"RTPMulticast,attr"` - RTP_TCP bool `xml:"RTP_TCP,attr"` - RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP,attr"` + RTPTCP bool `xml:"RTP_TCP,attr"` + RTPRTSPTCP bool `xml:"RTP_RTSP_TCP,attr"` } // PTZCapabilities represents PTZ service capabilities. @@ -153,7 +153,7 @@ func (s *Server) HandleGetDeviceInformation(body interface{}) (interface{}, erro Model: s.config.DeviceInfo.Model, FirmwareVersion: s.config.DeviceInfo.FirmwareVersion, SerialNumber: s.config.DeviceInfo.SerialNumber, - HardwareId: s.config.DeviceInfo.HardwareID, + HardwareID: s.config.DeviceInfo.HardwareID, }, nil } @@ -204,8 +204,8 @@ func (s *Server) HandleGetCapabilities(body interface{}) (interface{}, error) { XAddr: baseURL + "/media_service", StreamingCapabilities: &StreamingCapabilities{ RTPMulticast: false, - RTP_TCP: true, - RTP_RTSP_TCP: true, + RTPTCP: true, + RTPRTSPTCP: true, }, }, } diff --git a/server/device_test.go b/server/device_test.go index 95e333a..bffb2e6 100644 --- a/server/device_test.go +++ b/server/device_test.go @@ -28,7 +28,7 @@ func TestHandleGetDeviceInformation(t *testing.T) { {"Model", deviceResp.Model, config.DeviceInfo.Model}, {"FirmwareVersion", deviceResp.FirmwareVersion, config.DeviceInfo.FirmwareVersion}, {"SerialNumber", deviceResp.SerialNumber, config.DeviceInfo.SerialNumber}, - {"HardwareId", deviceResp.HardwareId, config.DeviceInfo.HardwareID}, + {"HardwareID", deviceResp.HardwareID, config.DeviceInfo.HardwareID}, } for _, tt := range tests { @@ -162,7 +162,7 @@ func TestGetDeviceInformationResponseXML(t *testing.T) { Model: "TestModel", FirmwareVersion: "1.0.0", SerialNumber: "SN123", - HardwareId: "HW001", + HardwareID: "HW001", } // Marshal to XML @@ -209,8 +209,8 @@ func TestCapabilitiesStructure(t *testing.T) { XAddr: "http://localhost:8080/onvif/media_service", StreamingCapabilities: &StreamingCapabilities{ RTPMulticast: true, - RTP_TCP: true, - RTP_RTSP_TCP: true, + RTPTCP: true, + RTPRTSPTCP: true, }, }, } @@ -239,8 +239,8 @@ func TestMediaCapabilitiesStructure(t *testing.T) { XAddr: "http://localhost:8080/onvif/media_service", StreamingCapabilities: &StreamingCapabilities{ RTPMulticast: true, - RTP_TCP: true, - RTP_RTSP_TCP: true, + RTPTCP: true, + RTPRTSPTCP: true, }, } @@ -251,10 +251,10 @@ func TestMediaCapabilitiesStructure(t *testing.T) { if !caps.StreamingCapabilities.RTPMulticast { t.Error("RTP Multicast should be supported") } - if !caps.StreamingCapabilities.RTP_TCP { + if !caps.StreamingCapabilities.RTPTCP { t.Error("RTP TCP should be supported") } - if !caps.StreamingCapabilities.RTP_RTSP_TCP { + if !caps.StreamingCapabilities.RTPRTSPTCP { t.Error("RTSP should be supported") } } @@ -368,8 +368,8 @@ func TestGetCapabilitiesResponse(t *testing.T) { XAddr: "http://localhost:8080/media", StreamingCapabilities: &StreamingCapabilities{ RTPMulticast: true, - RTP_TCP: true, - RTP_RTSP_TCP: true, + RTPTCP: true, + RTPRTSPTCP: true, }, }, } diff --git a/server/imaging.go b/server/imaging.go index 031627f..7eeeb19 100644 --- a/server/imaging.go +++ b/server/imaging.go @@ -266,6 +266,8 @@ func (s *Server) HandleGetImagingSettings(body interface{}) (interface{}, error) } // 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) { var req SetImagingSettingsRequest if err := unmarshalBody(body, &req); err != nil { diff --git a/server/media.go b/server/media.go index 9949d7f..7524c45 100644 --- a/server/media.go +++ b/server/media.go @@ -138,12 +138,12 @@ type IPAddress struct { // GetStreamURIResponse represents GetStreamURI response. type GetStreamURIResponse struct { 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. -type MediaUri struct { - Uri string `xml:"Uri"` +// MediaURI represents a media URI. +type MediaURI struct { + URI string `xml:"Uri"` InvalidAfterConnect bool `xml:"InvalidAfterConnect"` InvalidAfterReboot bool `xml:"InvalidAfterReboot"` Timeout string `xml:"Timeout"` @@ -152,7 +152,7 @@ type MediaUri struct { // GetSnapshotURIResponse represents GetSnapshotURI response. type GetSnapshotURIResponse struct { XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURIResponse"` - MediaUri MediaUri `xml:"MediaUri"` + MediaURI MediaURI `xml:"MediaUri"` } // GetVideoSourcesResponse represents GetVideoSources response. @@ -287,8 +287,8 @@ func (s *Server) HandleGetStreamURI(body interface{}) (interface{}, error) { } return &GetStreamURIResponse{ - MediaUri: MediaUri{ - Uri: uri, + MediaURI: MediaURI{ + URI: uri, InvalidAfterConnect: false, InvalidAfterReboot: true, Timeout: "PT60S", @@ -333,8 +333,8 @@ func (s *Server) HandleGetSnapshotURI(body interface{}) (interface{}, error) { host, s.config.Port, s.config.BasePath, req.ProfileToken) return &GetSnapshotURIResponse{ - MediaUri: MediaUri{ - Uri: uri, + MediaURI: MediaURI{ + URI: uri, InvalidAfterConnect: false, InvalidAfterReboot: true, Timeout: "PT5S", diff --git a/server/media_test.go b/server/media_test.go index fa26b91..acf5a09 100644 --- a/server/media_test.go +++ b/server/media_test.go @@ -52,15 +52,15 @@ func TestHandleGetStreamURI(t *testing.T) { t.Fatalf("Response is not GetStreamURIResponse, got %T", resp) } - if streamResp.MediaUri.Uri == "" { + if streamResp.MediaURI.URI == "" { t.Error("Stream URI is empty") return } // URI should contain stream path - if !contains(streamResp.MediaUri.Uri, "rtsp://") { - t.Errorf("Invalid stream URI format: %s", streamResp.MediaUri.Uri) + if !contains(streamResp.MediaURI.URI, "rtsp://") { + 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) } - if snapResp.MediaUri.Uri == "" { + if snapResp.MediaURI.URI == "" { t.Error("Snapshot URI is empty") } } diff --git a/server/soap/handler.go b/server/soap/handler.go index 1f15a85..99542f1 100644 --- a/server/soap/handler.go +++ b/server/soap/handler.go @@ -3,7 +3,7 @@ package soap import ( "bytes" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // SHA1 used for ONVIF digest authentication "encoding/base64" "encoding/xml" "fmt" @@ -123,7 +123,7 @@ func (h *Handler) authenticate(envelope *originsoap.Envelope) bool { } // Calculate expected digest - hash := sha1.New() + hash := sha1.New() //nolint:gosec // SHA1 required for ONVIF digest auth hash.Write(nonce) hash.Write([]byte(token.Created)) hash.Write([]byte(h.password)) diff --git a/server/types.go b/server/types.go index fe99998..663bfea 100644 --- a/server/types.go +++ b/server/types.go @@ -228,6 +228,8 @@ type WDRSettings struct { } // 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 { return &Config{ Host: "0.0.0.0", diff --git a/testing/mock_server.go b/testing/mock_server.go index dc96006..cb1cd55 100644 --- a/testing/mock_server.go +++ b/testing/mock_server.go @@ -35,7 +35,7 @@ type CameraCapture struct { // LoadCaptureFromArchive loads all captured exchanges from a tar.gz archive. 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 { return nil, fmt.Errorf("failed to open archive: %w", err) } diff --git a/types.go b/types.go index ac106a5..a2985e2 100644 --- a/types.go +++ b/types.go @@ -106,12 +106,12 @@ type SecurityCapabilities struct { // StreamingCapabilities represents streaming capabilities. type StreamingCapabilities struct { RTPMulticast bool - RTP_TCP bool - RTP_RTSP_TCP bool + RTPTCP bool + RTPRTSPTCP bool Extension *StreamingCapabilitiesExtension } -// Extension types. +// CapabilitiesExtension represents extension types for capabilities. type CapabilitiesExtension struct{} type NetworkCapabilitiesExtension struct{} type SystemCapabilitiesExtension struct{} @@ -324,7 +324,7 @@ type ProfileExtension struct{} // MediaServiceCapabilities represents media service capabilities. type MediaServiceCapabilities struct { - SnapshotUri bool + SnapshotURI bool Rotation bool VideoSourceMode bool OSD bool @@ -332,8 +332,8 @@ type MediaServiceCapabilities struct { EXICompression bool MaximumNumberOfProfiles int RTPMulticast bool - RTP_TCP bool - RTP_RTSP_TCP bool + RTPTCP bool + RTPRTSPTCP bool } // VideoEncoderConfigurationOptions represents available options for video encoder configuration. @@ -995,15 +995,15 @@ type SupportInformation struct { String string } -// SystemLogUriList represents system log URIs. -type SystemLogUriList struct { - SystemLog []SystemLogUri +// SystemLogURIList represents system log URIs. +type SystemLogURIList struct { + SystemLog []SystemLogURI } -// SystemLogUri represents system log URI. -type SystemLogUri struct { +// SystemLogURI represents system log URI. +type SystemLogURI struct { Type SystemLogType - Uri string + URI string } // NetworkZeroConfiguration represents zero-configuration. @@ -1187,7 +1187,7 @@ type StorageConfiguration struct { type StorageConfigurationData struct { Type string LocalPath string - StorageUri string + StorageURI string User *UserCredential CertPathValidationPolicyID string }