registry.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package ssdp
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "net/url"
  7. "regexp"
  8. "strconv"
  9. "sync"
  10. "time"
  11. "github.com/huin/goupnp/httpu"
  12. )
  13. const (
  14. maxExpiryTimeSeconds = 24 * 60 * 60
  15. )
  16. var (
  17. maxAgeRx = regexp.MustCompile("max-age= *([0-9]+)")
  18. )
  19. const (
  20. EventAlive = EventType(iota)
  21. EventUpdate
  22. EventByeBye
  23. )
  24. type EventType int8
  25. func (et EventType) String() string {
  26. switch et {
  27. case EventAlive:
  28. return "EventAlive"
  29. case EventUpdate:
  30. return "EventUpdate"
  31. case EventByeBye:
  32. return "EventByeBye"
  33. default:
  34. return fmt.Sprintf("EventUnknown(%d)", int8(et))
  35. }
  36. }
  37. type Update struct {
  38. // The USN of the service.
  39. USN string
  40. // What happened.
  41. EventType EventType
  42. // The entry, which is nil if the service was not known and
  43. // EventType==EventByeBye. The contents of this must not be modified as it is
  44. // shared with the registry and other listeners. Once created, the Registry
  45. // does not modify the Entry value - any updates are replaced with a new
  46. // Entry value.
  47. Entry *Entry
  48. }
  49. type Entry struct {
  50. // The address that the entry data was actually received from.
  51. RemoteAddr string
  52. // Unique Service Name. Identifies a unique instance of a device or service.
  53. USN string
  54. // Notfication Type. The type of device or service being announced.
  55. NT string
  56. // Server's self-identifying string.
  57. Server string
  58. Host string
  59. // Location of the UPnP root device description.
  60. Location url.URL
  61. // Despite BOOTID,CONFIGID being required fields, apparently they are not
  62. // always set by devices. Set to -1 if not present.
  63. BootID int32
  64. ConfigID int32
  65. SearchPort uint16
  66. // When the last update was received for this entry identified by this USN.
  67. LastUpdate time.Time
  68. // When the last update's cached values are advised to expire.
  69. CacheExpiry time.Time
  70. }
  71. func newEntryFromRequest(r *http.Request) (*Entry, error) {
  72. now := time.Now()
  73. expiryDuration, err := parseCacheControlMaxAge(r.Header.Get("CACHE-CONTROL"))
  74. if err != nil {
  75. return nil, fmt.Errorf("ssdp: error parsing CACHE-CONTROL max age: %v", err)
  76. }
  77. loc, err := url.Parse(r.Header.Get("LOCATION"))
  78. if err != nil {
  79. return nil, fmt.Errorf("ssdp: error parsing entry Location URL: %v", err)
  80. }
  81. bootID, err := parseUpnpIntHeader(r.Header, "BOOTID.UPNP.ORG", -1)
  82. if err != nil {
  83. return nil, err
  84. }
  85. configID, err := parseUpnpIntHeader(r.Header, "CONFIGID.UPNP.ORG", -1)
  86. if err != nil {
  87. return nil, err
  88. }
  89. searchPort, err := parseUpnpIntHeader(r.Header, "SEARCHPORT.UPNP.ORG", ssdpSearchPort)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if searchPort < 1 || searchPort > 65535 {
  94. return nil, fmt.Errorf("ssdp: search port %d is out of range", searchPort)
  95. }
  96. return &Entry{
  97. RemoteAddr: r.RemoteAddr,
  98. USN: r.Header.Get("USN"),
  99. NT: r.Header.Get("NT"),
  100. Server: r.Header.Get("SERVER"),
  101. Host: r.Header.Get("HOST"),
  102. Location: *loc,
  103. BootID: bootID,
  104. ConfigID: configID,
  105. SearchPort: uint16(searchPort),
  106. LastUpdate: now,
  107. CacheExpiry: now.Add(expiryDuration),
  108. }, nil
  109. }
  110. func parseCacheControlMaxAge(cc string) (time.Duration, error) {
  111. matches := maxAgeRx.FindStringSubmatch(cc)
  112. if len(matches) != 2 {
  113. return 0, fmt.Errorf("did not find exactly one max-age in cache control header: %q", cc)
  114. }
  115. expirySeconds, err := strconv.ParseInt(matches[1], 10, 16)
  116. if err != nil {
  117. return 0, err
  118. }
  119. if expirySeconds < 1 || expirySeconds > maxExpiryTimeSeconds {
  120. return 0, fmt.Errorf("rejecting bad expiry time of %d seconds", expirySeconds)
  121. }
  122. return time.Duration(expirySeconds) * time.Second, nil
  123. }
  124. // parseUpnpIntHeader is intended to parse the
  125. // {BOOT,CONFIGID,SEARCHPORT}.UPNP.ORG header fields. It returns the def if
  126. // the head is empty or missing.
  127. func parseUpnpIntHeader(headers http.Header, headerName string, def int32) (int32, error) {
  128. s := headers.Get(headerName)
  129. if s == "" {
  130. return def, nil
  131. }
  132. v, err := strconv.ParseInt(s, 10, 32)
  133. if err != nil {
  134. return 0, fmt.Errorf("ssdp: could not parse header %s: %v", headerName, err)
  135. }
  136. return int32(v), nil
  137. }
  138. var _ httpu.Handler = new(Registry)
  139. // Registry maintains knowledge of discovered devices and services.
  140. //
  141. // NOTE: the interface for this is experimental and may change, or go away
  142. // entirely.
  143. type Registry struct {
  144. lock sync.Mutex
  145. byUSN map[string]*Entry
  146. listenersLock sync.RWMutex
  147. listeners map[chan<- Update]struct{}
  148. }
  149. func NewRegistry() *Registry {
  150. return &Registry{
  151. byUSN: make(map[string]*Entry),
  152. listeners: make(map[chan<- Update]struct{}),
  153. }
  154. }
  155. // NewServerAndRegistry is a convenience function to create a registry, and an
  156. // httpu server to pass it messages. Call ListenAndServe on the server for
  157. // messages to be processed.
  158. func NewServerAndRegistry() (*httpu.Server, *Registry) {
  159. reg := NewRegistry()
  160. srv := &httpu.Server{
  161. Addr: ssdpUDP4Addr,
  162. Multicast: true,
  163. Handler: reg,
  164. }
  165. return srv, reg
  166. }
  167. func (reg *Registry) AddListener(c chan<- Update) {
  168. reg.listenersLock.Lock()
  169. defer reg.listenersLock.Unlock()
  170. reg.listeners[c] = struct{}{}
  171. }
  172. func (reg *Registry) RemoveListener(c chan<- Update) {
  173. reg.listenersLock.Lock()
  174. defer reg.listenersLock.Unlock()
  175. delete(reg.listeners, c)
  176. }
  177. func (reg *Registry) sendUpdate(u Update) {
  178. reg.listenersLock.RLock()
  179. defer reg.listenersLock.RUnlock()
  180. for c := range reg.listeners {
  181. c <- u
  182. }
  183. }
  184. // GetService returns known service (or device) entries for the given service
  185. // URN.
  186. func (reg *Registry) GetService(serviceURN string) []*Entry {
  187. // Currently assumes that the map is small, so we do a linear search rather
  188. // than indexed to avoid maintaining two maps.
  189. var results []*Entry
  190. reg.lock.Lock()
  191. defer reg.lock.Unlock()
  192. for _, entry := range reg.byUSN {
  193. if entry.NT == serviceURN {
  194. results = append(results, entry)
  195. }
  196. }
  197. return results
  198. }
  199. // ServeMessage implements httpu.Handler, and uses SSDP NOTIFY requests to
  200. // maintain the registry of devices and services.
  201. func (reg *Registry) ServeMessage(r *http.Request) {
  202. if r.Method != methodNotify {
  203. return
  204. }
  205. nts := r.Header.Get("nts")
  206. var err error
  207. switch nts {
  208. case ntsAlive:
  209. err = reg.handleNTSAlive(r)
  210. case ntsUpdate:
  211. err = reg.handleNTSUpdate(r)
  212. case ntsByebye:
  213. err = reg.handleNTSByebye(r)
  214. default:
  215. err = fmt.Errorf("unknown NTS value: %q", nts)
  216. }
  217. if err != nil {
  218. log.Printf("goupnp/ssdp: failed to handle %s message from %s: %v", nts, r.RemoteAddr, err)
  219. }
  220. }
  221. func (reg *Registry) handleNTSAlive(r *http.Request) error {
  222. entry, err := newEntryFromRequest(r)
  223. if err != nil {
  224. return err
  225. }
  226. reg.lock.Lock()
  227. reg.byUSN[entry.USN] = entry
  228. reg.lock.Unlock()
  229. reg.sendUpdate(Update{
  230. USN: entry.USN,
  231. EventType: EventAlive,
  232. Entry: entry,
  233. })
  234. return nil
  235. }
  236. func (reg *Registry) handleNTSUpdate(r *http.Request) error {
  237. entry, err := newEntryFromRequest(r)
  238. if err != nil {
  239. return err
  240. }
  241. nextBootID, err := parseUpnpIntHeader(r.Header, "NEXTBOOTID.UPNP.ORG", -1)
  242. if err != nil {
  243. return err
  244. }
  245. entry.BootID = nextBootID
  246. reg.lock.Lock()
  247. reg.byUSN[entry.USN] = entry
  248. reg.lock.Unlock()
  249. reg.sendUpdate(Update{
  250. USN: entry.USN,
  251. EventType: EventUpdate,
  252. Entry: entry,
  253. })
  254. return nil
  255. }
  256. func (reg *Registry) handleNTSByebye(r *http.Request) error {
  257. usn := r.Header.Get("USN")
  258. reg.lock.Lock()
  259. entry := reg.byUSN[usn]
  260. delete(reg.byUSN, usn)
  261. reg.lock.Unlock()
  262. reg.sendUpdate(Update{
  263. USN: usn,
  264. EventType: EventByeBye,
  265. Entry: entry,
  266. })
  267. return nil
  268. }