Files
onvif/ws-discovery/networking.go
Cedric Verstraeten 8902e4e789 upgrade onvif library
2023-12-18 20:16:21 +01:00

180 lines
5.3 KiB
Go

package wsdiscovery
/*******************************************************
* Copyright (C) 2018 Palanjyan Zhorzhik
*
* This file is part of ws-discovery project.
*
* ws-discovery can be copied and/or distributed without the express
* permission of Palanjyan Zhorzhik
*******************************************************/
// Copyright (C) 2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/beevik/etree"
"github.com/google/uuid"
"github.com/kerberos-io/onvif"
"golang.org/x/net/ipv4"
)
const (
bufSize = 8192
)
// GetAvailableDevicesAtSpecificEthernetInterface sends a ws-discovery Probe Message via
// UDP multicast to Discover NVT type Devices
func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) ([]onvif.Device, error) {
types := []string{"dn:NetworkVideoTransmitter"}
namespaces := map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl", "ds": "http://www.onvif.org/ver10/device/wsdl"}
probeResponses, err := SendProbe(interfaceName, nil, types, namespaces)
if err != nil {
return nil, fmt.Errorf("failed to probe: %w", err)
}
nvtDevices, err := DevicesFromProbeResponses(probeResponses)
if err != nil {
return nil, fmt.Errorf("failed to discover Onvif devices: %w", err)
}
return nvtDevices, nil
}
func DevicesFromProbeResponses(probeResponses []string) ([]onvif.Device, error) {
nvtDevices := make([]onvif.Device, 0)
xaddrSet := make(map[string]struct{})
for _, j := range probeResponses {
doc := etree.NewDocument()
if err := doc.ReadFromString(j); err != nil {
return nil, err
}
probeMatches := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch")
for _, probeMatch := range probeMatches {
var xaddr string
if address := probeMatch.FindElement("./XAddrs"); address != nil {
u, err := url.Parse(address.Text())
if err != nil {
// TODO: Add logger for fmt.Printf("Invalid XAddrs: %s\n", address.Text())
continue
}
xaddr = u.Host
}
if _, dupe := xaddrSet[xaddr]; dupe {
// TODO: Add logger for fmt.Printf("Skipping duplicate XAddr: %s\n", xaddr)
continue
}
var endpointRefAddress string
if ref := probeMatch.FindElement("./EndpointReference/Address"); ref != nil {
uuidElements := strings.Split(ref.Text(), ":")
endpointRefAddress = uuidElements[len(uuidElements)-1]
}
dev, err := onvif.NewDevice(onvif.DeviceParams{
Xaddr: xaddr,
EndpointRefAddress: endpointRefAddress,
HttpClient: &http.Client{
Timeout: 2 * time.Second,
},
})
if err != nil {
// TODO: Add logger for fmt.Printf("Failed to connect to camera at %s: %s\n", xaddr, err.Error())
continue
}
var scopes []string
ref := probeMatch.FindElement("./Scopes")
if ref != nil {
scopes = strings.Split(ref.Text(), " ")
}
dev.SetDeviceInfoFromScopes(scopes)
xaddrSet[xaddr] = struct{}{}
nvtDevices = append(nvtDevices, *dev)
// TODO: Add logger for fmt.Printf("Onvif WS-Discovery: Find Xaddr: %-25s EndpointRefAddress: %s\n", xaddr, string(endpointRefAddress))
}
}
return nvtDevices, nil
}
// SendProbe to device
func SendProbe(interfaceName string, scopes, types []string, namespaces map[string]string) ([]string, error) {
probeSOAP := BuildProbeMessage(uuid.NewString(), scopes, types, namespaces)
return SendUDPMulticast(probeSOAP.String(), interfaceName)
}
func SendUDPMulticast(msg string, interfaceName string) ([]string, error) {
var responses []string
data := []byte(msg)
c, err := net.ListenPacket("udp4", "0.0.0.0:0")
if err != nil {
return nil, err
}
defer c.Close()
p := ipv4.NewPacketConn(c)
// 239.255.255.250 port 3702 is the multicast address and port used by ws-discovery
group := net.IPv4(239, 255, 255, 250)
dest := &net.UDPAddr{IP: group, Port: 3702}
var iface *net.Interface
if interfaceName == "" {
iface = nil
} else {
iface, err = net.InterfaceByName(interfaceName)
if err != nil {
return nil, fmt.Errorf("failed to call InterfaceByName for interface %q: %w", interfaceName, err)
}
}
if err = p.JoinGroup(iface, &net.UDPAddr{IP: group}); err != nil {
return nil, fmt.Errorf("failed to JoinGroup for ws-discovery: %w", err)
}
if iface != nil {
if err = p.SetMulticastInterface(iface); err != nil {
return nil, fmt.Errorf("failed to SetMulticastInterface for interface %q: %w", interfaceName, err)
}
if err = p.SetMulticastTTL(2); err != nil {
return nil, fmt.Errorf("failed to SetMulticastTTL: %w", err)
}
}
if _, err = p.WriteTo(data, nil, dest); err != nil {
return nil, fmt.Errorf("failed to write to ws-discovery multicast address %s: %w", dest.String(), err)
}
if err = p.SetReadDeadline(time.Now().Add(time.Second * 1)); err != nil {
return nil, fmt.Errorf("failed to set read deadline: %w", err)
}
b := make([]byte, bufSize)
// keep reading from the PacketConn until the read deadline expires or an error occurs
for {
n, _, _, err := p.ReadFrom(b)
if err != nil {
// ErrDeadlineExceeded is expected once the read timeout is expired
if !errors.Is(err, os.ErrDeadlineExceeded) {
return nil, fmt.Errorf("unexpected error occurred while reading ws-discovery responses: %w", err)
}
break
}
responses = append(responses, string(b[0:n]))
}
return responses, nil
}