Skip to content

Commit 1701ee2

Browse files
authored
separate encrypted and plaintext api and streamline usage (#16)
* separate encrypted and plaintext api and streamline usage * optimise encrypted and plaintext storages * lower allocs per operating by using syscalls instead of fascades * better fd flags * use syscall to obtain fd directly
1 parent bd656b2 commit 1701ee2

File tree

10 files changed

+875
-487
lines changed

10 files changed

+875
-487
lines changed

.github/workflows/health.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ jobs:
3434
3535
- name: Unit Test
3636
run: |
37-
go test -v ./... -timeout=10s
37+
GOMAXPROCS=1 go test -v ./... -timeout=2m
38+
39+
- name: Benchmarck Test
40+
run: |
41+
GOMAXPROCS=1 go test -v ./... -run=^$ -bench=. -benchmem -timeout=2m

dev/lifecycle/test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ cd /go/src/github.com/jancajthaml-openbank/${TARGET_PACKAGE}
4343
-v ./... \
4444
-coverprofile=${coverage_out} \
4545
-coverpkg=./... \
46-
-timeout=20s | tee ${test_out}
46+
-timeout=2m | tee ${test_out}
4747

4848
go2xunit \
4949
-fail \
@@ -62,4 +62,4 @@ GOMAXPROCS=1 \
6262
-run=^$ \
6363
-bench=. \
6464
-benchmem \
65-
-timeout=1m
65+
-timeout=2m

storage.go

Lines changed: 12 additions & 339 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2016-2019, Jan Cajthaml <[email protected]>
1+
// Copyright (c) 2016-2020, Jan Cajthaml <[email protected]>
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -14,342 +14,15 @@
1414

1515
package storage
1616

17-
import (
18-
"bytes"
19-
"crypto/aes"
20-
"crypto/cipher"
21-
"crypto/rand"
22-
"fmt"
23-
"io"
24-
"os"
25-
"path/filepath"
26-
"reflect"
27-
"runtime"
28-
"sort"
29-
"syscall"
30-
"unsafe"
31-
)
32-
33-
func nameFromDirent(dirent *syscall.Dirent) []byte {
34-
reg := int(uint64(dirent.Reclen) - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)))
35-
36-
var name []byte
37-
header := (*reflect.SliceHeader)(unsafe.Pointer(&name))
38-
header.Cap = reg
39-
header.Len = reg
40-
header.Data = uintptr(unsafe.Pointer(&dirent.Name[0]))
41-
42-
if index := bytes.IndexByte(name, 0); index >= 0 {
43-
header.Cap = index
44-
header.Len = index
45-
}
46-
47-
return name
48-
}
49-
50-
// Storage is a fascade to access storage
51-
type Storage struct {
52-
Root string
53-
encryptionKey []byte
54-
bufferSize int
55-
}
56-
57-
// NewStorage returns new storage over given root
58-
func NewStorage(root string) Storage {
59-
if root == "" || os.MkdirAll(filepath.Clean(root), os.ModePerm) != nil {
60-
panic("unable to assert root storage directory")
61-
}
62-
return Storage{
63-
Root: root,
64-
bufferSize: 8192,
65-
}
66-
}
67-
68-
// SetEncryptionKey sets AES encryption key for data encryption and decryption
69-
func (storage *Storage) SetEncryptionKey(key []byte) {
70-
if storage == nil {
71-
return
72-
}
73-
storage.encryptionKey = key
74-
}
75-
76-
// ListDirectory returns sorted slice of item names in given absolute path
77-
// default sorting is ascending
78-
func (storage Storage) ListDirectory(path string, ascending bool) (result []string, err error) {
79-
var (
80-
n int
81-
dh *os.File
82-
de *syscall.Dirent
83-
)
84-
85-
dh, err = os.Open(filepath.Clean(storage.Root + "/" + path))
86-
if err != nil {
87-
return
88-
}
89-
90-
fd := int(dh.Fd())
91-
result = make([]string, 0)
92-
93-
scratchBuffer := make([]byte, storage.bufferSize)
94-
95-
for {
96-
n, err = syscall.ReadDirent(fd, scratchBuffer)
97-
runtime.KeepAlive(dh)
98-
if err != nil {
99-
if r := dh.Close(); r != nil {
100-
err = r
101-
}
102-
return
103-
}
104-
if n <= 0 {
105-
break
106-
}
107-
buf := scratchBuffer[:n]
108-
for len(buf) > 0 {
109-
de = (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
110-
buf = buf[de.Reclen:]
111-
112-
if de.Ino == 0 {
113-
continue
114-
}
115-
116-
nameSlice := nameFromDirent(de)
117-
switch len(nameSlice) {
118-
case 0:
119-
continue
120-
case 1:
121-
if nameSlice[0] == '.' {
122-
continue
123-
}
124-
case 2:
125-
if nameSlice[0] == '.' && nameSlice[1] == '.' {
126-
continue
127-
}
128-
}
129-
result = append(result, string(nameSlice))
130-
}
131-
}
132-
133-
if r := dh.Close(); r != nil {
134-
err = r
135-
return
136-
}
137-
138-
if ascending {
139-
sort.Slice(result, func(i, j int) bool {
140-
return result[i] < result[j]
141-
})
142-
} else {
143-
sort.Slice(result, func(i, j int) bool {
144-
return result[i] > result[j]
145-
})
146-
}
147-
148-
return
149-
}
150-
151-
// CountFiles returns number of items in directory
152-
func (storage Storage) CountFiles(path string) (result int, err error) {
153-
var (
154-
n int
155-
dh *os.File
156-
de *syscall.Dirent
157-
)
158-
159-
dh, err = os.Open(filepath.Clean(storage.Root + "/" + path))
160-
if err != nil {
161-
return
162-
}
163-
164-
fd := int(dh.Fd())
165-
166-
scratchBuffer := make([]byte, storage.bufferSize)
167-
168-
for {
169-
n, err = syscall.ReadDirent(fd, scratchBuffer)
170-
runtime.KeepAlive(dh)
171-
if err != nil {
172-
if r := dh.Close(); r != nil {
173-
err = r
174-
}
175-
return
176-
}
177-
if n <= 0 {
178-
break
179-
}
180-
buf := scratchBuffer[:n]
181-
for len(buf) > 0 {
182-
de = (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
183-
buf = buf[de.Reclen:]
184-
if de.Ino == 0 || de.Type != syscall.DT_REG {
185-
continue
186-
}
187-
result++
188-
}
189-
}
190-
191-
if r := dh.Close(); r != nil {
192-
err = r
193-
}
194-
195-
return
196-
}
197-
198-
// Exists returns true if absolute path exists
199-
func (storage Storage) Exists(path string) (bool, error) {
200-
var (
201-
trusted = new(syscall.Stat_t)
202-
cleaned = filepath.Clean(storage.Root + "/" + path)
203-
err error
204-
)
205-
err = syscall.Stat(cleaned, trusted)
206-
if err == nil {
207-
return true, nil
208-
} else if os.IsNotExist(err) {
209-
return false, nil
210-
} else {
211-
return false, err
212-
}
213-
}
214-
215-
// TouchFile creates files given absolute path if file does not already exist
216-
func (storage Storage) TouchFile(path string) error {
217-
cleanedPath := filepath.Clean(storage.Root + "/" + path)
218-
if err := os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm); err != nil {
219-
return err
220-
}
221-
f, err := os.OpenFile(cleanedPath, os.O_RDONLY|os.O_CREATE|os.O_EXCL, os.ModePerm)
222-
if err != nil {
223-
return err
224-
}
225-
defer f.Close()
226-
return nil
227-
}
228-
229-
// GetFileReader creates file io.Reader
230-
func (storage Storage) GetFileReader(path string) (*fileReader, error) {
231-
f, err := os.OpenFile(filepath.Clean(storage.Root+"/"+path), os.O_RDONLY, os.ModePerm)
232-
if err != nil {
233-
return nil, err
234-
}
235-
236-
reader := new(fileReader)
237-
reader.source = f
238-
239-
return reader, nil
240-
}
241-
242-
// ReadFileFully reads whole file given absolute path
243-
func (storage Storage) ReadFileFully(path string) ([]byte, error) {
244-
f, err := os.OpenFile(filepath.Clean(storage.Root+"/"+path), os.O_RDONLY, os.ModePerm)
245-
if err != nil {
246-
return nil, err
247-
}
248-
defer f.Close()
249-
fi, err := f.Stat()
250-
if err != nil {
251-
return nil, err
252-
}
253-
buf := make([]byte, fi.Size())
254-
_, err = f.Read(buf)
255-
if err != nil && err != io.EOF {
256-
return nil, err
257-
}
258-
return buf, nil
259-
}
260-
261-
// WriteFile writes data given absolute path to a file if that file does not
262-
// already exists
263-
func (storage Storage) WriteFile(path string, data []byte) error {
264-
cleanedPath := filepath.Clean(storage.Root + "/" + path)
265-
if err := os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm); err != nil {
266-
return err
267-
}
268-
f, err := os.OpenFile(cleanedPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm)
269-
if err != nil {
270-
return err
271-
}
272-
defer f.Close()
273-
if _, err := f.Write(data); err != nil {
274-
return err
275-
}
276-
return nil
277-
}
278-
279-
// DeleteFile removes file given absolute path if that file does exists
280-
func (storage Storage) DeleteFile(path string) error {
281-
return os.Remove(filepath.Clean(storage.Root + "/" + path))
282-
}
283-
284-
// UpdateFile rewrite file with data given absolute path to a file if that file
285-
// exist
286-
func (storage Storage) UpdateFile(path string, data []byte) (err error) {
287-
cleanedPath := filepath.Clean(storage.Root + "/" + path)
288-
var f *os.File
289-
f, err = os.OpenFile(cleanedPath, os.O_WRONLY|os.O_TRUNC, os.ModePerm)
290-
if err != nil {
291-
return
292-
}
293-
defer f.Close()
294-
_, err = f.Write(data)
295-
return
296-
}
297-
298-
// AppendFile appens data given absolute path to a file, creates it if it does
299-
// not exist
300-
func (storage Storage) AppendFile(path string, data []byte) (err error) {
301-
cleanedPath := filepath.Clean(storage.Root + "/" + path)
302-
err = os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm)
303-
if err != nil {
304-
return err
305-
}
306-
var f *os.File
307-
f, err = os.OpenFile(cleanedPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
308-
if err != nil {
309-
return
310-
}
311-
defer f.Close()
312-
_, err = f.Write(data)
313-
return
314-
}
315-
316-
// Encrypt data with encryption key
317-
func (storage Storage) Encrypt(data []byte) ([]byte, error) {
318-
if len(storage.encryptionKey) == 0 {
319-
return nil, fmt.Errorf("no encryption key setup")
320-
}
321-
block, err := aes.NewCipher(storage.encryptionKey)
322-
if err != nil {
323-
return nil, err
324-
}
325-
ciphertext := make([]byte, aes.BlockSize+len(data))
326-
iv := ciphertext[:aes.BlockSize]
327-
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
328-
return nil, err
329-
}
330-
cfb := cipher.NewCFBEncrypter(block, iv)
331-
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(data))
332-
return ciphertext, nil
333-
}
334-
335-
// Decrypt data with encryption key
336-
func (storage Storage) Decrypt(data []byte) ([]byte, error) {
337-
if len(storage.encryptionKey) == 0 {
338-
return nil, fmt.Errorf("no encryption key setup")
339-
}
340-
block, err := aes.NewCipher(storage.encryptionKey)
341-
if err != nil {
342-
return nil, err
343-
}
344-
if len(data) < aes.BlockSize {
345-
return nil, fmt.Errorf("invalid blocksize expected %d but actual is %d", aes.BlockSize, len(data))
346-
}
347-
348-
plaintext := make([]byte, len(data))
349-
copy(plaintext, data)
350-
iv := plaintext[:aes.BlockSize]
351-
plaintext = plaintext[aes.BlockSize:]
352-
cfb := cipher.NewCFBDecrypter(block, iv)
353-
cfb.XORKeyStream(plaintext, plaintext)
354-
return plaintext, nil
17+
// Storage hold contract for storage API
18+
type Storage interface {
19+
ListDirectory(string) ([]string, error)
20+
CountFiles(string) (int, error)
21+
Exists(string) (bool, error)
22+
TouchFile(string) error
23+
ReadFileFully(string) ([]byte, error)
24+
WriteFile(string, []byte) error
25+
DeleteFile(string) error
26+
UpdateFile(string, []byte) error
27+
AppendFile(string, []byte) error
35528
}

0 commit comments

Comments
 (0)