fix bug with capabilities breaking after reconnect

This commit is contained in:
Liam Stanley 2017-01-06 01:09:18 -05:00
parent 0648fdc4f7
commit 683485f34c
4 changed files with 58 additions and 75 deletions

116
cap.go

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"io" "io"
"strings" "strings"
"time"
) )
var possibleCap = []string{ var possibleCap = []string{
@ -19,89 +18,68 @@ var possibleCap = []string{
"message-tags", "message-tags",
} }
// capHandler attempts to find out what IRCv3 capabilities the server supports. // handleCAP attempts to find out what IRCv3 capabilities the server supports.
// This will lock further registration until we have acknowledged the // This will lock further registration until we have acknowledged the
// capabilities. // capabilities.
func (c *Client) capHandler() { func handleCAP(c *Client, e Event) {
// testnet.inspircd.org may potentially be used for testing. // testnet.inspircd.org may potentially be used for testing.
capDone := make(chan struct{})
var caps []string
possible := c.Config.SupportedCaps possible := c.Config.SupportedCaps
possible = append(possible, possibleCap...) possible = append(possible, possibleCap...)
cuid := c.Callbacks.sregister(true, CAP, CallbackFunc(func(client *Client, e Event) { // We can assume there was a failure attempting to enable a capability.
// We can assume there was a failure attempting to enable a capability. if len(e.Params) == 2 && e.Params[1] == CAP_NAK {
if len(e.Params) == 2 && e.Params[1] == CAP_NAK { // Let the server know that we're done.
// Let the server know that we're done. c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) return
}
capDone <- struct{}{} if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
return c.state.mu.Lock()
} // Loop through and check if it's one we support.
for _, cap := range strings.Split(e.Trailing, " ") {
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS { for i := 0; i < len(possible); i++ {
// Check if it's one that we support.
client.state.mu.Lock() if possibleCap[i] == cap {
client.state.supportedCap = strings.Split(e.Trailing, " ") // Ensure that there are no duplicates.
var isin bool
for i := 0; i < len(client.state.supportedCap); i++ { for j := 0; j < len(c.state.tmpCap); j++ {
for j := 0; j < len(possible); j++ { if c.state.tmpCap[j] == cap {
if client.state.supportedCap[i] == possibleCap[j] { isin = true
// It's one for which we support. break
caps = append(caps, client.state.supportedCap[i]) }
break
} }
if !isin {
c.state.tmpCap = append(c.state.tmpCap, cap)
}
break
} }
} }
client.state.mu.Unlock() }
c.state.mu.Unlock()
// Indicates if this is a multi-line LS. (2 args means it's the // Indicates if this is a multi-line LS. (2 args means it's the
// last LS) // last LS).
if len(e.Params) == 2 { if len(e.Params) == 2 {
// If we support no caps, just ack the CAP message and END. // If we support no caps, just ack the CAP message and END.
if len(caps) == 0 { if len(c.state.tmpCap) == 0 {
client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
capDone <- struct{}{} return
return
}
// Let them know which ones we'd like to enable.
client.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(caps, " ")})
} }
// Let them know which ones we'd like to enable.
c.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " ")})
} }
}
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK { if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
client.state.mu.Lock() c.state.mu.Lock()
client.state.enabledCap = strings.Split(e.Trailing, " ") c.state.enabledCap = strings.Split(e.Trailing, " ")
client.state.mu.Unlock() c.state.mu.Unlock()
// Let the server know that we're done. // Let the server know that we're done.
client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
return
capDone <- struct{}{}
return
}
}))
unknCuid := c.Callbacks.sregister(true, ERR_UNKNOWNCOMMAND, CallbackFunc(func(client *Client, e Event) {
capDone <- struct{}{}
}))
// Ensure that the callbacks are removed when done.
defer c.Callbacks.Remove(cuid)
defer c.Callbacks.Remove(unknCuid)
// List the capabilities, specifically with the max protocol we support.
c.Send(&Event{Command: CAP, Params: []string{CAP_LS, "302"}})
select {
case <-capDone:
close(capDone)
case <-time.After(time.Second * 15):
// Wait 15 seconds, only because the server HAS YET to tell us that
// it's an unknown command, meaning it's likely doing things behind
// the scenes.
} }
} }

@ -215,9 +215,12 @@ func (c *Client) Connect() error {
// Start read loop to process messages from the server. // Start read loop to process messages from the server.
go c.readLoop() go c.readLoop()
// Process potential IRCv3 capabilities. // List the IRCv3 capabilities, specifically with the max protocol we
if !c.Config.DisableCapTracking && !c.Config.DisableTracking { // support.
go c.capHandler() if !c.Config.DisableTracking && !c.Config.DisableCapTracking {
if err := c.Send(&Event{Command: CAP, Params: []string{CAP_LS, "302"}}); err != nil {
return err
}
} }
// Consider the connection a success at this point. // Consider the connection a success at this point.

@ -43,6 +43,7 @@ func (c *Client) registerHandlers() {
// CAP IRCv3-specific tracking and functionality. // CAP IRCv3-specific tracking and functionality.
if !c.Config.DisableTracking && !c.Config.DisableCapTracking { if !c.Config.DisableTracking && !c.Config.DisableCapTracking {
c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST)) c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY)) c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT)) c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))

@ -40,9 +40,10 @@ type state struct {
channels map[string]*Channel channels map[string]*Channel
// enabledCap are the capabilities which are enabled for this connection. // enabledCap are the capabilities which are enabled for this connection.
enabledCap []string enabledCap []string
// supportedCap are the capabilties which are supporteed by the server // tmpCap are the capabilties which we share with the server during the
// during the last capability check. // last capability check. These will get sent once we have received the
supportedCap []string // last capability list command from the server.
tmpCap []string
} }
// User represents an IRC user and the state attached to them. // User represents an IRC user and the state attached to them.