storj.io/uplink@v1.13.0/private/storage/streams/splitter/splitter.go (about) 1 // Copyright (C) 2023 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package splitter 5 6 import ( 7 "context" 8 "crypto/rand" 9 "io" 10 11 "github.com/zeebo/errs" 12 13 "storj.io/common/encryption" 14 "storj.io/common/storj" 15 "storj.io/uplink/private/metaclient" 16 "storj.io/uplink/private/storage/streams/buffer" 17 ) 18 19 // Segment is an interface describing what operations a segment must provide 20 // to be uploaded to the network. 21 type Segment interface { 22 // Begin returns a metaclient.BatchItem to begin the segment, either inline 23 // or remote. 24 Begin() metaclient.BatchItem 25 26 // Position returns the segment position. 27 Position() metaclient.SegmentPosition 28 29 // Inline returns true if the segment is small enough to be inline. 30 Inline() bool 31 32 // Reader returns a fresh io.Reader that reads the data of the segment. 33 Reader() io.Reader 34 35 // EncryptETag encrypts the provided etag with the correct encryption 36 // keys that the segment is using. 37 EncryptETag(eTag []byte) ([]byte, error) 38 39 // Finalize returns a SegmentInfo if the segment is done being read 40 // from. 41 Finalize() *SegmentInfo 42 43 // DoneReading reports to the segment that we are no longer reading 44 // with the provided error to report to writes. 45 DoneReading(err error) 46 } 47 48 // SegmentInfo is information related to what is necessary to commit 49 // the segment. 50 type SegmentInfo struct { 51 // Encryption contains the encryption parameters that will be stored 52 // on the satellite. 53 Encryption metaclient.SegmentEncryption 54 55 // PlainSize is the plaintext number of bytes in the segment. 56 PlainSize int64 57 58 // EncryptedSize is the encrypted number of bytes in the segment. 59 EncryptedSize int64 60 } 61 62 // Options controls parameters of how an incoming stream of bytes is 63 // split into segments, remote and inline. 64 type Options struct { 65 // Split is the plaintext number of bytes to start new segments. 66 Split int64 67 68 // Minimum is the plaintext number of bytes necessary to create 69 // a remote segment. 70 Minimum int64 71 72 // Params controls the encryption used on the plaintext bytes. 73 Params storj.EncryptionParameters 74 75 // Key is used to encrypt the encryption keys used to encrypt 76 // the data. 77 Key *storj.Key 78 79 // PartNumber is the segment's part number if doing multipart 80 // uploads, and 0 otherwise. 81 PartNumber int32 82 } 83 84 // Splitter takes an incoming stream of bytes and splits it into 85 // encrypted segments. 86 type Splitter struct { 87 // NewBackend lets one swap out the backend used to store segments 88 // while they are being uploaded. 89 NewBackend func() (buffer.Backend, error) 90 91 split *baseSplitter 92 opts Options 93 maxSegmentSize int64 94 index int32 95 } 96 97 // New constructs a Splitter with the provided Options. 98 func New(opts Options) (*Splitter, error) { 99 maxSegmentSize, err := encryption.CalcEncryptedSize(opts.Split, opts.Params) 100 if err != nil { 101 return nil, errs.Wrap(err) 102 } 103 104 return &Splitter{ 105 NewBackend: func() (buffer.Backend, error) { 106 return buffer.NewChunkBackend(maxSegmentSize), nil 107 }, 108 109 split: newBaseSplitter(opts.Split, opts.Minimum), 110 opts: opts, 111 maxSegmentSize: maxSegmentSize, 112 }, nil 113 } 114 115 // Finish informs the Splitter that no more writes are coming, along with any error 116 // that may have caused the writes to stop. 117 func (s *Splitter) Finish(err error) { s.split.Finish(err) } 118 119 // Write appends data into the stream. 120 func (s *Splitter) Write(p []byte) (int, error) { return s.split.Write(p) } 121 122 // Next returns the next Segment split from the stream. If the stream is finished then 123 // it will return nil, nil. 124 func (s *Splitter) Next(ctx context.Context) (Segment, error) { 125 position := metaclient.SegmentPosition{ 126 PartNumber: s.opts.PartNumber, 127 Index: s.index, 128 } 129 var contentKey storj.Key 130 var keyNonce storj.Nonce 131 132 // do all of the fallible actions before checking with the splitter 133 nonce, err := nonceForPosition(position) 134 if err != nil { 135 return nil, err 136 } 137 if _, err := rand.Read(contentKey[:]); err != nil { 138 return nil, errs.Wrap(err) 139 } 140 if _, err := rand.Read(keyNonce[:]); err != nil { 141 return nil, errs.Wrap(err) 142 } 143 144 // note that we are *not* using the cipher suite from the encryption store, which 145 // might be encnull. we must make sure this actually encrypts here, otherwise the 146 // satellite will receive the decryption keys for all uploaded data. 147 // also, maybe the storage nodes may receive unencrypted data? 148 if s.opts.Params.CipherSuite == storj.EncNull || 149 s.opts.Params.CipherSuite == storj.EncNullBase64URL { 150 return nil, errs.New("programmer error") 151 } 152 enc, err := encryption.NewEncrypter(s.opts.Params.CipherSuite, &contentKey, &nonce, int(s.opts.Params.BlockSize)) 153 if err != nil { 154 return nil, errs.Wrap(err) 155 } 156 encKey, err := encryption.EncryptKey(&contentKey, s.opts.Params.CipherSuite, s.opts.Key, &keyNonce) 157 if err != nil { 158 return nil, errs.Wrap(err) 159 } 160 backend, err := s.NewBackend() 161 if err != nil { 162 return nil, errs.Wrap(err) 163 } 164 165 buf := buffer.New(backend, s.opts.Minimum) 166 wrc := encryption.TransformWriterPadded(buf, enc) 167 encBuf := newEncryptedBuffer(buf, wrc) 168 segEncryption := metaclient.SegmentEncryption{ 169 EncryptedKeyNonce: keyNonce, 170 EncryptedKey: encKey, 171 } 172 173 // check for the next segment/inline boundary. if an error, don't update any 174 // local state. 175 inline, eof, err := s.split.Next(ctx, encBuf) 176 switch { 177 case err != nil: 178 return nil, errs.Combine(errs.Wrap(err), errs.Wrap(backend.Close())) 179 180 case eof: 181 return nil, errs.Wrap(backend.Close()) 182 183 case inline != nil: 184 // encrypt the inline data, and update the internal state if it succeeds. 185 encData, err := encryption.Encrypt(inline, s.opts.Params.CipherSuite, &contentKey, &nonce) 186 if err != nil { 187 return nil, errs.Combine(errs.Wrap(err), errs.Wrap(backend.Close())) 188 } 189 190 // everything fallible is done. update the internal state. 191 s.index++ 192 193 return &splitterInline{ 194 position: position, 195 encryption: segEncryption, 196 encParams: s.opts.Params, 197 contentKey: &contentKey, 198 199 encData: encData, 200 plainSize: int64(len(inline)), 201 }, errs.Wrap(backend.Close()) 202 203 default: 204 // everything fallible is done. update the internal state. 205 s.index++ 206 207 return &splitterSegment{ 208 position: position, 209 encryption: segEncryption, 210 encParams: s.opts.Params, 211 contentKey: &contentKey, 212 213 maxSegmentSize: s.maxSegmentSize, 214 encTransformer: enc, 215 encBuf: encBuf, 216 }, nil 217 } 218 }