rtc: measure data blob keys by their content, not their text form (#4923)

This commit is contained in:
RaphaelFakhri
2026-09-29 23:17:42 +05:30
committed by GitHub
parent d91b16238a
commit 2d9bf12581
3 changed files with 74 additions and 6 deletions
+17 -5
View File
@@ -399,8 +399,20 @@ func (l LimitConfig) CheckAttributesSize(attributes map[string]string) bool {
return uint32(total) <= l.MaxAttributesSize
}
func (l LimitConfig) CheckDataBlobKeyLength(key string) bool {
return l.MaxDataBlobKeyLength == 0 || len(key) <= l.MaxDataBlobKeyLength
// DataBlobKeyLength returns the length of the identifying content of a data blob key.
// The text form of the key message is not used, as it adds field names and quotes.
func DataBlobKeyLength(key *livekit.DataBlobKey) int {
switch k := key.GetKey().(type) {
case *livekit.DataBlobKey_Generic:
return len(k.Generic)
case *livekit.DataBlobKey_SchemaId:
return len(k.SchemaId.GetName()) + len(k.SchemaId.GetEncoding().GetCustom())
}
return 0
}
func (l LimitConfig) CheckDataBlobKeyLength(key *livekit.DataBlobKey) bool {
return l.MaxDataBlobKeyLength == 0 || DataBlobKeyLength(key) <= l.MaxDataBlobKeyLength
}
func (l LimitConfig) CheckDataTrackCustomEncodingLength(identifier string) bool {
@@ -430,7 +442,7 @@ func (l LimitConfig) CheckDataBlobsSize(dataBlobs []*livekit.DataBlob) bool {
total := 0
for _, dataBlob := range dataBlobs {
total += len(dataBlob.GetKey().String()) + len(dataBlob.Contents)
total += DataBlobKeyLength(dataBlob.GetKey()) + len(dataBlob.Contents)
}
return uint32(total) <= l.MaxDataBlobSize
}
@@ -442,9 +454,9 @@ func (l LimitConfig) CanAddDataBlob(dataBlobs []*livekit.DataBlob, toAdd *liveki
total := 0
for _, dataBlob := range dataBlobs {
total += len(dataBlob.Key.String()) + len(dataBlob.Contents)
total += DataBlobKeyLength(dataBlob.GetKey()) + len(dataBlob.Contents)
}
return uint32(total+len(toAdd.GetKey().String())+len(toAdd.Contents)) <= l.MaxDataBlobSize
return uint32(total+DataBlobKeyLength(toAdd.GetKey())+len(toAdd.Contents)) <= l.MaxDataBlobSize
}
// ---------------------------------
+2 -1
View File
@@ -15,6 +15,7 @@
package rtc
import (
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/rtc/types"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
@@ -31,7 +32,7 @@ func (p *ParticipantImpl) HandleStoreDataBlobRequest(req *livekit.StoreDataBlobR
return
}
if req.Blob == nil || req.Blob.Key == nil || len(req.Blob.Key.String()) == 0 || !p.params.LimitConfig.CheckDataBlobKeyLength(req.Blob.Key.String()) {
if req.Blob == nil || req.Blob.Key == nil || config.DataBlobKeyLength(req.Blob.Key) == 0 || !p.params.LimitConfig.CheckDataBlobKeyLength(req.Blob.Key) {
p.pubLogger.Warnw("data blob is invalid", nil, "req", logger.Proto(req))
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
@@ -124,6 +124,61 @@ func TestHandleStoreDataBlobRequest(t *testing.T) {
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
})
t.Run("returns INVALID_REQUEST when generic key is empty", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: genericKey(""),
Contents: []byte("def"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
require.Empty(t, p.dataBlob.GetAll())
})
t.Run("accepts a key exactly at the length limit", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 5, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
key := genericKey("abcde")
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: key,
Contents: []byte("def"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
msg := sink.WriteMessageArgsForCall(0).(*livekit.SignalResponse)
_, ok := msg.Message.(*livekit.SignalResponse_StoreDataBlobResponse)
require.True(t, ok, "expected SignalResponse_StoreDataBlobResponse, got %T", msg.Message)
require.NotNil(t, p.dataBlob.Get(key))
})
t.Run("counts only key and contents bytes against the size limit", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 9)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
key := genericKey("abc")
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: key,
Contents: []byte("defghi"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
msg := sink.WriteMessageArgsForCall(0).(*livekit.SignalResponse)
_, ok := msg.Message.(*livekit.SignalResponse_StoreDataBlobResponse)
require.True(t, ok, "expected SignalResponse_StoreDataBlobResponse, got %T", msg.Message)
require.NotNil(t, p.dataBlob.Get(key))
})
t.Run("returns INVALID_REQUEST when contents is empty", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)