This commit is contained in:
Shivaram Lingamneni 2018-05-04 00:24:54 -04:00
parent 00949442e0
commit f6373f7a4d
6 changed files with 179 additions and 63 deletions

View File

@ -690,7 +690,7 @@ func (client *Client) destroy(beingResumed bool) {
client.Quit("Connection closed") client.Quit("Connection closed")
if !beingResumed { if !beingResumed {
client.server.whoWas.Append(client) client.server.whoWas.Append(client.WhoWas())
} }
// remove from connection limits // remove from connection limits

View File

@ -209,6 +209,19 @@ func (client *Client) Channels() (result []*Channel) {
return return
} }
func (client *Client) WhoWas() (result WhoWas) {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
result.nicknameCasefolded = client.nickCasefolded
result.nickname = client.nick
result.username = client.username
result.hostname = client.hostname
result.realname = client.realname
return
}
func (channel *Channel) Name() string { func (channel *Channel) Name() string {
channel.stateMutex.RLock() channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock() defer channel.stateMutex.RUnlock()

View File

@ -2507,27 +2507,29 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
nicknames := strings.Split(msg.Params[0], ",") nicknames := strings.Split(msg.Params[0], ",")
var count int64 // 0 means "all the entries", as does a negative number
var count uint64
if len(msg.Params) > 1 { if len(msg.Params) > 1 {
count, _ = strconv.ParseInt(msg.Params[1], 10, 64) count, _ = strconv.ParseUint(msg.Params[1], 10, 64)
} }
//var target string //var target string
//if len(msg.Params) > 2 { //if len(msg.Params) > 2 {
// target = msg.Params[2] // target = msg.Params[2]
//} //}
cnick := client.Nick()
for _, nickname := range nicknames { for _, nickname := range nicknames {
results := server.whoWas.Find(nickname, count) results := server.whoWas.Find(nickname, int(count))
if len(results) == 0 { if len(results) == 0 {
if len(nickname) > 0 { if len(nickname) > 0 {
rb.Add(nil, server.name, ERR_WASNOSUCHNICK, client.nick, nickname, client.t("There was no such nickname")) rb.Add(nil, server.name, ERR_WASNOSUCHNICK, cnick, nickname, client.t("There was no such nickname"))
} }
} else { } else {
for _, whoWas := range results { for _, whoWas := range results {
rb.Add(nil, server.name, RPL_WHOWASUSER, client.nick, whoWas.nickname, whoWas.username, whoWas.hostname, "*", whoWas.realname) rb.Add(nil, server.name, RPL_WHOWASUSER, cnick, whoWas.nickname, whoWas.username, whoWas.hostname, "*", whoWas.realname)
} }
} }
if len(nickname) > 0 { if len(nickname) > 0 {
rb.Add(nil, server.name, RPL_ENDOFWHOWAS, client.nick, nickname, client.t("End of WHOWAS")) rb.Add(nil, server.name, RPL_ENDOFWHOWAS, cnick, nickname, client.t("End of WHOWAS"))
} }
} }
return false return false

View File

@ -43,8 +43,8 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s
} }
hadNick := target.HasNick() hadNick := target.HasNick()
origNick := target.Nick()
origNickMask := target.NickMaskString() origNickMask := target.NickMaskString()
whowas := client.WhoWas()
err = client.server.clients.SetNick(target, nickname) err = client.server.clients.SetNick(target, nickname)
if err == errNicknameInUse { if err == errNicknameInUse {
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use")) rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use"))
@ -61,8 +61,8 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s
client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick)) client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
if hadNick { if hadNick {
target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname)) target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), whowas.nickname, nickname))
target.server.whoWas.Append(client) target.server.whoWas.Append(whowas)
for friend := range target.Friends() { for friend := range target.Friends() {
friend.Send(nil, origNickMask, "NICK", nickname) friend.Send(nil, origNickMask, "NICK", nickname)
} }

View File

@ -10,11 +10,16 @@ import (
// WhoWasList holds our list of prior clients (for use with the WHOWAS command). // WhoWasList holds our list of prior clients (for use with the WHOWAS command).
type WhoWasList struct { type WhoWasList struct {
buffer []*WhoWas buffer []WhoWas
// three possible states:
// empty: start == end == -1
// partially full: start != end
// full: start == end > 0
// if entries exist, they go from `start` to `(end - 1) % length`
start int start int
end int end int
accessMutex sync.RWMutex // tier 2 accessMutex sync.RWMutex // tier 1
} }
// WhoWas is an entry in the WhoWasList. // WhoWas is an entry in the WhoWasList.
@ -29,77 +34,71 @@ type WhoWas struct {
// NewWhoWasList returns a new WhoWasList // NewWhoWasList returns a new WhoWasList
func NewWhoWasList(size uint) *WhoWasList { func NewWhoWasList(size uint) *WhoWasList {
return &WhoWasList{ return &WhoWasList{
buffer: make([]*WhoWas, size+1), buffer: make([]WhoWas, size),
start: -1,
end: -1,
} }
} }
// Append adds an entry to the WhoWasList. // Append adds an entry to the WhoWasList.
func (list *WhoWasList) Append(client *Client) { func (list *WhoWasList) Append(whowas WhoWas) {
list.accessMutex.Lock() list.accessMutex.Lock()
defer list.accessMutex.Unlock() defer list.accessMutex.Unlock()
list.buffer[list.end] = &WhoWas{ if len(list.buffer) == 0 {
nicknameCasefolded: client.nickCasefolded, return
nickname: client.nick,
username: client.username,
hostname: client.hostname,
realname: client.realname,
} }
var pos int
if list.start == -1 { // empty
pos = 0
list.start = 0
list.end = 1
} else if list.start != list.end { // partially full
pos = list.end
list.end = (list.end + 1) % len(list.buffer) list.end = (list.end + 1) % len(list.buffer)
if list.end == list.start { } else if list.start == list.end { // full
list.start = (list.end + 1) % len(list.buffer) pos = list.end
list.end = (list.end + 1) % len(list.buffer)
list.start = list.end // advance start as well, overwriting first entry
} }
list.buffer[pos] = whowas
} }
// Find tries to find an entry in our WhoWasList with the given details. // Find tries to find an entry in our WhoWasList with the given details.
func (list *WhoWasList) Find(nickname string, limit int64) []*WhoWas { func (list *WhoWasList) Find(nickname string, limit int) (results []WhoWas) {
casefoldedNickname, err := CasefoldName(nickname)
if err != nil {
return
}
list.accessMutex.RLock() list.accessMutex.RLock()
defer list.accessMutex.RUnlock() defer list.accessMutex.RUnlock()
results := make([]*WhoWas, 0) if list.start == -1 {
return
casefoldedNickname, err := CasefoldName(nickname)
if err != nil {
return results
} }
// iterate backwards through the ring buffer
for whoWas := range list.Each() { pos := list.prev(list.end)
if casefoldedNickname != whoWas.nicknameCasefolded { for limit == 0 || len(results) < limit {
continue if casefoldedNickname == list.buffer[pos].nicknameCasefolded {
results = append(results, list.buffer[pos])
} }
results = append(results, whoWas) if pos == list.start {
if int64(len(results)) >= limit {
break break
} }
pos = list.prev(pos)
} }
return results
return
} }
func (list *WhoWasList) prev(index int) int { func (list *WhoWasList) prev(index int) int {
list.accessMutex.RLock() switch index {
defer list.accessMutex.RUnlock() case 0:
return len(list.buffer) - 1
index-- default:
if index < 0 { return index - 1
index += len(list.buffer)
} }
return index
}
// Each iterates the WhoWasList in reverse.
func (list *WhoWasList) Each() <-chan *WhoWas {
ch := make(chan *WhoWas)
go func() {
defer close(ch)
if list.start == list.end {
return
}
start := list.prev(list.end)
end := list.prev(list.start)
for start != end {
ch <- list.buffer[start]
start = list.prev(start)
}
}()
return ch
} }

102
irc/whowas_test.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"testing"
)
func makeTestWhowas(nick string) WhoWas {
cfnick, err := CasefoldName(nick)
if err != nil {
panic(err)
}
return WhoWas{
nicknameCasefolded: cfnick,
nickname: nick,
username: "user",
hostname: "oragono.io",
realname: "Real Name",
}
}
func TestWhoWas(t *testing.T) {
var results []WhoWas
wwl := NewWhoWasList(3)
// test Find on empty list
results = wwl.Find("nobody", 10)
if len(results) != 0 {
t.Fatalf("incorrect whowas results: %v", results)
}
wwl.Append(makeTestWhowas("dan-"))
results = wwl.Find("nobody", 10)
if len(results) != 0 {
t.Fatalf("incorrect whowas results: %v", results)
}
results = wwl.Find("dan-", 10)
if len(results) != 1 || results[0].nickname != "dan-" {
t.Fatalf("incorrect whowas results: %v", results)
}
wwl.Append(makeTestWhowas("slingamn"))
results = wwl.Find("slingamN", 10)
if len(results) != 1 || results[0].nickname != "slingamn" {
t.Fatalf("incorrect whowas results: %v", results)
}
wwl.Append(makeTestWhowas("Dan-"))
results = wwl.Find("dan-", 10)
// reverse chronological order
if len(results) != 2 || results[0].nickname != "Dan-" || results[1].nickname != "dan-" {
t.Fatalf("incorrect whowas results: %v", results)
}
// 0 means no limit
results = wwl.Find("dan-", 0)
if len(results) != 2 || results[0].nickname != "Dan-" || results[1].nickname != "dan-" {
t.Fatalf("incorrect whowas results: %v", results)
}
// a limit of 1 should return the most recent entry only
results = wwl.Find("dan-", 1)
if len(results) != 1 || results[0].nickname != "Dan-" {
t.Fatalf("incorrect whowas results: %v", results)
}
wwl.Append(makeTestWhowas("moocow"))
results = wwl.Find("moocow", 10)
if len(results) != 1 || results[0].nickname != "moocow" {
t.Fatalf("incorrect whowas results: %v", results)
}
results = wwl.Find("dan-", 10)
// should have overwritten the original entry, leaving the second
if len(results) != 1 || results[0].nickname != "Dan-" {
t.Fatalf("incorrect whowas results: %v", results)
}
// overwrite the second entry
wwl.Append(makeTestWhowas("enckse"))
results = wwl.Find("enckse", 10)
if len(results) != 1 || results[0].nickname != "enckse" {
t.Fatalf("incorrect whowas results: %v", results)
}
results = wwl.Find("slingamn", 10)
if len(results) != 0 {
t.Fatalf("incorrect whowas results: %v", results)
}
}
func TestEmptyWhoWas(t *testing.T) {
// stupid edge case; setting an empty whowas buffer should not panic
wwl := NewWhoWasList(0)
results := wwl.Find("slingamn", 10)
if len(results) != 0 {
t.Fatalf("incorrect whowas results: %v", results)
}
wwl.Append(makeTestWhowas("slingamn"))
results = wwl.Find("slingamn", 10)
if len(results) != 0 {
t.Fatalf("incorrect whowas results: %v", results)
}
}