forked from External/ergo
remove draft/resume-0.5
This commit is contained in:
parent
df49137aca
commit
ba21987d03
14 changed files with 4 additions and 683 deletions
217
irc/client.go
217
irc/client.go
|
|
@ -56,22 +56,11 @@ const (
|
|||
// This is how long a client gets without sending any message, including the PONG to our
|
||||
// PING, before we disconnect them:
|
||||
DefaultTotalTimeout = 2*time.Minute + 30*time.Second
|
||||
// Resumeable clients (clients who have negotiated caps.Resume) get longer:
|
||||
ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
|
||||
|
||||
// round off the ping interval by this much, see below:
|
||||
PingCoalesceThreshold = time.Second
|
||||
)
|
||||
|
||||
// ResumeDetails is a place to stash data at various stages of
|
||||
// the resume process: when handling the RESUME command itself,
|
||||
// when completing the registration, and when rejoining channels.
|
||||
type ResumeDetails struct {
|
||||
PresentedToken string
|
||||
Timestamp time.Time
|
||||
HistoryIncomplete bool
|
||||
}
|
||||
|
||||
// Client is an IRC client.
|
||||
type Client struct {
|
||||
account string
|
||||
|
|
@ -79,7 +68,6 @@ type Client struct {
|
|||
accountRegDate time.Time
|
||||
accountSettings AccountSettings
|
||||
awayMessage string
|
||||
brbTimer BrbTimer
|
||||
channels ChannelSet
|
||||
ctime time.Time
|
||||
destroyed bool
|
||||
|
|
@ -109,7 +97,6 @@ type Client struct {
|
|||
registered bool
|
||||
registerCmdSent bool // already sent the draft/register command, can't send it again
|
||||
registrationTimer *time.Timer
|
||||
resumeID string
|
||||
server *Server
|
||||
skeleton string
|
||||
sessions []*Session
|
||||
|
|
@ -164,7 +151,6 @@ type Session struct {
|
|||
|
||||
fakelag Fakelag
|
||||
deferredFakelagCount int
|
||||
destroyed uint32
|
||||
|
||||
certfp string
|
||||
peerCerts []*x509.Certificate
|
||||
|
|
@ -184,8 +170,6 @@ type Session struct {
|
|||
|
||||
registrationMessages int
|
||||
|
||||
resumeID string
|
||||
resumeDetails *ResumeDetails
|
||||
zncPlaybackTimes *zncPlaybackTimes
|
||||
autoreplayMissedSince time.Time
|
||||
|
||||
|
|
@ -259,20 +243,6 @@ func (s *Session) IP() net.IP {
|
|||
return s.realIP
|
||||
}
|
||||
|
||||
// returns whether the session was actively destroyed (for example, by ping
|
||||
// timeout or NS GHOST).
|
||||
// avoids a race condition between asynchronous idle-timing-out of sessions,
|
||||
// and a condition that allows implicit BRB on connection errors (since
|
||||
// destroy()'s socket.Close() appears to socket.Read() as a connection error)
|
||||
func (session *Session) Destroyed() bool {
|
||||
return atomic.LoadUint32(&session.destroyed) == 1
|
||||
}
|
||||
|
||||
// sets the timed-out flag
|
||||
func (session *Session) SetDestroyed() {
|
||||
atomic.StoreUint32(&session.destroyed, 1)
|
||||
}
|
||||
|
||||
// returns whether the client supports a smart history replay cap,
|
||||
// and therefore autoreplay-on-join and similar should be suppressed
|
||||
func (session *Session) HasHistoryCaps() bool {
|
||||
|
|
@ -371,7 +341,6 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||
client.requireSASLMessage = banMsg
|
||||
}
|
||||
client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
|
||||
client.brbTimer.Initialize(client)
|
||||
session := &Session{
|
||||
client: client,
|
||||
socket: socket,
|
||||
|
|
@ -459,7 +428,6 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus m
|
|||
client.SetMode(m, true)
|
||||
}
|
||||
client.history.Initialize(0, 0)
|
||||
client.brbTimer.Initialize(client)
|
||||
|
||||
server.accounts.Login(client, account)
|
||||
|
||||
|
|
@ -553,7 +521,7 @@ func (client *Client) lookupHostname(session *Session, overwrite bool) {
|
|||
cloakedHostname := config.Server.Cloaks.ComputeCloak(ip)
|
||||
client.stateMutex.Lock()
|
||||
defer client.stateMutex.Unlock()
|
||||
// update the hostname if this is a new connection or a resume, but not if it's a reattach
|
||||
// update the hostname if this is a new connection, but not if it's a reattach
|
||||
if overwrite || client.rawHostname == "" {
|
||||
client.rawHostname = hostname
|
||||
client.cloakedHostname = cloakedHostname
|
||||
|
|
@ -671,14 +639,7 @@ func (client *Client) run(session *Session) {
|
|||
isReattach := client.Registered()
|
||||
if isReattach {
|
||||
client.Touch(session)
|
||||
if session.resumeDetails != nil {
|
||||
session.playResume()
|
||||
session.resumeDetails = nil
|
||||
client.brbTimer.Disable()
|
||||
session.SetAway("") // clear BRB message if any
|
||||
} else {
|
||||
client.playReattachMessages(session)
|
||||
}
|
||||
client.playReattachMessages(session)
|
||||
}
|
||||
|
||||
firstLine := !isReattach
|
||||
|
|
@ -697,11 +658,6 @@ func (client *Client) run(session *Session) {
|
|||
quitMessage = "connection closed"
|
||||
}
|
||||
client.Quit(quitMessage, session)
|
||||
// since the client did not actually send us a QUIT,
|
||||
// give them a chance to resume if applicable:
|
||||
if !session.Destroyed() {
|
||||
client.brbTimer.Enable()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -852,9 +808,6 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) {
|
|||
|
||||
func (session *Session) handleIdleTimeout() {
|
||||
totalTimeout := DefaultTotalTimeout
|
||||
if session.capabilities.Has(caps.Resume) {
|
||||
totalTimeout = ResumeableTotalTimeout
|
||||
}
|
||||
pingTimeout := DefaultIdleTimeout
|
||||
if session.isTor {
|
||||
pingTimeout = TorIdleTimeout
|
||||
|
|
@ -911,151 +864,6 @@ func (session *Session) Ping() {
|
|||
session.Send(nil, "", "PING", session.client.Nick())
|
||||
}
|
||||
|
||||
// tryResume tries to resume if the client asked us to.
|
||||
func (session *Session) tryResume() (success bool) {
|
||||
var oldResumeID string
|
||||
|
||||
defer func() {
|
||||
if success {
|
||||
// "On a successful request, the server [...] terminates the old client's connection"
|
||||
oldSession := session.client.GetSessionByResumeID(oldResumeID)
|
||||
if oldSession != nil {
|
||||
session.client.destroy(oldSession)
|
||||
}
|
||||
} else {
|
||||
session.resumeDetails = nil
|
||||
}
|
||||
}()
|
||||
|
||||
client := session.client
|
||||
server := client.server
|
||||
config := server.Config()
|
||||
|
||||
oldClient, oldResumeID := server.resumeManager.VerifyToken(client, session.resumeDetails.PresentedToken)
|
||||
if oldClient == nil {
|
||||
session.Send(nil, server.name, "FAIL", "RESUME", "INVALID_TOKEN", client.t("Cannot resume connection, token is not valid"))
|
||||
return
|
||||
}
|
||||
|
||||
resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
|
||||
if !resumeAllowed {
|
||||
session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection, old and new clients must have TLS"))
|
||||
return
|
||||
}
|
||||
|
||||
err := server.clients.Resume(oldClient, session)
|
||||
if err != nil {
|
||||
session.Send(nil, server.name, "FAIL", "RESUME", "CANNOT_RESUME", client.t("Cannot resume connection"))
|
||||
return
|
||||
}
|
||||
|
||||
success = true
|
||||
client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", oldClient.Nick()))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// playResume is called from the session's fresh goroutine after a resume;
|
||||
// it sends notifications to friends, then plays the registration burst and replays
|
||||
// stored history to the session
|
||||
func (session *Session) playResume() {
|
||||
client := session.client
|
||||
server := client.server
|
||||
config := server.Config()
|
||||
|
||||
friends := make(ClientSet)
|
||||
var oldestLostMessage time.Time
|
||||
|
||||
// work out how much time, if any, is not covered by history buffers
|
||||
// assume that a persistent buffer covers the whole resume period
|
||||
for _, channel := range client.Channels() {
|
||||
for _, member := range channel.auditoriumFriends(client) {
|
||||
friends.Add(member)
|
||||
}
|
||||
status, _, _ := channel.historyStatus(config)
|
||||
if status == HistoryEphemeral {
|
||||
lastDiscarded := channel.history.LastDiscarded()
|
||||
if oldestLostMessage.Before(lastDiscarded) {
|
||||
oldestLostMessage = lastDiscarded
|
||||
}
|
||||
}
|
||||
}
|
||||
cHistoryStatus, _ := client.historyStatus(config)
|
||||
if cHistoryStatus == HistoryEphemeral {
|
||||
lastDiscarded := client.history.LastDiscarded()
|
||||
if oldestLostMessage.Before(lastDiscarded) {
|
||||
oldestLostMessage = lastDiscarded
|
||||
}
|
||||
}
|
||||
|
||||
timestamp := session.resumeDetails.Timestamp
|
||||
gap := oldestLostMessage.Sub(timestamp)
|
||||
session.resumeDetails.HistoryIncomplete = gap > 0 || timestamp.IsZero()
|
||||
gapSeconds := int(gap.Seconds()) + 1 // round up to avoid confusion
|
||||
|
||||
details := client.Details()
|
||||
oldNickmask := details.nickMask
|
||||
client.lookupHostname(session, true)
|
||||
hostname := client.Hostname() // may be a vhost
|
||||
timestampString := timestamp.Format(IRCv3TimestampFormat)
|
||||
|
||||
// send quit/resume messages to friends
|
||||
for friend := range friends {
|
||||
if friend == client {
|
||||
continue
|
||||
}
|
||||
for _, fSession := range friend.Sessions() {
|
||||
if fSession.capabilities.Has(caps.Resume) {
|
||||
if !session.resumeDetails.HistoryIncomplete {
|
||||
fSession.Send(nil, oldNickmask, "RESUMED", hostname, "ok")
|
||||
} else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() {
|
||||
fSession.Send(nil, oldNickmask, "RESUMED", hostname, timestampString)
|
||||
} else {
|
||||
fSession.Send(nil, oldNickmask, "RESUMED", hostname)
|
||||
}
|
||||
} else {
|
||||
if !session.resumeDetails.HistoryIncomplete {
|
||||
fSession.Send(nil, oldNickmask, "QUIT", friend.t("Client reconnected"))
|
||||
} else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() {
|
||||
fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of message history lost)"), gapSeconds))
|
||||
} else {
|
||||
fSession.Send(nil, oldNickmask, "QUIT", friend.t("Client reconnected (message history may have been lost)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if session.resumeDetails.HistoryIncomplete {
|
||||
if !timestamp.IsZero() {
|
||||
session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds))
|
||||
} else {
|
||||
session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Resume may have lost some message history"))
|
||||
}
|
||||
}
|
||||
|
||||
session.Send(nil, client.server.name, "RESUME", "SUCCESS", details.nick)
|
||||
|
||||
server.playRegistrationBurst(session)
|
||||
|
||||
for _, channel := range client.Channels() {
|
||||
channel.Resume(session, timestamp)
|
||||
}
|
||||
|
||||
// replay direct PRIVSMG history
|
||||
_, privmsgSeq, err := server.GetHistorySequence(nil, client, "")
|
||||
if !timestamp.IsZero() && err == nil && privmsgSeq != nil {
|
||||
after := history.Selector{Time: timestamp}
|
||||
items, _ := privmsgSeq.Between(after, history.Selector{}, config.History.ZNCMax)
|
||||
if len(items) != 0 {
|
||||
rb := NewResponseBuffer(session)
|
||||
client.replayPrivmsgHistory(rb, items, "")
|
||||
rb.Send(true)
|
||||
}
|
||||
}
|
||||
|
||||
session.resumeDetails = nil
|
||||
}
|
||||
|
||||
func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string) {
|
||||
var batchID string
|
||||
details := client.Details()
|
||||
|
|
@ -1388,8 +1196,6 @@ func (client *Client) destroy(session *Session) {
|
|||
client.stateMutex.Lock()
|
||||
|
||||
details := client.detailsNoMutex()
|
||||
brbState := client.brbTimer.state
|
||||
brbAt := client.brbTimer.brbAt
|
||||
wasReattach := session != nil && session.client != client
|
||||
sessionRemoved := false
|
||||
registered := client.registered
|
||||
|
|
@ -1431,9 +1237,7 @@ func (client *Client) destroy(session *Session) {
|
|||
}
|
||||
|
||||
// should we destroy the whole client this time?
|
||||
// BRB is not respected if this is a destroy of the whole client (i.e., session == nil)
|
||||
brbEligible := session != nil && brbState == BrbEnabled
|
||||
shouldDestroy := !client.destroyed && remainingSessions == 0 && !brbEligible && !alwaysOn
|
||||
shouldDestroy := !client.destroyed && remainingSessions == 0 && !alwaysOn
|
||||
// decrement stats on a true destroy, or for the removal of the last connected session
|
||||
// of an always-on client
|
||||
shouldDecrement := shouldDestroy || (alwaysOn && len(sessionsToDestroy) != 0 && len(client.sessions) == 0)
|
||||
|
|
@ -1479,7 +1283,6 @@ func (client *Client) destroy(session *Session) {
|
|||
// send quit/error message to client if they haven't been sent already
|
||||
client.Quit("", session)
|
||||
quitMessage = session.quitMessage // doesn't need synch, we already detached
|
||||
session.SetDestroyed()
|
||||
session.socket.Close()
|
||||
|
||||
// clean up monitor state
|
||||
|
|
@ -1538,8 +1341,6 @@ func (client *Client) destroy(session *Session) {
|
|||
client.server.whoWas.Append(client.WhoWas())
|
||||
}
|
||||
|
||||
client.server.resumeManager.Delete(client)
|
||||
|
||||
// alert monitors
|
||||
if registered {
|
||||
client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false)
|
||||
|
|
@ -1561,20 +1362,8 @@ func (client *Client) destroy(session *Session) {
|
|||
client.server.clients.Remove(client)
|
||||
|
||||
// clean up self
|
||||
client.brbTimer.Disable()
|
||||
|
||||
client.server.accounts.Logout(client)
|
||||
|
||||
// this happens under failure to return from BRB
|
||||
if quitMessage == "" {
|
||||
if brbState == BrbDead && !brbAt.IsZero() {
|
||||
awayMessage := client.AwayMessage()
|
||||
if awayMessage == "" {
|
||||
awayMessage = "Disconnected" // auto-BRB
|
||||
}
|
||||
quitMessage = fmt.Sprintf("%s [%s ago]", awayMessage, time.Since(brbAt).Truncate(time.Second).String())
|
||||
}
|
||||
}
|
||||
if quitMessage == "" {
|
||||
quitMessage = "Exited"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue