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:
Péter Szilágyi
2016-10-28 20:05:01 +03:00
committed by Felix Lange
parent 7770304576
commit 289b30715d
886 changed files with 23194 additions and 32856 deletions

1
vendor/github.com/huin/goupnp/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/gotasks/specs

23
vendor/github.com/huin/goupnp/LICENSE generated vendored Normal file
View 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
View 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 ![GoDoc](https://godoc.org/github.com/huin/goupnp?status.svg).
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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

184
vendor/github.com/huin/goupnp/device.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}