diff --git a/README.md b/README.md index 5f22864..ea53fd9 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ Synopsis: // Add handlers to do things here! // e.g. join a channel on connect. - c.AddHandler("connected", + c.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, line *irc.Line) { conn.Join("#channel") }) // And a signal on disconnect quit := make(chan bool) - c.AddHandler("disconnected", + c.HandleFunc(irc.DISCONNECTED, func(conn *irc.Conn, line *irc.Line) { quit <- true }) // Tell client to connect diff --git a/client.go b/client.go index cb8d02f..0ff9d18 100644 --- a/client.go +++ b/client.go @@ -1,11 +1,11 @@ package main import ( - irc "github.com/fluffle/goirc/client" + "bufio" "flag" "fmt" + irc "github.com/fluffle/goirc/client" "os" - "bufio" "strings" ) @@ -16,14 +16,14 @@ func main() { flag.Parse() // create new IRC connection - c := irc.SimpleClient("GoTest", "gotest") + c := irc.Client("GoTest", "gotest") c.EnableStateTracking() - c.AddHandler("connected", + c.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, line *irc.Line) { conn.Join(*channel) }) // Set up a handler to notify of disconnect events. quit := make(chan bool) - c.AddHandler("disconnected", + c.HandleFunc(irc.DISCONNECTED, func(conn *irc.Conn, line *irc.Line) { quit <- true }) // set up a goroutine to read commands from stdin @@ -36,6 +36,8 @@ func main() { if err != nil { // wha?, maybe ctrl-D... close(in) + reallyquit = true + c.Quit("") break } // no point in sending empty lines down the channel diff --git a/client/commands.go b/client/commands.go index 870b6a1..f620f61 100644 --- a/client/commands.go +++ b/client/commands.go @@ -2,6 +2,34 @@ package client import "strings" +const ( + INIT = "init" + CONNECTED = "connected" + DISCONNECTED = "disconnected" + ACTION = "ACTION" + AWAY = "AWAY" + CTCP = "CTCP" + CTCPREPLY = "CTCPREPLY" + INVITE = "INVITE" + JOIN = "JOIN" + KICK = "KICK" + MODE = "MODE" + NICK = "NICK" + NOTICE = "NOTICE" + OPER = "OPER" + PART = "PART" + PASS = "PASS" + PING = "PING" + PONG = "PONG" + PRIVMSG = "PRIVMSG" + QUIT = "QUIT" + TOPIC = "TOPIC" + USER = "USER" + VERSION = "VERSION" + WHO = "WHO" + WHOIS = "WHOIS" +) + // this file contains the various commands you can // send to the server using an Conn connection @@ -14,18 +42,18 @@ import "strings" func (conn *Conn) Raw(rawline string) { conn.out <- rawline } // Pass() sends a PASS command to the server -func (conn *Conn) Pass(password string) { conn.out <- "PASS " + password } +func (conn *Conn) Pass(password string) { conn.out <- PASS + " " + password } // Nick() sends a NICK command to the server -func (conn *Conn) Nick(nick string) { conn.out <- "NICK " + nick } +func (conn *Conn) Nick(nick string) { conn.out <- NICK + " " + nick } // User() sends a USER command to the server func (conn *Conn) User(ident, name string) { - conn.out <- "USER " + ident + " 12 * :" + name + conn.out <- USER + " " + ident + " 12 * :" + name } // Join() sends a JOIN command to the server -func (conn *Conn) Join(channel string) { conn.out <- "JOIN " + channel } +func (conn *Conn) Join(channel string) { conn.out <- JOIN + " " + channel } // Part() sends a PART command to the server with an optional part message func (conn *Conn) Part(channel string, message ...string) { @@ -33,7 +61,7 @@ func (conn *Conn) Part(channel string, message ...string) { if msg != "" { msg = " :" + msg } - conn.out <- "PART " + channel + msg + conn.out <- PART + " " + channel + msg } // Kick() sends a KICK command to remove a nick from a channel @@ -42,7 +70,7 @@ func (conn *Conn) Kick(channel, nick string, message ...string) { if msg != "" { msg = " :" + msg } - conn.out <- "KICK " + channel + " " + nick + msg + conn.out <- KICK + " " + channel + " " + nick + msg } // Quit() sends a QUIT command to the server with an optional quit message @@ -51,20 +79,20 @@ func (conn *Conn) Quit(message ...string) { if msg == "" { msg = "GoBye!" } - conn.out <- "QUIT :" + msg + conn.out <- QUIT + " :" + msg } // Whois() sends a WHOIS command to the server -func (conn *Conn) Whois(nick string) { conn.out <- "WHOIS " + nick } +func (conn *Conn) Whois(nick string) { conn.out <- WHOIS + " " + nick } //Who() sends a WHO command to the server -func (conn *Conn) Who(nick string) { conn.out <- "WHO " + nick } +func (conn *Conn) Who(nick string) { conn.out <- WHO + " " + nick } // Privmsg() sends a PRIVMSG to the target t -func (conn *Conn) Privmsg(t, msg string) { conn.out <- "PRIVMSG " + t + " :" + msg } +func (conn *Conn) Privmsg(t, msg string) { conn.out <- PRIVMSG + " " + t + " :" + msg } // Notice() sends a NOTICE to the target t -func (conn *Conn) Notice(t, msg string) { conn.out <- "NOTICE " + t + " :" + msg } +func (conn *Conn) Notice(t, msg string) { conn.out <- NOTICE + " " + t + " :" + msg } // Ctcp() sends a (generic) CTCP message to the target t // with an optional argument @@ -87,10 +115,10 @@ func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) { } // Version() sends a CTCP "VERSION" to the target t -func (conn *Conn) Version(t string) { conn.Ctcp(t, "VERSION") } +func (conn *Conn) Version(t string) { conn.Ctcp(t, VERSION) } // Action() sends a CTCP "ACTION" to the target t -func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, "ACTION", msg) } +func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, ACTION, msg) } // Topic() sends a TOPIC command to the channel // Topic(channel) retrieves the current channel topic (see "332" handler) @@ -100,7 +128,7 @@ func (conn *Conn) Topic(channel string, topic ...string) { if t != "" { t = " :" + t } - conn.out <- "TOPIC " + channel + t + conn.out <- TOPIC + " " + channel + t } // Mode() sends a MODE command to the server. This one can get complicated if @@ -115,7 +143,7 @@ func (conn *Conn) Mode(t string, modestring ...string) { if mode != "" { mode = " " + mode } - conn.out <- "MODE " + t + mode + conn.out <- MODE + " " + t + mode } // Away() sends an AWAY command to the server @@ -126,15 +154,18 @@ func (conn *Conn) Away(message ...string) { if msg != "" { msg = " :" + msg } - conn.out <- "AWAY" + msg + conn.out <- AWAY + msg } // Invite() sends an INVITE command to the server -func (conn *Conn) Invite(nick, channel string) { - conn.out <- "INVITE " + nick + " " + channel -} +func (conn *Conn) Invite(nick, channel string) { conn.out <- INVITE + " " + nick + " " + channel } // Oper() sends an OPER command to the server -func (conn *Conn) Oper(user, pass string) { - conn.out <- "OPER " + user + " " + pass -} +func (conn *Conn) Oper(user, pass string) { conn.out <- OPER + " " + user + " " + pass } + +// Ping() sends a PING command to the server +// A PONG response is to be expected afterwards +func (conn *Conn) Ping(message string) { conn.out <- PING + " :" + message } + +// Pong() sends a PONG command to the server +func (conn *Conn) Pong(message string) { conn.out <- PONG + " :" + message } diff --git a/client/commands_test.go b/client/commands_test.go index c3bb6ee..05650e3 100644 --- a/client/commands_test.go +++ b/client/commands_test.go @@ -75,4 +75,10 @@ func TestClientCommands(t *testing.T) { c.Oper("user", "pass") s.nc.Expect("OPER user pass") + + c.Ping("woot") + s.nc.Expect("PING :woot") + + c.Pong("pwoot") + s.nc.Expect("PONG :pwoot") } diff --git a/client/connection.go b/client/connection.go index dce6c43..e1128a2 100644 --- a/client/connection.go +++ b/client/connection.go @@ -15,9 +15,10 @@ import ( // An IRC connection is represented by this struct. type Conn struct { // Connection Hostname and Nickname - Host string - Me *state.Nick - Network string + Host string + Me *state.Nick + Network string + password string // Handlers and Commands handlers *hSet @@ -60,7 +61,7 @@ type Conn struct { Flood bool // Internal counters for flood protection - badness time.Duration + badness time.Duration lastsent time.Time } @@ -164,13 +165,12 @@ func (conn *Conn) Connect(host string, pass ...string) error { } conn.Host = host conn.Connected = true - conn.postConnect() - if len(pass) > 0 { - conn.Pass(pass[0]) + conn.password = pass[0] + } else { + conn.password = "" } - conn.Nick(conn.Me.Nick) - conn.User(conn.Me.Ident, conn.Me.Name) + conn.postConnect() return nil } @@ -188,6 +188,7 @@ func (conn *Conn) postConnect() { go func() { <-conn.cPing }() } go conn.runLoop() + conn.dispatch(&Line{Cmd: INIT}) } // copied from http.client for great justice @@ -235,7 +236,7 @@ func (conn *Conn) ping() { for { select { case <-tick.C: - conn.Raw(fmt.Sprintf("PING :%d", time.Now().UnixNano())) + conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano())) case <-conn.cPing: tick.Stop() return @@ -305,7 +306,7 @@ func (conn *Conn) shutdown() { // as calling sock.Close() will cause recv() to recieve EOF in readstring() if conn.Connected { logging.Info("irc.shutdown(): Disconnected from server.") - conn.dispatch(&Line{Cmd: "disconnected"}) + conn.dispatch(&Line{Cmd: DISCONNECTED}) conn.Connected = false conn.sock.Close() conn.cSend <- true diff --git a/client/connection_test.go b/client/connection_test.go index 6386eb3..e4d09ba 100644 --- a/client/connection_test.go +++ b/client/connection_test.go @@ -3,8 +3,8 @@ package client import ( "bufio" "code.google.com/p/gomock/gomock" - "github.com/fluffle/golog/logging" "github.com/fluffle/goirc/state" + "github.com/fluffle/golog/logging" "strings" "testing" "time" @@ -33,6 +33,10 @@ func setUp(t *testing.T, start ...bool) (*Conn, *testState) { // Hack to allow tests of send, recv, write etc. // NOTE: the value of the boolean doesn't matter. c.postConnect() + // All connections start with NICK/USER expect these. + nc.Expect("NICK test") + nc.Expect("USER test 12 * :Testing IRC") + // Sleep 1ms to allow background routines to start. <-time.After(1e6) } @@ -57,7 +61,7 @@ func TestEOF(t *testing.T) { // Set up a handler to detect whether disconnected handlers are called dcon := false - c.HandleFunc("disconnected", func (conn *Conn, line *Line) { + c.HandleFunc(DISCONNECTED, func(conn *Conn, line *Line) { dcon = true }) @@ -356,12 +360,12 @@ func TestRunLoop(t *testing.T) { // Set up a handler to detect whether 001 handler is called h001 := false - c.HandleFunc("001", func (conn *Conn, line *Line) { + c.HandleFunc("001", func(conn *Conn, line *Line) { h001 = true }) // Set up a handler to detect whether 002 handler is called h002 := false - c.HandleFunc("002", func (conn *Conn, line *Line) { + c.HandleFunc("002", func(conn *Conn, line *Line) { h002 = true }) @@ -470,7 +474,7 @@ func TestRateLimit(t *testing.T) { // We'll be needing this later... abs := func(i time.Duration) time.Duration { - if (i < 0) { + if i < 0 { return -i } return i @@ -491,13 +495,13 @@ func TestRateLimit(t *testing.T) { // 2.5 seconds minus the delta between the two ratelimit calls. This should // be minimal but it's guaranteed that it won't be zero. Use 10us as a fuzz. if l := c.rateLimit(60); l != 0 || - abs(c.badness - 2500*time.Millisecond) > 10 * time.Microsecond { + abs(c.badness-2500*time.Millisecond) > 10*time.Microsecond { t.Errorf("Rate limit calculating badness incorrectly.") } // At this point, we can tip over the badness scale, with a bit of help. // 720 chars => +8 seconds of badness => 10.5 seconds => ratelimit - if l := c.rateLimit(720); l != 8 * time.Second || - abs(c.badness - 10500*time.Millisecond) > 10 * time.Microsecond { + if l := c.rateLimit(720); l != 8*time.Second || + abs(c.badness-10500*time.Millisecond) > 10*time.Microsecond { t.Errorf("Rate limit failed to return correct limiting values.") t.Errorf("l=%d, badness=%d", l, c.badness) } diff --git a/client/dispatch.go b/client/dispatch.go index f35a10f..dd73865 100644 --- a/client/dispatch.go +++ b/client/dispatch.go @@ -105,7 +105,9 @@ func (hs *hSet) dispatch(conn *Conn, line *Line) { defer hs.RUnlock() ev := strings.ToLower(line.Cmd) list, ok := hs.set[ev] - if !ok { return } + if !ok { + return + } for hn := list.start; hn != nil; hn = hn.next { go hn.Handle(conn, line) } diff --git a/client/dispatch_test.go b/client/dispatch_test.go index 427c69e..84b9e54 100644 --- a/client/dispatch_test.go +++ b/client/dispatch_test.go @@ -83,7 +83,7 @@ func TestHandlerSet(t *testing.T) { if callcount != 0 { t.Errorf("Something incremented call count before we were expecting it.") } - hs.dispatch(nil, &Line{Cmd:"One"}) + hs.dispatch(nil, &Line{Cmd: "One"}) <-time.After(time.Millisecond) if callcount != 4 { t.Errorf("Our handler wasn't called four times :-(") @@ -107,7 +107,7 @@ func TestHandlerSet(t *testing.T) { } // Dispatch should result in 3 additions. - hs.dispatch(nil, &Line{Cmd:"One"}) + hs.dispatch(nil, &Line{Cmd: "One"}) <-time.After(time.Millisecond) if callcount != 7 { t.Errorf("Our handler wasn't called three times :-(") @@ -129,7 +129,7 @@ func TestHandlerSet(t *testing.T) { } // Dispatch should result in 2 additions. - hs.dispatch(nil, &Line{Cmd:"One"}) + hs.dispatch(nil, &Line{Cmd: "One"}) <-time.After(time.Millisecond) if callcount != 9 { t.Errorf("Our handler wasn't called two times :-(") @@ -151,7 +151,7 @@ func TestHandlerSet(t *testing.T) { } // Dispatch should result in 1 addition. - hs.dispatch(nil, &Line{Cmd:"One"}) + hs.dispatch(nil, &Line{Cmd: "One"}) <-time.After(time.Millisecond) if callcount != 10 { t.Errorf("Our handler wasn't called once :-(") @@ -170,7 +170,7 @@ func TestHandlerSet(t *testing.T) { } // Dispatch should result in NO additions. - hs.dispatch(nil, &Line{Cmd:"One"}) + hs.dispatch(nil, &Line{Cmd: "One"}) <-time.After(time.Millisecond) if callcount != 10 { t.Errorf("Our handler was called?") @@ -184,7 +184,7 @@ func TestCommandSet(t *testing.T) { } c := &command{ - fn: func(c *Conn, l *Line) {}, + fn: func(c *Conn, l *Line) {}, help: "wtf?", } @@ -196,7 +196,7 @@ func TestCommandSet(t *testing.T) { if fail := cs.add("one", c); fail != nil { t.Errorf("Adding a second 'one' command did not fail as expected.") } - + cn2 := cs.add("One Two", c).(*cNode) if _, ok := cs.set["one two"]; !ok || cn2.set != cs || cn2.prefix != "one two" { t.Errorf("Command 'one two' not added to set correctly.") @@ -208,7 +208,7 @@ func TestCommandSet(t *testing.T) { if c, l := cs.match("one"); c.(*cNode) != cn1 || l != 3 { t.Errorf("Didn't match 'one' when we should have.") } - if c, l := cs.match ("one two three"); c.(*cNode) != cn2 || l != 7 { + if c, l := cs.match("one two three"); c.(*cNode) != cn2 || l != 7 { t.Errorf("Didn't match 'one two' when we should have.") } @@ -216,14 +216,14 @@ func TestCommandSet(t *testing.T) { if _, ok := cs.set["one two"]; ok || cn2.set != nil { t.Errorf("Command 'one two' not removed correctly.") } - if c, l := cs.match ("one two three"); c.(*cNode) != cn1 || l != 3 { + if c, l := cs.match("one two three"); c.(*cNode) != cn1 || l != 3 { t.Errorf("Didn't match 'one' when we should have.") } cn1.Remove() if _, ok := cs.set["one"]; ok || cn1.set != nil { t.Errorf("Command 'one' not removed correctly.") } - if c, l := cs.match ("one two three"); c != nil || l != 0 { + if c, l := cs.match("one two three"); c != nil || l != 0 { t.Errorf("Matched 'one' when we shouldn't have.") } } diff --git a/client/handlers.go b/client/handlers.go index dcc48f9..5d017be 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -9,11 +9,12 @@ import ( // sets up the internal event handlers to do essential IRC protocol things var intHandlers = map[string]HandlerFunc{ + INIT: (*Conn).h_init, "001": (*Conn).h_001, "433": (*Conn).h_433, - "CTCP": (*Conn).h_CTCP, - "NICK": (*Conn).h_NICK, - "PING": (*Conn).h_PING, + CTCP: (*Conn).h_CTCP, + NICK: (*Conn).h_NICK, + PING: (*Conn).h_PING, } func (conn *Conn) addIntHandlers() { @@ -24,15 +25,24 @@ func (conn *Conn) addIntHandlers() { } } +// Password/User/Nick broadcast on connection. +func (conn *Conn) h_init(line *Line) { + if conn.password != "" { + conn.Pass(conn.password) + } + conn.Nick(conn.Me.Nick) + conn.User(conn.Me.Ident, conn.Me.Name) +} + // Basic ping/pong handler func (conn *Conn) h_PING(line *Line) { - conn.Raw("PONG :" + line.Args[0]) + conn.Pong(line.Args[0]) } // Handler to trigger a "CONNECTED" event on receipt of numeric 001 func (conn *Conn) h_001(line *Line) { // we're connected! - conn.dispatch(&Line{Cmd: "connected"}) + conn.dispatch(&Line{Cmd: CONNECTED}) // and we're being given our hostname (from the server's perspective) t := line.Args[len(line.Args)-1] if idx := strings.LastIndex(t, " "); idx != -1 { @@ -70,10 +80,10 @@ func (conn *Conn) h_433(line *Line) { // Handle VERSION requests and CTCP PING func (conn *Conn) h_CTCP(line *Line) { - if line.Args[0] == "VERSION" { - conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...") - } else if line.Args[0] == "PING" { - conn.CtcpReply(line.Nick, "PING", line.Args[2]) + if line.Args[0] == VERSION { + conn.CtcpReply(line.Nick, VERSION, "powered by goirc...") + } else if line.Args[0] == PING { + conn.CtcpReply(line.Nick, PING, line.Args[2]) } } @@ -99,7 +109,9 @@ func (conn *Conn) h_PRIVMSG(line *Line) { } } cmd, l := conn.cmdMatch(txt) - if cmd == nil { return } + if cmd == nil { + return + } if conn.CommandStripPrefix { txt = strings.TrimSpace(txt[l:]) } diff --git a/client/handlers_test.go b/client/handlers_test.go index fcedbb2..1389967 100644 --- a/client/handlers_test.go +++ b/client/handlers_test.go @@ -27,7 +27,7 @@ func Test001(t *testing.T) { l := parseLine(":irc.server.org 001 test :Welcome to IRC test!ident@somehost.com") // Set up a handler to detect whether connected handler is called from 001 hcon := false - c.HandleFunc("connected", func (conn *Conn, line *Line) { + c.HandleFunc(CONNECTED, func(conn *Conn, line *Line) { hcon = true }) @@ -139,11 +139,11 @@ func TestCTCP(t *testing.T) { c.h_CTCP(parseLine(":blah!moo@cows.com PRIVMSG test :\001UNKNOWN ctcp\001")) } -func TestPRIVMSG(t *testing.T){ +func TestPRIVMSG(t *testing.T) { c, s := setUp(t) defer s.tearDown() - f := func (conn *Conn, line *Line) { + f := func(conn *Conn, line *Line) { conn.Privmsg(line.Args[0], line.Args[1]) } c.CommandFunc("prefix", f, "") @@ -188,7 +188,6 @@ func TestPRIVMSG(t *testing.T){ c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test! prefix bar")) s.nc.ExpectNothing() - } // Test the handler for JOIN messages @@ -317,7 +316,6 @@ func TestMODE(t *testing.T) { t.Errorf("Channel.ParseModes() not called correctly.") } - // Send a nick mode line, returning Me gomock.InOrder( s.st.EXPECT().GetChannel("test").Return(nil), diff --git a/client/line.go b/client/line.go index 706d373..b29a8fe 100644 --- a/client/line.go +++ b/client/line.go @@ -62,7 +62,7 @@ func parseLine(s string) *Line { // So, I think CTCP and (in particular) CTCP ACTION are better handled as // separate events as opposed to forcing people to have gargantuan // handlers to cope with the possibilities. - if (line.Cmd == "PRIVMSG" || line.Cmd == "NOTICE") && + if (line.Cmd == PRIVMSG || line.Cmd == NOTICE) && len(line.Args[1]) > 2 && strings.HasPrefix(line.Args[1], "\001") && strings.HasSuffix(line.Args[1], "\001") { @@ -72,16 +72,16 @@ func parseLine(s string) *Line { // Replace the line with the unwrapped CTCP line.Args[1] = t[1] } - if c := strings.ToUpper(t[0]); c == "ACTION" && line.Cmd == "PRIVMSG" { + if c := strings.ToUpper(t[0]); c == ACTION && line.Cmd == PRIVMSG { // make a CTCP ACTION it's own event a-la PRIVMSG line.Cmd = c } else { // otherwise, dispatch a generic CTCP/CTCPREPLY event that // contains the type of CTCP in line.Args[0] - if line.Cmd == "PRIVMSG" { - line.Cmd = "CTCP" + if line.Cmd == PRIVMSG { + line.Cmd = CTCP } else { - line.Cmd = "CTCPREPLY" + line.Cmd = CTCPREPLY } line.Args = append([]string{c}, line.Args...) } diff --git a/client/line_test.go b/client/line_test.go index 6b9af7e..37e43dd 100644 --- a/client/line_test.go +++ b/client/line_test.go @@ -7,14 +7,14 @@ import ( func TestCopy(t *testing.T) { l1 := &Line{ - Nick: "nick", + Nick: "nick", Ident: "ident", - Host: "host", - Src: "src", - Cmd: "cmd", - Raw: "raw", - Args: []string{"arg", "text"}, - Time: time.Now(), + Host: "host", + Src: "src", + Cmd: "cmd", + Raw: "raw", + Args: []string{"arg", "text"}, + Time: time.Now(), } l2 := l1.Copy() diff --git a/client/state_handlers.go b/client/state_handlers.go index ab2e60a..ffc9211 100644 --- a/client/state_handlers.go +++ b/client/state_handlers.go @@ -9,19 +9,19 @@ import ( ) var stHandlers = map[string]HandlerFunc{ - "JOIN": (*Conn).h_JOIN, - "KICK": (*Conn).h_KICK, - "MODE": (*Conn).h_MODE, - "NICK": (*Conn).h_STNICK, - "PART": (*Conn).h_PART, - "QUIT": (*Conn).h_QUIT, + "JOIN": (*Conn).h_JOIN, + "KICK": (*Conn).h_KICK, + "MODE": (*Conn).h_MODE, + "NICK": (*Conn).h_STNICK, + "PART": (*Conn).h_PART, + "QUIT": (*Conn).h_QUIT, "TOPIC": (*Conn).h_TOPIC, - "311": (*Conn).h_311, - "324": (*Conn).h_324, - "332": (*Conn).h_332, - "352": (*Conn).h_352, - "353": (*Conn).h_353, - "671": (*Conn).h_671, + "311": (*Conn).h_311, + "324": (*Conn).h_324, + "332": (*Conn).h_332, + "352": (*Conn).h_352, + "353": (*Conn).h_353, + "671": (*Conn).h_671, } func (conn *Conn) addSTHandlers() {