1package irc
2
3import (
4 "bufio"
5 "fmt"
6 "net"
7 "strconv"
8 "strings"
9 "sync/atomic"
10 "time"
11 "unicode"
12)
13
14// special internal Message commands to propagate labeled response status to the writing goroutine
15var labelEnableCommand = ":enable_labeled_response"
16var labelDisableCommand = ":disable_labeled_response"
17
18const chanCapacity = 64
19
20func ChanInOut(conn net.Conn) (in <-chan Message, out chan<- Message) {
21 in_ := make(chan Message, chanCapacity)
22 out_ := make(chan Message, chanCapacity)
23
24 const keepAlive = 30 * time.Second
25 const maxRTT = 10 * time.Second
26 var last atomic.Value
27 last.Store(time.Now())
28
29 go func() {
30 r := bufio.NewScanner(conn)
31 for r.Scan() {
32 line := r.Text()
33 line = strings.ToValidUTF8(line, string([]rune{unicode.ReplacementChar}))
34 // Workaround for https://bugs.unrealircd.org/view.php?id=6598
35 line = strings.TrimSuffix(line, "\r")
36 msg, err := ParseMessage(line)
37 if err != nil {
38 continue
39 }
40 now := time.Now()
41 last.Store(now)
42 conn.SetReadDeadline(now.Add(keepAlive + maxRTT))
43 in_ <- msg
44 }
45 close(in_)
46 }()
47
48 go func() {
49 t := time.NewTicker(time.Second)
50 defer t.Stop()
51 labelOff := 1
52 labeledResponse := false
53 outer:
54 for {
55 select {
56 case msg, ok := <-out_:
57 if !ok {
58 break outer
59 }
60 if msg.Command == labelEnableCommand {
61 labeledResponse = true
62 continue
63 }
64 if msg.Command == labelDisableCommand {
65 labeledResponse = false
66 continue
67 }
68 if labeledResponse {
69 label := strconv.Itoa(labelOff)
70 labelOff++
71 if msg.Tags == nil {
72 msg.Tags = map[string]string{"label": label}
73 } else {
74 msg.Tags["label"] = label
75 }
76 }
77
78 last.Store(time.Now())
79 // TODO send messages by batches
80 _, err := fmt.Fprintf(conn, "%s\r\n", msg.String())
81 if err != nil {
82 break outer
83 }
84 case <-t.C:
85 now := time.Now()
86 if last.Load().(time.Time).Add(keepAlive).After(now) {
87 continue
88 }
89 if last.Load().(time.Time).Add(keepAlive + maxRTT).Before(now) {
90 // probably out of sleep, reset connection
91 conn.Close()
92 continue
93 }
94 last.Store(now)
95 _, err := fmt.Fprint(conn, "PING _\r\n")
96 if err != nil {
97 break outer
98 }
99 }
100 }
101 _ = conn.Close()
102 }()
103
104 return in_, out_
105}