github.com/safing/portbase@v0.19.5/rng/entropy.go (about) 1 package rng 2 3 import ( 4 "context" 5 "encoding/binary" 6 7 "github.com/tevino/abool" 8 9 "github.com/safing/portbase/container" 10 ) 11 12 const ( 13 minFeedEntropy = 256 14 ) 15 16 var rngFeeder = make(chan []byte) 17 18 // The Feeder is used to feed entropy to the RNG. 19 type Feeder struct { 20 input chan *entropyData 21 entropy int64 22 needsEntropy *abool.AtomicBool 23 buffer *container.Container 24 } 25 26 type entropyData struct { 27 data []byte 28 entropy int 29 } 30 31 // NewFeeder returns a new entropy Feeder. 32 func NewFeeder() *Feeder { 33 newFeeder := &Feeder{ 34 input: make(chan *entropyData), 35 needsEntropy: abool.NewBool(true), 36 buffer: container.New(), 37 } 38 module.StartServiceWorker("feeder", 0, newFeeder.run) 39 return newFeeder 40 } 41 42 // NeedsEntropy returns whether the feeder is currently gathering entropy. 43 func (f *Feeder) NeedsEntropy() bool { 44 return f.needsEntropy.IsSet() 45 } 46 47 // SupplyEntropy supplies entropy to the Feeder, it will block until the Feeder has read from it. 48 func (f *Feeder) SupplyEntropy(data []byte, entropy int) { 49 f.input <- &entropyData{ 50 data: data, 51 entropy: entropy, 52 } 53 } 54 55 // SupplyEntropyIfNeeded supplies entropy to the Feeder, but will not block if no entropy is currently needed. 56 func (f *Feeder) SupplyEntropyIfNeeded(data []byte, entropy int) { 57 if f.needsEntropy.IsSet() { 58 return 59 } 60 61 select { 62 case f.input <- &entropyData{ 63 data: data, 64 entropy: entropy, 65 }: 66 default: 67 } 68 } 69 70 // SupplyEntropyAsInt supplies entropy to the Feeder, it will block until the Feeder has read from it. 71 func (f *Feeder) SupplyEntropyAsInt(n int64, entropy int) { 72 b := make([]byte, 8) 73 binary.LittleEndian.PutUint64(b, uint64(n)) 74 f.SupplyEntropy(b, entropy) 75 } 76 77 // SupplyEntropyAsIntIfNeeded supplies entropy to the Feeder, but will not block if no entropy is currently needed. 78 func (f *Feeder) SupplyEntropyAsIntIfNeeded(n int64, entropy int) { 79 if f.needsEntropy.IsSet() { // avoid allocating a slice if possible 80 b := make([]byte, 8) 81 binary.LittleEndian.PutUint64(b, uint64(n)) 82 f.SupplyEntropyIfNeeded(b, entropy) 83 } 84 } 85 86 // CloseFeeder stops the feed processing - the responsible goroutine exits. The input channel is closed and the feeder may not be used anymore in any way. 87 func (f *Feeder) CloseFeeder() { 88 close(f.input) 89 } 90 91 func (f *Feeder) run(ctx context.Context) error { 92 defer f.needsEntropy.UnSet() 93 94 for { 95 // gather 96 f.needsEntropy.Set() 97 gather: 98 for { 99 select { 100 case newEntropy := <-f.input: 101 // check if feed has been closed 102 if newEntropy == nil { 103 return nil 104 } 105 // append to buffer 106 f.buffer.Append(newEntropy.data) 107 f.entropy += int64(newEntropy.entropy) 108 if f.entropy >= minFeedEntropy { 109 break gather 110 } 111 case <-ctx.Done(): 112 return nil 113 } 114 } 115 // feed 116 f.needsEntropy.UnSet() 117 select { 118 case rngFeeder <- f.buffer.CompileData(): 119 case <-ctx.Done(): 120 return nil 121 } 122 f.buffer = container.New() 123 } 124 }