diff --git a/.gitignore b/.gitignore index 1e0abe9..4cb6d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .env .idea/ *.iml +/bridgetest +/10.24.4.1 \ No newline at end of file diff --git a/internal/drivers/hue/bridge.go b/internal/drivers/hue/bridge.go index d275ea8..48ece0f 100644 --- a/internal/drivers/hue/bridge.go +++ b/internal/drivers/hue/bridge.go @@ -8,9 +8,11 @@ import ( "git.aiterp.net/lucifer/new-server/models" "golang.org/x/sync/errgroup" "io" + "net" "net/http" "strings" "sync" + "time" ) type Bridge struct { @@ -203,7 +205,7 @@ func (b *Bridge) get(ctx context.Context, resource string, target interface{}) e return err } - res, err := http.DefaultClient.Do(req.WithContext(ctx)) + res, err := httpClient.Do(req.WithContext(ctx)) if err != nil { return err } @@ -227,7 +229,7 @@ func (b *Bridge) post(ctx context.Context, resource string, body interface{}, ta return err } - res, err := http.DefaultClient.Do(req.WithContext(ctx)) + res, err := httpClient.Do(req.WithContext(ctx)) if err != nil { return err } @@ -255,7 +257,7 @@ func (b *Bridge) put(ctx context.Context, resource string, body interface{}, tar return err } - res, err := http.DefaultClient.Do(req.WithContext(ctx)) + res, err := httpClient.Do(req.WithContext(ctx)) if err != nil { return err } @@ -289,3 +291,17 @@ func reqBody(body interface{}) (io.Reader, error) { 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, +} diff --git a/internal/drivers/lifx/bridge.go b/internal/drivers/lifx/bridge.go index 6331cd7..cee191e 100644 --- a/internal/drivers/lifx/bridge.go +++ b/internal/drivers/lifx/bridge.go @@ -5,6 +5,7 @@ import ( "git.aiterp.net/lucifer/new-server/models" "log" "sync" + "sync/atomic" "time" ) @@ -16,13 +17,13 @@ type Bridge struct { client *Client } -func (b *Bridge) StartSearch() error { +func (b *Bridge) StartSearch(ctx context.Context) error { c := b.getClient() if c == nil { return models.ErrBridgeRunningRequired } - _, err := c.Send("", &GetService{}) + _, err := c.HorribleBroadcast(ctx, &GetService{}) return err } @@ -214,8 +215,7 @@ func (b *Bridge) Run(ctx context.Context, debug bool) error { } b.mu.Unlock() - // - if time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 { + if atomic.LoadUint32(&client.isHorrible) == 0 && time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 { lastSearchTime = time.Now() _, err = client.Send("", &GetService{}) if err != nil { diff --git a/internal/drivers/lifx/client.go b/internal/drivers/lifx/client.go index 4d203a1..63c862d 100644 --- a/internal/drivers/lifx/client.go +++ b/internal/drivers/lifx/client.go @@ -2,12 +2,14 @@ package lifx import ( "context" + "encoding/binary" "errors" "git.aiterp.net/lucifer/new-server/models" "log" "math/rand" "net" "sync" + "sync/atomic" "time" ) @@ -21,6 +23,8 @@ type Client struct { source uint32 debug bool + isHorrible uint32 + addrMap map[string]*net.UDPAddr } @@ -74,6 +78,107 @@ func (c *Client) Send(addr string, payload Payload) (seq uint8, err error) { 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 // not be valid, however! func (c *Client) LastPacket() Packet { @@ -169,6 +274,8 @@ func createClient(ctx context.Context, bindAddr string, debug bool) (*Client, er return nil, err } + _ = conn.SetWriteBuffer(2048 * 1024) + go func() { <-ctx.Done() _ = conn.Close() diff --git a/internal/drivers/lifx/driver.go b/internal/drivers/lifx/driver.go index f1fa86f..c6caa96 100644 --- a/internal/drivers/lifx/driver.go +++ b/internal/drivers/lifx/driver.go @@ -72,7 +72,7 @@ func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeou return nil, err } - err = b.StartSearch() + err = b.StartSearch(ctx) if err != nil { return nil, err }