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