github.com/bhojpur/cache@v0.0.4/pkg/file/types/shard.go (about)

     1  package types
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"math"
    27  	"time"
    28  
    29  	"github.com/bhojpur/cache/pkg/file/core"
    30  	"github.com/bhojpur/cache/pkg/file/crypto"
    31  )
    32  
    33  var (
    34  	// ErrNilBytes is thrown when a shard is constructed when given nil bytes.
    35  	ErrNilBytes = errors.New("bytes to construct new shard must not be nil")
    36  
    37  	// ErrCannotCalculateShardSizes is thrown when the []byte to a CalculateShardSizes call is nil.
    38  	ErrCannotCalculateShardSizes = errors.New("bytes to calculate shard sizes must not be nil")
    39  )
    40  
    41  // Shard is a struct that holds a piece of data that is
    42  // a part of another, bigger piece of data.
    43  type Shard struct {
    44  	Size      uint32      `json:"size"`      // The size of the shard
    45  	Bytes     []byte      `json:"bytes"`     // The actual data of the shard
    46  	Hash      crypto.Hash `json:"hash"`      // The hash of the shard
    47  	Timestamp string      `json:"timestamp"` // The timestamp of the shard
    48  }
    49  
    50  // NewShard attempts to construct a new shard.
    51  func NewShard(bytes []byte) (*Shard, error) {
    52  	if bytes == nil {
    53  		return nil, ErrNilBytes
    54  	}
    55  
    56  	// Make the new shard
    57  	newShard := &Shard{
    58  		Size:      uint32(len(bytes)),
    59  		Bytes:     bytes,
    60  		Hash:      crypto.Sha3(bytes), // Hash the bytes of the shard, not the shard itself
    61  		Timestamp: time.Now().String(),
    62  	}
    63  
    64  	return newShard, nil
    65  }
    66  
    67  // GenerateShards generates a slice of shards given a string of bytes
    68  // This is an interface/api function.
    69  func GenerateShards(bytes []byte, n int) ([]Shard, error) {
    70  	var shards []Shard // Init the shard slice
    71  
    72  	shardSizes, err := calculateShardSizes(bytes, n) // Calculate the shard sizes
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	splitBytes, err := core.SplitBytes(bytes, shardSizes) // Split the bytes into the correct sizes
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// Generate the slices
    83  	for i := 0; i < len(shardSizes); i++ {
    84  		// Create a new shard
    85  		newShard, err := NewShard(
    86  			splitBytes[i],
    87  		)
    88  		if err != nil { // Check error
    89  			return nil, err
    90  		}
    91  		shards = append(shards, *newShard) // Append the new shard to the shard slice
    92  	}
    93  
    94  	return shards, nil
    95  }
    96  
    97  // calculateShardSizes determines the recommended size of each shard.
    98  func calculateShardSizes(raw []byte, n int) ([]uint32, error) {
    99  	rawSize := len(raw)
   100  
   101  	// Check that the input is not null
   102  	if rawSize == 0 || n == 0 {
   103  		return nil, ErrCannotCalculateShardSizes
   104  	}
   105  
   106  	partition := math.Floor(float64(rawSize / n)) // Calculate the size of each shard
   107  	partitionSize := uint32(partition)            // Convert to a uint32
   108  	modulo := uint32(rawSize % n)                 // Calculate the module mod n
   109  
   110  	// Populate a slice of the correct shard sizes
   111  	var sizes []uint32
   112  	for i := 0; i < n; i++ {
   113  		sizes = append(sizes, partitionSize)
   114  	}
   115  
   116  	// Adjust for the left over bytes
   117  	if modulo+partitionSize >= partitionSize*uint32(n) {
   118  		// This will be optimized eventually
   119  	}
   120  
   121  	sizes[n-1] += modulo // Add the left over bytes to the last element
   122  
   123  	return sizes, nil
   124  }
   125  
   126  /* ----- BEGIN HELPER FUNCTIONS ----- */
   127  
   128  func (shard *Shard) String() string {
   129  	json, _ := json.MarshalIndent(*shard, "", "  ")
   130  	return string(json)
   131  }
   132  
   133  // Serialize serializes a Shard pointer to bytes.
   134  func (shard *Shard) Serialize() []byte {
   135  	json, _ := json.MarshalIndent(*shard, "", "  ")
   136  	return json
   137  }
   138  
   139  // Validate makes sure that the shard is valid.
   140  func (shard *Shard) Validate() bool {
   141  	if crypto.Sha3((*shard).Bytes) == (*shard).Hash {
   142  		return true
   143  	}
   144  	return false
   145  }
   146  
   147  // ShardFromBytes constructs a *Shard from bytes.
   148  func ShardFromBytes(b []byte) (*Shard, error) {
   149  	buffer := &Shard{}               // Init shard buffer
   150  	err := json.Unmarshal(b, buffer) // Unmarshal json
   151  	return buffer, err
   152  }
   153  
   154  /* ----- END HELPER FUNCTIONS ----- */