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  }