From cb2374f03373d3a7298d5209d9aeedccd8d4ce4c Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 21 Sep 2026 16:12:41 +0200 Subject: [PATCH] Stop decoding unused Community and Raw fields of RIS messages (closes #9) Community and Raw are decoded from every live message and read by no handler; Raw is the hex of the whole BGP message. Each parsed message sits in up to four handler queues, so retaining them is a large share of queue memory. Tagging both json:"-" keeps them out of the decoded message while the fields handlers use still decode. A decode test over docs/message-examples.json confirms both stay empty and the used fields (Path, Announcements) still populate. Model: opus-4-8 --- internal/ristypes/ris.go | 40 +++++++++++---------- internal/ristypes/ris_test.go | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 internal/ristypes/ris_test.go diff --git a/internal/ristypes/ris.go b/internal/ristypes/ris.go index ba36b91..a6831a4 100644 --- a/internal/ristypes/ris.go +++ b/internal/ristypes/ris.go @@ -63,24 +63,28 @@ type RISLiveMessage struct { // the actual BGP update data including AS path, communities, announcements, // and withdrawals. type RISMessage struct { - Type string `json:"type"` - Timestamp float64 `json:"timestamp"` - ParsedTimestamp time.Time `json:"-"` // Parsed from Timestamp field - Peer string `json:"peer"` - PeerASN string `json:"peer_asn"` - ID string `json:"id"` - Host string `json:"host"` - RRC string `json:"rrc,omitempty"` - MrtTime float64 `json:"mrt_time,omitempty"` - SocketTime float64 `json:"socket_time,omitempty"` - Path ASPath `json:"path,omitempty"` - Community [][]int `json:"community,omitempty"` - Origin string `json:"origin,omitempty"` - MED *int `json:"med,omitempty"` - LocalPref *int `json:"local_pref,omitempty"` - Announcements []RISAnnouncement `json:"announcements,omitempty"` - Withdrawals []string `json:"withdrawals,omitempty"` - Raw string `json:"raw,omitempty"` + Type string `json:"type"` + Timestamp float64 `json:"timestamp"` + ParsedTimestamp time.Time `json:"-"` // Parsed from Timestamp field + Peer string `json:"peer"` + PeerASN string `json:"peer_asn"` + ID string `json:"id"` + Host string `json:"host"` + RRC string `json:"rrc,omitempty"` + MrtTime float64 `json:"mrt_time,omitempty"` + SocketTime float64 `json:"socket_time,omitempty"` + Path ASPath `json:"path,omitempty"` + // Community and Raw are present in the feed but read by no handler. + // They are the largest fields on a message that lives in up to four + // handler queues, so json:"-" keeps them out of the decoded message + // to save queue memory. Do not decode them without a consumer. + Community [][]int `json:"-"` + Origin string `json:"origin,omitempty"` + MED *int `json:"med,omitempty"` + LocalPref *int `json:"local_pref,omitempty"` + Announcements []RISAnnouncement `json:"announcements,omitempty"` + Withdrawals []string `json:"withdrawals,omitempty"` + Raw string `json:"-"` } // RISAnnouncement represents a BGP route announcement within a RIS message. diff --git a/internal/ristypes/ris_test.go b/internal/ristypes/ris_test.go new file mode 100644 index 0000000..6a3b7b0 --- /dev/null +++ b/internal/ristypes/ris_test.go @@ -0,0 +1,67 @@ +package ristypes + +import ( + "bufio" + "encoding/json" + "os" + "testing" +) + +// messageExamplesPath is the captured RIS Live feed used as decode fixtures, +// one JSON message per line. +const messageExamplesPath = "../../docs/message-examples.json" + +// TestDecodeDropsCommunityAndRaw decodes every captured message the way the +// streamer does and checks that the fields no handler reads (Community, Raw) +// stay empty while the fields handlers use (Path, Announcements) still decode. +func TestDecodeDropsCommunityAndRaw(t *testing.T) { + f, err := os.Open(messageExamplesPath) + if err != nil { + t.Fatalf("open fixtures: %v", err) + } + defer f.Close() + + var messages, withPath, withAnnouncements int + + scanner := bufio.NewScanner(f) + scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) + for scanner.Scan() { + line := scanner.Bytes() + if len(line) == 0 { + continue + } + + var wrapper RISLiveMessage + if err := json.Unmarshal(line, &wrapper); err != nil { + t.Fatalf("unmarshal message %d: %v", messages+1, err) + } + messages++ + + msg := wrapper.Data + if msg.Community != nil { + t.Errorf("message %d: Community decoded, want empty: %v", messages, msg.Community) + } + if msg.Raw != "" { + t.Errorf("message %d: Raw decoded, want empty", messages) + } + + if len(msg.Path) > 0 { + withPath++ + } + if len(msg.Announcements) > 0 { + withAnnouncements++ + } + } + if err := scanner.Err(); err != nil { + t.Fatalf("scan fixtures: %v", err) + } + + if messages == 0 { + t.Fatal("no messages decoded from fixtures") + } + // The fixtures include announcement messages; a used field must still decode, + // otherwise an empty Community/Raw would prove nothing. + if withPath == 0 || withAnnouncements == 0 { + t.Fatalf("used fields did not decode: withPath=%d withAnnouncements=%d", withPath, withAnnouncements) + } +}