git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/object/erasurecode/reconstruct.go (about) 1 package erasurecode 2 3 import ( 4 "bytes" 5 "crypto/ecdsa" 6 "fmt" 7 8 objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 9 "github.com/klauspost/reedsolomon" 10 ) 11 12 // Reconstruct returns full object reconstructed from parts. 13 // All non-nil objects in parts must have EC header with the same `total` field equal to len(parts). 14 // The slice must contain at least one non nil object. 15 // Index of the objects in parts must be equal to it's index field in the EC header. 16 // The parts slice isn't changed and can be used concurrently for reading. 17 func (c *Constructor) Reconstruct(parts []*objectSDK.Object) (*objectSDK.Object, error) { 18 res, err := c.ReconstructHeader(parts) 19 if err != nil { 20 return nil, err 21 } 22 23 c.fillPayload(parts) 24 25 payload, err := reconstructExact(c.enc, int(res.PayloadSize()), c.payloadShards) 26 if err != nil { 27 return nil, fmt.Errorf("%w: %w", ErrMalformedSlice, err) 28 } 29 30 res.SetPayload(payload) 31 return res, nil 32 } 33 34 // ReconstructHeader returns object header reconstructed from parts. 35 // All non-nil objects in parts must have EC header with the same `total` field equal to len(parts). 36 // The slice must contain at least one non nil object. 37 // Index of the objects in parts must be equal to it's index field in the EC header. 38 // The parts slice isn't changed and can be used concurrently for reading. 39 func (c *Constructor) ReconstructHeader(parts []*objectSDK.Object) (*objectSDK.Object, error) { 40 c.clear() 41 42 if err := c.fillHeader(parts); err != nil { 43 return nil, err 44 } 45 46 obj, err := c.reconstructHeader() 47 if err != nil { 48 return nil, fmt.Errorf("%w: %w", ErrMalformedSlice, err) 49 } 50 return obj, nil 51 } 52 53 // ReconstructParts reconstructs specific EC parts without reconstructing full object. 54 // All non-nil objects in parts must have EC header with the same `total` field equal to len(parts). 55 // The slice must contain at least one non nil object. 56 // Index of the objects in parts must be equal to it's index field in the EC header. 57 // Those parts for which corresponding element in required is true must be nil and will be overwritten. 58 // Because partial reconstruction only makes sense for full objects, all parts must have non-empty payload. 59 // If key is not nil, all reconstructed parts are signed with this key. 60 func (c *Constructor) ReconstructParts(parts []*objectSDK.Object, required []bool, key *ecdsa.PrivateKey) error { 61 if len(required) != len(parts) { 62 return fmt.Errorf("len(parts) != len(required): %d != %d", len(parts), len(required)) 63 } 64 65 c.clear() 66 67 if err := c.fillHeader(parts); err != nil { 68 return err 69 } 70 c.fillPayload(parts) 71 72 if err := c.enc.ReconstructSome(c.payloadShards, required); err != nil { 73 return fmt.Errorf("%w: %w", ErrMalformedSlice, err) 74 } 75 if err := c.enc.ReconstructSome(c.headerShards, required); err != nil { 76 return fmt.Errorf("%w: %w", ErrMalformedSlice, err) 77 } 78 79 nonNilPart := 0 80 for i := range parts { 81 if parts[i] != nil { 82 nonNilPart = i 83 break 84 } 85 } 86 87 ec := parts[nonNilPart].GetECHeader() 88 ecParentInfo := objectSDK.ECParentInfo{ 89 ID: ec.Parent(), 90 SplitID: ec.ParentSplitID(), 91 SplitParentID: ec.ParentSplitParentID(), 92 Attributes: ec.ParentAttributes(), 93 } 94 total := ec.Total() 95 96 for i := range required { 97 if parts[i] != nil || !required[i] { 98 continue 99 } 100 101 part := objectSDK.New() 102 copyRequiredFields(part, parts[nonNilPart]) 103 part.SetPayload(c.payloadShards[i]) 104 part.SetPayloadSize(uint64(len(c.payloadShards[i]))) 105 part.SetECHeader(objectSDK.NewECHeader(ecParentInfo, uint32(i), total, c.headerShards[i], c.headerLength)) 106 107 if err := setIDWithSignature(part, key); err != nil { 108 return err 109 } 110 parts[i] = part 111 } 112 return nil 113 } 114 115 func (c *Constructor) reconstructHeader() (*objectSDK.Object, error) { 116 data, err := reconstructExact(c.enc, int(c.headerLength), c.headerShards) 117 if err != nil { 118 return nil, err 119 } 120 121 var obj objectSDK.Object 122 return &obj, obj.Unmarshal(data) 123 } 124 125 func reconstructExact(enc reedsolomon.Encoder, size int, shards [][]byte) ([]byte, error) { 126 if err := enc.ReconstructData(shards); err != nil { 127 return nil, err 128 } 129 130 // Technically, this error will be returned from enc.Join(). 131 // However, allocating based on unvalidated user data is an easy attack vector. 132 // Preallocating seems to have enough benefits to justify a slight increase in code complexity. 133 maxSize := 0 134 for i := range shards { 135 maxSize += len(shards[i]) 136 } 137 if size > maxSize { 138 return nil, reedsolomon.ErrShortData 139 } 140 141 buf := bytes.NewBuffer(make([]byte, 0, size)) 142 if err := enc.Join(buf, shards, size); err != nil { 143 return nil, err 144 } 145 return buf.Bytes(), nil 146 }