summaryrefslogtreecommitdiff
path: root/src/papod.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/papod.go')
-rw-r--r--src/papod.go167
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