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...) }