aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2024-12-08 08:45:13 +0800
committerRunxi Yu <me@runxiyu.org>2024-12-08 01:20:26 +0800
commitd810d12355873d3eaf61ed3f7f0ae52db06c2c96 (patch)
tree5cfa6d11c31377b459b06f4e3399bcc0542c57d8
parentSlight refactor (diff)
downloadmeseircd-d810d12355873d3eaf61ed3f7f0ae52db06c2c96.tar.gz
meseircd-d810d12355873d3eaf61ed3f7f0ae52db06c2c96.tar.zst
meseircd-d810d12355873d3eaf61ed3f7f0ae52db06c2c96.zip
Add basic command handling
-rw-r--r--client.go25
-rw-r--r--cmd.go5
-rw-r--r--cmd_ping.go13
-rw-r--r--main.go41
-rw-r--r--msg.go45
-rw-r--r--numerics.go7
6 files changed, 124 insertions, 12 deletions
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..9c73444
--- /dev/null
+++ b/client.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "net"
+)
+
+type Client struct {
+ conn net.Conn
+ uid [6]byte
+}
+
+func (client *Client) Send(msg SMsg) {
+ client.SendRaw(msg.ClientSerialize())
+}
+
+// Send failures are not returned; broken connections detected and severed on
+// the next receive.
+func (client *Client) SendRaw(s string) {
+ _, err := client.conn.Write([]byte(s))
+ if err != nil {
+ // TODO: Should shut down the netFd instead but the stdlib
+ // doesn't expose a way to do this.
+ client.conn.Close()
+ }
+}
diff --git a/cmd.go b/cmd.go
new file mode 100644
index 0000000..6d60f4f
--- /dev/null
+++ b/cmd.go
@@ -0,0 +1,5 @@
+package main
+
+var commandHandlers = map[string](func(RMsg, *Client) (error)){}
+
+/* Maybe we should make command handlers return their values for easier labelled-reply? */
diff --git a/cmd_ping.go b/cmd_ping.go
new file mode 100644
index 0000000..4ad07c5
--- /dev/null
+++ b/cmd_ping.go
@@ -0,0 +1,13 @@
+package main
+
+func init() {
+ commandHandlers["PING"] = handleClientPing
+}
+
+func handleClientPing(msg RMsg, client *Client) (error) {
+ if len(msg.Params) < 1 {
+ client.Send(SMsg{Command: ERR_NEEDMOREPARAMS, Params: []string{"PING", "Not enough parameters"}})
+ }
+ client.Send(SMsg{Command: "PONG", Params: []string{msg.Params[0]}})
+ return nil
+}
diff --git a/main.go b/main.go
index 0898bd5..c320df0 100644
--- a/main.go
+++ b/main.go
@@ -7,10 +7,6 @@ import (
"net"
)
-type Client struct {
- conn net.Conn
-}
-
func main() {
listener, err := net.Listen("tcp", ":6667")
if err != nil {
@@ -29,6 +25,10 @@ func main() {
}
go func() {
defer func() {
+ client.conn.Close()
+ // TODO: Unified client clean-up
+ }()
+ defer func() {
raised := recover()
if raised != nil {
slog.Error("connection routine panicked", "raised", raised)
@@ -41,13 +41,42 @@ func main() {
func (client *Client) handleConnection() {
reader := bufio.NewReader(client.conn)
- for {
+ messageLoop: for {
line, err := reader.ReadString('\n')
if err != nil {
slog.Error("error while reading from connection", "error", err)
client.conn.Close()
return
}
- _, _ = parseIRCMsg(line)
+ msg, err := parseIRCMsg(line)
+ if err != nil {
+ switch (err) {
+ case ErrEmptyMessage:
+ continue messageLoop
+ case ErrIllegalByte:
+ client.Send(SMsg{Command: "ERROR", Params: []string{err.Error()}})
+ break messageLoop
+ case ErrTagsTooLong:
+ fallthrough
+ case ErrBodyTooLong:
+ client.Send(SMsg{Command: ERR_INPUTTOOLONG, Params: []string{err.Error()}})
+ continue messageLoop
+ default:
+ client.Send(SMsg{Command: "ERROR", Params: []string{err.Error()}})
+ break messageLoop
+ }
+ }
+
+ handler, ok := commandHandlers[msg.Command]
+ if !ok {
+ client.Send(SMsg{Command: ERR_UNKNOWNCOMMAND, Params: []string{msg.Command, "Unknown command"}})
+ continue
+ }
+
+ err = handler(msg, client)
+ if err != nil {
+ client.Send(SMsg{Command: "ERROR", Params: []string{err.Error()}})
+ break
+ }
}
}
diff --git a/msg.go b/msg.go
index 7e778f3..1743116 100644
--- a/msg.go
+++ b/msg.go
@@ -4,19 +4,52 @@ import (
"strings"
)
-type Msg struct {
- RawMessage string
+type RMsg struct {
RawSource string
Command string
Tags map[string]string
Params []string
}
-// Partially adapted from https://github.com/ergochat/irc-go.git
-func parseIRCMsg(line string) (msg Msg, err error) {
- msg = Msg{
- RawMessage: line,
+type Sourceable interface {
+ ClientSource() string
+ ServerSource() string
+}
+
+type SMsg struct {
+ Source *Sourceable
+ Command string
+ Tags map[string]string
+ Params []string
+}
+
+func (msg *SMsg) ClientSerialize() (final string) {
+ if msg.Tags != nil && len(msg.Tags) != 0 {
+ final = "@"
+ for k, v := range msg.Tags{
+ // TODO: Tag values must be escaped
+ final += k + "=" + v + ";"
+ }
+ final += " "
}
+ if msg.Source != nil {
+ final += ":" + (*msg.Source).ClientSource() + " "
+ }
+ final += msg.Command + " "
+
+ if len(msg.Params) > 0 {
+ for i := 0; i < len(msg.Params) - 1; i++ {
+ final += msg.Params[i] + " "
+ }
+ final += ":" + msg.Params[len(msg.Params) - 1]
+ }
+ final += "\n"
+ return
+}
+
+// Partially adapted from https://github.com/ergochat/irc-go.git
+func parseIRCMsg(line string) (msg RMsg, err error) {
+ msg = RMsg{}
line = strings.TrimSuffix(line, "\n")
line = strings.TrimSuffix(line, "\r")
diff --git a/numerics.go b/numerics.go
new file mode 100644
index 0000000..ec3a09f
--- /dev/null
+++ b/numerics.go
@@ -0,0 +1,7 @@
+package main
+
+const (
+ ERR_UNKNOWNCOMMAND = "421"
+ ERR_INPUTTOOLONG = "417"
+ ERR_NEEDMOREPARAMS = "461"
+)