Parcourir la source

atomic bitset implementations of caps.Set and modes.ModeSet

tags/v0.12.0
Shivaram Lingamneni il y a 5 ans
Parent
révision
2a33c1483b
12 fichiers modifiés avec 577 ajouts et 174 suppressions
  1. 7
    0
      Makefile
  2. 204
    0
      gencapdefs.py
  3. 24
    45
      irc/caps/constants.go
  4. 125
    0
      irc/caps/defs.go
  5. 27
    52
      irc/caps/set.go
  6. 8
    17
      irc/caps/set_test.go
  7. 11
    9
      irc/handlers.go
  8. 23
    39
      irc/modes/modes.go
  9. 3
    3
      irc/responsebuffer.go
  10. 7
    9
      irc/server.go
  11. 86
    0
      irc/utils/bitset.go
  12. 52
    0
      irc/utils/bitset_test.go

+ 7
- 0
Makefile Voir le fichier

@@ -1,5 +1,7 @@
1 1
 .PHONY: all build
2 2
 
3
+capdef_file = ./irc/caps/defs.go
4
+
3 5
 all: build
4 6
 
5 7
 build:
@@ -8,11 +10,16 @@ build:
8 10
 buildrelease:
9 11
 	goreleaser --skip-publish --rm-dist
10 12
 
13
+capdefs:
14
+	python3 ./gencapdefs.py > ${capdef_file}
15
+
11 16
 deps:
12 17
 	git submodule update --init
13 18
 
14 19
 test:
20
+	python3 ./gencapdefs.py | diff - ${capdef_file}
15 21
 	cd irc && go test . && go vet .
22
+	cd irc/caps && go test . && go vet .
16 23
 	cd irc/isupport && go test . && go vet .
17 24
 	cd irc/modes && go test . && go vet .
18 25
 	cd irc/utils && go test . && go vet .

+ 204
- 0
gencapdefs.py Voir le fichier

@@ -0,0 +1,204 @@
1
+#!/usr/bin/env python3
2
+
3
+"""
4
+Updates the capability definitions at irc/caps/defs.go
5
+
6
+To add a capability, add it to the CAPDEFS list below,
7
+then run `make capdefs` from the project root.
8
+"""
9
+
10
+import io
11
+import subprocess
12
+import sys
13
+from collections import namedtuple
14
+
15
+CapDef = namedtuple("CapDef", ['identifier', 'name', 'url', 'standard'])
16
+
17
+CAPDEFS = [
18
+    CapDef(
19
+        identifier="LabelTagName",
20
+        name="draft/label",
21
+        url="https://ircv3.net/specs/extensions/labeled-response.html",
22
+        standard="draft IRCv3 tag name",
23
+    ),
24
+    CapDef(
25
+        identifier="AccountNotify",
26
+        name="account-notify",
27
+        url="https://ircv3.net/specs/extensions/account-notify-3.1.html",
28
+        standard="IRCv3",
29
+    ),
30
+    CapDef(
31
+        identifier="AccountTag",
32
+        name="account-tag",
33
+        url="https://ircv3.net/specs/extensions/account-tag-3.2.html",
34
+        standard="IRCv3",
35
+    ),
36
+    CapDef(
37
+        identifier="AwayNotify",
38
+        name="away-notify",
39
+        url="https://ircv3.net/specs/extensions/away-notify-3.1.html",
40
+        standard="IRCv3",
41
+    ),
42
+    CapDef(
43
+        identifier="Batch",
44
+        name="batch",
45
+        url="https://ircv3.net/specs/extensions/batch-3.2.html",
46
+        standard="IRCv3",
47
+    ),
48
+    CapDef(
49
+        identifier="CapNotify",
50
+        name="cap-notify",
51
+        url="https://ircv3.net/specs/extensions/cap-notify-3.2.html",
52
+        standard="IRCv3",
53
+    ),
54
+    CapDef(
55
+        identifier="ChgHost",
56
+        name="chghost",
57
+        url="https://ircv3.net/specs/extensions/chghost-3.2.html",
58
+        standard="IRCv3",
59
+    ),
60
+    CapDef(
61
+        identifier="EchoMessage",
62
+        name="echo-message",
63
+        url="https://ircv3.net/specs/extensions/echo-message-3.2.html",
64
+        standard="IRCv3",
65
+    ),
66
+    CapDef(
67
+        identifier="ExtendedJoin",
68
+        name="extended-join",
69
+        url="https://ircv3.net/specs/extensions/extended-join-3.1.html",
70
+        standard="IRCv3",
71
+    ),
72
+    CapDef(
73
+        identifier="InviteNotify",
74
+        name="invite-notify",
75
+        url="https://ircv3.net/specs/extensions/invite-notify-3.2.html",
76
+        standard="IRCv3",
77
+    ),
78
+    CapDef(
79
+        identifier="LabeledResponse",
80
+        name="draft/labeled-response",
81
+        url="https://ircv3.net/specs/extensions/labeled-response.html",
82
+        standard="draft IRCv3",
83
+    ),
84
+    CapDef(
85
+        identifier="Languages",
86
+        name="draft/languages",
87
+        url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
88
+        standard="proposed IRCv3",
89
+    ),
90
+    CapDef(
91
+        identifier="MaxLine",
92
+        name="oragono.io/maxline",
93
+        url="https://oragono.io/maxline",
94
+        standard="Oragono-specific",
95
+    ),
96
+    CapDef(
97
+        identifier="MessageTags",
98
+        name="draft/message-tags-0.2",
99
+        url="https://ircv3.net/specs/core/message-tags-3.3.html",
100
+        standard="draft IRCv3",
101
+    ),
102
+    CapDef(
103
+        identifier="MultiPrefix",
104
+        name="multi-prefix",
105
+        url="https://ircv3.net/specs/extensions/multi-prefix-3.1.html",
106
+        standard="IRCv3",
107
+    ),
108
+    CapDef(
109
+        identifier="Rename",
110
+        name="draft/rename",
111
+        url="https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md",
112
+        standard="proposed IRCv3",
113
+    ),
114
+    CapDef(
115
+        identifier="Resume",
116
+        name="draft/resume",
117
+        url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md",
118
+        standard="proposed IRCv3",
119
+    ),
120
+    CapDef(
121
+        identifier="SASL",
122
+        name="sasl",
123
+        url="https://ircv3.net/specs/extensions/sasl-3.2.html",
124
+        standard="IRCv3",
125
+    ),
126
+    CapDef(
127
+        identifier="ServerTime",
128
+        name="server-time",
129
+        url="https://ircv3.net/specs/extensions/server-time-3.2.html",
130
+        standard="IRCv3",
131
+    ),
132
+    CapDef(
133
+        identifier="STS",
134
+        name="sts",
135
+        url="https://ircv3.net/specs/extensions/sts.html",
136
+        standard="IRCv3",
137
+    ),
138
+    CapDef(
139
+        identifier="UserhostInNames",
140
+        name="userhost-in-names",
141
+        url="https://ircv3.net/specs/extensions/userhost-in-names-3.2.html",
142
+        standard="IRCv3",
143
+    ),
144
+]
145
+
146
+def validate_defs():
147
+    numCaps = len(CAPDEFS)
148
+    numNames = len(set(capdef.name for capdef in CAPDEFS))
149
+    if numCaps != numNames:
150
+        raise Exception("defs must have unique names, but found duplicates")
151
+    numIdentifiers = len(set(capdef.identifier for capdef in CAPDEFS))
152
+    if numCaps != numIdentifiers:
153
+        raise Exception("defs must have unique identifiers, but found duplicates")
154
+
155
+def main():
156
+    validate_defs()
157
+    output = io.StringIO()
158
+    print("""
159
+package caps
160
+
161
+/*
162
+	WARNING: this file is autogenerated by `make capdefs`
163
+	DO NOT EDIT MANUALLY.
164
+*/
165
+
166
+
167
+    """, file=output)
168
+
169
+
170
+    numCapabs = len(CAPDEFS)
171
+    bitsetLen = numCapabs // 64
172
+    if numCapabs % 64 > 0:
173
+        bitsetLen += 1
174
+    print ("""
175
+const (
176
+	// number of recognized capabilities:
177
+	numCapabs = %d
178
+	// length of the uint64 array that represents the bitset:
179
+	bitsetLen = %d
180
+)
181
+    """ % (numCapabs, bitsetLen), file=output)
182
+
183
+    print("const (", file=output)
184
+    for capdef in CAPDEFS:
185
+        print("// %s is the %s capability named \"%s\":" % (capdef.identifier, capdef.standard, capdef.name), file=output)
186
+        print("// %s" % (capdef.url,), file=output)
187
+        print("%s Capability = iota" % (capdef.identifier,), file=output)
188
+        print(file=output)
189
+    print(")", file=output)
190
+
191
+    print("""var ( capabilityNames = [numCapabs]string{""", file=output)
192
+    for capdef in CAPDEFS:
193
+        print("\"%s\"," % (capdef.name,), file=output)
194
+    print("})", file=output)
195
+
196
+    gofmt = subprocess.Popen(['gofmt', '-s'], stdin=subprocess.PIPE)
197
+    gofmt.communicate(input=output.getvalue().encode('utf-8'))
198
+    if gofmt.poll() != 0:
199
+        print(output.getvalue())
200
+        raise Exception("gofmt failed")
201
+    return 0
202
+
203
+if __name__ == '__main__':
204
+    sys.exit(main())

+ 24
- 45
irc/caps/constants.go Voir le fichier

@@ -3,58 +3,30 @@
3 3
 
4 4
 package caps
5 5
 
6
+import "errors"
7
+
6 8
 // Capability represents an optional feature that a client may request from the server.
7
-type Capability string
9
+type Capability uint
8 10
 
9
-const (
10
-	// LabelTagName is the tag name used for the labeled-response spec.
11
-	LabelTagName = "draft/label"
11
+// actual capability definitions appear in defs.go
12 12
 
13
-	// AccountNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/account-notify-3.1.html
14
-	AccountNotify Capability = "account-notify"
15
-	// AccountTag is this IRCv3 capability: http://ircv3.net/specs/extensions/account-tag-3.2.html
16
-	AccountTag Capability = "account-tag"
17
-	// AwayNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/away-notify-3.1.html
18
-	AwayNotify Capability = "away-notify"
19
-	// Batch is this IRCv3 capability: http://ircv3.net/specs/extensions/batch-3.2.html
20
-	Batch Capability = "batch"
21
-	// CapNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/cap-notify-3.2.html
22
-	CapNotify Capability = "cap-notify"
23
-	// ChgHost is this IRCv3 capability: http://ircv3.net/specs/extensions/chghost-3.2.html
24
-	ChgHost Capability = "chghost"
25
-	// EchoMessage is this IRCv3 capability: http://ircv3.net/specs/extensions/echo-message-3.2.html
26
-	EchoMessage Capability = "echo-message"
27
-	// ExtendedJoin is this IRCv3 capability: http://ircv3.net/specs/extensions/extended-join-3.1.html
28
-	ExtendedJoin Capability = "extended-join"
29
-	// InviteNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/invite-notify-3.2.html
30
-	InviteNotify Capability = "invite-notify"
31
-	// LabeledResponse is this draft IRCv3 capability: http://ircv3.net/specs/extensions/labeled-response.html
32
-	LabeledResponse Capability = "draft/labeled-response"
33
-	// Languages is this proposed IRCv3 capability: https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
34
-	Languages Capability = "draft/languages"
35
-	// MaxLine is this capability: https://oragono.io/maxline
36
-	MaxLine Capability = "oragono.io/maxline"
37
-	// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
38
-	MessageTags Capability = "draft/message-tags-0.2"
39
-	// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
40
-	MultiPrefix Capability = "multi-prefix"
41
-	// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
42
-	Rename Capability = "draft/rename"
43
-	// Resume is this proposed capability: https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
44
-	Resume Capability = "draft/resume"
45
-	// SASL is this IRCv3 capability: http://ircv3.net/specs/extensions/sasl-3.2.html
46
-	SASL Capability = "sasl"
47
-	// ServerTime is this IRCv3 capability: http://ircv3.net/specs/extensions/server-time-3.2.html
48
-	ServerTime Capability = "server-time"
49
-	// STS is this IRCv3 capability: http://ircv3.net/specs/extensions/sts.html
50
-	STS Capability = "sts"
51
-	// UserhostInNames is this IRCv3 capability: http://ircv3.net/specs/extensions/userhost-in-names-3.2.html
52
-	UserhostInNames Capability = "userhost-in-names"
13
+var (
14
+	nameToCapability map[string]Capability
15
+
16
+	NoSuchCap = errors.New("Unsupported capability name")
53 17
 )
54 18
 
55 19
 // Name returns the name of the given capability.
56 20
 func (capability Capability) Name() string {
57
-	return string(capability)
21
+	return capabilityNames[capability]
22
+}
23
+
24
+func NameToCapability(name string) (result Capability, err error) {
25
+	result, found := nameToCapability[name]
26
+	if !found {
27
+		err = NoSuchCap
28
+	}
29
+	return
58 30
 }
59 31
 
60 32
 // Version is used to select which max version of CAP the client supports.
@@ -78,3 +50,10 @@ const (
78 50
 	// NegotiatedState means CAP negotiation has been successfully ended and reg should complete.
79 51
 	NegotiatedState State = iota
80 52
 )
53
+
54
+func init() {
55
+	nameToCapability = make(map[string]Capability)
56
+	for capab, name := range capabilityNames {
57
+		nameToCapability[name] = Capability(capab)
58
+	}
59
+}

+ 125
- 0
irc/caps/defs.go Voir le fichier

@@ -0,0 +1,125 @@
1
+package caps
2
+
3
+/*
4
+	WARNING: this file is autogenerated by `make capdefs`
5
+	DO NOT EDIT MANUALLY.
6
+*/
7
+
8
+const (
9
+	// number of recognized capabilities:
10
+	numCapabs = 21
11
+	// length of the uint64 array that represents the bitset:
12
+	bitsetLen = 1
13
+)
14
+
15
+const (
16
+	// LabelTagName is the draft IRCv3 tag name capability named "draft/label":
17
+	// https://ircv3.net/specs/extensions/labeled-response.html
18
+	LabelTagName Capability = iota
19
+
20
+	// AccountNotify is the IRCv3 capability named "account-notify":
21
+	// https://ircv3.net/specs/extensions/account-notify-3.1.html
22
+	AccountNotify Capability = iota
23
+
24
+	// AccountTag is the IRCv3 capability named "account-tag":
25
+	// https://ircv3.net/specs/extensions/account-tag-3.2.html
26
+	AccountTag Capability = iota
27
+
28
+	// AwayNotify is the IRCv3 capability named "away-notify":
29
+	// https://ircv3.net/specs/extensions/away-notify-3.1.html
30
+	AwayNotify Capability = iota
31
+
32
+	// Batch is the IRCv3 capability named "batch":
33
+	// https://ircv3.net/specs/extensions/batch-3.2.html
34
+	Batch Capability = iota
35
+
36
+	// CapNotify is the IRCv3 capability named "cap-notify":
37
+	// https://ircv3.net/specs/extensions/cap-notify-3.2.html
38
+	CapNotify Capability = iota
39
+
40
+	// ChgHost is the IRCv3 capability named "chghost":
41
+	// https://ircv3.net/specs/extensions/chghost-3.2.html
42
+	ChgHost Capability = iota
43
+
44
+	// EchoMessage is the IRCv3 capability named "echo-message":
45
+	// https://ircv3.net/specs/extensions/echo-message-3.2.html
46
+	EchoMessage Capability = iota
47
+
48
+	// ExtendedJoin is the IRCv3 capability named "extended-join":
49
+	// https://ircv3.net/specs/extensions/extended-join-3.1.html
50
+	ExtendedJoin Capability = iota
51
+
52
+	// InviteNotify is the IRCv3 capability named "invite-notify":
53
+	// https://ircv3.net/specs/extensions/invite-notify-3.2.html
54
+	InviteNotify Capability = iota
55
+
56
+	// LabeledResponse is the draft IRCv3 capability named "draft/labeled-response":
57
+	// https://ircv3.net/specs/extensions/labeled-response.html
58
+	LabeledResponse Capability = iota
59
+
60
+	// Languages is the proposed IRCv3 capability named "draft/languages":
61
+	// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
62
+	Languages Capability = iota
63
+
64
+	// MaxLine is the Oragono-specific capability named "oragono.io/maxline":
65
+	// https://oragono.io/maxline
66
+	MaxLine Capability = iota
67
+
68
+	// MessageTags is the draft IRCv3 capability named "draft/message-tags-0.2":
69
+	// https://ircv3.net/specs/core/message-tags-3.3.html
70
+	MessageTags Capability = iota
71
+
72
+	// MultiPrefix is the IRCv3 capability named "multi-prefix":
73
+	// https://ircv3.net/specs/extensions/multi-prefix-3.1.html
74
+	MultiPrefix Capability = iota
75
+
76
+	// Rename is the proposed IRCv3 capability named "draft/rename":
77
+	// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
78
+	Rename Capability = iota
79
+
80
+	// Resume is the proposed IRCv3 capability named "draft/resume":
81
+	// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
82
+	Resume Capability = iota
83
+
84
+	// SASL is the IRCv3 capability named "sasl":
85
+	// https://ircv3.net/specs/extensions/sasl-3.2.html
86
+	SASL Capability = iota
87
+
88
+	// ServerTime is the IRCv3 capability named "server-time":
89
+	// https://ircv3.net/specs/extensions/server-time-3.2.html
90
+	ServerTime Capability = iota
91
+
92
+	// STS is the IRCv3 capability named "sts":
93
+	// https://ircv3.net/specs/extensions/sts.html
94
+	STS Capability = iota
95
+
96
+	// UserhostInNames is the IRCv3 capability named "userhost-in-names":
97
+	// https://ircv3.net/specs/extensions/userhost-in-names-3.2.html
98
+	UserhostInNames Capability = iota
99
+)
100
+
101
+var (
102
+	capabilityNames = [numCapabs]string{
103
+		"draft/label",
104
+		"account-notify",
105
+		"account-tag",
106
+		"away-notify",
107
+		"batch",
108
+		"cap-notify",
109
+		"chghost",
110
+		"echo-message",
111
+		"extended-join",
112
+		"invite-notify",
113
+		"draft/labeled-response",
114
+		"draft/languages",
115
+		"oragono.io/maxline",
116
+		"draft/message-tags-0.2",
117
+		"multi-prefix",
118
+		"draft/rename",
119
+		"draft/resume",
120
+		"sasl",
121
+		"server-time",
122
+		"sts",
123
+		"userhost-in-names",
124
+	}
125
+)

+ 27
- 52
irc/caps/set.go Voir le fichier

@@ -6,43 +6,34 @@ package caps
6 6
 import (
7 7
 	"sort"
8 8
 	"strings"
9
-	"sync"
9
+
10
+	"github.com/oragono/oragono/irc/utils"
10 11
 )
11 12
 
12 13
 // Set holds a set of enabled capabilities.
13
-type Set struct {
14
-	sync.RWMutex
15
-	// capabilities holds the capabilities this manager has.
16
-	capabilities map[Capability]bool
17
-}
14
+type Set [bitsetLen]uint64
18 15
 
19 16
 // NewSet returns a new Set, with the given capabilities enabled.
20 17
 func NewSet(capabs ...Capability) *Set {
21
-	newSet := Set{
22
-		capabilities: make(map[Capability]bool),
23
-	}
18
+	var newSet Set
19
+	utils.BitsetInitialize(newSet[:])
24 20
 	newSet.Enable(capabs...)
25
-
26 21
 	return &newSet
27 22
 }
28 23
 
29 24
 // Enable enables the given capabilities.
30 25
 func (s *Set) Enable(capabs ...Capability) {
31
-	s.Lock()
32
-	defer s.Unlock()
33
-
26
+	asSlice := s[:]
34 27
 	for _, capab := range capabs {
35
-		s.capabilities[capab] = true
28
+		utils.BitsetSet(asSlice, uint(capab), true)
36 29
 	}
37 30
 }
38 31
 
39 32
 // Disable disables the given capabilities.
40 33
 func (s *Set) Disable(capabs ...Capability) {
41
-	s.Lock()
42
-	defer s.Unlock()
43
-
34
+	asSlice := s[:]
44 35
 	for _, capab := range capabs {
45
-		delete(s.capabilities, capab)
36
+		utils.BitsetSet(asSlice, uint(capab), false)
46 37
 	}
47 38
 }
48 39
 
@@ -58,51 +49,35 @@ func (s *Set) Remove(capabs ...Capability) {
58 49
 	s.Disable(capabs...)
59 50
 }
60 51
 
61
-// Has returns true if this set has the given capabilities.
62
-func (s *Set) Has(caps ...Capability) bool {
63
-	s.RLock()
64
-	defer s.RUnlock()
65
-
66
-	for _, cap := range caps {
67
-		if !s.capabilities[cap] {
68
-			return false
69
-		}
70
-	}
71
-	return true
52
+// Has returns true if this set has the given capability.
53
+func (s *Set) Has(capab Capability) bool {
54
+	return utils.BitsetGet(s[:], uint(capab))
72 55
 }
73 56
 
74
-// List return a list of our enabled capabilities.
75
-func (s *Set) List() []Capability {
76
-	s.RLock()
77
-	defer s.RUnlock()
78
-
79
-	var allCaps []Capability
80
-	for capab := range s.capabilities {
81
-		allCaps = append(allCaps, capab)
82
-	}
83
-
84
-	return allCaps
57
+// Union adds all the capabilities of another set to this set.
58
+func (s *Set) Union(other *Set) {
59
+	utils.BitsetUnion(s[:], other[:])
85 60
 }
86 61
 
87
-// Count returns how many enabled caps this set has.
88
-func (s *Set) Count() int {
89
-	s.RLock()
90
-	defer s.RUnlock()
91
-
92
-	return len(s.capabilities)
62
+// Empty returns whether the set is empty.
63
+func (s *Set) Empty() bool {
64
+	return utils.BitsetEmpty(s[:])
93 65
 }
94 66
 
95 67
 // String returns all of our enabled capabilities as a string.
96 68
 func (s *Set) String(version Version, values *Values) string {
97
-	s.RLock()
98
-	defer s.RUnlock()
99
-
100 69
 	var strs sort.StringSlice
101 70
 
102
-	for capability := range s.capabilities {
103
-		capString := capability.Name()
71
+	var capab Capability
72
+	asSlice := s[:]
73
+	for capab = 0; capab < numCapabs; capab++ {
74
+		// skip any capabilities that are not enabled
75
+		if !utils.BitsetGet(asSlice, uint(capab)) {
76
+			continue
77
+		}
78
+		capString := capab.Name()
104 79
 		if version == Cap302 {
105
-			val, exists := values.Get(capability)
80
+			val, exists := values.Get(capab)
106 81
 			if exists {
107 82
 				capString += "=" + val
108 83
 			}

+ 8
- 17
irc/caps/set_test.go Voir le fichier

@@ -11,12 +11,12 @@ func TestSets(t *testing.T) {
11 11
 
12 12
 	s1.Enable(AccountTag, EchoMessage, UserhostInNames)
13 13
 
14
-	if !s1.Has(AccountTag, EchoMessage, UserhostInNames) {
14
+	if !(s1.Has(AccountTag) && s1.Has(EchoMessage) && s1.Has(UserhostInNames)) {
15 15
 		t.Error("Did not have the tags we expected")
16 16
 	}
17 17
 
18
-	if s1.Has(AccountTag, EchoMessage, STS, UserhostInNames) {
19
-		t.Error("Has() returned true when we don't have all the given capabilities")
18
+	if s1.Has(STS) {
19
+		t.Error("Has() returned true when we don't have the given capability")
20 20
 	}
21 21
 
22 22
 	s1.Disable(AccountTag)
@@ -25,14 +25,9 @@ func TestSets(t *testing.T) {
25 25
 		t.Error("Disable() did not correctly disable the given capability")
26 26
 	}
27 27
 
28
-	enabledCaps := make(map[Capability]bool)
29
-	for _, capab := range s1.List() {
30
-		enabledCaps[capab] = true
31
-	}
32
-	expectedCaps := map[Capability]bool{
33
-		EchoMessage:     true,
34
-		UserhostInNames: true,
35
-	}
28
+	enabledCaps := NewSet()
29
+	enabledCaps.Union(s1)
30
+	expectedCaps := NewSet(EchoMessage, UserhostInNames)
36 31
 	if !reflect.DeepEqual(enabledCaps, expectedCaps) {
37 32
 		t.Errorf("Enabled and expected capability lists do not match: %v, %v", enabledCaps, expectedCaps)
38 33
 	}
@@ -40,16 +35,12 @@ func TestSets(t *testing.T) {
40 35
 	// make sure re-enabling doesn't add to the count or something weird like that
41 36
 	s1.Enable(EchoMessage)
42 37
 
43
-	if s1.Count() != 2 {
44
-		t.Error("Count() did not match expected capability count")
45
-	}
46
-
47 38
 	// make sure add and remove work fine
48 39
 	s1.Add(InviteNotify)
49 40
 	s1.Remove(EchoMessage)
50 41
 
51
-	if s1.Count() != 2 {
52
-		t.Error("Count() did not match expected capability count")
42
+	if !s1.Has(InviteNotify) || s1.Has(EchoMessage) {
43
+		t.Error("Add/Remove don't work")
53 44
 	}
54 45
 
55 46
 	// test String()

+ 11
- 9
irc/handlers.go Voir le fichier

@@ -442,12 +442,16 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
442 442
 	capabilities := caps.NewSet()
443 443
 	var capString string
444 444
 
445
+	var badCaps []string
445 446
 	if len(msg.Params) > 1 {
446 447
 		capString = msg.Params[1]
447
-		strs := strings.Split(capString, " ")
448
+		strs := strings.Fields(capString)
448 449
 		for _, str := range strs {
449
-			if len(str) > 0 {
450
-				capabilities.Enable(caps.Capability(str))
450
+			capab, err := caps.NameToCapability(str)
451
+			if err != nil || !SupportedCapabilities.Has(capab) {
452
+				badCaps = append(badCaps, str)
453
+			} else {
454
+				capabilities.Enable(capab)
451 455
 			}
452 456
 		}
453 457
 	}
@@ -475,13 +479,11 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
475 479
 		}
476 480
 
477 481
 		// make sure all capabilities actually exist
478
-		for _, capability := range capabilities.List() {
479
-			if !SupportedCapabilities.Has(capability) {
480
-				rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
481
-				return false
482
-			}
482
+		if len(badCaps) > 0 {
483
+			rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
484
+			return false
483 485
 		}
484
-		client.capabilities.Enable(capabilities.List()...)
486
+		client.capabilities.Union(capabilities)
485 487
 		rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString)
486 488
 
487 489
 	case "END":

+ 23
- 39
irc/modes/modes.go Voir le fichier

@@ -7,7 +7,9 @@ package modes
7 7
 
8 8
 import (
9 9
 	"strings"
10
-	"sync"
10
+	"sync/atomic"
11
+
12
+	"github.com/oragono/oragono/irc/utils"
11 13
 )
12 14
 
13 15
 var (
@@ -322,42 +324,29 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
322 324
 }
323 325
 
324 326
 // ModeSet holds a set of modes.
325
-type ModeSet struct {
326
-	sync.RWMutex // tier 0
327
-	modes        map[Mode]bool
328
-}
327
+type ModeSet [1]uint64
328
+
329
+// valid modes go from 65 ('A') to 122 ('z'), making at most 58 possible values;
330
+// subtract 65 from the mode value and use that bit of the uint64 to represent it
331
+const (
332
+	minMode = 65 // 'A'
333
+)
329 334
 
330 335
 // returns a pointer to a new ModeSet
331 336
 func NewModeSet() *ModeSet {
332
-	return &ModeSet{
333
-		modes: make(map[Mode]bool),
334
-	}
337
+	var set ModeSet
338
+	utils.BitsetInitialize(set[:])
339
+	return &set
335 340
 }
336 341
 
337 342
 // test whether `mode` is set
338 343
 func (set *ModeSet) HasMode(mode Mode) bool {
339
-	if set == nil {
340
-		return false
341
-	}
342
-
343
-	set.RLock()
344
-	defer set.RUnlock()
345
-	return set.modes[mode]
344
+	return utils.BitsetGet(set[:], uint(mode)-minMode)
346 345
 }
347 346
 
348 347
 // set `mode` to be on or off, return whether the value actually changed
349 348
 func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
350
-	set.Lock()
351
-	defer set.Unlock()
352
-
353
-	previouslyOn := set.modes[mode]
354
-	needsApply := (on != previouslyOn)
355
-	if on && needsApply {
356
-		set.modes[mode] = true
357
-	} else if !on && needsApply {
358
-		delete(set.modes, mode)
359
-	}
360
-	return needsApply
349
+	return utils.BitsetSet(set[:], uint(mode)-minMode, on)
361 350
 }
362 351
 
363 352
 // return the modes in the set as a slice
@@ -366,11 +355,12 @@ func (set *ModeSet) AllModes() (result []Mode) {
366 355
 		return
367 356
 	}
368 357
 
369
-	set.RLock()
370
-	defer set.RUnlock()
371
-
372
-	for mode := range set.modes {
373
-		result = append(result, mode)
358
+	block := atomic.LoadUint64(&set[0])
359
+	var i uint
360
+	for i = 0; i < 64; i++ {
361
+		if block&(1<<i) != 0 {
362
+			result = append(result, Mode(minMode+i))
363
+		}
374 364
 	}
375 365
 	return
376 366
 }
@@ -381,11 +371,8 @@ func (set *ModeSet) String() (result string) {
381 371
 		return
382 372
 	}
383 373
 
384
-	set.RLock()
385
-	defer set.RUnlock()
386
-
387 374
 	var buf strings.Builder
388
-	for mode := range set.modes {
375
+	for _, mode := range set.AllModes() {
389 376
 		buf.WriteRune(rune(mode))
390 377
 	}
391 378
 	return buf.String()
@@ -397,12 +384,9 @@ func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
397 384
 		return
398 385
 	}
399 386
 
400
-	set.RLock()
401
-	defer set.RUnlock()
402
-
403 387
 	// add prefixes in order from highest to lowest privs
404 388
 	for _, mode := range ChannelUserModes {
405
-		if set.modes[mode] {
389
+		if set.HasMode(mode) {
406 390
 			prefixes += ChannelModePrefixes[mode]
407 391
 		}
408 392
 	}

+ 3
- 3
irc/responsebuffer.go Voir le fichier

@@ -23,7 +23,7 @@ type ResponseBuffer struct {
23 23
 
24 24
 // GetLabel returns the label from the given message.
25 25
 func GetLabel(msg ircmsg.IrcMessage) string {
26
-	return msg.Tags[caps.LabelTagName].Value
26
+	return msg.Tags[caps.LabelTagName.Name()].Value
27 27
 }
28 28
 
29 29
 // NewResponseBuffer returns a new ResponseBuffer.
@@ -90,13 +90,13 @@ func (rb *ResponseBuffer) Send() error {
90 90
 	// if label but no batch, add label to first message
91 91
 	if useLabel && batch == nil {
92 92
 		message := rb.messages[0]
93
-		message.Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
93
+		message.Tags[caps.LabelTagName.Name()] = ircmsg.MakeTagValue(rb.Label)
94 94
 		rb.messages[0] = message
95 95
 	}
96 96
 
97 97
 	// start batch if required
98 98
 	if batch != nil {
99
-		batch.Start(rb.target, ircmsg.MakeTags(caps.LabelTagName, rb.Label))
99
+		batch.Start(rb.target, ircmsg.MakeTags(caps.LabelTagName.Name(), rb.Label))
100 100
 	}
101 101
 
102 102
 	// send each message out

+ 7
- 9
irc/server.go Voir le fichier

@@ -898,13 +898,11 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
898 898
 
899 899
 	// updated caps get DEL'd and then NEW'd
900 900
 	// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
901
-	server.logger.Debug("rehash", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues), strconv.Itoa(updatedCaps.Count()))
902
-	for _, capab := range updatedCaps.List() {
903
-		addedCaps.Enable(capab)
904
-		removedCaps.Enable(capab)
905
-	}
901
+	server.logger.Debug("rehash", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues))
902
+	addedCaps.Union(updatedCaps)
903
+	removedCaps.Union(updatedCaps)
906 904
 
907
-	if 0 < addedCaps.Count() || 0 < removedCaps.Count() {
905
+	if !addedCaps.Empty() || !removedCaps.Empty() {
908 906
 		capBurstClients = server.clients.AllWithCaps(caps.CapNotify)
909 907
 
910 908
 		added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
@@ -918,7 +916,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
918 916
 			// remove STS policy
919 917
 			//TODO(dan): this is an ugly hack. we can write this better.
920 918
 			stsPolicy := "sts=duration=0"
921
-			if 0 < addedCaps.Count() {
919
+			if !addedCaps.Empty() {
922 920
 				added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
923 921
 			} else {
924 922
 				addedCaps.Enable(caps.STS)
@@ -926,10 +924,10 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
926 924
 			}
927 925
 		}
928 926
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
929
-		if 0 < removedCaps.Count() {
927
+		if !removedCaps.Empty() {
930 928
 			sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed)
931 929
 		}
932
-		if 0 < addedCaps.Count() {
930
+		if !addedCaps.Empty() {
933 931
 			sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion])
934 932
 		}
935 933
 	}

+ 86
- 0
irc/utils/bitset.go Voir le fichier

@@ -0,0 +1,86 @@
1
+// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package utils
5
+
6
+import "sync/atomic"
7
+
8
+// Library functions for lock-free bitsets, typically (constant-sized) arrays of uint64.
9
+// For examples of use, see caps.Set and modes.ModeSet; the array has to be converted to a
10
+// slice to use these functions.
11
+
12
+// BitsetInitialize initializes a bitset.
13
+func BitsetInitialize(set []uint64) {
14
+	// XXX re-zero the bitset using atomic stores. it's unclear whether this is required,
15
+	// however, golang issue #5045 suggests that you shouldn't mix atomic operations
16
+	// with non-atomic operations (such as the runtime's automatic zero-initialization) on
17
+	// the same word
18
+	for i := 0; i < len(set); i++ {
19
+		atomic.StoreUint64(&set[i], 0)
20
+	}
21
+}
22
+
23
+// BitsetGet returns whether a given bit of the bitset is set.
24
+func BitsetGet(set []uint64, position uint) bool {
25
+	idx := position / 64
26
+	bit := position % 64
27
+	block := atomic.LoadUint64(&set[idx])
28
+	return (block & (1 << bit)) != 0
29
+}
30
+
31
+// BitsetSet sets a given bit of the bitset to 0 or 1, returning whether it changed.
32
+func BitsetSet(set []uint64, position uint, on bool) (changed bool) {
33
+	idx := position / 64
34
+	bit := position % 64
35
+	addr := &set[idx]
36
+	var mask uint64
37
+	mask = 1 << bit
38
+	for {
39
+		current := atomic.LoadUint64(addr)
40
+		previouslyOn := (current & mask) != 0
41
+		if on == previouslyOn {
42
+			return false
43
+		}
44
+		var desired uint64
45
+		if on {
46
+			desired = current | mask
47
+		} else {
48
+			desired = current & (^mask)
49
+		}
50
+		if atomic.CompareAndSwapUint64(addr, current, desired) {
51
+			return true
52
+		}
53
+	}
54
+}
55
+
56
+// BitsetEmpty returns whether the bitset is empty.
57
+// Right now, this is technically free of race conditions because we don't
58
+// have a method that can simultaneously modify two bits separated by a word boundary
59
+// such that one of those modifications is an unset. If we did, there would be a race
60
+// that could produce false positives. It's probably better to assume that they are
61
+// already possible under concurrent modification (which is not how we're using this).
62
+func BitsetEmpty(set []uint64) (empty bool) {
63
+	for i := 0; i < len(set); i++ {
64
+		if atomic.LoadUint64(&set[i]) != 0 {
65
+			return false
66
+		}
67
+	}
68
+	return true
69
+}
70
+
71
+// BitsetUnion modifies `set` to be the union of `set` and `other`.
72
+// This has race conditions in that we don't necessarily get a single
73
+// consistent view of `other` across word boundaries.
74
+func BitsetUnion(set []uint64, other []uint64) {
75
+	for i := 0; i < len(set); i++ {
76
+		for {
77
+			ourAddr := &set[i]
78
+			ourBlock := atomic.LoadUint64(ourAddr)
79
+			otherBlock := atomic.LoadUint64(&other[i])
80
+			newBlock := ourBlock | otherBlock
81
+			if atomic.CompareAndSwapUint64(ourAddr, ourBlock, newBlock) {
82
+				break
83
+			}
84
+		}
85
+	}
86
+}

+ 52
- 0
irc/utils/bitset_test.go Voir le fichier

@@ -0,0 +1,52 @@
1
+// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package utils
5
+
6
+import "testing"
7
+
8
+type testBitset [2]uint64
9
+
10
+func TestSets(t *testing.T) {
11
+	var t1 testBitset
12
+	t1s := t1[:]
13
+	BitsetInitialize(t1s)
14
+
15
+	if BitsetGet(t1s, 0) || BitsetGet(t1s, 63) || BitsetGet(t1s, 64) || BitsetGet(t1s, 127) {
16
+		t.Error("no bits should be set in a newly initialized bitset")
17
+	}
18
+
19
+	var i uint
20
+	for i = 0; i < 128; i++ {
21
+		if i%2 == 0 {
22
+			BitsetSet(t1s, i, true)
23
+		}
24
+	}
25
+
26
+	if !(BitsetGet(t1s, 0) && !BitsetGet(t1s, 1) && BitsetGet(t1s, 64) && BitsetGet(t1s, 72) && !BitsetGet(t1s, 127)) {
27
+		t.Error("exactly the even-numbered bits should be set")
28
+	}
29
+
30
+	BitsetSet(t1s, 72, false)
31
+	if BitsetGet(t1s, 72) {
32
+		t.Error("remove doesn't work")
33
+	}
34
+
35
+	var t2 testBitset
36
+	t2s := t2[:]
37
+	BitsetInitialize(t2s)
38
+
39
+	for i = 0; i < 128; i++ {
40
+		if i%2 == 1 {
41
+			BitsetSet(t2s, i, true)
42
+		}
43
+	}
44
+
45
+	BitsetUnion(t1s, t2s)
46
+	for i = 0; i < 128; i++ {
47
+		expected := (i != 72)
48
+		if BitsetGet(t1s, i) != expected {
49
+			t.Error("all bits should be set except 72")
50
+		}
51
+	}
52
+}

Chargement…
Annuler
Enregistrer