github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/block_ops_test.go (about)

     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  
     5  package libkbfs
     6  
     7  import (
     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.
    32  type 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  
    40  var _ 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.
    46  func 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  
    56  func (kmd fakeKeyMetadata) TlfID() tlf.ID {
    57  	return kmd.tlfID
    58  }
    59  
    60  type fakeBlockKeyGetter struct{}
    61  
    62  func (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  
    72  func (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  
    85  type 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  
   100  var _ blockOpsConfig = (*testBlockOpsConfig)(nil)
   101  
   102  func (config testBlockOpsConfig) BlockServer() BlockServer {
   103  	return config.bserver
   104  }
   105  
   106  func (config testBlockOpsConfig) cryptoPure() cryptoPure {
   107  	return config.cp
   108  }
   109  
   110  func (config testBlockOpsConfig) keyGetter() blockKeyGetter {
   111  	return fakeBlockKeyGetter{}
   112  }
   113  
   114  func (config testBlockOpsConfig) BlockCache() data.BlockCache {
   115  	return config.cache
   116  }
   117  
   118  func (config testBlockOpsConfig) DataVersion() data.Ver {
   119  	return data.ChildHolesVer
   120  }
   121  
   122  func (config testBlockOpsConfig) BlockCryptVersion() kbfscrypto.EncryptionVer {
   123  	return kbfscrypto.EncryptionSecretboxWithKeyNonce
   124  }
   125  
   126  func (config testBlockOpsConfig) Clock() Clock {
   127  	return config.clock
   128  }
   129  
   130  func (config testBlockOpsConfig) Reporter() Reporter {
   131  	return config.reporter
   132  }
   133  
   134  func (config testBlockOpsConfig) GetSettingsDB() *SettingsDB {
   135  	return nil
   136  }
   137  
   138  func (config testBlockOpsConfig) SubscriptionManager(
   139  	_ SubscriptionManagerClientID, _ bool,
   140  	_ SubscriptionNotifier) SubscriptionManager {
   141  	return config.subscriptionManager
   142  }
   143  
   144  func (config testBlockOpsConfig) SubscriptionManagerPublisher() SubscriptionManagerPublisher {
   145  	return config.subscriptionManagerPublisher
   146  }
   147  
   148  func 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  
   164  func 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.
   172  func 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.
   214  func 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  
   229  type badServerHalfMaker struct {
   230  	cryptoPure
   231  }
   232  
   233  func (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.
   241  func 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  
   257  type badBlockEncryptor struct {
   258  	cryptoPure
   259  }
   260  
   261  func (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.
   270  func 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  
   286  type badEncoder struct {
   287  	kbfscodec.Codec
   288  }
   289  
   290  func (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.
   296  func 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  
   312  type tooSmallEncoder struct {
   313  	kbfscodec.Codec
   314  }
   315  
   316  func (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.
   323  func 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.
   342  func 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.
   380  func 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  
   405  type badGetBlockServer struct {
   406  	BlockServer
   407  }
   408  
   409  func (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.
   424  func 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.
   457  func 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.
   493  type badDecoder struct {
   494  	kbfscodec.Codec
   495  
   496  	errorsLock sync.RWMutex
   497  	errors     map[string]error
   498  }
   499  
   500  func (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  
   507  func (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.
   522  func 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  
   560  type badBlockDecryptor struct {
   561  	cryptoPure
   562  }
   563  
   564  func (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.
   572  func 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  
   603  func 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  
   641  func 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  
   674  func 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  
   709  func 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  }