|
|
|
package checksum
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
)
|
|
|
|
|
|
|
|
const CHECKSUMLEN uint = 32
|
|
|
|
|
|
|
|
type CheckSum [CHECKSUMLEN]byte
|
|
|
|
|
|
|
|
// returns a checksum of given file. NOTE, that it creates checksum
|
|
|
|
// not of a full file (from all file bytes), but from separate byte blocks.
|
|
|
|
// This is done as an optimisation because the file can be very large in size.
|
|
|
|
// The general idea:
|
|
|
|
// BOF... CHUNK -> STEP -> CHUNK... EOF
|
|
|
|
// checksum := sha256.Sum256(ALLCHUNKS)
|
|
|
|
// GetPartialCheckSum is default method used to get a file checksum by sender and receiver
|
|
|
|
func GetPartialCheckSum(file *os.File) (CheckSum, error) {
|
|
|
|
// "capturing" CHUNKSIZE bytes and then skipping STEP bytes before the next chunk until the last one
|
|
|
|
const CHUNKS uint = 100
|
|
|
|
const CHUNKSIZE uint = 100
|
|
|
|
const STEP uint = 250
|
|
|
|
|
|
|
|
fileStats, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return [CHECKSUMLEN]byte{}, fmt.Errorf("could not get the stats: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fileSize := fileStats.Size()
|
|
|
|
|
|
|
|
if fileSize < int64(CHUNKS*CHUNKSIZE+STEP*(CHUNKS-1)) {
|
|
|
|
// file is too small to chop it in chunks, so just doing full checksum
|
|
|
|
|
|
|
|
checksum, err := getFullCheckSum(file)
|
|
|
|
if err != nil {
|
|
|
|
return [CHECKSUMLEN]byte{}, err
|
|
|
|
}
|
|
|
|
return checksum, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var capturedChunks string
|
|
|
|
var read uint64 = 0
|
|
|
|
for i := 0; uint(i) < CHUNKS; i++ {
|
|
|
|
buffer := make([]byte, CHUNKSIZE)
|
|
|
|
r, _ := file.ReadAt(buffer, int64(read))
|
|
|
|
|
|
|
|
capturedChunks += string(buffer)
|
|
|
|
|
|
|
|
read += uint64(r)
|
|
|
|
read += uint64(STEP)
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum := sha256.Sum256([]byte(capturedChunks))
|
|
|
|
return checksum, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a sha256 checksum of given file
|
|
|
|
func getFullCheckSum(file *os.File) (CheckSum, error) {
|
|
|
|
filebytes, err := io.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return [CHECKSUMLEN]byte{}, fmt.Errorf("could not read the file: %s", err)
|
|
|
|
}
|
|
|
|
checksum := sha256.Sum256(filebytes)
|
|
|
|
|
|
|
|
return checksum, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simply compares 2 given checksums. If they are equal - returns true
|
|
|
|
func AreEqual(checksum1, checksum2 CheckSum) bool {
|
|
|
|
var i int = 0
|
|
|
|
for _, checksum1Byte := range checksum1 {
|
|
|
|
checksum2Byte := checksum2[i]
|
|
|
|
if checksum1Byte != checksum2Byte {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tries to convert given bytes into CheckSum type
|
|
|
|
func BytesToChecksum(bytes []byte) (CheckSum, error) {
|
|
|
|
if uint(len(bytes)) > CHECKSUMLEN {
|
|
|
|
return CheckSum{}, fmt.Errorf("provided bytes` length is bigger than the checksum`s")
|
|
|
|
} else if uint(len(bytes)) < CHECKSUMLEN {
|
|
|
|
return CheckSum{}, fmt.Errorf("provided bytes` length is smaller than needed")
|
|
|
|
}
|
|
|
|
|
|
|
|
var checksum [CHECKSUMLEN]byte
|
|
|
|
for index, b := range bytes {
|
|
|
|
checksum[index] = b
|
|
|
|
}
|
|
|
|
return CheckSum(checksum), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Converts given checksum into []byte
|
|
|
|
func ChecksumToBytes(checksum CheckSum) []byte {
|
|
|
|
var checksumBytes []byte
|
|
|
|
for _, b := range checksum {
|
|
|
|
checksumBytes = append(checksumBytes, b)
|
|
|
|
}
|
|
|
|
return checksumBytes
|
|
|
|
}
|