95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package testutil
|
|
|
|
import "fmt"
|
|
import "slices"
|
|
import "strings"
|
|
|
|
// Snake lets you compare blocks of data where the ordering of certain parts may
|
|
// be swapped every which way. It is designed for comparing the encoding of
|
|
// maps where the ordering of individual elements is inconsistent.
|
|
//
|
|
// The snake is divided into sectors, which hold a number of variations. For a
|
|
// sector to be satisfied by some data, some ordering of it must match the data
|
|
// exactly. for the snake to be satisfied by some data, its sectors must match
|
|
// the data in order, but the internal ordering of each sector doesn't matter.
|
|
type Snake [] [] []byte
|
|
// snake sector variation
|
|
|
|
// S returns a new snake.
|
|
func S(data ...byte) Snake {
|
|
return (Snake { }).Add(data...)
|
|
}
|
|
|
|
// AddVar returns a new snake with the given sector added on to it. Successive
|
|
// calls of this method can be chained together to create a big ass snake.
|
|
func (sn Snake) AddVar(sector ...[]byte) Snake {
|
|
slice := make(Snake, len(sn) + 1)
|
|
copy(slice, sn)
|
|
slice[len(slice) - 1] = sector
|
|
return slice
|
|
}
|
|
|
|
// Add is like AddVar, but adds a sector with only one variation, which means it
|
|
// does not vary, hence why the method is called that.
|
|
func (sn Snake) Add(data ...byte) Snake {
|
|
return sn.AddVar(data)
|
|
}
|
|
|
|
// Check determines if the data satisfies the snake.
|
|
func (sn Snake) Check(data []byte) (ok bool, n int) {
|
|
left := data
|
|
variations := map[int] []byte { }
|
|
for _, sector := range sn {
|
|
clear(variations)
|
|
for key, variation := range sector {
|
|
variations[key] = variation
|
|
}
|
|
for len(variations) > 0 {
|
|
found := false
|
|
for key, variation := range variations {
|
|
if len(left) < len(variation) { continue }
|
|
if !slices.Equal(left[:len(variation)], variation) { continue }
|
|
n += len(variation)
|
|
left = data[n:]
|
|
delete(variations, key)
|
|
found = true
|
|
}
|
|
if !found { return false, n }
|
|
}
|
|
}
|
|
if n < len(data) {
|
|
return false, n
|
|
}
|
|
return true, n
|
|
}
|
|
|
|
func (sn Snake) String() string {
|
|
if len(sn) == 0 || len(sn[0]) == 0 || len(sn[0][0]) == 0 {
|
|
return "EMPTY"
|
|
}
|
|
|
|
out := strings.Builder { }
|
|
for index, sector := range sn {
|
|
if index > 0 { out.WriteString(" : ") }
|
|
out.WriteRune('[')
|
|
for index, variation := range sector {
|
|
if index > 0 { out.WriteString(" / ") }
|
|
for _, byt := range variation {
|
|
fmt.Fprintf(&out, "%02x", byt)
|
|
}
|
|
}
|
|
out.WriteRune(']')
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
// HexBytes formats bytes into a hexadecimal string.
|
|
func HexBytes(data []byte) string {
|
|
if len(data) == 0 { return "EMPTY" }
|
|
out := strings.Builder { }
|
|
for _, byt := range data {
|
|
fmt.Fprintf(&out, "%02x", byt)
|
|
}
|
|
return out.String()
|
|
}
|