You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
5.9 KiB

  1. package lifx
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "errors"
  6. "git.aiterp.net/lucifer/new-server/models"
  7. "log"
  8. "math/rand"
  9. "net"
  10. "sync"
  11. "sync/atomic"
  12. "time"
  13. )
  14. type Client struct {
  15. mu sync.Mutex
  16. seq uint8
  17. buf []byte
  18. lastN int
  19. conn *net.UDPConn
  20. source uint32
  21. debug bool
  22. isHorrible uint32
  23. addrMap map[string]*net.UDPAddr
  24. }
  25. // Send sends the payload to the hardware addr specified. If it cannot find a mapped
  26. // IP address from a past message, it will be broadcast and may be slow to use. If the
  27. // address is left blank, the package is marked as tagged and broadcast.
  28. //
  29. // ErrInvalidAddress can be returned by this, and indicates a badly configured device.
  30. // It should probably be logged.
  31. func (c *Client) Send(addr string, payload Payload) (seq uint8, err error) {
  32. c.mu.Lock()
  33. seq = c.seq
  34. c.seq += 1
  35. c.mu.Unlock()
  36. packet := createPacket()
  37. packet.SetSource(c.source)
  38. packet.SetSequence(seq)
  39. sendAddr := &net.UDPAddr{IP: net.IPv4(255, 255, 255, 255), Port: 56700}
  40. if addr == "" {
  41. packet.SetTagged(true)
  42. if c.debug {
  43. log.Println("Broadcasting", payload, "seq", seq)
  44. }
  45. } else {
  46. err = packet.SetTarget(addr)
  47. if err != nil {
  48. return
  49. }
  50. c.mu.Lock()
  51. if udpAddr, ok := c.addrMap[addr]; ok {
  52. sendAddr = udpAddr
  53. }
  54. c.mu.Unlock()
  55. if c.debug {
  56. log.Println("Sending", payload, "to", addr, "seq", seq)
  57. }
  58. }
  59. packet.SetPayload(payload)
  60. _, err = c.conn.WriteToUDP(packet, sendAddr)
  61. if err != nil {
  62. return
  63. }
  64. return
  65. }
  66. // HorribleBroadcast "broadcasts" by blasting every IP address in the space with a tagged packet, because LIFX is LIFX.
  67. func (c *Client) HorribleBroadcast(ctx context.Context, payload Payload) (seq uint8, err error) {
  68. c.mu.Lock()
  69. seq = c.seq
  70. c.seq += 1
  71. c.mu.Unlock()
  72. defer atomic.StoreUint32(&c.isHorrible, 0)
  73. packet := createPacket()
  74. packet.SetSource(c.source)
  75. packet.SetSequence(seq)
  76. packet.SetPayload(payload)
  77. packet.SetTagged(true)
  78. ifaces, err := net.Interfaces()
  79. if err != nil {
  80. return 0, err
  81. }
  82. connIP := c.conn.LocalAddr().(*net.UDPAddr).IP
  83. var minIPNum uint32
  84. var maxIPNum uint32
  85. for _, iface := range ifaces {
  86. addrs, err := iface.Addrs()
  87. if err != nil {
  88. continue
  89. }
  90. for _, addr := range addrs {
  91. naddr, ok := addr.(*net.IPNet)
  92. if !ok || !naddr.Contains(connIP) {
  93. continue
  94. }
  95. networkAddress := naddr.IP.Mask(naddr.Mask)
  96. cidr, bits := naddr.Mask.Size()
  97. minIPNum = binary.BigEndian.Uint32(networkAddress) + 1
  98. maxIPNum = minIPNum + (1 << (bits - cidr)) - 2
  99. break
  100. }
  101. }
  102. if minIPNum == maxIPNum {
  103. return
  104. }
  105. myIPNum := binary.BigEndian.Uint32(connIP)
  106. if c.debug {
  107. ipBuf := make(net.IP, 8)
  108. binary.BigEndian.PutUint32(ipBuf, minIPNum)
  109. binary.BigEndian.PutUint32(ipBuf[4:], maxIPNum-1)
  110. log.Println("Sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
  111. defer func() {
  112. log.Println("Completed sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
  113. }()
  114. }
  115. for ipNum := minIPNum; ipNum < maxIPNum; ipNum++ {
  116. if ipNum == myIPNum {
  117. continue
  118. }
  119. atomic.StoreUint32(&c.isHorrible, 1)
  120. ipBuf := make(net.IP, 4)
  121. binary.BigEndian.PutUint32(ipBuf, ipNum)
  122. addr := &net.UDPAddr{IP: ipBuf, Port: 56700}
  123. _, err = c.conn.WriteToUDP(packet, addr)
  124. if err != nil {
  125. if c.debug {
  126. log.Println("HorribleBroadcast™ aborted by error.")
  127. }
  128. return
  129. }
  130. if ipNum%256 == 0 {
  131. if c.debug {
  132. log.Println("Progress:", (ipNum-minIPNum)+1, "of", maxIPNum-minIPNum)
  133. }
  134. time.Sleep(time.Millisecond * 100)
  135. }
  136. if ctx.Err() != nil {
  137. if c.debug {
  138. log.Println("HorribleBroadcast™ aborted at call site.")
  139. }
  140. return
  141. }
  142. }
  143. return
  144. }
  145. // LastPacket gets the last read packet. This data is valid until the next call to Recv. The Packet may
  146. // not be valid, however!
  147. func (c *Client) LastPacket() Packet {
  148. if c.lastN < 36 {
  149. return nil
  150. }
  151. return c.buf[:c.lastN]
  152. }
  153. // Recv reads a message from the UDP socket. The data returned is decoded and will always be valid.
  154. //
  155. // However, these should be handled specifically:
  156. // - ErrPayloadTooShort, ErrInvalidPacketSize: Garbage was received, please ignore.
  157. // - ErrUnrecognizedPacketType: Log these and see what's up
  158. // - ErrReadTimeout: The connection gets a 50ms read deadline. It should be used to do other things than wait
  159. func (c *Client) Recv(timeout time.Duration) (target string, seq uint8, payload Payload, err error) {
  160. if c.buf == nil {
  161. c.buf = make([]byte, 2048)
  162. }
  163. if timeout > 0 {
  164. err = c.conn.SetReadDeadline(time.Now().Add(timeout))
  165. } else {
  166. err = c.conn.SetReadDeadline(time.Time{})
  167. }
  168. if err != nil {
  169. return
  170. }
  171. n, addr, err := c.conn.ReadFromUDP(c.buf)
  172. if n > 0 {
  173. c.lastN = n
  174. }
  175. if err != nil {
  176. if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
  177. err = models.ErrReadTimeout
  178. return
  179. }
  180. return
  181. }
  182. packet := Packet(c.buf[:n])
  183. if n < 2 || packet.Size() != n && packet.Protocol() != 1024 {
  184. err = models.ErrInvalidAddress
  185. return
  186. }
  187. seq = packet.Sequence()
  188. target = packet.Target().String()
  189. payload, err = packet.Payload()
  190. if err != nil {
  191. return
  192. }
  193. // Learn the IP address from state service or ack messages.
  194. if service, ok := payload.(*StateService); ok && service.Service == 1 {
  195. c.mu.Lock()
  196. if c.addrMap == nil {
  197. c.addrMap = make(map[string]*net.UDPAddr)
  198. }
  199. c.addrMap[packet.Target().String()] = addr
  200. c.mu.Unlock()
  201. }
  202. if c.debug {
  203. log.Println("Received", payload, "from", target, "seq", seq)
  204. }
  205. return
  206. }
  207. // createClient creates a client that will last as long as the context.
  208. func createClient(ctx context.Context, bindAddr string, debug bool) (*Client, error) {
  209. addr := net.ParseIP(bindAddr)
  210. if addr == nil {
  211. return nil, errors.New("invalid addr")
  212. }
  213. source := uint32(rand.Uint64())
  214. if source < 2 {
  215. source = 2
  216. }
  217. conn, err := net.ListenUDP("udp", &net.UDPAddr{
  218. IP: addr,
  219. Port: 0,
  220. Zone: "",
  221. })
  222. if err != nil {
  223. return nil, err
  224. }
  225. _ = conn.SetWriteBuffer(2048 * 1024)
  226. go func() {
  227. <-ctx.Done()
  228. _ = conn.Close()
  229. }()
  230. return &Client{conn: conn, source: source, debug: debug}, nil
  231. }