Skip to content

Commit 3583766

Browse files
authored
Validate history ttl to be less or equal to history meta ttl (#773)
1 parent e78cce5 commit 3583766

File tree

3 files changed

+56
-6
lines changed

3 files changed

+56
-6
lines changed

internal/rule/rule.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,21 @@ type Config struct {
8181
// ClientConnectionRateLimit sets the maximum number of new connections a single Centrifugo
8282
// node will accept per second.
8383
ClientConnectionRateLimit int
84+
85+
// GlobalHistoryMetaTTL from here is used only for validation.
86+
GlobalHistoryMetaTTL time.Duration
8487
}
8588

89+
const DefaultGlobalHistoryMetaTTL = 30 * 24 * time.Hour
90+
8691
// DefaultConfig has default config options.
8792
var DefaultConfig = Config{
8893
ChannelPrivatePrefix: "$", // so private channel will look like "$gossips".
8994
ChannelNamespaceBoundary: ":", // so namespace "public" can be used as "public:news".
9095
ChannelUserBoundary: "#", // so user limited channel is "user#2694" where "2696" is user ID.
9196
ChannelUserSeparator: ",", // so several users limited channel is "dialog#2694,3019".
9297
RpcNamespaceBoundary: ":", // so rpc namespace "chat" can be used as "chat:get_user_info".
98+
GlobalHistoryMetaTTL: DefaultGlobalHistoryMetaTTL,
9399
}
94100

95101
func stringInSlice(a string, list []string) bool {
@@ -104,13 +110,13 @@ func stringInSlice(a string, list []string) bool {
104110
var namePattern = "^[-a-zA-Z0-9_.]{2,}$"
105111
var nameRe = regexp.MustCompile(namePattern)
106112

107-
func ValidateNamespace(ns ChannelNamespace) error {
113+
func ValidateNamespace(ns ChannelNamespace, globalHistoryMetaTTL time.Duration) error {
108114
name := ns.Name
109115
match := nameRe.MatchString(name)
110116
if !match {
111117
return fmt.Errorf("invalid namespace name – %s (must match %s regular expression)", name, namePattern)
112118
}
113-
if err := ValidateChannelOptions(ns.ChannelOptions); err != nil {
119+
if err := ValidateChannelOptions(ns.ChannelOptions, globalHistoryMetaTTL); err != nil {
114120
return err
115121
}
116122
return nil
@@ -128,10 +134,17 @@ func ValidateRpcNamespace(ns RpcNamespace) error {
128134
return nil
129135
}
130136

131-
func ValidateChannelOptions(c ChannelOptions) error {
137+
func ValidateChannelOptions(c ChannelOptions, globalHistoryMetaTTL time.Duration) error {
132138
if (c.HistorySize != 0 && c.HistoryTTL == 0) || (c.HistorySize == 0 && c.HistoryTTL != 0) {
133139
return errors.New("both history size and history ttl required for history")
134140
}
141+
historyMetaTTL := globalHistoryMetaTTL
142+
if c.HistoryMetaTTL != 0 {
143+
historyMetaTTL = time.Duration(c.HistoryMetaTTL)
144+
}
145+
if historyMetaTTL < time.Duration(c.HistoryTTL) {
146+
return fmt.Errorf("history ttl (%s) can not be greater than history meta ttl (%s)", time.Duration(c.HistoryTTL), historyMetaTTL)
147+
}
135148
if c.ForceRecovery && (c.HistorySize == 0 || c.HistoryTTL == 0) {
136149
return errors.New("both history size and history ttl required for recovery")
137150
}
@@ -152,7 +165,7 @@ func ValidateRpcOptions(_ RpcOptions) error {
152165

153166
// Validate validates config and returns error if problems found
154167
func (c *Config) Validate() error {
155-
if err := ValidateChannelOptions(c.ChannelOptions); err != nil {
168+
if err := ValidateChannelOptions(c.ChannelOptions, c.GlobalHistoryMetaTTL); err != nil {
156169
return err
157170
}
158171
if err := ValidateRpcOptions(c.RpcOptions); err != nil {
@@ -175,7 +188,7 @@ func (c *Config) Validate() error {
175188
if stringInSlice(n.Name, nss) {
176189
return fmt.Errorf("namespace name must be unique: %s", n.Name)
177190
}
178-
if err := ValidateNamespace(n); err != nil {
191+
if err := ValidateNamespace(n, c.GlobalHistoryMetaTTL); err != nil {
179192
return fmt.Errorf("namespace %s: %v", n.Name, err)
180193
}
181194
if n.Name == personalChannelNamespace {

internal/rule/rule_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/centrifugal/centrifugo/v5/internal/tools"
10+
911
"github.com/stretchr/testify/require"
1012
)
1113

@@ -100,6 +102,39 @@ func TestConfigValidatePersonalSingleConnectionOK(t *testing.T) {
100102
require.NoError(t, err)
101103
}
102104

105+
func TestConfigValidateHistoryTTL(t *testing.T) {
106+
t.Run("in_namespace", func(t *testing.T) {
107+
c := DefaultConfig
108+
c.Namespaces = []ChannelNamespace{
109+
{
110+
Name: "name1",
111+
ChannelOptions: ChannelOptions{
112+
HistorySize: 10,
113+
HistoryTTL: tools.Duration(20 * time.Second),
114+
HistoryMetaTTL: tools.Duration(10 * time.Second),
115+
},
116+
},
117+
}
118+
err := c.Validate()
119+
require.ErrorContains(t, err, "history meta ttl")
120+
})
121+
t.Run("on_top_level", func(t *testing.T) {
122+
c := DefaultConfig
123+
c.HistorySize = 10
124+
c.HistoryTTL = tools.Duration(31 * 24 * time.Hour)
125+
err := c.Validate()
126+
require.ErrorContains(t, err, "history meta ttl")
127+
})
128+
t.Run("top_level_non_default_global", func(t *testing.T) {
129+
c := DefaultConfig
130+
c.GlobalHistoryMetaTTL = 10 * time.Hour
131+
c.HistorySize = 10
132+
c.HistoryTTL = tools.Duration(30 * 24 * time.Hour)
133+
err := c.Validate()
134+
require.ErrorContains(t, err, "history meta ttl")
135+
})
136+
}
137+
103138
func TestConfigValidatePersonalSingleConnectionNamespacedFail(t *testing.T) {
104139
c := DefaultConfig
105140
c.Namespaces = []ChannelNamespace{}

main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ var defaults = map[string]any{
134134

135135
"allowed_origins": []string{},
136136

137-
"global_history_meta_ttl": 30 * 24 * time.Hour,
137+
"global_history_meta_ttl": rule.DefaultGlobalHistoryMetaTTL,
138138
"global_presence_ttl": 60 * time.Second,
139139
"global_redis_presence_user_mapping": false,
140140

@@ -1726,6 +1726,8 @@ func ruleConfig() rule.Config {
17261726
cfg.SubRefreshProxyName = v.GetString("sub_refresh_proxy_name")
17271727
cfg.ProxySubscribeStream = v.GetBool("proxy_stream_subscribe")
17281728
cfg.ProxySubscribeStreamBidirectional = v.GetBool("proxy_subscribe_stream_bidirectional")
1729+
// GlobalHistoryMetaTTL is required here only for validation purposes.
1730+
cfg.GlobalHistoryMetaTTL = GetDuration("global_history_meta_ttl", true)
17291731

17301732
cfg.Namespaces = namespacesFromConfig(v)
17311733

0 commit comments

Comments
 (0)