do a few
This commit is contained in:
parent
a6ef3bf1b6
commit
f174c4e02d
14 changed files with 626 additions and 1 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.gocache
|
||||
protohacking
|
||||
19
Justfile
Normal file
19
Justfile
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# this is my deploy script, u dont need to worry abt this
|
||||
|
||||
# if ur wondering im on an arm macbook so when i deploy
|
||||
# i need to compile to amd64, so i just do it in a docker container
|
||||
build:
|
||||
docker run \
|
||||
-v "$(pwd):/src" \
|
||||
--platform linux/amd64 \
|
||||
--rm \
|
||||
-w /src \
|
||||
golang:1.24-alpine \
|
||||
/src/build.sh
|
||||
purge-remote:
|
||||
ssh redbox "sudo rm /usr/local/bin/protohacking || true"
|
||||
upload:
|
||||
dd if=protohacking | ssh redbox "sudo dd of=/usr/local/bin/protohacking && sudo chmod 755 /usr/local/bin/protohacking"
|
||||
push: build purge-remote upload
|
||||
start-remote: push
|
||||
ssh redbox "protohacking"
|
||||
2
build.sh
Executable file
2
build.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env ash
|
||||
GOCACHE=/src/.gocache go build -o protohacking -ldflags "-s -w" .
|
||||
19
cmd/budgetchat.go
Normal file
19
cmd/budgetchat.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"git.red-panda.pet/pandaware/protohacking/x/budgetchat"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var budgetChat = &cobra.Command{
|
||||
Use: "budgetchat",
|
||||
Aliases: []string{"p3"},
|
||||
Short: "Problem 3: Budget Chat",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runServer(budgetchat.New)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(budgetChat)
|
||||
}
|
||||
19
cmd/meanstoanend.go
Normal file
19
cmd/meanstoanend.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"git.red-panda.pet/pandaware/protohacking/x/meanstoanend"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var meansToAnEnd = &cobra.Command{
|
||||
Use: "meanstoanend",
|
||||
Aliases: []string{"p2"},
|
||||
Short: "Problem 2: Means to an End",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runServer(meanstoanend.New)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(meansToAnEnd)
|
||||
}
|
||||
37
cmd/root.go
Normal file
37
cmd/root.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
ip net.IP
|
||||
port uint16
|
||||
proto string
|
||||
v6 bool
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "protohacking",
|
||||
Short: "Protohackers challenge servers",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
pflags := rootCmd.PersistentFlags()
|
||||
pflags.Uint16VarP(&port, "port", "p", 8088, "Port to TCP listen on")
|
||||
pflags.IPVarP(&ip, "ip", "i", net.IPv6unspecified, "Address to listen on")
|
||||
pflags.BoolVarP(&v6, "v6", "6", true, "Whether to use IPv6")
|
||||
pflags.StringVar(&proto, "proto", "tcp", "Either tcp or udp")
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal("fatal error", "err", err)
|
||||
}
|
||||
}
|
||||
19
cmd/smoke-test.go
Normal file
19
cmd/smoke-test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"git.red-panda.pet/pandaware/protohacking/x/smoketest"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var smokeTest = &cobra.Command{
|
||||
Use: "smoketest",
|
||||
Aliases: []string{"p0"},
|
||||
Short: "Problem 0: Smoke Test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runServer(smoketest.New)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(smokeTest)
|
||||
}
|
||||
37
cmd/util.go
Normal file
37
cmd/util.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
func runServer(server func(net.Listener) error) {
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
log.Fatal("unsupported protocol, must be either tcp or udp")
|
||||
}
|
||||
|
||||
if v6 {
|
||||
proto += "6"
|
||||
}
|
||||
|
||||
addr, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
log.Fatal("invalid ip")
|
||||
}
|
||||
|
||||
ap := netip.AddrPortFrom(addr, port)
|
||||
|
||||
listener, err := net.Listen(proto, ap.String())
|
||||
if err != nil {
|
||||
log.Fatal("unable to listen", "err", err)
|
||||
}
|
||||
|
||||
log.Info("starting server", "proto", proto, "ip", ip, "port", port)
|
||||
|
||||
err = server(listener)
|
||||
if err != nil {
|
||||
log.Fatal("error while handling connections", "err", err)
|
||||
}
|
||||
}
|
||||
27
go.mod
27
go.mod
|
|
@ -1,3 +1,28 @@
|
|||
module git.red-panda.pet/red/protohacking
|
||||
module git.red-panda.pet/pandaware/protohacking
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
)
|
||||
|
|
|
|||
51
go.sum
Normal file
51
go.sum
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
7
main.go
Normal file
7
main.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "git.red-panda.pet/pandaware/protohacking/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
250
x/budgetchat/server.go
Normal file
250
x/budgetchat/server.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package budgetchat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
func isGoodAscii(str string) bool {
|
||||
// we disallow anything outside the printable ascii range
|
||||
// that means no control characters
|
||||
for _, c := range str {
|
||||
if c < ' ' || c > '~' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type client struct {
|
||||
id uint
|
||||
name string
|
||||
conn net.Conn
|
||||
reader *bufio.Reader
|
||||
bad bool
|
||||
}
|
||||
|
||||
func (c *client) read() (string, error) {
|
||||
// get the client's name
|
||||
line, err := c.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Error("unable to read name from client", "err", err)
|
||||
c.conn.Close()
|
||||
return "", err
|
||||
}
|
||||
|
||||
return line[:len(line)-1], nil
|
||||
}
|
||||
|
||||
func (c *client) send(msg string) error {
|
||||
if c.bad {
|
||||
log.Warn("not sending to bad client", "remote", c.conn.RemoteAddr())
|
||||
return errors.New("trying to send to a bad client")
|
||||
}
|
||||
|
||||
_, err := c.conn.Write([]byte(msg))
|
||||
if err != nil {
|
||||
c.bad = true
|
||||
log.Warn("marking client as bad", "err", err)
|
||||
c.conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type room struct {
|
||||
inc uint
|
||||
lock *sync.RWMutex
|
||||
clients []*client
|
||||
}
|
||||
|
||||
func (r *room) whoishere() string {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
builder := new(strings.Builder)
|
||||
builder.WriteString("* Currently online: ")
|
||||
if len(r.clients) == 0 {
|
||||
log.Info("nobody is here!")
|
||||
builder.WriteString("nobody!\n")
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
for i, client := range r.clients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
builder.WriteString(client.name)
|
||||
if i != len(r.clients) {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteRune('\n')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (r *room) leave(client *client) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
leaveMsg := "* " + client.name + " went offline!\n"
|
||||
|
||||
// loop over the current clients, saving the index of the one we're removing
|
||||
// and messaging everyone else that they left
|
||||
removable := []int{}
|
||||
for i, c := range r.clients {
|
||||
if c.bad || c.id == client.id {
|
||||
removable = append(removable, i)
|
||||
} else {
|
||||
c.send(leaveMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// remove the client who left and any bad connections
|
||||
for _, index := range removable {
|
||||
r.clients[index] = r.clients[len(r.clients)-1]
|
||||
r.clients = r.clients[:len(r.clients)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (r *room) addClient(client *client) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
client.id = r.inc
|
||||
r.inc += 1
|
||||
r.clients = append(r.clients, client)
|
||||
}
|
||||
|
||||
func (r *room) join(conn net.Conn) {
|
||||
log := log.Default().WithPrefix(conn.RemoteAddr().String())
|
||||
|
||||
log.Info("client connecting")
|
||||
client := new(client)
|
||||
client.conn = conn
|
||||
client.reader = bufio.NewReader(conn)
|
||||
client.bad = false
|
||||
|
||||
// start by asking for a name
|
||||
err := client.send("* Welcome, what's your name?\n")
|
||||
if err != nil {
|
||||
log.Warn("kicking client: unable to write welcome", "err", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// get the client's name
|
||||
name, err := client.read()
|
||||
if err != nil {
|
||||
log.Warn("kicking client: unable to read name", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// check to make sure the name is ok
|
||||
if len(name) > 64 {
|
||||
log.Warn("kicking client: name too long")
|
||||
client.send("* You've been kicked: name too long\n")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if !isGoodAscii(name) {
|
||||
log.Warn("kicking client: name contains illegal characters")
|
||||
client.send("* You've been kicked: name contains illegal characters\n")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// establish the name
|
||||
client.name = name
|
||||
log.Info("name established", "name", client.name)
|
||||
|
||||
log.Info("sending room membership")
|
||||
err = client.send(r.whoishere())
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
|
||||
// announce the new client
|
||||
joinMsg := "* " + client.name + " is now online\n"
|
||||
for _, c := range r.clients {
|
||||
c.send(joinMsg)
|
||||
}
|
||||
|
||||
// add the client to the room
|
||||
r.addClient(client)
|
||||
|
||||
log.Info("assigned id", "id", client.id)
|
||||
|
||||
// start message loop
|
||||
for {
|
||||
line, err := client.read()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = r.message(client, line)
|
||||
// if they send a bad message we tell them and kick them
|
||||
if err != nil {
|
||||
log.Warn("kicking client: bad message", "err", err)
|
||||
client.send("* You've been kicked: " + err.Error() + "\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
r.leave(client)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *room) message(from *client, msg string) error {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if len(msg) > 1000 {
|
||||
return errors.New("message too long")
|
||||
}
|
||||
|
||||
if !isGoodAscii(msg) {
|
||||
return errors.New("message contains illegal characters")
|
||||
}
|
||||
|
||||
out := "[" + from.name + "] " + msg + "\n"
|
||||
|
||||
for _, client := range r.clients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if client.id == from.id {
|
||||
continue
|
||||
}
|
||||
|
||||
client.send(out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(listener net.Listener) error {
|
||||
room := new(room)
|
||||
room.lock = new(sync.RWMutex)
|
||||
room.clients = []*client{}
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go room.join(conn)
|
||||
}
|
||||
}
|
||||
106
x/meanstoanend/server.go
Normal file
106
x/meanstoanend/server.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package meanstoanend
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
a int32
|
||||
b int32
|
||||
}
|
||||
|
||||
type client struct {
|
||||
conn net.Conn
|
||||
prices map[int32]int32
|
||||
log *log.Logger
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (c *client) handleMessage() error {
|
||||
_, err := io.ReadFull(c.conn, c.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := new(message)
|
||||
|
||||
_, err = binary.Decode(c.buf[1:5], binary.BigEndian, &msg.a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = binary.Decode(c.buf[5:], binary.BigEndian, &msg.b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch c.buf[0] {
|
||||
case 'I':
|
||||
c.prices[msg.a] = msg.b
|
||||
case 'Q':
|
||||
var i int64
|
||||
var mean int64
|
||||
|
||||
for k, v := range c.prices {
|
||||
if k >= msg.a && k <= msg.b {
|
||||
mean += int64(v)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
mean /= i
|
||||
}
|
||||
|
||||
binary.Write(c.conn, binary.BigEndian, int32(mean))
|
||||
default:
|
||||
c.conn.Close()
|
||||
return errors.New("bad message")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func acceptConn(conn net.Conn) {
|
||||
log.Info("accepting connection", "remote", conn.RemoteAddr())
|
||||
|
||||
var err error
|
||||
|
||||
c := new(client)
|
||||
c.log = log.Default().WithPrefix(conn.RemoteAddr().String())
|
||||
|
||||
c.buf = make([]byte, 9)
|
||||
c.conn = conn
|
||||
c.prices = map[int32]int32{}
|
||||
|
||||
defer c.conn.Close()
|
||||
|
||||
for {
|
||||
c.log.Debug("waiting for message", "remote", conn.RemoteAddr())
|
||||
err = c.handleMessage()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
c.log.Error("unable to read data", "err", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.log.Info("released connection")
|
||||
}
|
||||
|
||||
func New(listener net.Listener) error {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go acceptConn(conn)
|
||||
}
|
||||
}
|
||||
32
x/smoketest/server.go
Normal file
32
x/smoketest/server.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package smoketest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func handleConn(conn net.Conn) error {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
_, err := buf.ReadFrom(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(conn, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(listener net.Listener) error {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err == nil {
|
||||
go handleConn(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue