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