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	"fmt"
9	"sync"
10	"testing"
11
12	"github.com/golang/mock/gomock"
13	"github.com/keybase/client/go/kbfs/data"
14	"github.com/keybase/client/go/kbfs/env"
15	"github.com/keybase/client/go/kbfs/kbfsblock"
16	"github.com/keybase/client/go/kbfs/kbfscodec"
17	"github.com/keybase/client/go/kbfs/kbfscrypto"
18	"github.com/keybase/client/go/kbfs/kbfshash"
19	"github.com/keybase/client/go/kbfs/kbfsmd"
20	"github.com/keybase/client/go/kbfs/libkey"
21	"github.com/keybase/client/go/kbfs/test/clocktest"
22	"github.com/keybase/client/go/kbfs/tlf"
23	"github.com/keybase/client/go/protocol/keybase1"
24	"github.com/pkg/errors"
25	"github.com/stretchr/testify/require"
26	"golang.org/x/net/context"
27)
28
29// fakeKeyMetadata is an implementation of KeyMetadata that just
30// stores TLFCryptKeys directly. It's meant to be used with
31// fakeBlockKeyGetter.
32type fakeKeyMetadata struct {
33	// Embed a KeyMetadata that's always empty, so that all
34	// methods besides TlfID() panic.
35	libkey.KeyMetadata
36	tlfID tlf.ID
37	keys  []kbfscrypto.TLFCryptKey
38}
39
40var _ libkey.KeyMetadata = fakeKeyMetadata{}
41
42// makeFakeKeyMetadata returns a fakeKeyMetadata with keys for each
43// KeyGen up to latestKeyGen. The key for KeyGen i is a deterministic
44// function of i, so multiple calls to this function will have the
45// same keys.
46func makeFakeKeyMetadata(tlfID tlf.ID, latestKeyGen kbfsmd.KeyGen) fakeKeyMetadata {
47	keys := make([]kbfscrypto.TLFCryptKey, 0,
48		latestKeyGen-kbfsmd.FirstValidKeyGen+1)
49	for keyGen := kbfsmd.FirstValidKeyGen; keyGen <= latestKeyGen; keyGen++ {
50		keys = append(keys,
51			kbfscrypto.MakeTLFCryptKey([32]byte{byte(keyGen)}))
52	}
53	return fakeKeyMetadata{nil, tlfID, keys}
54}
55
56func (kmd fakeKeyMetadata) TlfID() tlf.ID {
57	return kmd.tlfID
58}
59
60type fakeBlockKeyGetter struct{}
61
62func (kg fakeBlockKeyGetter) GetTLFCryptKeyForEncryption(
63	ctx context.Context, kmd libkey.KeyMetadata) (kbfscrypto.TLFCryptKey, error) {
64	fkmd := kmd.(fakeKeyMetadata)
65	if len(fkmd.keys) == 0 {
66		return kbfscrypto.TLFCryptKey{}, errors.New(
67			"no keys for encryption")
68	}
69	return fkmd.keys[len(fkmd.keys)-1], nil
70}
71
72func (kg fakeBlockKeyGetter) GetTLFCryptKeyForBlockDecryption(
73	ctx context.Context, kmd libkey.KeyMetadata, blockPtr data.BlockPointer) (
74	kbfscrypto.TLFCryptKey, error) {
75	fkmd := kmd.(fakeKeyMetadata)
76	i := int(blockPtr.KeyGen - kbfsmd.FirstValidKeyGen)
77	if i >= len(fkmd.keys) {
78		return kbfscrypto.TLFCryptKey{}, errors.Errorf(
79			"no key for block decryption (keygen=%d)",
80			blockPtr.KeyGen)
81	}
82	return fkmd.keys[i], nil
83}
84
85type testBlockOpsConfig struct {
86	testCodecGetter
87	logMaker
88	bserver BlockServer
89	cp      cryptoPure
90	cache   data.BlockCache
91	diskBlockCacheGetter
92	*testSyncedTlfGetterSetter
93	initModeGetter
94	clock                        Clock
95	reporter                     Reporter
96	subscriptionManager          SubscriptionManager
97	subscriptionManagerPublisher SubscriptionManagerPublisher
98}
99
100var _ blockOpsConfig = (*testBlockOpsConfig)(nil)
101
102func (config testBlockOpsConfig) BlockServer() BlockServer {
103	return config.bserver
104}
105
106func (config testBlockOpsConfig) cryptoPure() cryptoPure {
107	return config.cp
108}
109
110func (config testBlockOpsConfig) keyGetter() blockKeyGetter {
111	return fakeBlockKeyGetter{}
112}
113
114func (config testBlockOpsConfig) BlockCache() data.BlockCache {
115	return config.cache
116}
117
118func (config testBlockOpsConfig) DataVersion() data.Ver {
119	return data.ChildHolesVer
120}
121
122func (config testBlockOpsConfig) BlockCryptVersion() kbfscrypto.EncryptionVer {
123	return kbfscrypto.EncryptionSecretboxWithKeyNonce
124}
125
126func (config testBlockOpsConfig) Clock() Clock {
127	return config.clock
128}
129
130func (config testBlockOpsConfig) Reporter() Reporter {
131	return config.reporter
132}
133
134func (config testBlockOpsConfig) GetSettingsDB() *SettingsDB {
135	return nil
136}
137
138func (config testBlockOpsConfig) SubscriptionManager(
139	_ SubscriptionManagerClientID, _ bool,
140	_ SubscriptionNotifier) SubscriptionManager {
141	return config.subscriptionManager
142}
143
144func (config testBlockOpsConfig) SubscriptionManagerPublisher() SubscriptionManagerPublisher {
145	return config.subscriptionManagerPublisher
146}
147
148func makeTestBlockOpsConfig(t *testing.T) testBlockOpsConfig {
149	lm := newTestLogMaker(t)
150	codecGetter := newTestCodecGetter()
151	bserver := NewBlockServerMemory(lm.MakeLogger(""))
152	crypto := MakeCryptoCommon(codecGetter.Codec(), makeBlockCryptV1())
153	cache := data.NewBlockCacheStandard(10, getDefaultCleanBlockCacheCapacity(NewInitModeFromType(InitDefault)))
154	dbcg := newTestDiskBlockCacheGetter(t, nil)
155	stgs := newTestSyncedTlfGetterSetter()
156	clock := clocktest.NewTestClockNow()
157	mockPublisher := NewMockSubscriptionManagerPublisher(gomock.NewController(t))
158	mockPublisher.EXPECT().PublishChange(gomock.Any()).AnyTimes()
159	return testBlockOpsConfig{codecGetter, lm, bserver, crypto, cache, dbcg,
160		stgs, testInitModeGetter{InitDefault}, clock,
161		NewReporterSimple(clock, 1), nil, mockPublisher}
162}
163
164func testBlockOpsShutdown(
165	ctx context.Context, t *testing.T, bops *BlockOpsStandard) {
166	err := bops.Shutdown(ctx)
167	require.NoError(t, err)
168}
169
170// TestBlockOpsReadySuccess checks that BlockOpsStandard.Ready()
171// encrypts its given block properly.
172func TestBlockOpsReadySuccess(t *testing.T) {
173	ctx := context.Background()
174	config := makeTestBlockOpsConfig(t)
175	bops := NewBlockOpsStandard(
176		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
177		0, env.EmptyAppStateUpdater{})
178	defer testBlockOpsShutdown(ctx, t, bops)
179
180	tlfID := tlf.FakeID(0, tlf.Private)
181	var latestKeyGen kbfsmd.KeyGen = 5
182	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
183
184	block := &data.FileBlock{
185		Contents: []byte{1, 2, 3, 4, 5},
186	}
187
188	encodedBlock, err := config.Codec().Encode(block)
189	require.NoError(t, err)
190
191	id, plainSize, readyBlockData, err := bops.Ready(ctx, kmd, block)
192	require.NoError(t, err)
193
194	require.Equal(t, len(encodedBlock), plainSize)
195
196	err = kbfsblock.VerifyID(readyBlockData.Buf, id)
197	require.NoError(t, err)
198
199	var encryptedBlock kbfscrypto.EncryptedBlock
200	err = config.Codec().Decode(readyBlockData.Buf, &encryptedBlock)
201	require.NoError(t, err)
202
203	decryptedBlock := &data.FileBlock{}
204	err = config.cryptoPure().DecryptBlock(
205		encryptedBlock, kmd.keys[latestKeyGen-kbfsmd.FirstValidKeyGen],
206		readyBlockData.ServerHalf, decryptedBlock)
207	require.NoError(t, err)
208	decryptedBlock.SetEncodedSize(uint32(readyBlockData.GetEncodedSize()))
209	require.Equal(t, block, decryptedBlock)
210}
211
212// TestBlockOpsReadyFailKeyGet checks that BlockOpsStandard.Ready()
213// fails properly if we fail to retrieve the key.
214func TestBlockOpsReadyFailKeyGet(t *testing.T) {
215	ctx := context.Background()
216	config := makeTestBlockOpsConfig(t)
217	bops := NewBlockOpsStandard(
218		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
219		0, env.EmptyAppStateUpdater{})
220	defer testBlockOpsShutdown(ctx, t, bops)
221
222	tlfID := tlf.FakeID(0, tlf.Private)
223	kmd := makeFakeKeyMetadata(tlfID, 0)
224
225	_, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
226	require.EqualError(t, err, "no keys for encryption")
227}
228
229type badServerHalfMaker struct {
230	cryptoPure
231}
232
233func (c badServerHalfMaker) MakeRandomBlockCryptKeyServerHalf() (
234	kbfscrypto.BlockCryptKeyServerHalf, error) {
235	return kbfscrypto.BlockCryptKeyServerHalf{}, errors.New(
236		"could not make server half")
237}
238
239// TestBlockOpsReadyFailServerHalfGet checks that BlockOpsStandard.Ready()
240// fails properly if we fail to generate a  server half.
241func TestBlockOpsReadyFailServerHalfGet(t *testing.T) {
242	ctx := context.Background()
243	config := makeTestBlockOpsConfig(t)
244	config.cp = badServerHalfMaker{config.cryptoPure()}
245	bops := NewBlockOpsStandard(
246		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
247		0, env.EmptyAppStateUpdater{})
248	defer testBlockOpsShutdown(ctx, t, bops)
249
250	tlfID := tlf.FakeID(0, tlf.Private)
251	kmd := makeFakeKeyMetadata(tlfID, kbfsmd.FirstValidKeyGen)
252
253	_, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
254	require.EqualError(t, err, "could not make server half")
255}
256
257type badBlockEncryptor struct {
258	cryptoPure
259}
260
261func (c badBlockEncryptor) EncryptBlock(
262	block data.Block, tlfCryptKey kbfscrypto.TLFCryptKey,
263	blockServerHalf kbfscrypto.BlockCryptKeyServerHalf) (
264	plainSize int, encryptedBlock kbfscrypto.EncryptedBlock, err error) {
265	return 0, kbfscrypto.EncryptedBlock{}, errors.New("could not encrypt block")
266}
267
268// TestBlockOpsReadyFailEncryption checks that BlockOpsStandard.Ready()
269// fails properly if we fail to encrypt the block.
270func TestBlockOpsReadyFailEncryption(t *testing.T) {
271	ctx := context.Background()
272	config := makeTestBlockOpsConfig(t)
273	config.cp = badBlockEncryptor{config.cryptoPure()}
274	bops := NewBlockOpsStandard(
275		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
276		0, env.EmptyAppStateUpdater{})
277	defer testBlockOpsShutdown(ctx, t, bops)
278
279	tlfID := tlf.FakeID(0, tlf.Private)
280	kmd := makeFakeKeyMetadata(tlfID, kbfsmd.FirstValidKeyGen)
281
282	_, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
283	require.EqualError(t, err, "could not encrypt block")
284}
285
286type badEncoder struct {
287	kbfscodec.Codec
288}
289
290func (c badEncoder) Encode(o interface{}) ([]byte, error) {
291	return nil, errors.New("could not encode")
292}
293
294// TestBlockOpsReadyFailEncode checks that BlockOpsStandard.Ready()
295// fails properly if we fail to encode the encrypted block.
296func TestBlockOpsReadyFailEncode(t *testing.T) {
297	ctx := context.Background()
298	config := makeTestBlockOpsConfig(t)
299	config.testCodecGetter.codec = badEncoder{config.codec}
300	bops := NewBlockOpsStandard(
301		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
302		0, env.EmptyAppStateUpdater{})
303	defer testBlockOpsShutdown(ctx, t, bops)
304
305	tlfID := tlf.FakeID(0, tlf.Private)
306	kmd := makeFakeKeyMetadata(tlfID, kbfsmd.FirstValidKeyGen)
307
308	_, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
309	require.EqualError(t, err, "could not encode")
310}
311
312type tooSmallEncoder struct {
313	kbfscodec.Codec
314}
315
316func (c tooSmallEncoder) Encode(o interface{}) ([]byte, error) {
317	return []byte{0x1}, nil
318}
319
320// TestBlockOpsReadyTooSmallEncode checks that
321// BlockOpsStandard.Ready() fails properly if the encrypted block
322// encodes to a too-small buffer.
323func TestBlockOpsReadyTooSmallEncode(t *testing.T) {
324	ctx := context.Background()
325	config := makeTestBlockOpsConfig(t)
326	config.codec = tooSmallEncoder{config.codec}
327	bops := NewBlockOpsStandard(
328		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
329		0, env.EmptyAppStateUpdater{})
330	defer testBlockOpsShutdown(ctx, t, bops)
331
332	tlfID := tlf.FakeID(0, tlf.Private)
333	kmd := makeFakeKeyMetadata(tlfID, kbfsmd.FirstValidKeyGen)
334
335	_, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
336	require.IsType(t, TooLowByteCountError{}, err)
337}
338
339// TestBlockOpsReadySuccess checks that BlockOpsStandard.Get()
340// retrieves a block properly, even if that block was encoded for a
341// previous key generation.
342func TestBlockOpsGetSuccess(t *testing.T) {
343	ctx := context.Background()
344	config := makeTestBlockOpsConfig(t)
345	bops := NewBlockOpsStandard(
346		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
347		0, env.EmptyAppStateUpdater{})
348	defer testBlockOpsShutdown(ctx, t, bops)
349
350	tlfID := tlf.FakeID(0, tlf.Private)
351	var keyGen kbfsmd.KeyGen = 3
352	kmd1 := makeFakeKeyMetadata(tlfID, keyGen)
353
354	block := &data.FileBlock{
355		Contents: []byte{1, 2, 3, 4, 5},
356	}
357
358	id, _, readyBlockData, err := bops.Ready(ctx, kmd1, block)
359	require.NoError(t, err)
360
361	bCtx := kbfsblock.MakeFirstContext(
362		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
363	err = config.bserver.Put(
364		ctx, tlfID, id, bCtx, readyBlockData.Buf, readyBlockData.ServerHalf,
365		DiskBlockAnyCache)
366	require.NoError(t, err)
367
368	kmd2 := makeFakeKeyMetadata(tlfID, keyGen+3)
369	decryptedBlock := &data.FileBlock{}
370	err = bops.Get(ctx, kmd2,
371		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
372			KeyGen: keyGen, Context: bCtx},
373		decryptedBlock, data.NoCacheEntry, data.MasterBranch)
374	require.NoError(t, err)
375	require.Equal(t, block, decryptedBlock)
376}
377
378// TestBlockOpsReadySuccess checks that BlockOpsStandard.Get() fails
379// if it can't retrieve the block from the server.
380func TestBlockOpsGetFailServerGet(t *testing.T) {
381	ctx := context.Background()
382	config := makeTestBlockOpsConfig(t)
383	bops := NewBlockOpsStandard(
384		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
385		0, env.EmptyAppStateUpdater{})
386	defer testBlockOpsShutdown(ctx, t, bops)
387
388	tlfID := tlf.FakeID(0, tlf.Private)
389	var latestKeyGen kbfsmd.KeyGen = 5
390	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
391
392	id, _, _, err := bops.Ready(ctx, kmd, &data.FileBlock{})
393	require.NoError(t, err)
394
395	bCtx := kbfsblock.MakeFirstContext(
396		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
397	var decryptedBlock data.FileBlock
398	err = bops.Get(ctx, kmd,
399		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
400			KeyGen: latestKeyGen, Context: bCtx},
401		&decryptedBlock, data.NoCacheEntry, data.MasterBranch)
402	require.IsType(t, kbfsblock.ServerErrorBlockNonExistent{}, err)
403}
404
405type badGetBlockServer struct {
406	BlockServer
407}
408
409func (bserver badGetBlockServer) Get(
410	ctx context.Context, tlfID tlf.ID, id kbfsblock.ID,
411	context kbfsblock.Context, cacheType DiskBlockCacheType) (
412	[]byte, kbfscrypto.BlockCryptKeyServerHalf, error) {
413	buf, serverHalf, err := bserver.BlockServer.Get(
414		ctx, tlfID, id, context, cacheType)
415	if err != nil {
416		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, nil
417	}
418
419	return append(buf, 0x1), serverHalf, nil
420}
421
422// TestBlockOpsReadyFailVerify checks that BlockOpsStandard.Get()
423// fails if it can't verify the block retrieved from the server.
424func TestBlockOpsGetFailVerify(t *testing.T) {
425	ctx := context.Background()
426	config := makeTestBlockOpsConfig(t)
427	config.bserver = badGetBlockServer{config.bserver}
428	bops := NewBlockOpsStandard(
429		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
430		0, env.EmptyAppStateUpdater{})
431	defer testBlockOpsShutdown(ctx, t, bops)
432
433	tlfID := tlf.FakeID(0, tlf.Private)
434	var latestKeyGen kbfsmd.KeyGen = 5
435	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
436
437	id, _, readyBlockData, err := bops.Ready(ctx, kmd, &data.FileBlock{})
438	require.NoError(t, err)
439
440	bCtx := kbfsblock.MakeFirstContext(
441		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
442	err = config.bserver.Put(
443		ctx, tlfID, id, bCtx, readyBlockData.Buf, readyBlockData.ServerHalf,
444		DiskBlockAnyCache)
445	require.NoError(t, err)
446
447	var decryptedBlock data.FileBlock
448	err = bops.Get(ctx, kmd,
449		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
450			KeyGen: latestKeyGen, Context: bCtx},
451		&decryptedBlock, data.NoCacheEntry, data.MasterBranch)
452	require.IsType(t, kbfshash.HashMismatchError{}, errors.Cause(err))
453}
454
455// TestBlockOpsReadyFailKeyGet checks that BlockOpsStandard.Get()
456// fails if it can't get the decryption key.
457func TestBlockOpsGetFailKeyGet(t *testing.T) {
458	ctx := context.Background()
459	config := makeTestBlockOpsConfig(t)
460	bops := NewBlockOpsStandard(
461		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
462		0, env.EmptyAppStateUpdater{})
463	defer testBlockOpsShutdown(ctx, t, bops)
464
465	tlfID := tlf.FakeID(0, tlf.Private)
466	var latestKeyGen kbfsmd.KeyGen = 5
467	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
468
469	id, _, readyBlockData, err := bops.Ready(ctx, kmd, &data.FileBlock{})
470	require.NoError(t, err)
471
472	bCtx := kbfsblock.MakeFirstContext(
473		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
474	err = config.bserver.Put(
475		ctx, tlfID, id, bCtx, readyBlockData.Buf, readyBlockData.ServerHalf,
476		DiskBlockAnyCache)
477	require.NoError(t, err)
478
479	var decryptedBlock data.FileBlock
480	err = bops.Get(ctx, kmd,
481		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
482			KeyGen: latestKeyGen + 1, Context: bCtx},
483		&decryptedBlock, data.NoCacheEntry, data.MasterBranch)
484	require.EqualError(t, err, fmt.Sprintf(
485		"no key for block decryption (keygen=%d)", latestKeyGen+1))
486}
487
488// badDecoder maintains a map from stringified byte buffers to
489// error. If Decode is called with a buffer that matches anything in
490// the map, the corresponding error is returned.
491//
492// This is necessary because codec functions are used everywhere.
493type badDecoder struct {
494	kbfscodec.Codec
495
496	errorsLock sync.RWMutex
497	errors     map[string]error
498}
499
500func (c *badDecoder) putError(buf []byte, err error) {
501	k := string(buf)
502	c.errorsLock.Lock()
503	defer c.errorsLock.Unlock()
504	c.errors[k] = err
505}
506
507func (c *badDecoder) Decode(buf []byte, o interface{}) error {
508	k := string(buf)
509	err := func() error {
510		c.errorsLock.RLock()
511		defer c.errorsLock.RUnlock()
512		return c.errors[k]
513	}()
514	if err != nil {
515		return err
516	}
517	return c.Codec.Decode(buf, o)
518}
519
520// TestBlockOpsReadyFailDecode checks that BlockOpsStandard.Get()
521// fails if it can't decode the encrypted block.
522func TestBlockOpsGetFailDecode(t *testing.T) {
523	ctx := context.Background()
524	config := makeTestBlockOpsConfig(t)
525	badDecoder := badDecoder{
526		Codec:  config.Codec(),
527		errors: make(map[string]error),
528	}
529	config.codec = &badDecoder
530	bops := NewBlockOpsStandard(
531		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
532		0, env.EmptyAppStateUpdater{})
533	defer testBlockOpsShutdown(ctx, t, bops)
534
535	tlfID := tlf.FakeID(0, tlf.Private)
536	var latestKeyGen kbfsmd.KeyGen = 5
537	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
538
539	id, _, readyBlockData, err := bops.Ready(ctx, kmd, &data.FileBlock{})
540	require.NoError(t, err)
541
542	decodeErr := errors.New("could not decode")
543	badDecoder.putError(readyBlockData.Buf, decodeErr)
544
545	bCtx := kbfsblock.MakeFirstContext(
546		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
547	err = config.bserver.Put(
548		ctx, tlfID, id, bCtx, readyBlockData.Buf, readyBlockData.ServerHalf,
549		DiskBlockAnyCache)
550	require.NoError(t, err)
551
552	var decryptedBlock data.FileBlock
553	err = bops.Get(ctx, kmd,
554		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
555			KeyGen: latestKeyGen, Context: bCtx},
556		&decryptedBlock, data.NoCacheEntry, data.MasterBranch)
557	require.Equal(t, decodeErr, err)
558}
559
560type badBlockDecryptor struct {
561	cryptoPure
562}
563
564func (c badBlockDecryptor) DecryptBlock(
565	_ kbfscrypto.EncryptedBlock, _ kbfscrypto.TLFCryptKey,
566	_ kbfscrypto.BlockCryptKeyServerHalf, _ data.Block) error {
567	return errors.New("could not decrypt block")
568}
569
570// TestBlockOpsReadyFailDecrypt checks that BlockOpsStandard.Get()
571// fails if it can't decrypt the encrypted block.
572func TestBlockOpsGetFailDecrypt(t *testing.T) {
573	ctx := context.Background()
574	config := makeTestBlockOpsConfig(t)
575	config.cp = badBlockDecryptor{config.cryptoPure()}
576	bops := NewBlockOpsStandard(
577		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
578		0, env.EmptyAppStateUpdater{})
579	defer testBlockOpsShutdown(ctx, t, bops)
580
581	tlfID := tlf.FakeID(0, tlf.Private)
582	var latestKeyGen kbfsmd.KeyGen = 5
583	kmd := makeFakeKeyMetadata(tlfID, latestKeyGen)
584
585	id, _, readyBlockData, err := bops.Ready(ctx, kmd, &data.FileBlock{})
586	require.NoError(t, err)
587
588	bCtx := kbfsblock.MakeFirstContext(
589		keybase1.MakeTestUID(1).AsUserOrTeam(), keybase1.BlockType_DATA)
590	err = config.bserver.Put(
591		ctx, tlfID, id, bCtx, readyBlockData.Buf, readyBlockData.ServerHalf,
592		DiskBlockAnyCache)
593	require.NoError(t, err)
594
595	var decryptedBlock data.FileBlock
596	err = bops.Get(ctx, kmd,
597		data.BlockPointer{ID: id, DataVer: data.FirstValidVer,
598			KeyGen: latestKeyGen, Context: bCtx},
599		&decryptedBlock, data.NoCacheEntry, data.MasterBranch)
600	require.EqualError(t, err, "could not decrypt block")
601}
602
603func TestBlockOpsDeleteSuccess(t *testing.T) {
604	ctx := context.Background()
605	ctr := NewSafeTestReporter(t)
606	mockCtrl := gomock.NewController(ctr)
607	defer mockCtrl.Finish()
608
609	bserver := NewMockBlockServer(mockCtrl)
610	config := makeTestBlockOpsConfig(t)
611	config.bserver = bserver
612	bops := NewBlockOpsStandard(
613		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
614		0, env.EmptyAppStateUpdater{})
615	defer testBlockOpsShutdown(ctx, t, bops)
616
617	// Expect one call to delete several blocks.
618
619	b1 := data.BlockPointer{ID: kbfsblock.FakeID(1)}
620	b2 := data.BlockPointer{ID: kbfsblock.FakeID(2)}
621
622	contexts := kbfsblock.ContextMap{
623		b1.ID: {b1.Context},
624		b2.ID: {b2.Context},
625	}
626
627	expectedLiveCounts := map[kbfsblock.ID]int{
628		b1.ID: 5,
629		b2.ID: 3,
630	}
631
632	tlfID := tlf.FakeID(1, tlf.Private)
633	bserver.EXPECT().RemoveBlockReferences(ctx, tlfID, contexts).
634		Return(expectedLiveCounts, nil)
635
636	liveCounts, err := bops.Delete(ctx, tlfID, []data.BlockPointer{b1, b2})
637	require.NoError(t, err)
638	require.Equal(t, expectedLiveCounts, liveCounts)
639}
640
641func TestBlockOpsDeleteFail(t *testing.T) {
642	ctx := context.Background()
643	ctr := NewSafeTestReporter(t)
644	mockCtrl := gomock.NewController(ctr)
645	defer mockCtrl.Finish()
646
647	bserver := NewMockBlockServer(mockCtrl)
648	config := makeTestBlockOpsConfig(t)
649	config.bserver = bserver
650	bops := NewBlockOpsStandard(
651		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
652		0, env.EmptyAppStateUpdater{})
653	defer testBlockOpsShutdown(ctx, t, bops)
654
655	b1 := data.BlockPointer{ID: kbfsblock.FakeID(1)}
656	b2 := data.BlockPointer{ID: kbfsblock.FakeID(2)}
657
658	contexts := kbfsblock.ContextMap{
659		b1.ID: {b1.Context},
660		b2.ID: {b2.Context},
661	}
662
663	// Fail the delete call.
664
665	tlfID := tlf.FakeID(1, tlf.Private)
666	expectedErr := errors.New("Fake fail")
667	bserver.EXPECT().RemoveBlockReferences(ctx, tlfID, contexts).
668		Return(nil, expectedErr)
669
670	_, err := bops.Delete(ctx, tlfID, []data.BlockPointer{b1, b2})
671	require.Equal(t, expectedErr, err)
672}
673
674func TestBlockOpsArchiveSuccess(t *testing.T) {
675	ctx := context.Background()
676	ctr := NewSafeTestReporter(t)
677	mockCtrl := gomock.NewController(ctr)
678	defer func() {
679		ctr.CheckForFailures()
680		mockCtrl.Finish()
681	}()
682
683	bserver := NewMockBlockServer(mockCtrl)
684	config := makeTestBlockOpsConfig(t)
685	config.bserver = bserver
686	bops := NewBlockOpsStandard(
687		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
688		0, env.EmptyAppStateUpdater{})
689	defer testBlockOpsShutdown(ctx, t, bops)
690
691	// Expect one call to archive several blocks.
692
693	b1 := data.BlockPointer{ID: kbfsblock.FakeID(1)}
694	b2 := data.BlockPointer{ID: kbfsblock.FakeID(2)}
695
696	contexts := kbfsblock.ContextMap{
697		b1.ID: {b1.Context},
698		b2.ID: {b2.Context},
699	}
700
701	tlfID := tlf.FakeID(1, tlf.Private)
702	bserver.EXPECT().ArchiveBlockReferences(ctx, tlfID, contexts).
703		Return(nil)
704
705	err := bops.Archive(ctx, tlfID, []data.BlockPointer{b1, b2})
706	require.NoError(t, err)
707}
708
709func TestBlockOpsArchiveFail(t *testing.T) {
710	ctx := context.Background()
711	ctr := NewSafeTestReporter(t)
712	mockCtrl := gomock.NewController(ctr)
713	defer func() {
714		ctr.CheckForFailures()
715		mockCtrl.Finish()
716	}()
717
718	bserver := NewMockBlockServer(mockCtrl)
719	config := makeTestBlockOpsConfig(t)
720	config.bserver = bserver
721	bops := NewBlockOpsStandard(
722		config, testBlockRetrievalWorkerQueueSize, testPrefetchWorkerQueueSize,
723		0, env.EmptyAppStateUpdater{})
724	defer testBlockOpsShutdown(ctx, t, bops)
725
726	b1 := data.BlockPointer{ID: kbfsblock.FakeID(1)}
727	b2 := data.BlockPointer{ID: kbfsblock.FakeID(2)}
728
729	contexts := kbfsblock.ContextMap{
730		b1.ID: {b1.Context},
731		b2.ID: {b2.Context},
732	}
733
734	// Fail the archive call.
735
736	tlfID := tlf.FakeID(1, tlf.Private)
737	expectedErr := errors.New("Fake fail")
738	bserver.EXPECT().ArchiveBlockReferences(ctx, tlfID, contexts).
739		Return(expectedErr)
740
741	err := bops.Archive(ctx, tlfID, []data.BlockPointer{b1, b2})
742	require.Equal(t, expectedErr, err)
743}
744