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  }