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  }