*
* @returns True if in progress, otherwise false
*/
- inline bool capNegotiationInProgress() const { return !_capsQueued.empty(); }
+ inline bool capNegotiationInProgress() const { return (!_capsQueuedIndividual.empty() ||
+ !_capsQueuedBundled.empty()); }
/**
* Queues a capability to be requested.
*/
void beginCapNegotiation();
+ /**
+ * Ends capability negotiation.
+ *
+ * This won't have effect if other CAP commands are in the command queue before calling this
+ * command. It should only be called when capability negotiation is complete.
+ */
+ void endCapNegotiation();
+
+ /**
+ * Queues the most recent capability set for retrying individually.
+ *
+ * Retries the most recent bundle of capabilities one at a time instead of as a group, working
+ * around the issue that IRC servers can deny a group of requested capabilities without
+ * indicating which capabilities failed.
+ *
+ * See: http://ircv3.net/specs/core/capability-negotiation-3.1.html
+ *
+ * This does NOT call CoreNetwork::sendNextCap(). Call that when ready afterwards. Does
+ * nothing if the last capability tried was individual instead of a set.
+ */
+ void retryCapsIndividually();
+
/**
* List of capabilities requiring further core<->server messages to configure.
*
* For example, SASL requires the back-and-forth of AUTHENTICATE, so the next capability cannot
* be immediately sent.
*
+ * Any capabilities in this list must call CoreNetwork::sendNextCap() on their own and they will
+ * not be batched together with other capabilities.
+ *
* See: http://ircv3.net/specs/extensions/sasl-3.2.html
*/
const QStringList capsRequiringConfiguration = QStringList {
void disconnectFromIrc(bool requested = true, const QString &reason = QString(),
bool withReconnect = false, bool forceImmediate = false);
+ /**
+ * Forcibly close the IRC server socket, waiting for it to close.
+ *
+ * Call CoreNetwork::disconnectFromIrc() first, allow the event loop to run, then if you need to
+ * be sure the network's disconencted (e.g. clean-up), call this.
+ *
+ * @param msecs Maximum time to wait for socket to close, in milliseconds.
+ * @return True if socket closes successfully; false if error occurs or timeout reached
+ */
+ bool forceDisconnect(int msecs = 1000);
+
void userInput(BufferInfo bufferInfo, QString msg);
/**
// Maintain a list of CAPs that are being checked; if empty, negotiation finished
// See http://ircv3.net/specs/core/capability-negotiation-3.2.html
- QStringList _capsQueued; /// Capabilities to be checked
- bool _capNegotiationActive; /// Whether or not full capability negotiation was started
+ QStringList _capsQueuedIndividual; /// Capabilities to check that require one at a time requests
+ QStringList _capsQueuedBundled; /// Capabilities to check that can be grouped together
+ QStringList _capsQueuedLastBundle; /// Most recent capability bundle requested (no individuals)
+ // Some capabilities, such as SASL, require follow-up messages to be fully enabled. These
+ // capabilities should not be grouped with others to avoid requesting new capabilities while the
+ // previous capability is still being set up.
+ // Additionally, IRC servers can choose to send a 'NAK' to any set of requested capabilities.
+ // If this happens, we need a way to retry each capability individually in order to avoid having
+ // one failing capability (e.g. SASL) block all other capabilities.
+
+ bool _capNegotiationActive; /// Whether or not full capability negotiation was started
// Avoid displaying repeat "negotiation finished" messages
- bool _capInitialNegotiationEnded; /// Whether or not initial capability negotiation finished
+ bool _capInitialNegotiationEnded; /// Whether or not initial capability negotiation finished
// Avoid sending repeat "CAP END" replies when registration is already ended
/**
- * Gets the next capability to request, removing it from the queue.
+ * Gets the next set of capabilities to request, removing them from the queue.
+ *
+ * May return one or multiple space-separated capabilities, depending on queue.
+ *
+ * @returns Space-separated names of capabilities to request, or empty string if none remain
+ */
+ QString takeQueuedCaps();
+
+ /**
+ * Maximum length of a single 'CAP REQ' command.
+ *
+ * To be safe, 100 chars. Higher numbers should be possible; this is following the conservative
+ * minimum number of characters that IRC servers must return in CAP NAK replies. This also
+ * means CAP NAK replies will contain the full list of denied capabilities.
*
- * @returns Name of capability to request
+ * See: http://ircv3.net/specs/core/capability-negotiation-3.1.html
*/
- QString takeQueuedCap();
+ const int maxCapRequestLength = 100;
QTimer _tokenBucketTimer;
int _messageDelay; // token refill speed in ms