Encode each part of an index key, so no two keys meet and every part is a bus token (issue 1966 S4)
mesh/delivery merged: source.main moved to novox/mesh-sdk@2341e8e98377
mesh/delivery-group group feat/bus-state merged: every member is merged
mesh/check pass f8bd12e4b7b1 on 7c015b652d36 | unit 6460 run, 262 kept, 27 skipped | integration 6639 run, 264 kept, 35 skipped | over bound: unit, in…
mesh/delivery merged: source.main moved to novox/mesh-sdk@2341e8e98377
mesh/delivery-group group feat/bus-state merged: every member is merged
mesh/check pass f8bd12e4b7b1 on 7c015b652d36 | unit 6460 run, 262 kept, 27 skipped | integration 6639 run, 264 kept, 35 skipped | over bound: unit, in…
A raw value holding "." collided with a longer path, and a space is refused by the bus: every byte outside [A-Za-z0-9_-] of a field value or an instance path part is written =XX (review round 1 of issue 1966 B1).
This commit is contained in:
+29
-4
@@ -10,6 +10,7 @@ package buscalls
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -294,11 +295,35 @@ type IndexEntry struct {
|
||||
Sequence uint64 `json:"sequence"`
|
||||
}
|
||||
|
||||
// IndexKey is the key of an instance in a declared index: the values of the index's fields, in its order, joined by
|
||||
// ".", then the instance's path with each "/" written "." (`17950.listing.a17`). The owner's watcher and the tool
|
||||
// runner's reindex both write it, so a reindex leaves the index as the watcher keeps it.
|
||||
// IndexKey is the key of an instance in a declared index: the values of the index's fields, in its order, then the
|
||||
// parts of the instance's path, all joined by ".". Each value and each path part is encoded (keyToken): every byte
|
||||
// outside [A-Za-z0-9_-] is written "=" and two upper-case hex digits ("." is "=2E", " " is "=20", "=" is "=3D"), so two
|
||||
// different keys never meet and every part is a token the bus takes. `17950.listing.a17` is the key of price 17950
|
||||
// and instance `listing/a17`. The owner's watcher and the tool runner's reindex both write it, so a reindex leaves the
|
||||
// index as the watcher keeps it.
|
||||
func IndexKey(fields []string, instance string) string {
|
||||
return strings.Join(append(append([]string{}, fields...), strings.ReplaceAll(instance, "/", ".")), ".")
|
||||
parts := make([]string, 0, len(fields)+2)
|
||||
for _, f := range fields {
|
||||
parts = append(parts, keyToken(f))
|
||||
}
|
||||
for _, p := range strings.Split(instance, "/") {
|
||||
parts = append(parts, keyToken(p))
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
// keyToken is v with every byte outside [A-Za-z0-9_-] written "=" and two upper-case hex digits (IndexKey).
|
||||
func keyToken(v string) string {
|
||||
var b strings.Builder
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' || c == '-' {
|
||||
b.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, "=%02X", c)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// QueryAnswer is query's answer.
|
||||
|
||||
@@ -112,8 +112,30 @@ func TestMeshSDKHoldsOnlyWhatEveryModuleShares(t *testing.T) {
|
||||
}
|
||||
// 1966/AC5: an index's key is its fields' values, then the instance's path, joined by "."; its entry names the
|
||||
// instance and the sequence.
|
||||
if k := buscalls.IndexKey([]string{"17950", "volvo"}, "listing/a17"); k != "17950.volvo.listing.a17" {
|
||||
t.Errorf("an index key is %q", k)
|
||||
for _, c := range []struct {
|
||||
fields []string
|
||||
instance string
|
||||
want string
|
||||
}{
|
||||
{[]string{"17950", "volvo"}, "listing/a17", "17950.volvo.listing.a17"},
|
||||
// A value holding "." and one that does not meet in no key.
|
||||
{[]string{"a.b"}, "c", "a=2Eb.c"},
|
||||
{[]string{"a"}, "b/c", "a.b.c"},
|
||||
// A title with spaces is a token the bus takes; "=" itself is encoded.
|
||||
{[]string{"How it is checked", "x=y"}, "decision/0290", "How=20it=20is=20checked.x=3Dy.decision.0290"},
|
||||
} {
|
||||
k := buscalls.IndexKey(c.fields, c.instance)
|
||||
if k != c.want {
|
||||
t.Errorf("the index key of %q and %q is %q, want %q", c.fields, c.instance, k, c.want)
|
||||
}
|
||||
for _, token := range strings.Split(k, ".") {
|
||||
if token == "" || strings.ContainsAny(token, " *>\t") {
|
||||
t.Errorf("the index key %q holds a token the bus refuses: %q", k, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
if buscalls.IndexKey([]string{"a.b"}, "c") == buscalls.IndexKey([]string{"a"}, "b/c") {
|
||||
t.Error("two different index keys meet")
|
||||
}
|
||||
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "listing/a17", Sequence: 3})
|
||||
if string(entry) != `{"instance":"listing/a17","sequence":3}` {
|
||||
|
||||
Reference in New Issue
Block a user