Godeps, vendor: convert dependency management to trash (#3198)
This commit converts the dependency management from Godeps to the vendor folder, also switching the tool from godep to trash. Since the upstream tool lacks a few features proposed via a few PRs, until those PRs are merged in (if), use github.com/karalabe/trash. You can update dependencies via trash --update. All dependencies have been updated to their latest version. Parts of the build system are reworked to drop old notions of Godeps and invocation of the go vet command so that it doesn't run against the vendor folder, as that will just blow up during vetting. The conversion drops OpenCL (and hence GPU mining support) from ethash and our codebase. The short reasoning is that there's noone to maintain and having opencl libs in our deps messes up builds as go install ./... tries to build them, failing with unsatisfied link errors for the C OpenCL deps. golang.org/x/net/context is not vendored in. We expect it to be fetched by the user (i.e. using go get). To keep ci.go builds reproducible the package is "vendored" in build/_vendor.
This commit is contained in:
committed by
Felix Lange
parent
7770304576
commit
289b30715d
1
vendor/github.com/huin/goupnp/.gitignore
generated
vendored
Normal file
1
vendor/github.com/huin/goupnp/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
/gotasks/specs
|
23
vendor/github.com/huin/goupnp/LICENSE
generated
vendored
Normal file
23
vendor/github.com/huin/goupnp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2013, John Beisley <greatred@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
44
vendor/github.com/huin/goupnp/README.md
generated
vendored
Normal file
44
vendor/github.com/huin/goupnp/README.md
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
goupnp is a UPnP client library for Go
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Run `go get -u github.com/huin/goupnp`.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
All doc links below are for .
|
||||
|
||||
Supported DCPs (you probably want to start with one of these):
|
||||
* [av1](https://godoc.org/github.com/huin/goupnp/dcps/av1) - Client for UPnP Device Control Protocol MediaServer v1 and MediaRenderer v1.
|
||||
* [internetgateway1](https://godoc.org/github.com/huin/goupnp/dcps/internetgateway1) - Client for UPnP Device Control Protocol Internet Gateway Device v1.
|
||||
* [internetgateway2](https://godoc.org/github.com/huin/goupnp/dcps/internetgateway2) - Client for UPnP Device Control Protocol Internet Gateway Device v2.
|
||||
|
||||
Core components:
|
||||
* [(goupnp)](https://godoc.org/github.com/huin/goupnp) core library - contains datastructures and utilities typically used by the implemented DCPs.
|
||||
* [httpu](https://godoc.org/github.com/huin/goupnp/httpu) HTTPU implementation, underlies SSDP.
|
||||
* [ssdp](https://godoc.org/github.com/huin/goupnp/ssdp) SSDP client implementation (simple service discovery protocol) - used to discover UPnP services on a network.
|
||||
* [soap](https://godoc.org/github.com/huin/goupnp/soap) SOAP client implementation (simple object access protocol) - used to communicate with discovered services.
|
||||
|
||||
|
||||
Regenerating dcps generated source code:
|
||||
----------------------------------------
|
||||
|
||||
1. Install gotasks: `go get -u github.com/jingweno/gotask`
|
||||
2. Change to the gotasks directory: `cd gotasks`
|
||||
3. Run specgen task: `gotask specgen`
|
||||
|
||||
Supporting additional UPnP devices and services:
|
||||
------------------------------------------------
|
||||
|
||||
Supporting additional services is, in the trivial case, simply a matter of
|
||||
adding the service to the `dcpMetadata` whitelist in `gotasks/specgen_task.go`,
|
||||
regenerating the source code (see above), and committing that source code.
|
||||
|
||||
However, it would be helpful if anyone needing such a service could test the
|
||||
service against the service they have, and then reporting any trouble
|
||||
encountered as an [issue on this
|
||||
project](https://github.com/huin/goupnp/issues/new). If it just works, then
|
||||
please report at least minimal working functionality as an issue, and
|
||||
optionally contribute the metadata upstream.
|
3717
vendor/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
Normal file
3717
vendor/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5378
vendor/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
Normal file
5378
vendor/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
184
vendor/github.com/huin/goupnp/device.go
generated
vendored
Normal file
184
vendor/github.com/huin/goupnp/device.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
// This file contains XML structures for communicating with UPnP devices.
|
||||
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/huin/goupnp/scpd"
|
||||
"github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceXMLNamespace = "urn:schemas-upnp-org:device-1-0"
|
||||
)
|
||||
|
||||
// RootDevice is the device description as described by section 2.3 "Device
|
||||
// description" in
|
||||
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
|
||||
type RootDevice struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
URLBase url.URL `xml:"-"`
|
||||
URLBaseStr string `xml:"URLBase"`
|
||||
Device Device `xml:"device"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the RootDevice and its underlying components.
|
||||
func (root *RootDevice) SetURLBase(urlBase *url.URL) {
|
||||
root.URLBase = *urlBase
|
||||
root.URLBaseStr = urlBase.String()
|
||||
root.Device.SetURLBase(urlBase)
|
||||
}
|
||||
|
||||
// SpecVersion is part of a RootDevice, describes the version of the
|
||||
// specification that the data adheres to.
|
||||
type SpecVersion struct {
|
||||
Major int32 `xml:"major"`
|
||||
Minor int32 `xml:"minor"`
|
||||
}
|
||||
|
||||
// Device is a UPnP device. It can have child devices.
|
||||
type Device struct {
|
||||
DeviceType string `xml:"deviceType"`
|
||||
FriendlyName string `xml:"friendlyName"`
|
||||
Manufacturer string `xml:"manufacturer"`
|
||||
ManufacturerURL URLField `xml:"manufacturerURL"`
|
||||
ModelDescription string `xml:"modelDescription"`
|
||||
ModelName string `xml:"modelName"`
|
||||
ModelNumber string `xml:"modelNumber"`
|
||||
ModelURL URLField `xml:"modelURL"`
|
||||
SerialNumber string `xml:"serialNumber"`
|
||||
UDN string `xml:"UDN"`
|
||||
UPC string `xml:"UPC,omitempty"`
|
||||
Icons []Icon `xml:"iconList>icon,omitempty"`
|
||||
Services []Service `xml:"serviceList>service,omitempty"`
|
||||
Devices []Device `xml:"deviceList>device,omitempty"`
|
||||
|
||||
// Extra observed elements:
|
||||
PresentationURL URLField `xml:"presentationURL"`
|
||||
}
|
||||
|
||||
// VisitDevices calls visitor for the device, and all its descendent devices.
|
||||
func (device *Device) VisitDevices(visitor func(*Device)) {
|
||||
visitor(device)
|
||||
for i := range device.Devices {
|
||||
device.Devices[i].VisitDevices(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitServices calls visitor for all Services under the device and all its
|
||||
// descendent devices.
|
||||
func (device *Device) VisitServices(visitor func(*Service)) {
|
||||
device.VisitDevices(func(d *Device) {
|
||||
for i := range d.Services {
|
||||
visitor(&d.Services[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FindService finds all (if any) Services under the device and its descendents
|
||||
// that have the given ServiceType.
|
||||
func (device *Device) FindService(serviceType string) []*Service {
|
||||
var services []*Service
|
||||
device.VisitServices(func(s *Service) {
|
||||
if s.ServiceType == serviceType {
|
||||
services = append(services, s)
|
||||
}
|
||||
})
|
||||
return services
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Device and its underlying components.
|
||||
func (device *Device) SetURLBase(urlBase *url.URL) {
|
||||
device.ManufacturerURL.SetURLBase(urlBase)
|
||||
device.ModelURL.SetURLBase(urlBase)
|
||||
device.PresentationURL.SetURLBase(urlBase)
|
||||
for i := range device.Icons {
|
||||
device.Icons[i].SetURLBase(urlBase)
|
||||
}
|
||||
for i := range device.Services {
|
||||
device.Services[i].SetURLBase(urlBase)
|
||||
}
|
||||
for i := range device.Devices {
|
||||
device.Devices[i].SetURLBase(urlBase)
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) String() string {
|
||||
return fmt.Sprintf("Device ID %s : %s (%s)", device.UDN, device.DeviceType, device.FriendlyName)
|
||||
}
|
||||
|
||||
// Icon is a representative image that a device might include in its
|
||||
// description.
|
||||
type Icon struct {
|
||||
Mimetype string `xml:"mimetype"`
|
||||
Width int32 `xml:"width"`
|
||||
Height int32 `xml:"height"`
|
||||
Depth int32 `xml:"depth"`
|
||||
URL URLField `xml:"url"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Icon.
|
||||
func (icon *Icon) SetURLBase(url *url.URL) {
|
||||
icon.URL.SetURLBase(url)
|
||||
}
|
||||
|
||||
// Service is a service provided by a UPnP Device.
|
||||
type Service struct {
|
||||
ServiceType string `xml:"serviceType"`
|
||||
ServiceId string `xml:"serviceId"`
|
||||
SCPDURL URLField `xml:"SCPDURL"`
|
||||
ControlURL URLField `xml:"controlURL"`
|
||||
EventSubURL URLField `xml:"eventSubURL"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Service.
|
||||
func (srv *Service) SetURLBase(urlBase *url.URL) {
|
||||
srv.SCPDURL.SetURLBase(urlBase)
|
||||
srv.ControlURL.SetURLBase(urlBase)
|
||||
srv.EventSubURL.SetURLBase(urlBase)
|
||||
}
|
||||
|
||||
func (srv *Service) String() string {
|
||||
return fmt.Sprintf("Service ID %s : %s", srv.ServiceId, srv.ServiceType)
|
||||
}
|
||||
|
||||
// RequestSCDP requests the SCPD (soap actions and state variables description)
|
||||
// for the service.
|
||||
func (srv *Service) RequestSCDP() (*scpd.SCPD, error) {
|
||||
if !srv.SCPDURL.Ok {
|
||||
return nil, errors.New("bad/missing SCPD URL, or no URLBase has been set")
|
||||
}
|
||||
s := new(scpd.SCPD)
|
||||
if err := requestXml(srv.SCPDURL.URL.String(), scpd.SCPDXMLNamespace, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (srv *Service) NewSOAPClient() *soap.SOAPClient {
|
||||
return soap.NewSOAPClient(srv.ControlURL.URL)
|
||||
}
|
||||
|
||||
// URLField is a URL that is part of a device description.
|
||||
type URLField struct {
|
||||
URL url.URL `xml:"-"`
|
||||
Ok bool `xml:"-"`
|
||||
Str string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func (uf *URLField) SetURLBase(urlBase *url.URL) {
|
||||
refUrl, err := url.Parse(uf.Str)
|
||||
if err != nil {
|
||||
uf.URL = url.URL{}
|
||||
uf.Ok = false
|
||||
return
|
||||
}
|
||||
|
||||
uf.URL = *urlBase.ResolveReference(refUrl)
|
||||
uf.Ok = true
|
||||
}
|
131
vendor/github.com/huin/goupnp/goupnp.go
generated
vendored
Normal file
131
vendor/github.com/huin/goupnp/goupnp.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// goupnp is an implementation of a client for various UPnP services.
|
||||
//
|
||||
// For most uses, it is recommended to use the code-generated packages under
|
||||
// github.com/huin/goupnp/dcps. Example use is shown at
|
||||
// http://godoc.org/github.com/huin/goupnp/example
|
||||
//
|
||||
// A commonly used client is internetgateway1.WANPPPConnection1:
|
||||
// http://godoc.org/github.com/huin/goupnp/dcps/internetgateway1#WANPPPConnection1
|
||||
//
|
||||
// Currently only a couple of schemas have code generated for them from the
|
||||
// UPnP example XML specifications. Not all methods will work on these clients,
|
||||
// because the generated stubs contain the full set of specified methods from
|
||||
// the XML specifications, and the discovered services will likely support a
|
||||
// subset of those methods.
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html/charset"
|
||||
|
||||
"github.com/huin/goupnp/httpu"
|
||||
"github.com/huin/goupnp/ssdp"
|
||||
)
|
||||
|
||||
// ContextError is an error that wraps an error with some context information.
|
||||
type ContextError struct {
|
||||
Context string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err ContextError) Error() string {
|
||||
return fmt.Sprintf("%s: %v", err.Context, err.Err)
|
||||
}
|
||||
|
||||
// MaybeRootDevice contains either a RootDevice or an error.
|
||||
type MaybeRootDevice struct {
|
||||
// Set iff Err == nil.
|
||||
Root *RootDevice
|
||||
|
||||
// The location the device was discovered at. This can be used with
|
||||
// DeviceByURL, assuming the device is still present. A location represents
|
||||
// the discovery of a device, regardless of if there was an error probing it.
|
||||
Location *url.URL
|
||||
|
||||
// Any error encountered probing a discovered device.
|
||||
Err error
|
||||
}
|
||||
|
||||
// DiscoverDevices attempts to find targets of the given type. This is
|
||||
// typically the entry-point for this package. searchTarget is typically a URN
|
||||
// in the form "urn:schemas-upnp-org:device:..." or
|
||||
// "urn:schemas-upnp-org:service:...". A single error is returned for errors
|
||||
// while attempting to send the query. An error or RootDevice is returned for
|
||||
// each discovered RootDevice.
|
||||
func DiscoverDevices(searchTarget string) ([]MaybeRootDevice, error) {
|
||||
httpu, err := httpu.NewHTTPUClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer httpu.Close()
|
||||
responses, err := ssdp.SSDPRawSearch(httpu, string(searchTarget), 2, 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]MaybeRootDevice, len(responses))
|
||||
for i, response := range responses {
|
||||
maybe := &results[i]
|
||||
loc, err := response.Location()
|
||||
if err != nil {
|
||||
maybe.Err = ContextError{"unexpected bad location from search", err}
|
||||
continue
|
||||
}
|
||||
maybe.Location = loc
|
||||
if root, err := DeviceByURL(loc); err != nil {
|
||||
maybe.Err = err
|
||||
} else {
|
||||
maybe.Root = root
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func DeviceByURL(loc *url.URL) (*RootDevice, error) {
|
||||
locStr := loc.String()
|
||||
root := new(RootDevice)
|
||||
if err := requestXml(locStr, DeviceXMLNamespace, root); err != nil {
|
||||
return nil, ContextError{fmt.Sprintf("error requesting root device details from %q", locStr), err}
|
||||
}
|
||||
var urlBaseStr string
|
||||
if root.URLBaseStr != "" {
|
||||
urlBaseStr = root.URLBaseStr
|
||||
} else {
|
||||
urlBaseStr = locStr
|
||||
}
|
||||
urlBase, err := url.Parse(urlBaseStr)
|
||||
if err != nil {
|
||||
return nil, ContextError{fmt.Sprintf("error parsing location URL %q", locStr), err}
|
||||
}
|
||||
root.SetURLBase(urlBase)
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func requestXml(url string, defaultSpace string, doc interface{}) error {
|
||||
timeout := time.Duration(3 * time.Second)
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("goupnp: got response status %s from %q",
|
||||
resp.Status, url)
|
||||
}
|
||||
|
||||
decoder := xml.NewDecoder(resp.Body)
|
||||
decoder.DefaultSpace = defaultSpace
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
return decoder.Decode(doc)
|
||||
}
|
132
vendor/github.com/huin/goupnp/httpu/httpu.go
generated
vendored
Normal file
132
vendor/github.com/huin/goupnp/httpu/httpu.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
package httpu
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPUClient is a client for dealing with HTTPU (HTTP over UDP). Its typical
|
||||
// function is for HTTPMU, and particularly SSDP.
|
||||
type HTTPUClient struct {
|
||||
connLock sync.Mutex // Protects use of conn.
|
||||
conn net.PacketConn
|
||||
}
|
||||
|
||||
// NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the
|
||||
// purpose.
|
||||
func NewHTTPUClient() (*HTTPUClient, error) {
|
||||
conn, err := net.ListenPacket("udp", ":0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HTTPUClient{conn: conn}, nil
|
||||
}
|
||||
|
||||
// NewHTTPUClientAddr creates a new HTTPUClient which will broadcast packets
|
||||
// from the specified address, opening up a new UDP socket for the purpose
|
||||
func NewHTTPUClientAddr(addr string) (*HTTPUClient, error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return nil, errors.New("Invalid listening address")
|
||||
}
|
||||
conn, err := net.ListenPacket("udp", ip.String()+":0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HTTPUClient{conn: conn}, nil
|
||||
}
|
||||
|
||||
// Close shuts down the client. The client will no longer be useful following
|
||||
// this.
|
||||
func (httpu *HTTPUClient) Close() error {
|
||||
httpu.connLock.Lock()
|
||||
defer httpu.connLock.Unlock()
|
||||
return httpu.conn.Close()
|
||||
}
|
||||
|
||||
// Do performs a request. The timeout is how long to wait for before returning
|
||||
// the responses that were received. An error is only returned for failing to
|
||||
// send the request. Failures in receipt simply do not add to the resulting
|
||||
// responses.
|
||||
//
|
||||
// Note that at present only one concurrent connection will happen per
|
||||
// HTTPUClient.
|
||||
func (httpu *HTTPUClient) Do(req *http.Request, timeout time.Duration, numSends int) ([]*http.Response, error) {
|
||||
httpu.connLock.Lock()
|
||||
defer httpu.connLock.Unlock()
|
||||
|
||||
// Create the request. This is a subset of what http.Request.Write does
|
||||
// deliberately to avoid creating extra fields which may confuse some
|
||||
// devices.
|
||||
var requestBuf bytes.Buffer
|
||||
method := req.Method
|
||||
if method == "" {
|
||||
method = "GET"
|
||||
}
|
||||
if _, err := fmt.Fprintf(&requestBuf, "%s %s HTTP/1.1\r\n", method, req.URL.RequestURI()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Header.Write(&requestBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := requestBuf.Write([]byte{'\r', '\n'}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destAddr, err := net.ResolveUDPAddr("udp", req.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = httpu.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send request.
|
||||
for i := 0; i < numSends; i++ {
|
||||
if n, err := httpu.conn.WriteTo(requestBuf.Bytes(), destAddr); err != nil {
|
||||
return nil, err
|
||||
} else if n < len(requestBuf.Bytes()) {
|
||||
return nil, fmt.Errorf("httpu: wrote %d bytes rather than full %d in request",
|
||||
n, len(requestBuf.Bytes()))
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Await responses until timeout.
|
||||
var responses []*http.Response
|
||||
responseBytes := make([]byte, 2048)
|
||||
for {
|
||||
// 2048 bytes should be sufficient for most networks.
|
||||
n, _, err := httpu.conn.ReadFrom(responseBytes)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok {
|
||||
if err.Timeout() {
|
||||
break
|
||||
}
|
||||
if err.Temporary() {
|
||||
// Sleep in case this is a persistent error to avoid pegging CPU until deadline.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse response.
|
||||
response, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(responseBytes[:n])), req)
|
||||
if err != nil {
|
||||
log.Print("httpu: error while parsing response: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
responses = append(responses, response)
|
||||
}
|
||||
return responses, err
|
||||
}
|
108
vendor/github.com/huin/goupnp/httpu/serve.go
generated
vendored
Normal file
108
vendor/github.com/huin/goupnp/httpu/serve.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
package httpu
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxMessageBytes = 2048
|
||||
)
|
||||
|
||||
var (
|
||||
trailingWhitespaceRx = regexp.MustCompile(" +\r\n")
|
||||
crlf = []byte("\r\n")
|
||||
)
|
||||
|
||||
// Handler is the interface by which received HTTPU messages are passed to
|
||||
// handling code.
|
||||
type Handler interface {
|
||||
// ServeMessage is called for each HTTPU message received. peerAddr contains
|
||||
// the address that the message was received from.
|
||||
ServeMessage(r *http.Request)
|
||||
}
|
||||
|
||||
// HandlerFunc is a function-to-Handler adapter.
|
||||
type HandlerFunc func(r *http.Request)
|
||||
|
||||
func (f HandlerFunc) ServeMessage(r *http.Request) {
|
||||
f(r)
|
||||
}
|
||||
|
||||
// A Server defines parameters for running an HTTPU server.
|
||||
type Server struct {
|
||||
Addr string // UDP address to listen on
|
||||
Multicast bool // Should listen for multicast?
|
||||
Interface *net.Interface // Network interface to listen on for multicast, nil for default multicast interface
|
||||
Handler Handler // handler to invoke
|
||||
MaxMessageBytes int // maximum number of bytes to read from a packet, DefaultMaxMessageBytes if 0
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the UDP network address srv.Addr. If srv.Multicast
|
||||
// is true, then a multicast UDP listener will be used on srv.Interface (or
|
||||
// default interface if nil).
|
||||
func (srv *Server) ListenAndServe() error {
|
||||
var err error
|
||||
|
||||
var addr *net.UDPAddr
|
||||
if addr, err = net.ResolveUDPAddr("udp", srv.Addr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var conn net.PacketConn
|
||||
if srv.Multicast {
|
||||
if conn, err = net.ListenMulticastUDP("udp", srv.Interface, addr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if conn, err = net.ListenUDP("udp", addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return srv.Serve(conn)
|
||||
}
|
||||
|
||||
// Serve messages received on the given packet listener to the srv.Handler.
|
||||
func (srv *Server) Serve(l net.PacketConn) error {
|
||||
maxMessageBytes := DefaultMaxMessageBytes
|
||||
if srv.MaxMessageBytes != 0 {
|
||||
maxMessageBytes = srv.MaxMessageBytes
|
||||
}
|
||||
for {
|
||||
buf := make([]byte, maxMessageBytes)
|
||||
n, peerAddr, err := l.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
go func(buf []byte, peerAddr net.Addr) {
|
||||
// At least one router's UPnP implementation has added a trailing space
|
||||
// after "HTTP/1.1" - trim it.
|
||||
buf = trailingWhitespaceRx.ReplaceAllLiteral(buf, crlf)
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(buf)))
|
||||
if err != nil {
|
||||
log.Printf("httpu: Failed to parse request: %v", err)
|
||||
return
|
||||
}
|
||||
req.RemoteAddr = peerAddr.String()
|
||||
srv.Handler.ServeMessage(req)
|
||||
// No need to call req.Body.Close - underlying reader is bytes.Buffer.
|
||||
}(buf, peerAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve messages received on the given packet listener to the given handler.
|
||||
func Serve(l net.PacketConn, handler Handler) error {
|
||||
srv := Server{
|
||||
Handler: handler,
|
||||
MaxMessageBytes: DefaultMaxMessageBytes,
|
||||
}
|
||||
return srv.Serve(l)
|
||||
}
|
167
vendor/github.com/huin/goupnp/scpd/scpd.go
generated
vendored
Normal file
167
vendor/github.com/huin/goupnp/scpd/scpd.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
package scpd
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
SCPDXMLNamespace = "urn:schemas-upnp-org:service-1-0"
|
||||
)
|
||||
|
||||
func cleanWhitespace(s *string) {
|
||||
*s = strings.TrimSpace(*s)
|
||||
}
|
||||
|
||||
// SCPD is the service description as described by section 2.5 "Service
|
||||
// description" in
|
||||
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
|
||||
type SCPD struct {
|
||||
XMLName xml.Name `xml:"scpd"`
|
||||
ConfigId string `xml:"configId,attr"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
Actions []Action `xml:"actionList>action"`
|
||||
StateVariables []StateVariable `xml:"serviceStateTable>stateVariable"`
|
||||
}
|
||||
|
||||
// Clean attempts to remove stray whitespace etc. in the structure. It seems
|
||||
// unfortunately common for stray whitespace to be present in SCPD documents,
|
||||
// this method attempts to make it easy to clean them out.
|
||||
func (scpd *SCPD) Clean() {
|
||||
cleanWhitespace(&scpd.ConfigId)
|
||||
for i := range scpd.Actions {
|
||||
scpd.Actions[i].clean()
|
||||
}
|
||||
for i := range scpd.StateVariables {
|
||||
scpd.StateVariables[i].clean()
|
||||
}
|
||||
}
|
||||
|
||||
func (scpd *SCPD) GetStateVariable(variable string) *StateVariable {
|
||||
for i := range scpd.StateVariables {
|
||||
v := &scpd.StateVariables[i]
|
||||
if v.Name == variable {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scpd *SCPD) GetAction(action string) *Action {
|
||||
for i := range scpd.Actions {
|
||||
a := &scpd.Actions[i]
|
||||
if a.Name == action {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpecVersion is part of a SCPD document, describes the version of the
|
||||
// specification that the data adheres to.
|
||||
type SpecVersion struct {
|
||||
Major int32 `xml:"major"`
|
||||
Minor int32 `xml:"minor"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Name string `xml:"name"`
|
||||
Arguments []Argument `xml:"argumentList>argument"`
|
||||
}
|
||||
|
||||
func (action *Action) clean() {
|
||||
cleanWhitespace(&action.Name)
|
||||
for i := range action.Arguments {
|
||||
action.Arguments[i].clean()
|
||||
}
|
||||
}
|
||||
|
||||
func (action *Action) InputArguments() []*Argument {
|
||||
var result []*Argument
|
||||
for i := range action.Arguments {
|
||||
arg := &action.Arguments[i]
|
||||
if arg.IsInput() {
|
||||
result = append(result, arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (action *Action) OutputArguments() []*Argument {
|
||||
var result []*Argument
|
||||
for i := range action.Arguments {
|
||||
arg := &action.Arguments[i]
|
||||
if arg.IsOutput() {
|
||||
result = append(result, arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name string `xml:"name"`
|
||||
Direction string `xml:"direction"` // in|out
|
||||
RelatedStateVariable string `xml:"relatedStateVariable"` // ?
|
||||
Retval string `xml:"retval"` // ?
|
||||
}
|
||||
|
||||
func (arg *Argument) clean() {
|
||||
cleanWhitespace(&arg.Name)
|
||||
cleanWhitespace(&arg.Direction)
|
||||
cleanWhitespace(&arg.RelatedStateVariable)
|
||||
cleanWhitespace(&arg.Retval)
|
||||
}
|
||||
|
||||
func (arg *Argument) IsInput() bool {
|
||||
return arg.Direction == "in"
|
||||
}
|
||||
|
||||
func (arg *Argument) IsOutput() bool {
|
||||
return arg.Direction == "out"
|
||||
}
|
||||
|
||||
type StateVariable struct {
|
||||
Name string `xml:"name"`
|
||||
SendEvents string `xml:"sendEvents,attr"` // yes|no
|
||||
Multicast string `xml:"multicast,attr"` // yes|no
|
||||
DataType DataType `xml:"dataType"`
|
||||
DefaultValue string `xml:"defaultValue"`
|
||||
AllowedValueRange *AllowedValueRange `xml:"allowedValueRange"`
|
||||
AllowedValues []string `xml:"allowedValueList>allowedValue"`
|
||||
}
|
||||
|
||||
func (v *StateVariable) clean() {
|
||||
cleanWhitespace(&v.Name)
|
||||
cleanWhitespace(&v.SendEvents)
|
||||
cleanWhitespace(&v.Multicast)
|
||||
v.DataType.clean()
|
||||
cleanWhitespace(&v.DefaultValue)
|
||||
if v.AllowedValueRange != nil {
|
||||
v.AllowedValueRange.clean()
|
||||
}
|
||||
for i := range v.AllowedValues {
|
||||
cleanWhitespace(&v.AllowedValues[i])
|
||||
}
|
||||
}
|
||||
|
||||
type AllowedValueRange struct {
|
||||
Minimum string `xml:"minimum"`
|
||||
Maximum string `xml:"maximum"`
|
||||
Step string `xml:"step"`
|
||||
}
|
||||
|
||||
func (r *AllowedValueRange) clean() {
|
||||
cleanWhitespace(&r.Minimum)
|
||||
cleanWhitespace(&r.Maximum)
|
||||
cleanWhitespace(&r.Step)
|
||||
}
|
||||
|
||||
type DataType struct {
|
||||
Name string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
func (dt *DataType) clean() {
|
||||
cleanWhitespace(&dt.Name)
|
||||
cleanWhitespace(&dt.Type)
|
||||
}
|
88
vendor/github.com/huin/goupnp/service_client.go
generated
vendored
Normal file
88
vendor/github.com/huin/goupnp/service_client.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
// ServiceClient is a SOAP client, root device and the service for the SOAP
|
||||
// client rolled into one value. The root device, location, and service are
|
||||
// intended to be informational. Location can be used to later recreate a
|
||||
// ServiceClient with NewServiceClientByURL if the service is still present;
|
||||
// bypassing the discovery process.
|
||||
type ServiceClient struct {
|
||||
SOAPClient *soap.SOAPClient
|
||||
RootDevice *RootDevice
|
||||
Location *url.URL
|
||||
Service *Service
|
||||
}
|
||||
|
||||
// NewServiceClients discovers services, and returns clients for them. err will
|
||||
// report any error with the discovery process (blocking any device/service
|
||||
// discovery), errors reports errors on a per-root-device basis.
|
||||
func NewServiceClients(searchTarget string) (clients []ServiceClient, errors []error, err error) {
|
||||
var maybeRootDevices []MaybeRootDevice
|
||||
if maybeRootDevices, err = DiscoverDevices(searchTarget); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
clients = make([]ServiceClient, 0, len(maybeRootDevices))
|
||||
|
||||
for _, maybeRootDevice := range maybeRootDevices {
|
||||
if maybeRootDevice.Err != nil {
|
||||
errors = append(errors, maybeRootDevice.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
deviceClients, err := NewServiceClientsFromRootDevice(maybeRootDevice.Root, maybeRootDevice.Location, searchTarget)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
clients = append(clients, deviceClients...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewServiceClientsByURL creates client(s) for the given service URN, for a
|
||||
// root device at the given URL.
|
||||
func NewServiceClientsByURL(loc *url.URL, searchTarget string) ([]ServiceClient, error) {
|
||||
rootDevice, err := DeviceByURL(loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServiceClientsFromRootDevice(rootDevice, loc, searchTarget)
|
||||
}
|
||||
|
||||
// NewServiceClientsFromDevice creates client(s) for the given service URN, in
|
||||
// a given root device. The loc parameter is simply assigned to the
|
||||
// Location attribute of the returned ServiceClient(s).
|
||||
func NewServiceClientsFromRootDevice(rootDevice *RootDevice, loc *url.URL, searchTarget string) ([]ServiceClient, error) {
|
||||
device := &rootDevice.Device
|
||||
srvs := device.FindService(searchTarget)
|
||||
if len(srvs) == 0 {
|
||||
return nil, fmt.Errorf("goupnp: service %q not found within device %q (UDN=%q)",
|
||||
searchTarget, device.FriendlyName, device.UDN)
|
||||
}
|
||||
|
||||
clients := make([]ServiceClient, 0, len(srvs))
|
||||
for _, srv := range srvs {
|
||||
clients = append(clients, ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: rootDevice,
|
||||
Location: loc,
|
||||
Service: srv,
|
||||
})
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
// GetServiceClient returns the ServiceClient itself. This is provided so that the
|
||||
// service client attributes can be accessed via an interface method on a
|
||||
// wrapping type.
|
||||
func (client *ServiceClient) GetServiceClient() *ServiceClient {
|
||||
return client
|
||||
}
|
157
vendor/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
157
vendor/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// Definition for the SOAP structure required for UPnP's SOAP usage.
|
||||
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
|
||||
soapSuffix = `</s:Body></s:Envelope>`
|
||||
)
|
||||
|
||||
type SOAPClient struct {
|
||||
EndpointURL url.URL
|
||||
HTTPClient http.Client
|
||||
}
|
||||
|
||||
func NewSOAPClient(endpointURL url.URL) *SOAPClient {
|
||||
return &SOAPClient{
|
||||
EndpointURL: endpointURL,
|
||||
}
|
||||
}
|
||||
|
||||
// PerformSOAPAction makes a SOAP request, with the given action.
|
||||
// inAction and outAction must both be pointers to structs with string fields
|
||||
// only.
|
||||
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||
requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.HTTPClient.Do(&http.Request{
|
||||
Method: "POST",
|
||||
URL: &client.EndpointURL,
|
||||
Header: http.Header{
|
||||
"SOAPACTION": []string{`"` + actionNamespace + "#" + actionName + `"`},
|
||||
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
|
||||
// Set ContentLength to avoid chunked encoding - some servers might not support it.
|
||||
ContentLength: int64(len(requestBytes)),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
|
||||
}
|
||||
|
||||
responseEnv := newSOAPEnvelope()
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(responseEnv); err != nil {
|
||||
return fmt.Errorf("goupnp: error decoding response body: %v", err)
|
||||
}
|
||||
|
||||
if responseEnv.Body.Fault != nil {
|
||||
return responseEnv.Body.Fault
|
||||
}
|
||||
|
||||
if outAction != nil {
|
||||
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
||||
return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newSOAPAction creates a soapEnvelope with the given action and arguments.
|
||||
func newSOAPEnvelope() *soapEnvelope {
|
||||
return &soapEnvelope{
|
||||
EncodingStyle: soapEncodingStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRequestAction is a hacky way to create an encoded SOAP envelope
|
||||
// containing the given action. Experiments with one router have shown that it
|
||||
// 500s for requests where the outer default xmlns is set to the SOAP
|
||||
// namespace, and then reassigning the default namespace within that to the
|
||||
// service namespace. Hand-coding the outer XML to work-around this.
|
||||
func encodeRequestAction(actionNamespace, actionName string, inAction interface{}) ([]byte, error) {
|
||||
requestBuf := new(bytes.Buffer)
|
||||
requestBuf.WriteString(soapPrefix)
|
||||
requestBuf.WriteString(`<u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(` xmlns:u="`)
|
||||
xml.EscapeText(requestBuf, []byte(actionNamespace))
|
||||
requestBuf.WriteString(`">`)
|
||||
if inAction != nil {
|
||||
if err := encodeRequestArgs(requestBuf, inAction); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
requestBuf.WriteString(`</u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(`>`)
|
||||
requestBuf.WriteString(soapSuffix)
|
||||
return requestBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
||||
in := reflect.Indirect(reflect.ValueOf(inAction))
|
||||
if in.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
|
||||
}
|
||||
enc := xml.NewEncoder(w)
|
||||
nFields := in.NumField()
|
||||
inType := in.Type()
|
||||
for i := 0; i < nFields; i++ {
|
||||
field := inType.Field(i)
|
||||
argName := field.Name
|
||||
if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
|
||||
argName = nameOverride
|
||||
}
|
||||
value := in.Field(i)
|
||||
if value.Kind() != reflect.String {
|
||||
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
||||
}
|
||||
if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
|
||||
return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
}
|
||||
enc.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
type soapEnvelope struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
||||
Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||
}
|
||||
|
||||
type soapBody struct {
|
||||
Fault *SOAPFaultError `xml:"Fault"`
|
||||
RawAction []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// SOAPFaultError implements error, and contains SOAP fault information.
|
||||
type SOAPFaultError struct {
|
||||
FaultCode string `xml:"faultcode"`
|
||||
FaultString string `xml:"faultstring"`
|
||||
Detail string `xml:"detail"`
|
||||
}
|
||||
|
||||
func (err *SOAPFaultError) Error() string {
|
||||
return fmt.Sprintf("SOAP fault: %s", err.FaultString)
|
||||
}
|
519
vendor/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
519
vendor/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// localLoc acts like time.Local for this package, but is faked out by the
|
||||
// unit tests to ensure that things stay constant (especially when running
|
||||
// this test in a place where local time is UTC which might mask bugs).
|
||||
localLoc = time.Local
|
||||
)
|
||||
|
||||
func MarshalUi1(v uint8) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi1(s string) (uint8, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 8)
|
||||
return uint8(v), err
|
||||
}
|
||||
|
||||
func MarshalUi2(v uint16) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi2(s string) (uint16, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 16)
|
||||
return uint16(v), err
|
||||
}
|
||||
|
||||
func MarshalUi4(v uint32) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi4(s string) (uint32, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
func MarshalI1(v int8) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI1(s string) (int8, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 8)
|
||||
return int8(v), err
|
||||
}
|
||||
|
||||
func MarshalI2(v int16) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI2(s string) (int16, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 16)
|
||||
return int16(v), err
|
||||
}
|
||||
|
||||
func MarshalI4(v int32) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI4(s string) (int32, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
return int32(v), err
|
||||
}
|
||||
|
||||
func MarshalInt(v int64) (string, error) {
|
||||
return strconv.FormatInt(v, 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalInt(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
func MarshalR4(v float32) (string, error) {
|
||||
return strconv.FormatFloat(float64(v), 'G', -1, 32), nil
|
||||
}
|
||||
|
||||
func UnmarshalR4(s string) (float32, error) {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
return float32(v), err
|
||||
}
|
||||
|
||||
func MarshalR8(v float64) (string, error) {
|
||||
return strconv.FormatFloat(v, 'G', -1, 64), nil
|
||||
}
|
||||
|
||||
func UnmarshalR8(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
return float64(v), err
|
||||
}
|
||||
|
||||
// MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
|
||||
func MarshalFixed14_4(v float64) (string, error) {
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', 4, 64), nil
|
||||
}
|
||||
|
||||
// UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
|
||||
func UnmarshalFixed14_4(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MarshalChar marshals rune to SOAP "char" type.
|
||||
func MarshalChar(v rune) (string, error) {
|
||||
if v == 0 {
|
||||
return "", errors.New("soap char: rune 0 is not allowed")
|
||||
}
|
||||
return string(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalChar unmarshals rune from SOAP "char" type.
|
||||
func UnmarshalChar(s string) (rune, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("soap char: got empty string")
|
||||
}
|
||||
r, n := utf8.DecodeRune([]byte(s))
|
||||
if n != len(s) {
|
||||
return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func MarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func UnmarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseInt(s string, err *error) int {
|
||||
v, parseErr := strconv.ParseInt(s, 10, 64)
|
||||
if parseErr != nil {
|
||||
*err = parseErr
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
var dateRegexps = []*regexp.Regexp{
|
||||
// yyyy[-mm[-dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`),
|
||||
// yyyy[mm[dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseDateParts(s string) (year, month, day int, err error) {
|
||||
var parts []string
|
||||
for _, re := range dateRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date: value %q is not in a recognized ISO8601 date format", s)
|
||||
return
|
||||
}
|
||||
|
||||
year = parseInt(parts[1], &err)
|
||||
month = 1
|
||||
day = 1
|
||||
if len(parts[2]) != 0 {
|
||||
month = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
day = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap date: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var timeRegexps = []*regexp.Regexp{
|
||||
// hh[:mm[:ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
|
||||
// hh[mm[ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseTimeParts(s string) (hour, minute, second int, err error) {
|
||||
var parts []string
|
||||
for _, re := range timeRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
|
||||
return
|
||||
}
|
||||
|
||||
hour = parseInt(parts[1], &err)
|
||||
if len(parts[2]) != 0 {
|
||||
minute = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
second = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap time: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// (+|-)hh[[:]mm]
|
||||
var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
|
||||
|
||||
func parseTimezone(s string) (offset int, err error) {
|
||||
if s == "Z" {
|
||||
return 0, nil
|
||||
}
|
||||
parts := timezoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
|
||||
return
|
||||
}
|
||||
|
||||
offset = parseInt(parts[2], &err) * 3600
|
||||
if len(parts[3]) != 0 {
|
||||
offset += parseInt(parts[3], &err) * 60
|
||||
}
|
||||
if parts[1] == "-" {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap timezone: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
|
||||
|
||||
// splitCompleteDateTimeZone splits date, time and timezone apart from an
|
||||
// ISO8601 string. It does not ensure that the contents of each part are
|
||||
// correct, it merely splits on certain delimiters.
|
||||
// e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
|
||||
// Timezone can only be present if time is also present.
|
||||
func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
|
||||
parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
|
||||
return
|
||||
}
|
||||
dateStr = parts[1]
|
||||
timeStr = parts[2]
|
||||
zoneStr = parts[3]
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
|
||||
// to local time, and discards the time-of-day components.
|
||||
func MarshalDate(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02"), nil
|
||||
}
|
||||
|
||||
var dateFmts = []string{"2006-01-02", "20060102"}
|
||||
|
||||
// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
|
||||
// date as midnight in the local time zone.
|
||||
func UnmarshalDate(s string) (time.Time, error) {
|
||||
year, month, day, err := parseDateParts(s)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc), nil
|
||||
}
|
||||
|
||||
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
|
||||
type TimeOfDay struct {
|
||||
// Duration of time since midnight.
|
||||
FromMidnight time.Duration
|
||||
|
||||
// Set to true if Offset is specified. If false, then the timezone is
|
||||
// unspecified (and by ISO8601 - implies some "local" time).
|
||||
HasOffset bool
|
||||
|
||||
// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
|
||||
// non-zero, then it is regarded as a UTC offset in seconds. Note that the
|
||||
// sub-minutes is ignored by the marshal function.
|
||||
Offset int
|
||||
}
|
||||
|
||||
// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
|
||||
func MarshalTimeOfDay(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
|
||||
func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
|
||||
t, err := UnmarshalTimeOfDayTz(s)
|
||||
if err != nil {
|
||||
return TimeOfDay{}, err
|
||||
} else if t.HasOffset {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
|
||||
func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
tz := ""
|
||||
if v.HasOffset {
|
||||
if v.Offset == 0 {
|
||||
tz = "Z"
|
||||
} else {
|
||||
offsetMins := v.Offset / 60
|
||||
sign := '+'
|
||||
if offsetMins < 1 {
|
||||
offsetMins = -offsetMins
|
||||
sign = '-'
|
||||
}
|
||||
tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
|
||||
func UnmarshalTimeOfDayTz(s string) (tod TimeOfDay, err error) {
|
||||
zoneIndex := strings.IndexAny(s, "Z+-")
|
||||
var timePart string
|
||||
var hasOffset bool
|
||||
var offset int
|
||||
if zoneIndex == -1 {
|
||||
hasOffset = false
|
||||
timePart = s
|
||||
} else {
|
||||
hasOffset = true
|
||||
timePart = s[:zoneIndex]
|
||||
if offset, err = parseTimezone(s[zoneIndex:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hour, minute, second, err := parseTimeParts(timePart)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second
|
||||
|
||||
// ISO8601 special case - values up to 24:00:00 are allowed, so using
|
||||
// strictly greater-than for the maximum value.
|
||||
if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
|
||||
}
|
||||
|
||||
return TimeOfDay{
|
||||
FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
|
||||
HasOffset: hasOffset,
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalDateTime marshals time.Time to SOAP "dateTime" type. Note that this
|
||||
// converts to local time.
|
||||
func MarshalDateTime(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02T15:04:05"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTime unmarshals time.Time from the SOAP "dateTime" type. This
|
||||
// returns a value in the local timezone.
|
||||
func UnmarshalDateTime(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(zoneStr) != 0 {
|
||||
err = fmt.Errorf("soap datetime: unexpected timezone in %q", s)
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, localLoc)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDateTimeTz marshals time.Time to SOAP "dateTime.tz" type.
|
||||
func MarshalDateTimeTz(v time.Time) (string, error) {
|
||||
return v.Format("2006-01-02T15:04:05-07:00"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTimeTz unmarshals time.Time from the SOAP "dateTime.tz" type.
|
||||
// This returns a value in the local timezone when the timezone is unspecified.
|
||||
func UnmarshalDateTimeTz(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
var location *time.Location = localLoc
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(zoneStr) != 0 {
|
||||
var offset int
|
||||
offset, err = parseTimezone(zoneStr)
|
||||
if offset == 0 {
|
||||
location = time.UTC
|
||||
} else {
|
||||
location = time.FixedZone("", offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBoolean marshals bool to SOAP "boolean" type.
|
||||
func MarshalBoolean(v bool) (string, error) {
|
||||
if v {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
}
|
||||
|
||||
// UnmarshalBoolean unmarshals bool from the SOAP "boolean" type.
|
||||
func UnmarshalBoolean(s string) (bool, error) {
|
||||
switch s {
|
||||
case "0", "false", "no":
|
||||
return false, nil
|
||||
case "1", "true", "yes":
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
|
||||
}
|
||||
|
||||
// MarshalBinBase64 marshals []byte to SOAP "bin.base64" type.
|
||||
func MarshalBinBase64(v []byte) (string, error) {
|
||||
return base64.StdEncoding.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinBase64 unmarshals []byte from the SOAP "bin.base64" type.
|
||||
func UnmarshalBinBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// MarshalBinHex marshals []byte to SOAP "bin.hex" type.
|
||||
func MarshalBinHex(v []byte) (string, error) {
|
||||
return hex.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinHex unmarshals []byte from the SOAP "bin.hex" type.
|
||||
func UnmarshalBinHex(s string) ([]byte, error) {
|
||||
return hex.DecodeString(s)
|
||||
}
|
||||
|
||||
// MarshalURI marshals *url.URL to SOAP "uri" type.
|
||||
func MarshalURI(v *url.URL) (string, error) {
|
||||
return v.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalURI unmarshals *url.URL from the SOAP "uri" type.
|
||||
func UnmarshalURI(s string) (*url.URL, error) {
|
||||
return url.Parse(s)
|
||||
}
|
312
vendor/github.com/huin/goupnp/ssdp/registry.go
generated
vendored
Normal file
312
vendor/github.com/huin/goupnp/ssdp/registry.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp/httpu"
|
||||
)
|
||||
|
||||
const (
|
||||
maxExpiryTimeSeconds = 24 * 60 * 60
|
||||
)
|
||||
|
||||
var (
|
||||
maxAgeRx = regexp.MustCompile("max-age=([0-9]+)")
|
||||
)
|
||||
|
||||
const (
|
||||
EventAlive = EventType(iota)
|
||||
EventUpdate
|
||||
EventByeBye
|
||||
)
|
||||
|
||||
type EventType int8
|
||||
|
||||
func (et EventType) String() string {
|
||||
switch et {
|
||||
case EventAlive:
|
||||
return "EventAlive"
|
||||
case EventUpdate:
|
||||
return "EventUpdate"
|
||||
case EventByeBye:
|
||||
return "EventByeBye"
|
||||
default:
|
||||
return fmt.Sprintf("EventUnknown(%d)", int8(et))
|
||||
}
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
// The USN of the service.
|
||||
USN string
|
||||
// What happened.
|
||||
EventType EventType
|
||||
// The entry, which is nil if the service was not known and
|
||||
// EventType==EventByeBye. The contents of this must not be modified as it is
|
||||
// shared with the registry and other listeners. Once created, the Registry
|
||||
// does not modify the Entry value - any updates are replaced with a new
|
||||
// Entry value.
|
||||
Entry *Entry
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
// The address that the entry data was actually received from.
|
||||
RemoteAddr string
|
||||
// Unique Service Name. Identifies a unique instance of a device or service.
|
||||
USN string
|
||||
// Notfication Type. The type of device or service being announced.
|
||||
NT string
|
||||
// Server's self-identifying string.
|
||||
Server string
|
||||
Host string
|
||||
// Location of the UPnP root device description.
|
||||
Location url.URL
|
||||
|
||||
// Despite BOOTID,CONFIGID being required fields, apparently they are not
|
||||
// always set by devices. Set to -1 if not present.
|
||||
|
||||
BootID int32
|
||||
ConfigID int32
|
||||
|
||||
SearchPort uint16
|
||||
|
||||
// When the last update was received for this entry identified by this USN.
|
||||
LastUpdate time.Time
|
||||
// When the last update's cached values are advised to expire.
|
||||
CacheExpiry time.Time
|
||||
}
|
||||
|
||||
func newEntryFromRequest(r *http.Request) (*Entry, error) {
|
||||
now := time.Now()
|
||||
expiryDuration, err := parseCacheControlMaxAge(r.Header.Get("CACHE-CONTROL"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssdp: error parsing CACHE-CONTROL max age: %v", err)
|
||||
}
|
||||
|
||||
loc, err := url.Parse(r.Header.Get("LOCATION"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssdp: error parsing entry Location URL: %v", err)
|
||||
}
|
||||
|
||||
bootID, err := parseUpnpIntHeader(r.Header, "BOOTID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configID, err := parseUpnpIntHeader(r.Header, "CONFIGID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
searchPort, err := parseUpnpIntHeader(r.Header, "SEARCHPORT.UPNP.ORG", ssdpSearchPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if searchPort < 1 || searchPort > 65535 {
|
||||
return nil, fmt.Errorf("ssdp: search port %d is out of range", searchPort)
|
||||
}
|
||||
|
||||
return &Entry{
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
USN: r.Header.Get("USN"),
|
||||
NT: r.Header.Get("NT"),
|
||||
Server: r.Header.Get("SERVER"),
|
||||
Host: r.Header.Get("HOST"),
|
||||
Location: *loc,
|
||||
BootID: bootID,
|
||||
ConfigID: configID,
|
||||
SearchPort: uint16(searchPort),
|
||||
LastUpdate: now,
|
||||
CacheExpiry: now.Add(expiryDuration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCacheControlMaxAge(cc string) (time.Duration, error) {
|
||||
matches := maxAgeRx.FindStringSubmatch(cc)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("did not find exactly one max-age in cache control header: %q", cc)
|
||||
}
|
||||
expirySeconds, err := strconv.ParseInt(matches[1], 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if expirySeconds < 1 || expirySeconds > maxExpiryTimeSeconds {
|
||||
return 0, fmt.Errorf("rejecting bad expiry time of %d seconds", expirySeconds)
|
||||
}
|
||||
return time.Duration(expirySeconds) * time.Second, nil
|
||||
}
|
||||
|
||||
// parseUpnpIntHeader is intended to parse the
|
||||
// {BOOT,CONFIGID,SEARCHPORT}.UPNP.ORG header fields. It returns the def if
|
||||
// the head is empty or missing.
|
||||
func parseUpnpIntHeader(headers http.Header, headerName string, def int32) (int32, error) {
|
||||
s := headers.Get(headerName)
|
||||
if s == "" {
|
||||
return def, nil
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ssdp: could not parse header %s: %v", headerName, err)
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
|
||||
var _ httpu.Handler = new(Registry)
|
||||
|
||||
// Registry maintains knowledge of discovered devices and services.
|
||||
//
|
||||
// NOTE: the interface for this is experimental and may change, or go away
|
||||
// entirely.
|
||||
type Registry struct {
|
||||
lock sync.Mutex
|
||||
byUSN map[string]*Entry
|
||||
|
||||
listenersLock sync.RWMutex
|
||||
listeners map[chan<- Update]struct{}
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
byUSN: make(map[string]*Entry),
|
||||
listeners: make(map[chan<- Update]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewServerAndRegistry is a convenience function to create a registry, and an
|
||||
// httpu server to pass it messages. Call ListenAndServe on the server for
|
||||
// messages to be processed.
|
||||
func NewServerAndRegistry() (*httpu.Server, *Registry) {
|
||||
reg := NewRegistry()
|
||||
srv := &httpu.Server{
|
||||
Addr: ssdpUDP4Addr,
|
||||
Multicast: true,
|
||||
Handler: reg,
|
||||
}
|
||||
return srv, reg
|
||||
}
|
||||
|
||||
func (reg *Registry) AddListener(c chan<- Update) {
|
||||
reg.listenersLock.Lock()
|
||||
defer reg.listenersLock.Unlock()
|
||||
reg.listeners[c] = struct{}{}
|
||||
}
|
||||
|
||||
func (reg *Registry) RemoveListener(c chan<- Update) {
|
||||
reg.listenersLock.Lock()
|
||||
defer reg.listenersLock.Unlock()
|
||||
delete(reg.listeners, c)
|
||||
}
|
||||
|
||||
func (reg *Registry) sendUpdate(u Update) {
|
||||
reg.listenersLock.RLock()
|
||||
defer reg.listenersLock.RUnlock()
|
||||
for c := range reg.listeners {
|
||||
c <- u
|
||||
}
|
||||
}
|
||||
|
||||
// GetService returns known service (or device) entries for the given service
|
||||
// URN.
|
||||
func (reg *Registry) GetService(serviceURN string) []*Entry {
|
||||
// Currently assumes that the map is small, so we do a linear search rather
|
||||
// than indexed to avoid maintaining two maps.
|
||||
var results []*Entry
|
||||
reg.lock.Lock()
|
||||
defer reg.lock.Unlock()
|
||||
for _, entry := range reg.byUSN {
|
||||
if entry.NT == serviceURN {
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ServeMessage implements httpu.Handler, and uses SSDP NOTIFY requests to
|
||||
// maintain the registry of devices and services.
|
||||
func (reg *Registry) ServeMessage(r *http.Request) {
|
||||
if r.Method != methodNotify {
|
||||
return
|
||||
}
|
||||
|
||||
nts := r.Header.Get("nts")
|
||||
|
||||
var err error
|
||||
switch nts {
|
||||
case ntsAlive:
|
||||
err = reg.handleNTSAlive(r)
|
||||
case ntsUpdate:
|
||||
err = reg.handleNTSUpdate(r)
|
||||
case ntsByebye:
|
||||
err = reg.handleNTSByebye(r)
|
||||
default:
|
||||
err = fmt.Errorf("unknown NTS value: %q", nts)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("goupnp/ssdp: failed to handle %s message from %s: %v", nts, r.RemoteAddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSAlive(r *http.Request) error {
|
||||
entry, err := newEntryFromRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg.lock.Lock()
|
||||
reg.byUSN[entry.USN] = entry
|
||||
reg.lock.Unlock()
|
||||
|
||||
reg.sendUpdate(Update{
|
||||
USN: entry.USN,
|
||||
EventType: EventAlive,
|
||||
Entry: entry,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSUpdate(r *http.Request) error {
|
||||
entry, err := newEntryFromRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nextBootID, err := parseUpnpIntHeader(r.Header, "NEXTBOOTID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.BootID = nextBootID
|
||||
|
||||
reg.lock.Lock()
|
||||
reg.byUSN[entry.USN] = entry
|
||||
reg.lock.Unlock()
|
||||
|
||||
reg.sendUpdate(Update{
|
||||
USN: entry.USN,
|
||||
EventType: EventUpdate,
|
||||
Entry: entry,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSByebye(r *http.Request) error {
|
||||
usn := r.Header.Get("USN")
|
||||
|
||||
reg.lock.Lock()
|
||||
entry := reg.byUSN[usn]
|
||||
delete(reg.byUSN, usn)
|
||||
reg.lock.Unlock()
|
||||
|
||||
reg.sendUpdate(Update{
|
||||
USN: usn,
|
||||
EventType: EventByeBye,
|
||||
Entry: entry,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
83
vendor/github.com/huin/goupnp/ssdp/ssdp.go
generated
vendored
Normal file
83
vendor/github.com/huin/goupnp/ssdp/ssdp.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp/httpu"
|
||||
)
|
||||
|
||||
const (
|
||||
ssdpDiscover = `"ssdp:discover"`
|
||||
ntsAlive = `ssdp:alive`
|
||||
ntsByebye = `ssdp:byebye`
|
||||
ntsUpdate = `ssdp:update`
|
||||
ssdpUDP4Addr = "239.255.255.250:1900"
|
||||
ssdpSearchPort = 1900
|
||||
methodSearch = "M-SEARCH"
|
||||
methodNotify = "NOTIFY"
|
||||
)
|
||||
|
||||
// SSDPRawSearch performs a fairly raw SSDP search request, and returns the
|
||||
// unique response(s) that it receives. Each response has the requested
|
||||
// searchTarget, a USN, and a valid location. maxWaitSeconds states how long to
|
||||
// wait for responses in seconds, and must be a minimum of 1 (the
|
||||
// implementation waits an additional 100ms for responses to arrive), 2 is a
|
||||
// reasonable value for this. numSends is the number of requests to send - 3 is
|
||||
// a reasonable value for this.
|
||||
func SSDPRawSearch(httpu *httpu.HTTPUClient, searchTarget string, maxWaitSeconds int, numSends int) ([]*http.Response, error) {
|
||||
if maxWaitSeconds < 1 {
|
||||
return nil, errors.New("ssdp: maxWaitSeconds must be >= 1")
|
||||
}
|
||||
|
||||
seenUsns := make(map[string]bool)
|
||||
var responses []*http.Response
|
||||
req := http.Request{
|
||||
Method: methodSearch,
|
||||
// TODO: Support both IPv4 and IPv6.
|
||||
Host: ssdpUDP4Addr,
|
||||
URL: &url.URL{Opaque: "*"},
|
||||
Header: http.Header{
|
||||
// Putting headers in here avoids them being title-cased.
|
||||
// (The UPnP discovery protocol uses case-sensitive headers)
|
||||
"HOST": []string{ssdpUDP4Addr},
|
||||
"MX": []string{strconv.FormatInt(int64(maxWaitSeconds), 10)},
|
||||
"MAN": []string{ssdpDiscover},
|
||||
"ST": []string{searchTarget},
|
||||
},
|
||||
}
|
||||
allResponses, err := httpu.Do(&req, time.Duration(maxWaitSeconds)*time.Second+100*time.Millisecond, numSends)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, response := range allResponses {
|
||||
if response.StatusCode != 200 {
|
||||
log.Printf("ssdp: got response status code %q in search response", response.Status)
|
||||
continue
|
||||
}
|
||||
if st := response.Header.Get("ST"); st != searchTarget {
|
||||
log.Printf("ssdp: got unexpected search target result %q", st)
|
||||
continue
|
||||
}
|
||||
location, err := response.Location()
|
||||
if err != nil {
|
||||
log.Printf("ssdp: no usable location in search response (discarding): %v", err)
|
||||
continue
|
||||
}
|
||||
usn := response.Header.Get("USN")
|
||||
if usn == "" {
|
||||
log.Printf("ssdp: empty/missing USN in search response (using location instead): %v", err)
|
||||
usn = location.String()
|
||||
}
|
||||
if _, alreadySeen := seenUsns[usn]; !alreadySeen {
|
||||
seenUsns[usn] = true
|
||||
responses = append(responses, response)
|
||||
}
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
Reference in New Issue
Block a user