status-go/vendor/github.com/koron/go-ssdp/advertise.go

186 lines
4.1 KiB
Go
Raw Permalink Normal View History

2019-06-09 07:24:20 +00:00
package ssdp
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"sync"
2023-05-19 20:23:55 +00:00
"github.com/koron/go-ssdp/internal/multicast"
"github.com/koron/go-ssdp/internal/ssdplog"
2019-06-09 07:24:20 +00:00
)
type message struct {
to net.Addr
2023-05-19 20:23:55 +00:00
data multicast.DataProvider
2019-06-09 07:24:20 +00:00
}
// Advertiser is a server to advertise a service.
type Advertiser struct {
2023-05-19 20:23:55 +00:00
st string
usn string
locProv LocationProvider
server string
maxAge int
2019-06-09 07:24:20 +00:00
2023-05-19 20:23:55 +00:00
conn *multicast.Conn
2019-06-09 07:24:20 +00:00
ch chan *message
wg sync.WaitGroup
2021-10-19 13:43:41 +00:00
wgS sync.WaitGroup
2019-06-09 07:24:20 +00:00
}
// Advertise starts advertisement of service.
2023-05-19 20:23:55 +00:00
// location should be a string or a ssdp.LocationProvider.
func Advertise(st, usn string, location interface{}, server string, maxAge int) (*Advertiser, error) {
locProv, err := toLocationProvider(location)
if err != nil {
return nil, err
}
conn, err := multicast.Listen(multicast.RecvAddrResolver)
2019-06-09 07:24:20 +00:00
if err != nil {
return nil, err
}
2023-05-19 20:23:55 +00:00
ssdplog.Printf("SSDP advertise on: %s", conn.LocalAddr().String())
2019-06-09 07:24:20 +00:00
a := &Advertiser{
2023-05-19 20:23:55 +00:00
st: st,
usn: usn,
locProv: locProv,
server: server,
maxAge: maxAge,
conn: conn,
ch: make(chan *message),
2019-06-09 07:24:20 +00:00
}
a.wg.Add(2)
2021-10-19 13:43:41 +00:00
a.wgS.Add(1)
2019-06-09 07:24:20 +00:00
go func() {
a.sendMain()
2021-10-19 13:43:41 +00:00
a.wgS.Done()
2019-06-09 07:24:20 +00:00
a.wg.Done()
}()
go func() {
2021-10-19 13:43:41 +00:00
a.recvMain()
2019-06-09 07:24:20 +00:00
a.wg.Done()
}()
return a, nil
}
2021-10-19 13:43:41 +00:00
func (a *Advertiser) recvMain() error {
2023-05-19 20:23:55 +00:00
// TODO: update listening interfaces of a.conn
err := a.conn.ReadPackets(0, func(addr net.Addr, data []byte) error {
2019-06-09 07:24:20 +00:00
if err := a.handleRaw(addr, data); err != nil {
2023-05-19 20:23:55 +00:00
ssdplog.Printf("failed to handle message: %s", err)
2019-06-09 07:24:20 +00:00
}
return nil
})
if err != nil && err != io.EOF {
return err
}
return nil
}
2023-05-19 20:23:55 +00:00
func (a *Advertiser) sendMain() {
2021-10-19 13:43:41 +00:00
for msg := range a.ch {
_, err := a.conn.WriteTo(msg.data, msg.to)
if err != nil {
2023-05-19 20:23:55 +00:00
ssdplog.Printf("failed to send: %s", err)
2019-06-09 07:24:20 +00:00
}
}
}
func (a *Advertiser) handleRaw(from net.Addr, raw []byte) error {
if !bytes.HasPrefix(raw, []byte("M-SEARCH ")) {
// unexpected method.
return nil
}
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw)))
if err != nil {
return err
}
var (
man = req.Header.Get("MAN")
st = req.Header.Get("ST")
)
if man != `"ssdp:discover"` {
return fmt.Errorf("unexpected MAN: %s", man)
}
if st != All && st != RootDevice && st != a.st {
// skip when ST is not matched/expected.
return nil
}
2023-05-19 20:23:55 +00:00
ssdplog.Printf("received M-SEARCH MAN=%s ST=%s from %s", man, st, from.String())
2019-06-09 07:24:20 +00:00
// build and send a response.
2023-05-19 20:23:55 +00:00
msg := buildOK(a.st, a.usn, a.locProv.Location(from, nil), a.server, a.maxAge)
a.ch <- &message{to: from, data: multicast.BytesDataProvider(msg)}
2019-06-09 07:24:20 +00:00
return nil
}
2023-05-19 20:23:55 +00:00
func buildOK(st, usn, location, server string, maxAge int) []byte {
// bytes.Buffer#Write() is never fail, so we can omit error checks.
2019-06-09 07:24:20 +00:00
b := new(bytes.Buffer)
b.WriteString("HTTP/1.1 200 OK\r\n")
2021-10-19 13:43:41 +00:00
fmt.Fprintf(b, "EXT: \r\n")
2019-06-09 07:24:20 +00:00
fmt.Fprintf(b, "ST: %s\r\n", st)
fmt.Fprintf(b, "USN: %s\r\n", usn)
if location != "" {
fmt.Fprintf(b, "LOCATION: %s\r\n", location)
}
if server != "" {
fmt.Fprintf(b, "SERVER: %s\r\n", server)
}
fmt.Fprintf(b, "CACHE-CONTROL: max-age=%d\r\n", maxAge)
b.WriteString("\r\n")
2023-05-19 20:23:55 +00:00
return b.Bytes()
2019-06-09 07:24:20 +00:00
}
// Close stops advertisement.
func (a *Advertiser) Close() error {
if a.conn != nil {
2021-10-19 13:43:41 +00:00
// closing order is very important. be careful to change:
// stop sending loop by closing the channel and wait it.
close(a.ch)
a.wgS.Wait()
// stop receiving loop by closing the connection.
2019-06-09 07:24:20 +00:00
a.conn.Close()
a.wg.Wait()
a.conn = nil
}
return nil
}
// Alive announces ssdp:alive message.
func (a *Advertiser) Alive() error {
2023-05-19 20:23:55 +00:00
addr, err := multicast.SendAddr()
2021-10-19 13:43:41 +00:00
if err != nil {
return err
}
2023-05-19 20:23:55 +00:00
msg := &aliveDataProvider{
host: addr,
nt: a.st,
usn: a.usn,
location: a.locProv,
server: a.server,
maxAge: a.maxAge,
2019-06-09 07:24:20 +00:00
}
2021-10-19 13:43:41 +00:00
a.ch <- &message{to: addr, data: msg}
2023-05-19 20:23:55 +00:00
ssdplog.Printf("sent alive")
2019-06-09 07:24:20 +00:00
return nil
}
// Bye announces ssdp:byebye message.
func (a *Advertiser) Bye() error {
2023-05-19 20:23:55 +00:00
addr, err := multicast.SendAddr()
2021-10-19 13:43:41 +00:00
if err != nil {
return err
}
msg, err := buildBye(addr, a.st, a.usn)
2019-06-09 07:24:20 +00:00
if err != nil {
return err
}
2023-05-19 20:23:55 +00:00
a.ch <- &message{to: addr, data: multicast.BytesDataProvider(msg)}
ssdplog.Printf("sent bye")
2019-06-09 07:24:20 +00:00
return nil
}