mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
184 lines
12 KiB
Go
184 lines
12 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package zenfix
|
|
|
|
// AllRules is the single source of truth for zen checker metadata.
|
|
// To add a rule: define an ID constant, append here, wire a checker.
|
|
var AllRules = []Rule{
|
|
{
|
|
ID: RuleRequestPathLoop, Severity: SeverityError,
|
|
Title: "path request inside a loop",
|
|
Why: "Looping RequestPath or NudgePathRequest floods the transport and competes with real path resolution. Throttling kicks in after repeated calls, and on shared instances you often see path resolved but the link handshake never completes.",
|
|
Hint: "call Transport.AwaitPath(ctx, dest) once with a context deadline instead of spinning",
|
|
Refs: []string{"docs/en/transport.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleRequestPathIgnoredErr, Severity: SeverityWarning, Fixable: true,
|
|
Title: "RequestPath result discarded in a function that returns error",
|
|
Why: "RequestPath returns ErrPathRequestThrottled when called too often inside the 20 second window. Ignoring the error makes callers believe the path request was accepted.",
|
|
Hint: "check the error and return ErrPathRequestThrottled to the caller",
|
|
Refs: []string{"docs/en/transport.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleHasPathLoop, Severity: SeverityError,
|
|
Title: "HasPath polling inside a loop",
|
|
Why: "Spinning on HasPath burns CPU and does not wait for the path response window on slow interfaces. It looks like a wait but never backs off correctly.",
|
|
Hint: "call Transport.AwaitPath(ctx, dest) once and handle ErrNoPathToDestination on timeout",
|
|
Refs: []string{"docs/en/transport.md"},
|
|
},
|
|
{
|
|
ID: RuleAwaitInLoop, Severity: SeverityWarning,
|
|
Title: "AwaitPath inside a loop",
|
|
Why: "Wrapping AwaitPath in an outer loop retries the full wait window on every iteration, multiplying timeouts and masking the real failure.",
|
|
Hint: "call AwaitPath once with an appropriate context deadline, handle the error outside any retry loop",
|
|
Refs: []string{"docs/en/transport.md"},
|
|
},
|
|
{
|
|
ID: RuleEstablishLoop, Severity: SeverityError,
|
|
Title: "link Establish inside a loop",
|
|
Why: "Each Establish starts a new outbound handshake. Loops create duplicate in-flight links, ErrLinkEstablishBusy storms, and the remote may never settle one link.",
|
|
Hint: "create one Link, call Establish once, wait on SetEstablishedCallback for retries",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleEstablishNoAwait, Severity: SeverityWarning,
|
|
Title: "Establish without AwaitPath or HasPath in this function",
|
|
Why: "Establish before a path exists usually times out after about 15 seconds with little useful feedback. The handshake cannot complete without a route.",
|
|
Hint: "call transport.AwaitPath(ctx, destHash) before Link.Establish",
|
|
Refs: []string{"docs/en/transport.md", "docs/en/links-channels-and-resources.md"},
|
|
},
|
|
{
|
|
ID: RuleEstablishRepeat, Severity: SeverityWarning,
|
|
Title: "multiple Establish calls in one function",
|
|
Why: "A second Establish on the same link returns ErrLinkAlreadySettled or ErrLinkEstablishBusy. Retrying by calling Establish again is not the same as waiting for the callback.",
|
|
Hint: "one Link and one Establish per destination, use the established callback for follow-up work",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleNewLinkLoop, Severity: SeverityError,
|
|
Title: "NewLink inside a loop",
|
|
Why: "Allocating a fresh link on every retry leaks handshake state and confuses which link owns the path. The transport tracks one outbound link per destination.",
|
|
Hint: "reuse one Link per destination, handle ErrLinkEstablishBusy instead of allocating again",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md"},
|
|
},
|
|
{
|
|
ID: RuleNewLinkRepeat, Severity: SeverityWarning,
|
|
Title: "multiple NewLink calls in one function",
|
|
Why: "More than one Link for the same destination in a single flow usually means a retry bug. Only one outbound handshake should be in flight.",
|
|
Hint: "reuse the same Link, return ErrLinkEstablishBusy to the caller when a handshake is pending",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleLinkNotActive, Severity: SeverityWarning,
|
|
Title: "link use without SetEstablishedCallback in this function",
|
|
Why: "Send, Request, and Identify on a link that is not yet active return ErrLinkNotActive. Without a callback there is no reliable signal that the handshake finished.",
|
|
Hint: "call SetEstablishedCallback before Send, Request, or Identify",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleLinkActiveUseLoop, Severity: SeverityError,
|
|
Title: "link Send, Request, or Identify inside a loop",
|
|
Why: "Retrying link operations in a loop before the established callback fires duplicates work and can wedge the handshake.",
|
|
Hint: "wait for SetEstablishedCallback, then send data once the link is active",
|
|
Refs: []string{"docs/en/links-channels-and-resources.md"},
|
|
},
|
|
{
|
|
ID: RuleAnnounceLoop, Severity: SeverityWarning,
|
|
Title: "Announce inside a loop",
|
|
Why: "Bursts of Announce calls hit ErrDestAnnounceThrottled after 8 announces in 10 seconds. Spinning does not make the mesh hear you faster.",
|
|
Hint: "handle ErrDestAnnounceThrottled and back off instead of looping Announce",
|
|
Refs: []string{"docs/en/identity-and-destinations.md", "docs/en/api-reference.md"},
|
|
},
|
|
{
|
|
ID: RuleFixed15sTimeout, Severity: SeverityHint,
|
|
Title: "hard-coded 15 second timeout",
|
|
Why: "Fifteen seconds matches legacy RNS defaults but fails on sub-kbps radios and shared instances where path and link waits are sized from interface bitrate.",
|
|
Hint: "use Transport.AwaitPath, PathResponseWindow, Link.EstablishmentTimeout, or Transport.FirstHopTimeout",
|
|
Refs: []string{"docs/en/configuration.md", "docs/en/transport.md"},
|
|
},
|
|
{
|
|
ID: RulePythonLinkSpin, Severity: SeverityWarning,
|
|
Title: "python link_ready polling loop",
|
|
Why: "Spinning on link_ready with time.sleep delays the handshake and hides whether the shared instance relayed link traffic correctly.",
|
|
Hint: "use the link established callback, or get_first_hop_timeout from a shared instance RPC",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/links-channels-and-resources.md"},
|
|
},
|
|
{
|
|
ID: RulePythonPathSpin, Severity: SeverityError,
|
|
Title: "python has_path polling loop",
|
|
Why: "Polling Transport.has_path in a loop does not respect path response windows and triggers the same throttling and flake patterns as Go HasPath spins.",
|
|
Hint: "use Transport.await_path instead of a tight has_path loop",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/transport.md"},
|
|
},
|
|
{
|
|
ID: RulePythonRequestPathLoop, Severity: SeverityError,
|
|
Title: "python request_path inside a loop",
|
|
Why: "Repeated request_path calls flood the transport and compete with real path resolution, especially through shared-instance clients.",
|
|
Hint: "use Transport.await_path once with an appropriate timeout",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/transport.md"},
|
|
},
|
|
{
|
|
ID: RulePythonFixed15s, Severity: SeverityHint,
|
|
Title: "python LINK_TIMEOUT = 15",
|
|
Why: "A fixed 15 second link timeout ignores adaptive waits from interface bitrate and shared-instance RPC helpers.",
|
|
Hint: "use Reticulum.get_first_hop_timeout when attached to a shared instance",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/configuration.md", "https://reticulum.network/manual/reference.html"},
|
|
},
|
|
{
|
|
ID: RuleOnInterfaceOverride, Severity: SeverityWarning,
|
|
Title: "RequestPath with on_interface override",
|
|
Why: "Reticulum selects the best interface for path requests automatically. Forcing on_interface often fails when that iface is offline or receive-only, and duplicates logic the transport already handles.",
|
|
Hint: "call RequestPath with an empty on_interface and let the transport fan out",
|
|
Refs: []string{"docs/en/transport.md", "https://reticulum.network/manual/reference.html"},
|
|
},
|
|
{
|
|
ID: RuleRecallBeforePath, Severity: SeverityWarning,
|
|
Title: "Identity.Recall before path wait",
|
|
Why: "Recall needs a path response or announce first. Calling Recall before AwaitPath or HasPath returns ErrIdentityNotFound and leads to building destinations or links with no identity.",
|
|
Hint: "call Transport.AwaitPath(ctx, destHash) before identity.Recall",
|
|
Refs: []string{"docs/en/api-reference.md", "docs/en/identity-and-destinations.md", "https://reticulum.network/manual/examples.html"},
|
|
},
|
|
{
|
|
ID: RulePythonAwaitInLoop, Severity: SeverityWarning,
|
|
Title: "python await_path inside a loop",
|
|
Why: "Wrapping await_path in a retry loop multiplies the full wait window on every iteration. The RNS API provides await_path so apps do not need spin loops.",
|
|
Hint: "call Transport.await_path once with a timeout, handle failure outside any loop",
|
|
Refs: []string{"docs/en/librns.md", "https://reticulum.network/manual/reference.html", "https://unsigned.io/articles/2025_05_09_The_End_Is_Nigh_For_The_Beta_Days.html"},
|
|
},
|
|
{
|
|
ID: RulePythonLinkStatusSpin, Severity: SeverityWarning,
|
|
Title: "python link status polling loop",
|
|
Why: "Polling link.status or Link.ACTIVE with time.sleep is the pre-callback pattern from old examples. It races link closure and hides shared-instance relay issues.",
|
|
Hint: "use set_link_established_callback and do work when the link is active",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/links-channels-and-resources.md", "https://reticulum.network/manual/examples.html"},
|
|
},
|
|
{
|
|
ID: RulePythonPathThenSpin, Severity: SeverityError,
|
|
Title: "python request_path then has_path spin",
|
|
Why: "This is the classic RNS example anti-pattern: request_path followed by a has_path sleep loop. It floods path requests and still does not wait correctly on slow interfaces.",
|
|
Hint: "replace the whole block with Transport.await_path(dest, timeout=...)",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/transport.md", "https://reticulum.network/manual/interfaces.html", "https://unsigned.io/articles/2025_05_09_The_End_Is_Nigh_For_The_Beta_Days.html"},
|
|
},
|
|
{
|
|
ID: RulePythonRecallBeforePath, Severity: SeverityWarning,
|
|
Title: "python Identity.recall before path wait",
|
|
Why: "Recall returns None until a path response or announce is seen. Building a Destination or Link before await_path fails quietly or panics later.",
|
|
Hint: "call Transport.await_path before Identity.recall",
|
|
Refs: []string{"docs/en/librns.md", "docs/en/identity-and-destinations.md", "https://reticulum.network/manual/examples.html"},
|
|
},
|
|
{
|
|
ID: RulePythonOnInterface, Severity: SeverityWarning,
|
|
Title: "python on_interface override on path API",
|
|
Why: "The RNS manual warns that on_interface should rarely be used. Reticulum picks interfaces automatically and a wrong choice blocks path resolution.",
|
|
Hint: "omit on_interface from request_path and await_path",
|
|
Refs: []string{"docs/en/librns.md", "https://reticulum.network/manual/reference.html"},
|
|
},
|
|
{
|
|
ID: RulePythonRequireShared, Severity: SeverityHint,
|
|
Title: "python require_shared_instance without a master rnsd",
|
|
Why: "require_shared_instance=True aborts when no shared instance is listening. Apps that start before rnsd or run in isolated containers need their own transport or a backbone interface instead.",
|
|
Hint: "run rnsd as a service, or use TCPServerInterface between hosts instead of shared_instance_port across containers",
|
|
Refs: []string{"docs/en/utilities.md", "docs/en/architecture.md", "https://reticulum.network/manual/using.html", "https://unsigned.io/rnode_bootstrap_console/m/understanding.html"},
|
|
},
|
|
}
|