// Package 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. package snake import "fmt" import "strings" import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil" var _ Node = Order { } var _ Node = Snake { } var _ Node = Leaf { } // Check checks the data against the specified node. If the data doesn't satisfy // the node, or the comparison succeded but didn't consume all the data, this // function returns false, and the index of the byte where the inequality is. func Check(node Node, data []byte) (ok bool, n int) { ok, n = node.Check(data) if !ok { return false, n } if n != len(data) { return false, n } return true, n } // O returns a new order given a vararg node slice. func O(nodes ...Node) Order { return Order(nodes) } // S returns a new snake given a vararg node slice. func S(nodes ...Node) Snake { return Snake(nodes) } // L returns a new leaf given a vararg byte slice. func L(data ...byte) Leaf { return Leaf([]byte(data)) } // Order is satisfied when the data satisfies each of its nodes in the order // that they are specified in the slice. type Order []Node // Check determines if the data satisfies the Order. func (this Order) Check(data []byte) (ok bool, n int) { left := data for _, node := range this { ok, nn := node.Check(left) n += nn; if !ok { return false, n } left = left[nn:] } return true, n } // Flatten returns the Order flattened to a byte array. The result of this // function always satisfies the Order. func (this Order) Flatten() []byte { flat := []byte { } for _, node := range this { flat = append(flat, node.Flatten()...) } return flat } func (this Order) String() string { out := strings.Builder { } for index, node := range this { if index > 0 { fmt.Fprint(&out, " :") } fmt.Fprintf(&out, " %v", node) } return out.String() } // Add returns a new order with the given nodes appended to it. func (this Order) Add(nodes ...Node) Order { newOrder := make(Order, len(this) + len(nodes)) copy(newOrder, this) copy(newOrder[len(this):], Order(nodes)) return newOrder } // AddO returns a new order with the given order appended to it. func (this Order) AddO(nodes ...Node) Order { return this.Add(O(nodes...)) } // AddS returns a new order with the given snake appended to it. func (this Order) AddS(nodes ...Node) Order { return this.Add(S(nodes...)) } // AddL returns a new order with the given leaf appended to it. func (this Order) AddL(data ...byte) Order { return this.Add(L(data...)) } // Snake is satisfied when the data satisfies each of its nodes in no particular // order. type Snake []Node // Check determines if the data satisfies the snake. func (this Snake) Check(data []byte) (ok bool, n int) { fmt.Println("CHECKING SNAKE") left := data nodes := map[int] Node { } for key, node := range this { nodes[key] = node } for len(nodes) > 0 { found := false for key, node := range nodes { fmt.Println(left, key, node) ok, nn := node.Check(left) fmt.Println(ok, nn) if !ok { continue } n += nn left = data[n:] delete(nodes, key) found = true break } if !found { return false, n } } return true, n } // Flatten returns the snake flattened to a byte array. The result of this // function always satisfies the snake. func (this Snake) Flatten() []byte { flat := []byte { } for _, node := range this { flat = append(flat, node.Flatten()...) } return flat } func (this Snake) String() string { out := strings.Builder { } out.WriteString("[") for index, node := range this { if index > 0 { fmt.Fprint(&out, " /") } fmt.Fprintf(&out, " %v", node) } out.WriteString(" ]") return out.String() } // Add returns a new snake with the given nodes appended to it. func (this Snake) Add(nodes ...Node) Snake { newSnake := make(Snake, len(this) + len(nodes)) copy(newSnake, this) copy(newSnake[len(this):], Snake(nodes)) return newSnake } // AddO returns a new snake with the given order appended to it. func (this Snake) AddO(nodes ...Node) Snake { return this.Add(O(nodes...)) } // AddS returns a new snake with the given snake appended to it. func (this Snake) AddS(nodes ...Node) Snake { return this.Add(S(nodes...)) } // AddL returns a new snake with the given leaf appended to it. func (this Snake) AddL(data ... byte) Snake { return this.Add(L(data...)) } // Leaf is satisfied when the data matches it exactly. type Leaf []byte // Check determines if the data is equal to the leaf. func (this Leaf) Check(data []byte) (ok bool, n int) { if len(data) < len(this) { return false, len(data) } for index, byt := range this { if byt != data[index] { return false, index } } return true, len(this) } // This one's easy. func (this Leaf) Flatten() []byte { return []byte(this) } func (this Leaf) String() string { return tu.HexBytes([]byte(this)) } // Node represents a snake node. type Node interface { // Check determines if the data satisfies the node. If satisfied, the function // will return true, and the index at which it stopped. If not, the // function will return false, and the index of the first byte that // didn't match. As long as the start of the data satisfies the node, // whatever comes after it doesn't matter. Check(data []byte) (ok bool, n int) // Flatten returns the node flattened to a byte array. The result of // this function always satisfies the node. Flatten() []byte }