diff --git a/verify.go b/verify.go index 3ad8c32..13dcd70 100644 --- a/verify.go +++ b/verify.go @@ -178,28 +178,46 @@ type verifier struct { dataSize int } -func (v *verifier) HashWithExpEntryHandler(key string) func(field string, value string, exp time.Time) error { +func (v *verifier) HandleString(key string, value string) error { if len(key) > v.maxKeySize { - return func(field, value string, exp time.Time) error { + return errMaxKeySizeExceeded(len(key), v.maxKeySize) + } + + if len(value) > v.maxEntrySize { + return errMaxEntrySizeExceeded(len(value), v.maxEntrySize) + } + + v.dataSize += len(key) + len(value) + if v.dataSize > v.maxDataSize { + return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) + } + + return nil +} + +func (v *verifier) HashEntryHandler(key string) func(field string, value string) error { + if len(key) > v.maxKeySize { + return func(field, value string) error { return errMaxKeySizeExceeded(len(key), v.maxKeySize) } } v.dataSize += len(key) if v.dataSize > v.maxDataSize { - return func(field, value string, exp time.Time) error { + return func(field, value string) error { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } } var entrySize int - return func(field, value string, exp time.Time) error { - entrySize += len(field) + len(value) + 8 + return func(field, value string) error { + elementSize := len(field) + len(value) + entrySize += elementSize if entrySize > v.maxEntrySize { return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += elementSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } @@ -208,45 +226,29 @@ func (v *verifier) HashWithExpEntryHandler(key string) func(field string, value } } -func (v *verifier) HandleString(key string, value string) error { - if len(key) > v.maxKeySize { - return errMaxKeySizeExceeded(len(key), v.maxKeySize) - } - - if len(value) > v.maxEntrySize { - return errMaxEntrySizeExceeded(len(value), v.maxEntrySize) - } - - v.dataSize += len(key) + len(value) - if v.dataSize > v.maxDataSize { - return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) - } - - return nil -} - -func (v *verifier) HashEntryHandler(key string) func(field string, value string) error { +func (v *verifier) HashWithExpEntryHandler(key string) func(field string, value string, exp time.Time) error { if len(key) > v.maxKeySize { - return func(field, value string) error { + return func(field, value string, exp time.Time) error { return errMaxKeySizeExceeded(len(key), v.maxKeySize) } } v.dataSize += len(key) if v.dataSize > v.maxDataSize { - return func(field, value string) error { + return func(field, value string, exp time.Time) error { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } } var entrySize int - return func(field, value string) error { - entrySize += len(field) + len(value) + return func(field, value string, exp time.Time) error { + elementSize := len(field) + len(value) + 8 + entrySize += elementSize if entrySize > v.maxEntrySize { return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += elementSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } @@ -271,12 +273,13 @@ func (v *verifier) ListEntryHandler(key string) func(elem string) error { var entrySize int return func(elem string) error { - entrySize += len(elem) + elementSize := len(elem) + entrySize += elementSize if entrySize > v.maxEntrySize { return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += elementSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } @@ -301,12 +304,13 @@ func (v *verifier) SetEntryHandler(key string) func(elem string) error { var entrySize int return func(elem string) error { - entrySize += len(elem) + elementSize := len(elem) + entrySize += elementSize if entrySize > v.maxEntrySize { return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += elementSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } @@ -331,12 +335,13 @@ func (v *verifier) ZsetEntryHandler(key string) func(elem string, score float64) var entrySize int return func(elem string, score float64) error { - entrySize += len(elem) + 8 + elementSize := len(elem) + 8 + entrySize += elementSize if entrySize > v.maxEntrySize { return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += elementSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } @@ -402,35 +407,37 @@ func (v *verifier) StreamEntryHandler(key string) func(entry StreamEntry) error func (v *verifier) StreamGroupHandler(key string) func(group StreamConsumerGroup) error { var entrySize int return func(group StreamConsumerGroup) error { + var groupSize int if len(group.Name) > maxStreamStrSize { return errMaxStreamStrSizeExceeded(len(group.Name), maxStreamStrSize) } - entrySize += len(group.Name) + 24 // 8: LastID#Seq + 8: LastID#Millis + 8: EntriesRead + groupSize += len(group.Name) + 24 // 8: LastID#Seq + 8: LastID#Millis + 8: EntriesRead for _, consumer := range group.Consumers { if len(consumer.Name) > maxStreamStrSize { return errMaxStreamStrSizeExceeded(len(consumer.Name), maxStreamStrSize) } - entrySize += len(consumer.Name) + 16 // 8: SeenTime + 8: ActiveTime + groupSize += len(consumer.Name) + 16 // 8: SeenTime + 8: ActiveTime if len(consumer.PendingEntries) > v.maxStreamPELSize { return errMaxStreamPELSizeExceeded(len(consumer.PendingEntries), v.maxStreamPELSize) } for _, pe := range consumer.PendingEntries { - entrySize += 32 // 8: ID#Seq + 8: ID#Millis + 8: DeliveryCount + 8: DeliveryTime + groupSize += 32 // 8: ID#Seq + 8: ID#Millis + 8: DeliveryCount + 8: DeliveryTime for _, val := range pe.Entry.Value { if len(val) > maxStreamStrSize { return errMaxStreamStrSizeExceeded(len(val), maxStreamStrSize) } - entrySize += len(val) + groupSize += len(val) } } } + entrySize += groupSize // unlike normal stream entries, pending entries are stored both on disk // and memory. @@ -439,7 +446,7 @@ func (v *verifier) StreamGroupHandler(key string) func(group StreamConsumerGroup return errMaxEntrySizeExceeded(entrySize, v.maxEntrySize) } - v.dataSize += entrySize + v.dataSize += groupSize if v.dataSize > v.maxDataSize { return errMaxDataSizeExceeded(v.dataSize, v.maxDataSize) } diff --git a/verify_test.go b/verify_test.go index 4628aa0..efd4c07 100644 --- a/verify_test.go +++ b/verify_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -249,3 +250,358 @@ func TestVerifyReader_RequireStrictEOF(t *testing.T) { }) require.ErrorContains(t, err, "eof") } + +func TestVerifier_String_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 3, + maxEntrySize: 100, + maxKeySize: 100, + } + + require.NoError(t, v.HandleString("k", "v")) + require.Equal(t, 2, v.dataSize) + + require.ErrorContains(t, v.HandleString("k", "v"), "max data size") +} + +func TestVerifier_String_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + require.ErrorContains(t, v.HandleString("longkey", "v"), "max key size") +} + +func TestVerifier_String_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 2, + maxKeySize: 100, + } + + require.ErrorContains(t, v.HandleString("k", "longvalue"), "max entry size") +} + +func TestVerifier_HashEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 5, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 100, + } + + h := v.HashEntryHandler("k") + require.NoError(t, h("f", "v")) + require.NoError(t, h("x", "y")) + require.Equal(t, 5, v.dataSize) + + require.ErrorContains(t, h("a", "b"), "max data size") +} + +func TestVerifier_HashEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + h := v.HashEntryHandler("longkey") + require.ErrorContains(t, h("f", "v"), "max key size") +} + +func TestVerifier_HashEntryHandler_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 3, + maxKeySize: 100, + } + + h := v.HashEntryHandler("k") + require.NoError(t, h("f", "v")) + require.ErrorContains(t, h("x", "y"), "max entry size") +} + +func TestVerifier_HashWithExpEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 21, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 100, + } + + h := v.HashWithExpEntryHandler("k") + require.NoError(t, h("f", "v", time.Now())) + require.NoError(t, h("x", "y", time.Now())) + require.Equal(t, 21, v.dataSize) + + require.ErrorContains(t, h("a", "b", time.Now()), "max data size") +} + +func TestVerifier_HashWithExpEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + h := v.HashWithExpEntryHandler("longkey") + require.ErrorContains(t, h("f", "v", time.Now()), "max key size") +} + +func TestVerifier_HashWithExpEntryHandler_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 10, + maxKeySize: 100, + } + + h := v.HashWithExpEntryHandler("k") + // "f" + "v" + 8 = 10, at limit + require.NoError(t, h("f", "v", time.Now())) + // cumulative: 20, exceeds 10 + require.ErrorContains(t, h("x", "y", time.Now()), "max entry size") +} + +func TestVerifier_ListEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 3, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 100, + } + + h := v.ListEntryHandler("k") + require.NoError(t, h("a")) + require.NoError(t, h("b")) + require.Equal(t, 3, v.dataSize) + + require.ErrorContains(t, h("c"), "max data size") +} + +func TestVerifier_ListEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + h := v.ListEntryHandler("longkey") + require.ErrorContains(t, h("a"), "max key size") +} + +func TestVerifier_ListEntryHandler_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 1, + maxKeySize: 100, + } + + h := v.ListEntryHandler("k") + require.NoError(t, h("a")) + require.ErrorContains(t, h("b"), "max entry size") +} + +func TestVerifier_SetEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 3, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 100, + } + + h := v.SetEntryHandler("k") + require.NoError(t, h("a")) + require.NoError(t, h("b")) + require.Equal(t, 3, v.dataSize) + + require.ErrorContains(t, h("c"), "max data size") +} + +func TestVerifier_SetEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + h := v.SetEntryHandler("longkey") + require.ErrorContains(t, h("a"), "max key size") +} + +func TestVerifier_SetEntryHandler_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 1, + maxKeySize: 100, + } + + h := v.SetEntryHandler("k") + require.NoError(t, h("a")) + require.ErrorContains(t, h("b"), "max entry size") +} + +func TestVerifier_ZsetEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 19, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 100, + } + + h := v.ZsetEntryHandler("k") + require.NoError(t, h("a", 1)) + require.NoError(t, h("b", 2)) + require.Equal(t, 19, v.dataSize) + + require.ErrorContains(t, h("c", 3), "max data size") +} + +func TestVerifier_ZsetEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 100, + maxKeySize: 2, + } + + h := v.ZsetEntryHandler("longkey") + require.ErrorContains(t, h("a", 1), "max key size") +} + +func TestVerifier_ZsetEntryHandler_MaxEntrySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 9, + maxKeySize: 100, + } + + h := v.ZsetEntryHandler("k") + // "a" + 8 = 9, at limit + require.NoError(t, h("a", 1)) + // cumulative: 18, exceeds 9 + require.ErrorContains(t, h("b", 2), "max entry size") +} + +func TestVerifier_StreamEntryHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 42, + maxEntrySize: 1000, + maxKeySize: 100, + maxStreamPELSize: 10, + } + + h := v.StreamEntryHandler("stream") + require.NoError(t, h(StreamEntry{Value: []string{"a", "b"}})) + require.NoError(t, h(StreamEntry{Value: []string{"x", "y"}})) + require.Equal(t, 42, v.dataSize) + + require.ErrorContains(t, h(StreamEntry{Value: []string{"z"}}), "max data size") +} + +func TestVerifier_StreamEntryHandler_MaxKeySize(t *testing.T) { + v := &verifier{ + maxDataSize: 100, + maxEntrySize: 1000, + maxKeySize: 2, + } + + h := v.StreamEntryHandler("stream") + require.ErrorContains(t, h(StreamEntry{Value: []string{"a"}}), "max key size") +} + +func TestVerifier_StreamGroupHandler_MaxDataSize(t *testing.T) { + v := &verifier{ + maxDataSize: 156, + maxEntrySize: 1000, + maxKeySize: 100, + maxStreamPELSize: 10, + } + + h := v.StreamGroupHandler("stream") + + group1 := StreamConsumerGroup{ + Name: "g1", + Consumers: []StreamConsumer{ + { + Name: "c1", + PendingEntries: []*StreamPendingEntry{ + { + Entry: StreamEntry{Value: []string{"a", "b"}}, + }, + }, + }, + }, + } + + group2 := StreamConsumerGroup{ + Name: "g2", + Consumers: []StreamConsumer{ + { + Name: "c2", + PendingEntries: []*StreamPendingEntry{ + { + Entry: StreamEntry{Value: []string{"x", "y"}}, + }, + }, + }, + }, + } + + require.NoError(t, h(group1)) + require.NoError(t, h(group2)) + require.Equal(t, 156, v.dataSize) + + require.ErrorContains(t, h(group1), "max data size") +} + +func TestVerifier_StreamGroupHandler_MaxEntrySize(t *testing.T) { + // group size: name "g" (1) + 24 + consumer name "c" (1) + 16 + pending 32 + value "a" (1) = 75 + // two groups cumulative entrySize = 150, limit = 100 + v := &verifier{ + maxDataSize: 10000, + maxEntrySize: 100, + maxKeySize: 100, + maxStreamPELSize: 10, + } + + h := v.StreamGroupHandler("stream") + + group := StreamConsumerGroup{ + Name: "g", + Consumers: []StreamConsumer{ + { + Name: "c", + PendingEntries: []*StreamPendingEntry{ + {Entry: StreamEntry{Value: []string{"a"}}}, + }, + }, + }, + } + require.NoError(t, h(group)) + + require.ErrorContains(t, h(group), "max entry size") +} + +func TestVerifier_StreamGroupHandler_MaxStreamPELSize(t *testing.T) { + v := &verifier{ + maxDataSize: 10000, + maxEntrySize: 10000, + maxKeySize: 100, + maxStreamPELSize: 0, + } + + h := v.StreamGroupHandler("stream") + group := StreamConsumerGroup{ + Name: "g1", + Consumers: []StreamConsumer{ + { + Name: "c1", + PendingEntries: []*StreamPendingEntry{ + {Entry: StreamEntry{Value: []string{"a"}}}, + }, + }, + }, + } + require.ErrorContains(t, h(group), "max stream pel size") +}