github.com/ethersphere/bee/v2@v2.2.0/pkg/accesscontrol/history.go (about) 1 // Copyright 2024 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package accesscontrol 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "math" 12 "strconv" 13 "time" 14 15 "github.com/ethersphere/bee/v2/pkg/file" 16 "github.com/ethersphere/bee/v2/pkg/manifest" 17 "github.com/ethersphere/bee/v2/pkg/manifest/mantaray" 18 "github.com/ethersphere/bee/v2/pkg/swarm" 19 ) 20 21 var ( 22 // ErrEndIteration indicates that the iteration terminated. 23 ErrEndIteration = errors.New("end iteration") 24 // ErrUnexpectedType indicates that an error occurred during the mantary-manifest creation. 25 ErrUnexpectedType = errors.New("unexpected type") 26 // ErrInvalidTimestamp indicates that the timestamp given to Lookup is invalid. 27 ErrInvalidTimestamp = errors.New("invalid timestamp") 28 // ErrNotFound is returned when an Entry is not found in the history. 29 ErrNotFound = errors.New("access control: not found") 30 ) 31 32 // History represents the interface for managing access control history. 33 type History interface { 34 // Add adds a new entry to the access control history with the given timestamp and metadata. 35 Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error 36 // Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. 37 Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) 38 // Store stores the history to the underlying storage and returns the reference. 39 Store(ctx context.Context) (swarm.Address, error) 40 } 41 42 var _ History = (*HistoryStruct)(nil) 43 44 // manifestInterface extends the `manifest.Interface` interface and adds a `Root` method. 45 type manifestInterface interface { 46 manifest.Interface 47 Root() *mantaray.Node 48 } 49 50 // HistoryStruct represents an access control history with a mantaray-based manifest. 51 type HistoryStruct struct { 52 manifest manifestInterface 53 ls file.LoadSaver 54 } 55 56 // NewHistory creates a new history with a mantaray-based manifest. 57 func NewHistory(ls file.LoadSaver) (*HistoryStruct, error) { 58 m, err := manifest.NewMantarayManifest(ls, false) 59 if err != nil { 60 return nil, fmt.Errorf("failed to create mantaray manifest: %w", err) 61 } 62 63 mm, ok := m.(manifestInterface) 64 if !ok { 65 return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) 66 } 67 68 return &HistoryStruct{manifest: mm, ls: ls}, nil 69 } 70 71 // NewHistoryReference loads a history with a mantaray-based manifest. 72 func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (*HistoryStruct, error) { 73 m, err := manifest.NewMantarayManifestReference(ref, ls) 74 if err != nil { 75 return nil, fmt.Errorf("failed to create mantaray manifest reference: %w", err) 76 } 77 78 mm, ok := m.(manifestInterface) 79 if !ok { 80 return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) 81 } 82 83 return &HistoryStruct{manifest: mm, ls: ls}, nil 84 } 85 86 // Add adds a new entry to the access control history with the given timestamp and metadata. 87 func (h *HistoryStruct) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { 88 mtdt := map[string]string{} 89 if metadata != nil { 90 mtdt = *metadata 91 } 92 // add timestamps transformed so that the latests timestamp becomes the smallest key. 93 unixTime := time.Now().Unix() 94 if timestamp != nil { 95 unixTime = *timestamp 96 } 97 98 key := strconv.FormatInt(math.MaxInt64-unixTime, 10) 99 err := h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) 100 if err != nil { 101 return fmt.Errorf("failed to add to manifest: %w", err) 102 } 103 104 return nil 105 } 106 107 // Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. 108 func (h *HistoryStruct) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { 109 if timestamp <= 0 { 110 return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrInvalidTimestamp 111 } 112 113 reversedTimestamp := math.MaxInt64 - timestamp 114 node, err := h.lookupNode(ctx, reversedTimestamp) 115 if err != nil { 116 switch { 117 case errors.Is(err, manifest.ErrNotFound): 118 return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrNotFound 119 default: 120 return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), err 121 } 122 } 123 124 if node != nil { 125 return manifest.NewEntry(swarm.NewAddress(node.Entry()), node.Metadata()), nil 126 } 127 128 return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrNotFound 129 } 130 131 func (h *HistoryStruct) lookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { 132 // before node's timestamp is the closest one that is less than or equal to the searched timestamp 133 // for instance: 2030, 2020, 1994 -> search for 2021 -> before is 2020 134 var beforeNode *mantaray.Node 135 // after node's timestamp is after the latest 136 // for instance: 2030, 2020, 1994 -> search for 1980 -> after is 1994 137 var afterNode *mantaray.Node 138 139 walker := func(pathTimestamp []byte, currNode *mantaray.Node, err error) error { 140 if err != nil { 141 return err 142 } 143 144 if currNode.IsValueType() && len(currNode.Entry()) > 0 { 145 afterNode = currNode 146 147 match, err := isBeforeMatch(pathTimestamp, searchedTimestamp) 148 if match { 149 beforeNode = currNode 150 // return error to stop the walk, this is how WalkNode works... 151 return ErrEndIteration 152 } 153 154 return err 155 } 156 157 return nil 158 } 159 160 rootNode := h.manifest.Root() 161 err := rootNode.WalkNode(ctx, []byte{}, h.ls, walker) 162 163 if err != nil && !errors.Is(err, ErrEndIteration) { 164 return nil, fmt.Errorf("history lookup node error: %w", err) 165 } 166 167 if beforeNode != nil { 168 return beforeNode, nil 169 } 170 if afterNode != nil { 171 return afterNode, nil 172 } 173 return nil, nil 174 } 175 176 // Store stores the history to the underlying storage and returns the reference. 177 func (h *HistoryStruct) Store(ctx context.Context) (swarm.Address, error) { 178 return h.manifest.Store(ctx) 179 } 180 181 func bytesToInt64(b []byte) (int64, error) { 182 num, err := strconv.ParseInt(string(b), 10, 64) 183 if err != nil { 184 return -1, err 185 } 186 187 return num, nil 188 } 189 190 func isBeforeMatch(pathTimestamp []byte, searchedTimestamp int64) (bool, error) { 191 targetTimestamp, err := bytesToInt64(pathTimestamp) 192 if err != nil { 193 return false, err 194 } 195 if targetTimestamp == 0 { 196 return false, nil 197 } 198 return searchedTimestamp <= targetTimestamp, nil 199 }