diff options
author | Bernd Fix <brf@hoi-polloi.org> | 2020-02-17 12:34:45 +0100 |
---|---|---|
committer | Bernd Fix <brf@hoi-polloi.org> | 2020-02-17 12:34:45 +0100 |
commit | db8edd2189c526a79039661844314081739010e1 (patch) | |
tree | 011d51a33c739b4319cf1dddb62c6f838ccfdf82 | |
parent | 7bfc54a066e5a2c65f43ef17783fc9b3abbfbef0 (diff) | |
download | gnunet-go-db8edd2189c526a79039661844314081739010e1.tar.gz gnunet-go-db8edd2189c526a79039661844314081739010e1.zip |
Started revocation implementation.
-rw-r--r-- | src/cmd/pow-test/main.go | 48 | ||||
-rw-r--r-- | src/gnunet/message/factory.go | 12 | ||||
-rw-r--r-- | src/gnunet/message/msg_revocation.go | 181 | ||||
-rw-r--r-- | src/gnunet/service/gns/module.go | 10 | ||||
-rw-r--r-- | src/gnunet/service/revocation/pow.go | 231 |
5 files changed, 482 insertions, 0 deletions
diff --git a/src/cmd/pow-test/main.go b/src/cmd/pow-test/main.go new file mode 100644 index 0000000..4b74b6e --- /dev/null +++ b/src/cmd/pow-test/main.go | |||
@@ -0,0 +1,48 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "flag" | ||
5 | "fmt" | ||
6 | "log" | ||
7 | |||
8 | "gnunet/service/revocation" | ||
9 | |||
10 | "github.com/bfix/gospel/crypto/ed25519" | ||
11 | "github.com/bfix/gospel/math" | ||
12 | ) | ||
13 | |||
14 | func main() { | ||
15 | var ( | ||
16 | quiet bool | ||
17 | bits int | ||
18 | ) | ||
19 | flag.IntVar(&bits, "b", 25, "Number of leading zero bits") | ||
20 | flag.BoolVar(&quiet, "q", false, "Be quiet") | ||
21 | flag.Parse() | ||
22 | fmt.Printf("Leading zeros required: %d\n", bits) | ||
23 | |||
24 | // generate a random key pair | ||
25 | pkey, _ := ed25519.NewKeypair() | ||
26 | |||
27 | // initialize RevData structure | ||
28 | rd := revocation.NewRevData(0, pkey) | ||
29 | |||
30 | // pre-set difficulty | ||
31 | difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE) | ||
32 | |||
33 | var count uint64 = 0 | ||
34 | for { | ||
35 | result, err := rd.Compute() | ||
36 | if err != nil { | ||
37 | log.Fatal(err) | ||
38 | } | ||
39 | //fmt.Printf("Nonce=%d, Result=(%d) %v\n", rd.GetNonce(), result.BitLen(), result) | ||
40 | if result.Cmp(difficulty) < 0 { | ||
41 | break | ||
42 | } | ||
43 | count++ | ||
44 | rd.Next() | ||
45 | } | ||
46 | fmt.Printf("PoW found after %d iterations:\n", count) | ||
47 | fmt.Printf("--> Nonce=%d\n", rd.GetNonce()) | ||
48 | } | ||
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go index f6a8783..e6e6e73 100644 --- a/src/gnunet/message/factory.go +++ b/src/gnunet/message/factory.go | |||
@@ -81,6 +81,18 @@ func NewEmptyMessage(msgType uint16) (Message, error) { | |||
81 | return NewNamecacheCacheMsg(nil), nil | 81 | return NewNamecacheCacheMsg(nil), nil |
82 | case NAMECACHE_BLOCK_CACHE_RESPONSE: | 82 | case NAMECACHE_BLOCK_CACHE_RESPONSE: |
83 | return NewNamecacheCacheResponseMsg(), nil | 83 | return NewNamecacheCacheResponseMsg(), nil |
84 | |||
85 | //------------------------------------------------------------------ | ||
86 | // Revocation | ||
87 | //------------------------------------------------------------------ | ||
88 | case REVOCATION_QUERY: | ||
89 | return NewRevocationQueryMsg(nil), nil | ||
90 | case REVOCATION_QUERY_RESPONSE: | ||
91 | return NewRevocationQueryResponseMsg(true), nil | ||
92 | case REVOCATION_REVOKE: | ||
93 | return NewRevocationRevokeMsg(0, nil, nil), nil | ||
94 | case REVOCATION_REVOKE_RESPONSE: | ||
95 | return NewRevocationRevokeResponseMsg(false), nil | ||
84 | } | 96 | } |
85 | return nil, errors.New(fmt.Sprintf("Unknown message type %d", msgType)) | 97 | return nil, errors.New(fmt.Sprintf("Unknown message type %d", msgType)) |
86 | } | 98 | } |
diff --git a/src/gnunet/message/msg_revocation.go b/src/gnunet/message/msg_revocation.go new file mode 100644 index 0000000..fea727c --- /dev/null +++ b/src/gnunet/message/msg_revocation.go | |||
@@ -0,0 +1,181 @@ | |||
1 | // This file is part of gnunet-go, a GNUnet-implementation in Golang. | ||
2 | // Copyright (C) 2019, 2020 Bernd Fix >Y< | ||
3 | // | ||
4 | // gnunet-go is free software: you can redistribute it and/or modify it | ||
5 | // under the terms of the GNU Affero General Public License as published | ||
6 | // by the Free Software Foundation, either version 3 of the License, | ||
7 | // or (at your option) any later version. | ||
8 | // | ||
9 | // gnunet-go is distributed in the hope that it will be useful, but | ||
10 | // WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | // Affero General Public License for more details. | ||
13 | // | ||
14 | // You should have received a copy of the GNU Affero General Public License | ||
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | // | ||
17 | // SPDX-License-Identifier: AGPL3.0-or-later | ||
18 | |||
19 | package message | ||
20 | |||
21 | import ( | ||
22 | "fmt" | ||
23 | |||
24 | "gnunet/crypto" | ||
25 | "gnunet/enums" | ||
26 | "gnunet/util" | ||
27 | |||
28 | "github.com/bfix/gospel/crypto/ed25519" | ||
29 | ) | ||
30 | |||
31 | //---------------------------------------------------------------------- | ||
32 | // REVOCATION_QUERY | ||
33 | //---------------------------------------------------------------------- | ||
34 | |||
35 | // RevocationQueryMsg | ||
36 | type RevocationQueryMsg struct { | ||
37 | MsgSize uint16 `order:"big"` // total size of message | ||
38 | MsgType uint16 `order:"big"` // REVOCATION_QUERY (636) | ||
39 | Reserved uint32 `order:"big"` // Reserved for future use | ||
40 | Zone []byte `size:"32"` // Zone that is to be checked for revocation | ||
41 | } | ||
42 | |||
43 | // NewRevocationQueryMsg creates a new message for a given zone. | ||
44 | func NewRevocationQueryMsg(zone *ed25519.PublicKey) *RevocationQueryMsg { | ||
45 | msg := &RevocationQueryMsg{ | ||
46 | MsgSize: 40, | ||
47 | MsgType: REVOCATION_QUERY, | ||
48 | Reserved: 0, | ||
49 | Zone: make([]byte, 32), | ||
50 | } | ||
51 | if zone != nil { | ||
52 | copy(msg.Zone, zone.Bytes()) | ||
53 | } | ||
54 | return msg | ||
55 | } | ||
56 | |||
57 | // String returns a human-readable representation of the message. | ||
58 | func (m *RevocationQueryMsg) String() string { | ||
59 | return fmt.Sprintf("RevocationQueryMsg{zone=%s}", util.EncodeBinaryToString(m.Zone)) | ||
60 | } | ||
61 | |||
62 | // Header returns the message header in a separate instance. | ||
63 | func (msg *RevocationQueryMsg) Header() *MessageHeader { | ||
64 | return &MessageHeader{msg.MsgSize, msg.MsgType} | ||
65 | } | ||
66 | |||
67 | //---------------------------------------------------------------------- | ||
68 | // REVOCATION_QUERY_RESPONSE | ||
69 | //---------------------------------------------------------------------- | ||
70 | |||
71 | // RevocationQueryResponseMsg | ||
72 | type RevocationQueryResponseMsg struct { | ||
73 | MsgSize uint16 `order:"big"` // total size of message | ||
74 | MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637) | ||
75 | Valid uint32 `order:"big"` // revoked(0), valid(1) | ||
76 | } | ||
77 | |||
78 | // NewRevocationQueryResponseMsg creates a new response for a query. | ||
79 | func NewRevocationQueryResponseMsg(revoked bool) *RevocationQueryResponseMsg { | ||
80 | valid := 1 | ||
81 | if revoked { | ||
82 | valid = 0 | ||
83 | } | ||
84 | return &RevocationQueryResponseMsg{ | ||
85 | MsgSize: 8, | ||
86 | MsgType: REVOCATION_QUERY_RESPONSE, | ||
87 | Valid: uint32(valid), | ||
88 | } | ||
89 | } | ||
90 | |||
91 | // String returns a human-readable representation of the message. | ||
92 | func (m *RevocationQueryResponseMsg) String() string { | ||
93 | return fmt.Sprintf("RevocationQueryResponseMsg{valid=%d}", m.Valid) | ||
94 | } | ||
95 | |||
96 | // Header returns the message header in a separate instance. | ||
97 | func (msg *RevocationQueryResponseMsg) Header() *MessageHeader { | ||
98 | return &MessageHeader{msg.MsgSize, msg.MsgType} | ||
99 | } | ||
100 | |||
101 | //---------------------------------------------------------------------- | ||
102 | // REVOCATION_REVOKE | ||
103 | //---------------------------------------------------------------------- | ||
104 | |||
105 | // RevocationRevokeMsg | ||
106 | type RevocationRevokeMsg struct { | ||
107 | MsgSize uint16 `order:"big"` // total size of message | ||
108 | MsgType uint16 `order:"big"` // REVOCATION_QUERY (636) | ||
109 | Reserved uint32 `order:"big"` // Reserved for future use | ||
110 | PoW uint64 `order:"big"` // Proof-of-work: nonce that satisfy condition | ||
111 | Signature []byte `size:"64"` // Signature of the revocation. | ||
112 | Purpose *crypto.SignaturePurpose // Size and purpose of signature (8 bytes) | ||
113 | ZoneKey []byte `size:"32"` // Zone key to be revoked | ||
114 | } | ||
115 | |||
116 | // NewRevocationRevokeMsg creates a new message for a given zone. | ||
117 | func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig *ed25519.EcSignature) *RevocationRevokeMsg { | ||
118 | msg := &RevocationRevokeMsg{ | ||
119 | MsgSize: 120, | ||
120 | MsgType: REVOCATION_REVOKE, | ||
121 | Reserved: 0, | ||
122 | PoW: pow, | ||
123 | Signature: make([]byte, 64), | ||
124 | Purpose: &crypto.SignaturePurpose{ | ||
125 | Size: 40, | ||
126 | Purpose: enums.SIG_REVOCATION, | ||
127 | }, | ||
128 | ZoneKey: make([]byte, 32), | ||
129 | } | ||
130 | if zoneKey != nil { | ||
131 | copy(msg.ZoneKey, zoneKey.Bytes()) | ||
132 | } | ||
133 | if sig != nil { | ||
134 | copy(msg.Signature, sig.Bytes()) | ||
135 | } | ||
136 | return msg | ||
137 | } | ||
138 | |||
139 | // String returns a human-readable representation of the message. | ||
140 | func (m *RevocationRevokeMsg) String() string { | ||
141 | return fmt.Sprintf("RevocationRevokeMsg{pow=%d,zone=%s}", m.PoW, util.EncodeBinaryToString(m.ZoneKey)) | ||
142 | } | ||
143 | |||
144 | // Header returns the message header in a separate instance. | ||
145 | func (msg *RevocationRevokeMsg) Header() *MessageHeader { | ||
146 | return &MessageHeader{msg.MsgSize, msg.MsgType} | ||
147 | } | ||
148 | |||
149 | //---------------------------------------------------------------------- | ||
150 | // REVOCATION_REVOKE_RESPONSE | ||
151 | //---------------------------------------------------------------------- | ||
152 | |||
153 | // RevocationRevokeResponseMsg | ||
154 | type RevocationRevokeResponseMsg struct { | ||
155 | MsgSize uint16 `order:"big"` // total size of message | ||
156 | MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637) | ||
157 | Success uint32 `order:"big"` // Revoke successful? | ||
158 | } | ||
159 | |||
160 | // NewRevocationRevokeResponseMsg creates a new response for a query. | ||
161 | func NewRevocationRevokeResponseMsg(success bool) *RevocationRevokeResponseMsg { | ||
162 | status := 0 | ||
163 | if success { | ||
164 | status = 1 | ||
165 | } | ||
166 | return &RevocationRevokeResponseMsg{ | ||
167 | MsgSize: 8, | ||
168 | MsgType: REVOCATION_QUERY_RESPONSE, | ||
169 | Success: uint32(status), | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // String returns a human-readable representation of the message. | ||
174 | func (m *RevocationRevokeResponseMsg) String() string { | ||
175 | return fmt.Sprintf("RevocationRevokeResponseMsg{success=%v}", m.Success == 1) | ||
176 | } | ||
177 | |||
178 | // Header returns the message header in a separate instance. | ||
179 | func (msg *RevocationRevokeResponseMsg) Header() *MessageHeader { | ||
180 | return &MessageHeader{msg.MsgSize, msg.MsgType} | ||
181 | } | ||
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go index 5626432..e8d8847 100644 --- a/src/gnunet/service/gns/module.go +++ b/src/gnunet/service/gns/module.go | |||
@@ -280,6 +280,16 @@ func (gns *GNSModule) ResolveRelative(labels []string, pkey *ed25519.PublicKey, | |||
280 | set.AddRecord(inst.rec) | 280 | set.AddRecord(inst.rec) |
281 | } | 281 | } |
282 | } | 282 | } |
283 | |||
284 | // if the result set is not empty, add all supplemental records we are not | ||
285 | // asking for explicitly. | ||
286 | if set.Count > 0 { | ||
287 | for _, rec := range records { | ||
288 | if !kind.HasType(int(rec.Type)) && (int(rec.Flags)&enums.GNS_FLAG_SUPPL) != 0 { | ||
289 | set.AddRecord(rec) | ||
290 | } | ||
291 | } | ||
292 | } | ||
283 | return | 293 | return |
284 | } | 294 | } |
285 | 295 | ||
diff --git a/src/gnunet/service/revocation/pow.go b/src/gnunet/service/revocation/pow.go new file mode 100644 index 0000000..368b4e4 --- /dev/null +++ b/src/gnunet/service/revocation/pow.go | |||
@@ -0,0 +1,231 @@ | |||
1 | // This file is part of gnunet-go, a GNUnet-implementation in Golang. | ||
2 | // Copyright (C) 2019, 2020 Bernd Fix >Y< | ||
3 | // | ||
4 | // gnunet-go is free software: you can redistribute it and/or modify it | ||
5 | // under the terms of the GNU Affero General Public License as published | ||
6 | // by the Free Software Foundation, either version 3 of the License, | ||
7 | // or (at your option) any later version. | ||
8 | // | ||
9 | // gnunet-go is distributed in the hope that it will be useful, but | ||
10 | // WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | // Affero General Public License for more details. | ||
13 | // | ||
14 | // You should have received a copy of the GNU Affero General Public License | ||
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | // | ||
17 | // SPDX-License-Identifier: AGPL3.0-or-later | ||
18 | |||
19 | package revocation | ||
20 | |||
21 | import ( | ||
22 | "bytes" | ||
23 | "crypto/sha256" | ||
24 | "crypto/sha512" | ||
25 | "encoding/binary" | ||
26 | "sync" | ||
27 | |||
28 | "gnunet/crypto" | ||
29 | "gnunet/util" | ||
30 | |||
31 | "github.com/bfix/gospel/crypto/ed25519" | ||
32 | "github.com/bfix/gospel/data" | ||
33 | "github.com/bfix/gospel/math" | ||
34 | "golang.org/x/crypto/hkdf" | ||
35 | "golang.org/x/crypto/scrypt" | ||
36 | ) | ||
37 | |||
38 | //---------------------------------------------------------------------- | ||
39 | // Revocation data | ||
40 | //---------------------------------------------------------------------- | ||
41 | |||
42 | // RevData is the revocation data structure (wire format) | ||
43 | type RevData struct { | ||
44 | Nonce uint64 `order:"big"` // start with this nonce value | ||
45 | ZoneKey []byte `size:"32"` // public zone key to be revoked | ||
46 | |||
47 | // transient attributes (not serialized) | ||
48 | blob []byte // binary representation of serialized data | ||
49 | } | ||
50 | |||
51 | // NewRevData creates a RevData instance for the given arguments. | ||
52 | func NewRevData(nonce uint64, zoneKey *ed25519.PublicKey) *RevData { | ||
53 | rd := &RevData{ | ||
54 | Nonce: nonce, | ||
55 | ZoneKey: make([]byte, 32), | ||
56 | } | ||
57 | copy(rd.ZoneKey, zoneKey.Bytes()) | ||
58 | blob, err := data.Marshal(rd) | ||
59 | if err != nil { | ||
60 | return nil | ||
61 | } | ||
62 | rd.blob = blob | ||
63 | return rd | ||
64 | } | ||
65 | |||
66 | // GetNonce returns the last checked nonce value | ||
67 | func (r *RevData) GetNonce() uint64 { | ||
68 | if r.blob != nil { | ||
69 | var val uint64 | ||
70 | binary.Read(bytes.NewReader(r.blob[:8]), binary.BigEndian, &val) | ||
71 | r.Nonce = val | ||
72 | } | ||
73 | return r.Nonce | ||
74 | } | ||
75 | |||
76 | // Next selects the next nonce to be tested. | ||
77 | func (r *RevData) Next() { | ||
78 | var incr func(pos int) | ||
79 | incr = func(pos int) { | ||
80 | r.blob[pos]++ | ||
81 | if r.blob[pos] != 0 || pos == 0 { | ||
82 | return | ||
83 | } | ||
84 | incr(pos - 1) | ||
85 | } | ||
86 | incr(7) | ||
87 | } | ||
88 | |||
89 | // Compute calculates the current result for a RevData content. | ||
90 | // The result is returned as a big integer value. | ||
91 | func (r *RevData) Compute() (*math.Int, error) { | ||
92 | |||
93 | // generate key material | ||
94 | k, err := scrypt.Key(r.blob, []byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 64) | ||
95 | if err != nil { | ||
96 | return nil, err | ||
97 | } | ||
98 | |||
99 | // generate keys | ||
100 | skey := crypto.NewSymmetricKey() | ||
101 | copy(skey.AESKey, k[:32]) | ||
102 | copy(skey.TwofishKey, k[32:]) | ||
103 | |||
104 | // generate initialization vector | ||
105 | iv := crypto.NewSymmetricIV() | ||
106 | prk := hkdf.Extract(sha512.New, k[:32], []byte("gnunet-proof-of-work-ivAES!")) | ||
107 | rdr := hkdf.Expand(sha256.New, prk, []byte("gnunet-revocation-proof-of-work")) | ||
108 | rdr.Read(iv.AESIv) | ||
109 | prk = hkdf.Extract(sha512.New, k[32:], []byte("gnunet-proof-of-work-ivFISH")) | ||
110 | rdr = hkdf.Expand(sha256.New, prk, []byte("gnunet-revocation-proof-of-work")) | ||
111 | rdr.Read(iv.TwofishIv) | ||
112 | |||
113 | // perform encryption | ||
114 | enc, err := crypto.SymmetricEncrypt(r.blob, skey, iv) | ||
115 | if err != nil { | ||
116 | return nil, err | ||
117 | } | ||
118 | |||
119 | // compute result | ||
120 | result, err := scrypt.Key(enc, []byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 64) | ||
121 | return math.NewIntFromBytes(result), nil | ||
122 | } | ||
123 | |||
124 | //---------------------------------------------------------------------- | ||
125 | // Command types for Worker | ||
126 | //---------------------------------------------------------------------- | ||
127 | |||
128 | // StartCmd starts the PoW calculation beginng at given nonce. If a | ||
129 | // revocation is initiated the first time, the nonce is 0. If the computation | ||
130 | // was interrupted (because the revocation service was shutting down), the | ||
131 | // computation can resume for the next unchecked nonce value. | ||
132 | // see: StartResponse | ||
133 | type StartCmd struct { | ||
134 | ID int // Command identifier (to relate responses) | ||
135 | task *RevData // RevData instance to be started | ||
136 | } | ||
137 | |||
138 | // PauseCmd temporarily pauses the calculation of a PoW. | ||
139 | // see: PauseResponse | ||
140 | type PauseCmd struct { | ||
141 | ID int // Command identifier (to relate responses) | ||
142 | taskID int // identifier for PoW task | ||
143 | } | ||
144 | |||
145 | // ResumeCmd resumes a paused PoW calculation. | ||
146 | // see: ResumeResponse | ||
147 | type ResumeCmd struct { | ||
148 | ID int // Command identifier (to relate responses) | ||
149 | taskID int // identifier for PoW task | ||
150 | } | ||
151 | |||
152 | // BreakCmd interrupts a running PoW calculation | ||
153 | type BreakCmd struct { | ||
154 | ID int // Command identifier (to relate responses) | ||
155 | taskID int // identifier for PoW task | ||
156 | } | ||
157 | |||
158 | //---------------------------------------------------------------------- | ||
159 | // Response types for Worker | ||
160 | //---------------------------------------------------------------------- | ||
161 | |||
162 | // StartResponse is a reply to the StartCmd message | ||
163 | type StartResponse struct { | ||
164 | ID int // Command identifier (to relate responses) | ||
165 | taskID int // identifier for PoW task | ||
166 | err error // error code (nil on success) | ||
167 | } | ||
168 | |||
169 | // PauseResponse is a reply to the PauseCmd message | ||
170 | type PauseResponse struct { | ||
171 | ID int // Command identifier (to relate responses) | ||
172 | err error // error code (nil on success) | ||
173 | } | ||
174 | |||
175 | // ResumeResponse is a reply to the ResumeCmd message | ||
176 | type ResumeResponse struct { | ||
177 | ID int // Command identifier (to relate responses) | ||
178 | err error // error code (nil on success) | ||
179 | } | ||
180 | |||
181 | // BreakResponse is a reply to the BreakCmd message | ||
182 | type BreakResponse struct { | ||
183 | ID int // Command identifier (to relate responses) | ||
184 | Nonce uint64 // last checked nonce value | ||
185 | } | ||
186 | |||
187 | //---------------------------------------------------------------------- | ||
188 | // Worker instance | ||
189 | //---------------------------------------------------------------------- | ||
190 | |||
191 | // Task represents a currently active PoW calculation | ||
192 | type Task struct { | ||
193 | ID int | ||
194 | rev *RevData | ||
195 | active bool | ||
196 | } | ||
197 | |||
198 | // Worker is the revocation worker. It is responsible to manage ad schedule | ||
199 | // the proof-of-work tasks for revocations. | ||
200 | type Worker struct { | ||
201 | tasks map[int]*Task | ||
202 | wg *sync.WaitGroup | ||
203 | } | ||
204 | |||
205 | func NewWorker() *Worker { | ||
206 | return &Worker{ | ||
207 | tasks: make(map[int]*Task), | ||
208 | wg: new(sync.WaitGroup), | ||
209 | } | ||
210 | } | ||
211 | |||
212 | func (w *Worker) Run(wg *sync.WaitGroup, cmdCh chan interface{}, responseCh chan interface{}) { | ||
213 | defer wg.Done() | ||
214 | for { | ||
215 | select { | ||
216 | case cmd := <-cmdCh: | ||
217 | switch x := cmd.(type) { | ||
218 | case *StartCmd: | ||
219 | task := &Task{ | ||
220 | ID: util.NextID(), | ||
221 | rev: x.task, | ||
222 | active: true, | ||
223 | } | ||
224 | w.tasks[task.ID] = task | ||
225 | } | ||
226 | |||
227 | default: | ||
228 | // compute a single round of currently active tasks | ||
229 | } | ||
230 | } | ||
231 | } | ||