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  )