github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/emulator/maps_hash_lru.go (about)

     1  package emulator
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"fmt"
     7  
     8  	"github.com/dylandreimerink/gobpfld/bpfsys"
     9  )
    10  
    11  type HashMapLRU struct {
    12  	AbstractMap
    13  
    14  	// Go can't use slices as map values, so what we do is we sha256 hash the slice which always results in a
    15  	// uniform sized array which we can use as key. Since we now don't index by the actual key, we also need to
    16  	// store the actual key value so we can return k/v pairs
    17  
    18  	KeysMap map[hashKey]*ByteMemory
    19  	Values  map[hashKey]*ByteMemory
    20  
    21  	// Keep track of recent usage. Every time a key is updated or looked up, it is moved to the top of the slice.
    22  	// The bottom most key is thus always the least recently used and will be overwritten if an entry is added to
    23  	// the map while full.
    24  	UsageList []hashKey
    25  }
    26  
    27  type hashKey [sha256.Size]byte
    28  
    29  func (m *HashMapLRU) Init() error {
    30  	m.KeysMap = make(map[hashKey]*ByteMemory)
    31  	m.Values = make(map[hashKey]*ByteMemory)
    32  
    33  	// NOTE: we always ignore the BPFMapFlagsNoPreAlloc flag since we never pre-alloc, it is an optimization which
    34  	// we don't need for the current purposes of the emulator.
    35  
    36  	return nil
    37  }
    38  
    39  func (m *HashMapLRU) Keys() []RegisterValue {
    40  	keys := make([]RegisterValue, len(m.KeysMap))
    41  	i := 0
    42  	for _, val := range m.KeysMap {
    43  		keys[i] = &MemoryPtr{
    44  			Memory: val.Clone(), // Clone since we don't want to give access to a modifyable reference
    45  		}
    46  		i++
    47  	}
    48  	return keys
    49  }
    50  
    51  func (m *HashMapLRU) promote(key hashKey) {
    52  	cur := 0
    53  	for i, k := range m.UsageList {
    54  		if k == key {
    55  			cur = i
    56  			break
    57  		}
    58  	}
    59  	// Key not found, or already at the top, nothing to do
    60  	if cur == 0 {
    61  		return
    62  	}
    63  
    64  	// Move all keys above cur 1 index down
    65  	copy(m.UsageList[1:cur+1], m.UsageList[:cur])
    66  	// Set cur a index 0
    67  	m.UsageList[0] = key
    68  }
    69  
    70  func (m *HashMapLRU) Lookup(key RegisterValue) (RegisterValue, error) {
    71  	keyPtr, ok := key.(PointerValue)
    72  	if !ok {
    73  		return nil, errMapKeyNoPtr
    74  	}
    75  
    76  	keyVal, err := keyPtr.ReadRange(0, int(m.Def.KeySize))
    77  	if !ok {
    78  		return nil, fmt.Errorf("key read range: %w", err)
    79  	}
    80  
    81  	keyHash := sha256.Sum256(keyVal)
    82  	value, found := m.Values[keyHash]
    83  	if !found {
    84  		// Return NULL if value doesn't exist
    85  		return newIMM(0), nil
    86  	}
    87  
    88  	m.promote(keyHash)
    89  
    90  	return &MemoryPtr{Memory: value, Offset: 0}, nil
    91  }
    92  
    93  func (m *HashMapLRU) Update(
    94  	key RegisterValue,
    95  	value RegisterValue,
    96  	flags bpfsys.BPFAttrMapElemFlags,
    97  ) (
    98  	RegisterValue,
    99  	error,
   100  ) {
   101  	keyPtr, ok := key.(PointerValue)
   102  	if !ok {
   103  		return nil, errMapKeyNoPtr
   104  	}
   105  
   106  	keyVal, err := keyPtr.ReadRange(0, int(m.Def.KeySize))
   107  	if !ok {
   108  		return nil, fmt.Errorf("key read range: %w", err)
   109  	}
   110  
   111  	keyHash := sha256.Sum256(keyVal)
   112  	existingValue, found := m.Values[keyHash]
   113  	if !found {
   114  		// If map is full
   115  		if len(m.Values)+1 > int(m.Def.MaxEntries) {
   116  			// Find the least recently used key, and delete it,
   117  			lru := m.UsageList[len(m.UsageList)-1]
   118  			m.delete(lru)
   119  		}
   120  
   121  		existingValue = &ByteMemory{
   122  			MemName: fmt.Sprintf("%s[0x%s]", m.Name, hex.EncodeToString(keyVal)),
   123  			Backing: make([]byte, m.Def.ValueSize),
   124  		}
   125  	}
   126  	existingKey, found := m.KeysMap[keyHash]
   127  	if !found {
   128  		existingKey = &ByteMemory{
   129  			MemName: hex.EncodeToString(keyVal),
   130  			Backing: make([]byte, m.Def.KeySize),
   131  		}
   132  	}
   133  
   134  	valPtr, ok := value.(PointerValue)
   135  	if !ok {
   136  		return nil, errMapValNoPtr
   137  	}
   138  
   139  	valVal, err := valPtr.ReadRange(0, int(m.Def.ValueSize))
   140  	if !ok {
   141  		return nil, fmt.Errorf("val read range: %w", err)
   142  	}
   143  
   144  	// If the key is new, add it to the usage list
   145  	if !found {
   146  		// Add new key to the usage list
   147  		m.UsageList = append(m.UsageList, keyHash)
   148  	}
   149  	// Promote the key, since it is now the most recently used
   150  	m.promote(keyHash)
   151  
   152  	// We can safely assing, valVal is otherwise unreferenced
   153  	existingValue.Backing = valVal
   154  	m.Values[keyHash] = existingValue
   155  
   156  	// We can safely assing, valVal is otherwise unreferenced
   157  	existingKey.Backing = keyVal
   158  	m.KeysMap[keyHash] = existingKey
   159  
   160  	return newIMM(0), nil
   161  }
   162  
   163  func (m *HashMapLRU) delete(key hashKey) {
   164  	cur := -1
   165  	for i, k := range m.UsageList {
   166  		if k == key {
   167  			cur = i
   168  			break
   169  		}
   170  	}
   171  	// Key not found, nothing to do
   172  	if cur == -1 {
   173  		return
   174  	}
   175  
   176  	// Move all keys below cur up by 1 spot
   177  	copy(m.UsageList[cur:], m.UsageList[cur+1:])
   178  	// Shrink the slice
   179  	m.UsageList = m.UsageList[:len(m.UsageList)-1]
   180  
   181  	delete(m.KeysMap, key)
   182  	delete(m.Values, key)
   183  }
   184  
   185  func (m *HashMapLRU) Delete(key RegisterValue, flags bpfsys.BPFAttrMapElemFlags) error {
   186  	keyPtr, ok := key.(PointerValue)
   187  	if !ok {
   188  		return errMapKeyNoPtr
   189  	}
   190  
   191  	keyVal, err := keyPtr.ReadRange(0, int(m.Def.KeySize))
   192  	if !ok {
   193  		return fmt.Errorf("key read range: %w", err)
   194  	}
   195  	keyHash := sha256.Sum256(keyVal)
   196  
   197  	m.delete(keyHash)
   198  
   199  	return nil
   200  }