mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-26 22:09:24 +01:00
commit
477a9023ff
7
Makefile
7
Makefile
@ -1,5 +1,7 @@
|
|||||||
.PHONY: all build
|
.PHONY: all build
|
||||||
|
|
||||||
|
capdef_file = ./irc/caps/defs.go
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@ -8,11 +10,16 @@ build:
|
|||||||
buildrelease:
|
buildrelease:
|
||||||
goreleaser --skip-publish --rm-dist
|
goreleaser --skip-publish --rm-dist
|
||||||
|
|
||||||
|
capdefs:
|
||||||
|
python3 ./gencapdefs.py > ${capdef_file}
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
python3 ./gencapdefs.py | diff - ${capdef_file}
|
||||||
cd irc && go test . && go vet .
|
cd irc && go test . && go vet .
|
||||||
|
cd irc/caps && go test . && go vet .
|
||||||
cd irc/isupport && go test . && go vet .
|
cd irc/isupport && go test . && go vet .
|
||||||
cd irc/modes && go test . && go vet .
|
cd irc/modes && go test . && go vet .
|
||||||
cd irc/utils && go test . && go vet .
|
cd irc/utils && go test . && go vet .
|
||||||
|
200
gencapdefs.py
Normal file
200
gencapdefs.py
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Updates the capability definitions at irc/caps/defs.go
|
||||||
|
|
||||||
|
To add a capability, add it to the CAPDEFS list below,
|
||||||
|
then run `make capdefs` from the project root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
CapDef = namedtuple("CapDef", ['identifier', 'name', 'url', 'standard'])
|
||||||
|
|
||||||
|
CAPDEFS = [
|
||||||
|
CapDef(
|
||||||
|
identifier="AccountNotify",
|
||||||
|
name="account-notify",
|
||||||
|
url="https://ircv3.net/specs/extensions/account-notify-3.1.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="AccountTag",
|
||||||
|
name="account-tag",
|
||||||
|
url="https://ircv3.net/specs/extensions/account-tag-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="AwayNotify",
|
||||||
|
name="away-notify",
|
||||||
|
url="https://ircv3.net/specs/extensions/away-notify-3.1.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="Batch",
|
||||||
|
name="batch",
|
||||||
|
url="https://ircv3.net/specs/extensions/batch-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="CapNotify",
|
||||||
|
name="cap-notify",
|
||||||
|
url="https://ircv3.net/specs/extensions/cap-notify-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="ChgHost",
|
||||||
|
name="chghost",
|
||||||
|
url="https://ircv3.net/specs/extensions/chghost-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="EchoMessage",
|
||||||
|
name="echo-message",
|
||||||
|
url="https://ircv3.net/specs/extensions/echo-message-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="ExtendedJoin",
|
||||||
|
name="extended-join",
|
||||||
|
url="https://ircv3.net/specs/extensions/extended-join-3.1.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="InviteNotify",
|
||||||
|
name="invite-notify",
|
||||||
|
url="https://ircv3.net/specs/extensions/invite-notify-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="LabeledResponse",
|
||||||
|
name="draft/labeled-response",
|
||||||
|
url="https://ircv3.net/specs/extensions/labeled-response.html",
|
||||||
|
standard="draft IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="Languages",
|
||||||
|
name="draft/languages",
|
||||||
|
url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
|
||||||
|
standard="proposed IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="MaxLine",
|
||||||
|
name="oragono.io/maxline",
|
||||||
|
url="https://oragono.io/maxline",
|
||||||
|
standard="Oragono-specific",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="MessageTags",
|
||||||
|
name="draft/message-tags-0.2",
|
||||||
|
url="https://ircv3.net/specs/core/message-tags-3.3.html",
|
||||||
|
standard="draft IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="MultiPrefix",
|
||||||
|
name="multi-prefix",
|
||||||
|
url="https://ircv3.net/specs/extensions/multi-prefix-3.1.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="Rename",
|
||||||
|
name="draft/rename",
|
||||||
|
url="https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md",
|
||||||
|
standard="proposed IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="Resume",
|
||||||
|
name="draft/resume",
|
||||||
|
url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md",
|
||||||
|
standard="proposed IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="SASL",
|
||||||
|
name="sasl",
|
||||||
|
url="https://ircv3.net/specs/extensions/sasl-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="ServerTime",
|
||||||
|
name="server-time",
|
||||||
|
url="https://ircv3.net/specs/extensions/server-time-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="STS",
|
||||||
|
name="sts",
|
||||||
|
url="https://ircv3.net/specs/extensions/sts.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
CapDef(
|
||||||
|
identifier="UserhostInNames",
|
||||||
|
name="userhost-in-names",
|
||||||
|
url="https://ircv3.net/specs/extensions/userhost-in-names-3.2.html",
|
||||||
|
standard="IRCv3",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate_defs():
|
||||||
|
numCaps = len(CAPDEFS)
|
||||||
|
numNames = len(set(capdef.name for capdef in CAPDEFS))
|
||||||
|
if numCaps != numNames:
|
||||||
|
raise Exception("defs must have unique names, but found duplicates")
|
||||||
|
numIdentifiers = len(set(capdef.identifier for capdef in CAPDEFS))
|
||||||
|
if numCaps != numIdentifiers:
|
||||||
|
raise Exception("defs must have unique identifiers, but found duplicates")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
validate_defs()
|
||||||
|
output = io.StringIO()
|
||||||
|
print("""
|
||||||
|
package caps
|
||||||
|
|
||||||
|
/*
|
||||||
|
WARNING: this file is autogenerated by `make capdefs`
|
||||||
|
DO NOT EDIT MANUALLY.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
""", file=output)
|
||||||
|
|
||||||
|
|
||||||
|
numCapabs = len(CAPDEFS)
|
||||||
|
bitsetLen = numCapabs // 64
|
||||||
|
if numCapabs % 64 > 0:
|
||||||
|
bitsetLen += 1
|
||||||
|
print ("""
|
||||||
|
const (
|
||||||
|
// number of recognized capabilities:
|
||||||
|
numCapabs = %d
|
||||||
|
// length of the uint64 array that represents the bitset:
|
||||||
|
bitsetLen = %d
|
||||||
|
)
|
||||||
|
""" % (numCapabs, bitsetLen), file=output)
|
||||||
|
|
||||||
|
print("const (", file=output)
|
||||||
|
for capdef in CAPDEFS:
|
||||||
|
print("// %s is the %s capability named \"%s\":" % (capdef.identifier, capdef.standard, capdef.name), file=output)
|
||||||
|
print("// %s" % (capdef.url,), file=output)
|
||||||
|
print("%s Capability = iota" % (capdef.identifier,), file=output)
|
||||||
|
print(file=output)
|
||||||
|
print(")", file=output)
|
||||||
|
|
||||||
|
print("// `capabilityNames[capab]` is the string name of the capability `capab`", file=output)
|
||||||
|
print("""var ( capabilityNames = [numCapabs]string{""", file=output)
|
||||||
|
for capdef in CAPDEFS:
|
||||||
|
print("\"%s\"," % (capdef.name,), file=output)
|
||||||
|
print("})", file=output)
|
||||||
|
|
||||||
|
# run the generated code through `gofmt -s`, which will print it to stdout
|
||||||
|
gofmt = subprocess.Popen(['gofmt', '-s'], stdin=subprocess.PIPE)
|
||||||
|
gofmt.communicate(input=output.getvalue().encode('utf-8'))
|
||||||
|
if gofmt.poll() != 0:
|
||||||
|
print(output.getvalue())
|
||||||
|
raise Exception("gofmt failed")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
@ -3,58 +3,30 @@
|
|||||||
|
|
||||||
package caps
|
package caps
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
// Capability represents an optional feature that a client may request from the server.
|
// Capability represents an optional feature that a client may request from the server.
|
||||||
type Capability string
|
type Capability uint
|
||||||
|
|
||||||
const (
|
// actual capability definitions appear in defs.go
|
||||||
// LabelTagName is the tag name used for the labeled-response spec.
|
|
||||||
LabelTagName = "draft/label"
|
|
||||||
|
|
||||||
// AccountNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/account-notify-3.1.html
|
var (
|
||||||
AccountNotify Capability = "account-notify"
|
nameToCapability map[string]Capability
|
||||||
// AccountTag is this IRCv3 capability: http://ircv3.net/specs/extensions/account-tag-3.2.html
|
|
||||||
AccountTag Capability = "account-tag"
|
NoSuchCap = errors.New("Unsupported capability name")
|
||||||
// AwayNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/away-notify-3.1.html
|
|
||||||
AwayNotify Capability = "away-notify"
|
|
||||||
// Batch is this IRCv3 capability: http://ircv3.net/specs/extensions/batch-3.2.html
|
|
||||||
Batch Capability = "batch"
|
|
||||||
// CapNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/cap-notify-3.2.html
|
|
||||||
CapNotify Capability = "cap-notify"
|
|
||||||
// ChgHost is this IRCv3 capability: http://ircv3.net/specs/extensions/chghost-3.2.html
|
|
||||||
ChgHost Capability = "chghost"
|
|
||||||
// EchoMessage is this IRCv3 capability: http://ircv3.net/specs/extensions/echo-message-3.2.html
|
|
||||||
EchoMessage Capability = "echo-message"
|
|
||||||
// ExtendedJoin is this IRCv3 capability: http://ircv3.net/specs/extensions/extended-join-3.1.html
|
|
||||||
ExtendedJoin Capability = "extended-join"
|
|
||||||
// InviteNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/invite-notify-3.2.html
|
|
||||||
InviteNotify Capability = "invite-notify"
|
|
||||||
// LabeledResponse is this draft IRCv3 capability: http://ircv3.net/specs/extensions/labeled-response.html
|
|
||||||
LabeledResponse Capability = "draft/labeled-response"
|
|
||||||
// Languages is this proposed IRCv3 capability: https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
|
||||||
Languages Capability = "draft/languages"
|
|
||||||
// MaxLine is this capability: https://oragono.io/maxline
|
|
||||||
MaxLine Capability = "oragono.io/maxline"
|
|
||||||
// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
|
|
||||||
MessageTags Capability = "draft/message-tags-0.2"
|
|
||||||
// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
|
|
||||||
MultiPrefix Capability = "multi-prefix"
|
|
||||||
// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
|
|
||||||
Rename Capability = "draft/rename"
|
|
||||||
// Resume is this proposed capability: https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
|
|
||||||
Resume Capability = "draft/resume"
|
|
||||||
// SASL is this IRCv3 capability: http://ircv3.net/specs/extensions/sasl-3.2.html
|
|
||||||
SASL Capability = "sasl"
|
|
||||||
// ServerTime is this IRCv3 capability: http://ircv3.net/specs/extensions/server-time-3.2.html
|
|
||||||
ServerTime Capability = "server-time"
|
|
||||||
// STS is this IRCv3 capability: http://ircv3.net/specs/extensions/sts.html
|
|
||||||
STS Capability = "sts"
|
|
||||||
// UserhostInNames is this IRCv3 capability: http://ircv3.net/specs/extensions/userhost-in-names-3.2.html
|
|
||||||
UserhostInNames Capability = "userhost-in-names"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name returns the name of the given capability.
|
// Name returns the name of the given capability.
|
||||||
func (capability Capability) Name() string {
|
func (capability Capability) Name() string {
|
||||||
return string(capability)
|
return capabilityNames[capability]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NameToCapability(name string) (result Capability, err error) {
|
||||||
|
result, found := nameToCapability[name]
|
||||||
|
if !found {
|
||||||
|
err = NoSuchCap
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version is used to select which max version of CAP the client supports.
|
// Version is used to select which max version of CAP the client supports.
|
||||||
@ -78,3 +50,16 @@ const (
|
|||||||
// NegotiatedState means CAP negotiation has been successfully ended and reg should complete.
|
// NegotiatedState means CAP negotiation has been successfully ended and reg should complete.
|
||||||
NegotiatedState State = iota
|
NegotiatedState State = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LabelTagName is the tag name used for the labeled-response spec.
|
||||||
|
// https://ircv3.net/specs/extensions/labeled-response.html
|
||||||
|
LabelTagName = "draft/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
nameToCapability = make(map[string]Capability)
|
||||||
|
for capab, name := range capabilityNames {
|
||||||
|
nameToCapability[name] = Capability(capab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
121
irc/caps/defs.go
Normal file
121
irc/caps/defs.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package caps
|
||||||
|
|
||||||
|
/*
|
||||||
|
WARNING: this file is autogenerated by `make capdefs`
|
||||||
|
DO NOT EDIT MANUALLY.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
// number of recognized capabilities:
|
||||||
|
numCapabs = 20
|
||||||
|
// length of the uint64 array that represents the bitset:
|
||||||
|
bitsetLen = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccountNotify is the IRCv3 capability named "account-notify":
|
||||||
|
// https://ircv3.net/specs/extensions/account-notify-3.1.html
|
||||||
|
AccountNotify Capability = iota
|
||||||
|
|
||||||
|
// AccountTag is the IRCv3 capability named "account-tag":
|
||||||
|
// https://ircv3.net/specs/extensions/account-tag-3.2.html
|
||||||
|
AccountTag Capability = iota
|
||||||
|
|
||||||
|
// AwayNotify is the IRCv3 capability named "away-notify":
|
||||||
|
// https://ircv3.net/specs/extensions/away-notify-3.1.html
|
||||||
|
AwayNotify Capability = iota
|
||||||
|
|
||||||
|
// Batch is the IRCv3 capability named "batch":
|
||||||
|
// https://ircv3.net/specs/extensions/batch-3.2.html
|
||||||
|
Batch Capability = iota
|
||||||
|
|
||||||
|
// CapNotify is the IRCv3 capability named "cap-notify":
|
||||||
|
// https://ircv3.net/specs/extensions/cap-notify-3.2.html
|
||||||
|
CapNotify Capability = iota
|
||||||
|
|
||||||
|
// ChgHost is the IRCv3 capability named "chghost":
|
||||||
|
// https://ircv3.net/specs/extensions/chghost-3.2.html
|
||||||
|
ChgHost Capability = iota
|
||||||
|
|
||||||
|
// EchoMessage is the IRCv3 capability named "echo-message":
|
||||||
|
// https://ircv3.net/specs/extensions/echo-message-3.2.html
|
||||||
|
EchoMessage Capability = iota
|
||||||
|
|
||||||
|
// ExtendedJoin is the IRCv3 capability named "extended-join":
|
||||||
|
// https://ircv3.net/specs/extensions/extended-join-3.1.html
|
||||||
|
ExtendedJoin Capability = iota
|
||||||
|
|
||||||
|
// InviteNotify is the IRCv3 capability named "invite-notify":
|
||||||
|
// https://ircv3.net/specs/extensions/invite-notify-3.2.html
|
||||||
|
InviteNotify Capability = iota
|
||||||
|
|
||||||
|
// LabeledResponse is the draft IRCv3 capability named "draft/labeled-response":
|
||||||
|
// https://ircv3.net/specs/extensions/labeled-response.html
|
||||||
|
LabeledResponse Capability = iota
|
||||||
|
|
||||||
|
// Languages is the proposed IRCv3 capability named "draft/languages":
|
||||||
|
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
||||||
|
Languages Capability = iota
|
||||||
|
|
||||||
|
// MaxLine is the Oragono-specific capability named "oragono.io/maxline":
|
||||||
|
// https://oragono.io/maxline
|
||||||
|
MaxLine Capability = iota
|
||||||
|
|
||||||
|
// MessageTags is the draft IRCv3 capability named "draft/message-tags-0.2":
|
||||||
|
// https://ircv3.net/specs/core/message-tags-3.3.html
|
||||||
|
MessageTags Capability = iota
|
||||||
|
|
||||||
|
// MultiPrefix is the IRCv3 capability named "multi-prefix":
|
||||||
|
// https://ircv3.net/specs/extensions/multi-prefix-3.1.html
|
||||||
|
MultiPrefix Capability = iota
|
||||||
|
|
||||||
|
// Rename is the proposed IRCv3 capability named "draft/rename":
|
||||||
|
// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
|
||||||
|
Rename Capability = iota
|
||||||
|
|
||||||
|
// Resume is the proposed IRCv3 capability named "draft/resume":
|
||||||
|
// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
|
||||||
|
Resume Capability = iota
|
||||||
|
|
||||||
|
// SASL is the IRCv3 capability named "sasl":
|
||||||
|
// https://ircv3.net/specs/extensions/sasl-3.2.html
|
||||||
|
SASL Capability = iota
|
||||||
|
|
||||||
|
// ServerTime is the IRCv3 capability named "server-time":
|
||||||
|
// https://ircv3.net/specs/extensions/server-time-3.2.html
|
||||||
|
ServerTime Capability = iota
|
||||||
|
|
||||||
|
// STS is the IRCv3 capability named "sts":
|
||||||
|
// https://ircv3.net/specs/extensions/sts.html
|
||||||
|
STS Capability = iota
|
||||||
|
|
||||||
|
// UserhostInNames is the IRCv3 capability named "userhost-in-names":
|
||||||
|
// https://ircv3.net/specs/extensions/userhost-in-names-3.2.html
|
||||||
|
UserhostInNames Capability = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// `capabilityNames[capab]` is the string name of the capability `capab`
|
||||||
|
var (
|
||||||
|
capabilityNames = [numCapabs]string{
|
||||||
|
"account-notify",
|
||||||
|
"account-tag",
|
||||||
|
"away-notify",
|
||||||
|
"batch",
|
||||||
|
"cap-notify",
|
||||||
|
"chghost",
|
||||||
|
"echo-message",
|
||||||
|
"extended-join",
|
||||||
|
"invite-notify",
|
||||||
|
"draft/labeled-response",
|
||||||
|
"draft/languages",
|
||||||
|
"oragono.io/maxline",
|
||||||
|
"draft/message-tags-0.2",
|
||||||
|
"multi-prefix",
|
||||||
|
"draft/rename",
|
||||||
|
"draft/resume",
|
||||||
|
"sasl",
|
||||||
|
"server-time",
|
||||||
|
"sts",
|
||||||
|
"userhost-in-names",
|
||||||
|
}
|
||||||
|
)
|
@ -6,43 +6,34 @@ package caps
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set holds a set of enabled capabilities.
|
// Set holds a set of enabled capabilities.
|
||||||
type Set struct {
|
type Set [bitsetLen]uint64
|
||||||
sync.RWMutex
|
|
||||||
// capabilities holds the capabilities this manager has.
|
|
||||||
capabilities map[Capability]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSet returns a new Set, with the given capabilities enabled.
|
// NewSet returns a new Set, with the given capabilities enabled.
|
||||||
func NewSet(capabs ...Capability) *Set {
|
func NewSet(capabs ...Capability) *Set {
|
||||||
newSet := Set{
|
var newSet Set
|
||||||
capabilities: make(map[Capability]bool),
|
utils.BitsetInitialize(newSet[:])
|
||||||
}
|
|
||||||
newSet.Enable(capabs...)
|
newSet.Enable(capabs...)
|
||||||
|
|
||||||
return &newSet
|
return &newSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable enables the given capabilities.
|
// Enable enables the given capabilities.
|
||||||
func (s *Set) Enable(capabs ...Capability) {
|
func (s *Set) Enable(capabs ...Capability) {
|
||||||
s.Lock()
|
asSlice := s[:]
|
||||||
defer s.Unlock()
|
|
||||||
|
|
||||||
for _, capab := range capabs {
|
for _, capab := range capabs {
|
||||||
s.capabilities[capab] = true
|
utils.BitsetSet(asSlice, uint(capab), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable disables the given capabilities.
|
// Disable disables the given capabilities.
|
||||||
func (s *Set) Disable(capabs ...Capability) {
|
func (s *Set) Disable(capabs ...Capability) {
|
||||||
s.Lock()
|
asSlice := s[:]
|
||||||
defer s.Unlock()
|
|
||||||
|
|
||||||
for _, capab := range capabs {
|
for _, capab := range capabs {
|
||||||
delete(s.capabilities, capab)
|
utils.BitsetSet(asSlice, uint(capab), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,51 +49,35 @@ func (s *Set) Remove(capabs ...Capability) {
|
|||||||
s.Disable(capabs...)
|
s.Disable(capabs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns true if this set has the given capabilities.
|
// Has returns true if this set has the given capability.
|
||||||
func (s *Set) Has(caps ...Capability) bool {
|
func (s *Set) Has(capab Capability) bool {
|
||||||
s.RLock()
|
return utils.BitsetGet(s[:], uint(capab))
|
||||||
defer s.RUnlock()
|
|
||||||
|
|
||||||
for _, cap := range caps {
|
|
||||||
if !s.capabilities[cap] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List return a list of our enabled capabilities.
|
// Union adds all the capabilities of another set to this set.
|
||||||
func (s *Set) List() []Capability {
|
func (s *Set) Union(other *Set) {
|
||||||
s.RLock()
|
utils.BitsetUnion(s[:], other[:])
|
||||||
defer s.RUnlock()
|
|
||||||
|
|
||||||
var allCaps []Capability
|
|
||||||
for capab := range s.capabilities {
|
|
||||||
allCaps = append(allCaps, capab)
|
|
||||||
}
|
|
||||||
|
|
||||||
return allCaps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns how many enabled caps this set has.
|
// Empty returns whether the set is empty.
|
||||||
func (s *Set) Count() int {
|
func (s *Set) Empty() bool {
|
||||||
s.RLock()
|
return utils.BitsetEmpty(s[:])
|
||||||
defer s.RUnlock()
|
|
||||||
|
|
||||||
return len(s.capabilities)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns all of our enabled capabilities as a string.
|
// String returns all of our enabled capabilities as a string.
|
||||||
func (s *Set) String(version Version, values *Values) string {
|
func (s *Set) String(version Version, values *Values) string {
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
|
|
||||||
var strs sort.StringSlice
|
var strs sort.StringSlice
|
||||||
|
|
||||||
for capability := range s.capabilities {
|
var capab Capability
|
||||||
capString := capability.Name()
|
asSlice := s[:]
|
||||||
|
for capab = 0; capab < numCapabs; capab++ {
|
||||||
|
// skip any capabilities that are not enabled
|
||||||
|
if !utils.BitsetGet(asSlice, uint(capab)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
capString := capab.Name()
|
||||||
if version == Cap302 {
|
if version == Cap302 {
|
||||||
val, exists := values.Get(capability)
|
val, exists := values.Get(capab)
|
||||||
if exists {
|
if exists {
|
||||||
capString += "=" + val
|
capString += "=" + val
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,12 @@ func TestSets(t *testing.T) {
|
|||||||
|
|
||||||
s1.Enable(AccountTag, EchoMessage, UserhostInNames)
|
s1.Enable(AccountTag, EchoMessage, UserhostInNames)
|
||||||
|
|
||||||
if !s1.Has(AccountTag, EchoMessage, UserhostInNames) {
|
if !(s1.Has(AccountTag) && s1.Has(EchoMessage) && s1.Has(UserhostInNames)) {
|
||||||
t.Error("Did not have the tags we expected")
|
t.Error("Did not have the tags we expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s1.Has(AccountTag, EchoMessage, STS, UserhostInNames) {
|
if s1.Has(STS) {
|
||||||
t.Error("Has() returned true when we don't have all the given capabilities")
|
t.Error("Has() returned true when we don't have the given capability")
|
||||||
}
|
}
|
||||||
|
|
||||||
s1.Disable(AccountTag)
|
s1.Disable(AccountTag)
|
||||||
@ -25,14 +25,9 @@ func TestSets(t *testing.T) {
|
|||||||
t.Error("Disable() did not correctly disable the given capability")
|
t.Error("Disable() did not correctly disable the given capability")
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledCaps := make(map[Capability]bool)
|
enabledCaps := NewSet()
|
||||||
for _, capab := range s1.List() {
|
enabledCaps.Union(s1)
|
||||||
enabledCaps[capab] = true
|
expectedCaps := NewSet(EchoMessage, UserhostInNames)
|
||||||
}
|
|
||||||
expectedCaps := map[Capability]bool{
|
|
||||||
EchoMessage: true,
|
|
||||||
UserhostInNames: true,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(enabledCaps, expectedCaps) {
|
if !reflect.DeepEqual(enabledCaps, expectedCaps) {
|
||||||
t.Errorf("Enabled and expected capability lists do not match: %v, %v", enabledCaps, expectedCaps)
|
t.Errorf("Enabled and expected capability lists do not match: %v, %v", enabledCaps, expectedCaps)
|
||||||
}
|
}
|
||||||
@ -40,16 +35,12 @@ func TestSets(t *testing.T) {
|
|||||||
// make sure re-enabling doesn't add to the count or something weird like that
|
// make sure re-enabling doesn't add to the count or something weird like that
|
||||||
s1.Enable(EchoMessage)
|
s1.Enable(EchoMessage)
|
||||||
|
|
||||||
if s1.Count() != 2 {
|
|
||||||
t.Error("Count() did not match expected capability count")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure add and remove work fine
|
// make sure add and remove work fine
|
||||||
s1.Add(InviteNotify)
|
s1.Add(InviteNotify)
|
||||||
s1.Remove(EchoMessage)
|
s1.Remove(EchoMessage)
|
||||||
|
|
||||||
if s1.Count() != 2 {
|
if !s1.Has(InviteNotify) || s1.Has(EchoMessage) {
|
||||||
t.Error("Count() did not match expected capability count")
|
t.Error("Add/Remove don't work")
|
||||||
}
|
}
|
||||||
|
|
||||||
// test String()
|
// test String()
|
||||||
@ -68,3 +59,24 @@ func TestSets(t *testing.T) {
|
|||||||
t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString)
|
t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkSetReads(b *testing.B) {
|
||||||
|
set := NewSet(UserhostInNames, EchoMessage)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
set.Has(UserhostInNames)
|
||||||
|
set.Has(LabeledResponse)
|
||||||
|
set.Has(EchoMessage)
|
||||||
|
set.Has(Rename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSetWrites(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
set := NewSet(UserhostInNames, EchoMessage)
|
||||||
|
set.Add(Rename)
|
||||||
|
set.Add(ExtendedJoin)
|
||||||
|
set.Remove(UserhostInNames)
|
||||||
|
set.Remove(LabeledResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -442,12 +442,16 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
capabilities := caps.NewSet()
|
capabilities := caps.NewSet()
|
||||||
var capString string
|
var capString string
|
||||||
|
|
||||||
|
var badCaps []string
|
||||||
if len(msg.Params) > 1 {
|
if len(msg.Params) > 1 {
|
||||||
capString = msg.Params[1]
|
capString = msg.Params[1]
|
||||||
strs := strings.Split(capString, " ")
|
strs := strings.Fields(capString)
|
||||||
for _, str := range strs {
|
for _, str := range strs {
|
||||||
if len(str) > 0 {
|
capab, err := caps.NameToCapability(str)
|
||||||
capabilities.Enable(caps.Capability(str))
|
if err != nil || !SupportedCapabilities.Has(capab) {
|
||||||
|
badCaps = append(badCaps, str)
|
||||||
|
} else {
|
||||||
|
capabilities.Enable(capab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -475,13 +479,11 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure all capabilities actually exist
|
// make sure all capabilities actually exist
|
||||||
for _, capability := range capabilities.List() {
|
if len(badCaps) > 0 {
|
||||||
if !SupportedCapabilities.Has(capability) {
|
rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
|
||||||
rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
|
return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
client.capabilities.Enable(capabilities.List()...)
|
client.capabilities.Union(capabilities)
|
||||||
rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString)
|
rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString)
|
||||||
|
|
||||||
case "END":
|
case "END":
|
||||||
|
@ -7,7 +7,9 @@ package modes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -322,42 +324,29 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ModeSet holds a set of modes.
|
// ModeSet holds a set of modes.
|
||||||
type ModeSet struct {
|
type ModeSet [1]uint64
|
||||||
sync.RWMutex // tier 0
|
|
||||||
modes map[Mode]bool
|
// valid modes go from 65 ('A') to 122 ('z'), making at most 58 possible values;
|
||||||
}
|
// subtract 65 from the mode value and use that bit of the uint64 to represent it
|
||||||
|
const (
|
||||||
|
minMode = 65 // 'A'
|
||||||
|
)
|
||||||
|
|
||||||
// returns a pointer to a new ModeSet
|
// returns a pointer to a new ModeSet
|
||||||
func NewModeSet() *ModeSet {
|
func NewModeSet() *ModeSet {
|
||||||
return &ModeSet{
|
var set ModeSet
|
||||||
modes: make(map[Mode]bool),
|
utils.BitsetInitialize(set[:])
|
||||||
}
|
return &set
|
||||||
}
|
}
|
||||||
|
|
||||||
// test whether `mode` is set
|
// test whether `mode` is set
|
||||||
func (set *ModeSet) HasMode(mode Mode) bool {
|
func (set *ModeSet) HasMode(mode Mode) bool {
|
||||||
if set == nil {
|
return utils.BitsetGet(set[:], uint(mode)-minMode)
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
set.RLock()
|
|
||||||
defer set.RUnlock()
|
|
||||||
return set.modes[mode]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set `mode` to be on or off, return whether the value actually changed
|
// set `mode` to be on or off, return whether the value actually changed
|
||||||
func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
|
func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
|
||||||
set.Lock()
|
return utils.BitsetSet(set[:], uint(mode)-minMode, on)
|
||||||
defer set.Unlock()
|
|
||||||
|
|
||||||
previouslyOn := set.modes[mode]
|
|
||||||
needsApply := (on != previouslyOn)
|
|
||||||
if on && needsApply {
|
|
||||||
set.modes[mode] = true
|
|
||||||
} else if !on && needsApply {
|
|
||||||
delete(set.modes, mode)
|
|
||||||
}
|
|
||||||
return needsApply
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the modes in the set as a slice
|
// return the modes in the set as a slice
|
||||||
@ -366,11 +355,12 @@ func (set *ModeSet) AllModes() (result []Mode) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
set.RLock()
|
block := atomic.LoadUint64(&set[0])
|
||||||
defer set.RUnlock()
|
var i uint
|
||||||
|
for i = 0; i < 64; i++ {
|
||||||
for mode := range set.modes {
|
if block&(1<<i) != 0 {
|
||||||
result = append(result, mode)
|
result = append(result, Mode(minMode+i))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -381,11 +371,8 @@ func (set *ModeSet) String() (result string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
set.RLock()
|
|
||||||
defer set.RUnlock()
|
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
for mode := range set.modes {
|
for _, mode := range set.AllModes() {
|
||||||
buf.WriteRune(rune(mode))
|
buf.WriteRune(rune(mode))
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
@ -397,12 +384,9 @@ func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
set.RLock()
|
|
||||||
defer set.RUnlock()
|
|
||||||
|
|
||||||
// add prefixes in order from highest to lowest privs
|
// add prefixes in order from highest to lowest privs
|
||||||
for _, mode := range ChannelUserModes {
|
for _, mode := range ChannelUserModes {
|
||||||
if set.modes[mode] {
|
if set.HasMode(mode) {
|
||||||
prefixes += ChannelModePrefixes[mode]
|
prefixes += ChannelModePrefixes[mode]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -898,13 +898,11 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
|
|
||||||
// updated caps get DEL'd and then NEW'd
|
// updated caps get DEL'd and then NEW'd
|
||||||
// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
|
// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
|
||||||
server.logger.Debug("rehash", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues), strconv.Itoa(updatedCaps.Count()))
|
server.logger.Debug("rehash", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues))
|
||||||
for _, capab := range updatedCaps.List() {
|
addedCaps.Union(updatedCaps)
|
||||||
addedCaps.Enable(capab)
|
removedCaps.Union(updatedCaps)
|
||||||
removedCaps.Enable(capab)
|
|
||||||
}
|
|
||||||
|
|
||||||
if 0 < addedCaps.Count() || 0 < removedCaps.Count() {
|
if !addedCaps.Empty() || !removedCaps.Empty() {
|
||||||
capBurstClients = server.clients.AllWithCaps(caps.CapNotify)
|
capBurstClients = server.clients.AllWithCaps(caps.CapNotify)
|
||||||
|
|
||||||
added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
|
added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
|
||||||
@ -918,7 +916,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
// remove STS policy
|
// remove STS policy
|
||||||
//TODO(dan): this is an ugly hack. we can write this better.
|
//TODO(dan): this is an ugly hack. we can write this better.
|
||||||
stsPolicy := "sts=duration=0"
|
stsPolicy := "sts=duration=0"
|
||||||
if 0 < addedCaps.Count() {
|
if !addedCaps.Empty() {
|
||||||
added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
|
added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
|
||||||
} else {
|
} else {
|
||||||
addedCaps.Enable(caps.STS)
|
addedCaps.Enable(caps.STS)
|
||||||
@ -926,10 +924,10 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DEL caps and then send NEW ones so that updated caps get removed/added correctly
|
// DEL caps and then send NEW ones so that updated caps get removed/added correctly
|
||||||
if 0 < removedCaps.Count() {
|
if !removedCaps.Empty() {
|
||||||
sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed)
|
sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed)
|
||||||
}
|
}
|
||||||
if 0 < addedCaps.Count() {
|
if !addedCaps.Empty() {
|
||||||
sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion])
|
sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
irc/utils/bitset.go
Normal file
86
irc/utils/bitset.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
// Library functions for lock-free bitsets, typically (constant-sized) arrays of uint64.
|
||||||
|
// For examples of use, see caps.Set and modes.ModeSet; the array has to be converted to a
|
||||||
|
// slice to use these functions.
|
||||||
|
|
||||||
|
// BitsetInitialize initializes a bitset.
|
||||||
|
func BitsetInitialize(set []uint64) {
|
||||||
|
// XXX re-zero the bitset using atomic stores. it's unclear whether this is required,
|
||||||
|
// however, golang issue #5045 suggests that you shouldn't mix atomic operations
|
||||||
|
// with non-atomic operations (such as the runtime's automatic zero-initialization) on
|
||||||
|
// the same word
|
||||||
|
for i := 0; i < len(set); i++ {
|
||||||
|
atomic.StoreUint64(&set[i], 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitsetGet returns whether a given bit of the bitset is set.
|
||||||
|
func BitsetGet(set []uint64, position uint) bool {
|
||||||
|
idx := position / 64
|
||||||
|
bit := position % 64
|
||||||
|
block := atomic.LoadUint64(&set[idx])
|
||||||
|
return (block & (1 << bit)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitsetSet sets a given bit of the bitset to 0 or 1, returning whether it changed.
|
||||||
|
func BitsetSet(set []uint64, position uint, on bool) (changed bool) {
|
||||||
|
idx := position / 64
|
||||||
|
bit := position % 64
|
||||||
|
addr := &set[idx]
|
||||||
|
var mask uint64
|
||||||
|
mask = 1 << bit
|
||||||
|
for {
|
||||||
|
current := atomic.LoadUint64(addr)
|
||||||
|
previouslyOn := (current & mask) != 0
|
||||||
|
if on == previouslyOn {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var desired uint64
|
||||||
|
if on {
|
||||||
|
desired = current | mask
|
||||||
|
} else {
|
||||||
|
desired = current & (^mask)
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapUint64(addr, current, desired) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitsetEmpty returns whether the bitset is empty.
|
||||||
|
// Right now, this is technically free of race conditions because we don't
|
||||||
|
// have a method that can simultaneously modify two bits separated by a word boundary
|
||||||
|
// such that one of those modifications is an unset. If we did, there would be a race
|
||||||
|
// that could produce false positives. It's probably better to assume that they are
|
||||||
|
// already possible under concurrent modification (which is not how we're using this).
|
||||||
|
func BitsetEmpty(set []uint64) (empty bool) {
|
||||||
|
for i := 0; i < len(set); i++ {
|
||||||
|
if atomic.LoadUint64(&set[i]) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitsetUnion modifies `set` to be the union of `set` and `other`.
|
||||||
|
// This has race conditions in that we don't necessarily get a single
|
||||||
|
// consistent view of `other` across word boundaries.
|
||||||
|
func BitsetUnion(set []uint64, other []uint64) {
|
||||||
|
for i := 0; i < len(set); i++ {
|
||||||
|
for {
|
||||||
|
ourAddr := &set[i]
|
||||||
|
ourBlock := atomic.LoadUint64(ourAddr)
|
||||||
|
otherBlock := atomic.LoadUint64(&other[i])
|
||||||
|
newBlock := ourBlock | otherBlock
|
||||||
|
if atomic.CompareAndSwapUint64(ourAddr, ourBlock, newBlock) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
irc/utils/bitset_test.go
Normal file
65
irc/utils/bitset_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type testBitset [2]uint64
|
||||||
|
|
||||||
|
func TestSets(t *testing.T) {
|
||||||
|
var t1 testBitset
|
||||||
|
t1s := t1[:]
|
||||||
|
BitsetInitialize(t1s)
|
||||||
|
|
||||||
|
if BitsetGet(t1s, 0) || BitsetGet(t1s, 63) || BitsetGet(t1s, 64) || BitsetGet(t1s, 127) {
|
||||||
|
t.Error("no bits should be set in a newly initialized bitset")
|
||||||
|
}
|
||||||
|
|
||||||
|
var i uint
|
||||||
|
for i = 0; i < 128; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
if !BitsetSet(t1s, i, true) {
|
||||||
|
t.Error("setting an uninitialized bit should return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if BitsetSet(t1s, 24, true) {
|
||||||
|
t.Error("setting an already-set bit should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(BitsetGet(t1s, 0) && !BitsetGet(t1s, 1) && BitsetGet(t1s, 64) && BitsetGet(t1s, 72) && !BitsetGet(t1s, 127)) {
|
||||||
|
t.Error("exactly the even-numbered bits should be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !BitsetSet(t1s, 72, false) {
|
||||||
|
t.Error("removing a set bit should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if BitsetGet(t1s, 72) {
|
||||||
|
t.Error("remove doesn't work")
|
||||||
|
}
|
||||||
|
|
||||||
|
if BitsetSet(t1s, 72, false) {
|
||||||
|
t.Error("removing an unset bit should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
var t2 testBitset
|
||||||
|
t2s := t2[:]
|
||||||
|
BitsetInitialize(t2s)
|
||||||
|
|
||||||
|
for i = 0; i < 128; i++ {
|
||||||
|
if i%2 == 1 {
|
||||||
|
BitsetSet(t2s, i, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BitsetUnion(t1s, t2s)
|
||||||
|
for i = 0; i < 128; i++ {
|
||||||
|
expected := (i != 72)
|
||||||
|
if BitsetGet(t1s, i) != expected {
|
||||||
|
t.Error("all bits should be set except 72")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user