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