source: code/trunk/downstream.go@ 50

Last change on this file since 50 was 50, checked in by contact, 5 years ago

Add an in-memory ring buffer

References: https://todo.sr.ht/~emersion/jounce/2

File size: 7.0 KB
RevLine 
[13]1package jounce
2
3import (
4 "fmt"
5 "io"
6 "net"
[39]7 "strings"
[13]8
9 "gopkg.in/irc.v3"
10)
11
12type ircError struct {
13 Message *irc.Message
14}
15
16func newUnknownCommandError(cmd string) ircError {
17 return ircError{&irc.Message{
18 Command: irc.ERR_UNKNOWNCOMMAND,
19 Params: []string{
20 "*",
21 cmd,
22 "Unknown command",
23 },
24 }}
25}
26
27func newNeedMoreParamsError(cmd string) ircError {
28 return ircError{&irc.Message{
29 Command: irc.ERR_NEEDMOREPARAMS,
30 Params: []string{
31 "*",
32 cmd,
33 "Not enough parameters",
34 },
35 }}
36}
37
38func (err ircError) Error() string {
39 return err.Message.String()
40}
41
42type downstreamConn struct {
[26]43 net net.Conn
44 irc *irc.Conn
45 srv *Server
46 logger Logger
47 messages chan<- *irc.Message
[22]48
[13]49 registered bool
[37]50 user *user
[13]51 closed bool
52 nick string
53 username string
54 realname string
55}
56
[22]57func newDownstreamConn(srv *Server, netConn net.Conn) *downstreamConn {
[26]58 msgs := make(chan *irc.Message, 64)
59 conn := &downstreamConn{
60 net: netConn,
61 irc: irc.NewConn(netConn),
62 srv: srv,
63 logger: &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", netConn.RemoteAddr())},
64 messages: msgs,
[22]65 }
[26]66
67 go func() {
68 for msg := range msgs {
69 if err := conn.irc.WriteMessage(msg); err != nil {
70 conn.logger.Printf("failed to write message: %v", err)
71 }
72 }
[45]73 if err := conn.net.Close(); err != nil {
74 conn.logger.Printf("failed to close connection: %v", err)
75 } else {
76 conn.logger.Printf("connection closed")
77 }
[26]78 }()
79
80 return conn
[22]81}
82
[27]83func (c *downstreamConn) prefix() *irc.Prefix {
84 return &irc.Prefix{
85 Name: c.nick,
86 User: c.username,
87 // TODO: fill the host?
88 }
89}
90
[22]91func (c *downstreamConn) readMessages() error {
92 c.logger.Printf("new connection")
93
94 for {
95 msg, err := c.irc.ReadMessage()
96 if err == io.EOF {
97 break
98 } else if err != nil {
99 return fmt.Errorf("failed to read IRC command: %v", err)
100 }
101
102 err = c.handleMessage(msg)
103 if ircErr, ok := err.(ircError); ok {
104 ircErr.Message.Prefix = c.srv.prefix()
[26]105 c.messages <- ircErr.Message
[22]106 } else if err != nil {
107 return fmt.Errorf("failed to handle IRC command %q: %v", msg.Command, err)
108 }
109
110 if c.closed {
111 return nil
112 }
113 }
114
[45]115 return nil
[22]116}
117
[13]118func (c *downstreamConn) Close() error {
[26]119 if c.closed {
120 return fmt.Errorf("downstream connection already closed")
121 }
[40]122
123 if u := c.user; u != nil {
124 u.lock.Lock()
125 for i := range u.downstreamConns {
126 if u.downstreamConns[i] == c {
127 u.downstreamConns = append(u.downstreamConns[:i], u.downstreamConns[i+1:]...)
128 }
129 }
130 u.lock.Unlock()
[13]131 }
[40]132
[26]133 close(c.messages)
[13]134 c.closed = true
[40]135
[45]136 return nil
[13]137}
138
139func (c *downstreamConn) handleMessage(msg *irc.Message) error {
140 switch msg.Command {
[28]141 case "QUIT":
142 return c.Close()
[13]143 case "PING":
144 // TODO: handle params
[31]145 c.messages <- &irc.Message{
146 Prefix: c.srv.prefix(),
[13]147 Command: "PONG",
148 Params: []string{c.srv.Hostname},
[31]149 }
[26]150 return nil
[13]151 default:
152 if c.registered {
153 return c.handleMessageRegistered(msg)
154 } else {
155 return c.handleMessageUnregistered(msg)
156 }
157 }
158}
159
160func (c *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
161 switch msg.Command {
162 case "NICK":
[43]163 if err := parseMessageParams(msg, &c.nick); err != nil {
164 return err
[13]165 }
166 case "USER":
[43]167 var username string
168 if err := parseMessageParams(msg, &username, nil, nil, &c.realname); err != nil {
169 return err
[13]170 }
[43]171 c.username = "~" + username
[13]172 default:
[22]173 c.logger.Printf("unhandled message: %v", msg)
[13]174 return newUnknownCommandError(msg.Command)
175 }
176 if c.username != "" && c.nick != "" {
177 return c.register()
178 }
179 return nil
180}
181
182func (c *downstreamConn) register() error {
[39]183 u := c.srv.getUser(strings.TrimPrefix(c.username, "~"))
[38]184 if u == nil {
[39]185 c.logger.Printf("failed authentication: unknown username %q", c.username)
[37]186 c.messages <- &irc.Message{
187 Prefix: c.srv.prefix(),
188 Command: irc.ERR_PASSWDMISMATCH,
189 Params: []string{"*", "Invalid username or password"},
190 }
191 return nil
192 }
193
[13]194 c.registered = true
[37]195 c.user = u
[13]196
[40]197 u.lock.Lock()
198 u.downstreamConns = append(u.downstreamConns, c)
199 u.lock.Unlock()
200
[31]201 c.messages <- &irc.Message{
202 Prefix: c.srv.prefix(),
[13]203 Command: irc.RPL_WELCOME,
204 Params: []string{c.nick, "Welcome to jounce, " + c.nick},
[31]205 }
206 c.messages <- &irc.Message{
207 Prefix: c.srv.prefix(),
[13]208 Command: irc.RPL_YOURHOST,
209 Params: []string{c.nick, "Your host is " + c.srv.Hostname},
[31]210 }
211 c.messages <- &irc.Message{
212 Prefix: c.srv.prefix(),
[13]213 Command: irc.RPL_CREATED,
[47]214 Params: []string{c.nick, "Who cares when the server was created?"},
[31]215 }
216 c.messages <- &irc.Message{
217 Prefix: c.srv.prefix(),
[13]218 Command: irc.RPL_MYINFO,
[28]219 Params: []string{c.nick, c.srv.Hostname, "jounce", "aiwroO", "OovaimnqpsrtklbeI"},
[31]220 }
221 c.messages <- &irc.Message{
222 Prefix: c.srv.prefix(),
[13]223 Command: irc.ERR_NOMOTD,
224 Params: []string{c.nick, "No MOTD"},
[31]225 }
[13]226
[39]227 u.forEachUpstream(func(uc *upstreamConn) {
[30]228 // TODO: fix races accessing upstream connection data
229 for _, ch := range uc.channels {
230 if ch.complete {
231 forwardChannel(c, ch)
232 }
233 }
[50]234
235 consumer := uc.ring.Consumer()
236 for {
237 msg := consumer.Consume()
238 if msg == nil {
239 break
240 }
241 c.messages <- msg
242 }
[39]243 })
[50]244
[13]245 return nil
246}
247
248func (c *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
249 switch msg.Command {
[42]250 case "USER":
[13]251 return ircError{&irc.Message{
252 Command: irc.ERR_ALREADYREGISTERED,
[42]253 Params: []string{c.nick, "You may not reregister"},
[13]254 }}
[42]255 case "NICK":
256 c.user.forEachUpstream(func(uc *upstreamConn) {
257 uc.messages <- msg
258 })
[48]259 case "JOIN":
260 var name string
261 if err := parseMessageParams(msg, &name); err != nil {
262 return err
263 }
264
265 if ch, _ := c.user.getChannel(name); ch != nil {
266 break // already joined
267 }
268
269 // TODO: extract network name from channel name
270 return ircError{&irc.Message{
271 Command: irc.ERR_NOSUCHCHANNEL,
272 Params: []string{name, "Channel name ambiguous"},
273 }}
[49]274 case "PART":
275 var name string
276 if err := parseMessageParams(msg, &name); err != nil {
277 return err
278 }
279
280 ch, err := c.user.getChannel(name)
281 if err != nil {
282 return err
283 }
284
285 ch.conn.messages <- msg
286 // TODO: remove channel from upstream config
[46]287 case "MODE":
288 var name string
289 if err := parseMessageParams(msg, &name); err != nil {
290 return err
291 }
292
293 var modeStr string
294 if len(msg.Params) > 1 {
295 modeStr = msg.Params[1]
296 }
297
298 if msg.Prefix.Name != name {
299 ch, err := c.user.getChannel(name)
300 if err != nil {
301 return err
302 }
303
304 if modeStr != "" {
305 ch.conn.messages <- msg
306 } else {
307 c.messages <- &irc.Message{
308 Prefix: c.srv.prefix(),
309 Command: irc.RPL_CHANNELMODEIS,
310 Params: []string{ch.Name, string(ch.modes)},
311 }
312 }
313 } else {
314 if name != c.nick {
315 return ircError{&irc.Message{
316 Command: irc.ERR_USERSDONTMATCH,
317 Params: []string{c.nick, "Cannot change mode for other users"},
318 }}
319 }
320
321 if modeStr != "" {
322 c.user.forEachUpstream(func(uc *upstreamConn) {
323 uc.messages <- msg
324 })
325 } else {
326 c.messages <- &irc.Message{
327 Prefix: c.srv.prefix(),
328 Command: irc.RPL_UMODEIS,
329 Params: []string{""}, // TODO
330 }
331 }
332 }
[13]333 default:
[22]334 c.logger.Printf("unhandled message: %v", msg)
[13]335 return newUnknownCommandError(msg.Command)
336 }
[42]337 return nil
[13]338}
Note: See TracBrowser for help on using the repository browser.