go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/experiments/megaphone/main.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package main 9 10 import ( 11 "bytes" 12 "context" 13 "encoding/gob" 14 "fmt" 15 "net" 16 "os" 17 "os/user" 18 "time" 19 20 "github.com/urfave/cli/v2" 21 "go.charczuk.com/sdk/collections" 22 ) 23 24 var ( 25 ipv4mcastaddr = &net.UDPAddr{ 26 IP: net.ParseIP("224.0.0.251"), 27 Port: 1337, 28 } 29 ) 30 31 const bufferSize = 32 * 1024 32 33 func main() { 34 app := &cli.App{ 35 Name: "mega", 36 Usage: "Megaphone is a UDP anycast based chat program.", 37 Copyright: "(c) 2023 Will Charczuk", 38 Commands: []*cli.Command{ 39 listen(), 40 send(), 41 }, 42 } 43 if err := app.Run(os.Args); err != nil { 44 fmt.Fprintf(os.Stderr, "%+v\n", err) 45 os.Exit(1) 46 } 47 } 48 49 func listen() *cli.Command { 50 return &cli.Command{ 51 Name: "listen", 52 Usage: "Print message received", 53 Action: func(ctx *cli.Context) error { 54 c := new(Client) 55 if err := c.Open(); err != nil { 56 return err 57 } 58 59 c.Listen(ctx.Context) 60 var msg Message 61 for { 62 msg = <-c.Messages 63 fmt.Printf("%s\t%s\t%s\n", msg.Timestamp.Format(time.Kitchen), msg.User, msg.Message) 64 } 65 }, 66 } 67 } 68 69 func send() *cli.Command { 70 return &cli.Command{ 71 Name: "send", 72 Usage: "Send a message", 73 Action: func(ctx *cli.Context) error { 74 user, err := user.Current() 75 if err != nil { 76 return err 77 } 78 host, err := os.Hostname() 79 if err != nil { 80 return err 81 } 82 message := Message{ 83 User: fmt.Sprintf("%s@%s", user.Username, host), 84 Timestamp: time.Now().UTC(), 85 Message: ctx.Args().First(), 86 } 87 c := new(Client) 88 if err = c.Open(); err != nil { 89 return err 90 } 91 c.SendMessage(ctx.Context, message) 92 93 fmt.Println("sent!") 94 return nil 95 }, 96 } 97 } 98 99 type Client struct { 100 Messages chan Message 101 102 recv *net.UDPConn 103 send *net.UDPConn 104 peers map[string]*PeerInfo 105 messages *collections.Queue[Message] 106 } 107 108 func (c *Client) Open() error { 109 c.messages = new(collections.Queue[Message]) 110 var err error 111 c.recv, err = net.ListenMulticastUDP("udp4", nil, ipv4mcastaddr) 112 if err != nil { 113 return fmt.Errorf("client cannot open receive connection; %w", err) 114 } 115 c.recv.SetReadBuffer(bufferSize) 116 c.send, err = net.DialUDP("udp4", nil, ipv4mcastaddr) 117 if err != nil { 118 return err 119 } 120 c.send.SetWriteBuffer(bufferSize) 121 122 // do initial sync 123 return c.startSync() 124 } 125 126 func (c *Client) Listen(ctx context.Context) { 127 c.Messages = make(chan Message) 128 go func() { 129 var readBytes int 130 var packet Packet 131 for { 132 select { 133 case <-ctx.Done(): 134 return 135 default: 136 } 137 buffer := make([]byte, bufferSize) 138 readBytes, _, _ = c.recv.ReadFromUDP(buffer) 139 _ = gob.NewDecoder(bytes.NewBuffer(buffer[:readBytes])).Decode(&packet) 140 c.handleIncomingPacket(packet) 141 } 142 }() 143 } 144 145 func (c *Client) SendMessage(ctx context.Context, msg Message) error { 146 return nil 147 } 148 149 func (c *Client) Close() error { 150 if c.recv != nil { 151 _ = c.Close() 152 } 153 if c.send != nil { 154 _ = c.send.Close() 155 } 156 return nil 157 } 158 159 // 160 // internal client methods 161 // 162 163 func (c *Client) startSync() error { 164 return nil 165 } 166 167 func (c *Client) handleIncomingPacket(pck Packet) { 168 switch pck.Method { 169 case MethodHeartbeat: 170 heartbeat, _ := pck.Heartbeat() 171 if c.peers == nil { 172 c.peers = make(map[string]*PeerInfo) 173 } 174 c.peers[heartbeat.User].LastSeen = time.Now().UTC() 175 case MethodSync: 176 // nop 177 } 178 } 179 180 func (c *Client) sendPacket(ctx context.Context, pck Packet) error { 181 buf := new(bytes.Buffer) 182 err := gob.NewEncoder(buf).Encode(pck) 183 if err != nil { 184 return fmt.Errorf("client cannot send packet; %w", err) 185 } 186 if _, err = c.send.Write(buf.Bytes()); err != nil { 187 return err 188 } 189 return nil 190 } 191 192 type Packet struct { 193 Version uint8 194 Size uint16 195 Method uint16 196 Data []byte 197 } 198 199 // PeerInfo is metadata about peers. 200 type PeerInfo struct { 201 User string 202 LastSeen time.Time 203 } 204 205 // Message is a byte packed message we'll send or receive over the wire. 206 type Message struct { 207 User string 208 Hostname string 209 Timestamp time.Time 210 Message string 211 Labels map[string]string 212 } 213 214 type Heartbeat struct { 215 User string 216 Hostname string 217 Timestamp time.Time 218 } 219 220 type SyncRequest struct { 221 User string 222 AsOf time.Time 223 } 224 225 func (p Packet) Message() (msg Message, err error) { 226 err = gob.NewDecoder(bytes.NewBuffer(p.Data)).Decode(&msg) 227 return 228 } 229 230 func (p Packet) Heartbeat() (hb Heartbeat, err error) { 231 err = gob.NewDecoder(bytes.NewBuffer(p.Data)).Decode(&hb) 232 return 233 } 234 235 type Method uint16 236 237 const ( 238 MethodSendMessage = 10 239 MethodSync = 20 240 MethodHeartbeat = 50 241 )