aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/huin/goupnp/ssdp
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2016-10-29 01:05:01 +0800
committerFelix Lange <fjl@twurst.com>2016-10-29 01:05:01 +0800
commit289b30715d097edafd5562f66cb3567a70b2d330 (patch)
tree7eaaa6da97c84727469303b986e364606ece57ce /vendor/github.com/huin/goupnp/ssdp
parent77703045765343c489ded2f43e3ed0f332c5f148 (diff)
downloadgo-tangerine-289b30715d097edafd5562f66cb3567a70b2d330.tar.gz
go-tangerine-289b30715d097edafd5562f66cb3567a70b2d330.tar.zst
go-tangerine-289b30715d097edafd5562f66cb3567a70b2d330.zip
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.
Diffstat (limited to 'vendor/github.com/huin/goupnp/ssdp')
-rw-r--r--vendor/github.com/huin/goupnp/ssdp/registry.go312
-rw-r--r--vendor/github.com/huin/goupnp/ssdp/ssdp.go83
2 files changed, 395 insertions, 0 deletions
diff --git a/vendor/github.com/huin/goupnp/ssdp/registry.go b/vendor/github.com/huin/goupnp/ssdp/registry.go
new file mode 100644
index 000000000..2f84beaae
--- /dev/null
+++ b/vendor/github.com/huin/goupnp/ssdp/registry.go
@@ -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
+}
diff --git a/vendor/github.com/huin/goupnp/ssdp/ssdp.go b/vendor/github.com/huin/goupnp/ssdp/ssdp.go
new file mode 100644
index 000000000..8178f5d94
--- /dev/null
+++ b/vendor/github.com/huin/goupnp/ssdp/ssdp.go
@@ -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
+}