storj.io/uplink@v1.13.0/private/stream/download.go (about) 1 // Copyright (C) 2019 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package stream 5 6 import ( 7 "context" 8 "crypto/hmac" 9 "crypto/sha1" 10 "io" 11 12 "storj.io/common/encryption" 13 "storj.io/common/paths" 14 "storj.io/common/storj" 15 "storj.io/eventkit" 16 "storj.io/picobuf" 17 "storj.io/uplink/private/eestream" 18 "storj.io/uplink/private/metaclient" 19 "storj.io/uplink/private/storage/streams" 20 ) 21 22 const ( 23 maxDownloadRetries = 6 24 ) 25 26 var ( 27 evs = eventkit.Package() 28 ) 29 30 // Download implements Reader, Seeker and Closer for reading from stream. 31 type Download struct { 32 ctx context.Context 33 info metaclient.DownloadInfo 34 streams *streams.Store 35 reader io.ReadCloser 36 offset int64 37 length int64 38 closed bool 39 40 decryptionRetries int 41 quiescenceRetries int 42 } 43 44 // NewDownload creates new stream download. 45 func NewDownload(ctx context.Context, info metaclient.DownloadInfo, streams *streams.Store) *Download { 46 return NewDownloadRange(ctx, info, streams, 0, -1) 47 } 48 49 // NewDownloadRange creates new stream range download with range from start to start+length. 50 func NewDownloadRange(ctx context.Context, info metaclient.DownloadInfo, streams *streams.Store, start, length int64) *Download { 51 size := info.Object.Size 52 if start > size { 53 start = size 54 } 55 if length < 0 || length+start > size { 56 length = size - start 57 } 58 return &Download{ 59 ctx: ctx, 60 info: info, 61 streams: streams, 62 offset: start, 63 length: length, 64 } 65 } 66 67 // Read reads up to len(data) bytes into data. 68 // 69 // If this is the first call it will read from the beginning of the stream. 70 // Use Seek to change the current offset for the next Read call. 71 // 72 // See io.Reader for more details. 73 func (download *Download) Read(data []byte) (n int, err error) { 74 if download.closed { 75 return 0, Error.New("already closed") 76 } 77 78 if download.reader == nil { 79 err = download.resetReader(false) 80 if err != nil { 81 return 0, err 82 } 83 } 84 85 if download.length <= 0 { 86 return 0, io.EOF 87 } 88 if download.length < int64(len(data)) { 89 data = data[:download.length] 90 } 91 n, err = download.reader.Read(data) 92 download.length -= int64(n) 93 download.offset += int64(n) 94 95 if err == nil && n > 0 { 96 download.decryptionRetries = 0 97 98 } else if encryption.ErrDecryptFailed.Has(err) { 99 evs.Event("decryption-failure", 100 eventkit.Int64("decryption-retries", int64(download.decryptionRetries)), 101 eventkit.Int64("quiescence-retries", int64(download.quiescenceRetries)), 102 eventkit.Int64("offset", download.offset), 103 eventkit.Int64("length", download.length), 104 eventkit.Bytes("path-checksum", pathChecksum(download.info.EncPath)), 105 eventkit.String("cipher-suite", download.info.Object.CipherSuite.String()), 106 eventkit.Bytes("stream-id", maybeSatStreamID(download.info.Object.Stream.ID)), 107 ) 108 109 if download.decryptionRetries+download.quiescenceRetries < maxDownloadRetries { 110 download.decryptionRetries++ 111 112 // force us to get new a new collection of limits. 113 download.info.DownloadedSegments = nil 114 115 err = download.resetReader(true) 116 } 117 } else if eestream.QuiescentError.Has(err) { 118 evs.Event("quiescence-failure", 119 eventkit.Int64("decryption-retries", int64(download.decryptionRetries)), 120 eventkit.Int64("quiescence-retries", int64(download.quiescenceRetries)), 121 eventkit.Int64("offset", download.offset), 122 eventkit.Int64("length", download.length), 123 eventkit.Bytes("path-checksum", pathChecksum(download.info.EncPath)), 124 eventkit.String("cipher-suite", download.info.Object.CipherSuite.String()), 125 eventkit.Bytes("stream-id", maybeSatStreamID(download.info.Object.Stream.ID)), 126 ) 127 128 if download.decryptionRetries+download.quiescenceRetries < maxDownloadRetries { 129 download.quiescenceRetries++ 130 131 download.info.DownloadedSegments = nil 132 133 err = download.resetReader(false) 134 } 135 } 136 137 return n, err 138 } 139 140 // Close closes the stream and releases the underlying resources. 141 func (download *Download) Close() error { 142 if download.closed { 143 return Error.New("already closed") 144 } 145 146 download.closed = true 147 148 if download.reader == nil { 149 return nil 150 } 151 152 return download.reader.Close() 153 } 154 155 func (download *Download) resetReader(nextSegmentErrorDetection bool) error { 156 if download.reader != nil { 157 err := download.reader.Close() 158 if err != nil { 159 return err 160 } 161 } 162 163 obj := download.info.Object 164 165 rr, err := download.streams.Get(download.ctx, obj.Bucket.Name, obj.Path, download.info, nextSegmentErrorDetection) 166 if err != nil { 167 return err 168 } 169 170 download.reader, err = rr.Range(download.ctx, download.offset, download.length) 171 if err != nil { 172 return err 173 } 174 175 return nil 176 } 177 178 // pathChecksum matches uplink.pathChecksum. 179 func pathChecksum(encPath paths.Encrypted) []byte { 180 mac := hmac.New(sha1.New, []byte(encPath.Raw())) 181 _, err := mac.Write([]byte("event")) 182 if err != nil { 183 panic(err) 184 } 185 return mac.Sum(nil)[:16] 186 } 187 188 // maybeSatStreamID returns the satellite-internal stream id for a given 189 // uplink stream id, without needing access to the internalpb package. 190 // it relies on the stream id being a protocol buffer with the internal id 191 // as a bytes field at position 10. if this ever changes, then this will 192 // just return nil, so callers should expect nil here. 193 func maybeSatStreamID(streamID storj.StreamID) (rv []byte) { 194 const satStreamIDField = 10 195 196 decoder := picobuf.NewDecoder(streamID.Bytes()) 197 decoder.Loop(func(d *picobuf.Decoder) { d.Bytes(satStreamIDField, &rv) }) 198 if decoder.Err() != nil { 199 rv = nil 200 } 201 202 return rv 203 }