diff options
-rw-r--r-- | client.go | 25 | ||||
-rw-r--r-- | cmd.go | 5 | ||||
-rw-r--r-- | cmd_ping.go | 13 | ||||
-rw-r--r-- | main.go | 41 | ||||
-rw-r--r-- | msg.go | 45 | ||||
-rw-r--r-- | numerics.go | 7 |
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() + } +} @@ -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 +} @@ -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 + } } } @@ -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" +) |