Skip to content

Commit 704acdf

Browse files
committed
firewalldb: add Action CRUD logic
This commit adds a new Action type to the firewalldb package along with Serialization and Deserialization methods for it. It also adds ListAction and ListSessionAction methods to the DB which can be used to paginate through the actions. Various interfaces are also added that will be used by `rules` to access certain RuleActions.
1 parent eaffa4a commit 704acdf

File tree

4 files changed

+1282
-1
lines changed

4 files changed

+1282
-1
lines changed

firewalldb/action_paginator.go

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package firewalldb
2+
3+
import (
4+
"encoding/binary"
5+
6+
"github.com/lightningnetwork/lnd/kvdb"
7+
)
8+
9+
type actionPaginator struct {
10+
// cursor is the cursor which we are using to iterate through a bucket.
11+
cursor kvdb.RCursor
12+
13+
// cfg is the query config which we are using to determine how to
14+
// iterate over the data.
15+
cfg *ListActionsQuery
16+
17+
// filterFn is the filter function which we are using to determine which
18+
// actions should be included in the return list.
19+
filterFn ListActionsFilterFn
20+
21+
// readAction is a closure which we use to read an action from the db
22+
// given a key value pair.
23+
readAction func(k, v []byte) (*Action, error)
24+
}
25+
26+
// paginateActions paginates through the set of actions in the database. It
27+
// uses the provided cursor to determine which keys to iterate over, it uses the
28+
// provided query options to modify how the iteration is done, and it uses the
29+
// filter function to determine which actions to include in the result.
30+
// It returns the list of selected actions, the last index that was read from,
31+
// and the total number of actions that matched the filter function (iff
32+
// cfg.CountAll is set).
33+
func paginateActions(cfg *ListActionsQuery, c kvdb.RCursor,
34+
readAction func(k, v []byte) (*Action, error),
35+
filterFn ListActionsFilterFn) ([]*Action, uint64, uint64, error) {
36+
37+
if cfg == nil {
38+
cfg = &ListActionsQuery{}
39+
}
40+
41+
if filterFn == nil {
42+
filterFn = func(a *Action, reversed bool) (bool, bool) {
43+
return true, true
44+
}
45+
}
46+
47+
p := actionPaginator{
48+
cfg: cfg,
49+
cursor: c,
50+
readAction: readAction,
51+
filterFn: filterFn,
52+
}
53+
54+
if cfg.CountAll {
55+
return p.queryCountAll()
56+
}
57+
58+
actions, lastIndex, err := p.query()
59+
60+
return actions, lastIndex, 0, err
61+
}
62+
63+
// keyValueForIndex seeks our cursor to a given index and returns the key and
64+
// value at that position.
65+
func (p *actionPaginator) keyValueForIndex(index uint64) ([]byte, []byte) {
66+
var keyIndex [8]byte
67+
byteOrder.PutUint64(keyIndex[:], index)
68+
return p.cursor.Seek(keyIndex[:])
69+
}
70+
71+
// lastIndex returns the last value in our index, if our index is empty it
72+
// returns 0.
73+
func (p *actionPaginator) lastIndex() uint64 {
74+
keyIndex, _ := p.cursor.Last()
75+
if keyIndex == nil {
76+
return 0
77+
}
78+
79+
return byteOrder.Uint64(keyIndex)
80+
}
81+
82+
// nextKey is a helper closure to determine what key we should use next when
83+
// we are iterating, depending on whether we are iterating forwards or in
84+
// reverse.
85+
func (p *actionPaginator) nextKey() ([]byte, []byte) {
86+
if p.cfg.Reversed {
87+
return p.cursor.Prev()
88+
}
89+
return p.cursor.Next()
90+
}
91+
92+
// cursorStart gets the index key and value for the first item we are looking
93+
// up, taking into account that we may be paginating in reverse. The index
94+
// offset provided is *excusive* so we will start with the item after the offset
95+
// for forwards queries, and the item before the index for backwards queries.
96+
func (p *actionPaginator) cursorStart() ([]byte, []byte) {
97+
indexKey, indexValue := p.keyValueForIndex(p.cfg.IndexOffset + 1)
98+
99+
// If the query is specifying reverse iteration, then we must
100+
// handle a few offset cases.
101+
if p.cfg.Reversed {
102+
switch {
103+
// This indicates the default case, where no offset was
104+
// specified. In that case we just start from the last
105+
// entry.
106+
case p.cfg.IndexOffset == 0:
107+
indexKey, indexValue = p.cursor.Last()
108+
109+
// This indicates the offset being set to the very
110+
// first entry. Since there are no entries before
111+
// this offset, and the direction is reversed, we can
112+
// return without adding any invoices to the response.
113+
case p.cfg.IndexOffset == 1:
114+
return nil, nil
115+
116+
// If we have been given an index offset that is beyond our last
117+
// index value, we just return the last indexed value in our set
118+
// since we are querying in reverse. We do not cover the case
119+
// where our index offset equals our last index value, because
120+
// index offset is exclusive, so we would want to start at the
121+
// value before our last index.
122+
case p.cfg.IndexOffset > p.lastIndex():
123+
return p.cursor.Last()
124+
125+
// Otherwise we have an index offset which is within our set of
126+
// indexed keys, and we want to start at the item before our
127+
// offset. We seek to our index offset, then return the element
128+
// before it. We do this rather than p.indexOffset-1 to account
129+
// for indexes that have gaps.
130+
default:
131+
p.keyValueForIndex(p.cfg.IndexOffset)
132+
indexKey, indexValue = p.cursor.Prev()
133+
}
134+
}
135+
136+
return indexKey, indexValue
137+
}
138+
139+
// query gets the start point for our index offset and iterates through keys
140+
// in our index until we reach the total number of items required for the query
141+
// or we run out of cursor values. This function takes a fetchAndAppend function
142+
// which is responsible for looking up the entry at that index, adding the entry
143+
// to its set of return items (if desired) and return a boolean which indicates
144+
// whether the item was added. This is required to allow the actionPaginator to
145+
// determine when the response has the maximum number of required items.
146+
func (p *actionPaginator) query() ([]*Action, uint64, error) {
147+
indexKey, indexValue := p.cursorStart()
148+
149+
var (
150+
actions []*Action
151+
lastIndex = uint64(1)
152+
)
153+
for ; indexKey != nil; indexKey, indexValue = p.nextKey() {
154+
// If our current return payload exceeds the max number
155+
// of invoices, then we'll exit now.
156+
if p.cfg.MaxNum != 0 &&
157+
uint64(len(actions)) >= p.cfg.MaxNum {
158+
159+
break
160+
}
161+
162+
lastIndex = binary.BigEndian.Uint64(indexKey)
163+
164+
action, err := p.readAction(indexKey, indexValue)
165+
if err != nil {
166+
return nil, 0, err
167+
}
168+
169+
add, cont := p.filterFn(action, p.cfg.Reversed)
170+
if !cont {
171+
break
172+
}
173+
174+
if !add {
175+
continue
176+
}
177+
178+
actions = append(actions, action)
179+
}
180+
181+
return actions, lastIndex, nil
182+
}
183+
184+
// queryCountAll is similar to query except that instead of only iterating over
185+
// a limited set of actions (as defined by the cfg.IndexOffset and cfg.MaxNum),
186+
// it will instead iterate through all actions so that it can count the total
187+
// number of actions that match the filter function. It will however only
188+
// return actions in the range specified by the cfg.IndexOffset and cfg.MaxNum.
189+
// Callers should be aware that this is a much slower function than query if
190+
// there are a large number of actions in the database.
191+
func (p *actionPaginator) queryCountAll() ([]*Action, uint64, uint64, error) {
192+
// Start at the very first, or very last item.
193+
indexKey, indexValue := p.cursor.First()
194+
if p.cfg.Reversed {
195+
indexKey, indexValue = p.cursor.Last()
196+
}
197+
// Then iterate from first to last and check each action. If passes
198+
// filter, increment total count. Only if the current index is after
199+
// (or before (in reverse mode)) the offset do we add the action & that
200+
// is only if the num we have collected is below MaxNum.
201+
202+
var (
203+
actions []*Action
204+
lastIndex = uint64(1)
205+
beforeIndexOffset = p.cfg.IndexOffset != 0
206+
totalCount uint64
207+
)
208+
for ; indexKey != nil; indexKey, indexValue = p.nextKey() {
209+
action, err := p.readAction(indexKey, indexValue)
210+
if err != nil {
211+
return nil, 0, 0, err
212+
}
213+
214+
add, cont := p.filterFn(action, p.cfg.Reversed)
215+
if !cont {
216+
break
217+
}
218+
219+
if !add {
220+
continue
221+
}
222+
223+
totalCount++
224+
225+
if p.cfg.IndexOffset != 0 &&
226+
binary.BigEndian.Uint64(indexKey) == p.cfg.IndexOffset+1 {
227+
228+
beforeIndexOffset = false
229+
}
230+
231+
// Don't add the action if we are still before the offset.
232+
if beforeIndexOffset {
233+
continue
234+
}
235+
236+
// If our current return payload exceeds the max number
237+
// of invoices, then we continue without adding the action to
238+
// our return list.
239+
if p.cfg.MaxNum != 0 &&
240+
uint64(len(actions)) >= p.cfg.MaxNum {
241+
242+
continue
243+
}
244+
245+
lastIndex = binary.BigEndian.Uint64(indexKey)
246+
actions = append(actions, action)
247+
}
248+
249+
return actions, lastIndex, totalCount, nil
250+
}

0 commit comments

Comments
 (0)