github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/storage/hasherstore.go (about)

     1  // Copyleft 2018 The susy-graviton Authors
     2  // This file is part of the susy-graviton library.
     3  //
     4  // The susy-graviton library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The susy-graviton library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync/atomic"
    23  
    24  	ch "github.com/susy-go/susy-graviton/swarm/chunk"
    25  	"github.com/susy-go/susy-graviton/swarm/storage/encryption"
    26  	"golang.org/x/crypto/sha3"
    27  )
    28  
    29  type hasherStore struct {
    30  	store     ChunkStore
    31  	toEncrypt bool
    32  	hashFunc  SwarmHasher
    33  	hashSize  int           // content hash size
    34  	refSize   int64         // reference size (content hash + possibly encryption key)
    35  	errC      chan error    // global error channel
    36  	doneC     chan struct{} // closed by Close() call to indicate that count is the final number of chunks
    37  	quitC     chan struct{} // closed to quit unterminated routines
    38  	// nrChunks is used with atomic functions
    39  	// it is required to be at the end of the struct to ensure 64bit alignment for arm architecture
    40  	// see: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
    41  	nrChunks uint64 // number of chunks to store
    42  }
    43  
    44  // NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces.
    45  // With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore
    46  // and the hasherStore will take core of encryption/decryption of data if necessary
    47  func NewHasherStore(store ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *hasherStore {
    48  	hashSize := hashFunc().Size()
    49  	refSize := int64(hashSize)
    50  	if toEncrypt {
    51  		refSize += encryption.KeyLength
    52  	}
    53  
    54  	h := &hasherStore{
    55  		store:     store,
    56  		toEncrypt: toEncrypt,
    57  		hashFunc:  hashFunc,
    58  		hashSize:  hashSize,
    59  		refSize:   refSize,
    60  		errC:      make(chan error),
    61  		doneC:     make(chan struct{}),
    62  		quitC:     make(chan struct{}),
    63  	}
    64  
    65  	return h
    66  }
    67  
    68  // Put stores the chunkData into the ChunkStore of the hasherStore and returns the reference.
    69  // If hasherStore has a chunkEncryption object, the data will be encrypted.
    70  // Asynchronous function, the data will not necessarily be stored when it returns.
    71  func (h *hasherStore) Put(ctx context.Context, chunkData ChunkData) (Reference, error) {
    72  	c := chunkData
    73  	var encryptionKey encryption.Key
    74  	if h.toEncrypt {
    75  		var err error
    76  		c, encryptionKey, err = h.encryptChunkData(chunkData)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  	}
    81  	chunk := h.createChunk(c)
    82  	h.storeChunk(ctx, chunk)
    83  
    84  	return Reference(append(chunk.Address(), encryptionKey...)), nil
    85  }
    86  
    87  // Get returns data of the chunk with the given reference (retrieved from the ChunkStore of hasherStore).
    88  // If the data is encrypted and the reference contains an encryption key, it will be decrypted before
    89  // return.
    90  func (h *hasherStore) Get(ctx context.Context, ref Reference) (ChunkData, error) {
    91  	addr, encryptionKey, err := parseReference(ref, h.hashSize)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	chunk, err := h.store.Get(ctx, addr)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	chunkData := ChunkData(chunk.Data())
   102  	toDecrypt := (encryptionKey != nil)
   103  	if toDecrypt {
   104  		var err error
   105  		chunkData, err = h.decryptChunkData(chunkData, encryptionKey)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  	}
   110  	return chunkData, nil
   111  }
   112  
   113  // Close indicates that no more chunks will be put with the hasherStore, so the Wait
   114  // function can return when all the previously put chunks has been stored.
   115  func (h *hasherStore) Close() {
   116  	close(h.doneC)
   117  }
   118  
   119  // Wait returns when
   120  //    1) the Close() function has been called and
   121  //    2) all the chunks which has been Put has been stored
   122  func (h *hasherStore) Wait(ctx context.Context) error {
   123  	defer close(h.quitC)
   124  	var nrStoredChunks uint64 // number of stored chunks
   125  	var done bool
   126  	doneC := h.doneC
   127  	for {
   128  		select {
   129  		// if context is done earlier, just return with the error
   130  		case <-ctx.Done():
   131  			return ctx.Err()
   132  		// doneC is closed if all chunks have been submitted, from then we just wait until all of them are also stored
   133  		case <-doneC:
   134  			done = true
   135  			doneC = nil
   136  		// a chunk has been stored, if err is nil, then successfully, so increase the stored chunk counter
   137  		case err := <-h.errC:
   138  			if err != nil {
   139  				return err
   140  			}
   141  			nrStoredChunks++
   142  		}
   143  		// if all the chunks have been submitted and all of them are stored, then we can return
   144  		if done {
   145  			if nrStoredChunks >= atomic.LoadUint64(&h.nrChunks) {
   146  				return nil
   147  			}
   148  		}
   149  	}
   150  }
   151  
   152  func (h *hasherStore) createHash(chunkData ChunkData) Address {
   153  	hasher := h.hashFunc()
   154  	hasher.ResetWithLength(chunkData[:8]) // 8 bytes of length
   155  	hasher.Write(chunkData[8:])           // minus 8 []byte length
   156  	return hasher.Sum(nil)
   157  }
   158  
   159  func (h *hasherStore) createChunk(chunkData ChunkData) *chunk {
   160  	hash := h.createHash(chunkData)
   161  	chunk := NewChunk(hash, chunkData)
   162  	return chunk
   163  }
   164  
   165  func (h *hasherStore) encryptChunkData(chunkData ChunkData) (ChunkData, encryption.Key, error) {
   166  	if len(chunkData) < 8 {
   167  		return nil, nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData))
   168  	}
   169  
   170  	key, encryptedSpan, encryptedData, err := h.encrypt(chunkData)
   171  	if err != nil {
   172  		return nil, nil, err
   173  	}
   174  	c := make(ChunkData, len(encryptedSpan)+len(encryptedData))
   175  	copy(c[:8], encryptedSpan)
   176  	copy(c[8:], encryptedData)
   177  	return c, key, nil
   178  }
   179  
   180  func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryption.Key) (ChunkData, error) {
   181  	if len(chunkData) < 8 {
   182  		return nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData))
   183  	}
   184  
   185  	decryptedSpan, decryptedData, err := h.decrypt(chunkData, encryptionKey)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	// removing extra bytes which were just added for padding
   191  	length := ChunkData(decryptedSpan).Size()
   192  	for length > ch.DefaultSize {
   193  		length = length + (ch.DefaultSize - 1)
   194  		length = length / ch.DefaultSize
   195  		length *= uint64(h.refSize)
   196  	}
   197  
   198  	c := make(ChunkData, length+8)
   199  	copy(c[:8], decryptedSpan)
   200  	copy(c[8:], decryptedData[:length])
   201  
   202  	return c, nil
   203  }
   204  
   205  func (h *hasherStore) RefSize() int64 {
   206  	return h.refSize
   207  }
   208  
   209  func (h *hasherStore) encrypt(chunkData ChunkData) (encryption.Key, []byte, []byte, error) {
   210  	key := encryption.GenerateRandomKey(encryption.KeyLength)
   211  	encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8])
   212  	if err != nil {
   213  		return nil, nil, nil, err
   214  	}
   215  	encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:])
   216  	if err != nil {
   217  		return nil, nil, nil, err
   218  	}
   219  	return key, encryptedSpan, encryptedData, nil
   220  }
   221  
   222  func (h *hasherStore) decrypt(chunkData ChunkData, key encryption.Key) ([]byte, []byte, error) {
   223  	encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8])
   224  	if err != nil {
   225  		return nil, nil, err
   226  	}
   227  	encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:])
   228  	if err != nil {
   229  		return nil, nil, err
   230  	}
   231  	return encryptedSpan, encryptedData, nil
   232  }
   233  
   234  func (h *hasherStore) newSpanEncryption(key encryption.Key) encryption.Encryption {
   235  	return encryption.New(key, 0, uint32(ch.DefaultSize/h.refSize), sha3.NewLegacyKeccak256)
   236  }
   237  
   238  func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryption {
   239  	return encryption.New(key, int(ch.DefaultSize), 0, sha3.NewLegacyKeccak256)
   240  }
   241  
   242  func (h *hasherStore) storeChunk(ctx context.Context, chunk *chunk) {
   243  	atomic.AddUint64(&h.nrChunks, 1)
   244  	go func() {
   245  		select {
   246  		case h.errC <- h.store.Put(ctx, chunk):
   247  		case <-h.quitC:
   248  		}
   249  	}()
   250  }
   251  
   252  func parseReference(ref Reference, hashSize int) (Address, encryption.Key, error) {
   253  	encryptedRefLength := hashSize + encryption.KeyLength
   254  	switch len(ref) {
   255  	case AddressLength:
   256  		return Address(ref), nil, nil
   257  	case encryptedRefLength:
   258  		encKeyIdx := len(ref) - encryption.KeyLength
   259  		return Address(ref[:encKeyIdx]), encryption.Key(ref[encKeyIdx:]), nil
   260  	default:
   261  		return nil, nil, fmt.Errorf("Invalid reference length, expected %v or %v got %v", hashSize, encryptedRefLength, len(ref))
   262  	}
   263  }