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 (
"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)
+2 -2
View File
@@ -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
+2 -1
View File
@@ -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)
}
+18 -6
View File
@@ -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
+4 -1
View File
@@ -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)
+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")
)
//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)
}
+22 -11
View File
@@ -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: ")
+1
View File
@@ -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")
+6 -4
View File
@@ -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,
}
}
}
+16 -16
View File
@@ -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
}
+2 -2
View File
@@ -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"
+13 -10
View File
@@ -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"`
+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.
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
}
+3 -3
View File
@@ -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" {
+6 -4
View File
@@ -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"`
+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)
}
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",
},
}
+8 -7
View File
@@ -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,
+10 -4
View File
@@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
@@ -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 {
+2
View File
@@ -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 == "" {
+6 -5
View File
@@ -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))
+30 -26
View File
@@ -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 == "" {
+4 -4
View File
@@ -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)")
+2 -2
View File
@@ -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 {
+6 -6
View File
@@ -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,
},
},
}
+10 -10
View File
@@ -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,
},
},
}
+2
View File
@@ -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 {
+9 -9
View File
@@ -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",
+4 -4
View File
@@ -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")
}
}
+2 -2
View File
@@ -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))
+2
View File
@@ -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",
+1 -1
View File
@@ -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)
}
+13 -13
View File
@@ -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
}