From 0c360d24c56f7d65f1b2be6a259491342eeb99f3 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 9 Nov 2021 21:32:26 +0100 Subject: [PATCH] Remove support for mixed multi-upstream LIST Multi-upstream connections can still send LIST commands with a network suffix. --- downstream.go | 64 ++++++++----------- upstream.go | 171 +++++++++++++++++++++++++------------------------- user.go | 11 +--- 3 files changed, 112 insertions(+), 134 deletions(-) diff --git a/downstream.go b/downstream.go index 2d80d9d..d6d287a 100644 --- a/downstream.go +++ b/downstream.go @@ -1837,48 +1837,34 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { case "LIST": // TODO: support ELIST when supported by all upstreams - pl := pendingLIST{ - downstreamID: dc.id, - pendingCommands: make(map[int64]*irc.Message), - } - 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 { - return err - } - upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel) - } + network := dc.network + if network == nil && len(msg.Params) > 0 { + var err error + network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0]) + if err != nil { + return err } } + 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) - dc.forEachUpstream(func(uc *upstreamConn) { - if upstream != nil && upstream != uc { - return - } - var params []string - 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) - }) + uc := network.conn + if uc == nil { + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_LISTEND, + Params: []string{dc.nick, "Disconnected from upstream server"}, + }) + return nil + } + + uc.enqueueLIST(dc, msg) case "NAMES": if len(msg.Params) == 0 { dc.SendMessage(&irc.Message{ diff --git a/upstream.go b/upstream.go index eaefff6..19e3da5 100644 --- a/upstream.go +++ b/upstream.go @@ -72,6 +72,11 @@ func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) { }) } +type pendingUpstreamCommand struct { + downstreamID uint64 + cmd *irc.Message +} + type upstreamConn struct { conn @@ -104,8 +109,10 @@ type upstreamConn struct { casemapIsSet bool - // set of LIST commands in progress, per downstream - pendingLISTDownstreamSet map[uint64]struct{} + // Queue of LIST commands in progress. The first entry has been sent to the + // server and is awaiting reply. The following entries have not been sent + // yet. + pendingLIST []pendingUpstreamCommand gotMotd bool } @@ -190,18 +197,17 @@ func connectToUpstream(network *network) (*upstreamConn, error) { } uc := &upstreamConn{ - conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options), - network: network, - user: network.user, - channels: upstreamChannelCasemapMap{newCasemapMap(0)}, - supportedCaps: make(map[string]string), - caps: make(map[string]bool), - batches: make(map[string]batch), - availableChannelTypes: stdChannelTypes, - availableChannelModes: stdChannelModes, - availableMemberships: stdMemberships, - isupport: make(map[string]*string), - pendingLISTDownstreamSet: make(map[uint64]struct{}), + conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options), + network: network, + user: network.user, + channels: upstreamChannelCasemapMap{newCasemapMap(0)}, + supportedCaps: make(map[string]string), + caps: make(map[string]bool), + batches: make(map[string]batch), + availableChannelTypes: stdChannelTypes, + availableChannelModes: stdChannelModes, + availableMemberships: stdMemberships, + isupport: make(map[string]*string), } return uc, nil } @@ -235,73 +241,63 @@ func (uc *upstreamConn) isOurNick(nick string) bool { return uc.nickCM == uc.network.casemap(nick) } -func (uc *upstreamConn) getPendingLIST() *pendingLIST { - for _, pl := range uc.user.pendingLISTs { - if _, ok := pl.pendingCommands[uc.network.ID]; !ok { - continue - } - return &pl +func (uc *upstreamConn) endPendingLISTs() { + for _, pendingCmd := range uc.pendingLIST { + uc.forEachDownstreamByID(pendingCmd.downstreamID, func(dc *downstreamConn) { + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_LISTEND, + Params: []string{dc.nick, "End of /LIST"}, + }) + }) } - return nil + uc.pendingLIST = 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{ - Prefix: dc.srv.prefix(), - Command: irc.RPL_LISTEND, - Params: []string{dc.nick, "End of /LIST"}, - }) - }) - } - found = true - if !all { - delete(uc.pendingLISTDownstreamSet, pl.downstreamID) - uc.user.forEachUpstream(func(uc *upstreamConn) { - uc.trySendLIST(pl.downstreamID) - }) - return - } +func (uc *upstreamConn) sendNextPendingLIST() { + if len(uc.pendingLIST) == 0 { + return } - return + uc.SendMessage(uc.pendingLIST[0].cmd) } -func (uc *upstreamConn) trySendLIST(downstreamID uint64) { - if _, ok := uc.pendingLISTDownstreamSet[downstreamID]; ok { - // a LIST command is already pending - // we will try again when that command is completed - return +func (uc *upstreamConn) enqueueLIST(dc *downstreamConn, cmd *irc.Message) { + uc.pendingLIST = append(uc.pendingLIST, pendingUpstreamCommand{ + downstreamID: dc.id, + cmd: cmd, + }) + + if len(uc.pendingLIST) == 1 { + uc.sendNextPendingLIST() + } +} + +func (uc *upstreamConn) currentPendingLIST() (*downstreamConn, *irc.Message) { + if len(uc.pendingLIST) == 0 { + return nil, nil } - for _, pl := range uc.user.pendingLISTs { - if pl.downstreamID != downstreamID { - continue + pendingCmd := uc.pendingLIST[0] + for _, dc := range uc.user.downstreamConns { + if dc.id == pendingCmd.downstreamID { + return dc, pendingCmd.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) - - uc.pendingLISTDownstreamSet[downstreamID] = struct{}{} - return } + + 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) { @@ -1099,23 +1095,31 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { return err } - pl := uc.getPendingLIST() - if pl == nil { + dc, cmd := uc.currentPendingLIST() + if cmd == nil { 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{ - Prefix: dc.srv.prefix(), - Command: irc.RPL_LIST, - Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic}, - }) + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: irc.RPL_LIST, + Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic}, }) case irc.RPL_LISTEND: - ok := uc.endPendingLISTs(false) - if !ok { + dc, cmd := uc.dequeueLIST() + if cmd == nil { 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: var name, statusStr, members string 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" { - ok := uc.endPendingLISTs(false) - if !ok { - return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command) - } + uc.endPendingLISTs() } uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { diff --git a/user.go b/user.go index f3db7eb..6f24e9c 100644 --- a/user.go +++ b/user.go @@ -405,15 +405,6 @@ type user struct { networks []*network downstreamConns []*downstreamConn 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 { @@ -690,7 +681,7 @@ func (u *user) run() { func (u *user) handleUpstreamDisconnected(uc *upstreamConn) { uc.network.conn = nil - uc.endPendingLISTs(true) + uc.endPendingLISTs() for _, entry := range uc.channels.innerMap { uch := entry.value.(*upstreamChannel)