]> Cypherpunks.ru repositories - goircd.git/blob - client.go
Refactored and corrected client's network data reading
[goircd.git] / client.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package main
20
21 import (
22         "bytes"
23         "log"
24         "net"
25         "strings"
26         "time"
27 )
28
29 const (
30         BufSize = 1500
31 )
32
33 var (
34         CRLF []byte = []byte{'\x0d', '\x0a'}
35 )
36
37 type Client struct {
38         hostname   *string
39         conn       net.Conn
40         registered bool
41         nickname   string
42         username   string
43         realname   string
44         password   string
45         away       *string
46 }
47
48 type ClientAlivenessState struct {
49         pingSent  bool
50         timestamp time.Time
51 }
52
53 func (client Client) String() string {
54         return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String()
55 }
56
57 func NewClient(hostname *string, conn net.Conn) *Client {
58         return &Client{hostname: hostname, conn: conn, nickname: "*", password: ""}
59 }
60
61 // Client processor blockingly reads everything remote client sends,
62 // splits messages by CRLF and send them to Daemon gorouting for processing
63 // it futher. Also it can signalize that client is unavailable (disconnected).
64 func (client *Client) Processor(sink chan<- ClientEvent) {
65         sink <- ClientEvent{client, EventNew, ""}
66         log.Println(client, "New client")
67         buf := make([]byte, BufSize*2)
68         var n int
69         var prev int
70         var i int
71         var err error
72         for {
73                 if prev == BufSize {
74                         log.Println(client, "buffer size exceeded, kicking him")
75                         sink <- ClientEvent{client, EventDel, ""}
76                         client.conn.Close()
77                         break
78                 }
79                 n, err = client.conn.Read(buf[prev:])
80                 if err != nil {
81                         sink <- ClientEvent{client, EventDel, ""}
82                         break
83                 }
84                 prev += n
85         CheckMore:
86                 i = bytes.Index(buf[:prev], CRLF)
87                 if i == -1 {
88                         continue
89                 }
90                 sink <- ClientEvent{client, EventMsg, string(buf[:i])}
91                 copy(buf, buf[i+2:prev])
92                 prev -= (i + 2)
93                 goto CheckMore
94         }
95 }
96
97 // Send message as is with CRLF appended.
98 func (client *Client) Msg(text string) {
99         client.conn.Write(append([]byte(text), CRLF...))
100 }
101
102 // Send message from server. It has ": servername" prefix.
103 func (client *Client) Reply(text string) {
104         client.Msg(":" + *client.hostname + " " + text)
105 }
106
107 // Send server message, concatenating all provided text parts and
108 // prefix the last one with ":".
109 func (client *Client) ReplyParts(code string, text ...string) {
110         parts := []string{code}
111         for _, t := range text {
112                 parts = append(parts, t)
113         }
114         parts[len(parts)-1] = ":" + parts[len(parts)-1]
115         client.Reply(strings.Join(parts, " "))
116 }
117
118 // Send nicknamed server message. After servername it always has target
119 // client's nickname. The last part is prefixed with ":".
120 func (client *Client) ReplyNicknamed(code string, text ...string) {
121         client.ReplyParts(code, append([]string{client.nickname}, text...)...)
122 }
123
124 // Reply "461 not enough parameters" error for given command.
125 func (client *Client) ReplyNotEnoughParameters(command string) {
126         client.ReplyNicknamed("461", command, "Not enough parameters")
127 }
128
129 // Reply "403 no such channel" error for specified channel.
130 func (client *Client) ReplyNoChannel(channel string) {
131         client.ReplyNicknamed("403", channel, "No such channel")
132 }
133
134 func (client *Client) ReplyNoNickChan(channel string) {
135         client.ReplyNicknamed("401", channel, "No such nick/channel")
136 }