Add fuzz testing for Section encoding/decoding with multiple edge cases
This commit is contained in:
+21
-42
@@ -1,54 +1,33 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math"
|
||||
import "io"
|
||||
|
||||
"github.com/admin-else/strom/data"
|
||||
"github.com/admin-else/strom/util"
|
||||
)
|
||||
|
||||
type BlockState struct {
|
||||
Id string
|
||||
Properties map[string]string
|
||||
type Chunk struct {
|
||||
Sections []Section
|
||||
version string
|
||||
}
|
||||
|
||||
const (
|
||||
ChunkWidth = 16
|
||||
ChunkColumns = ChunkWidth * ChunkWidth
|
||||
BlocksPerChunkSection = ChunkWidth * ChunkWidth * ChunkWidth
|
||||
ChunkBiomesWidth = 4
|
||||
BiomesPerChunkSection = ChunkBiomesWidth * ChunkBiomesWidth * ChunkBiomesWidth
|
||||
)
|
||||
func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (err error) {
|
||||
c := &Chunk{}
|
||||
c.version = version
|
||||
nSections := worldHeight / ChunkWidth
|
||||
|
||||
type Section struct {
|
||||
Blocks *Storage[*BlockState]
|
||||
Biomes *Storage[string]
|
||||
}
|
||||
|
||||
func PossibleBpesBlocks(version string) (ret []uint8) {
|
||||
maxBpe := util.BpeByNum(float64(len(data.BlocksForVersion(version))))
|
||||
ret = []uint8{0, 4, 5, 6, 7, 8, maxBpe}
|
||||
return
|
||||
}
|
||||
|
||||
func PossibleBpesBiomes(version string) (ret []uint8) {
|
||||
maxBpe := uint8(math.Ceil(math.Log2(float64(len(data.BiomesForVersion(version))))))
|
||||
ret = []uint8{0, 1, 2, 3, maxBpe}
|
||||
c.Sections = make([]Section, nSections)
|
||||
for i := range c.Sections {
|
||||
c.Sections[i], err = SectionDecodePacket(r, version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SectionFromPacket(r io.Reader, version string) (s *Section, err error) {
|
||||
s = &Section{}
|
||||
s.Blocks = NewStorage[*BlockState](BlocksPerChunkSection, PossibleBpesBlocks(version))
|
||||
s.Biomes = NewStorage[string](BiomesPerChunkSection, PossibleBpesBiomes(version))
|
||||
|
||||
var bitsPerEntryBlocks uint8
|
||||
err = binary.Read(r, binary.BigEndian, &bitsPerEntryBlocks)
|
||||
if err != nil {
|
||||
return
|
||||
func (c *Chunk) WriteChunkData(w io.Writer) (err error) {
|
||||
for _, s := range c.Sections {
|
||||
err = SectionEncodePacket(w, s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/admin-else/strom/data"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/util"
|
||||
)
|
||||
|
||||
type BlockState struct {
|
||||
Id string
|
||||
Properties map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
ChunkWidth = 16
|
||||
ChunkColumns = ChunkWidth * ChunkWidth
|
||||
BlocksPerChunkSection = ChunkWidth * ChunkWidth * ChunkWidth
|
||||
ChunkBiomesWidth = 4
|
||||
BiomesPerChunkSection = ChunkBiomesWidth * ChunkBiomesWidth * ChunkBiomesWidth
|
||||
)
|
||||
|
||||
type Section struct {
|
||||
BlockCount int16
|
||||
Blocks, Biomes *Storage
|
||||
}
|
||||
|
||||
func MakeBiomeFormat(version string) StorageFormat {
|
||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data.BiomesForVersion(version))))))
|
||||
return StorageFormat{
|
||||
AvailableBpes: []uint8{0, 1, 2, 3, directBpe},
|
||||
BiggestDirect: true,
|
||||
Len: BiomesPerChunkSection,
|
||||
}
|
||||
}
|
||||
|
||||
func MakeBlockFormat(version string) StorageFormat {
|
||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data.BlocksForVersion(version))))))
|
||||
return StorageFormat{
|
||||
AvailableBpes: []uint8{0, 1, 2, 3, directBpe},
|
||||
BiggestDirect: true,
|
||||
Len: BiomesPerChunkSection,
|
||||
}
|
||||
}
|
||||
|
||||
var BadPaletteLenErr = errors.New("bad palette length")
|
||||
|
||||
func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err error) {
|
||||
var bpe uint8
|
||||
err = binary.Read(r, binary.BigEndian, &bpe)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var pallette []int32
|
||||
if bpe == 0 {
|
||||
var v int32
|
||||
v, err = proto_base.DecodeVarInt(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pallette = []int32{v}
|
||||
} else if !util.IsLastElement(format.AvailableBpes, bpe) {
|
||||
var paletteLen int32
|
||||
paletteLen, err = proto_base.DecodeVarInt(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if paletteLen < 0 {
|
||||
err = BadPaletteLenErr
|
||||
return
|
||||
}
|
||||
for range pallette {
|
||||
var v int32
|
||||
v, err = proto_base.DecodeVarInt(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pallette = append(pallette, v)
|
||||
}
|
||||
}
|
||||
return format.ImportFromReader(r, bpe, pallette)
|
||||
}
|
||||
|
||||
func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
||||
err = binary.Write(w, binary.BigEndian, s.bpe)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if s.bpe == 0 {
|
||||
err = proto_base.EncodeVarInt(w, s.palette[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !util.IsLastElement(s.format.AvailableBpes, s.bpe) {
|
||||
err = proto_base.EncodeVarInt(w, int32(len(s.palette)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range s.palette {
|
||||
err = proto_base.EncodeVarInt(w, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return binary.Write(w, binary.BigEndian, s.data)
|
||||
}
|
||||
|
||||
func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
||||
s = Section{}
|
||||
err = binary.Read(r, binary.BigEndian, &s.BlockCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Blocks, err = ReadSectionStorage(r, MakeBlockFormat(version))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Biomes, err = ReadSectionStorage(r, MakeBiomeFormat(version))
|
||||
return
|
||||
}
|
||||
|
||||
func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
||||
err = binary.Write(w, binary.BigEndian, s.BlockCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = WriteSectionStorage(w, s.Blocks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = WriteSectionStorage(w, s.Biomes)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package level_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/admin-else/strom/level"
|
||||
)
|
||||
|
||||
func FuzzSectionDeEnCodePacket(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
r := bytes.NewReader(raw)
|
||||
s1, err := level.SectionDecodePacket(r, TestVersion)
|
||||
if err != nil {
|
||||
t.Skipf("decode failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err = level.SectionEncodePacket(buf, s1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
s2, err := level.SectionDecodePacket(buf, TestVersion)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if s1.BlockCount != s2.BlockCount {
|
||||
t.Error("BlockCount mismatch")
|
||||
}
|
||||
if level.CompareStorage(s1.Blocks, s2.Blocks) {
|
||||
t.Error("Blocks mismatch")
|
||||
}
|
||||
if level.CompareStorage(s1.Biomes, s2.Biomes) {
|
||||
t.Error("Biomes mismatch")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
@@ -53,6 +55,28 @@ func (r StorageFormat) ImportDataDirect(data []uint64) (s *Storage, err error) {
|
||||
return r.Import(data, r.AvailableBpes[0], nil)
|
||||
}
|
||||
|
||||
func (r StorageFormat) ImportFromReader(reader io.Reader, bpe uint8, palette []int32) (s *Storage, err error) {
|
||||
if !slices.Contains(r.AvailableBpes, bpe) {
|
||||
err = BpeNotAvailableErr
|
||||
return
|
||||
}
|
||||
var longs []uint64
|
||||
if bpe == 0 {
|
||||
return r.Import(longs, bpe, palette)
|
||||
}
|
||||
elementsPerLong := int32(64 / int(bpe))
|
||||
numberOfLongs := int(math.Ceil(float64(r.Len) / float64(elementsPerLong)))
|
||||
for range numberOfLongs {
|
||||
var long uint64
|
||||
err = binary.Read(reader, binary.BigEndian, &long) // surely this works?
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
longs = append(longs, long)
|
||||
}
|
||||
return r.Import(longs, bpe, palette)
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
format StorageFormat
|
||||
data []uint64
|
||||
@@ -81,14 +105,14 @@ func (r *Storage) resize(bpe uint8) {
|
||||
r.maxAtCurrentBpe = 1
|
||||
return
|
||||
}
|
||||
numberOfElements := int32(64 / int(bpe))
|
||||
numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(numberOfElements)))
|
||||
elementsPerLong := int32(64 / int(bpe))
|
||||
numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(elementsPerLong)))
|
||||
newData := make([]uint64, numberOfLongs)
|
||||
mask := (uint64(1) << uint32(bpe)) - 1
|
||||
maxAtCurrentBpe := int32(1 << bpe)
|
||||
if r.bpe == 0 {
|
||||
r.bpe = bpe
|
||||
r.elementsPerLong = numberOfElements
|
||||
r.elementsPerLong = elementsPerLong
|
||||
r.data = newData
|
||||
r.mask = mask
|
||||
r.maxAtCurrentBpe = maxAtCurrentBpe
|
||||
@@ -102,13 +126,13 @@ func (r *Storage) resize(bpe uint8) {
|
||||
} else {
|
||||
v = int32((r.data[i/r.elementsPerLong] >> ((i % r.elementsPerLong) * int32(r.bpe))) & r.mask)
|
||||
}
|
||||
newData[i/int32(numberOfElements)] |= uint64(v) << ((i % int32(numberOfElements)) * int32(bpe))
|
||||
newData[i/int32(elementsPerLong)] |= uint64(v) << ((i % int32(elementsPerLong)) * int32(bpe))
|
||||
}
|
||||
if resizeDirect {
|
||||
r.palette = nil
|
||||
}
|
||||
r.bpe = bpe
|
||||
r.elementsPerLong = numberOfElements
|
||||
r.elementsPerLong = elementsPerLong
|
||||
r.data = newData
|
||||
r.mask = mask
|
||||
r.maxAtCurrentBpe = maxAtCurrentBpe
|
||||
@@ -196,3 +220,21 @@ func (r *Storage) appendPalette(v int32) (i int, err error) {
|
||||
i = len(r.palette) - 1
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Storage) Export() (data []uint64, bpe uint8, palette []int32, format StorageFormat) {
|
||||
return r.data, r.bpe, r.palette, r.format
|
||||
}
|
||||
|
||||
func CompareStorage(a, b *Storage) bool {
|
||||
if a.format.Len != b.format.Len {
|
||||
return false
|
||||
}
|
||||
for i := range a.format.Len {
|
||||
av, _ := a.Get(i)
|
||||
bv, _ := b.Get(i)
|
||||
if av == bv {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("000\xcf\xff\xff\xe88")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("00A\x00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("00\x000")
|
||||
Reference in New Issue
Block a user