mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
// Cross-implementation msgpack compatibility test for the blackhole list
|
|
// format. Set RUN_PY_INTEROP=1 to enable (optional interpreter path via env).
|
|
|
|
package blackhole
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func toBase64(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
|
|
func fromBase64(s string) ([]byte, error) { return base64.StdEncoding.DecodeString(s) }
|
|
|
|
func pythonOrSkip(t *testing.T) string {
|
|
t.Helper()
|
|
if os.Getenv("RUN_PY_INTEROP") == "" {
|
|
t.Skip("set RUN_PY_INTEROP=1 to enable python blackhole interop test")
|
|
}
|
|
exe := os.Getenv("PYTHON_INTEROP")
|
|
if exe == "" {
|
|
exe = "python3"
|
|
}
|
|
if _, err := exec.LookPath(exe); err != nil {
|
|
t.Skipf("python interpreter %q not found: %v", exe, err)
|
|
}
|
|
return exe
|
|
}
|
|
|
|
func TestBlackholeMsgpackRoundtripWithPython(t *testing.T) {
|
|
pyExe := pythonOrSkip(t)
|
|
dir := t.TempDir()
|
|
local := bytes.Repeat([]byte{0xab}, HashLen)
|
|
SetLocalIdentityHash(local)
|
|
tab := New(dir)
|
|
id1 := bytes.Repeat([]byte{0x21}, HashLen)
|
|
id2 := bytes.Repeat([]byte{0x22}, HashLen)
|
|
if _, err := tab.Add(id1, 0, ""); err != nil {
|
|
t.Fatalf("add1: %v", err)
|
|
}
|
|
if _, err := tab.Add(id2, 1700000000.5, "abuse"); err != nil {
|
|
t.Fatalf("add2: %v", err)
|
|
}
|
|
packed, err := os.ReadFile(filepath.Join(dir, "local"))
|
|
if err != nil {
|
|
t.Fatalf("read local: %v", err)
|
|
}
|
|
|
|
script := `import sys, base64, json
|
|
from RNS.vendor import umsgpack
|
|
raw = base64.b64decode(sys.argv[1])
|
|
obj = umsgpack.unpackb(raw)
|
|
out = {}
|
|
for k, v in obj.items():
|
|
e = {}
|
|
if v.get("until") is not None: e["until"] = v["until"]
|
|
if v.get("reason") is not None: e["reason"] = v["reason"]
|
|
e["source"] = v.get("source").hex() if v.get("source") else None
|
|
out[k.hex()] = e
|
|
print(json.dumps(out, sort_keys=True))
|
|
`
|
|
enc := []byte(toBase64(packed))
|
|
cmd := exec.Command(pyExe, "-c", script, string(enc))
|
|
cmd.Stderr = os.Stderr
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
t.Skipf("python decode failed (umsgpack missing?): %v", err)
|
|
}
|
|
got := strings.TrimSpace(string(out))
|
|
if !strings.Contains(got, hex.EncodeToString(id1)) || !strings.Contains(got, hex.EncodeToString(id2)) {
|
|
t.Fatalf("python could not decode our blackhole entries: %s", got)
|
|
}
|
|
if !strings.Contains(got, `"abuse"`) {
|
|
t.Fatalf("reason missing from python decode: %s", got)
|
|
}
|
|
if !strings.Contains(got, `"until": 1700000000.5`) {
|
|
t.Fatalf("until missing from python decode: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestPythonEncodedBlackholeIsDecoded(t *testing.T) {
|
|
pyExe := pythonOrSkip(t)
|
|
id := bytes.Repeat([]byte{0x33}, HashLen)
|
|
src := bytes.Repeat([]byte{0xcd}, HashLen)
|
|
script := `import sys, base64
|
|
from RNS.vendor import umsgpack
|
|
ident = bytes.fromhex(sys.argv[1])
|
|
src = bytes.fromhex(sys.argv[2])
|
|
data = {ident: {"source": src, "until": 1700000050.25, "reason": "py-side"}}
|
|
sys.stdout.write(base64.b64encode(umsgpack.packb(data)).decode())
|
|
`
|
|
cmd := exec.Command(pyExe, "-c", script, hex.EncodeToString(id), hex.EncodeToString(src))
|
|
cmd.Stderr = os.Stderr
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
t.Skipf("python encode failed (umsgpack missing?): %v", err)
|
|
}
|
|
raw, err := fromBase64(strings.TrimSpace(string(out)))
|
|
if err != nil {
|
|
t.Fatalf("base64: %v", err)
|
|
}
|
|
decoded, err := DecodeBlackholeMap(raw)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
got, ok := decoded[string(id)]
|
|
if !ok {
|
|
t.Fatalf("python entry not found in decoded map")
|
|
}
|
|
if got.Reason != "py-side" {
|
|
t.Fatalf("reason mismatch: %q", got.Reason)
|
|
}
|
|
if got.Until != 1700000050.25 {
|
|
t.Fatalf("until mismatch: %v", got.Until)
|
|
}
|
|
if !bytes.Equal(got.Source, src) {
|
|
t.Fatalf("source mismatch: %x vs %x", got.Source, src)
|
|
}
|
|
}
|