1package storage
2
3import (
4	"crypto/sha256"
5	"encoding/json"
6	"fmt"
7
8	"github.com/theupdateframework/notary"
9	"github.com/theupdateframework/notary/tuf/data"
10	"github.com/theupdateframework/notary/tuf/utils"
11)
12
13// NewMemoryStore returns a MetadataStore that operates entirely in memory.
14// Very useful for testing
15func NewMemoryStore(seed map[data.RoleName][]byte) *MemoryStore {
16	var (
17		consistent = make(map[string][]byte)
18		initial    = make(map[string][]byte)
19	)
20	// add all seed meta to consistent
21	for name, d := range seed {
22		checksum := sha256.Sum256(d)
23		path := utils.ConsistentName(name.String(), checksum[:])
24		initial[name.String()] = d
25		consistent[path] = d
26	}
27
28	return &MemoryStore{
29		data:       initial,
30		consistent: consistent,
31	}
32}
33
34// MemoryStore implements a mock RemoteStore entirely in memory.
35// For testing purposes only.
36type MemoryStore struct {
37	data       map[string][]byte
38	consistent map[string][]byte
39}
40
41// GetSized returns up to size bytes of data references by name.
42// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
43// predefined threshold "notary.MaxDownloadSize", as we will always know the
44// size for everything but a timestamp and sometimes a root,
45// neither of which should be exceptionally large
46func (m MemoryStore) GetSized(name string, size int64) ([]byte, error) {
47	d, ok := m.data[name]
48	if ok {
49		if size == NoSizeLimit {
50			size = notary.MaxDownloadSize
51		}
52		if int64(len(d)) < size {
53			return d, nil
54		}
55		return d[:size], nil
56	}
57	d, ok = m.consistent[name]
58	if ok {
59		if int64(len(d)) < size {
60			return d, nil
61		}
62		return d[:size], nil
63	}
64	return nil, ErrMetaNotFound{Resource: name}
65}
66
67// Get returns the data associated with name
68func (m MemoryStore) Get(name string) ([]byte, error) {
69	if d, ok := m.data[name]; ok {
70		return d, nil
71	}
72	if d, ok := m.consistent[name]; ok {
73		return d, nil
74	}
75	return nil, ErrMetaNotFound{Resource: name}
76}
77
78// Set sets the metadata value for the given name
79func (m *MemoryStore) Set(name string, meta []byte) error {
80	m.data[name] = meta
81
82	parsedMeta := &data.SignedMeta{}
83	err := json.Unmarshal(meta, parsedMeta)
84	if err == nil {
85		// no parse error means this is metadata and not a key, so store by version
86		version := parsedMeta.Signed.Version
87		versionedName := fmt.Sprintf("%d.%s", version, name)
88		m.data[versionedName] = meta
89	}
90
91	checksum := sha256.Sum256(meta)
92	path := utils.ConsistentName(name, checksum[:])
93	m.consistent[path] = meta
94	return nil
95}
96
97// SetMulti sets multiple pieces of metadata for multiple names
98// in a single operation.
99func (m *MemoryStore) SetMulti(metas map[string][]byte) error {
100	for role, blob := range metas {
101		m.Set(role, blob)
102	}
103	return nil
104}
105
106// Remove removes the metadata for a single role - if the metadata doesn't
107// exist, no error is returned
108func (m *MemoryStore) Remove(name string) error {
109	if meta, ok := m.data[name]; ok {
110		checksum := sha256.Sum256(meta)
111		path := utils.ConsistentName(name, checksum[:])
112		delete(m.data, name)
113		delete(m.consistent, path)
114	}
115	return nil
116}
117
118// RemoveAll clears the existing memory store by setting this store as new empty one
119func (m *MemoryStore) RemoveAll() error {
120	*m = *NewMemoryStore(nil)
121	return nil
122}
123
124// Location provides a human readable name for the storage location
125func (m MemoryStore) Location() string {
126	return "memory"
127}
128
129// ListFiles returns a list of all files. The names returned should be
130// usable with Get directly, with no modification.
131func (m *MemoryStore) ListFiles() []string {
132	names := make([]string, 0, len(m.data))
133	for n := range m.data {
134		names = append(names, n)
135	}
136	return names
137}
138