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

     1  // Copyright 2017 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 kbfsblock
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/keybase/backoff"
    13  	"github.com/keybase/client/go/kbfs/kbfscodec"
    14  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    15  	"github.com/keybase/client/go/kbfs/tlf"
    16  	"github.com/keybase/client/go/logger"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  )
    19  
    20  func makeIDCombo(id ID, context Context) keybase1.BlockIdCombo {
    21  	// ChargedTo is somewhat confusing when this BlockIdCombo is
    22  	// used in a BlockReference -- it just refers to the original
    23  	// creator of the block, i.e. the original user charged for
    24  	// the block.
    25  	//
    26  	// This may all change once we implement groups.
    27  	return keybase1.BlockIdCombo{
    28  		BlockHash: id.String(),
    29  		ChargedTo: context.GetCreator(),
    30  		BlockType: context.GetBlockType(),
    31  	}
    32  }
    33  
    34  func makeReference(id ID, context Context) keybase1.BlockReference {
    35  	// Block references to MD blocks are allowed, because they can be
    36  	// deleted in the case of an MD put failing.
    37  	return keybase1.BlockReference{
    38  		Bid: makeIDCombo(id, context),
    39  		// The actual writer to modify quota for.
    40  		ChargedTo: context.GetWriter(),
    41  		Nonce:     keybase1.BlockRefNonce(context.GetRefNonce()),
    42  	}
    43  }
    44  
    45  // MakeGetBlockArg builds a keybase1.GetBlockArg from the given params.
    46  func MakeGetBlockArg(tlfID tlf.ID, id ID, context Context) keybase1.GetBlockArg {
    47  	return keybase1.GetBlockArg{
    48  		Bid:    makeIDCombo(id, context),
    49  		Folder: tlfID.String(),
    50  	}
    51  }
    52  
    53  // MakeGetBlockSizesArg builds a keybase1.GetBlockSizesArg from the
    54  // given params.
    55  func MakeGetBlockSizesArg(
    56  	tlfID tlf.ID, ids []ID, contexts []Context) (
    57  	keybase1.GetBlockSizesArg, error) {
    58  	if len(ids) != len(contexts) {
    59  		return keybase1.GetBlockSizesArg{}, fmt.Errorf(
    60  			"MakeGetBlockSizesArg: %d IDs but %d contexts",
    61  			len(ids), len(contexts))
    62  	}
    63  	arg := keybase1.GetBlockSizesArg{
    64  		Bids:   make([]keybase1.BlockIdCombo, len(ids)),
    65  		Folder: tlfID.String(),
    66  	}
    67  	for i, id := range ids {
    68  		arg.Bids[i] = makeIDCombo(id, contexts[i])
    69  	}
    70  	return arg, nil
    71  }
    72  
    73  // ParseGetBlockRes parses the given keybase1.GetBlockRes into its
    74  // components.
    75  func ParseGetBlockRes(res keybase1.GetBlockRes, resErr error) (
    76  	buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf, err error) {
    77  	if resErr != nil {
    78  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, resErr
    79  	}
    80  	serverHalf, err = kbfscrypto.ParseBlockCryptKeyServerHalf(res.BlockKey)
    81  	if err != nil {
    82  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
    83  	}
    84  	return res.Buf, serverHalf, nil
    85  }
    86  
    87  // MakePutBlockArg builds a keybase1.PutBlockArg from the given params.
    88  func MakePutBlockArg(tlfID tlf.ID, id ID,
    89  	bContext Context, buf []byte,
    90  	serverHalf kbfscrypto.BlockCryptKeyServerHalf) keybase1.PutBlockArg {
    91  	return keybase1.PutBlockArg{
    92  		Bid: makeIDCombo(id, bContext),
    93  		// BlockKey is misnamed -- it contains just the server
    94  		// half.
    95  		BlockKey: serverHalf.String(),
    96  		Folder:   tlfID.String(),
    97  		Buf:      buf,
    98  	}
    99  }
   100  
   101  // MakePutBlockAgainArg builds a keybase1.PutBlockAgainArg from the
   102  // given params.
   103  func MakePutBlockAgainArg(tlfID tlf.ID, id ID,
   104  	bContext Context, buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf) keybase1.PutBlockAgainArg {
   105  	return keybase1.PutBlockAgainArg{
   106  		Ref: makeReference(id, bContext),
   107  		// BlockKey is misnamed -- it contains just the server
   108  		// half.
   109  		BlockKey: serverHalf.String(),
   110  		Folder:   tlfID.String(),
   111  		Buf:      buf,
   112  	}
   113  }
   114  
   115  // MakeAddReferenceArg builds a keybase1.AddReferenceArg from the
   116  // given params.
   117  func MakeAddReferenceArg(tlfID tlf.ID, id ID, context Context) keybase1.AddReferenceArg {
   118  	return keybase1.AddReferenceArg{
   119  		Ref:    makeReference(id, context),
   120  		Folder: tlfID.String(),
   121  	}
   122  }
   123  
   124  // getNotDone returns the set of block references in "all" that do not
   125  // yet appear in "results"
   126  func getNotDone(all ContextMap, doneRefs map[ID]map[RefNonce]int) (
   127  	notDone []keybase1.BlockReference) {
   128  	for id, idContexts := range all {
   129  		for _, context := range idContexts {
   130  			if _, ok := doneRefs[id]; ok {
   131  				if _, ok1 := doneRefs[id][context.GetRefNonce()]; ok1 {
   132  					continue
   133  				}
   134  			}
   135  			ref := makeReference(id, context)
   136  			notDone = append(notDone, ref)
   137  		}
   138  	}
   139  	return notDone
   140  }
   141  
   142  // BatchDowngradeReferences archives or deletes a batch of references,
   143  // handling all batching and throttles.
   144  func BatchDowngradeReferences(ctx context.Context, log logger.Logger,
   145  	tlfID tlf.ID, contexts ContextMap, archive bool,
   146  	server keybase1.BlockInterface) (
   147  	doneRefs map[ID]map[RefNonce]int, finalError error) {
   148  	doneRefs = make(map[ID]map[RefNonce]int)
   149  	notDone := getNotDone(contexts, doneRefs)
   150  
   151  	throttleErr := backoff.Retry(func() error {
   152  		var res keybase1.DowngradeReferenceRes
   153  		var err error
   154  		if archive {
   155  			res, err = server.ArchiveReferenceWithCount(ctx,
   156  				keybase1.ArchiveReferenceWithCountArg{
   157  					Refs:   notDone,
   158  					Folder: tlfID.String(),
   159  				})
   160  		} else {
   161  			res, err = server.DelReferenceWithCount(ctx,
   162  				keybase1.DelReferenceWithCountArg{
   163  					Refs:   notDone,
   164  					Folder: tlfID.String(),
   165  				})
   166  		}
   167  
   168  		// log errors
   169  		if err != nil {
   170  			log.CWarningf(ctx, "batchDowngradeReferences archive=%t sent=%v done=%v failedRef=%v err=%v",
   171  				archive, notDone, res.Completed, res.Failed, err)
   172  		} else {
   173  			log.CDebugf(ctx, "batchDowngradeReferences archive=%t notdone=%v all succeeded",
   174  				archive, notDone)
   175  		}
   176  
   177  		// update the set of completed reference
   178  		for _, ref := range res.Completed {
   179  			bid, err := IDFromString(ref.Ref.Bid.BlockHash)
   180  			if err != nil {
   181  				continue
   182  			}
   183  			nonces, ok := doneRefs[bid]
   184  			if !ok {
   185  				nonces = make(map[RefNonce]int)
   186  				doneRefs[bid] = nonces
   187  			}
   188  			nonces[RefNonce(ref.Ref.Nonce)] = ref.LiveCount
   189  		}
   190  		// update the list of references to downgrade
   191  		notDone = getNotDone(contexts, doneRefs)
   192  
   193  		// if context is cancelled, return immediately
   194  		select {
   195  		case <-ctx.Done():
   196  			finalError = ctx.Err()
   197  			return nil
   198  		default:
   199  		}
   200  
   201  		// check whether to backoff and retry
   202  		if err != nil {
   203  			// if error is of type throttle, retry
   204  			if IsThrottleError(err) {
   205  				return err
   206  			}
   207  			// non-throttle error, do not retry here
   208  			finalError = err
   209  		}
   210  		return nil
   211  	}, backoff.NewExponentialBackOff())
   212  
   213  	// if backoff has given up retrying, return error
   214  	if throttleErr != nil {
   215  		return doneRefs, throttleErr
   216  	}
   217  
   218  	if finalError == nil {
   219  		if len(notDone) != 0 {
   220  			log.CErrorf(ctx, "batchDowngradeReferences finished successfully with outstanding refs? all=%v done=%v notDone=%v\n", contexts, doneRefs, notDone)
   221  			return doneRefs,
   222  				errors.New("batchDowngradeReferences inconsistent result")
   223  		}
   224  	}
   225  	return doneRefs, finalError
   226  }
   227  
   228  // GetLiveCounts computes the maximum live count for each ID over its
   229  // RefNonces.
   230  func GetLiveCounts(doneRefs map[ID]map[RefNonce]int) map[ID]int {
   231  	liveCounts := make(map[ID]int)
   232  	for id, nonces := range doneRefs {
   233  		for _, count := range nonces {
   234  			if existing, ok := liveCounts[id]; !ok || existing > count {
   235  				liveCounts[id] = count
   236  			}
   237  		}
   238  	}
   239  	return liveCounts
   240  }
   241  
   242  // ParseGetQuotaInfoRes parses the given quota result into a
   243  // *QuotaInfo.
   244  func ParseGetQuotaInfoRes(codec kbfscodec.Codec, res []byte, resErr error) (
   245  	info *QuotaInfo, err error) {
   246  	if resErr != nil {
   247  		return nil, resErr
   248  	}
   249  	return QuotaInfoDecode(res, codec)
   250  }
   251  
   252  // GetReferenceCount returns the number of live references (at least
   253  // as "live" as `refStatus`) for each block ID.
   254  func GetReferenceCount(
   255  	ctx context.Context, tlfID tlf.ID, contexts ContextMap,
   256  	refStatus keybase1.BlockStatus, server keybase1.BlockInterface) (
   257  	liveCounts map[ID]int, err error) {
   258  	arg := keybase1.GetReferenceCountArg{
   259  		Ids:    make([]keybase1.BlockIdCombo, 0, len(contexts)),
   260  		Folder: tlfID.String(),
   261  		Status: refStatus,
   262  	}
   263  	for id, idContexts := range contexts {
   264  		if len(idContexts) < 1 {
   265  			return nil, errors.New("Each ID must have at least one context")
   266  		}
   267  		context := idContexts[0]
   268  		arg.Ids = append(arg.Ids, makeIDCombo(id, context))
   269  	}
   270  
   271  	res, err := server.GetReferenceCount(ctx, arg)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	liveCounts = make(map[ID]int, len(res.Counts))
   277  	for _, count := range res.Counts {
   278  		id, err := IDFromString(count.Id.BlockHash)
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  
   283  		liveCounts[id] = count.LiveCount
   284  	}
   285  	return liveCounts, nil
   286  }