mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-12 07:57:42 +00:00
Observer `id` and `iata` come from the MQTT topic; `origin` (name), `model`, `firmware`, `client_version` and `radio` come from the status JSON. Any publisher controls them and nothing bounded their length, so one message could store a 64 KB observer id or name. Each new id is also a new `observers` row, and that table is joined by most packet queries. **Fix:** a small `clampObserverField` helper strips control characters and truncates: ids to 128 runes, text fields to 128, IATA to 16. Applied on the status path, the packet path and in `extractObserverMeta`. Values are truncated rather than rejected, so a legitimate observer with a long name still appears. **Tests:** `observer_fields_test.go` — short values untouched, long values cut at 128 runes (not bytes, so multi-byte names are not split), control characters removed, `extractObserverMeta` caps all string fields. Full ingestor suite passes. Running in production on our instance since 2026-10-07. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
48 lines
1.5 KiB
Go
48 lines
1.5 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
func TestClampObserverField(t *testing.T) {
|
|
if got := clampObserverField("Observer One", 128); got != "Observer One" {
|
|
t.Fatalf("short value changed: %q", got)
|
|
}
|
|
long := strings.Repeat("x", 5000)
|
|
if got := clampObserverField(long, 128); utf8.RuneCountInString(got) != 128 {
|
|
t.Fatalf("long value not truncated to 128 runes: %d", utf8.RuneCountInString(got))
|
|
}
|
|
// Truncation counts runes, not bytes, so a multi-byte name is not cut mid-character.
|
|
multi := strings.Repeat("é", 200)
|
|
got := clampObserverField(multi, 128)
|
|
if utf8.RuneCountInString(got) != 128 || !utf8.ValidString(got) {
|
|
t.Fatalf("multi-byte truncation broke the string: %d runes valid=%v", utf8.RuneCountInString(got), utf8.ValidString(got))
|
|
}
|
|
if got := clampObserverField("a\x00b\x1fc", 128); got != "abc" {
|
|
t.Fatalf("control characters not stripped: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestExtractObserverMetaCapsStrings(t *testing.T) {
|
|
long := strings.Repeat("m", 1000)
|
|
meta := extractObserverMeta(map[string]interface{}{
|
|
"model": long,
|
|
"firmware": long,
|
|
"client_version": long,
|
|
"radio": long,
|
|
})
|
|
if meta == nil {
|
|
t.Fatal("meta nil")
|
|
}
|
|
for name, v := range map[string]*string{"model": meta.Model, "firmware": meta.Firmware, "client_version": meta.ClientVersion, "radio": meta.Radio} {
|
|
if v == nil {
|
|
t.Fatalf("%s not set", name)
|
|
}
|
|
if n := utf8.RuneCountInString(*v); n != maxObserverTextLen {
|
|
t.Fatalf("%s not capped: %d runes", name, n)
|
|
}
|
|
}
|
|
}
|