uniformize limit handling

* max-keys is enforced for channels as well
* remove unlimited configurations
* maintain the limit exactly without off-by-one cases
This commit is contained in:
Shivaram Lingamneni 2025-06-15 18:57:18 -04:00
parent f85222f5f5
commit c82d324a83
6 changed files with 31 additions and 28 deletions

View file

@ -1093,10 +1093,9 @@ metadata:
# can clients store metadata? # can clients store metadata?
enabled: true enabled: true
# how many keys can a client subscribe to? # how many keys can a client subscribe to?
# set to 0 to disable subscriptions or -1 to allow unlimited subscriptions.
max-subs: 100 max-subs: 100
# how many keys can a user store about themselves? set to -1 to allow unlimited keys. # how many keys can be stored per entity?
max-keys: 1000 max-keys: 100
# experimental support for mobile push notifications # experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications. # see the manual for potential security, privacy, and performance implications.

View file

@ -1649,21 +1649,22 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.supportedCaps.Disable(caps.Metadata) config.Server.supportedCaps.Disable(caps.Metadata)
} else { } else {
var metadataValues []string var metadataValues []string
if config.Metadata.MaxSubs >= 0 { // these are required for normal operation, so set sane defaults:
if config.Metadata.MaxSubs == 0 {
config.Metadata.MaxSubs = 10
}
metadataValues = append(metadataValues, fmt.Sprintf("max-subs=%d", config.Metadata.MaxSubs)) metadataValues = append(metadataValues, fmt.Sprintf("max-subs=%d", config.Metadata.MaxSubs))
if config.Metadata.MaxKeys == 0 {
config.Metadata.MaxKeys = 10
} }
if config.Metadata.MaxKeys > 0 {
metadataValues = append(metadataValues, fmt.Sprintf("max-keys=%d", config.Metadata.MaxKeys)) metadataValues = append(metadataValues, fmt.Sprintf("max-keys=%d", config.Metadata.MaxKeys))
} // this is not required since we enforce a hardcoded upper bound on key+value
if config.Metadata.MaxValueBytes > 0 { if config.Metadata.MaxValueBytes > 0 {
metadataValues = append(metadataValues, fmt.Sprintf("max-value-bytes=%d", config.Metadata.MaxValueBytes)) metadataValues = append(metadataValues, fmt.Sprintf("max-value-bytes=%d", config.Metadata.MaxValueBytes))
} }
if len(metadataValues) != 0 {
config.Server.capValues[caps.Metadata] = strings.Join(metadataValues, ",") config.Server.capValues[caps.Metadata] = strings.Join(metadataValues, ",")
} }
}
err = config.processExtjwt() err = config.processExtjwt()
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -894,7 +894,7 @@ func (channel *Channel) GetMetadata(key string) (string, bool) {
return val, ok return val, ok
} }
func (channel *Channel) SetMetadata(key string, value string) (updated bool) { func (channel *Channel) SetMetadata(key string, value string, limit int) (updated bool, err error) {
defer channel.MarkDirty(IncludeAllAttrs) defer channel.MarkDirty(IncludeAllAttrs)
channel.stateMutex.Lock() channel.stateMutex.Lock()
@ -905,11 +905,14 @@ func (channel *Channel) SetMetadata(key string, value string) (updated bool) {
} }
existing, ok := channel.metadata[key] existing, ok := channel.metadata[key]
if !ok && len(channel.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing updated = !ok || value != existing
if updated { if updated {
channel.metadata[key] = value channel.metadata[key] = value
} }
return updated return updated, nil
} }
func (channel *Channel) ListMetadata() map[string]string { func (channel *Channel) ListMetadata() map[string]string {
@ -958,7 +961,7 @@ func (client *Client) GetMetadata(key string) (string, bool) {
return val, ok return val, ok
} }
func (client *Client) SetMetadata(key string, value string) (updated bool) { func (client *Client) SetMetadata(key string, value string, limit int) (updated bool, err error) {
client.stateMutex.Lock() client.stateMutex.Lock()
defer client.stateMutex.Unlock() defer client.stateMutex.Unlock()
@ -967,11 +970,14 @@ func (client *Client) SetMetadata(key string, value string) (updated bool) {
} }
existing, ok := client.metadata[key] existing, ok := client.metadata[key]
if !ok && len(client.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing updated = !ok || value != existing
if updated { if updated {
client.metadata[key] = value client.metadata[key] = value
} }
return updated return updated, nil
} }
func (client *Client) ListMetadata() map[string]string { func (client *Client) ListMetadata() map[string]string {

View file

@ -3175,19 +3175,17 @@ func metadataHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res
return return
} }
maxKeys := config.Metadata.MaxKeys updated, err := targetObj.SetMetadata(key, value, config.Metadata.MaxKeys)
isSelf := targetClient != nil && client == targetClient if err != nil {
// errLimitExceeded is the only possible error
if isSelf && maxKeys > 0 && targetObj.CountMetadata() >= maxKeys { rb.Add(nil, server.name, "FAIL", "METADATA", "LIMIT_REACHED", client.t("Too many metadata keys"))
rb.Add(nil, server.name, "FAIL", "METADATA", "LIMIT_REACHED", client.t("You have too many keys set on yourself"))
return return
} }
if updated := targetObj.SetMetadata(key, value); updated {
notifySubscribers(server, rb.session, targetObj, target, key, value)
}
// echo the value to the client whether or not there was a real update // echo the value to the client whether or not there was a real update
rb.Add(nil, server.name, RPL_KEYVALUE, client.Nick(), target, key, "*", value) rb.Add(nil, server.name, RPL_KEYVALUE, client.Nick(), target, key, "*", value)
if updated {
notifySubscribers(server, rb.session, targetObj, target, key, value)
}
} else { } else {
if updated := targetObj.DeleteMetadata(key); updated { if updated := targetObj.DeleteMetadata(key); updated {
notifySubscribers(server, rb.session, targetObj, target, key, "") notifySubscribers(server, rb.session, targetObj, target, key, "")

View file

@ -21,7 +21,7 @@ var (
) )
type MetadataHaver = interface { type MetadataHaver = interface {
SetMetadata(key string, value string) (updated bool) SetMetadata(key string, value string, limit int) (updated bool, err error)
GetMetadata(key string) (string, bool) GetMetadata(key string) (string, bool)
DeleteMetadata(key string) (updated bool) DeleteMetadata(key string) (updated bool)
ListMetadata() map[string]string ListMetadata() map[string]string

View file

@ -1064,10 +1064,9 @@ metadata:
# can clients store metadata? # can clients store metadata?
enabled: true enabled: true
# how many keys can a client subscribe to? # how many keys can a client subscribe to?
# set to 0 to disable subscriptions or -1 to allow unlimited subscriptions.
max-subs: 100 max-subs: 100
# how many keys can a user store about themselves? set to -1 to allow unlimited keys. # how many keys can be stored per entity?
max-keys: 1000 max-keys: 100
# experimental support for mobile push notifications # experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications. # see the manual for potential security, privacy, and performance implications.