162 lines
4.2 KiB
Go
162 lines
4.2 KiB
Go
package testutil
|
|
|
|
import "fmt"
|
|
import "slices"
|
|
import "strings"
|
|
import "reflect"
|
|
|
|
// 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()
|
|
}
|
|
|
|
// Describe returns a string representing the type and data of the given value.
|
|
func Describe(value any) string {
|
|
desc := describer { }
|
|
desc.describe(reflect.ValueOf(value))
|
|
return desc.String()
|
|
}
|
|
|
|
type describer struct {
|
|
strings.Builder
|
|
indent int
|
|
}
|
|
|
|
func (this *describer) describe(value reflect.Value) {
|
|
value = reflect.ValueOf(value.Interface())
|
|
switch value.Kind() {
|
|
case reflect.Array, reflect.Slice:
|
|
this.printf("[\n")
|
|
this.indent += 1
|
|
for index := 0; index < value.Len(); index ++ {
|
|
this.iprintf("")
|
|
this.describe(value.Index(index))
|
|
this.iprintf("\n")
|
|
}
|
|
this.indent -= 1
|
|
this.iprintf("]")
|
|
case reflect.Struct:
|
|
this.printf("struct {\n")
|
|
this.indent += 1
|
|
typ := value.Type()
|
|
for index := range typ.NumField() {
|
|
indexBuffer := [1]int { index }
|
|
this.iprintf("%s: ", typ.Field(index).Name)
|
|
this.describe(value.FieldByIndex(indexBuffer[:]))
|
|
this.iprintf("\n")
|
|
}
|
|
this.indent -= 1
|
|
this.iprintf("}\n")
|
|
case reflect.Map:
|
|
this.printf("map {\n")
|
|
this.indent += 1
|
|
iter := value.MapRange()
|
|
for iter.Next() {
|
|
this.iprintf("")
|
|
this.describe(iter.Key())
|
|
this.printf(": ")
|
|
this.describe(iter.Value())
|
|
this.iprintf("\n")
|
|
}
|
|
this.indent -= 1
|
|
this.iprintf("}\n")
|
|
case reflect.Pointer:
|
|
this.printf("& ")
|
|
this.describe(value.Elem())
|
|
default:
|
|
this.printf("<%v %v>", value.Type(), value.Interface())
|
|
}
|
|
}
|
|
|
|
func (this *describer) printf(format string, v ...any) {
|
|
fmt.Fprintf(this, format, v...)
|
|
}
|
|
|
|
func (this *describer) iprintf(format string, v ...any) {
|
|
fmt.Fprintf(this, strings.Repeat("\t", this.indent) + format, v...)
|
|
}
|