mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-12 16:15:29 +00:00
A held seat now remembers which connection it waits for, so a token from a kicked player is refused instead of landing in whoever took the seat. A room answers every input left in its inbox when it exits, disconnect notices no longer share the lossy inbox, quick-match no longer leaves autoStart armed after a pairing that never started, only lobby changes restart the idle window, and draining refuses new queue entries. Sockets that never send Hello close after ten seconds and the room budget is charged per address, so one client cannot hold the global caps. IPv6 limiter keys use the /64. Corpus log lines get a process-wide rate limit with a suppressed counter. Responses carry nosniff, frame-ancestors and referrer headers. Unicode spaces in a word become spaces rather than vanishing, and blank-rendering letters are dropped from names and chat. A resume into a lobby whose game ended during the absence is replayed that seat's GameOver. Unknown payloads get unknown_message.
56 lines
1.8 KiB
Go
56 lines
1.8 KiB
Go
package wsapi
|
|
|
|
import (
|
|
"log/slog"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// Corpus log lines are word_rejected and word_reported: the feedback loop the
|
|
// dictionary is tuned by. Each session is already bounded on how many it can
|
|
// cause, but a session is free to reconnect, so the per-session bounds do not
|
|
// bound the process. This is the bound that does.
|
|
const (
|
|
// corpusLogPerSecond and corpusLogBurst are the process-wide budget for
|
|
// those lines. Far above what real play produces — a rejection is a typo
|
|
// or a missing word, a few a minute per room — so only a flood is sampled.
|
|
corpusLogPerSecond = 20
|
|
corpusLogBurst = 100
|
|
)
|
|
|
|
// corpusLogger writes the corpus lines through one process-wide bucket. The
|
|
// counters that mirror them stay exact; only the log is sampled, so a flood
|
|
// cannot fill the disk or rotate the real signal out.
|
|
type corpusLogger struct {
|
|
bucket *bucket
|
|
suppressed atomic.Int64
|
|
// out writes one line that got through; slog.Info outside of tests.
|
|
out func(msg string, args ...any)
|
|
}
|
|
|
|
func newCorpusLogger(now time.Time) *corpusLogger {
|
|
return &corpusLogger{
|
|
bucket: newBucket(corpusLogPerSecond, corpusLogBurst, now),
|
|
out: slog.Info,
|
|
}
|
|
}
|
|
|
|
// corpusLog is the one instance every corpus line goes through.
|
|
var corpusLog = newCorpusLogger(time.Now())
|
|
|
|
// info logs one corpus line, or counts it as suppressed when the budget is
|
|
// spent. The first line to get through after a suppressed run carries how many
|
|
// were dropped, so the gap is visible in the log itself and not only in the
|
|
// noitu_corpus_log_suppressed counter.
|
|
func (c *corpusLogger) info(now time.Time, msg string, args ...any) {
|
|
if !c.bucket.allow(now) {
|
|
c.suppressed.Add(1)
|
|
metrics.corpusLogSuppressed.Add(1)
|
|
return
|
|
}
|
|
if n := c.suppressed.Swap(0); n > 0 {
|
|
args = append(args, "suppressed_before", n)
|
|
}
|
|
c.out(msg, args...)
|
|
}
|