github.com/ethersphere/bee/v2@v2.2.0/pkg/cac/cac.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cac
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  
    12  	"github.com/ethersphere/bee/v2/pkg/bmtpool"
    13  	"github.com/ethersphere/bee/v2/pkg/swarm"
    14  )
    15  
    16  var (
    17  	ErrChunkSpanShort = fmt.Errorf("chunk span must have exactly length of %d", swarm.SpanSize)
    18  	ErrChunkDataLarge = fmt.Errorf("chunk data exceeds maximum allowed length")
    19  )
    20  
    21  // New creates a new content address chunk by initializing a span and appending the data to it.
    22  func New(data []byte) (swarm.Chunk, error) {
    23  	dataLength := len(data)
    24  
    25  	if err := validateDataLength(dataLength); err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	span := make([]byte, swarm.SpanSize)
    30  	binary.LittleEndian.PutUint64(span, uint64(dataLength))
    31  
    32  	return newWithSpan(data, span)
    33  }
    34  
    35  // NewWithDataSpan creates a new chunk assuming that the span precedes the actual data.
    36  func NewWithDataSpan(data []byte) (swarm.Chunk, error) {
    37  	dataLength := len(data)
    38  
    39  	if err := validateDataLength(dataLength - swarm.SpanSize); err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	return newWithSpan(data[swarm.SpanSize:], data[:swarm.SpanSize])
    44  }
    45  
    46  // validateDataLength validates if data length (without span) is correct.
    47  func validateDataLength(dataLength int) error {
    48  	if dataLength < 0 { // dataLength could be negative when span size is subtracted
    49  		spanLength := swarm.SpanSize + dataLength
    50  		return fmt.Errorf("invalid CAC span length %d: %w", spanLength, ErrChunkSpanShort)
    51  	}
    52  	if dataLength > swarm.ChunkSize {
    53  		return fmt.Errorf("invalid CAC data length %d: %w", dataLength, ErrChunkDataLarge)
    54  	}
    55  	return nil
    56  }
    57  
    58  // newWithSpan creates a new chunk prepending the given span to the data.
    59  func newWithSpan(data, span []byte) (swarm.Chunk, error) {
    60  	hash, err := DoHash(data, span)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	cacData := make([]byte, len(data)+len(span))
    66  	copy(cacData, span)
    67  	copy(cacData[swarm.SpanSize:], data)
    68  
    69  	return swarm.NewChunk(swarm.NewAddress(hash), cacData), nil
    70  }
    71  
    72  // Valid checks whether the given chunk is a valid content-addressed chunk.
    73  func Valid(c swarm.Chunk) bool {
    74  	data := c.Data()
    75  
    76  	if validateDataLength(len(data)-swarm.SpanSize) != nil {
    77  		return false
    78  	}
    79  
    80  	hash, _ := DoHash(data[swarm.SpanSize:], data[:swarm.SpanSize])
    81  
    82  	return bytes.Equal(hash, c.Address().Bytes())
    83  }
    84  
    85  func DoHash(data, span []byte) ([]byte, error) {
    86  	hasher := bmtpool.Get()
    87  	defer bmtpool.Put(hasher)
    88  
    89  	hasher.SetHeader(span)
    90  	if _, err := hasher.Write(data); err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	return hasher.Hash(nil)
    95  }