diff --git a/README.md b/README.md
index 9211605..3531134 100644
--- a/README.md
+++ b/README.md
@@ -29,10 +29,10 @@ Valkey General Language Independent Driver for the Enterprise (GLIDE) is the off
Valkey GLIDE for C# is API-compatible with the following engine versions:
-| Engine Type | 6.2 | 7.0 | 7.1 | 7.2 | 8.0 | 8.1 |
-|-----------------------|-------|-------|--------|-------|-------|-------|
-| Valkey | - | - | - | V | V | V |
-| Redis | V | V | V | V | - | - |
+| Engine Type | 6.2 | 7.0 | 7.1 | 7.2 | 8.0 | 8.1 | 9.0 |
+|-----------------------|-------|-------|--------|-------|-------|-------|-------|
+| Valkey | - | - | - | V | V | V | V |
+| Redis | V | V | V | V | - | - | - |
## Installation
diff --git a/sources/Valkey.Glide/BaseClient.BitmapCommands.cs b/sources/Valkey.Glide/BaseClient.BitmapCommands.cs
new file mode 100644
index 0000000..aa9beec
--- /dev/null
+++ b/sources/Valkey.Glide/BaseClient.BitmapCommands.cs
@@ -0,0 +1,77 @@
+// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
+
+using Valkey.Glide.Commands;
+using Valkey.Glide.Commands.Options;
+using Valkey.Glide.Internals;
+
+namespace Valkey.Glide;
+
+public abstract partial class BaseClient : IBitmapCommands
+{
+ ///
+ public async Task StringGetBitAsync(ValkeyKey key, long offset, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.GetBitAsync(key, offset));
+ }
+
+ ///
+ public async Task StringSetBitAsync(ValkeyKey key, long offset, bool value, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.SetBitAsync(key, offset, value));
+ }
+
+ ///
+ public async Task StringBitCountAsync(ValkeyKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.BitCountAsync(key, start, end, indexType));
+ }
+
+ ///
+ public async Task StringBitPositionAsync(ValkeyKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.BitPositionAsync(key, bit, start, end, indexType));
+ }
+
+ ///
+ public async Task StringBitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey first, ValkeyKey second, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.BitOperationAsync(operation, destination, first, second));
+ }
+
+ ///
+ public async Task StringBitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey[] keys, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.BitOperationAsync(operation, destination, keys));
+ }
+
+ ///
+ public async Task StringBitFieldAsync(ValkeyKey key, BitFieldOptions.IBitFieldSubCommand[] subCommands, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+
+ // Check if all subcommands are read-only (GET operations)
+ bool allReadOnly = subCommands.All(cmd => cmd is BitFieldOptions.IBitFieldReadOnlySubCommand);
+
+ if (allReadOnly)
+ {
+ // Convert to read-only subcommands and use BITFIELD_RO
+ var readOnlyCommands = subCommands.Cast().ToArray();
+ return await Command(Request.BitFieldReadOnlyAsync(key, readOnlyCommands));
+ }
+
+ return await Command(Request.BitFieldAsync(key, subCommands));
+ }
+
+ ///
+ public async Task StringBitFieldReadOnlyAsync(ValkeyKey key, BitFieldOptions.IBitFieldReadOnlySubCommand[] subCommands, CommandFlags flags = CommandFlags.None)
+ {
+ Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
+ return await Command(Request.BitFieldReadOnlyAsync(key, subCommands));
+ }
+}
diff --git a/sources/Valkey.Glide/Commands/IBitmapCommands.cs b/sources/Valkey.Glide/Commands/IBitmapCommands.cs
new file mode 100644
index 0000000..45829be
--- /dev/null
+++ b/sources/Valkey.Glide/Commands/IBitmapCommands.cs
@@ -0,0 +1,183 @@
+// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
+
+namespace Valkey.Glide.Commands;
+
+///
+/// Supports bitmap commands for standalone and cluster clients.
+///
+/// See more on valkey.io.
+///
+public interface IBitmapCommands
+{
+ ///
+ /// Returns the bit value at offset in the string value stored at key.
+ ///
+ ///
+ /// The key of the string.
+ /// The offset in the string to get the bit at.
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The bit value stored at offset. Returns 0 if the key does not exist or if the offset is beyond the string length.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("mykey", "A"); // ASCII 'A' is 01000001
+ /// bool bit = await client.StringGetBitAsync("mykey", 1);
+ /// Console.WriteLine(bit); // Output: true (bit 1 is set)
+ ///
+ ///
+ ///
+ Task StringGetBitAsync(ValkeyKey key, long offset, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Sets or clears the bit at offset in the string value stored at key.
+ ///
+ ///
+ /// The key of the string.
+ /// The offset in the string to set the bit at.
+ /// The bit value to set (true for 1, false for 0).
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The original bit value stored at offset. Returns false if the key does not exist or if the offset is beyond the string length.
+ ///
+ ///
+ ///
+ /// bool oldBit = await client.StringSetBitAsync("mykey", 1, true);
+ /// Console.WriteLine(oldBit); // Output: false (original bit value)
+ ///
+ ///
+ ///
+ Task StringSetBitAsync(ValkeyKey key, long offset, bool value, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Count the number of set bits in a string.
+ ///
+ ///
+ /// The key of the string.
+ /// The start offset.
+ /// The end offset.
+ /// The index type (bit or byte).
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The number of bits set to 1.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("mykey", "A"); // ASCII 'A' is 01000001
+ /// long count = await client.StringBitCountAsync("mykey");
+ /// Console.WriteLine(count); // Output: 2 (two bits set)
+ ///
+ ///
+ ///
+ Task StringBitCountAsync(ValkeyKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Return the position of the first bit set to 1 or 0 in a string.
+ ///
+ ///
+ /// The key of the string.
+ /// The bit value to search for (true for 1, false for 0).
+ /// The start offset.
+ /// The end offset.
+ /// The index type (bit or byte).
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The position of the first bit with the specified value, or -1 if not found.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("mykey", "A"); // ASCII 'A' is 01000001
+ /// long pos = await client.StringBitPositionAsync("mykey", true);
+ /// Console.WriteLine(pos); // Output: 1 (first set bit at position 1)
+ ///
+ ///
+ ///
+ Task StringBitPositionAsync(ValkeyKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Perform a bitwise operation between multiple keys and store the result in the destination key.
+ ///
+ ///
+ /// The bitwise operation to perform.
+ /// The key to store the result.
+ /// The first source key.
+ /// The second source key.
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The size of the string stored in the destination key.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("key1", "A");
+ /// await client.StringSetAsync("key2", "B");
+ /// long size = await client.StringBitOperationAsync(Bitwise.And, "result", "key1", "key2");
+ /// Console.WriteLine(size); // Output: 1 (size of result)
+ ///
+ ///
+ ///
+ Task StringBitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey first, ValkeyKey second, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Perform a bitwise operation between multiple keys and store the result in the destination key.
+ ///
+ ///
+ /// The bitwise operation to perform.
+ /// The key to store the result.
+ /// The source keys.
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// The size of the string stored in the destination key.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("key1", "A");
+ /// await client.StringSetAsync("key2", "B");
+ /// await client.StringSetAsync("key3", "C");
+ /// long size = await client.StringBitOperationAsync(Bitwise.Or, "result", new ValkeyKey[] { "key1", "key2", "key3" });
+ /// Console.WriteLine(size); // Output: 1 (size of result)
+ ///
+ ///
+ ///
+ Task StringBitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey[] keys, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Reads or modifies the array of bits representing the string stored at key based on the specified subcommands.
+ ///
+ ///
+ /// The key of the string.
+ /// The subcommands to execute (GET, SET, INCRBY).
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// An array of results from the executed subcommands. Null responses from the server are converted to 0.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("mykey", "A"); // ASCII 'A' is 01000001
+ /// var subCommands = new IBitFieldSubCommand[] {
+ /// new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(8), 0),
+ /// new BitFieldOptions.BitFieldSet(BitFieldOptions.Encoding.Unsigned(8), 0, 66) // ASCII 'B'
+ /// };
+ /// long[] results = await client.StringBitFieldAsync("mykey", subCommands);
+ /// Console.WriteLine(results[0]); // Output: 65 (ASCII 'A')
+ /// Console.WriteLine(results[1]); // Output: 65 (old value)
+ ///
+ ///
+ ///
+ Task StringBitFieldAsync(ValkeyKey key, Commands.Options.BitFieldOptions.IBitFieldSubCommand[] subCommands, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Reads the array of bits representing the string stored at key based on the specified GET subcommands.
+ /// This is a read-only variant of BITFIELD.
+ ///
+ ///
+ /// The key of the string.
+ /// The GET subcommands to execute.
+ /// The flags to use for this operation. Currently flags are ignored.
+ /// An array of results from the executed GET subcommands. Null responses from the server are converted to 0.
+ ///
+ ///
+ ///
+ /// await client.StringSetAsync("mykey", "A"); // ASCII 'A' is 01000001
+ /// var subCommands = new IBitFieldReadOnlySubCommand[] {
+ /// new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(8), 0)
+ /// };
+ /// long[] results = await client.StringBitFieldReadOnlyAsync("mykey", subCommands);
+ /// Console.WriteLine(results[0]); // Output: 65 (ASCII 'A')
+ ///
+ ///
+ ///
+ Task StringBitFieldReadOnlyAsync(ValkeyKey key, Commands.Options.BitFieldOptions.IBitFieldReadOnlySubCommand[] subCommands, CommandFlags flags = CommandFlags.None);
+}
diff --git a/sources/Valkey.Glide/Commands/Options/BitFieldOptions.cs b/sources/Valkey.Glide/Commands/Options/BitFieldOptions.cs
new file mode 100644
index 0000000..1f63842
--- /dev/null
+++ b/sources/Valkey.Glide/Commands/Options/BitFieldOptions.cs
@@ -0,0 +1,136 @@
+// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
+
+namespace Valkey.Glide.Commands.Options;
+
+///
+/// Options for BITFIELD command operations.
+///
+public static class BitFieldOptions
+{
+ ///
+ /// Base interface for BitField subcommands.
+ ///
+ public interface IBitFieldSubCommand
+ {
+ ///
+ /// Converts the subcommand to its string arguments.
+ ///
+ /// Array of string arguments for the subcommand.
+ string[] ToArgs();
+ }
+
+ ///
+ /// Interface for read-only BitField subcommands.
+ ///
+ public interface IBitFieldReadOnlySubCommand : IBitFieldSubCommand
+ {
+ }
+
+ ///
+ /// Interface for bit field offsets.
+ ///
+ public interface IBitOffset
+ {
+ string GetOffset();
+ }
+
+ ///
+ /// Regular bit offset.
+ ///
+ /// The bit offset value.
+ public class BitOffset(long offset) : IBitOffset
+ {
+ public string GetOffset() => offset.ToString();
+ }
+
+ ///
+ /// Offset multiplied by encoding width (prefixed with #).
+ ///
+ /// The multiplier value.
+ public class BitOffsetMultiplier(long multiplier) : IBitOffset
+ {
+ public string GetOffset() => $"#{multiplier}";
+ }
+
+ ///
+ /// GET subcommand for reading bits from the string.
+ ///
+ /// The bit field encoding.
+ /// The bit field offset.
+ public class BitFieldGet(string encoding, IBitOffset offset) : IBitFieldReadOnlySubCommand
+ {
+ public string[] ToArgs() => ["GET", encoding, offset.GetOffset()];
+ }
+
+ ///
+ /// SET subcommand for setting bits in the string.
+ ///
+ /// The bit field encoding.
+ /// The bit field offset.
+ /// The value to set.
+ public class BitFieldSet(string encoding, IBitOffset offset, long value) : IBitFieldSubCommand
+ {
+ public string[] ToArgs() => ["SET", encoding, offset.GetOffset(), value.ToString()];
+ }
+
+ ///
+ /// INCRBY subcommand for incrementing bits in the string.
+ ///
+ /// The bit field encoding.
+ /// The bit field offset.
+ /// The increment value.
+ public class BitFieldIncrBy(string encoding, IBitOffset offset, long increment) : IBitFieldSubCommand
+ {
+ public string[] ToArgs() => ["INCRBY", encoding, offset.GetOffset(), increment.ToString()];
+ }
+
+ ///
+ /// OVERFLOW subcommand for controlling overflow behavior.
+ ///
+ /// The overflow behavior type.
+ public class BitFieldOverflow(OverflowType overflowType) : IBitFieldSubCommand
+ {
+ public string[] ToArgs() => ["OVERFLOW", overflowType.ToString().ToUpper()];
+ }
+
+ ///
+ /// Overflow behavior types for BitField operations.
+ ///
+ public enum OverflowType
+ {
+ ///
+ /// Wrap around on overflow (modulo arithmetic).
+ ///
+ Wrap,
+ ///
+ /// Saturate at min/max values on overflow.
+ ///
+ Sat,
+ ///
+ /// Return null on overflow.
+ ///
+ Fail
+ }
+
+ ///
+ /// Helper methods for creating common encodings.
+ ///
+ public static class Encoding
+ {
+ ///
+ /// Creates an unsigned encoding string.
+ ///
+ /// Number of bits (1-63).
+ /// Unsigned encoding string (e.g., "u8").
+ public static string Unsigned(int bits) => $"u{bits}";
+
+ ///
+ /// Creates a signed encoding string.
+ ///
+ /// Number of bits (1-64).
+ /// Signed encoding string (e.g., "i8").
+ public static string Signed(int bits) => $"i{bits}";
+ }
+
+
+}
diff --git a/sources/Valkey.Glide/GlideString.cs b/sources/Valkey.Glide/GlideString.cs
index 37f4773..ff2c72c 100644
--- a/sources/Valkey.Glide/GlideString.cs
+++ b/sources/Valkey.Glide/GlideString.cs
@@ -45,6 +45,13 @@ public static GlideString ToGlideString(this double @double)
: double.IsNaN(@double) ? new("nan")
: new(@double.ToString("G17", System.Globalization.CultureInfo.InvariantCulture));
+ ///
+ /// Convert a to a ("1" for true, "0" for false).
+ ///
+ /// A to convert.
+ /// A .
+ public static GlideString ToGlideString(this bool @bool) => new(@bool ? "1" : "0");
+
///
/// Convert a to a .
///
diff --git a/sources/Valkey.Glide/Internals/Request.BitmapCommands.cs b/sources/Valkey.Glide/Internals/Request.BitmapCommands.cs
new file mode 100644
index 0000000..b9c089f
--- /dev/null
+++ b/sources/Valkey.Glide/Internals/Request.BitmapCommands.cs
@@ -0,0 +1,71 @@
+// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
+
+using Valkey.Glide.Commands.Options;
+
+using static Valkey.Glide.Internals.FFI;
+
+namespace Valkey.Glide.Internals;
+
+internal partial class Request
+{
+ public static Cmd GetBitAsync(ValkeyKey key, long offset)
+ => new(RequestType.GetBit, [key.ToGlideString(), offset.ToGlideString()], false, response => response != 0);
+
+ public static Cmd SetBitAsync(ValkeyKey key, long offset, bool value)
+ => new(RequestType.SetBit, [key.ToGlideString(), offset.ToGlideString(), value.ToGlideString()], false, response => response != 0);
+
+ public static Cmd BitCountAsync(ValkeyKey key, long start, long end, StringIndexType indexType)
+ {
+ List args = [key.ToGlideString(), start.ToGlideString(), end.ToGlideString()];
+ if (indexType != StringIndexType.Byte)
+ {
+ args.Add(indexType.ToLiteral().ToGlideString());
+ }
+ return Simple(RequestType.BitCount, [.. args]);
+ }
+
+ public static Cmd BitPositionAsync(ValkeyKey key, bool bit, long start, long end, StringIndexType indexType)
+ {
+ List args = [key.ToGlideString(), bit.ToGlideString(), start.ToGlideString(), end.ToGlideString()];
+ if (indexType != StringIndexType.Byte)
+ {
+ args.Add(indexType.ToLiteral().ToGlideString());
+ }
+ return Simple(RequestType.BitPos, [.. args]);
+ }
+
+ public static Cmd BitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey first, ValkeyKey second)
+ {
+ GlideString[] args = [ValkeyLiterals.Get(operation).ToGlideString(), destination.ToGlideString(), first.ToGlideString(), second.ToGlideString()];
+ return Simple(RequestType.BitOp, args);
+ }
+
+ public static Cmd BitOperationAsync(Bitwise operation, ValkeyKey destination, ValkeyKey[] keys)
+ {
+ List args = [ValkeyLiterals.Get(operation).ToGlideString(), destination.ToGlideString()];
+ args.AddRange(keys.ToGlideStrings());
+ return Simple(RequestType.BitOp, [.. args]);
+ }
+
+ public static Cmd