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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -294,11 +295,35 @@ type IndexEntry struct {
|
|||||||
Sequence uint64 `json:"sequence"`
|
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
|
// IndexKey is the key of an instance in a declared index: the values of the index's fields, in its order, then the
|
||||||
// ".", then the instance's path with each "/" written "." (`17950.listing.a17`). The owner's watcher and the tool
|
// parts of the instance's path, all joined by ".". Each value and each path part is encoded (keyToken): every byte
|
||||||
// runner's reindex both write it, so a reindex leaves the index as the watcher keeps it.
|
// 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 {
|
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.
|
// 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
|
// 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.
|
// instance and the sequence.
|
||||||
if k := buscalls.IndexKey([]string{"17950", "volvo"}, "listing/a17"); k != "17950.volvo.listing.a17" {
|
for _, c := range []struct {
|
||||||
t.Errorf("an index key is %q", k)
|
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})
|
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "listing/a17", Sequence: 3})
|
||||||
if string(entry) != `{"instance":"listing/a17","sequence":3}` {
|
if string(entry) != `{"instance":"listing/a17","sequence":3}` {
|
||||||
|
|||||||
Reference in New Issue
Block a user