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 ----- */