gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skyfilefanout.go (about)

     1  package renter
     2  
     3  // skyfilefanout.go implements the encoding and decoding of skyfile fanouts. A
     4  // fanout is a description of all of the Merkle roots in a file, organized by
     5  // chunk. Each chunk has N pieces, and each piece has a Merkle root which is a
     6  // 32 byte hash.
     7  //
     8  // The fanout is encoded such that the first 32 bytes are chunk 0 index 0, the
     9  // second 32 bytes are chunk 0 index 1, etc... and then the second chunk is
    10  // appended immediately after, and so on.
    11  
    12  import (
    13  	"fmt"
    14  
    15  	"gitlab.com/NebulousLabs/errors"
    16  	"gitlab.com/SkynetLabs/skyd/build"
    17  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem"
    18  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile"
    19  	"go.sia.tech/siad/crypto"
    20  )
    21  
    22  // skyfileEncodeFanoutFromFileNode will create the serialized fanout for
    23  // a fileNode. The encoded fanout is just the list of hashes that can be used to
    24  // retrieve a file concatenated together, where piece 0 of chunk 0 is first,
    25  // piece 1 of chunk 0 is second, etc. This method assumes the  special case for
    26  // unencrypted 1-of-N files. Because every piece is identical for an unencrypted
    27  // 1-of-N file, only the first piece of each chunk is included.
    28  func skyfileEncodeFanoutFromFileNode(fileNode *filesystem.FileNode, onePiece bool) ([]byte, error) {
    29  	// Allocate the memory for the fanout.
    30  	fanout := make([]byte, 0, fileNode.NumChunks()*crypto.HashSize)
    31  
    32  	// findPieceInPieceSet will scan through a piece set and return the first
    33  	// non-empty piece in the set. If the set is empty, or every piece in the
    34  	// set is empty, then the emptyHash is returned.
    35  	var emptyHash crypto.Hash
    36  	findPieceInPieceSet := func(pieceSet []siafile.Piece) crypto.Hash {
    37  		for _, piece := range pieceSet {
    38  			if piece.MerkleRoot != emptyHash {
    39  				return piece.MerkleRoot
    40  			}
    41  		}
    42  		return emptyHash
    43  	}
    44  
    45  	// Build the fanout one chunk at a time.
    46  	for i := uint64(0); i < fileNode.NumChunks(); i++ {
    47  		// Get the pieces for this chunk.
    48  		allPieces, err := fileNode.Pieces(i)
    49  		if err != nil {
    50  			return nil, errors.AddContext(err, "unable to get sector roots from file")
    51  		}
    52  
    53  		// Special case: if only one piece is needed, only use the first piece
    54  		// that is available. This is because 1-of-N files are encoded more
    55  		// compactly in the fanout.
    56  		if onePiece {
    57  			root := emptyHash
    58  			for _, pieceSet := range allPieces {
    59  				root = findPieceInPieceSet(pieceSet)
    60  				if root != emptyHash {
    61  					fanout = append(fanout, root[:]...)
    62  					break
    63  				}
    64  			}
    65  			// If root is still equal to emptyHash it means that we didn't add a
    66  			// piece root for this chunk.
    67  			if root == emptyHash {
    68  				err = fmt.Errorf("No piece root encoded for chunk %v", i)
    69  				build.Critical(err)
    70  				return nil, err
    71  			}
    72  			continue
    73  		}
    74  
    75  		// Generate all the piece roots
    76  		for pi, pieceSet := range allPieces {
    77  			root := findPieceInPieceSet(pieceSet)
    78  			if root == emptyHash {
    79  				err = fmt.Errorf("Empty piece root at index %v found for chunk %v", pi, i)
    80  				build.Critical(err)
    81  				return nil, err
    82  			}
    83  			fanout = append(fanout, root[:]...)
    84  		}
    85  	}
    86  	return fanout, nil
    87  }