Remove support for mixed multi-upstream LIST

Multi-upstream connections can still send LIST commands with a
network suffix.
This commit is contained in:
Simon Ser 2021-11-09 21:32:26 +01:00
parent d870efa666
commit 0c360d24c5
3 changed files with 112 additions and 134 deletions

View File

@ -1837,48 +1837,34 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
case "LIST": case "LIST":
// TODO: support ELIST when supported by all upstreams // TODO: support ELIST when supported by all upstreams
pl := pendingLIST{ network := dc.network
downstreamID: dc.id, if network == nil && len(msg.Params) > 0 {
pendingCommands: make(map[int64]*irc.Message), var err error
} network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
var upstream *upstreamConn
var upstreamChannels map[int64][]string
if len(msg.Params) > 0 {
uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
upstream = uc
} else {
upstreamChannels = make(map[int64][]string)
channels := strings.Split(msg.Params[0], ",")
for _, channel := range channels {
uc, upstreamChannel, err := dc.unmarshalEntity(channel)
if err != nil { if err != nil {
return err return err
} }
upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
}
} }
if network == nil {
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: irc.RPL_LISTEND,
Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
})
return nil
} }
dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl) uc := network.conn
dc.forEachUpstream(func(uc *upstreamConn) { if uc == nil {
if upstream != nil && upstream != uc { dc.SendMessage(&irc.Message{
return Prefix: dc.srv.prefix(),
} Command: irc.RPL_LISTEND,
var params []string Params: []string{dc.nick, "Disconnected from upstream server"},
if upstreamChannels != nil {
if channels, ok := upstreamChannels[uc.network.ID]; ok {
params = []string{strings.Join(channels, ",")}
} else {
return
}
}
pl.pendingCommands[uc.network.ID] = &irc.Message{
Command: "LIST",
Params: params,
}
uc.trySendLIST(dc.id)
}) })
return nil
}
uc.enqueueLIST(dc, msg)
case "NAMES": case "NAMES":
if len(msg.Params) == 0 { if len(msg.Params) == 0 {
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{

View File

@ -72,6 +72,11 @@ func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
}) })
} }
type pendingUpstreamCommand struct {
downstreamID uint64
cmd *irc.Message
}
type upstreamConn struct { type upstreamConn struct {
conn conn
@ -104,8 +109,10 @@ type upstreamConn struct {
casemapIsSet bool casemapIsSet bool
// set of LIST commands in progress, per downstream // Queue of LIST commands in progress. The first entry has been sent to the
pendingLISTDownstreamSet map[uint64]struct{} // server and is awaiting reply. The following entries have not been sent
// yet.
pendingLIST []pendingUpstreamCommand
gotMotd bool gotMotd bool
} }
@ -201,7 +208,6 @@ func connectToUpstream(network *network) (*upstreamConn, error) {
availableChannelModes: stdChannelModes, availableChannelModes: stdChannelModes,
availableMemberships: stdMemberships, availableMemberships: stdMemberships,
isupport: make(map[string]*string), isupport: make(map[string]*string),
pendingLISTDownstreamSet: make(map[uint64]struct{}),
} }
return uc, nil return uc, nil
} }
@ -235,28 +241,9 @@ func (uc *upstreamConn) isOurNick(nick string) bool {
return uc.nickCM == uc.network.casemap(nick) return uc.nickCM == uc.network.casemap(nick)
} }
func (uc *upstreamConn) getPendingLIST() *pendingLIST { func (uc *upstreamConn) endPendingLISTs() {
for _, pl := range uc.user.pendingLISTs { for _, pendingCmd := range uc.pendingLIST {
if _, ok := pl.pendingCommands[uc.network.ID]; !ok { uc.forEachDownstreamByID(pendingCmd.downstreamID, func(dc *downstreamConn) {
continue
}
return &pl
}
return nil
}
func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
found = false
for i := 0; i < len(uc.user.pendingLISTs); i++ {
pl := uc.user.pendingLISTs[i]
if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
continue
}
delete(pl.pendingCommands, uc.network.ID)
if len(pl.pendingCommands) == 0 {
uc.user.pendingLISTs = append(uc.user.pendingLISTs[:i], uc.user.pendingLISTs[i+1:]...)
i--
uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),
Command: irc.RPL_LISTEND, Command: irc.RPL_LISTEND,
@ -264,46 +251,55 @@ func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
}) })
}) })
} }
found = true uc.pendingLIST = nil
if !all {
delete(uc.pendingLISTDownstreamSet, pl.downstreamID)
uc.user.forEachUpstream(func(uc *upstreamConn) {
uc.trySendLIST(pl.downstreamID)
})
return
}
}
return
} }
func (uc *upstreamConn) trySendLIST(downstreamID uint64) { func (uc *upstreamConn) sendNextPendingLIST() {
if _, ok := uc.pendingLISTDownstreamSet[downstreamID]; ok { if len(uc.pendingLIST) == 0 {
// a LIST command is already pending
// we will try again when that command is completed
return return
} }
uc.SendMessage(uc.pendingLIST[0].cmd)
}
for _, pl := range uc.user.pendingLISTs { func (uc *upstreamConn) enqueueLIST(dc *downstreamConn, cmd *irc.Message) {
if pl.downstreamID != downstreamID { uc.pendingLIST = append(uc.pendingLIST, pendingUpstreamCommand{
continue downstreamID: dc.id,
} cmd: cmd,
// this is the first pending LIST command list of the downstream })
listCommand, ok := pl.pendingCommands[uc.network.ID]
if !ok {
// there is no command for this upstream in these LIST commands
// do not send anything
continue
}
// there is a command for this upstream in these LIST commands
// send it now
uc.SendMessageLabeled(downstreamID, listCommand) if len(uc.pendingLIST) == 1 {
uc.sendNextPendingLIST()
uc.pendingLISTDownstreamSet[downstreamID] = struct{}{}
return
} }
} }
func (uc *upstreamConn) currentPendingLIST() (*downstreamConn, *irc.Message) {
if len(uc.pendingLIST) == 0 {
return nil, nil
}
pendingCmd := uc.pendingLIST[0]
for _, dc := range uc.user.downstreamConns {
if dc.id == pendingCmd.downstreamID {
return dc, pendingCmd.cmd
}
}
return nil, pendingCmd.cmd
}
func (uc *upstreamConn) dequeueLIST() (*downstreamConn, *irc.Message) {
dc, cmd := uc.currentPendingLIST()
if len(uc.pendingLIST) > 0 {
copy(uc.pendingLIST, uc.pendingLIST[1:])
uc.pendingLIST = uc.pendingLIST[:len(uc.pendingLIST)-1]
}
uc.sendNextPendingLIST()
return dc, cmd
}
func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) { func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
memberships := make(memberships, 0, 4) memberships := make(memberships, 0, 4)
i := 0 i := 0
@ -1099,23 +1095,31 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
return err return err
} }
pl := uc.getPendingLIST() dc, cmd := uc.currentPendingLIST()
if pl == nil { if cmd == nil {
return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST") return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
} else if dc == nil {
return nil
} }
uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),
Command: irc.RPL_LIST, Command: irc.RPL_LIST,
Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic}, Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
}) })
})
case irc.RPL_LISTEND: case irc.RPL_LISTEND:
ok := uc.endPendingLISTs(false) dc, cmd := uc.dequeueLIST()
if !ok { if cmd == nil {
return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST") return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
} else if dc == nil {
return nil
} }
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: irc.RPL_LISTEND,
Params: []string{dc.nick, "End of /LIST"},
})
case irc.RPL_NAMREPLY: case irc.RPL_NAMREPLY:
var name, statusStr, members string var name, statusStr, members string
if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil { if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
@ -1433,10 +1437,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
} }
if command == "LIST" { if command == "LIST" {
ok := uc.endPendingLISTs(false) uc.endPendingLISTs()
if !ok {
return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command)
}
} }
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {

11
user.go
View File

@ -405,15 +405,6 @@ type user struct {
networks []*network networks []*network
downstreamConns []*downstreamConn downstreamConns []*downstreamConn
msgStore messageStore msgStore messageStore
// LIST commands in progress
pendingLISTs []pendingLIST
}
type pendingLIST struct {
downstreamID uint64
// list of per-upstream LIST commands not yet sent or completed
pendingCommands map[int64]*irc.Message
} }
func newUser(srv *Server, record *User) *user { func newUser(srv *Server, record *User) *user {
@ -690,7 +681,7 @@ func (u *user) run() {
func (u *user) handleUpstreamDisconnected(uc *upstreamConn) { func (u *user) handleUpstreamDisconnected(uc *upstreamConn) {
uc.network.conn = nil uc.network.conn = nil
uc.endPendingLISTs(true) uc.endPendingLISTs()
for _, entry := range uc.channels.innerMap { for _, entry := range uc.channels.innerMap {
uch := entry.value.(*upstreamChannel) uch := entry.value.(*upstreamChannel)