add HorribleBroadcast™ functionality to LifX driver.
This commit is contained in:
@@ -2,3 +2,5 @@
|
|||||||
.env
|
.env
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
/bridgetest
|
||||||
|
/10.24.4.1
|
||||||
@@ -8,9 +8,11 @@ import (
|
|||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
@@ -203,7 +205,7 @@ func (b *Bridge) get(ctx context.Context, resource string, target interface{}) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req.WithContext(ctx))
|
res, err := httpClient.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -227,7 +229,7 @@ func (b *Bridge) post(ctx context.Context, resource string, body interface{}, ta
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req.WithContext(ctx))
|
res, err := httpClient.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -255,7 +257,7 @@ func (b *Bridge) put(ctx context.Context, resource string, body interface{}, tar
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req.WithContext(ctx))
|
res, err := httpClient.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -289,3 +291,17 @@ func reqBody(body interface{}) (io.Reader, error) {
|
|||||||
return bytes.NewReader(jsonData), nil
|
return bytes.NewReader(jsonData), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var httpClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 256,
|
||||||
|
MaxIdleConnsPerHost: 16,
|
||||||
|
IdleConnTimeout: 10 * time.Minute,
|
||||||
|
},
|
||||||
|
Timeout: time.Minute,
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,13 +17,13 @@ type Bridge struct {
|
|||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) StartSearch() error {
|
func (b *Bridge) StartSearch(ctx context.Context) error {
|
||||||
c := b.getClient()
|
c := b.getClient()
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return models.ErrBridgeRunningRequired
|
return models.ErrBridgeRunningRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := c.Send("", &GetService{})
|
_, err := c.HorribleBroadcast(ctx, &GetService{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +215,7 @@ func (b *Bridge) Run(ctx context.Context, debug bool) error {
|
|||||||
}
|
}
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
//
|
if atomic.LoadUint32(&client.isHorrible) == 0 && time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 {
|
||||||
if time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 {
|
|
||||||
lastSearchTime = time.Now()
|
lastSearchTime = time.Now()
|
||||||
_, err = client.Send("", &GetService{})
|
_, err = client.Send("", &GetService{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package lifx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ type Client struct {
|
|||||||
source uint32
|
source uint32
|
||||||
debug bool
|
debug bool
|
||||||
|
|
||||||
|
isHorrible uint32
|
||||||
|
|
||||||
addrMap map[string]*net.UDPAddr
|
addrMap map[string]*net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +78,107 @@ func (c *Client) Send(addr string, payload Payload) (seq uint8, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HorribleBroadcast "broadcasts" by blasting every IP address in the space with a tagged packet, because LIFX is LIFX.
|
||||||
|
func (c *Client) HorribleBroadcast(ctx context.Context, payload Payload) (seq uint8, err error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
seq = c.seq
|
||||||
|
c.seq += 1
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
defer atomic.StoreUint32(&c.isHorrible, 0)
|
||||||
|
|
||||||
|
packet := createPacket()
|
||||||
|
packet.SetSource(c.source)
|
||||||
|
packet.SetSequence(seq)
|
||||||
|
packet.SetPayload(payload)
|
||||||
|
packet.SetTagged(true)
|
||||||
|
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connIP := c.conn.LocalAddr().(*net.UDPAddr).IP
|
||||||
|
|
||||||
|
var minIPNum uint32
|
||||||
|
var maxIPNum uint32
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
naddr, ok := addr.(*net.IPNet)
|
||||||
|
if !ok || !naddr.Contains(connIP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
networkAddress := naddr.IP.Mask(naddr.Mask)
|
||||||
|
cidr, bits := naddr.Mask.Size()
|
||||||
|
minIPNum = binary.BigEndian.Uint32(networkAddress) + 1
|
||||||
|
maxIPNum = minIPNum + (1 << (bits - cidr)) - 2
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if minIPNum == maxIPNum {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
myIPNum := binary.BigEndian.Uint32(connIP)
|
||||||
|
|
||||||
|
if c.debug {
|
||||||
|
ipBuf := make(net.IP, 8)
|
||||||
|
binary.BigEndian.PutUint32(ipBuf, minIPNum)
|
||||||
|
binary.BigEndian.PutUint32(ipBuf[4:], maxIPNum-1)
|
||||||
|
|
||||||
|
log.Println("Sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.Println("Completed sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for ipNum := minIPNum; ipNum < maxIPNum; ipNum++ {
|
||||||
|
if ipNum == myIPNum {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreUint32(&c.isHorrible, 1)
|
||||||
|
|
||||||
|
ipBuf := make(net.IP, 4)
|
||||||
|
binary.BigEndian.PutUint32(ipBuf, ipNum)
|
||||||
|
addr := &net.UDPAddr{IP: ipBuf, Port: 56700}
|
||||||
|
|
||||||
|
_, err = c.conn.WriteToUDP(packet, addr)
|
||||||
|
if err != nil {
|
||||||
|
if c.debug {
|
||||||
|
log.Println("HorribleBroadcast™ aborted by error.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipNum%256 == 0 {
|
||||||
|
if c.debug {
|
||||||
|
log.Println("Progress:", (ipNum-minIPNum)+1, "of", maxIPNum-minIPNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
if c.debug {
|
||||||
|
log.Println("HorribleBroadcast™ aborted at call site.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// LastPacket gets the last read packet. This data is valid until the next call to Recv. The Packet may
|
// LastPacket gets the last read packet. This data is valid until the next call to Recv. The Packet may
|
||||||
// not be valid, however!
|
// not be valid, however!
|
||||||
func (c *Client) LastPacket() Packet {
|
func (c *Client) LastPacket() Packet {
|
||||||
@@ -169,6 +274,8 @@ func createClient(ctx context.Context, bindAddr string, debug bool) (*Client, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = conn.SetWriteBuffer(2048 * 1024)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeou
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.StartSearch()
|
err = b.StartSearch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user