aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/huin/goupnp/ssdp/ssdp.go
blob: 8178f5d9486f7453789d4bc085950d5b65354f29 (plain) (blame)
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
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
}