From f601c4721833eb9fbb45dba7874890cab01cf974 Mon Sep 17 00:00:00 2001 From: Alex X Date: Mon, 30 Dec 2024 22:34:08 +0300 Subject: [PATCH] Improve ONVIF server --- examples/onvif_client/main.go | 72 +++++ internal/onvif/README.md | 25 ++ internal/onvif/{init.go => onvif.go} | 80 ++--- pkg/onvif/README.md | 38 +++ pkg/onvif/client.go | 113 ++----- pkg/onvif/envelope.go | 79 +++++ pkg/onvif/helpers.go | 23 ++ pkg/onvif/server.go | 422 +++++++++++++-------------- 8 files changed, 506 insertions(+), 346 deletions(-) create mode 100644 examples/onvif_client/main.go create mode 100644 internal/onvif/README.md rename internal/onvif/{init.go => onvif.go} (67%) create mode 100644 pkg/onvif/README.md create mode 100644 pkg/onvif/envelope.go diff --git a/examples/onvif_client/main.go b/examples/onvif_client/main.go new file mode 100644 index 00000000..03dd12ba --- /dev/null +++ b/examples/onvif_client/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "net" + "net/url" + "os" + + "github.com/AlexxIT/go2rtc/pkg/onvif" +) + +func main() { + var rawURL = os.Args[1] + var operation = os.Args[2] + var token string + if len(os.Args) > 3 { + token = os.Args[3] + } + + client, err := onvif.NewClient(rawURL) + if err != nil { + log.Panic(err) + } + + var b []byte + + switch operation { + case onvif.ServiceGetServiceCapabilities: + b, err = client.MediaRequest(operation) + case onvif.DeviceGetCapabilities, + onvif.DeviceGetDeviceInformation, + onvif.DeviceGetDiscoveryMode, + onvif.DeviceGetDNS, + onvif.DeviceGetHostname, + onvif.DeviceGetNetworkDefaultGateway, + onvif.DeviceGetNetworkInterfaces, + onvif.DeviceGetNetworkProtocols, + onvif.DeviceGetNTP, + onvif.DeviceGetScopes, + onvif.DeviceGetServices, + onvif.DeviceGetSystemDateAndTime, + onvif.DeviceSystemReboot: + b, err = client.DeviceRequest(operation) + case onvif.MediaGetProfiles, onvif.MediaGetVideoSources: + b, err = client.MediaRequest(operation) + case onvif.MediaGetProfile: + b, err = client.GetProfile(token) + case onvif.MediaGetVideoSourceConfiguration: + b, err = client.GetVideoSourceConfiguration(token) + case onvif.MediaGetStreamUri: + b, err = client.GetStreamUri(token) + case onvif.MediaGetSnapshotUri: + b, err = client.GetSnapshotUri(token) + default: + log.Printf("unknown action\n") + } + + if err != nil { + log.Printf("%s\n", err) + } + + u, err := url.Parse(rawURL) + if err != nil { + log.Fatal(err) + } + + host, _, _ := net.SplitHostPort(u.Host) + + if err = os.WriteFile(host+"_"+operation+".xml", b, 0644); err != nil { + log.Printf("%s\n", err) + } +} diff --git a/internal/onvif/README.md b/internal/onvif/README.md new file mode 100644 index 00000000..ee922fbf --- /dev/null +++ b/internal/onvif/README.md @@ -0,0 +1,25 @@ +# ONVIF + +A regular camera has a single video source (`GetVideoSources`) and two profiles (`GetProfiles`). + +Go2rtc has one video source and one profile per stream. + +## Tested clients + +Go2rtc works as ONVIF server: + +- Happytime onvif client (windows) +- Home Assistant ONVIF integration (linux) +- Onvier (android) +- ONVIF Device Manager (windows) + +PS. Support only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet. + +## Tested cameras + +Go2rtc works as ONVIF client: + +- Dahua IPC-K42 +- OpenIPC +- Reolink RLC-520A +- TP-Link Tapo TC60 diff --git a/internal/onvif/init.go b/internal/onvif/onvif.go similarity index 67% rename from internal/onvif/init.go rename to internal/onvif/onvif.go index e5ed9a7c..d332ca38 100644 --- a/internal/onvif/init.go +++ b/internal/onvif/onvif.go @@ -55,55 +55,65 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { return } - action := onvif.GetRequestAction(b) - if action == "" { + operation := onvif.GetRequestAction(b) + if operation == "" { http.Error(w, "malformed request body", http.StatusBadRequest) return } - log.Trace().Msgf("[onvif] %s", action) + log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b) - var res string + switch operation { + case onvif.DeviceGetNetworkInterfaces, // important for Hass + onvif.DeviceGetSystemDateAndTime, // important for Hass + onvif.DeviceGetDiscoveryMode, + onvif.DeviceGetDNS, + onvif.DeviceGetHostname, + onvif.DeviceGetNetworkDefaultGateway, + onvif.DeviceGetNetworkProtocols, + onvif.DeviceGetNTP, + onvif.DeviceGetScopes: + b = onvif.StaticResponse(operation) - switch action { - case onvif.ActionGetCapabilities: + case onvif.DeviceGetCapabilities: // important for Hass: Media section - res = onvif.GetCapabilitiesResponse(r.Host) + b = onvif.GetCapabilitiesResponse(r.Host) - case onvif.ActionGetServices: - res = onvif.GetServicesResponse(r.Host) + case onvif.DeviceGetServices: + b = onvif.GetServicesResponse(r.Host) - case onvif.ActionGetSystemDateAndTime: - // important for Hass - res = onvif.GetSystemDateAndTimeResponse() - - case onvif.ActionGetNetworkInterfaces: - // important for Hass: none - res = onvif.GetNetworkInterfacesResponse() - - case onvif.ActionGetDeviceInformation: + case onvif.DeviceGetDeviceInformation: // important for Hass: SerialNumber (unique server ID) - res = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host) + b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host) - case onvif.ActionGetServiceCapabilities: + case onvif.ServiceGetServiceCapabilities: // important for Hass - res = onvif.GetServiceCapabilitiesResponse() + // TODO: check path links to media + b = onvif.GetMediaServiceCapabilitiesResponse() - case onvif.ActionSystemReboot: - res = onvif.SystemRebootResponse() + case onvif.DeviceSystemReboot: + b = onvif.StaticResponse(operation) time.AfterFunc(time.Second, func() { os.Exit(0) }) - case onvif.ActionGetProfiles: + case onvif.MediaGetVideoSources: + b = onvif.GetVideoSourcesResponse(streams.GetAll()) + + case onvif.MediaGetProfiles: // important for Hass: H264 codec, width, height - res = onvif.GetProfilesResponse(streams.GetAll()) + b = onvif.GetProfilesResponse(streams.GetAll()) - case onvif.ActionGetVideoSources: - res = onvif.GetVideoSourcesResponse(streams.GetAll()) + case onvif.MediaGetProfile: + token := onvif.FindTagValue(b, "ProfileToken") + b = onvif.GetProfileResponse(token) - case onvif.ActionGetStreamUri: + case onvif.MediaGetVideoSourceConfiguration: + token := onvif.FindTagValue(b, "ConfigurationToken") + b = onvif.GetVideoSourceConfigurationResponse(token) + + case onvif.MediaGetStreamUri: host, _, err := net.SplitHostPort(r.Host) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -111,20 +121,22 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { } uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken") - res = onvif.GetStreamUriResponse(uri) + b = onvif.GetStreamUriResponse(uri) - case onvif.ActionGetSnapshotUri: + case onvif.MediaGetSnapshotUri: uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken") - res = onvif.GetSnapshotUriResponse(uri) + b = onvif.GetSnapshotUriResponse(uri) default: - http.Error(w, "unsupported action", http.StatusBadRequest) + http.Error(w, "unsupported operation", http.StatusBadRequest) log.Debug().Msgf("[onvif] unsupported request:\n%s", b) return } + log.Trace().Msgf("[onvif] server response:\n%s", b) + w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8") - if _, err = w.Write([]byte(res)); err != nil { + if _, err = w.Write(b); err != nil { log.Error().Err(err).Caller().Send() } } @@ -170,7 +182,7 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) { } if l := log.Trace(); l.Enabled() { - b, _ := client.GetProfiles() + b, _ := client.MediaRequest(onvif.MediaGetProfiles) l.Msgf("[onvif] src=%s profiles:\n%s", src, b) } diff --git a/pkg/onvif/README.md b/pkg/onvif/README.md new file mode 100644 index 00000000..73267379 --- /dev/null +++ b/pkg/onvif/README.md @@ -0,0 +1,38 @@ +## Profiles + +- Profile A - For access control configuration +- Profile C - For door control and event management +- Profile S - For basic video streaming + - Video streaming and configuration +- Profile T - For advanced video streaming + - H.264 / H.265 video compression + - Imaging settings + - Motion alarm and tampering events + - Metadata streaming + - Bi-directional audio + +## Services + +https://www.onvif.org/profiles/specifications/ + +- https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl +- https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl +- https://www.onvif.org/ver10/media/wsdl/media.wsdl + +## TMP + +| | Dahua | Reolink | TP-Link | +|------------------------|---------|---------|---------| +| GetCapabilities | no auth | no auth | no auth | +| GetServices | no auth | no auth | no auth | +| GetServiceCapabilities | no auth | no auth | auth | +| GetSystemDateAndTime | no auth | no auth | no auth | +| GetNetworkInterfaces | auth | auth | auth | +| GetDeviceInformation | auth | auth | auth | +| GetProfiles | auth | auth | auth | +| GetScopes | auth | auth | auth | + +- Dahua - onvif://192.168.10.90:80 +- Reolink - onvif://192.168.10.92:8000 +- TP-Link - onvif://192.168.10.91:2020/onvif/device_service +- \ No newline at end of file diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 97bfd8dc..cb6221e1 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -2,8 +2,6 @@ package onvif import ( "bytes" - "crypto/sha1" - "encoding/base64" "errors" "html" "io" @@ -12,8 +10,6 @@ import ( "regexp" "strings" "time" - - "github.com/AlexxIT/go2rtc/pkg/core" ) const PathDevice = "/onvif/device_service" @@ -41,7 +37,7 @@ func NewClient(rawURL string) (*Client, error) { client.deviceURL = baseURL + u.Path } - b, err := client.GetCapabilities() + b, err := client.DeviceRequest(DeviceGetCapabilities) if err != nil { return nil, err } @@ -95,7 +91,7 @@ func (c *Client) GetURI() (string, error) { } func (c *Client) GetName() (string, error) { - b, err := c.GetDeviceInformation() + b, err := c.DeviceRequest(DeviceGetDeviceInformation) if err != nil { return "", err } @@ -104,7 +100,7 @@ func (c *Client) GetName() (string, error) { } func (c *Client) GetProfilesTokens() ([]string, error) { - b, err := c.GetProfiles() + b, err := c.MediaRequest(MediaGetProfiles) if err != nil { return nil, err } @@ -127,86 +123,53 @@ func (c *Client) HasSnapshots() bool { return strings.Contains(string(b), `SnapshotUri="true"`) } -func (c *Client) GetCapabilities() ([]byte, error) { +func (c *Client) GetProfile(token string) ([]byte, error) { return c.Request( - c.deviceURL, - ` - All -`, + c.mediaURL, ``+token+``, ) } -func (c *Client) GetNetworkInterfaces() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) -} - -func (c *Client) GetDeviceInformation() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) -} - -func (c *Client) GetProfiles() ([]byte, error) { - return c.Request( - c.mediaURL, ``, - ) +func (c *Client) GetVideoSourceConfiguration(token string) ([]byte, error) { + return c.Request(c.mediaURL, ` + `+token+` +`) } func (c *Client) GetStreamUri(token string) ([]byte, error) { - return c.Request( - c.mediaURL, - ` + return c.Request(c.mediaURL, ` RTP-Unicast RTSP `+token+` -`, - ) +`) } func (c *Client) GetSnapshotUri(token string) ([]byte, error) { return c.Request( - c.imaginURL, - ` - `+token+` -`, - ) -} - -func (c *Client) GetSystemDateAndTime() ([]byte, error) { - return c.Request( - c.deviceURL, ``, + c.imaginURL, ``+token+``, ) } func (c *Client) GetServiceCapabilities() ([]byte, error) { // some cameras answer GetServiceCapabilities for media only for path = "/onvif/media" return c.Request( - c.mediaURL, ``, + c.mediaURL, ``, ) } -func (c *Client) SystemReboot() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) +func (c *Client) DeviceRequest(operation string) ([]byte, error) { + if operation == DeviceGetServices { + operation = `true` + } else { + operation = `` + } + return c.Request(c.deviceURL, operation) } -func (c *Client) GetServices() ([]byte, error) { - return c.Request( - c.deviceURL, ` - true -`, - ) -} - -func (c *Client) GetScopes() ([]byte, error) { - return c.Request( - c.deviceURL, ``, - ) +func (c *Client) MediaRequest(operation string) ([]byte, error) { + operation = `` + return c.Request(c.mediaURL, operation) } func (c *Client) Request(url, body string) ([]byte, error) { @@ -214,35 +177,11 @@ func (c *Client) Request(url, body string) ([]byte, error) { return nil, errors.New("onvif: unsupported service") } - buf := bytes.NewBuffer(nil) - buf.WriteString( - ``, - ) - - if user := c.url.User; user != nil { - nonce := core.RandString(16, 36) - created := time.Now().UTC().Format(time.RFC3339Nano) - pass, _ := user.Password() - - h := sha1.New() - h.Write([]byte(nonce + created + pass)) - - buf.WriteString(` - - -` + user.Username() + ` -` + base64.StdEncoding.EncodeToString(h.Sum(nil)) + ` -` + base64.StdEncoding.EncodeToString([]byte(nonce)) + ` -` + created + ` - - -`) - } - - buf.WriteString(`` + body + ``) + e := NewEnvelopeWithUser(c.url.User) + e.Append(body) client := &http.Client{Timeout: time.Second * 5000} - res, err := client.Post(url, `application/soap+xml;charset=utf-8`, buf) + res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes())) if err != nil { return nil, err } diff --git a/pkg/onvif/envelope.go b/pkg/onvif/envelope.go new file mode 100644 index 00000000..f0e1b29c --- /dev/null +++ b/pkg/onvif/envelope.go @@ -0,0 +1,79 @@ +package onvif + +import ( + "crypto/sha1" + "encoding/base64" + "fmt" + "net/url" + "time" + + "github.com/AlexxIT/go2rtc/pkg/core" +) + +type Envelope struct { + buf []byte +} + +const ( + prefix1 = ` + +` + prefix2 = ` +` + suffix = ` + +` +) + +func NewEnvelope() *Envelope { + e := &Envelope{buf: make([]byte, 0, 1024)} + e.Append(prefix1, prefix2) + return e +} + +func NewEnvelopeWithUser(user *url.Userinfo) *Envelope { + if user == nil { + return NewEnvelope() + } + + nonce := core.RandString(16, 36) + created := time.Now().UTC().Format(time.RFC3339Nano) + pass, _ := user.Password() + + h := sha1.New() + h.Write([]byte(nonce + created + pass)) + + e := &Envelope{buf: make([]byte, 0, 1024)} + e.Append(prefix1) + e.Appendf(` + + + %s + %s + %s + %s + + + +`, + user.Username(), + base64.StdEncoding.EncodeToString(h.Sum(nil)), + base64.StdEncoding.EncodeToString([]byte(nonce)), + created) + e.Append(prefix2) + return e +} + +func (e *Envelope) Append(args ...string) { + for _, s := range args { + e.buf = append(e.buf, s...) + } +} + +func (e *Envelope) Appendf(format string, args ...any) { + e.buf = fmt.Appendf(e.buf, format, args...) +} + +func (e *Envelope) Bytes() []byte { + return append(e.buf, suffix...) +} diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go index fc9c8392..251f4579 100644 --- a/pkg/onvif/helpers.go +++ b/pkg/onvif/helpers.go @@ -1,6 +1,7 @@ package onvif import ( + "fmt" "net" "regexp" "strconv" @@ -106,3 +107,25 @@ func atoi(s string) int { } return i } + +func GetPosixTZ(current time.Time) string { + // Thanks to https://github.com/Path-Variable/go-posix-time + _, offset := current.Zone() + + if current.IsDST() { + _, end := current.ZoneBounds() + endPlus1 := end.Add(time.Hour * 25) + _, offset = endPlus1.Zone() + } + + var prefix string + if offset < 0 { + prefix = "GMT+" + offset = -offset / 60 + } else { + prefix = "GMT-" + offset = offset / 60 + } + + return prefix + fmt.Sprintf("%02d:%02d", offset/60, offset%60) +} diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index bc3f8ffe..42343d37 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -2,31 +2,40 @@ package onvif import ( "bytes" - "fmt" "regexp" - "strconv" "time" ) -const ( - ActionGetCapabilities = "GetCapabilities" - ActionGetSystemDateAndTime = "GetSystemDateAndTime" - ActionGetNetworkInterfaces = "GetNetworkInterfaces" - ActionGetDeviceInformation = "GetDeviceInformation" - ActionGetServiceCapabilities = "GetServiceCapabilities" - ActionGetProfiles = "GetProfiles" - ActionGetStreamUri = "GetStreamUri" - ActionGetSnapshotUri = "GetSnapshotUri" - ActionSystemReboot = "SystemReboot" +const ServiceGetServiceCapabilities = "GetServiceCapabilities" - ActionGetServices = "GetServices" - ActionGetScopes = "GetScopes" - ActionGetVideoSources = "GetVideoSources" - ActionGetAudioSources = "GetAudioSources" - ActionGetVideoSourceConfigurations = "GetVideoSourceConfigurations" - ActionGetAudioSourceConfigurations = "GetAudioSourceConfigurations" - ActionGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" - ActionGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" +const ( + DeviceGetCapabilities = "GetCapabilities" + DeviceGetDeviceInformation = "GetDeviceInformation" + DeviceGetDiscoveryMode = "GetDiscoveryMode" + DeviceGetDNS = "GetDNS" + DeviceGetHostname = "GetHostname" + DeviceGetNetworkDefaultGateway = "GetNetworkDefaultGateway" + DeviceGetNetworkInterfaces = "GetNetworkInterfaces" + DeviceGetNetworkProtocols = "GetNetworkProtocols" + DeviceGetNTP = "GetNTP" + DeviceGetScopes = "GetScopes" + DeviceGetServices = "GetServices" + DeviceGetSystemDateAndTime = "GetSystemDateAndTime" + DeviceSystemReboot = "SystemReboot" +) + +const ( + MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" + MediaGetAudioSources = "GetAudioSources" + MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations" + MediaGetProfile = "GetProfile" + MediaGetProfiles = "GetProfiles" + MediaGetSnapshotUri = "GetSnapshotUri" + MediaGetStreamUri = "GetStreamUri" + MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" + MediaGetVideoSources = "GetVideoSources" + MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration" + MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations" ) func GetRequestAction(b []byte) string { @@ -43,236 +52,199 @@ func GetRequestAction(b []byte) string { return string(m[1]) } -func GetCapabilitiesResponse(host string) string { - return ` - - - - - - http://` + host + `/onvif/device_service - - - http://` + host + `/onvif/media_service - - false - false - true - - - - - -` +func GetCapabilitiesResponse(host string) []byte { + e := NewEnvelope() + e.Append(` + + + http://`, host, `/onvif/device_service + + + http://`, host, `/onvif/media_service + + false + false + true + + + +`) + return e.Bytes() } -func GetServicesResponse(host string) string { - return ` - - - - - http://www.onvif.org/ver10/device/wsdl - http://` + host + `/onvif/device_service - - 2 - 5 - - - - http://www.onvif.org/ver10/media/wsdl - http://` + host + `/onvif/media_service - - 2 - 5 - - - - -` +func GetServicesResponse(host string) []byte { + e := NewEnvelope() + e.Append(` + + http://www.onvif.org/ver10/device/wsdl + http://`, host, `/onvif/device_service + 25 + + + http://www.onvif.org/ver10/media/wsdl + http://`, host, `/onvif/media_service + 25 + +`) + return e.Bytes() } -func GetSystemDateAndTimeResponse() string { +func GetSystemDateAndTimeResponse() []byte { loc := time.Now() utc := loc.UTC() - return fmt.Sprintf(` - - - - - NTP - false - - GMT%s - - - - %d - %d - %d - - - %d - %d - %d - - - - - %d - %d - %d - - - %d - %d - %d - - - - - -`, - loc.Format("-07:00"), + e := NewEnvelope() + e.Appendf(` + + NTP + true + + %s + + + %d%d%d + %d%d%d + + + %d%d%d + %d%d%d + + +`, + GetPosixTZ(loc), utc.Hour(), utc.Minute(), utc.Second(), utc.Year(), utc.Month(), utc.Day(), loc.Hour(), loc.Minute(), loc.Second(), loc.Year(), loc.Month(), loc.Day(), ) + return e.Bytes() } -func GetNetworkInterfacesResponse() string { - return ` - - - - -` +func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte { + e := NewEnvelope() + e.Append(` + `, manuf, ` + `, model, ` + `, firmware, ` + `, serial, ` + 1.00 +`) + return e.Bytes() } -func GetDeviceInformationResponse(manuf, model, firmware, serial string) string { - return ` - - - - ` + manuf + ` - ` + model + ` - ` + firmware + ` - ` + serial + ` - 1.00 - - -` +func GetMediaServiceCapabilitiesResponse() []byte { + e := NewEnvelope() + e.Append(` + + + +`) + return e.Bytes() } -func GetServiceCapabilitiesResponse() string { - return ` - - - - - - - - -` +func GetProfilesResponse(names []string) []byte { + e := NewEnvelope() + e.Append(` +`) + for _, name := range names { + appendProfile(e, "Profiles", name) + } + e.Append(``) + return e.Bytes() } -func SystemRebootResponse() string { - return ` - - - - system reboot in 1 second... - - -` +func GetProfileResponse(name string) []byte { + e := NewEnvelope() + e.Append(` +`) + appendProfile(e, "Profile", name) + e.Append(``) + return e.Bytes() } -func GetProfilesResponse(names []string) string { - buf := bytes.NewBuffer(nil) - buf.WriteString(` - - - `) +func appendProfile(e *Envelope, tag, name string) { + e.Append(` + `, name, ` + + VSC + `, name, ` + + + + VEC + H264 + 19201080 + + +`) +} - for i, name := range names { - buf.WriteString(` - - ` + name + ` - - ` + name + ` - H264 - - 1920 - 1080 - - - - - - ` + name + ` - ` + strconv.Itoa(i) + ` - - - `) +func GetVideoSourceConfigurationResponse(name string) []byte { + e := NewEnvelope() + e.Append(` + + VSC + `, name, ` + + +`) + return e.Bytes() +} + +func GetVideoSourcesResponse(names []string) []byte { + e := NewEnvelope() + e.Append(` +`) + for _, name := range names { + e.Append(` + 30.000000 + 19201080 + +`) + } + e.Append(``) + return e.Bytes() +} + +func GetStreamUriResponse(uri string) []byte { + e := NewEnvelope() + e.Append(``, uri, ``) + return e.Bytes() +} + +func GetSnapshotUriResponse(uri string) []byte { + e := NewEnvelope() + e.Append(``, uri, ``) + return e.Bytes() +} + +func StaticResponse(operation string) []byte { + switch operation { + case DeviceGetSystemDateAndTime: + return GetSystemDateAndTimeResponse() } - buf.WriteString(` - - -`) - - return buf.String() -} - - -func GetVideoSourcesResponse(names []string) string { - buf := bytes.NewBuffer(nil) - buf.WriteString(` - - - `) - - for i, _ := range names { - buf.WriteString(` - - - 1920 - 1080 - - `) + e := NewEnvelope() + e.Append(responses[operation]) + b := e.Bytes() + if operation == DeviceGetNetworkInterfaces { + println() } - - buf.WriteString(` - - -`) - - return buf.String() + return b } -func GetStreamUriResponse(uri string) string { - return ` - - - - - ` + uri + ` - - - -` -} +var responses = map[string]string{ + DeviceGetDiscoveryMode: `Discoverable`, + DeviceGetDNS: ``, + DeviceGetHostname: ``, + DeviceGetNetworkDefaultGateway: ``, + DeviceGetNTP: ``, + DeviceSystemReboot: `OK`, -func GetSnapshotUriResponse(uri string) string { - return ` - - - - - ` + uri + ` - - - -` + DeviceGetNetworkInterfaces: ``, + DeviceGetNetworkProtocols: ``, + DeviceGetScopes: ` + Fixedonvif://www.onvif.org/name/go2rtc + Fixedonvif://www.onvif.org/location/github + Fixedonvif://www.onvif.org/Profile/Streaming + Fixedonvif://www.onvif.org/type/Network_Video_Transmitter +`, }