aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 09:18:10 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 09:18:10 +0200
commit73bd9ea2e297a88ae8de11414819cf45e5fc0bb2 (patch)
tree1ce1db69822f7fe65b7d516b4e3771fab980c91f
parent08b8b36dcf5c42bf76d02258c4dd05834627c9d3 (diff)
downloadnotifyd-73bd9ea2e297a88ae8de11414819cf45e5fc0bb2.tar.gz
notifyd-73bd9ea2e297a88ae8de11414819cf45e5fc0bb2.zip
fix(notify): bound config timeouts to a day
A hand-edited timeout near 1e10 seconds overflowed time.Duration and wrapped to an immediate or negative expiry with no log line. Out of range now reads as the default, like a negative value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--README.md2
-rw-r--r--internal/notify/config.go25
-rw-r--r--internal/notify/config_test.go4
3 files changed, 19 insertions, 12 deletions
diff --git a/README.md b/README.md
index f2f7cef..7a4c026 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ is optional and the defaults are the behaviour without the file:
"historyCap": 20
}
-Timeouts are seconds, 0 is never, and apply only when a client sends
+Timeouts are seconds up to a day (86400), 0 is never, and apply only when a client sends
`expire_timeout -1`. Caps clamp to 1..100; a lowered cap applies at the next
notification. A broken file is logged and read as the defaults.
diff --git a/internal/notify/config.go b/internal/notify/config.go
index 2b6ba5e..fcba5a8 100644
--- a/internal/notify/config.go
+++ b/internal/notify/config.go
@@ -74,18 +74,25 @@ func LoadConfig(path string) Config {
}
c.LiveCap = clamp(c.LiveCap, 1, 100)
c.HistoryCap = clamp(c.HistoryCap, 1, 100)
- if c.Timeouts.Low < 0 {
- c.Timeouts.Low = def.Timeouts.Low
- }
- if c.Timeouts.Normal < 0 {
- c.Timeouts.Normal = def.Timeouts.Normal
- }
- if c.Timeouts.Critical < 0 {
- c.Timeouts.Critical = def.Timeouts.Critical
- }
+ c.Timeouts.Low = seconds(c.Timeouts.Low, def.Timeouts.Low)
+ c.Timeouts.Normal = seconds(c.Timeouts.Normal, def.Timeouts.Normal)
+ c.Timeouts.Critical = seconds(c.Timeouts.Critical, def.Timeouts.Critical)
return c
}
+// maxTimeout is a day. Beyond it nothing is meaningfully a timeout, and a
+// value near 1e10 seconds would overflow time.Duration and wrap to an
+// immediate or negative expiry.
+const maxTimeout = 86400
+
+// seconds keeps a timeout in 0..maxTimeout, else the default.
+func seconds(v, def int) int {
+ if v < 0 || v > maxTimeout {
+ return def
+ }
+ return v
+}
+
func clamp(v, lo, hi int) int {
return min(max(v, lo), hi)
}
diff --git a/internal/notify/config_test.go b/internal/notify/config_test.go
index 46032ee..818e88f 100644
--- a/internal/notify/config_test.go
+++ b/internal/notify/config_test.go
@@ -58,8 +58,8 @@ func TestLoadConfigWrongTypeKeepsThatDefault(t *testing.T) {
}
func TestLoadConfigClamps(t *testing.T) {
- got := LoadConfig(writeConfig(t, `{"liveCap": 0, "historyCap": 1000, "timeouts": {"normal": -4}}`))
- if got.LiveCap != 1 || got.HistoryCap != 100 || got.Timeouts.Normal != 10 {
+ got := LoadConfig(writeConfig(t, `{"liveCap": 0, "historyCap": 1000, "timeouts": {"normal": -4, "low": 99999999999}}`))
+ if got.LiveCap != 1 || got.HistoryCap != 100 || got.Timeouts.Normal != 10 || got.Timeouts.Low != 10 {
t.Errorf("got %+v", got)
}
}