2022-08-24 02:54:01 +00:00
|
|
|
package erasureencode
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-08-28 06:04:36 +00:00
|
|
|
|
|
|
|
chunkmeta "git.keganmyers.com/terribleplan/file-store/pkg/chunk/meta"
|
|
|
|
filemeta "git.keganmyers.com/terribleplan/file-store/pkg/file/meta"
|
|
|
|
"git.keganmyers.com/terribleplan/file-store/pkg/util"
|
2022-08-24 02:54:01 +00:00
|
|
|
)
|
|
|
|
|
2022-08-28 06:04:36 +00:00
|
|
|
type ReadPlanner func(meta *filemeta.Meta) []chunkmeta.ShardMeta
|
|
|
|
type ReadHandler func(data []byte, plan chunkmeta.ShardMeta, readNum int) error
|
2022-08-24 02:54:01 +00:00
|
|
|
|
2022-08-28 06:04:36 +00:00
|
|
|
func decodeFn(inputs []io.ReadSeeker, file io.Writer, meta *filemeta.Meta, getPlan ReadPlanner, handleRead ReadHandler) error {
|
2022-08-24 02:54:01 +00:00
|
|
|
raw := []byte{}
|
|
|
|
rawLen := int32(0)
|
|
|
|
fullPlan := getPlan(meta)
|
|
|
|
|
|
|
|
// we only need to seek once as the rest of the reads should be linear
|
2022-08-28 06:04:36 +00:00
|
|
|
for _, plan := range fullPlan[0:util.Min(int64(meta.Params.Shards), int64(len(fullPlan)))] {
|
2022-08-25 03:12:12 +00:00
|
|
|
if _, err := inputs[plan.Chunk].Seek(plan.ChunkOffset, io.SeekStart); err != nil {
|
2022-08-24 02:54:01 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, plan := range fullPlan {
|
|
|
|
if rawLen != plan.Size {
|
|
|
|
raw = make([]byte, plan.Size)
|
|
|
|
rawLen = plan.Size
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := io.ReadFull(inputs[plan.Chunk], raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := handleRead(raw, plan, i); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-28 06:04:36 +00:00
|
|
|
func Decode(inputs []io.ReadSeeker, file io.Writer, meta *filemeta.Meta) error {
|
|
|
|
return decodeFn(inputs, file, meta, func(meta *filemeta.Meta) []chunkmeta.ShardMeta {
|
2022-08-24 02:54:01 +00:00
|
|
|
return meta.Params.Plan(0, meta.Params.Size)
|
2022-08-28 06:04:36 +00:00
|
|
|
}, func(data []byte, _ chunkmeta.ShardMeta, _ int) error {
|
2022-08-24 02:54:01 +00:00
|
|
|
if _, err := file.Write(data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-08-28 06:04:36 +00:00
|
|
|
func DecodeAndValidate(inputs []io.ReadSeeker, file io.Writer, meta *filemeta.Meta) error {
|
2022-08-25 03:12:12 +00:00
|
|
|
shards := int64(meta.Params.Shards)
|
2022-08-24 02:54:01 +00:00
|
|
|
|
|
|
|
// get set up to read meta including the padding
|
|
|
|
validateParams := *meta
|
2022-08-25 03:12:12 +00:00
|
|
|
if meta.Params.Size%shards > 0 {
|
|
|
|
validateParams.Size = (meta.Params.Size / shards) * (shards + 1)
|
2022-08-24 02:54:01 +00:00
|
|
|
}
|
|
|
|
|
2022-08-28 06:04:36 +00:00
|
|
|
return decodeFn(inputs, file, meta, func(_ *filemeta.Meta) []chunkmeta.ShardMeta {
|
2022-08-24 02:54:01 +00:00
|
|
|
return validateParams.Plan(0, validateParams.Size)
|
2022-08-28 06:04:36 +00:00
|
|
|
}, func(data []byte, read chunkmeta.ShardMeta, i int) error {
|
2022-08-24 02:54:01 +00:00
|
|
|
actual := sha256sum(data)
|
|
|
|
if !bytes.Equal(actual, meta.ShardHashes[i]) {
|
|
|
|
return fmt.Errorf("shard hash mismatch")
|
|
|
|
}
|
2022-08-25 03:12:12 +00:00
|
|
|
dataLen := int64(len(data))
|
2022-08-24 02:54:01 +00:00
|
|
|
writeData := data
|
2022-08-25 03:12:12 +00:00
|
|
|
if read.GlobalOffset > meta.Params.Size {
|
2022-08-24 02:54:01 +00:00
|
|
|
writeData = nil
|
2022-08-25 03:12:12 +00:00
|
|
|
} else if read.GlobalOffset+dataLen > meta.Params.Size {
|
|
|
|
writeData = data[0 : read.GlobalOffset-meta.Params.Size]
|
2022-08-24 02:54:01 +00:00
|
|
|
}
|
|
|
|
if writeData != nil {
|
|
|
|
if _, err := file.Write(writeData); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
raw := make([]byte, meta.Params.Stride)
|
|
|
|
rawLen := int32(len(raw))
|
|
|
|
for _, plan := range meta.Params.Plan(0, meta.Params.Size) {
|
|
|
|
if rawLen != plan.Size {
|
|
|
|
raw = make([]byte, plan.Size)
|
|
|
|
rawLen = plan.Size
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can be particularly lazy and ignore Offset since we are reading the full file from the chunks
|
|
|
|
if _, err := io.ReadFull(inputs[plan.Chunk], raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := file.Write(raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|