mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 12:57:40 +00:00
Red commit: `46e3b082` ([regression CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40203680455)). Fixes #2266 and #2267. Certain relay activity now requires an eligible path from the observation that supplied it. A representative transmission header, destination attribution or catalogue uniqueness alone cannot supply that evidence. Later valid FLOOD evidence survives observer/path deduplication. The fix preserves valid own packets and excludes failed signatures, planned DIRECT/TRACE paths, ambiguous/retired prefixes and missing or mismatched headers. Firmware reference: meshcore-dev/MeshCore `81dbae59`. - One eligibility byte replaces an unused raw-frame field. StoreObs remains 152 bytes in the protected 160-byte allocation class; no wire frames are retained. Existing dedup maps gain one pointer per slot only at 64+ observations. - Processing is linear in retained paths, bounded by 64 wire path bytes. No new SQL, schema, configuration or maps. - Tests cover five load/ingest paths, late duplicates, analytics/adaptive thresholds, positive controls and concurrent readers. Review correction `4ad6c5ad` preserves lazy decoded caches while retaining escaped/legacy signature rejection. - Full server suite, focused/race checks and frontend checks pass. Browser verified on http://127.0.0.1:18905: both upstream false-positive cases disappear and a genuine later FLOOD is recognized. Independent review findings are addressed. Local preflight checks passed; external home-directory bundle unavailable. [Final CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40205015381) passed on `4ad6c5ad`, including all three browser shards and both architecture builds.
86 lines
3.0 KiB
Go
86 lines
3.0 KiB
Go
package main
|
|
|
|
// obsDedupKey identifies an observation within its transmission: the same
|
|
// observer reporting the same path is a duplicate.
|
|
type obsDedupKey struct{ observerID, pathJSON string }
|
|
|
|
// obsIndexThreshold is the observation count at which a transmission starts
|
|
// keeping maps for its duplicate and distinct-observer checks. Below it the
|
|
// checks scan tx.Observations, which compares string headers and costs
|
|
// nothing to keep; the maps cost a few hundred bytes per transmission, and
|
|
// most transmissions are heard a handful of times (a week of a busy regional
|
|
// network peaked at 40). A miss scans in about 150ns at 40 observations and
|
|
// 240ns at 63; past the threshold a map lookup is about 15ns. The maps' keys
|
|
// share the observations' strings, so building them allocates no key bytes.
|
|
const obsIndexThreshold = 64
|
|
|
|
// hasObservation reports whether tx already holds an observation from
|
|
// observerID with pathJSON.
|
|
func (tx *StoreTx) hasObservation(observerID, pathJSON string) bool {
|
|
return tx.observation(observerID, pathJSON) != nil
|
|
}
|
|
|
|
func (tx *StoreTx) observation(observerID, pathJSON string) *StoreObs {
|
|
if tx.obsKeys != nil {
|
|
return tx.obsKeys[obsDedupKey{observerID, pathJSON}]
|
|
}
|
|
for _, o := range tx.Observations {
|
|
if o.ObserverID == observerID && o.PathJSON == pathJSON {
|
|
return o
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// mergeObservationEvidence preserves an eligible flood when observer/path
|
|
// dedup merges frames with different headers. The validated path fixes its
|
|
// width, so known evidence can only upgrade unknown/ineligible evidence.
|
|
// Caller owns tx privately during load or holds s.mu for writing.
|
|
func (tx *StoreTx) mergeObservationEvidence(observerID, pathJSON string, n uint8) *StoreObs {
|
|
obs := tx.observation(observerID, pathJSON)
|
|
if obs != nil && n != 0 {
|
|
obs.relayHopHexLen = n
|
|
}
|
|
return obs
|
|
}
|
|
|
|
// hasObserver reports whether any observation on tx came from observerID.
|
|
func (tx *StoreTx) hasObserver(observerID string) bool {
|
|
if tx.observerSet != nil {
|
|
_, ok := tx.observerSet[observerID]
|
|
return ok
|
|
}
|
|
for _, o := range tx.Observations {
|
|
if o.ObserverID == observerID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// addObservation appends obs to tx and keeps UniqueObserverCount and
|
|
// ObservationCount in step. Callers check hasObservation first.
|
|
func (tx *StoreTx) addObservation(obs *StoreObs) {
|
|
if obs.ObserverID != "" && !tx.hasObserver(obs.ObserverID) {
|
|
tx.UniqueObserverCount++
|
|
}
|
|
tx.Observations = append(tx.Observations, obs)
|
|
tx.ObservationCount++
|
|
switch {
|
|
case tx.obsKeys != nil:
|
|
tx.obsKeys[obsDedupKey{obs.ObserverID, obs.PathJSON}] = obs
|
|
if obs.ObserverID != "" {
|
|
tx.observerSet[obs.ObserverID] = struct{}{}
|
|
}
|
|
case len(tx.Observations) >= obsIndexThreshold:
|
|
tx.obsKeys = make(map[obsDedupKey]*StoreObs, 2*len(tx.Observations))
|
|
tx.observerSet = make(map[string]struct{}, len(tx.Observations))
|
|
for _, o := range tx.Observations {
|
|
tx.obsKeys[obsDedupKey{o.ObserverID, o.PathJSON}] = o
|
|
if o.ObserverID != "" {
|
|
tx.observerSet[o.ObserverID] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|