1// Copyright 2016 Keybase Inc. All rights reserved.
2// Use of this source code is governed by a BSD
3// license that can be found in the LICENSE file.
4
5package libkbfs
6
7import (
8	"context"
9	"os"
10	"path/filepath"
11	"testing"
12
13	"github.com/keybase/client/go/kbfs/ioutil"
14	"github.com/keybase/client/go/kbfs/kbfsblock"
15	"github.com/keybase/client/go/kbfs/kbfscodec"
16	"github.com/keybase/client/go/kbfs/kbfscrypto"
17	"github.com/keybase/client/go/protocol/keybase1"
18	"github.com/stretchr/testify/assert"
19	"github.com/stretchr/testify/require"
20)
21
22func setupBlockDiskStoreTest(t *testing.T) (tempdir string, s *blockDiskStore) {
23	codec := kbfscodec.NewMsgpack()
24
25	tempdir, err := ioutil.TempDir(os.TempDir(), "block_disk_store")
26	require.NoError(t, err)
27
28	s = makeBlockDiskStore(codec, tempdir)
29	return tempdir, s
30}
31
32func teardownBlockDiskStoreTest(t *testing.T, tempdir string) {
33	err := ioutil.RemoveAll(tempdir)
34	assert.NoError(t, err)
35}
36
37func putBlockDisk(
38	ctx context.Context, t *testing.T, s *blockDiskStore, data []byte) (
39	kbfsblock.ID, kbfsblock.Context, kbfscrypto.BlockCryptKeyServerHalf) {
40	bID, err := kbfsblock.MakePermanentID(
41		data, kbfscrypto.EncryptionSecretboxWithKeyNonce)
42	require.NoError(t, err)
43
44	uid1 := keybase1.MakeTestUID(1)
45	bCtx := kbfsblock.MakeFirstContext(
46		uid1.AsUserOrTeam(), keybase1.BlockType_DATA)
47	serverHalf, err := kbfscrypto.MakeRandomBlockCryptKeyServerHalf()
48	require.NoError(t, err)
49
50	didPut, err := s.put(ctx, true, bID, bCtx, data, serverHalf)
51	require.NoError(t, err)
52	err = s.addReference(ctx, bID, bCtx, "tag")
53	require.NoError(t, err)
54	require.True(t, didPut)
55
56	return bID, bCtx, serverHalf
57}
58
59func addBlockDiskRef(
60	ctx context.Context, t *testing.T, s *blockDiskStore,
61	bID kbfsblock.ID) kbfsblock.Context {
62	nonce, err := kbfsblock.MakeRefNonce()
63	require.NoError(t, err)
64
65	uid1 := keybase1.MakeTestUID(1)
66	uid2 := keybase1.MakeTestUID(2)
67	bCtx2 := kbfsblock.MakeContext(
68		uid1.AsUserOrTeam(), uid2.AsUserOrTeam(), nonce,
69		keybase1.BlockType_DATA)
70	err = s.addReference(ctx, bID, bCtx2, "")
71	require.NoError(t, err)
72	return bCtx2
73}
74
75func getAndCheckBlockDiskData(
76	ctx context.Context, t *testing.T, s *blockDiskStore,
77	bID kbfsblock.ID, bCtx kbfsblock.Context, expectedData []byte,
78	expectedServerHalf kbfscrypto.BlockCryptKeyServerHalf) {
79	data, serverHalf, err := s.getDataWithContext(ctx, bID, bCtx)
80	require.NoError(t, err)
81	require.Equal(t, expectedData, data)
82	require.Equal(t, expectedServerHalf, serverHalf)
83}
84
85func TestBlockDiskStoreBasic(t *testing.T) {
86	tempdir, s := setupBlockDiskStoreTest(t)
87	defer teardownBlockDiskStoreTest(t, tempdir)
88	ctx := context.Background()
89
90	// Put the block.
91	data := []byte{1, 2, 3, 4}
92	bID, bCtx, serverHalf := putBlockDisk(ctx, t, s, data)
93
94	// Make sure we get the same block back.
95	getAndCheckBlockDiskData(ctx, t, s, bID, bCtx, data, serverHalf)
96
97	// Add a reference.
98	bCtx2 := addBlockDiskRef(ctx, t, s, bID)
99
100	// Make sure we get the same block via that reference.
101	getAndCheckBlockDiskData(ctx, t, s, bID, bCtx2, data, serverHalf)
102
103	// Shutdown and restart.
104	s = makeBlockDiskStore(s.codec, tempdir)
105
106	// Make sure we get the same block for both refs.
107
108	getAndCheckBlockDiskData(ctx, t, s, bID, bCtx, data, serverHalf)
109	getAndCheckBlockDiskData(ctx, t, s, bID, bCtx2, data, serverHalf)
110}
111
112func TestBlockDiskStoreAddReference(t *testing.T) {
113	tempdir, s := setupBlockDiskStoreTest(t)
114	defer teardownBlockDiskStoreTest(t, tempdir)
115	ctx := context.Background()
116
117	data := []byte{1, 2, 3, 4}
118	bID, err := kbfsblock.MakePermanentID(
119		data, kbfscrypto.EncryptionSecretboxWithKeyNonce)
120	require.NoError(t, err)
121
122	// Add a reference, which should succeed.
123	bCtx := addBlockDiskRef(ctx, t, s, bID)
124
125	// Of course, the block get should still fail.
126	_, _, err = s.getDataWithContext(ctx, bID, bCtx)
127	require.Equal(t, blockNonExistentError{bID}, err)
128}
129
130func TestBlockDiskStoreArchiveReferences(t *testing.T) {
131	tempdir, s := setupBlockDiskStoreTest(t)
132	defer teardownBlockDiskStoreTest(t, tempdir)
133	ctx := context.Background()
134
135	// Put the block.
136	data := []byte{1, 2, 3, 4}
137	bID, bCtx, serverHalf := putBlockDisk(ctx, t, s, data)
138
139	// Add a reference.
140	bCtx2 := addBlockDiskRef(ctx, t, s, bID)
141
142	// Archive references.
143	err := s.archiveReferences(
144		ctx, kbfsblock.ContextMap{bID: {bCtx, bCtx2}}, "")
145	require.NoError(t, err)
146
147	// Get block should still succeed.
148	getAndCheckBlockDiskData(ctx, t, s, bID, bCtx, data, serverHalf)
149}
150
151func TestBlockDiskStoreArchiveNonExistentReference(t *testing.T) {
152	tempdir, s := setupBlockDiskStoreTest(t)
153	defer teardownBlockDiskStoreTest(t, tempdir)
154	ctx := context.Background()
155
156	uid1 := keybase1.MakeTestUID(1)
157
158	bCtx := kbfsblock.MakeFirstContext(
159		uid1.AsUserOrTeam(), keybase1.BlockType_DATA)
160
161	data := []byte{1, 2, 3, 4}
162	bID, err := kbfsblock.MakePermanentID(
163		data, kbfscrypto.EncryptionSecretboxWithKeyNonce)
164	require.NoError(t, err)
165
166	// Archive references.
167	err = s.archiveReferences(ctx, kbfsblock.ContextMap{bID: {bCtx}}, "")
168	require.NoError(t, err)
169}
170
171func TestBlockDiskStoreRemoveReferences(t *testing.T) {
172	tempdir, s := setupBlockDiskStoreTest(t)
173	defer teardownBlockDiskStoreTest(t, tempdir)
174	ctx := context.Background()
175
176	// Put the block.
177	data := []byte{1, 2, 3, 4}
178	bID, bCtx, serverHalf := putBlockDisk(ctx, t, s, data)
179
180	// Add a reference.
181	bCtx2 := addBlockDiskRef(ctx, t, s, bID)
182
183	// Remove references.
184	liveCount, err := s.removeReferences(
185		ctx, bID, []kbfsblock.Context{bCtx, bCtx2}, "")
186	require.NoError(t, err)
187	require.Equal(t, 0, liveCount)
188
189	// Make sure the block data is inaccessible.
190	_, _, err = s.getDataWithContext(ctx, bID, bCtx)
191	require.Equal(t, blockNonExistentError{bID}, err)
192
193	// But the actual data should remain.
194	buf, half, err := s.getData(ctx, bID)
195	require.NoError(t, err)
196	require.Equal(t, data, buf)
197	require.Equal(t, serverHalf, half)
198}
199
200func TestBlockDiskStoreRemove(t *testing.T) {
201	tempdir, s := setupBlockDiskStoreTest(t)
202	defer teardownBlockDiskStoreTest(t, tempdir)
203	ctx := context.Background()
204
205	// Put the block.
206	data := []byte{1, 2, 3, 4}
207	bID, bCtx, _ := putBlockDisk(ctx, t, s, data)
208
209	// Should not be removable.
210	err := s.remove(ctx, bID)
211	require.Error(t, err, "Trying to remove data")
212
213	// Remove reference.
214	liveCount, err := s.removeReferences(
215		ctx, bID, []kbfsblock.Context{bCtx}, "")
216	require.NoError(t, err)
217	require.Equal(t, 0, liveCount)
218
219	// Should now be removable.
220	err = s.remove(ctx, bID)
221	require.NoError(t, err)
222
223	_, _, err = s.getData(ctx, bID)
224	require.Equal(t, blockNonExistentError{bID}, err)
225
226	err = filepath.Walk(s.dir,
227		func(path string, info os.FileInfo, _ error) error {
228			// We should only find the blocks directory here.
229			if path != s.dir {
230				t.Errorf("Found unexpected block path: %s", path)
231			}
232			return nil
233		})
234	require.NoError(t, err)
235}
236