Source file src/pkg/cmd/go/internal/sumweb/client.go
1
2
3
4
5 package sumweb
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "strings"
12 "sync"
13 "sync/atomic"
14
15 "cmd/go/internal/note"
16 "cmd/go/internal/str"
17 "cmd/go/internal/tlog"
18 )
19
20
21
22
23
24 type Client interface {
25
26
27
28
29
30 ReadRemote(path string) ([]byte, error)
31
32
33
34
35
36
37
38
39
40
41 ReadConfig(file string) ([]byte, error)
42
43
44
45
46
47
48 WriteConfig(file string, old, new []byte) error
49
50
51
52
53
54
55 ReadCache(file string) ([]byte, error)
56
57
58 WriteCache(file string, data []byte)
59
60
61 Log(msg string)
62
63
64
65
66
67 SecurityError(msg string)
68 }
69
70
71 var ErrWriteConflict = errors.New("write conflict")
72
73
74 var ErrSecurity = errors.New("security error: misbehaving server")
75
76
77
78 type Conn struct {
79 client Client
80
81 didLookup uint32
82
83
84 initOnce sync.Once
85 initErr error
86 name string
87 verifiers note.Verifiers
88 tileReader tileReader
89 tileHeight int
90 nosumdb string
91
92 record parCache
93 tileCache parCache
94
95 latestMu sync.Mutex
96 latest tlog.Tree
97 latestMsg []byte
98
99 tileSavedMu sync.Mutex
100 tileSaved map[tlog.Tile]bool
101 }
102
103
104 func NewConn(client Client) *Conn {
105 return &Conn{
106 client: client,
107 }
108 }
109
110
111
112 func (c *Conn) init() error {
113 c.initOnce.Do(c.initWork)
114 return c.initErr
115 }
116
117
118 func (c *Conn) initWork() {
119 defer func() {
120 if c.initErr != nil {
121 c.initErr = fmt.Errorf("initializing sumweb.Conn: %v", c.initErr)
122 }
123 }()
124
125 c.tileReader.c = c
126 if c.tileHeight == 0 {
127 c.tileHeight = 8
128 }
129 c.tileSaved = make(map[tlog.Tile]bool)
130
131 vkey, err := c.client.ReadConfig("key")
132 if err != nil {
133 c.initErr = err
134 return
135 }
136 verifier, err := note.NewVerifier(strings.TrimSpace(string(vkey)))
137 if err != nil {
138 c.initErr = err
139 return
140 }
141 c.verifiers = note.VerifierList(verifier)
142 c.name = verifier.Name()
143
144 data, err := c.client.ReadConfig(c.name + "/latest")
145 if err != nil {
146 c.initErr = err
147 return
148 }
149 if err := c.mergeLatest(data); err != nil {
150 c.initErr = err
151 return
152 }
153 }
154
155
156
157
158 func (c *Conn) SetTileHeight(height int) {
159 if atomic.LoadUint32(&c.didLookup) != 0 {
160 panic("SetTileHeight used after Lookup")
161 }
162 if c.tileHeight != 0 {
163 panic("multiple calls to SetTileHeight")
164 }
165 c.tileHeight = height
166 }
167
168
169
170
171
172 func (c *Conn) SetGONOSUMDB(list string) {
173 if atomic.LoadUint32(&c.didLookup) != 0 {
174 panic("SetGONOSUMDB used after Lookup")
175 }
176 if c.nosumdb != "" {
177 panic("multiple calls to SetGONOSUMDB")
178 }
179 c.nosumdb = list
180 }
181
182
183
184
185 var ErrGONOSUMDB = errors.New("skipped (listed in GONOSUMDB)")
186
187 func (c *Conn) skip(target string) bool {
188 return str.GlobsMatchPath(c.nosumdb, target)
189 }
190
191
192
193
194 func (c *Conn) Lookup(path, vers string) (lines []string, err error) {
195 atomic.StoreUint32(&c.didLookup, 1)
196
197 if c.skip(path) {
198 return nil, ErrGONOSUMDB
199 }
200
201 defer func() {
202 if err != nil {
203 err = fmt.Errorf("%s@%s: %v", path, vers, err)
204 }
205 }()
206
207 if err := c.init(); err != nil {
208 return nil, err
209 }
210
211
212 epath, err := encodePath(path)
213 if err != nil {
214 return nil, err
215 }
216 evers, err := encodeVersion(strings.TrimSuffix(vers, "/go.mod"))
217 if err != nil {
218 return nil, err
219 }
220 file := c.name + "/lookup/" + epath + "@" + evers
221 remotePath := "/lookup/" + epath + "@" + evers
222
223
224
225
226
227
228 type cached struct {
229 data []byte
230 err error
231 }
232 result := c.record.Do(file, func() interface{} {
233
234 writeCache := false
235 data, err := c.client.ReadCache(file)
236 if err != nil {
237 data, err = c.client.ReadRemote(remotePath)
238 if err != nil {
239 return cached{nil, err}
240 }
241 writeCache = true
242 }
243
244
245 id, text, treeMsg, err := tlog.ParseRecord(data)
246 if err != nil {
247 return cached{nil, err}
248 }
249 if err := c.mergeLatest(treeMsg); err != nil {
250 return cached{nil, err}
251 }
252 if err := c.checkRecord(id, text); err != nil {
253 return cached{nil, err}
254 }
255
256
257
258 if writeCache {
259 c.client.WriteCache(file, data)
260 }
261
262 return cached{data, nil}
263 }).(cached)
264 if result.err != nil {
265 return nil, result.err
266 }
267
268
269
270 prefix := path + " " + vers + " "
271 var hashes []string
272 for _, line := range strings.Split(string(result.data), "\n") {
273 if strings.HasPrefix(line, prefix) {
274 hashes = append(hashes, line)
275 }
276 }
277 return hashes, nil
278 }
279
280
281
282
283
284
285
286
287
288
289 func (c *Conn) mergeLatest(msg []byte) error {
290
291 when, err := c.mergeLatestMem(msg)
292 if err != nil {
293 return err
294 }
295 if when != msgFuture {
296
297
298 return nil
299 }
300
301
302
303
304
305 for {
306 msg, err := c.client.ReadConfig(c.name + "/latest")
307 if err != nil {
308 return err
309 }
310 when, err := c.mergeLatestMem(msg)
311 if err != nil {
312 return err
313 }
314 if when != msgPast {
315
316
317 return nil
318 }
319
320
321 c.latestMu.Lock()
322 latestMsg := c.latestMsg
323 c.latestMu.Unlock()
324 if err := c.client.WriteConfig(c.name+"/latest", msg, latestMsg); err != ErrWriteConflict {
325
326 return err
327 }
328 }
329 }
330
331 const (
332 msgPast = 1 + iota
333 msgNow
334 msgFuture
335 )
336
337
338
339
340
341
342
343
344
345 func (c *Conn) mergeLatestMem(msg []byte) (when int, err error) {
346 if len(msg) == 0 {
347
348 c.latestMu.Lock()
349 latest := c.latest
350 c.latestMu.Unlock()
351 if latest.N == 0 {
352 return msgNow, nil
353 }
354 return msgPast, nil
355 }
356
357 note, err := note.Open(msg, c.verifiers)
358 if err != nil {
359 return 0, fmt.Errorf("reading tree note: %v\nnote:\n%s", err, msg)
360 }
361 tree, err := tlog.ParseTree([]byte(note.Text))
362 if err != nil {
363 return 0, fmt.Errorf("reading tree: %v\ntree:\n%s", err, note.Text)
364 }
365
366
367
368
369 c.latestMu.Lock()
370 latest := c.latest
371 latestMsg := c.latestMsg
372 c.latestMu.Unlock()
373
374 for {
375
376 if tree.N <= latest.N {
377 if err := c.checkTrees(tree, msg, latest, latestMsg); err != nil {
378 return 0, err
379 }
380 if tree.N < latest.N {
381 return msgPast, nil
382 }
383 return msgNow, nil
384 }
385
386
387 if err := c.checkTrees(latest, latestMsg, tree, msg); err != nil {
388 return 0, err
389 }
390
391
392
393 c.latestMu.Lock()
394 installed := false
395 if c.latest == latest {
396 installed = true
397 c.latest = tree
398 c.latestMsg = msg
399 } else {
400 latest = c.latest
401 latestMsg = c.latestMsg
402 }
403 c.latestMu.Unlock()
404
405 if installed {
406 return msgFuture, nil
407 }
408 }
409 }
410
411
412
413
414
415 func (c *Conn) checkTrees(older tlog.Tree, olderNote []byte, newer tlog.Tree, newerNote []byte) error {
416 thr := tlog.TileHashReader(newer, &c.tileReader)
417 h, err := tlog.TreeHash(older.N, thr)
418 if err != nil {
419 if older.N == newer.N {
420 return fmt.Errorf("checking tree#%d: %v", older.N, err)
421 }
422 return fmt.Errorf("checking tree#%d against tree#%d: %v", older.N, newer.N, err)
423 }
424 if h == older.Hash {
425 return nil
426 }
427
428
429
430 var buf bytes.Buffer
431 fmt.Fprintf(&buf, "SECURITY ERROR\n")
432 fmt.Fprintf(&buf, "go.sum database server misbehavior detected!\n\n")
433 indent := func(b []byte) []byte {
434 return bytes.Replace(b, []byte("\n"), []byte("\n\t"), -1)
435 }
436 fmt.Fprintf(&buf, "old database:\n\t%s\n", indent(olderNote))
437 fmt.Fprintf(&buf, "new database:\n\t%s\n", indent(newerNote))
438
439
440
441
442
443
444
445
446
447
448
449 fmt.Fprintf(&buf, "proof of misbehavior:\n\t%v", h)
450 if p, err := tlog.ProveTree(newer.N, older.N, thr); err != nil {
451 fmt.Fprintf(&buf, "\tinternal error: %v\n", err)
452 } else if err := tlog.CheckTree(p, newer.N, newer.Hash, older.N, h); err != nil {
453 fmt.Fprintf(&buf, "\tinternal error: generated inconsistent proof\n")
454 } else {
455 for _, h := range p {
456 fmt.Fprintf(&buf, "\n\t%v", h)
457 }
458 }
459 c.client.SecurityError(buf.String())
460 return ErrSecurity
461 }
462
463
464 func (c *Conn) checkRecord(id int64, data []byte) error {
465 c.latestMu.Lock()
466 latest := c.latest
467 c.latestMu.Unlock()
468
469 if id >= latest.N {
470 return fmt.Errorf("cannot validate record %d in tree of size %d", id, latest.N)
471 }
472 hashes, err := tlog.TileHashReader(latest, &c.tileReader).ReadHashes([]int64{tlog.StoredHashIndex(0, id)})
473 if err != nil {
474 return err
475 }
476 if hashes[0] == tlog.RecordHash(data) {
477 return nil
478 }
479 return fmt.Errorf("cannot authenticate record data in server response")
480 }
481
482
483
484
485 type tileReader struct {
486 c *Conn
487 }
488
489 func (r *tileReader) Height() int {
490 return r.c.tileHeight
491 }
492
493
494
495 func (r *tileReader) ReadTiles(tiles []tlog.Tile) ([][]byte, error) {
496
497 data := make([][]byte, len(tiles))
498 errs := make([]error, len(tiles))
499 var wg sync.WaitGroup
500 for i, tile := range tiles {
501 wg.Add(1)
502 go func(i int, tile tlog.Tile) {
503 defer wg.Done()
504 data[i], errs[i] = r.c.readTile(tile)
505 }(i, tile)
506 }
507 wg.Wait()
508
509 for _, err := range errs {
510 if err != nil {
511 return nil, err
512 }
513 }
514
515 return data, nil
516 }
517
518
519 func (c *Conn) tileCacheKey(tile tlog.Tile) string {
520 return c.name + "/" + tile.Path()
521 }
522
523
524 func (c *Conn) tileRemotePath(tile tlog.Tile) string {
525 return "/" + tile.Path()
526 }
527
528
529 func (c *Conn) readTile(tile tlog.Tile) ([]byte, error) {
530 type cached struct {
531 data []byte
532 err error
533 }
534
535 result := c.tileCache.Do(tile, func() interface{} {
536
537 data, err := c.client.ReadCache(c.tileCacheKey(tile))
538 if err == nil {
539 c.markTileSaved(tile)
540 return cached{data, nil}
541 }
542
543
544
545
546 full := tile
547 full.W = 1 << tile.H
548 if tile != full {
549 data, err := c.client.ReadCache(c.tileCacheKey(full))
550 if err == nil {
551 c.markTileSaved(tile)
552 return cached{data[:len(data)/full.W*tile.W], nil}
553 }
554 }
555
556
557 data, err = c.client.ReadRemote(c.tileRemotePath(tile))
558 if err == nil {
559 return cached{data, nil}
560 }
561
562
563
564
565
566 if tile != full {
567 data, err := c.client.ReadRemote(c.tileRemotePath(full))
568 if err == nil {
569
570
571
572
573
574 return cached{data[:len(data)/full.W*tile.W], nil}
575 }
576 }
577
578
579
580 return cached{nil, err}
581 }).(cached)
582
583 return result.data, result.err
584 }
585
586
587
588 func (c *Conn) markTileSaved(tile tlog.Tile) {
589 c.tileSavedMu.Lock()
590 c.tileSaved[tile] = true
591 c.tileSavedMu.Unlock()
592 }
593
594
595 func (r *tileReader) SaveTiles(tiles []tlog.Tile, data [][]byte) {
596 c := r.c
597
598
599
600 save := make([]bool, len(tiles))
601 c.tileSavedMu.Lock()
602 for i, tile := range tiles {
603 if !c.tileSaved[tile] {
604 save[i] = true
605 c.tileSaved[tile] = true
606 }
607 }
608 c.tileSavedMu.Unlock()
609
610 for i, tile := range tiles {
611 if save[i] {
612
613
614
615
616 c.client.WriteCache(c.name+"/"+tile.Path(), data[i])
617 }
618 }
619 }
620
View as plain text