This repository has been archived by the owner on Dec 3, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
/
ssdp.go
84 lines (77 loc) · 2.53 KB
/
ssdp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package ssdp
import (
"context"
"errors"
"net/http"
"net/url"
"strconv"
"time"
"gitlab.com/NebulousLabs/go-upnp/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 is deprecated; use SSDPRawSearchCtx instead
func SSDPRawSearch(httpu *httpu.HTTPUClient, searchTarget string, maxWaitSeconds int, numSends int) ([]*http.Response, error) {
return SSDPRawSearchCtx(context.Background(), httpu, searchTarget, maxWaitSeconds, numSends)
}
// 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 SSDPRawSearchCtx(ctx context.Context, 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},
},
}).WithContext(ctx)
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 {
continue
}
if st := response.Header.Get("ST"); st != searchTarget {
continue
}
location, err := response.Location()
if err != nil {
continue
}
usn := response.Header.Get("USN")
if usn == "" {
usn = location.String()
}
if _, alreadySeen := seenUsns[usn]; !alreadySeen {
seenUsns[usn] = true
responses = append(responses, response)
}
}
return responses, nil
}