package main import "os" import "fmt" import "log" import "errors" import "crypto/tls" import "git.tebibyte.media/sashakoshka/hopp" import "git.tebibyte.media/sashakoshka/go-util/sync" import "git.tebibyte.media/sashakoshka/go-util/container" import "git.tebibyte.media/sashakoshka/hopp/examples/chat" var clients usync.RWMonitor[ucontainer.Set[*client]] func main() { name := os.Args[0] if len(os.Args) != 4 { fmt.Fprintf(os.Stderr, "Usage: %s HOST:PORT CERT KEY\n", name) os.Exit(2) } address := os.Args[1] certPath := os.Args[2] keyPath := os.Args[3] err := host(address, certPath, keyPath) handleErr(1, err) } func host(address string, certPath, keyPath string) error { keyPair, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return err } listener, err := hopp.ListenQUIC("quic", address, &tls.Config { InsecureSkipVerify: true, Certificates: []tls.Certificate { keyPair }, }) clients.Set(ucontainer.NewSet[*client]()) if err != nil { return err } log.Printf("(i) hosting on %s", address) for { conn, err := listener.Accept() if err != nil { return err } client := &client { conn: conn, rooms: usync.NewRWMonitor(make(map[string] hopp.Trans)), } go client.run() } } type client struct { conn hopp.Conn nickname hopp.Option[string] rooms usync.RWMonitor[map[string] hopp.Trans] } func (this *client) run() { log.Printf("-=E %v connected", this.conn.RemoteAddr()) defer log.Printf("X=- %v disconnected", this.conn.RemoteAddr()) defer this.conn.Close() for { log.Println("accepting transaction") trans, err := this.conn.AcceptTrans() log.Println("accepted transaction") if err != nil { log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err) } go this.runTrans(trans) } } func (this *client) runTrans(trans hopp.Trans) { defer trans.Close() message, err := chat.Receive(trans) if err != nil { log.Printf( "XXX %v transaction failed: %v", this.conn.RemoteAddr(), err) } switch message := message.(type) { case *chat.MessageJoin: err = this.transTalk(trans, message) } if err != nil { var actual *chat.MessageError if !errors.As(err, &actual) { chat.Send(trans, &chat.MessageError { Description: hopp.O(fmt.Sprint(err)), }) } log.Printf("XXX %v transaction failed: %v", this.conn.RemoteAddr(), err) } } func (this *client) transTalk(trans hopp.Trans, initial *chat.MessageJoin) error { room := initial.Room err := this.joinRoom(trans, room) if err != nil { return err } defer this.leaveRoom(trans, room) for { message, err := chat.Receive(trans) if err != nil { return err } switch message := message.(type) { case *chat.MessageChat: err := this.handleMessageChat(trans, room, message) if err != nil { return err } case *chat.MessageError: return message } } } func (this *client) handleMessageChat(trans hopp.Trans, room string, message *chat.MessageChat) error { log.Println("(). %s #%s: %s", this.nickname.Default("Anonymous"), room, message.Content) clients, done := clients.RBorrow() defer done() for client := range clients { err := client.relayMessage(room, message) if err != nil { log.Printf("!!! %v", err) } } return nil } func (this *client) relayMessage(room string, message *chat.MessageChat) error { rooms, done := this.rooms.RBorrow() defer done() if trans, ok := rooms[room]; ok { err := chat.Send(trans, message) if err != nil { return fmt.Errorf("could not relay message: %w", err) } } return nil } func (this *client) joinRoom(trans hopp.Trans, room string) error { rooms, done := this.rooms.Borrow() defer done() if _, exists := rooms[room]; exists { return fmt.Errorf("already joined %s", room) } rooms[room] = trans log.Printf("--> user %s joined #%s", this.nickname.Default("Anonymous"), room) return nil } func (this *client) leaveRoom(trans hopp.Trans, room string) error { rooms, done := this.rooms.Borrow() defer done() if _, exists := rooms[room]; !exists { return fmt.Errorf("not in %s", room) } delete(rooms, room) log.Printf("<-- user %s left #%s", this.nickname.Default("Anonymous"), room) return nil } func handleErr(code int, err error) { if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err) os.Exit(code) } }