diff options
author | EuAndreh <eu@euandre.org> | 2024-05-07 11:49:29 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2024-05-07 12:23:25 -0300 |
commit | f96f51fc600556b002376a71123ba07fbb2a5b68 (patch) | |
tree | c5901b0fc2169d3f8a26d673f8b9415d3afdd8eb /src | |
parent | Rename from "papo" to "papod" (diff) | |
download | papod-f96f51fc600556b002376a71123ba07fbb2a5b68.tar.gz papod-f96f51fc600556b002376a71123ba07fbb2a5b68.tar.xz |
src/papod.go: Add message parsing code with some tests
Diffstat (limited to 'src')
-rw-r--r-- | src/papod.go | 167 |
1 files changed, 154 insertions, 13 deletions
diff --git a/src/papod.go b/src/papod.go index 413b902..38f6d4c 100644 --- a/src/papod.go +++ b/src/papod.go @@ -1,7 +1,8 @@ package papo import ( - // "bufio" + "bufio" + "bytes" "crypto/rand" "database/sql" "encoding/hex" @@ -14,7 +15,9 @@ import ( "math/big" "net" "os" + "regexp" "runtime/debug" + "strings" "sync" "syscall" "time" @@ -247,14 +250,18 @@ func MakeCounter(label string) func(...any) { var EmitActiveConnection = MakeGauge("active-connections") var EmitNicksInChannel = MakeGauge("nicks-in-channel") -var EmitReceivedCommand = MakeCounter("received-command") +var EmitReceivedMessage = MakeCounter("received-message") const pingFrequency = time.Duration(30) * time.Second const pongMaxLatency = time.Duration(5) * time.Second func Fatal(err error) { - slog.Error("fatal-error", "error", err, "stack", string(debug.Stack())) + Error( + "Fatal error", "fatal-error", + "error", err, + "stack", string(debug.Stack()), + ) syscall.Kill(os.Getpid(), syscall.SIGABRT) os.Exit(3) } @@ -274,28 +281,160 @@ type Context struct { tx chan int } +type Connection struct { + conn net.Conn + // id *UUID + id string + isAuthenticated bool +} + +type MessageParams struct { + Middle []string + Trailing string +} + type Message struct { - Prefix string + Prefix string + Command string + Params MessageParams + Raw string +} + +var ( + CmdUser = Message { Command: "USER" } +) + +func SplitOnCRLF(data []byte, _atEOF bool) (int, []byte, error) { + idx := bytes.Index(data, []byte { '\r', '\n' }) + if idx == -1 { + return 0, nil, nil + } + + return idx + 2, data[0:idx], nil +} + +func SplitOnRawMessage(data []byte, atEOF bool) (int, []byte, error) { + advance, token, error := SplitOnCRLF(data, atEOF) + + if len(token) == 0 { + return advance, nil, error + } + + return advance, token, error } -func ReadLoop(ctx *Context, conn net.Conn) { - fmt.Println("ReadLoop") +func SplitSpaces(r rune) bool { + return r == ' ' +} + +func ParseMessageParams(params string) MessageParams { + const sep = " :" + + var middle string + var trailing string + + idx := strings.Index(params, sep) + if idx == -1 { + middle = params + trailing = "" + } else { + middle = params[:idx] + trailing = params[idx + len(sep):] + } + + return MessageParams { + Middle: strings.FieldsFunc(middle, SplitSpaces), + Trailing: trailing, + } } -func WriteLoop(ctx *Context, conn net.Conn) { +var MessageRegex = regexp.MustCompilePOSIX( + // <prefix> <command> <params> + //1 2 3 4 + `^(:([^ ]+) +)?([a-zA-Z]+|[0-9]{3}) *( .*)$`, + // ^^^^ FIXME: test these spaces +) +func ParseMessage(rawMessage string) (Message, error) { + var msg Message + + components := MessageRegex.FindStringSubmatch(rawMessage) + if components == nil { + return msg, nil + } + + msg = Message { + Prefix: components[2], + Command: components[3], + Params: ParseMessageParams(components[4]), + Raw: rawMessage, + } + return msg, nil +} + +func HandleMessage(msg Message) { + fmt.Printf("msg: %#v\n", msg) +} + +func ReplyAnonymous() { +} + +func PersistMessage(msg Message) { +} + +func ActionsFor(msg Message) []int { + return []int { } +} + +func RunAction(action int) { +} + +func ProcessMessage(ctx *Context, connection *Connection, rawMessage string) { + msg, err := ParseMessage(rawMessage) + if err != nil { + return + } + + if msg.Command == CmdUser.Command { + connection.id = msg.Params.Middle[0] + connection.isAuthenticated = true + } + + if !connection.isAuthenticated { + go ReplyAnonymous() + return + } + + for _, action := range ActionsFor(msg) { + RunAction(action) + } +} + +func ReadLoop(ctx *Context, connection *Connection) { + scanner := bufio.NewScanner(connection.conn) + scanner.Split(SplitOnRawMessage) + for scanner.Scan() { + ProcessMessage(ctx, connection, scanner.Text()) + } +} + +func WriteLoop(ctx *Context, connection *Connection) { fmt.Println("WriteLoop") } -func PingLoop(ctx *Context, conn net.Conn) { +func PingLoop(ctx *Context, connection *Connection) { fmt.Println("PingLoop") } func HandleConnection(ctx *Context, conn net.Conn) { EmitActiveConnection.Inc() // FIXME: WaitGroup here? - go ReadLoop(ctx, conn) - go WriteLoop(ctx, conn) - go PingLoop(ctx, conn) + connection := Connection { + conn: conn, + isAuthenticated: false, + } + go ReadLoop(ctx, &connection) + go WriteLoop(ctx, &connection) + go PingLoop(ctx, &connection) } func IRCdLoop(ctx *Context, publicSocketPath string) { @@ -306,8 +445,9 @@ func IRCdLoop(ctx *Context, publicSocketPath string) { for { conn, err := listener.Accept() if err != nil { - slog.Warn( + Warning( "Error accepting a public IRCd connection", + "accept-connection", "err", err, ) // conn.Close() // FIXME: is conn nil? @@ -325,8 +465,9 @@ func CommandListenerLoop(ctx *Context, commandSocketPath string) { for { conn, err := listener.Accept() if err != nil { - slog.Warn( + Warning( "Error accepting a command connection", + "accept-command", "err", err, ) continue |