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 }