diff --git a/libs/server/Resp/Bitmap/BitmapManagerBitPos.cs b/libs/server/Resp/Bitmap/BitmapManagerBitPos.cs
index aba15d649a..0cde3dcdd6 100644
--- a/libs/server/Resp/Bitmap/BitmapManagerBitPos.cs
+++ b/libs/server/Resp/Bitmap/BitmapManagerBitPos.cs
@@ -1,185 +1,174 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
-using System.Diagnostics;
-using System.Runtime.Intrinsics.X86;
+using System.Buffers.Binary;
+using System.Numerics;
namespace Garnet.server
{
public unsafe partial class BitmapManager
{
///
- /// Find pos of bit set/clear for given bit offsets within a single byte.
+ /// Main driver for BITPOS command
///
- /// Byte value to search within.
- /// Bit value to search for (0|1).
- /// Start most significant bit offset in byte value.
- /// End most significant bit offset in bitmap.
- ///
- private static long BitPosIndexBitSingleByteSearch(byte value, byte bSetVal, int startBitOffset = 0, int endBitOffset = 8)
- {
- Debug.Assert(startBitOffset >= 0 && startBitOffset <= 8);
- Debug.Assert(endBitOffset >= 0 && endBitOffset <= 8);
- bool bflag = (bSetVal == 0);
- long mask = bflag ? -1 : 0;
-
- int leftBitIndex = 1 << (8 - startBitOffset);
- int rightBitIndex = 1 << (8 - endBitOffset);
-
- // Create extraction mask
- long extract = leftBitIndex - rightBitIndex;
-
- long payload = (long)(value & extract) << 56;
- // Trim leading bits
- payload = payload << startBitOffset;
-
- // Transform to count leading zeros
- payload = bflag ? ~payload : payload;
-
- // Return not found
- if (payload == mask) return -1;
-
- return (long)Lzcnt.X64.LeadingZeroCount((ulong)payload);
- }
-
- ///
- /// Find pos of bit set/clear for given bit offset.
- ///
- /// Pointer to start of bitmap.
- /// Bit value to search for (0|1).
- /// Bit offset in bitmap.
- ///
- private static long BitPosIndexBitSearch(byte* value, byte bSetVal, long offset = 0)
- {
- bool bflag = (bSetVal == 0);
- long mask = bflag ? -1 : 0;
- long startByteOffset = (offset / 8);
- int bitOffset = (int)(offset & 7);
-
- long payload = (long)value[startByteOffset] << 56;
- // Trim leading bits
- payload = payload << bitOffset;
-
- // Transform to count leading zeros
- payload = bflag ? ~payload : payload;
-
- // Return not found
- if (payload == mask)
- return -1;
-
- return (long)Lzcnt.X64.LeadingZeroCount((ulong)payload);
- }
-
- ///
- /// Main driver for bit position command.
- ///
- ///
+ ///
+ ///
///
///
+ ///
///
- /// Pointer to start of bitmap.
- /// Length of bitmap.
///
- public static long BitPosDriver(byte setVal, long startOffset, long endOffset, byte offsetType, byte* value, int valLen)
+ public static long BitPosDriver(byte* input, int inputLen, long startOffset, long endOffset, byte searchFor, byte offsetType)
{
if (offsetType == 0x0)
{
- startOffset = startOffset < 0 ? ProcessNegativeOffset(startOffset, valLen) : startOffset;
- endOffset = endOffset < 0 ? ProcessNegativeOffset(endOffset, valLen) : endOffset;
+ startOffset = startOffset < 0 ? ProcessNegativeOffset(startOffset, inputLen) : startOffset;
+ endOffset = endOffset < 0 ? ProcessNegativeOffset(endOffset, inputLen) : endOffset;
- if (startOffset >= valLen) // If startOffset greater that valLen always bitpos -1
+ if (startOffset >= inputLen) // If startOffset greater that valLen always bitpos -1
return -1;
if (startOffset > endOffset) // If start offset beyond endOffset return 0
return -1;
- endOffset = endOffset >= valLen ? valLen : endOffset;
- long pos = BitPosByte(value, setVal, startOffset, endOffset);
- // check if position is exceeding the last byte in acceptable range
- return pos >= ((endOffset + 1) * 8) ? -1 : pos;
+ endOffset = endOffset >= inputLen ? inputLen : endOffset;
+ // BYTE search
+ return BitPosByteSearch(input, inputLen, startOffset, endOffset, searchFor);
}
-
- startOffset = startOffset < 0 ? ProcessNegativeOffset(startOffset, valLen * 8) : startOffset;
- endOffset = endOffset < 0 ? ProcessNegativeOffset(endOffset, valLen * 8) : endOffset;
-
- var startByte = (startOffset / 8);
- var endByte = (endOffset / 8);
- if (startByte == endByte)
+ else
{
- // Search only inside single byte for pos
- var leftBitIndex = (int)(startOffset & 7);
- var rightBitIndex = (int)((endOffset + 1) & 7);
- var _ipos = BitPosIndexBitSingleByteSearch(value[startByte], setVal, leftBitIndex, rightBitIndex);
- return _ipos == -1 ? _ipos : startOffset + _ipos;
- }
+ startOffset = startOffset < 0 ? ProcessNegativeOffset(startOffset, inputLen * 8) : startOffset;
+ endOffset = endOffset < 0 ? ProcessNegativeOffset(endOffset, inputLen * 8) : endOffset;
+
+ var startByteIndex = startOffset >> 3;
+ var endByteIndex = endOffset >> 3;
- // Search prefix and terminate if found position of bit
- var _ppos = BitPosIndexBitSearch(value, setVal, startOffset);
- if (_ppos != -1) return startOffset + _ppos;
+ if (startByteIndex >= inputLen) // If startOffset greater that valLen always bitpos -1
+ return -1;
- // Adjust offsets to skip first and last byte
- var _startOffset = (startOffset / 8) + 1;
- var _endOffset = (endOffset / 8) - 1;
- var _bpos = BitPosByte(value, setVal, _startOffset, _endOffset);
+ if (startByteIndex > endByteIndex) // If start offset beyond endOffset return 0
+ return -1;
- if (_bpos != -1 && _bpos < (_endOffset + 1) * 8) return _bpos;
+ endOffset = endByteIndex >= inputLen ? inputLen << 3 : endOffset;
- // Search suffix
- var _spos = BitPosIndexBitSearch(value, setVal, endOffset);
- return _spos;
+ // BIT search
+ return BitPosBitSearch(input, inputLen, startOffset, endOffset, searchFor);
+ }
}
///
- /// Find pos of set/clear bit in a sequence of bytes.
+ /// Search for position of bit set in byte array using bit offset for start and end range
///
- /// Pointer to start of bitmap.
- /// The bit value to search for (0 for cleared bit or 1 for set bit).
- /// Starting offset into bitmap.
- /// End offset into bitmap.
+ ///
+ ///
+ ///
+ ///
+ ///
///
- private static long BitPosByte(byte* value, byte bSetVal, long startOffset, long endOffset)
+ private static long BitPosBitSearch(byte* input, long inputLen, long startBitOffset, long endBitOffset, byte searchFor)
{
- // Mask set to look for 0 or 1 depending on clear/set flag
- bool bflag = (bSetVal == 0);
- long mask = bflag ? -1 : 0;
- long len = (endOffset - startOffset) + 1;
- long remainder = len & 7;
- byte* curr = value + startOffset;
- byte* end = curr + (len - remainder);
-
- // Search for first word not matching mask.
- while (curr < end)
+ var searchBit = searchFor == 1;
+ var invalidPayload = (byte)(searchBit ? 0x00 : 0xff);
+ var currentBitOffset = (int)startBitOffset;
+ while (currentBitOffset <= endBitOffset)
{
- long v = *(long*)(curr);
- if (v != mask) break;
- curr += 8;
- }
+ var byteIndex = currentBitOffset >> 3;
+ var leftBitOffset = currentBitOffset & 7;
+ var boundary = 8 - leftBitOffset;
+ var rightBitOffset = currentBitOffset + boundary <= endBitOffset ? leftBitOffset + boundary : (int)(endBitOffset & 7) + 1;
+
+ // Trim byte to start and end bit index
+ var mask = (0xff >> leftBitOffset) ^ (0xff >> rightBitOffset);
+ var payload = (long)(input[byteIndex] & mask);
- long pos = (((long)(curr - value)) << 3);
+ // Invalid only if equals the masked payload
+ var invalidMask = invalidPayload & mask;
- long payload = 0;
- // Adjust end so we can retrieve word
- end = end + remainder;
+ // If transformed payload is invalid skip to next byte
+ if (payload != invalidMask)
+ {
+ payload <<= (56 + leftBitOffset);
+ payload = searchBit ? payload : ~payload;
- // Build payload at least one byte to examine
- if (curr < end) payload |= (long)curr[0] << 56;
- if (curr + 1 < end) payload |= (long)curr[1] << 48;
- if (curr + 2 < end) payload |= (long)curr[2] << 40;
- if (curr + 3 < end) payload |= (long)curr[3] << 32;
- if (curr + 4 < end) payload |= (long)curr[4] << 24;
- if (curr + 5 < end) payload |= (long)curr[5] << 16;
- if (curr + 6 < end) payload |= (long)curr[6] << 8;
- if (curr + 7 < end) payload |= (long)curr[7];
+ var lzcnt = (long)BitOperations.LeadingZeroCount((ulong)payload);
+ return currentBitOffset + lzcnt;
+ }
- // Transform to count leading zeros
- payload = (bSetVal == 0) ? ~payload : payload;
+ currentBitOffset += boundary;
+ }
- if (payload == mask)
- return pos + 0;
+ return -1;
+ }
- pos += (long)Lzcnt.X64.LeadingZeroCount((ulong)payload);
+ ///
+ /// Search for position of bit set in byte array using byte offset for start and end range
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static long BitPosByteSearch(byte* input, long inputLen, long startOffset, long endOffset, byte searchFor)
+ {
+ // Initialize variables
+ var searchBit = searchFor == 1;
+ var invalidMask8 = searchBit ? 0x00 : 0xff;
+ var invalidMask32 = searchBit ? 0 : -1;
+ var invalidMask64 = searchBit ? 0L : -1L;
+ var currentStartOffset = startOffset;
+
+ while (currentStartOffset <= endOffset)
+ {
+ var remainder = endOffset - currentStartOffset + 1;
+ if (remainder >= 8)
+ {
+ var payload = *(long*)(input + currentStartOffset);
+ payload = BinaryPrimitives.ReverseEndianness(payload);
+
+ // Process only if payload is valid (i.e. not all bits are set or clear based on searchFor parameter)
+ if (payload != invalidMask64)
+ {
+ // Transform to count leading zeros
+ payload = searchBit ? payload : ~payload;
+ var lzcnt = (long)BitOperations.LeadingZeroCount((ulong)payload);
+ return (currentStartOffset << 3) + lzcnt;
+ }
+ currentStartOffset += 8;
+ }
+ else if (remainder >= 4)
+ {
+ var payload = *(int*)(input + currentStartOffset);
+ payload = BinaryPrimitives.ReverseEndianness(payload);
+
+ // Process only if payload is valid (i.e. not all bits are set or clear based on searchFor parameter)
+ if (payload != invalidMask32)
+ {
+ // Transform to count leading zeros
+ payload = searchBit ? payload : ~payload;
+ var lzcnt = (long)BitOperations.LeadingZeroCount((uint)payload);
+ return (currentStartOffset << 3) + lzcnt;
+ }
+ currentStartOffset += 4;
+ }
+ else
+ {
+ // Process only if payload is valid (i.e. not all bits are set or clear based on searchFor parameter)
+ if (input[currentStartOffset] != invalidMask8)
+ {
+ // Create a payload with the current byte shifted to the most significant byte position
+ var payload = (long)input[currentStartOffset] << 56;
+ // Transform to count leading zeros
+ payload = searchBit ? payload : ~payload;
+ var lzcnt = (long)BitOperations.LeadingZeroCount((ulong)payload);
+ return (currentStartOffset << 3) + lzcnt;
+ }
+ currentStartOffset++;
+ }
+ }
- return pos;
+ // Return -1 if no matching bit is found
+ return -1;
}
}
}
\ No newline at end of file
diff --git a/libs/server/Storage/Functions/MainStore/PrivateMethods.cs b/libs/server/Storage/Functions/MainStore/PrivateMethods.cs
index b3fcf728ee..625bff7bf8 100644
--- a/libs/server/Storage/Functions/MainStore/PrivateMethods.cs
+++ b/libs/server/Storage/Functions/MainStore/PrivateMethods.cs
@@ -184,8 +184,14 @@ void CopyRespToWithInput(ref RawStringInput input, ref SpanByte value, ref SpanB
}
}
- var pos = BitmapManager.BitPosDriver(bpSetVal, bpStartOffset, bpEndOffset, bpOffsetType,
- value.ToPointer() + functionsState.etagState.etagSkippedStart, value.Length - functionsState.etagState.etagSkippedStart);
+ var pos = BitmapManager.BitPosDriver(
+ input: value.ToPointer() + functionsState.etagState.etagSkippedStart,
+ inputLen: value.Length - functionsState.etagState.etagSkippedStart,
+ startOffset: bpStartOffset,
+ endOffset: bpEndOffset,
+ searchFor: bpSetVal,
+ offsetType: bpOffsetType
+ );
*(long*)dst.SpanByte.ToPointer() = pos;
CopyRespNumber(pos, ref dst);
break;
diff --git a/test/Garnet.test/GarnetBitmapTests.cs b/test/Garnet.test/GarnetBitmapTests.cs
index 627a4984d4..9ce31136fb 100644
--- a/test/Garnet.test/GarnetBitmapTests.cs
+++ b/test/Garnet.test/GarnetBitmapTests.cs
@@ -501,8 +501,8 @@ public unsafe void BitmapSimpleBITCOUNT_PCT(int bytesPerSend)
private static unsafe long Bitpos(byte[] bitmap, int startOffset = 0, int endOffset = -1, bool set = true)
{
long pos = 0;
- int start = startOffset < 0 ? (startOffset % bitmap.Length) + bitmap.Length : startOffset;
- int end = endOffset < 0 ? (endOffset % bitmap.Length) + bitmap.Length : endOffset;
+ var start = startOffset < 0 ? (startOffset % bitmap.Length) + bitmap.Length : startOffset;
+ var end = endOffset < 0 ? (endOffset % bitmap.Length) + bitmap.Length : endOffset;
if (start >= bitmap.Length) // If startOffset greater that valLen alway bitcount zero
return -1;
@@ -510,24 +510,27 @@ private static unsafe long Bitpos(byte[] bitmap, int startOffset = 0, int endOff
if (start > end) // If start offset beyond endOffset return 0
return -1;
- byte mask = (byte)(!set ? 0xFF : 0x00);
+ var mask = (byte)(!set ? 0xFF : 0x00);
+ var setbit = set ? 1 : 0;
fixed (byte* b = bitmap)
{
- byte* curr = b + start;
- byte* vend = b + end + 1;
+ var curr = b + start;
+ var vend = b + end + 1;
while (curr < vend)
{
if (*curr != mask) break;
curr++;
}
+
+ if (curr > vend) return -1;
+
pos = (curr - b) << 3;
- byte byteVal = *curr;
- byte bitv = (byte)(!set ? 0x0 : 0x1);
- int bit = 7;
- while (((byteVal >> bit) & 0x1) != bitv && bit > 0)
+ var value = *curr;
+ for (var i = 7; i >= 0; i--)
{
- bit--;
+ if (((value & (1 << i)) >> i) == setbit)
+ return pos;
pos++;
}
}
@@ -542,47 +545,49 @@ public void BitmapSimpleBitPosTests()
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);
- string key = "SimpleBitPosTests";
+ var key = "SimpleBitPosTests";
byte[] buf;
- int maxBitmapLen = 1 << 10;
- int iter = 256;
+ var maxBitmapLen = 1 << 10;
+ var iter = 256;
long maxOffset = 0;
- for (int i = 0; i < iter; i++)
+ for (var i = 0; i < iter; i++)
{
long offset = r.Next(1, maxBitmapLen);
- db.StringSetBit(key, offset, true);
+ _ = db.StringSetBit(key, offset, true);
+ buf = db.StringGet(key);
- long offsetPos = db.StringBitPosition(key, true);
- ClassicAssert.AreEqual(offset, offsetPos);
+ var offsetPos = db.StringBitPosition(key, true);
+ ClassicAssert.AreEqual(offset, offsetPos, $"iter:{i}");
buf = db.StringGet(key);
- long expectedPos = Bitpos(buf, set: true);
- ClassicAssert.AreEqual(expectedPos, offsetPos);
+ var expectedPos = Bitpos(buf, set: true);
+ ClassicAssert.AreEqual(expectedPos, offsetPos, $"iter:{i}");
- db.StringSetBit(key, offset, false);
+ _ = db.StringSetBit(key, offset, false);
maxOffset = Math.Max(maxOffset, offset);
}
- for (int i = 0; i < maxOffset; i++)
- db.StringSetBit(key, i, true);
+ for (var i = 0; i < maxOffset; i++)
+ _ = db.StringSetBit(key, i, true);
- long count = db.StringBitCount(key);
+ var count = db.StringBitCount(key);
ClassicAssert.AreEqual(count, maxOffset);
- for (int i = 0; i < iter; i++)
+ for (var i = 0; i < iter; i++)
{
long offset = r.Next(1, (int)maxOffset);
- db.StringSetBit(key, offset, false);
+ _ = db.StringSetBit(key, offset, false);
- long offsetPos = db.StringBitPosition(key, false);
- ClassicAssert.AreEqual(offset, offsetPos);
+ buf = db.StringGet(key);
+ var offsetPos = db.StringBitPosition(key, false);
+ ClassicAssert.AreEqual(offset, offsetPos, $"iter:{i}");
buf = db.StringGet(key);
- long expectedPos = Bitpos(buf, set: false);
- ClassicAssert.AreEqual(expectedPos, offsetPos);
+ var expectedPos = Bitpos(buf, set: false);
+ ClassicAssert.AreEqual(expectedPos, offsetPos, $"iter:{i}");
- db.StringSetBit(key, offset, true);
+ _ = db.StringSetBit(key, offset, true);
}
}
@@ -593,43 +598,53 @@ public void BitmapBitPosOffsetsTest()
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);
- string key = "BitmapBitPosNegativeOffsets";
+ var key = "BitmapBitPosNegativeOffsets";
- int maxBitmapLen = 1 << 12;
- int maxByteLen = maxBitmapLen >> 3;
- int iter = 1 << 5;
- byte[] buf = new byte[maxByteLen];
+ var maxBitmapLen = 1 << 12;
+ var maxByteLen = maxBitmapLen >> 3;
+ var iter = 1 << 5;
+ var buf = new byte[maxByteLen];
long expectedPos;
long pos;
- for (int j = 0; j < iter; j++)
+ for (var j = 0; j < iter; j++)
{
r.NextBytes(buf);
- db.StringSet(key, buf);
+ _ = db.StringSet(key, buf);
- int startOffset = r.Next(0, maxByteLen);
- int endOffset = r.Next(startOffset, maxByteLen);
+ var startOffset = r.Next(0, maxByteLen);
+ var endOffset = r.Next(startOffset, maxByteLen);
- bool set = r.Next(0, 1) == 0 ? false : true;
+ var set = r.Next(0, 1) == 0 ? false : true;
expectedPos = Bitpos(buf, startOffset, endOffset, set);
pos = db.StringBitPosition(key, set, startOffset, endOffset);
- ClassicAssert.AreEqual(expectedPos, pos, $"{set} {startOffset} {endOffset}");
+ ClassicAssert.AreEqual(expectedPos, pos, $"{j} {set} {startOffset} {endOffset}");
+
+ var startBitOffset = startOffset << 3;
+ var endBitOffset = endOffset << 3;
+ pos = db.StringBitPosition(key, set, startBitOffset, endBitOffset, StringIndexType.Bit);
+ ClassicAssert.AreEqual(expectedPos, pos, $"{j} {set} {startBitOffset} {endBitOffset} bit");
}
- //check negative offsets in range
- for (int j = 0; j < iter; j++)
+ // check negative offsets in range
+ for (var j = 0; j < iter; j++)
{
r.NextBytes(buf);
- db.StringSet(key, buf);
+ _ = db.StringSet(key, buf);
- int startOffset = j == 0 ? -10 : r.Next(-maxByteLen, 0);
- int endOffset = j == 0 ? -1 : r.Next(startOffset, 0);
+ var startOffset = j == 0 ? -10 : r.Next(-maxByteLen, 0);
+ var endOffset = j == 0 ? -1 : r.Next(startOffset, 0);
+
+ var set = r.Next(0, 1) != 0;
+ expectedPos = Bitpos(buf, startOffset, endOffset, set);
+ pos = db.StringBitPosition(key, set, startOffset, endOffset);
+ ClassicAssert.AreEqual(expectedPos, pos, $"{j} {set} {startOffset} {endOffset}");
- bool set = r.Next(0, 1) == 0 ? false : true;
- pos = Bitpos(buf, startOffset, endOffset, set);
- expectedPos = db.StringBitPosition(key, set, startOffset, endOffset);
- ClassicAssert.AreEqual(pos, expectedPos);
+ var startBitOffset = startOffset << 3;
+ var endBitOffset = endOffset << 3;
+ pos = db.StringBitPosition(key, set, startBitOffset, endBitOffset, StringIndexType.Bit);
+ ClassicAssert.AreEqual(expectedPos, pos, $"{j} {set} {startBitOffset} {endBitOffset} bit");
}
}
@@ -637,7 +652,7 @@ public void BitmapBitPosOffsetsTest()
[Category("BITPOS")]
public void BitmapBitPosTest_LTM()
{
- int bitmapBytes = 512;
+ var bitmapBytes = 512;
server.Dispose();
server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir,
lowMemory: true,
@@ -647,26 +662,26 @@ public void BitmapBitPosTest_LTM()
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);
- int keyCount = 64;
- byte[] bitmap = new byte[bitmapBytes];
+ var keyCount = 64;
+ var bitmap = new byte[bitmapBytes];
List bitmapList = [];
- for (int i = 0; i < keyCount; i++)
+ for (var i = 0; i < keyCount; i++)
{
- string sKey = i.ToString();
+ var sKey = i.ToString();
r.NextBytes(bitmap);
bitmapList.Add(Bitpos(bitmap, set: true));
- db.StringSet(sKey, bitmap);
+ _ = db.StringSet(sKey, bitmap);
}
- int iter = 128;
- for (int i = 0; i < iter; i++)
+ var iter = 128;
+ for (var i = 0; i < iter; i++)
{
- int key = r.Next(0, keyCount);
- string sKey = key.ToString();
- long pos = db.StringBitPosition(sKey, true);
- long expectedPos = bitmapList[key];
+ var key = r.Next(0, keyCount);
+ var sKey = key.ToString();
+ var pos = db.StringBitPosition(sKey, true);
+ var expectedPos = bitmapList[key];
ClassicAssert.AreEqual(expectedPos, pos);
}
}
@@ -704,15 +719,15 @@ public unsafe void BitmapSimpleBITPOS_PCT(int bytesPerSend)
using var lightClientRequest = TestUtils.CreateRequest();
var db = redis.GetDatabase(0);
- string key = "mykey";
- int maxBitmapLen = 1 << 12;
- byte[] buf = new byte[maxBitmapLen >> 3];
+ var key = "mykey";
+ var maxBitmapLen = 1 << 12;
+ var buf = new byte[maxBitmapLen >> 3];
r.NextBytes(buf);
db.StringSet(key, buf);
- long expectedPos = Bitpos(buf);
+ var expectedPos = Bitpos(buf);
long pos = 0;
- byte[] response = lightClientRequest.SendCommandChunks("BITPOS mykey 1", bytesPerSend);
+ var response = lightClientRequest.SendCommandChunks("BITPOS mykey 1", bytesPerSend);
pos = ResponseToLong(response, 1);
ClassicAssert.AreEqual(expectedPos, pos);
}
@@ -2267,24 +2282,27 @@ public void BitmapBitPosFixedTests()
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);
- string key = "mykey";
- byte[] value = [0x0, 0xff, 0xf0];
+ var key = "mykey";
+ byte[] value = [0x00, 0xff, 0xf0];
db.StringSet(key, value);
- long pos = db.StringBitPosition(key, true, 0);
+ var pos = db.StringBitPosition(key, true, 0);
ClassicAssert.AreEqual(8, pos);
pos = db.StringBitPosition(key, true, 2, -1, StringIndexType.Byte);
ClassicAssert.AreEqual(16, pos);
pos = db.StringBitPosition(key, true, 0, 0, StringIndexType.Byte);
- ClassicAssert.AreEqual(0, pos);
+ ClassicAssert.AreEqual(-1, pos);
pos = db.StringBitPosition(key, false, 0, 0, StringIndexType.Byte);
ClassicAssert.AreEqual(0, pos);
+ pos = db.StringBitPosition(key, true, 7, 15, StringIndexType.Bit);
+ ClassicAssert.AreEqual(8, pos);
+
value = [0xf8, 0x6f, 0xf0];
- db.StringSet(key, value);
+ _ = db.StringSet(key, value);
pos = db.StringBitPosition(key, true, 5, 17, StringIndexType.Bit);
ClassicAssert.AreEqual(9, pos);
@@ -2295,12 +2313,17 @@ public void BitmapBitPosFixedTests()
ClassicAssert.AreEqual(-1, pos);
key = "mykey2";
- db.StringSetBit(key, 63, false);
+ _ = db.StringSetBit(key, 63, false);
pos = db.StringBitPosition(key, false, 1);
ClassicAssert.AreEqual(8, pos);
pos = db.StringBitPosition(key, false, 0);
ClassicAssert.AreEqual(0, pos);
+
+ value = [0xff, 0x7f, 0xf0];
+ _ = db.StringSet(key, value);
+ pos = db.StringBitPosition(key, false, 7, 15, StringIndexType.Bit);
+ ClassicAssert.AreEqual(8, pos);
}
[Test, Order(35)]
@@ -2357,5 +2380,111 @@ public void BitmapOperationTooManyKeys()
ClassicAssert.AreEqual("ERR Bitop source key limit (64) exceeded", ex.Message);
}
}
+
+ [Test, Order(38)]
+ [Category("BITPOS")]
+ public void BitmapBitPosBitOffsetTests([Values] bool searchFor)
+ {
+ using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
+ var db = redis.GetDatabase(0);
+
+ var key = "mykey";
+ byte[] value = searchFor ?
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] :
+ [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
+ _ = db.StringSet(key, value);
+
+ var bitLength = value.Length * 8;
+ var expectedPosOffset = 5;
+
+ for (var i = 0; i < 10; i++)
+ {
+ // Set or clear bit
+ _ = db.StringSetBit(key, offset: expectedPosOffset, bit: searchFor);
+
+ // Find pos of bit set/clear
+ var pos = db.StringBitPosition(key, bit: searchFor, 0, 19, StringIndexType.Bit);
+ ClassicAssert.AreEqual(expectedPosOffset, pos);
+
+ // Toggle bit back to initial value
+ _ = db.StringSetBit(key, offset: expectedPosOffset, bit: !searchFor);
+
+ expectedPosOffset++;
+ }
+ }
+
+ [Test, Order(38)]
+ [Category("BITPOS")]
+ public void BitmapBitPosBitInvalidMaskTests()
+ {
+ using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
+ var db = redis.GetDatabase(0);
+
+ var key = "mykey";
+ // 0x3e = 00111110
+ byte[] value = [0x3e];
+ _ = db.StringSet(key, value);
+
+ // 0x3e = 00111110
+ var pos = db.StringBitPosition(key, bit: false, start: 0, end: 5, StringIndexType.Bit);
+ ClassicAssert.AreEqual(0, pos);
+
+ pos = db.StringBitPosition(key, bit: false, start: 1, end: 5, StringIndexType.Bit);
+ ClassicAssert.AreEqual(1, pos);
+
+ pos = db.StringBitPosition(key, bit: false, start: 2, end: 5, StringIndexType.Bit);
+ ClassicAssert.AreEqual(-1, pos);
+
+ pos = db.StringBitPosition(key, bit: false, start: 2, end: 6, StringIndexType.Bit);
+ ClassicAssert.AreEqual(-1, pos);
+
+ pos = db.StringBitPosition(key, bit: false, start: 2, end: 7, StringIndexType.Bit);
+ ClassicAssert.AreEqual(7, pos);
+
+ // 0x7e02 = 0111111000000010
+ value = [0x7e, 0x02];
+ _ = db.StringSet(key, value);
+ pos = db.StringBitPosition(key, bit: true, start: 7, end: 13, StringIndexType.Bit);
+ ClassicAssert.AreEqual(-1, pos);
+
+ pos = db.StringBitPosition(key, bit: true, start: 7, end: 14, StringIndexType.Bit);
+ ClassicAssert.AreEqual(14, pos);
+ }
+
+ [Test, Order(39)]
+ [Category("BITPOS")]
+ public void BitmapBitPosBitSearchSingleBitRangeTests()
+ {
+ using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
+ var db = redis.GetDatabase(0);
+
+ var key = "mykey";
+ var valueLen = 1 << 12;
+ var value = new byte[valueLen];
+ for (var i = 0; i < valueLen; i++)
+ value[i] = 0xAA;
+
+ _ = db.StringSet(key, value);
+
+ var iter = 1 << 12;
+ var valueLenBits = valueLen << 3;
+ for (var i = 0; i < iter; i++)
+ {
+ var offset = r.NextInt64(0, valueLenBits);
+ BitSearch(offset, searchFor: true);
+ BitSearch(offset, searchFor: false);
+ }
+
+ void BitSearch(long offset, bool searchFor)
+ {
+ var pos = db.StringBitPosition(key, bit: searchFor, start: offset, end: offset, StringIndexType.Bit);
+ var equalsSearchFor = (offset & 0x1) == (searchFor ? 0 : 1);
+
+ if (equalsSearchFor)
+ ClassicAssert.AreEqual(offset, pos);
+ else
+ ClassicAssert.AreEqual(-1, pos);
+ }
+ }
}
}
\ No newline at end of file