aboutsummaryrefslogtreecommitdiff
path: root/src/gnunet/service/store.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/gnunet/service/store.go')
-rw-r--r--src/gnunet/service/store.go379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/gnunet/service/store.go b/src/gnunet/service/store.go
new file mode 100644
index 0000000..1e5af8b
--- /dev/null
+++ b/src/gnunet/service/store.go
@@ -0,0 +1,379 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package service
20
21import (
22 "context"
23 "database/sql"
24 "encoding/binary"
25 "encoding/hex"
26 "errors"
27 "fmt"
28 "gnunet/crypto"
29 "gnunet/service/dht/blocks"
30 "gnunet/util"
31 "io/ioutil"
32 "os"
33 "strconv"
34 "strings"
35
36 redis "github.com/go-redis/redis/v8"
37)
38
39// Error messages related to the key/value-store implementations
40var (
41 ErrStoreInvalidSpec = fmt.Errorf("Invalid Store specification")
42 ErrStoreUnknown = fmt.Errorf("Unknown Store type")
43 ErrStoreNotAvailable = fmt.Errorf("Store not available")
44)
45
46//------------------------------------------------------------
47// Generic storage interface. Can be used for persistent or
48// transient (caching) storage of key/value data.
49// One set of methods (Get/Put) work on DHT queries and blocks,
50// the other set (GetS, PutS) work on key/value strings.
51// Each custom implementation can decide which sets to support.
52//------------------------------------------------------------
53
54// Store is a key/value storage where the type of the key is either
55// a SHA512 hash value or a string and the value is either a DHT
56// block or a string.
57type Store[K, V any] interface {
58 // Put block into storage under given key
59 Put(key K, val V) error
60
61 // Get block with given key from storage
62 Get(key K) (V, error)
63
64 // List all store queries
65 List() ([]K, error)
66}
67
68//------------------------------------------------------------
69// Types for custom store requirements
70//------------------------------------------------------------
71
72// DHTStore for DHT queries and blocks
73type DHTStore Store[blocks.Query, blocks.Block]
74
75// KVStore for key/value string pairs
76type KVStore Store[string, string]
77
78//------------------------------------------------------------
79// NewDHTStore creates a new storage handler with given spec
80// for use with DHT queries and blocks
81func NewDHTStore(spec string) (DHTStore, error) {
82 specs := strings.SplitN(spec, ":", 2)
83 if len(specs) < 2 {
84 return nil, ErrStoreInvalidSpec
85 }
86 switch specs[0] {
87 //------------------------------------------------------------------
88 // File-base storage
89 //------------------------------------------------------------------
90 case "file_store":
91 return NewFileStore(specs[1])
92 case "file_cache":
93 if len(specs) < 3 {
94 return nil, ErrStoreInvalidSpec
95 }
96 return NewFileCache(specs[1], specs[2])
97 }
98 return nil, ErrStoreUnknown
99}
100
101//------------------------------------------------------------
102// NewKVStore creates a new storage handler with given spec
103// for use with key/value string pairs.
104func NewKVStore(spec string) (KVStore, error) {
105 specs := strings.SplitN(spec, ":", 2)
106 if len(specs) < 2 {
107 return nil, ErrStoreInvalidSpec
108 }
109 switch specs[0] {
110 //--------------------------------------------------------------
111 // Redis service
112 //--------------------------------------------------------------
113 case "redis":
114 if len(specs) < 4 {
115 return nil, ErrStoreInvalidSpec
116 }
117 return NewRedisStore(specs[1], specs[2], specs[3])
118
119 //--------------------------------------------------------------
120 // SQL database service
121 //--------------------------------------------------------------
122 case "sql":
123 if len(specs) < 4 {
124 return nil, ErrStoreInvalidSpec
125 }
126 return NewSQLStore(specs[1])
127 }
128 return nil, errors.New("unknown storage mechanism")
129}
130
131//------------------------------------------------------------
132// Filesystem-based storage
133//------------------------------------------------------------
134
135// FileStore implements a filesystem-based storage mechanism for
136// DHT queries and blocks.
137type FileStore struct {
138 path string // storage path
139 cached []*crypto.HashCode // list of cached entries (key)
140 wrPos int // write position in cyclic list
141}
142
143// NewFileStore instantiates a new file storage.
144func NewFileStore(path string) (DHTStore, error) {
145 // create file store
146 return &FileStore{
147 path: path,
148 }, nil
149}
150
151// NewFileCache instantiates a new file-based cache.
152func NewFileCache(path, param string) (DHTStore, error) {
153 // remove old cache content
154 os.RemoveAll(path)
155
156 // get number of cache entries
157 num, err := strconv.ParseUint(param, 10, 32)
158 if err != nil {
159 return nil, err
160 }
161 // create file store
162 return &FileStore{
163 path: path,
164 cached: make([]*crypto.HashCode, num),
165 wrPos: 0,
166 }, nil
167}
168
169// Put block into storage under given key
170func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
171 // get query parameters for entry
172 var (
173 btype uint16 // block type
174 expire util.AbsoluteTime // block expiration
175 )
176 query.Get("blkType", &btype)
177 query.Get("expire", &expire)
178
179 // are we in caching mode?
180 if s.cached != nil {
181 // release previous block if defined
182 if key := s.cached[s.wrPos]; key != nil {
183 // get path and filename from key
184 path, fname := s.expandPath(key)
185 if err = os.Remove(path + "/" + fname); err != nil {
186 return
187 }
188 // free slot
189 s.cached[s.wrPos] = nil
190 }
191 }
192 // get path and filename from key
193 path, fname := s.expandPath(query.Key())
194 // make sure the path exists
195 if err = os.MkdirAll(path, 0755); err != nil {
196 return
197 }
198 // write to file for storage
199 var fp *os.File
200 if fp, err = os.Create(path + "/" + fname); err == nil {
201 defer fp.Close()
202 // write block data
203 if err = binary.Write(fp, binary.BigEndian, btype); err == nil {
204 if err = binary.Write(fp, binary.BigEndian, expire); err == nil {
205 _, err = fp.Write(block.Data())
206 }
207 }
208 }
209 // update cache list
210 if s.cached != nil {
211 s.cached[s.wrPos] = query.Key()
212 s.wrPos = (s.wrPos + 1) % len(s.cached)
213 }
214 return
215}
216
217// Get block with given key from storage
218func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
219 // get requested block type
220 var (
221 btype uint16 = blocks.DHT_BLOCK_ANY
222 blkt uint16 // actual block type
223 expire util.AbsoluteTime // expiration date
224 data []byte // block data
225 )
226 query.Get("blkType", &btype)
227
228 // get path and filename from key
229 path, fname := s.expandPath(query.Key())
230 // read file content (block data)
231 var file *os.File
232 if file, err = os.Open(path + "/" + fname); err != nil {
233 return
234 }
235 // read block data
236 if err = binary.Read(file, binary.BigEndian, &blkt); err == nil {
237 if btype != blocks.DHT_BLOCK_ANY && btype != blkt {
238 // block types not matching
239 return
240 }
241 if err = binary.Read(file, binary.BigEndian, &expire); err == nil {
242 if data, err = ioutil.ReadAll(file); err == nil {
243 block = blocks.NewGenericBlock(data)
244 }
245 }
246 }
247 return
248}
249
250// Get a list of all stored block keys (generic query).
251func (s *FileStore) List() ([]blocks.Query, error) {
252 return make([]blocks.Query, 0), nil
253}
254
255// expandPath returns the full path to the file for given key.
256func (s *FileStore) expandPath(key *crypto.HashCode) (string, string) {
257 h := hex.EncodeToString(key.Bits)
258 return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
259}
260
261//------------------------------------------------------------
262// Redis: only use for caching purposes on key/value strings
263//------------------------------------------------------------
264
265// RedisStore uses a (local) Redis server for key/value storage
266type RedisStore struct {
267 client *redis.Client // client connection
268 db int // index to database
269}
270
271// NewRedisStore creates a Redis service client instance.
272func NewRedisStore(addr, passwd, db string) (s KVStore, err error) {
273 kvs := new(RedisStore)
274 if kvs.db, err = strconv.Atoi(db); err != nil {
275 err = ErrStoreInvalidSpec
276 return
277 }
278 kvs.client = redis.NewClient(&redis.Options{
279 Addr: addr,
280 Password: passwd,
281 DB: kvs.db,
282 })
283 if kvs.client == nil {
284 err = ErrStoreNotAvailable
285 }
286 s = kvs
287 return
288}
289
290// Put block into storage under given key
291func (s *RedisStore) Put(key string, value string) (err error) {
292 return s.client.Set(context.TODO(), key, value, 0).Err()
293}
294
295// Get block with given key from storage
296func (s *RedisStore) Get(key string) (value string, err error) {
297 return s.client.Get(context.TODO(), key).Result()
298}
299
300// List all keys in store
301func (s *RedisStore) List() (keys []string, err error) {
302 var (
303 crs uint64
304 segm []string
305 ctx = context.TODO()
306 )
307 keys = make([]string, 0)
308 for {
309 segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
310 if err != nil {
311 return
312 }
313 if crs == 0 {
314 break
315 }
316 keys = append(keys, segm...)
317 }
318 return
319}
320
321//------------------------------------------------------------
322// SQL-based key-value-store
323//------------------------------------------------------------
324
325// SQLStore for generic SQL database handling
326type SQLStore struct {
327 db *util.DbConn
328}
329
330// NewSQLStore creates a new SQL-based key/value store.
331func NewSQLStore(spec string) (s KVStore, err error) {
332 kvs := new(SQLStore)
333
334 // connect to SQL database
335 kvs.db, err = util.DbPool.Connect(spec)
336 if err != nil {
337 return nil, err
338 }
339 // get number of key/value pairs (as a check for existing table)
340 row := kvs.db.QueryRow("select count(*) from store")
341 var num int
342 if row.Scan(&num) != nil {
343 return nil, ErrStoreNotAvailable
344 }
345 return kvs, nil
346
347}
348
349// Put a key/value pair into the store
350func (s *SQLStore) Put(key string, value string) error {
351 _, err := s.db.Exec("insert into store(key,value) values(?,?)", key, value)
352 return err
353}
354
355// Get a value for a given key from store
356func (s *SQLStore) Get(key string) (value string, err error) {
357 row := s.db.QueryRow("select value from store where key=?", key)
358 err = row.Scan(&value)
359 return
360}
361
362// List all keys in store
363func (s *SQLStore) List() (keys []string, err error) {
364 var (
365 rows *sql.Rows
366 key string
367 )
368 keys = make([]string, 0)
369 rows, err = s.db.Query("select key from store")
370 if err == nil {
371 for rows.Next() {
372 if err = rows.Scan(&key); err != nil {
373 break
374 }
375 keys = append(keys, key)
376 }
377 }
378 return
379}