storj.io/uplink@v1.13.0/common.go (about) 1 // Copyright (C) 2020 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package uplink 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "strings" 11 _ "unsafe" // for go:linkname 12 13 "github.com/spacemonkeygo/monkit/v3" 14 "github.com/zeebo/errs" 15 16 "storj.io/common/encryption" 17 "storj.io/common/errs2" 18 "storj.io/common/rpc/rpcstatus" 19 "storj.io/eventkit" 20 "storj.io/uplink/private/metaclient" 21 "storj.io/uplink/private/piecestore" 22 ) 23 24 var mon = monkit.Package() 25 var evs = eventkit.Package() 26 27 // We use packageError.Wrap/New instead of plain errs.Wrap/New to add a prefix "uplink" to every error 28 // message emitted by the Uplink library. 29 // It is private because it's not intended to be part of the public API. 30 var packageError = errs.Class("uplink") 31 32 // ErrTooManyRequests is returned when user has sent too many requests in a given amount of time. 33 var ErrTooManyRequests = errors.New("too many requests") 34 35 // ErrBandwidthLimitExceeded is returned when project will exceeded bandwidth limit. 36 var ErrBandwidthLimitExceeded = errors.New("bandwidth limit exceeded") 37 38 // ErrStorageLimitExceeded is returned when project will exceeded storage limit. 39 var ErrStorageLimitExceeded = errors.New("storage limit exceeded") 40 41 // ErrSegmentsLimitExceeded is returned when project will exceeded segments limit. 42 var ErrSegmentsLimitExceeded = errors.New("segments limit exceeded") 43 44 // ErrPermissionDenied is returned when the request is denied due to invalid permissions. 45 var ErrPermissionDenied = errors.New("permission denied") 46 47 //go:linkname convertKnownErrors 48 func convertKnownErrors(err error, bucket, key string) error { 49 switch { 50 case errors.Is(err, io.EOF): 51 return err 52 case metaclient.ErrNoBucket.Has(err): 53 return errwrapf("%w (%q)", ErrBucketNameInvalid, bucket) 54 case metaclient.ErrNoPath.Has(err): 55 return errwrapf("%w (%q)", ErrObjectKeyInvalid, key) 56 case metaclient.ErrBucketNotFound.Has(err): 57 return errwrapf("%w (%q)", ErrBucketNotFound, bucket) 58 case metaclient.ErrObjectNotFound.Has(err): 59 return errwrapf("%w (%q)", ErrObjectNotFound, key) 60 case metaclient.ErrUploadIDInvalid.Has(err): 61 return errwrapf("%w (%q)", ErrUploadIDInvalid, key) 62 case encryption.ErrMissingEncryptionBase.Has(err): 63 return errwrapf("%w (%q)", ErrPermissionDenied, key) 64 case encryption.ErrMissingDecryptionBase.Has(err): 65 return errwrapf("%w (%q)", ErrPermissionDenied, key) 66 case errs2.IsRPC(err, rpcstatus.ResourceExhausted): 67 // TODO is a better way to do this? 68 message := errs.Unwrap(err).Error() 69 if strings.HasSuffix(message, "Exceeded Usage Limit") { 70 return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrBandwidthLimitExceeded)) 71 } else if strings.HasSuffix(message, "Too Many Requests") { 72 return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrTooManyRequests)) 73 } else if strings.Contains(message, "Exceeded Storage Limit") { 74 // contains used to have some flexibility in constructing error message on server-side 75 return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrStorageLimitExceeded)) 76 } else if strings.Contains(message, "Exceeded Segments Limit") { 77 // contains used to have some flexibility in constructing error message on server-side 78 return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrSegmentsLimitExceeded)) 79 } 80 case errs2.IsRPC(err, rpcstatus.NotFound): 81 const ( 82 bucketNotFoundPrefix = "bucket not found" 83 objectNotFoundPrefix = "object not found" 84 ) 85 86 message := errs.Unwrap(err).Error() 87 if strings.HasPrefix(message, bucketNotFoundPrefix) { 88 // remove error prefix + ": " from message 89 bucket := strings.TrimPrefix(message[len(bucketNotFoundPrefix):], ": ") 90 return errwrapf("%w (%q)", ErrBucketNotFound, bucket) 91 } else if strings.HasPrefix(message, objectNotFoundPrefix) { 92 return errwrapf("%w (%q)", ErrObjectNotFound, key) 93 } 94 case errs2.IsRPC(err, rpcstatus.PermissionDenied): 95 originalErr := err 96 wrappedErr := errwrapf("%w (%v)", ErrPermissionDenied, originalErr) 97 // TODO: once we have confirmed nothing downstream 98 // is using errs2.IsRPC(err, rpcstatus.PermissionDenied), we should 99 // just return wrappedErr instead of this. 100 return &joinedErr{main: wrappedErr, alt: originalErr, code: rpcstatus.PermissionDenied} 101 } 102 103 return packageError.Wrap(err) 104 } 105 106 func errwrapf(format string, err error, args ...interface{}) error { 107 var all []interface{} 108 all = append(all, err) 109 all = append(all, args...) 110 return packageError.Wrap(fmt.Errorf(format, all...)) 111 } 112 113 type joinedErr struct { 114 main error 115 alt error 116 code rpcstatus.StatusCode 117 } 118 119 func (err *joinedErr) Is(target error) bool { 120 return errors.Is(err.main, target) || errors.Is(err.alt, target) 121 } 122 123 func (err *joinedErr) As(target interface{}) bool { 124 if errors.As(err.main, target) { 125 return true 126 } 127 if errors.As(err.alt, target) { 128 return true 129 } 130 return false 131 } 132 133 func (err *joinedErr) Code() uint64 { 134 return uint64(err.code) 135 } 136 137 func (err *joinedErr) Unwrap() error { 138 return err.main 139 } 140 141 func (err *joinedErr) Error() string { 142 return err.main.Error() 143 } 144 145 // Ungroup works with errs2.IsRPC and errs.IsFunc. 146 func (err *joinedErr) Ungroup() []error { 147 return []error{err.main, err.alt} 148 } 149 150 var noiseVersion = func() int64 { 151 if piecestore.NoiseEnabled { 152 // this is a number that indicates what noise support exists so far. 153 // 1 was our first implementation, but crucially had an errant round 154 // trip on uploads and downloads. 2 has the round trip fixed for 155 // downloads but not uploads. 3 has long tail cancelation for 156 // downloads fixed. 4 has round trips reduced for small uploads. 157 // we'll probably have future values here. 158 // we'll want to compare the performance of these different cases. 159 return 4 160 } 161 // 0 means no noise 162 return 0 163 }()