165 lines
4.2 KiB
Go
165 lines
4.2 KiB
Go
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)
|
|
}
|
|
}
|