]> Cypherpunks.ru repositories - goircd.git/commitdiff
Code refactoring
authorSergey Matveev <stargrave@stargrave.org>
Sun, 11 Oct 2015 08:12:55 +0000 (11:12 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 11 Oct 2015 11:51:07 +0000 (14:51 +0300)
* Less memory allocations
* Daemon instance replaced with global variables
* Code simplification
* Asynchronously send messages to clients

client.go
client_test.go
common_test.go [new file with mode: 0644]
daemon.go
daemon_test.go
events.go
goircd.go
room.go
room_test.go

index 9bcaf30575d6829b00d96cedd04b7166330fb986..a43cc1efc1eb781a1de8b7f07667857402550480 100644 (file)
--- a/client.go
+++ b/client.go
@@ -23,11 +23,13 @@ import (
        "log"
        "net"
        "strings"
+       "sync"
        "time"
 )
 
 const (
-       BufSize = 1500
+       BufSize   = 1500
+       MaxOutBuf = 1 << 12
 )
 
 var (
@@ -35,35 +37,60 @@ var (
 )
 
 type Client struct {
-       hostname   *string
-       conn       net.Conn
-       registered bool
-       nickname   string
-       username   string
-       realname   string
-       password   string
-       away       *string
+       conn          net.Conn
+       registered    bool
+       nickname      *string
+       username      *string
+       realname      *string
+       password      *string
+       away          *string
+       recvTimestamp time.Time
+       sendTimestamp time.Time
+       outBuf        chan string
+       alive         bool
+       sync.Mutex
 }
 
-type ClientAlivenessState struct {
-       pingSent  bool
-       timestamp time.Time
+func (c Client) String() string {
+       return *c.nickname + "!" + *c.username + "@" + c.conn.RemoteAddr().String()
 }
 
-func (client Client) String() string {
-       return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String()
+func NewClient(conn net.Conn) *Client {
+       nickname := "*"
+       username := ""
+       c := Client{
+               conn:          conn,
+               nickname:      &nickname,
+               username:      &username,
+               recvTimestamp: time.Now(),
+               sendTimestamp: time.Now(),
+               alive:         true,
+               outBuf:        make(chan string, MaxOutBuf),
+       }
+       go c.MsgSender()
+       return &c
 }
 
-func NewClient(hostname *string, conn net.Conn) *Client {
-       return &Client{hostname: hostname, conn: conn, nickname: "*", password: ""}
+func (c *Client) SetDead() {
+       close(c.outBuf)
+       c.alive = false
+}
+
+func (c *Client) Close() {
+       c.Lock()
+       c.conn.Close()
+       if c.alive {
+               c.SetDead()
+       }
+       c.Unlock()
 }
 
 // Client processor blockingly reads everything remote client sends,
 // splits messages by CRLF and send them to Daemon gorouting for processing
 // it futher. Also it can signalize that client is unavailable (disconnected).
-func (client *Client) Processor(sink chan<- ClientEvent) {
-       sink <- ClientEvent{client, EventNew, ""}
-       log.Println(client, "New client")
+func (c *Client) Processor(sink chan ClientEvent) {
+       sink <- ClientEvent{c, EventNew, ""}
+       log.Println(c, "New client")
        buf := make([]byte, BufSize*2)
        var n int
        var prev int
@@ -71,14 +98,11 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
        var err error
        for {
                if prev == BufSize {
-                       log.Println(client, "buffer size exceeded, kicking him")
-                       sink <- ClientEvent{client, EventDel, ""}
-                       client.conn.Close()
+                       log.Println(c, "input buffer size exceeded, kicking him")
                        break
                }
-               n, err = client.conn.Read(buf[prev:])
+               n, err = c.conn.Read(buf[prev:])
                if err != nil {
-                       sink <- ClientEvent{client, EventDel, ""}
                        break
                }
                prev += n
@@ -87,50 +111,69 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
                if i == -1 {
                        continue
                }
-               sink <- ClientEvent{client, EventMsg, string(buf[:i])}
+               sink <- ClientEvent{c, EventMsg, string(buf[:i])}
                copy(buf, buf[i+2:prev])
                prev -= (i + 2)
                goto CheckMore
        }
+       c.Close()
+       sink <- ClientEvent{c, EventDel, ""}
+}
+
+func (c *Client) MsgSender() {
+       for msg := range c.outBuf {
+               c.conn.Write(append([]byte(msg), CRLF...))
+       }
 }
 
 // Send message as is with CRLF appended.
-func (client *Client) Msg(text string) {
-       client.conn.Write(append([]byte(text), CRLF...))
+func (c *Client) Msg(text string) {
+       c.Lock()
+       defer c.Unlock()
+       if !c.alive {
+               return
+       }
+       if len(c.outBuf) == MaxOutBuf {
+               log.Println(c, "output buffer size exceeded, kicking him")
+               go c.Close()
+               c.SetDead()
+               return
+       }
+       c.outBuf <- text
 }
 
 // Send message from server. It has ": servername" prefix.
-func (client *Client) Reply(text string) {
-       client.Msg(":" + *client.hostname + " " + text)
+func (c *Client) Reply(text string) {
+       c.Msg(":" + *hostname + " " + text)
 }
 
 // Send server message, concatenating all provided text parts and
 // prefix the last one with ":".
-func (client *Client) ReplyParts(code string, text ...string) {
+func (c *Client) ReplyParts(code string, text ...string) {
        parts := []string{code}
        for _, t := range text {
                parts = append(parts, t)
        }
        parts[len(parts)-1] = ":" + parts[len(parts)-1]
-       client.Reply(strings.Join(parts, " "))
+       c.Reply(strings.Join(parts, " "))
 }
 
 // Send nicknamed server message. After servername it always has target
 // client's nickname. The last part is prefixed with ":".
-func (client *Client) ReplyNicknamed(code string, text ...string) {
-       client.ReplyParts(code, append([]string{client.nickname}, text...)...)
+func (c *Client) ReplyNicknamed(code string, text ...string) {
+       c.ReplyParts(code, append([]string{*c.nickname}, text...)...)
 }
 
 // Reply "461 not enough parameters" error for given command.
-func (client *Client) ReplyNotEnoughParameters(command string) {
-       client.ReplyNicknamed("461", command, "Not enough parameters")
+func (c *Client) ReplyNotEnoughParameters(command string) {
+       c.ReplyNicknamed("461", command, "Not enough parameters")
 }
 
 // Reply "403 no such channel" error for specified channel.
-func (client *Client) ReplyNoChannel(channel string) {
-       client.ReplyNicknamed("403", channel, "No such channel")
+func (c *Client) ReplyNoChannel(channel string) {
+       c.ReplyNicknamed("403", channel, "No such channel")
 }
 
-func (client *Client) ReplyNoNickChan(channel string) {
-       client.ReplyNicknamed("401", channel, "No such nick/channel")
+func (c *Client) ReplyNoNickChan(channel string) {
+       c.ReplyNicknamed("401", channel, "No such nick/channel")
 }
index 3735e1f71034f8c3ebcd03f7efb63c522eff4c1a..4c9682ecb25785f4675befeb8522cb983d6a4b23 100644 (file)
@@ -19,86 +19,17 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package main
 
 import (
-       "net"
        "testing"
-       "time"
 )
 
-// Testing network connection that satisfies net.Conn interface
-// Can send predefined messages and store all written ones
-type TestingConn struct {
-       inbound  chan string
-       outbound chan string
-       closed   bool
-}
-
-func NewTestingConn() *TestingConn {
-       inbound := make(chan string, 8)
-       outbound := make(chan string, 8)
-       return &TestingConn{inbound: inbound, outbound: outbound}
-}
-
-func (conn TestingConn) Error() string {
-       return "i am finished"
-}
-
-func (conn *TestingConn) Read(b []byte) (n int, err error) {
-       msg := <-conn.inbound
-       if msg == "" {
-               return 0, conn
-       }
-       for n, bt := range append([]byte(msg), CRLF...) {
-               b[n] = bt
-       }
-       return len(msg)+2, nil
-}
-
-type MyAddr struct{}
-
-func (a MyAddr) String() string {
-       return "someclient"
-}
-func (a MyAddr) Network() string {
-       return "somenet"
-}
-
-func (conn *TestingConn) Write(b []byte) (n int, err error) {
-       conn.outbound <- string(b)
-       return len(b), nil
-}
-
-func (conn *TestingConn) Close() error {
-       conn.closed = true
-       return nil
-}
-
-func (conn TestingConn) LocalAddr() net.Addr {
-       return nil
-}
-
-func (conn TestingConn) RemoteAddr() net.Addr {
-       return MyAddr{}
-}
-
-func (conn TestingConn) SetDeadline(t time.Time) error {
-       return nil
-}
-
-func (conn TestingConn) SetReadDeadline(t time.Time) error {
-       return nil
-}
-
-func (conn TestingConn) SetWriteDeadline(t time.Time) error {
-       return nil
-}
-
 // New client creation test. It must send an event about new client,
 // two predefined messages from it and deletion one
 func TestNewClient(t *testing.T) {
        conn := NewTestingConn()
        sink := make(chan ClientEvent)
        host := "foohost"
-       client := NewClient(&host, conn)
+       hostname = &host
+       client := NewClient(conn)
        go client.Processor(sink)
 
        event := <-sink
@@ -126,8 +57,10 @@ func TestNewClient(t *testing.T) {
 func TestClientReplies(t *testing.T) {
        conn := NewTestingConn()
        host := "foohost"
-       client := NewClient(&host, conn)
-       client.nickname = "мойник"
+       hostname = &host
+       client := NewClient(conn)
+       nickname := "мойник"
+       client.nickname = &nickname
 
        client.Reply("hello")
        if r := <-conn.outbound; r != ":foohost hello\r\n" {
diff --git a/common_test.go b/common_test.go
new file mode 100644 (file)
index 0000000..9c4cb05
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+goircd -- minimalistic simple Internet Relay Chat (IRC) server
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+       "net"
+       "time"
+)
+
+// Testing network connection that satisfies net.Conn interface
+// Can send predefined messages and store all written ones
+type TestingConn struct {
+       inbound  chan string
+       outbound chan string
+       closed   bool
+}
+
+func NewTestingConn() *TestingConn {
+       inbound := make(chan string, 8)
+       outbound := make(chan string, 8)
+       return &TestingConn{inbound: inbound, outbound: outbound}
+}
+
+func (conn TestingConn) Error() string {
+       return "i am finished"
+}
+
+func (conn *TestingConn) Read(b []byte) (n int, err error) {
+       msg := <-conn.inbound
+       if msg == "" {
+               return 0, conn
+       }
+       for n, bt := range append([]byte(msg), CRLF...) {
+               b[n] = bt
+       }
+       return len(msg) + 2, nil
+}
+
+type MyAddr struct{}
+
+func (a MyAddr) String() string {
+       return "someclient"
+}
+func (a MyAddr) Network() string {
+       return "somenet"
+}
+
+func (conn *TestingConn) Write(b []byte) (n int, err error) {
+       conn.outbound <- string(b)
+       return len(b), nil
+}
+
+func (conn *TestingConn) Close() error {
+       conn.closed = true
+       close(conn.outbound)
+       return nil
+}
+
+func (conn TestingConn) LocalAddr() net.Addr {
+       return nil
+}
+
+func (conn TestingConn) RemoteAddr() net.Addr {
+       return MyAddr{}
+}
+
+func (conn TestingConn) SetDeadline(t time.Time) error {
+       return nil
+}
+
+func (conn TestingConn) SetReadDeadline(t time.Time) error {
+       return nil
+}
+
+func (conn TestingConn) SetWriteDeadline(t time.Time) error {
+       return nil
+}
index 9145ba08dd950e345b2df1c7843b93817de17d24..80a3229b4f7ec9735617d5b9835c5a76f7c4c80f 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -26,56 +26,28 @@ import (
        "regexp"
        "sort"
        "strings"
+       "sync"
        "time"
 )
 
 const (
-       // Max time deadline for client's unresponsiveness
+       // Max deadline time of client's unresponsiveness
        PingTimeout = time.Second * 180
        // Max idle client's time before PING are sent
        PingThreshold = time.Second * 90
-       // Client's aliveness check period
-       AlivenessCheck = time.Second * 10
 )
 
 var (
        RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
-)
 
-type Daemon struct {
-       Verbose            bool
-       version            string
-       hostname           *string
-       motd               *string
-       passwords          *string
-       clients            map[*Client]struct{}
-       clientAliveness    map[*Client]*ClientAlivenessState
-       rooms              map[string]*Room
-       roomSinks          map[*Room]chan ClientEvent
-       lastAlivenessCheck time.Time
-       logSink            chan<- LogEvent
-       stateSink          chan<- StateEvent
-}
+       roomsGroup sync.WaitGroup
 
-func NewDaemon(version string, hostname, motd, passwords *string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon {
-       daemon := Daemon{
-               version:   version,
-               hostname:  hostname,
-               motd:      motd,
-               passwords: passwords,
-       }
-       daemon.clients = make(map[*Client]struct{})
-       daemon.clientAliveness = make(map[*Client]*ClientAlivenessState)
-       daemon.rooms = make(map[string]*Room)
-       daemon.roomSinks = make(map[*Room]chan ClientEvent)
-       daemon.logSink = logSink
-       daemon.stateSink = stateSink
-       return &daemon
-}
+       clients map[*Client]struct{} = make(map[*Client]struct{})
+)
 
-func (daemon *Daemon) SendLusers(client *Client) {
+func SendLusers(client *Client) {
        lusers := 0
-       for client := range daemon.clients {
+       for client := range clients {
                if client.registered {
                        lusers++
                }
@@ -83,79 +55,87 @@ func (daemon *Daemon) SendLusers(client *Client) {
        client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
 }
 
-func (daemon *Daemon) SendMotd(client *Client) {
-       if daemon.motd == nil || *daemon.motd == "" {
+func SendMotd(client *Client) {
+       if motd == nil {
                client.ReplyNicknamed("422", "MOTD File is missing")
                return
        }
-
-       motd, err := ioutil.ReadFile(*daemon.motd)
+       motdText, err := ioutil.ReadFile(*motd)
        if err != nil {
-               log.Printf("Can not read motd file %s: %v", *daemon.motd, err)
+               log.Printf("Can not read motd file %s: %v", *motd, err)
                client.ReplyNicknamed("422", "Error reading MOTD File")
                return
        }
-
-       client.ReplyNicknamed("375", "- "+*daemon.hostname+" Message of the day -")
-       for _, s := range strings.Split(strings.Trim(string(motd), "\n"), "\n") {
-               client.ReplyNicknamed("372", "- "+string(s))
+       client.ReplyNicknamed("375", "- "+*hostname+" Message of the day -")
+       for _, s := range strings.Split(strings.Trim(string(motdText), "\n"), "\n") {
+               client.ReplyNicknamed("372", "- "+s)
        }
        client.ReplyNicknamed("376", "End of /MOTD command")
 }
 
-func (daemon *Daemon) SendWhois(client *Client, nicknames []string) {
+func SendWhois(client *Client, nicknames []string) {
+       var c *Client
+       var hostPort string
+       var err error
+       var subscriptions []string
+       var room *Room
+       var subscriber *Client
        for _, nickname := range nicknames {
                nickname = strings.ToLower(nickname)
-               found := false
-               for c := range daemon.clients {
-                       if strings.ToLower(c.nickname) != nickname {
-                               continue
-                       }
-                       found = true
-                       h := c.conn.RemoteAddr().String()
-                       h, _, err := net.SplitHostPort(h)
-                       if err != nil {
-                               log.Printf("Can't parse RemoteAddr %q: %v", h, err)
-                               h = "Unknown"
-                       }
-                       client.ReplyNicknamed("311", c.nickname, c.username, h, "*", c.realname)
-                       client.ReplyNicknamed("312", c.nickname, *daemon.hostname, *daemon.hostname)
-                       if c.away != nil {
-                               client.ReplyNicknamed("301", c.nickname, *c.away)
+               for c = range clients {
+                       if strings.ToLower(*c.nickname) == nickname {
+                               goto Found
                        }
-                       subscriptions := []string{}
-                       for _, room := range daemon.rooms {
-                               for subscriber := range room.members {
-                                       if subscriber.nickname == nickname {
-                                               subscriptions = append(subscriptions, room.name)
-                                       }
+               }
+               client.ReplyNoNickChan(nickname)
+               continue
+       Found:
+               hostPort, _, err = net.SplitHostPort(c.conn.RemoteAddr().String())
+               if err != nil {
+                       log.Printf("Can't parse RemoteAddr %q: %v", hostPort, err)
+                       hostPort = "Unknown"
+               }
+               client.ReplyNicknamed("311", *c.nickname, *c.username, hostPort, "*", *c.realname)
+               client.ReplyNicknamed("312", *c.nickname, *hostname, *hostname)
+               if c.away != nil {
+                       client.ReplyNicknamed("301", *c.nickname, *c.away)
+               }
+               subscriptions = make([]string, 0)
+               for _, room = range rooms {
+                       for subscriber = range room.members {
+                               if *subscriber.nickname == nickname {
+                                       subscriptions = append(subscriptions, *room.name)
                                }
                        }
-                       sort.Strings(subscriptions)
-                       client.ReplyNicknamed("319", c.nickname, strings.Join(subscriptions, " "))
-                       client.ReplyNicknamed("318", c.nickname, "End of /WHOIS list")
-               }
-               if !found {
-                       client.ReplyNoNickChan(nickname)
                }
+               sort.Strings(subscriptions)
+               client.ReplyNicknamed("319", *c.nickname, strings.Join(subscriptions, " "))
+               client.ReplyNicknamed("318", *c.nickname, "End of /WHOIS list")
        }
 }
 
-func (daemon *Daemon) SendList(client *Client, cols []string) {
-       var rooms []string
+func SendList(client *Client, cols []string) {
+       var rs []string
+       var r string
        if (len(cols) > 1) && (cols[1] != "") {
-               rooms = strings.Split(strings.Split(cols[1], " ")[0], ",")
+               rs = strings.Split(strings.Split(cols[1], " ")[0], ",")
        } else {
-               rooms = []string{}
-               for room := range daemon.rooms {
-                       rooms = append(rooms, room)
+               rs = make([]string, 0)
+               for r = range rooms {
+                       rs = append(rs, r)
                }
        }
-       sort.Strings(rooms)
-       for _, room := range rooms {
-               r, found := daemon.rooms[room]
-               if found {
-                       client.ReplyNicknamed("322", room, fmt.Sprintf("%d", len(r.members)), r.topic)
+       sort.Strings(rs)
+       var room *Room
+       var found bool
+       for _, r = range rs {
+               if room, found = rooms[r]; found {
+                       client.ReplyNicknamed(
+                               "322",
+                               r,
+                               fmt.Sprintf("%d", len(room.members)),
+                               *room.topic,
+                       )
                }
        }
        client.ReplyNicknamed("323", "End of /LIST")
@@ -166,14 +146,14 @@ func (daemon *Daemon) SendList(client *Client, cols []string) {
 // * only QUIT, NICK and USER commands are processed
 // * other commands are quietly ignored
 // When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
-func (daemon *Daemon) ClientRegister(client *Client, command string, cols []string) {
-       switch command {
+func ClientRegister(client *Client, cmd string, cols []string) {
+       switch cmd {
        case "PASS":
                if len(cols) == 1 || len(cols[1]) < 1 {
                        client.ReplyNotEnoughParameters("PASS")
                        return
                }
-               client.password = cols[1]
+               client.password = &cols[1]
        case "NICK":
                if len(cols) == 1 || len(cols[1]) < 1 {
                        client.ReplyParts("431", "No nickname given")
@@ -182,8 +162,8 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
                nickname := cols[1]
                // Compatibility with some clients prepending colons to nickname
                nickname = strings.TrimPrefix(nickname, ":")
-               for existingClient := range daemon.clients {
-                       if existingClient.nickname == nickname {
+               for existingClient := range clients {
+                       if *existingClient.nickname == nickname {
                                client.ReplyParts("433", "*", nickname, "Nickname is already in use")
                                return
                        }
@@ -192,7 +172,7 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
                        client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
                        return
                }
-               client.nickname = nickname
+               client.nickname = &nickname
        case "USER":
                if len(cols) == 1 {
                        client.ReplyNotEnoughParameters("USER")
@@ -203,66 +183,69 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
                        client.ReplyNotEnoughParameters("USER")
                        return
                }
-               client.username = args[0]
-               client.realname = strings.TrimLeft(args[3], ":")
+               client.username = &args[0]
+               realname := strings.TrimLeft(args[3], ":")
+               client.realname = &realname
        }
-       if client.nickname != "*" && client.username != "" {
-               if daemon.passwords != nil && *daemon.passwords != "" {
-                       if client.password == "" {
+       if *client.nickname != "*" && *client.username != "" {
+               if passwords != nil && *passwords != "" {
+                       if client.password == nil {
                                client.ReplyParts("462", "You may not register")
-                               client.conn.Close()
+                               client.Close()
                                return
                        }
-                       contents, err := ioutil.ReadFile(*daemon.passwords)
+                       contents, err := ioutil.ReadFile(*passwords)
                        if err != nil {
-                               log.Fatalf("Can no read passwords file %s: %s", *daemon.passwords, err)
+                               log.Fatalf("Can no read passwords file %s: %s", *passwords, err)
                                return
                        }
                        for _, entry := range strings.Split(string(contents), "\n") {
                                if entry == "" {
                                        continue
                                }
-                               if lp := strings.Split(entry, ":"); lp[0] == client.nickname && lp[1] != client.password {
+                               if lp := strings.Split(entry, ":"); lp[0] == *client.nickname && lp[1] != *client.password {
                                        client.ReplyParts("462", "You may not register")
-                                       client.conn.Close()
+                                       client.Close()
                                        return
                                }
                        }
                }
-
                client.registered = true
                client.ReplyNicknamed("001", "Hi, welcome to IRC")
-               client.ReplyNicknamed("002", "Your host is "+*daemon.hostname+", running goircd "+daemon.version)
+               client.ReplyNicknamed("002", "Your host is "+*hostname+", running goircd "+version)
                client.ReplyNicknamed("003", "This server was created sometime")
-               client.ReplyNicknamed("004", *daemon.hostname+" goircd o o")
-               daemon.SendLusers(client)
-               daemon.SendMotd(client)
+               client.ReplyNicknamed("004", *hostname+" goircd o o")
+               SendLusers(client)
+               SendMotd(client)
                log.Println(client, "logged in")
        }
 }
 
 // Register new room in Daemon. Create an object, events sink, save pointers
 // to corresponding daemon's places and start room's processor goroutine.
-func (daemon *Daemon) RoomRegister(name string) (*Room, chan<- ClientEvent) {
-       roomNew := NewRoom(daemon.hostname, name, daemon.logSink, daemon.stateSink)
-       roomNew.Verbose = daemon.Verbose
+func RoomRegister(name string) (*Room, chan ClientEvent) {
+       roomNew := NewRoom(name)
        roomSink := make(chan ClientEvent)
-       daemon.rooms[name] = roomNew
-       daemon.roomSinks[roomNew] = roomSink
+       rooms[name] = roomNew
+       roomSinks[roomNew] = roomSink
        go roomNew.Processor(roomSink)
+       roomsGroup.Add(1)
        return roomNew, roomSink
 }
 
-func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
+func HandlerJoin(client *Client, cmd string) {
        args := strings.Split(cmd, " ")
-       rooms := strings.Split(args[0], ",")
+       rs := strings.Split(args[0], ",")
        var keys []string
        if len(args) > 1 {
                keys = strings.Split(args[1], ",")
        } else {
-               keys = []string{}
+               keys = make([]string, 0)
        }
-       for n, room := range rooms {
+       var roomExisting *Room
+       var roomSink chan ClientEvent
+       var roomNew *Room
+       for n, room := range rs {
                if !RoomNameValid(room) {
                        client.ReplyNoChannel(room)
                        continue
@@ -273,97 +256,88 @@ func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
                } else {
                        key = ""
                }
-               denied := false
-               joined := false
-               for roomExisting, roomSink := range daemon.roomSinks {
-                       if room == roomExisting.name {
-                               if (roomExisting.key != "") && (roomExisting.key != key) {
-                                       denied = true
-                               } else {
-                                       roomSink <- ClientEvent{client, EventNew, ""}
-                                       joined = true
+               for roomExisting, roomSink = range roomSinks {
+                       if room == *roomExisting.name {
+                               if (roomExisting.key != nil) && (*roomExisting.key != key) {
+                                       goto Denied
                                }
-                               break
+                               roomSink <- ClientEvent{client, EventNew, ""}
+                               goto Joined
                        }
                }
-               if denied {
-                       client.ReplyNicknamed("475", room, "Cannot join channel (+k) - bad key")
-               }
-               if denied || joined {
-                       continue
-               }
-               roomNew, roomSink := daemon.RoomRegister(room)
+               roomNew, roomSink = RoomRegister(room)
                log.Println("Room", roomNew, "created")
                if key != "" {
-                       roomNew.key = key
+                       roomNew.key = &key
                        roomNew.StateSave()
                }
                roomSink <- ClientEvent{client, EventNew, ""}
+               continue
+       Denied:
+               client.ReplyNicknamed("475", room, "Cannot join channel (+k) - bad key")
+       Joined:
        }
 }
 
-func (daemon *Daemon) Processor(events <-chan ClientEvent) {
+func Processor(events chan ClientEvent, finished chan struct{}) {
        var now time.Time
+       go func() {
+               for {
+                       time.Sleep(10 * time.Second)
+                       events <- ClientEvent{eventType: EventTick}
+               }
+       }()
        for event := range events {
                now = time.Now()
                client := event.client
-
-               // Check for clients aliveness
-               if daemon.lastAlivenessCheck.Add(AlivenessCheck).Before(now) {
-                       for c := range daemon.clients {
-                               aliveness, alive := daemon.clientAliveness[c]
-                               if !alive {
-                                       continue
-                               }
-                               if aliveness.timestamp.Add(PingTimeout).Before(now) {
+               switch event.eventType {
+               case EventTick:
+                       for c := range clients {
+                               if c.recvTimestamp.Add(PingTimeout).Before(now) {
                                        log.Println(c, "ping timeout")
-                                       c.conn.Close()
+                                       c.Close()
                                        continue
                                }
-                               if !aliveness.pingSent && aliveness.timestamp.Add(PingThreshold).Before(now) {
+                               if c.sendTimestamp.Add(PingThreshold).Before(now) {
                                        if c.registered {
-                                               c.Msg("PING :" + *daemon.hostname)
-                                               aliveness.pingSent = true
+                                               c.Msg("PING :" + *hostname)
+                                               c.sendTimestamp = time.Now()
                                        } else {
                                                log.Println(c, "ping timeout")
-                                               c.conn.Close()
+                                               c.Close()
                                        }
                                }
                        }
-                       daemon.lastAlivenessCheck = now
-               }
-
-               switch event.eventType {
-               case EventNew:
-                       daemon.clients[client] = struct{}{}
-                       daemon.clientAliveness[client] = &ClientAlivenessState{
-                               pingSent:  false,
-                               timestamp: now,
+               case EventTerm:
+                       for _, sink := range roomSinks {
+                               sink <- ClientEvent{eventType: EventTerm}
                        }
+                       roomsGroup.Wait()
+                       close(finished)
+                       return
+               case EventNew:
+                       clients[client] = struct{}{}
                case EventDel:
-                       delete(daemon.clients, client)
-                       delete(daemon.clientAliveness, client)
-                       for _, roomSink := range daemon.roomSinks {
+                       delete(clients, client)
+                       for _, roomSink := range roomSinks {
                                roomSink <- event
                        }
                case EventMsg:
                        cols := strings.SplitN(event.text, " ", 2)
-                       command := strings.ToUpper(cols[0])
-                       if daemon.Verbose {
-                               log.Println(client, "command", command)
+                       cmd := strings.ToUpper(cols[0])
+                       if *verbose {
+                               log.Println(client, "command", cmd)
                        }
-                       if command == "QUIT" {
+                       if cmd == "QUIT" {
                                log.Println(client, "quit")
-                               delete(daemon.clients, client)
-                               delete(daemon.clientAliveness, client)
-                               client.conn.Close()
+                               client.Close()
                                continue
                        }
                        if !client.registered {
-                               daemon.ClientRegister(client, command, cols)
+                               ClientRegister(client, cmd, cols)
                                continue
                        }
-                       switch command {
+                       switch cmd {
                        case "AWAY":
                                if len(cols) == 1 {
                                        client.away = nil
@@ -378,63 +352,63 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                        client.ReplyNotEnoughParameters("JOIN")
                                        continue
                                }
-                               daemon.HandlerJoin(client, cols[1])
+                               HandlerJoin(client, cols[1])
                        case "LIST":
-                               daemon.SendList(client, cols)
+                               SendList(client, cols)
                        case "LUSERS":
-                               daemon.SendLusers(client)
+                               SendLusers(client)
                        case "MODE":
                                if len(cols) == 1 || len(cols[1]) < 1 {
                                        client.ReplyNotEnoughParameters("MODE")
                                        continue
                                }
                                cols = strings.SplitN(cols[1], " ", 2)
-                               if cols[0] == client.username {
+                               if cols[0] == *client.username {
                                        if len(cols) == 1 {
-                                               client.Msg("221 " + client.nickname + " +")
+                                               client.Msg("221 " + *client.nickname + " +")
                                        } else {
                                                client.ReplyNicknamed("501", "Unknown MODE flag")
                                        }
                                        continue
                                }
                                room := cols[0]
-                               r, found := daemon.rooms[room]
+                               r, found := rooms[room]
                                if !found {
                                        client.ReplyNoChannel(room)
                                        continue
                                }
                                if len(cols) == 1 {
-                                       daemon.roomSinks[r] <- ClientEvent{client, EventMode, ""}
+                                       roomSinks[r] <- ClientEvent{client, EventMode, ""}
                                } else {
-                                       daemon.roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
+                                       roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
                                }
                        case "MOTD":
-                               go daemon.SendMotd(client)
+                               SendMotd(client)
                        case "PART":
                                if len(cols) == 1 || len(cols[1]) < 1 {
                                        client.ReplyNotEnoughParameters("PART")
                                        continue
                                }
-                               rooms := strings.Split(cols[1], " ")[0]
-                               for _, room := range strings.Split(rooms, ",") {
-                                       r, found := daemon.rooms[room]
-                                       if !found {
+                               rs := strings.Split(cols[1], " ")[0]
+                               for _, room := range strings.Split(rs, ",") {
+                                       if r, found := rooms[room]; found {
+                                               roomSinks[r] <- ClientEvent{client, EventDel, ""}
+                                       } else {
                                                client.ReplyNoChannel(room)
                                                continue
                                        }
-                                       daemon.roomSinks[r] <- ClientEvent{client, EventDel, ""}
                                }
                        case "PING":
                                if len(cols) == 1 {
                                        client.ReplyNicknamed("409", "No origin specified")
                                        continue
                                }
-                               client.Reply(fmt.Sprintf("PONG %s :%s", *daemon.hostname, cols[1]))
+                               client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
                        case "PONG":
                                continue
                        case "NOTICE", "PRIVMSG":
                                if len(cols) == 1 {
-                                       client.ReplyNicknamed("411", "No recipient given ("+command+")")
+                                       client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
                                        continue
                                }
                                cols = strings.SplitN(cols[1], " ", 2)
@@ -444,12 +418,12 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                }
                                msg := ""
                                target := strings.ToLower(cols[0])
-                               for c := range daemon.clients {
-                                       if c.nickname == target {
-                                               msg = fmt.Sprintf(":%s %s %s %s", client, command, c.nickname, cols[1])
+                               for c := range clients {
+                                       if *c.nickname == target {
+                                               msg = fmt.Sprintf(":%s %s %s %s", client, cmd, *c.nickname, cols[1])
                                                c.Msg(msg)
                                                if c.away != nil {
-                                                       client.ReplyNicknamed("301", c.nickname, *c.away)
+                                                       client.ReplyNicknamed("301", *c.nickname, *c.away)
                                                }
                                                break
                                        }
@@ -457,15 +431,14 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                if msg != "" {
                                        continue
                                }
-                               r, found := daemon.rooms[target]
-                               if !found {
+                               if r, found := rooms[target]; found {
+                                       roomSinks[r] <- ClientEvent{
+                                               client,
+                                               EventMsg,
+                                               cmd + " " + strings.TrimLeft(cols[1], ":"),
+                                       }
+                               } else {
                                        client.ReplyNoNickChan(target)
-                                       continue
-                               }
-                               daemon.roomSinks[r] <- ClientEvent{
-                                       client,
-                                       EventMsg,
-                                       command + " " + strings.TrimLeft(cols[1], ":"),
                                }
                        case "TOPIC":
                                if len(cols) == 1 {
@@ -473,7 +446,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                        continue
                                }
                                cols = strings.SplitN(cols[1], " ", 2)
-                               r, found := daemon.rooms[cols[0]]
+                               r, found := rooms[cols[0]]
                                if !found {
                                        client.ReplyNoChannel(cols[0])
                                        continue
@@ -484,19 +457,18 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                } else {
                                        change = ""
                                }
-                               daemon.roomSinks[r] <- ClientEvent{client, EventTopic, change}
+                               roomSinks[r] <- ClientEvent{client, EventTopic, change}
                        case "WHO":
                                if len(cols) == 1 || len(cols[1]) < 1 {
                                        client.ReplyNotEnoughParameters("WHO")
                                        continue
                                }
                                room := strings.Split(cols[1], " ")[0]
-                               r, found := daemon.rooms[room]
-                               if !found {
+                               if r, found := rooms[room]; found {
+                                       roomSinks[r] <- ClientEvent{client, EventWho, ""}
+                               } else {
                                        client.ReplyNoChannel(room)
-                                       continue
                                }
-                               daemon.roomSinks[r] <- ClientEvent{client, EventWho, ""}
                        case "WHOIS":
                                if len(cols) == 1 || len(cols[1]) < 1 {
                                        client.ReplyNotEnoughParameters("WHOIS")
@@ -504,22 +476,21 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
                                }
                                cols := strings.Split(cols[1], " ")
                                nicknames := strings.Split(cols[len(cols)-1], ",")
-                               daemon.SendWhois(client, nicknames)
+                               SendWhois(client, nicknames)
                        case "VERSION":
                                var debug string
-                               if daemon.Verbose {
+                               if *verbose {
                                        debug = "debug"
                                } else {
                                        debug = ""
                                }
-                               client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", daemon.version, debug, *daemon.hostname))
+                               client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", version, debug, *hostname))
                        default:
-                               client.ReplyNicknamed("421", command, "Unknown command")
+                               client.ReplyNicknamed("421", cmd, "Unknown command")
                        }
                }
-               if aliveness, alive := daemon.clientAliveness[client]; alive {
-                       aliveness.timestamp = now
-                       aliveness.pingSent = false
+               if client != nil {
+                       client.recvTimestamp = now
                }
        }
 }
index efdb39fd3e3598dea52a2cd2431bcdadb7a5d8f0..e48d22dde0c3642e1ebbf4cabefa6053b74e8af6 100644 (file)
@@ -27,21 +27,24 @@ import (
 
 func TestRegistrationWorkflow(t *testing.T) {
        host := "foohost"
-       daemon := NewDaemon("ver1", &host, nil, nil, nil, nil)
+       hostname = &host
        events := make(chan ClientEvent)
-       go daemon.Processor(events)
+       defer func() {
+               events <- ClientEvent{eventType: EventTerm}
+       }()
+       go Processor(events, make(chan struct{}))
        conn := NewTestingConn()
-       client := NewClient(&host, conn)
+       client := NewClient(conn)
        go client.Processor(events)
 
-       conn.inbound <- "UNEXISTENT CMD" // should recieve nothing on this
+       conn.inbound <- "UNEXISTENT CMD" // should receive nothing on this
        conn.inbound <- "NICK"
 
        if r := <-conn.outbound; r != ":foohost 431 :No nickname given\r\n" {
                t.Fatal("431 for NICK", r)
        }
 
-       for _, n := range []string{"привет", " foo", "longlonglong", "#foo", "mein nick", "foo_bar"} {
+       for _, n := range []string{"привет", " foo", "#foo", "mein nick", "foo_bar"} {
                conn.inbound <- "NICK " + n
                if r := <-conn.outbound; r != ":foohost 432 * "+n+" :Erroneous nickname\r\n" {
                        t.Fatal("nickname validation", r)
@@ -52,7 +55,7 @@ func TestRegistrationWorkflow(t *testing.T) {
        if r := <-conn.outbound; r != ":foohost 461 meinick USER :Not enough parameters\r\n" {
                t.Fatal("461 for USER", r)
        }
-       if (client.nickname != "meinick") || client.registered {
+       if (*client.nickname != "meinick") || client.registered {
                t.Fatal("NICK saved")
        }
 
@@ -61,7 +64,7 @@ func TestRegistrationWorkflow(t *testing.T) {
                t.Fatal("461 again for USER", r)
        }
 
-       daemon.SendLusers(client)
+       SendLusers(client)
        if r := <-conn.outbound; !strings.Contains(r, "There are 0 users") {
                t.Fatal("LUSERS", r)
        }
@@ -85,7 +88,7 @@ func TestRegistrationWorkflow(t *testing.T) {
        if r := <-conn.outbound; !strings.Contains(r, ":foohost 422") {
                t.Fatal("422 after registration", r)
        }
-       if (client.username != "1") || (client.realname != "4 5") || !client.registered {
+       if (*client.username != "1") || (*client.realname != "4 5") || !client.registered {
                t.Fatal("client register")
        }
 
@@ -96,7 +99,7 @@ func TestRegistrationWorkflow(t *testing.T) {
                t.Fatal("reply for unexistent command", r)
        }
 
-       daemon.SendLusers(client)
+       SendLusers(client)
        if r := <-conn.outbound; !strings.Contains(r, "There are 1 users") {
                t.Fatal("1 users logged in", r)
        }
@@ -107,10 +110,6 @@ func TestRegistrationWorkflow(t *testing.T) {
        }
 
        conn.inbound <- "QUIT\r\nUNEXISTENT CMD"
-       <-conn.outbound
-       if !conn.closed {
-               t.Fatal("closed connection on QUIT")
-       }
 }
 
 func TestMotd(t *testing.T) {
@@ -123,11 +122,12 @@ func TestMotd(t *testing.T) {
 
        conn := NewTestingConn()
        host := "foohost"
-       client := NewClient(&host, conn)
+       hostname = &host
+       client := NewClient(conn)
        motdName := fd.Name()
-       daemon := NewDaemon("ver1", &host, &motdName, nil, nil, nil)
+       motd = &motdName
 
-       daemon.SendMotd(client)
+       SendMotd(client)
        if r := <-conn.outbound; !strings.HasPrefix(r, ":foohost 375") {
                t.Fatal("MOTD start", r)
        }
index bc659a6c9eeb0e8302d6e55abecb492245622707..9f3673b74563645eaeee2171f51ff9beacb24516 100644 (file)
--- a/events.go
+++ b/events.go
@@ -34,10 +34,17 @@ const (
        EventTopic = iota
        EventWho   = iota
        EventMode  = iota
+       EventTerm  = iota
+       EventTick  = iota
        FormatMsg  = "[%s] <%s> %s\n"
        FormatMeta = "[%s] * %s %s\n"
 )
 
+var (
+       logSink   chan LogEvent   = make(chan LogEvent)
+       stateSink chan StateEvent = make(chan StateEvent)
+)
+
 // Client events going from each of client
 // They can be either NEW, DEL or unparsed MSG
 type ClientEvent struct {
@@ -70,7 +77,7 @@ func Logger(logdir string, events <-chan LogEvent) {
        var fd *os.File
        var err error
        for event := range events {
-               logfile = path.Join(logdir, event.where + ".log")
+               logfile = path.Join(logdir, event.where+".log")
                fd, err = os.OpenFile(logfile, mode, perm)
                if err != nil {
                        log.Println("Can not open logfile", logfile, err)
index 95b463aef0c00fb0ee439cc1cfbc6e46adbae5b3..8bc5e3ecfa091928249d319b0941d508a32154dd 100644 (file)
--- a/goircd.go
+++ b/goircd.go
@@ -37,21 +37,19 @@ var (
        logdir    = flag.String("logdir", "", "Absolute path to directory for logs")
        statedir  = flag.String("statedir", "", "Absolute path to directory for states")
        passwords = flag.String("passwords", "", "Optional path to passwords file")
-
-       tlsBind = flag.String("tlsbind", "", "TLS address to bind to")
-       tlsPEM  = flag.String("tlspem", "", "Path to TLS certificat+key PEM file")
-
-       verbose = flag.Bool("v", false, "Enable verbose logging.")
+       tlsBind   = flag.String("tlsbind", "", "TLS address to bind to")
+       tlsPEM    = flag.String("tlspem", "", "Path to TLS certificat+key PEM file")
+       verbose   = flag.Bool("v", false, "Enable verbose logging.")
 )
 
-func listenerLoop(sock net.Listener, events chan<- ClientEvent) {
+func listenerLoop(sock net.Listener, events chan ClientEvent) {
        for {
                conn, err := sock.Accept()
                if err != nil {
                        log.Println("Error during accepting connection", err)
                        continue
                }
-               client := NewClient(hostname, conn)
+               client := NewClient(conn)
                go client.Processor(events)
        }
 }
@@ -60,7 +58,6 @@ func Run() {
        events := make(chan ClientEvent)
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
-       logSink := make(chan LogEvent)
        if *logdir == "" {
                // Dummy logger
                go func() {
@@ -75,10 +72,7 @@ func Run() {
                log.Println(*logdir, "logger initialized")
        }
 
-       stateSink := make(chan StateEvent)
-       daemon := NewDaemon(version, hostname, motd, passwords, logSink, stateSink)
-       daemon.Verbose = *verbose
-       log.Println("goircd " + daemon.version + " is starting")
+       log.Println("goircd " + version + " is starting")
        if *statedir == "" {
                // Dummy statekeeper
                go func() {
@@ -98,14 +92,14 @@ func Run() {
                        if err != nil {
                                log.Fatalf("Can not read state %s: %v", state, err)
                        }
-                       room, _ := daemon.RoomRegister(path.Base(state))
+                       room, _ := RoomRegister(path.Base(state))
                        contents := strings.Split(string(buf), "\n")
                        if len(contents) < 2 {
-                               log.Printf("State corrupted for %s: %q", room.name, contents)
+                               log.Printf("State corrupted for %s: %q", *room.name, contents)
                        } else {
-                               room.topic = contents[0]
-                               room.key = contents[1]
-                               log.Println("Loaded state for room", room.name)
+                               room.topic = &contents[0]
+                               room.key = &contents[1]
+                               log.Println("Loaded state for room", *room.name)
                        }
                }
                go StateKeeper(*statedir, stateSink)
@@ -133,8 +127,7 @@ func Run() {
                log.Println("TLS listening on", *tlsBind)
                go listenerLoop(listenerTLS, events)
        }
-
-       daemon.Processor(events)
+       Processor(events, make(chan struct{}))
 }
 
 func main() {
diff --git a/room.go b/room.go
index 4bdb4b2f18427c728c762d74b85e6149bc7215f2..3da5a3de2cbafc592ba3faf9f9d141aa593ed232 100644 (file)
--- a/room.go
+++ b/room.go
@@ -28,6 +28,10 @@ import (
 
 var (
        RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
+
+       rooms map[string]*Room = make(map[string]*Room)
+
+       roomSinks map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent)
 )
 
 // Sanitize room's name. It can consist of 1 to 50 ASCII symbols
@@ -37,40 +41,34 @@ func RoomNameValid(name string) bool {
 }
 
 type Room struct {
-       Verbose   bool
-       name      string
-       topic     string
-       key       string
-       members   map[*Client]bool
-       hostname  *string
-       logSink   chan<- LogEvent
-       stateSink chan<- StateEvent
+       name    *string
+       topic   *string
+       key     *string
+       members map[*Client]struct{}
 }
 
 func (room Room) String() string {
-       return room.name
+       return *room.name
 }
 
-func NewRoom(hostname *string, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room {
-       room := Room{name: name}
-       room.members = make(map[*Client]bool)
-       room.topic = ""
-       room.key = ""
-       room.hostname = hostname
-       room.logSink = logSink
-       room.stateSink = stateSink
-       return &room
+func NewRoom(name string) *Room {
+       topic := ""
+       return &Room{
+               name:    &name,
+               topic:   &topic,
+               members: make(map[*Client]struct{}),
+       }
 }
 
 func (room *Room) SendTopic(client *Client) {
-       if room.topic == "" {
-               client.ReplyNicknamed("331", room.name, "No topic is set")
+       if *room.topic == "" {
+               client.ReplyNicknamed("331", *room.name, "No topic is set")
        } else {
-               client.ReplyNicknamed("332", room.name, room.topic)
+               client.ReplyNicknamed("332", *room.name, *room.topic)
        }
 }
 
-// Send message to all room's subscribers, possibly excluding someone
+// Send message to all room's subscribers, possibly excluding someone.
 func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
        for member := range room.members {
                if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
@@ -81,7 +79,11 @@ func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
 }
 
 func (room *Room) StateSave() {
-       room.stateSink <- StateEvent{room.name, room.topic, room.key}
+       var key string
+       if room.key != nil {
+               key = *room.key
+       }
+       stateSink <- StateEvent{*room.name, *room.topic, key}
 }
 
 func (room *Room) Processor(events <-chan ClientEvent) {
@@ -89,46 +91,50 @@ func (room *Room) Processor(events <-chan ClientEvent) {
        for event := range events {
                client = event.client
                switch event.eventType {
+               case EventTerm:
+                       roomsGroup.Done()
+                       return
                case EventNew:
-                       room.members[client] = true
-                       if room.Verbose {
+                       room.members[client] = struct{}{}
+                       if *verbose {
                                log.Println(client, "joined", room.name)
                        }
                        room.SendTopic(client)
-                       room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.name))
-                       room.logSink <- LogEvent{room.name, client.nickname, "joined", true}
-                       nicknames := []string{}
+                       room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, *room.name))
+                       logSink <- LogEvent{*room.name, *client.nickname, "joined", true}
+                       nicknames := make([]string, 0)
                        for member := range room.members {
-                               nicknames = append(nicknames, member.nickname)
+                               nicknames = append(nicknames, *member.nickname)
                        }
                        sort.Strings(nicknames)
-                       client.ReplyNicknamed("353", "=", room.name, strings.Join(nicknames, " "))
-                       client.ReplyNicknamed("366", room.name, "End of NAMES list")
+                       client.ReplyNicknamed("353", "=", *room.name, strings.Join(nicknames, " "))
+                       client.ReplyNicknamed("366", *room.name, "End of NAMES list")
                case EventDel:
                        if _, subscribed := room.members[client]; !subscribed {
-                               client.ReplyNicknamed("442", room.name, "You are not on that channel")
+                               client.ReplyNicknamed("442", *room.name, "You are not on that channel")
                                continue
                        }
                        delete(room.members, client)
-                       msg := fmt.Sprintf(":%s PART %s :%s", client, room.name, client.nickname)
+                       msg := fmt.Sprintf(":%s PART %s :%s", client, *room.name, *client.nickname)
                        room.Broadcast(msg)
-                       room.logSink <- LogEvent{room.name, client.nickname, "left", true}
+                       logSink <- LogEvent{*room.name, *client.nickname, "left", true}
                case EventTopic:
                        if _, subscribed := room.members[client]; !subscribed {
-                               client.ReplyParts("442", room.name, "You are not on that channel")
+                               client.ReplyParts("442", *room.name, "You are not on that channel")
                                continue
                        }
                        if event.text == "" {
-                               go room.SendTopic(client)
+                               room.SendTopic(client)
                                continue
                        }
-                       room.topic = strings.TrimLeft(event.text, ":")
-                       msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.name, room.topic)
-                       go room.Broadcast(msg)
-                       room.logSink <- LogEvent{
-                               room.name,
-                               client.nickname,
-                               "set topic to " + room.topic,
+                       topic := strings.TrimLeft(event.text, ":")
+                       room.topic = &topic
+                       msg := fmt.Sprintf(":%s TOPIC %s :%s", client, *room.name, *room.topic)
+                       room.Broadcast(msg)
+                       logSink <- LogEvent{
+                               *room.name,
+                               *client.nickname,
+                               "set topic to " + *room.topic,
                                true,
                        }
                        room.StateSave()
@@ -136,32 +142,32 @@ func (room *Room) Processor(events <-chan ClientEvent) {
                        for m := range room.members {
                                client.ReplyNicknamed(
                                        "352",
-                                       room.name,
-                                       m.username,
+                                       *room.name,
+                                       *m.username,
                                        m.conn.RemoteAddr().String(),
-                                       *room.hostname,
-                                       m.nickname,
+                                       *hostname,
+                                       *m.nickname,
                                        "H",
-                                       "0 "+m.realname,
+                                       "0 "+*m.realname,
                                )
                        }
-                       client.ReplyNicknamed("315", room.name, "End of /WHO list")
+                       client.ReplyNicknamed("315", *room.name, "End of /WHO list")
                case EventMode:
                        if event.text == "" {
                                mode := "+"
-                               if room.key != "" {
+                               if room.key != nil {
                                        mode = mode + "k"
                                }
-                               client.Msg(fmt.Sprintf("324 %s %s %s", client.nickname, room.name, mode))
+                               client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, *room.name, mode))
                                continue
                        }
                        if strings.HasPrefix(event.text, "b") {
-                               client.ReplyNicknamed("368", room.name, "End of channel ban list")
+                               client.ReplyNicknamed("368", *room.name, "End of channel ban list")
                                continue
                        }
                        if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
                                if _, subscribed := room.members[client]; !subscribed {
-                                       client.ReplyParts("442", room.name, "You are not on that channel")
+                                       client.ReplyParts("442", *room.name, "You are not on that channel")
                                        continue
                                }
                        } else {
@@ -176,16 +182,16 @@ func (room *Room) Processor(events <-chan ClientEvent) {
                                        client.ReplyNotEnoughParameters("MODE")
                                        continue
                                }
-                               room.key = cols[1]
-                               msg = fmt.Sprintf(":%s MODE %s +k %s", client, room.name, room.key)
-                               msgLog = "set channel key to " + room.key
-                       } else if strings.HasPrefix(event.text, "-k") {
-                               room.key = ""
-                               msg = fmt.Sprintf(":%s MODE %s -k", client, room.name)
+                               room.key = &cols[1]
+                               msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
+                               msgLog = "set channel key to " + *room.key
+                       } else {
+                               room.key = nil
+                               msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
                                msgLog = "removed channel key"
                        }
-                       go room.Broadcast(msg)
-                       room.logSink <- LogEvent{room.name, client.nickname, msgLog, true}
+                       room.Broadcast(msg)
+                       logSink <- LogEvent{*room.name, *client.nickname, msgLog, true}
                        room.StateSave()
                case EventMsg:
                        sep := strings.Index(event.text, " ")
@@ -193,13 +199,13 @@ func (room *Room) Processor(events <-chan ClientEvent) {
                                ":%s %s %s :%s",
                                client,
                                event.text[:sep],
-                               room.name,
+                               *room.name,
                                event.text[sep+1:]),
                                client,
                        )
-                       room.logSink <- LogEvent{
-                               room.name,
-                               client.nickname,
+                       logSink <- LogEvent{
+                               *room.name,
+                               *client.nickname,
                                event.text[sep+1:],
                                false,
                        }
index d94e8d49634d24ef836cbb899d31bff9752b4a23..342e58ceecb121a162b2046b2b4065f50bdfd408 100644 (file)
@@ -42,17 +42,25 @@ func notEnoughParams(t *testing.T, c *TestingConn) {
 }
 
 func TestTwoUsers(t *testing.T) {
-       logSink := make(chan LogEvent, 8)
-       stateSink := make(chan StateEvent, 8)
+       logSink = make(chan LogEvent, 8)
+       stateSink = make(chan StateEvent, 8)
        host := "foohost"
-       daemon := NewDaemon("ver1", &host, nil, nil, logSink, stateSink)
+       hostname = &host
        events := make(chan ClientEvent)
-       go daemon.Processor(events)
+       rooms = make(map[string]*Room)
+       clients = make(map[*Client]struct{})
+       roomSinks = make(map[*Room]chan ClientEvent)
+       finished := make(chan struct{})
+       go Processor(events, finished)
+       defer func() {
+               events <- ClientEvent{eventType: EventTerm}
+               <-finished
+       }()
 
        conn1 := NewTestingConn()
        conn2 := NewTestingConn()
-       client1 := NewClient(&host, conn1)
-       client2 := NewClient(&host, conn2)
+       client1 := NewClient(conn1)
+       client2 := NewClient(conn2)
        go client1.Processor(events)
        go client2.Processor(events)
 
@@ -63,7 +71,7 @@ func TestTwoUsers(t *testing.T) {
                <-conn2.outbound
        }
 
-       daemon.SendLusers(client1)
+       SendLusers(client1)
        if r := <-conn1.outbound; !strings.Contains(r, "There are 2 users") {
                t.Fatal("LUSERS", r)
        }
@@ -105,27 +113,40 @@ func TestTwoUsers(t *testing.T) {
        conn1.inbound <- "PRIVMSG nick2 :Hello"
        conn1.inbound <- "PRIVMSG #foo :world"
        conn1.inbound <- "NOTICE #foo :world"
-       <-conn2.outbound
-       if r := <-conn2.outbound; r != ":nick1!foo1@someclient PRIVMSG nick2 :Hello\r\n" {
-               t.Fatal("first message", r)
+       m1 := <-conn2.outbound
+       m2 := <-conn2.outbound
+       mNeeded := ":nick1!foo1@someclient PRIVMSG nick2 :Hello\r\n"
+       if !(m1 == mNeeded || m2 == mNeeded) {
+               t.Fatal("first message", m1, m2)
        }
-       if r := <-conn2.outbound; r != ":nick1!foo1@someclient PRIVMSG #foo :world\r\n" {
-               t.Fatal("second message", r)
+       if m2 == mNeeded {
+               m2 = <-conn2.outbound
        }
-       if r := <-conn2.outbound; r != ":nick1!foo1@someclient NOTICE #foo :world\r\n" {
-               t.Fatal("third message", r)
+       if m2 != ":nick1!foo1@someclient PRIVMSG #foo :world\r\n" {
+               t.Fatal("second message", m2)
+       }
+       if m2 = <-conn2.outbound; m2 != ":nick1!foo1@someclient NOTICE #foo :world\r\n" {
+               t.Fatal("third message", m2)
        }
 }
 
 func TestJoin(t *testing.T) {
-       logSink := make(chan LogEvent, 8)
-       stateSink := make(chan StateEvent, 8)
+       logSink = make(chan LogEvent, 8)
+       stateSink = make(chan StateEvent, 8)
        host := "foohost"
-       daemon := NewDaemon("ver1", &host, nil, nil, logSink, stateSink)
+       hostname = &host
        events := make(chan ClientEvent)
-       go daemon.Processor(events)
+       rooms = make(map[string]*Room)
+       clients = make(map[*Client]struct{})
+       roomSinks = make(map[*Room]chan ClientEvent)
+       finished := make(chan struct{})
+       go Processor(events, finished)
+       defer func() {
+               events <- ClientEvent{eventType: EventTerm}
+               <-finished
+       }()
        conn := NewTestingConn()
-       client := NewClient(&host, conn)
+       client := NewClient(conn)
        go client.Processor(events)
 
        conn.inbound <- "NICK nick2\r\nUSER foo2 bar2 baz2 :Long name2"
@@ -161,10 +182,10 @@ func TestJoin(t *testing.T) {
        for i := 0; i < 4*2; i++ {
                <-conn.outbound
        }
-       if _, ok := daemon.rooms["#bar"]; !ok {
+       if _, ok := rooms["#bar"]; !ok {
                t.Fatal("#bar does not exist")
        }
-       if _, ok := daemon.rooms["#baz"]; !ok {
+       if _, ok := rooms["#baz"]; !ok {
                t.Fatal("#baz does not exist")
        }
        if r := <-logSink; (r.what != "joined") || (r.where != "#bar") || (r.who != "nick2") || (r.meta != true) {
@@ -178,10 +199,10 @@ func TestJoin(t *testing.T) {
        for i := 0; i < 4*2; i++ {
                <-conn.outbound
        }
-       if daemon.rooms["#barenc"].key != "key1" {
+       if *rooms["#barenc"].key != "key1" {
                t.Fatal("no room with key1")
        }
-       if daemon.rooms["#bazenc"].key != "key2" {
+       if *rooms["#bazenc"].key != "key2" {
                t.Fatal("no room with key2")
        }
        if r := <-logSink; (r.what != "joined") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
@@ -201,7 +222,7 @@ func TestJoin(t *testing.T) {
        if r := <-conn.outbound; r != ":nick2!foo2@someclient MODE #barenc -k\r\n" {
                t.Fatal("remove #barenc key", r)
        }
-       if daemon.rooms["#barenc"].key != "" {
+       if rooms["#barenc"].key != nil {
                t.Fatal("removing key from #barenc")
        }
        if r := <-logSink; (r.what != "removed channel key") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
@@ -253,5 +274,4 @@ func TestJoin(t *testing.T) {
        if r := <-conn.outbound; r != ":foohost 315 nick2 #barenc :End of /WHO list\r\n" {
                t.Fatal("end of WHO", r)
        }
-
 }