174 lines
3.2 KiB
Go
174 lines
3.2 KiB
Go
|
package ssdp
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
// Monitor monitors SSDP's alive and byebye messages.
|
||
|
type Monitor struct {
|
||
|
alive AliveHandler
|
||
|
bye ByeHandler
|
||
|
conn net.PacketConn
|
||
|
wg sync.WaitGroup
|
||
|
}
|
||
|
|
||
|
// NewMonitor creates a new Monitor.
|
||
|
func NewMonitor(alive AliveHandler, bye ByeHandler) (*Monitor, error) {
|
||
|
if alive == nil {
|
||
|
alive = nullAlive
|
||
|
}
|
||
|
if bye == nil {
|
||
|
bye = nullBye
|
||
|
}
|
||
|
conn, err := multicastListen("0.0.0.0:1900")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
logf("monitoring on %s", conn.LocalAddr().String())
|
||
|
m := &Monitor{
|
||
|
alive: alive,
|
||
|
bye: bye,
|
||
|
conn: conn,
|
||
|
}
|
||
|
m.wg.Add(1)
|
||
|
go func() {
|
||
|
m.serve()
|
||
|
m.wg.Done()
|
||
|
}()
|
||
|
return m, nil
|
||
|
}
|
||
|
|
||
|
func (m *Monitor) serve() error {
|
||
|
buf := make([]byte, 65535)
|
||
|
for {
|
||
|
n, addr, err := m.conn.ReadFrom(buf)
|
||
|
if err != nil {
|
||
|
if err == io.EOF {
|
||
|
return nil
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
msg := make([]byte, n)
|
||
|
copy(msg, buf[:n])
|
||
|
go m.handleRaw(addr, msg)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *Monitor) handleRaw(addr net.Addr, raw []byte) error {
|
||
|
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw)))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
switch nts := req.Header.Get("NTS"); nts {
|
||
|
case "ssdp:alive":
|
||
|
if req.Method != "NOTIFY" {
|
||
|
return fmt.Errorf("unexpected method for %q: %s", "ssdp:alive", req.Method)
|
||
|
}
|
||
|
m.alive(&Alive{
|
||
|
From: addr,
|
||
|
Type: req.Header.Get("NT"),
|
||
|
USN: req.Header.Get("USN"),
|
||
|
Location: req.Header.Get("LOCATION"),
|
||
|
Server: req.Header.Get("SERVER"),
|
||
|
rawHeader: req.Header,
|
||
|
})
|
||
|
case "ssdp:byebye":
|
||
|
if req.Method != "NOTIFY" {
|
||
|
return fmt.Errorf("unexpected method for %q: %s", "ssdp:byebye", req.Method)
|
||
|
}
|
||
|
m.bye(&Bye{
|
||
|
From: addr,
|
||
|
Type: req.Header.Get("NT"),
|
||
|
USN: req.Header.Get("USN"),
|
||
|
rawHeader: req.Header,
|
||
|
})
|
||
|
default:
|
||
|
return fmt.Errorf("unknown NTS: %s", nts)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close closes monitoring.
|
||
|
func (m *Monitor) Close() error {
|
||
|
if m.conn != nil {
|
||
|
m.conn.Close()
|
||
|
m.conn = nil
|
||
|
m.wg.Wait()
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Alive represents SSDP's ssdp:alive message.
|
||
|
type Alive struct {
|
||
|
// From is a sender of this message
|
||
|
From net.Addr
|
||
|
|
||
|
// Type is a property of "NT"
|
||
|
Type string
|
||
|
|
||
|
// USN is a property of "USN"
|
||
|
USN string
|
||
|
|
||
|
// Location is a property of "LOCATION"
|
||
|
Location string
|
||
|
|
||
|
// Server is a property of "SERVER"
|
||
|
Server string
|
||
|
|
||
|
rawHeader http.Header
|
||
|
maxAge *int
|
||
|
}
|
||
|
|
||
|
// Header returns all properties in response of search.
|
||
|
func (m *Alive) Header() http.Header {
|
||
|
return m.rawHeader
|
||
|
}
|
||
|
|
||
|
// MaxAge extracts "max-age" value from "CACHE-CONTROL" property.
|
||
|
func (m *Alive) MaxAge() int {
|
||
|
if m.maxAge == nil {
|
||
|
m.maxAge = new(int)
|
||
|
*m.maxAge = extractMaxAge(m.rawHeader.Get("CACHE-CONTROL"), -1)
|
||
|
}
|
||
|
return *m.maxAge
|
||
|
}
|
||
|
|
||
|
// AliveHandler is handler of Alive message.
|
||
|
type AliveHandler func(*Alive)
|
||
|
|
||
|
func nullAlive(*Alive) {
|
||
|
// nothing to do.
|
||
|
}
|
||
|
|
||
|
// Bye represents SSDP's ssdp:byebye message.
|
||
|
type Bye struct {
|
||
|
// From is a sender of this message
|
||
|
From net.Addr
|
||
|
|
||
|
// Type is a property of "NT"
|
||
|
Type string
|
||
|
|
||
|
// USN is a property of "USN"
|
||
|
USN string
|
||
|
|
||
|
rawHeader http.Header
|
||
|
}
|
||
|
|
||
|
// Header returns all properties in response of search.
|
||
|
func (m *Bye) Header() http.Header {
|
||
|
return m.rawHeader
|
||
|
}
|
||
|
|
||
|
// ByeHandler is handler of Bye message.
|
||
|
type ByeHandler func(*Bye)
|
||
|
|
||
|
func nullBye(*Bye) {
|
||
|
// nothing to do.
|
||
|
}
|