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

     1  package renter
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/SkynetLabs/skyd/build"
    10  	"gitlab.com/SkynetLabs/skyd/skymodules"
    11  	"go.sia.tech/core/net/rhp"
    12  	"go.sia.tech/siad/crypto"
    13  )
    14  
    15  // BuildSubSectionProof builds a merkle proof by combining a subSectionProof
    16  // leading up to a section root and a sectionProof leading up to a sector root.
    17  // The provided sectionIndex describes the index of the datasection within the
    18  // streambuffer.
    19  func BuildSubSectionProof(sectionIndexInChunk uint64, subSectionProof, sectionProof []crypto.Hash) []crypto.Hash {
    20  	i := rhp.RangeProofSize(sectionIndexInChunk+1, sectionIndexInChunk, sectionIndexInChunk+1)
    21  	left := append([]crypto.Hash{}, sectionProof[:i]...)
    22  	right := append([]crypto.Hash{}, sectionProof[i:]...)
    23  	proof := append(left, subSectionProof...)
    24  	proof = append(proof, right...)
    25  	return proof
    26  }
    27  
    28  // writeTrustlessLargeRecursive handles downloading from a large skyfile. Since
    29  // this is more involved than downloading a small file this will be implemented
    30  // in a f/u.
    31  func (s *stream) writeTrustlessLarge(w io.Writer, length uint64) error {
    32  	// Convenience vars.
    33  	sb := s.staticStreamBuffer
    34  	dataSize := sb.staticDataSource.DataSize()
    35  
    36  	// Check that the read is within bounds.
    37  	if s.offset+length > dataSize {
    38  		return fmt.Errorf("out-of-bounds read: %v + %v > %v", s.offset, length, dataSize)
    39  	}
    40  
    41  	// Write sections one-by-one.
    42  	for length > 0 {
    43  		n, err := s.writeTrustlessLargeSection(w, s.offset, length)
    44  		if err != nil {
    45  			return fmt.Errorf("failed to write section %v: %v", s.offset, err)
    46  		}
    47  		length -= n
    48  	}
    49  	return nil
    50  }
    51  
    52  // writeTrustlessLargeSection writes the logical data required for the next
    53  // length bytes of a section plus proves to the connection.
    54  func (s *stream) writeTrustlessLargeSection(w io.Writer, currentOffset, length uint64) (uint64, error) {
    55  	// Helper vars.
    56  	sb := s.staticStreamBuffer
    57  	ds := sb.staticDataSource
    58  	layout := sb.staticDataSource.Layout()
    59  
    60  	// Various helper vars related to indices and offsets within chunks/sections.
    61  	currentSection := currentOffset / sb.staticDataSource.RequestSize()
    62  	offsetInSection := currentOffset % sb.staticDataSource.RequestSize()
    63  	chunkSize := skymodules.ChunkSize(layout.CipherType, uint64(layout.FanoutDataPieces))
    64  	sectionSize := sb.staticDataSource.RequestSize()
    65  	chunkIndex := currentOffset / chunkSize
    66  	offsetInChunk := currentOffset % chunkSize
    67  	sectionsPerChunk := chunkSize / sectionSize
    68  	if chunkSize%sectionSize != 0 {
    69  		return 0, fmt.Errorf("chunkSize must be section aligned %v mod %v == %v", chunkSize, sectionSize, chunkSize%sectionSize)
    70  	}
    71  
    72  	// Cap the length at a section size.
    73  	if offsetInSection+length > sb.staticDataSource.RequestSize() {
    74  		length = sb.staticDataSource.RequestSize() - offsetInSection
    75  	}
    76  
    77  	// Get an erasure coder for the fanout.
    78  	fanoutEC, err := skymodules.NewRSSubCode(int(layout.FanoutDataPieces), int(layout.FanoutParityPieces), crypto.SegmentSize)
    79  	if err != nil {
    80  		return 0, err
    81  	}
    82  
    83  	// Compute the sectionIndexInChunk.
    84  	sectionIndexInChunk := currentSection % sectionsPerChunk
    85  	sectionOffInChunk, _ := GetPieceOffsetAndLen(fanoutEC, offsetInChunk/sectionSize*sectionSize, sectionSize)
    86  
    87  	// Get the datasection for the current section.
    88  	sb.mu.Lock()
    89  	dataSection, exists := sb.dataSections[currentSection]
    90  	sb.mu.Unlock()
    91  	if !exists {
    92  		err := errors.New("writeTrustLessLarge: data section should always be present in the stream buffer for the current offset of a stream")
    93  		build.Critical(err)
    94  		return 0, err
    95  	}
    96  
    97  	// Wait for the data.
    98  	dd, err := dataSection.managedData(s.staticContext)
    99  	if err != nil {
   100  		return 0, err
   101  	}
   102  
   103  	// We need to return only the minimum number of pieces
   104  	neededPieces := layout.FanoutDataPieces
   105  
   106  	// Write a piece, followed by the proof.
   107  	piecesSent := uint8(0)
   108  	for pieceIndex, piece := range dd.LogicalChunkData {
   109  		if piecesSent >= neededPieces {
   110  			break // don't need more than min pieces even if we got more
   111  		}
   112  		if len(piece) == 0 {
   113  			continue
   114  		}
   115  		proof := dd.Proofs[pieceIndex]
   116  		if len(proof) == 0 {
   117  			build.Critical("shouldn't have an empty proof for a non-empty piece")
   118  			continue
   119  		}
   120  
   121  		// Fetch the root of the piece within the fanout, the proof for
   122  		// that root as well as the offset.
   123  		logicalPieceRoot, pieceRootProof, pieceRootOff, err := ds.ReadFanout(chunkIndex, uint64(pieceIndex))
   124  		if err != nil {
   125  			return 0, err
   126  		}
   127  		var pieceRoot crypto.Hash
   128  		copy(pieceRoot[:], logicalPieceRoot[pieceRootOff%crypto.SegmentSize:][:crypto.HashSize])
   129  
   130  		// Write the offset of the piece root, followed by the piece root and
   131  		// the proof for the piece root.
   132  		err = binary.Write(w, binary.LittleEndian, pieceRootOff)
   133  		if err != nil {
   134  			return 0, err
   135  		}
   136  		_, err = w.Write(logicalPieceRoot)
   137  		if err != nil {
   138  			return 0, err
   139  		}
   140  		for _, h := range pieceRootProof {
   141  			_, err = w.Write(h[:])
   142  			if err != nil {
   143  				return 0, err
   144  			}
   145  		}
   146  
   147  		// Trim the piece according to the requested offset and length.
   148  		pieceOff, pieceLen := GetPieceOffsetAndLen(fanoutEC, offsetInChunk, length)
   149  		pieceOffInChunk := pieceOff - sectionOffInChunk
   150  		trimmedPiece := piece[pieceOffInChunk:][:pieceLen]
   151  
   152  		// Finally write the piece and the piece proof.
   153  		_, err = w.Write(trimmedPiece)
   154  		if err != nil {
   155  			return 0, err
   156  		}
   157  
   158  		subSectionProofStart := pieceOffInChunk / crypto.SegmentSize
   159  		subSectionProofEnd := (pieceOffInChunk + pieceLen) / crypto.SegmentSize
   160  		subSectionProof := crypto.MerkleRangeProof(piece, int(subSectionProofStart), int(subSectionProofEnd))
   161  		fullPieceProof := BuildSubSectionProof(sectionIndexInChunk, subSectionProof, proof)
   162  		for _, h := range fullPieceProof {
   163  			_, err = w.Write(h[:])
   164  			if err != nil {
   165  				return 0, err
   166  			}
   167  		}
   168  
   169  		// Piece sent successfully.
   170  		piecesSent++
   171  	}
   172  	if piecesSent < neededPieces {
   173  		return 0, errors.New("not enough pieces sent")
   174  	}
   175  
   176  	// Update offset.
   177  	s.offset += length
   178  	s.prepareOffset()
   179  	return length, nil
   180  }
   181  
   182  // writeTrustlessLargeRecursive handles downloading from a large skyfile with a
   183  // recursive fanout.
   184  // NOTE: This is not supported right at the moment.
   185  func (s *stream) writeTrustlessLargeRecursive() error {
   186  	return errors.New("trustless download of recursive fanout skylinks not yet supported")
   187  }
   188  
   189  // writeTrustlessSmall handles downloading from a small skyfile.
   190  func (s *stream) writeTrustlessSmall(w io.Writer, length uint64) error {
   191  	sb := s.staticStreamBuffer
   192  	ds := sb.staticDataSource
   193  	dr, err := ds.ReadBaseSectorPayload(s.offset, length)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	dd := dr.externDownloadedData
   198  
   199  	// We are sending payload from a base sector. So we should not need to send more
   200  	// than a single piece.
   201  	for pieceIndex, piece := range dd.LogicalChunkData {
   202  		if piece == nil {
   203  			continue
   204  		}
   205  		// Write the piece.
   206  		_, err := w.Write(piece)
   207  		if err != nil {
   208  			return err
   209  		}
   210  
   211  		// Write the corresponding proof.
   212  		for _, h := range dd.Proofs[pieceIndex] {
   213  			_, err = w.Write(h[:])
   214  			if err != nil {
   215  				return err
   216  			}
   217  		}
   218  		return nil
   219  	}
   220  	err = errors.New("Failed to send a piece to the client. This should never happen.")
   221  	build.Critical(err)
   222  	return err
   223  }
   224  
   225  // TrustlessWrite writes the trustless download of a certain length to the
   226  // writer.
   227  func (s *stream) TrustlessWrite(w io.Writer, length uint64) error {
   228  	s.mu.Lock()
   229  	defer s.mu.Unlock()
   230  
   231  	sb := s.staticStreamBuffer
   232  	ds := sb.staticDataSource
   233  
   234  	// Fetch the layout and its proof.
   235  	layout, layoutData, proof := ds.RawLayout()
   236  
   237  	// Can't request no data.
   238  	if length == 0 {
   239  		return errors.New("can't request 0 byte")
   240  	}
   241  
   242  	// Check for out-of-bounds read.
   243  	if s.offset+length > layout.Filesize {
   244  		return fmt.Errorf("read at offset %v and length %v is out-of-bounds for file of size %v", s.offset, length, layout.Filesize)
   245  	}
   246  
   247  	// Write metadata first.
   248  	err := binary.Write(w, binary.LittleEndian, uint64(ds.RequestSize()))
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	// Write the layout and proof.
   254  	_, err = w.Write(layoutData)
   255  	if err != nil {
   256  		return errors.AddContext(err, "failed to write layout data")
   257  	}
   258  
   259  	for _, h := range proof {
   260  		_, err = w.Write(h[:])
   261  		if err != nil {
   262  			return errors.AddContext(err, "failed to write layout proof")
   263  		}
   264  	}
   265  
   266  	// Call appropriate handler for the actual data.
   267  	if layout.IsSmallFile() {
   268  		return s.writeTrustlessSmall(w, length)
   269  	}
   270  	if ds.HasRecursiveFanout() {
   271  		return s.writeTrustlessLargeRecursive()
   272  	}
   273  	return s.writeTrustlessLarge(w, length)
   274  }