Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic implementation of HELLO #398

Merged
merged 18 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.5.1" />
<PackageVersion Include="Microsoft.IdentityModel.Validators" Version="7.5.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.6.80" />
<PackageVersion Include="StackExchange.Redis" Version="2.7.33" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
<PackageVersion Include="System.Interactive.Async" Version="6.0.1" />
</ItemGroup>
Expand Down
34 changes: 34 additions & 0 deletions libs/common/ConvertUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

using System;
using System.Diagnostics;

namespace Garnet.common
{
Expand Down Expand Up @@ -53,5 +54,38 @@ public static void MakeUpperCase(Span<byte> command)
if (c > 96 && c < 123)
c -= 32;
}

/// <summary>
/// Check if two byte spans are equal, where right is an all-upper-case span, ignoring case if there are ASCII bytes.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool EqualsUpperCaseSpanIgnoringCase(this ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
{
if (left.SequenceEqual(right))
badrishc marked this conversation as resolved.
Show resolved Hide resolved
return true;
if (left.Length != right.Length)
return false;
for (int i = 0; i < left.Length; i++)
{
var b1 = left[i];
var b2 = right[i];

// Debug assert that b2 is an upper case letter 'A'-'Z'
Debug.Assert(b2 is >= 65 and <= 90);

if (b1 == b2 || b1 - 32 == b2)
continue;
return false;
}
return true;
}

/// <summary>
/// Check if two byte spans are equal, where right is an all-upper-case span, ignoring case if there are ASCII bytes.
/// </summary>
public static bool EqualsUpperCaseSpanIgnoringCase(this Span<byte> left, ReadOnlySpan<byte> right)
=> EqualsUpperCaseSpanIgnoringCase((ReadOnlySpan<byte>)left, right);
}
}
15 changes: 15 additions & 0 deletions libs/common/RespWriteUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ namespace Garnet.common
/// </summary>
public static unsafe class RespWriteUtils
{
/// <summary>
/// Write map length
/// </summary>
public static bool WriteMapLength(int len, ref byte* curr, byte* end)
{
int numDigits = NumUtils.NumDigits(len);
int totalLen = 1 + numDigits + 2;
if (totalLen > (int)(end - curr))
return false;
*curr++ = (byte)'%';
NumUtils.IntToBytes(len, numDigits, ref curr);
WriteNewline(ref curr);
return true;
}

/// <summary>
/// Write array length
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions libs/server/Metrics/Info/GarnetInfoMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ private void PopulateServerInfo(StoreWrapper storeWrapper)
serverInfo =
[
new("garnet_version", storeWrapper.version),
new("garnet_mode", storeWrapper.serverOptions.EnableCluster ? "cluster" : "standalone"),
new("os", Environment.OSVersion.ToString()),
new("processor_count", Environment.ProcessorCount.ToString()),
new("arch_bits", Environment.Is64BitProcess ? "64" : "32"),
Expand All @@ -56,7 +55,8 @@ private void PopulateServerInfo(StoreWrapper storeWrapper)
new("monitor_freq", storeWrapper.serverOptions.MetricsSamplingFrequency.ToString()),
new("latency_monitor", storeWrapper.serverOptions.LatencyMonitor ? "enabled" : "disabled"),
new("run_id", storeWrapper.run_id),
new("redis_version", storeWrapper.redisProtocolVersion)
new("redis_version", storeWrapper.redisProtocolVersion),
new("redis_mode", storeWrapper.serverOptions.EnableCluster ? "cluster" : "standalone"),
];
}

Expand Down
155 changes: 151 additions & 4 deletions libs/server/Resp/AdminCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ private bool ProcessAdminCommands<TGarnetApi>(RespCommand command, ReadOnlySpan<
{
if (username.IsEmpty)
{
while (!RespWriteUtils.WriteError("WRONGPASS Invalid password"u8, ref dcurr, dend))
while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend))
SendAndReset();
}
else
{
while (!RespWriteUtils.WriteError("WRONGPASS Invalid username/password combination"u8, ref dcurr, dend))
while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend))
SendAndReset();
}
}
Expand Down Expand Up @@ -250,8 +250,16 @@ private bool ProcessAdminCommands<TGarnetApi>(RespCommand command, ReadOnlySpan<
{
badrishc marked this conversation as resolved.
Show resolved Hide resolved
if (count == 0)
{
while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_PONG, ref dcurr, dend))
SendAndReset();
if (isSubscriptionSession && respProtocolVersion == 2)
{
while (!RespWriteUtils.WriteDirect(CmdStrings.SUSCRIBE_PONG, ref dcurr, dend))
SendAndReset();
}
else
{
while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_PONG, ref dcurr, dend))
SendAndReset();
}
}
else if (count == 1)
{
Expand All @@ -270,6 +278,74 @@ private bool ProcessAdminCommands<TGarnetApi>(RespCommand command, ReadOnlySpan<
errorCmd = "ping";
}
}
else if (command == RespCommand.HELLO)
badrishc marked this conversation as resolved.
Show resolved Hide resolved
badrishc marked this conversation as resolved.
Show resolved Hide resolved
{
int? respProtocolVersion = null;
ReadOnlySpan<byte> authUsername = default, authPassword = default;
string clientName = null;

if (count > 0)
{
var ptr = recvBufferPtr + readHead;
int localRespProtocolVersion;
if (!RespReadUtils.ReadIntWithLengthHeader(out localRespProtocolVersion, ref ptr, recvBufferPtr + bytesRead))
return false;
readHead = (int)(ptr - recvBufferPtr);

respProtocolVersion = localRespProtocolVersion;
count--;
while (count > 0)
{
var param = GetCommand(bufSpan, out bool success1);
if (!success1) return false;
count--;
if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.AUTH))
{
if (count < 2)
{
if (!DrainCommands(bufSpan, count))
return false;
count = 0;
errorFlag = true;
errorCmd = nameof(RespCommand.HELLO);
break;
}
authUsername = GetCommand(bufSpan, out success1);
if (!success1) return false;
count--;
authPassword = GetCommand(bufSpan, out success1);
if (!success1) return false;
count--;
}
else if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SETNAME))
{
if (count < 1)
{
if (!DrainCommands(bufSpan, count))
return false;
count = 0;
errorFlag = true;
errorCmd = nameof(RespCommand.HELLO);
break;
}

var arg = GetCommand(bufSpan, out success1);
if (!success1) return false;
count--;
clientName = Encoding.ASCII.GetString(arg);
}
else
{
if (!DrainCommands(bufSpan, count))
return false;
count = 0;
errorFlag = true;
errorCmd = nameof(RespCommand.HELLO);
}
}
}
if (!errorFlag) ProcessHelloCommand(respProtocolVersion, authUsername, authPassword, clientName);
}
else if (command is RespCommand.CLUSTER or RespCommand.MIGRATE or RespCommand.FAILOVER or RespCommand.REPLICAOF or RespCommand.SECONDARYOF)
{
if (clusterSession == null)
Expand Down Expand Up @@ -488,6 +564,77 @@ private bool ProcessAdminCommands<TGarnetApi>(RespCommand command, ReadOnlySpan<
return true;
}

void ProcessHelloCommand(int? respProtocolVersion, ReadOnlySpan<byte> username, ReadOnlySpan<byte> password, string clientName)
{
if (respProtocolVersion != null)
{
if (respProtocolVersion.Value != 2)
{
while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION, ref dcurr, dend))
SendAndReset();
return;
}

this.respProtocolVersion = respProtocolVersion.Value;
}

if (username != default)
{
if (!this.AuthenticateUser(username, password))
{
if (username.IsEmpty)
{
while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend))
SendAndReset();
}
else
{
while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend))
SendAndReset();
}
return;
}
}

if (clientName != null)
{
this.clientName = clientName;
}

(string, string)[] helloResult =
[
("server", "redis"),
badrishc marked this conversation as resolved.
Show resolved Hide resolved
("version", storeWrapper.redisProtocolVersion),
("garnet_version", storeWrapper.version),
("proto", $"{this.respProtocolVersion}"),
("id", "63"),
badrishc marked this conversation as resolved.
Show resolved Hide resolved
("mode", storeWrapper.serverOptions.EnableCluster ? "cluster" : "standalone"),
("role", storeWrapper.serverOptions.EnableCluster && storeWrapper.clusterProvider.IsReplica() ? "replica" : "master"),
];

if (this.respProtocolVersion == 2)
{
while (!RespWriteUtils.WriteArrayLength(helloResult.Length * 2 + 2, ref dcurr, dend))
SendAndReset();
}
else
{
while (!RespWriteUtils.WriteMapLength(helloResult.Length + 1, ref dcurr, dend))
SendAndReset();
}
for (int i = 0; i < helloResult.Length; i++)
{
while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item1, ref dcurr, dend))
SendAndReset();
while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item2, ref dcurr, dend))
SendAndReset();
}
while (!RespWriteUtils.WriteAsciiBulkString("modules", ref dcurr, dend))
SendAndReset();
while (!RespWriteUtils.WriteArrayLength(0, ref dcurr, dend))
SendAndReset();
}

/// <summary>
/// Performs @admin command group permission checks for the current user and the given command.
/// (NOTE: This function is temporary until per-command permissions are implemented)
Expand Down
12 changes: 10 additions & 2 deletions libs/server/Resp/BasicCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,16 @@ private bool NetworkAppend<TGarnetApi>(byte* ptr, ref TGarnetApi storageApi)
/// </summary>
private bool NetworkPING()
{
while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_PONG, ref dcurr, dend))
SendAndReset();
if (isSubscriptionSession && respProtocolVersion == 2)
{
while (!RespWriteUtils.WriteDirect(CmdStrings.SUSCRIBE_PONG, ref dcurr, dend))
SendAndReset();
}
else
{
while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_PONG, ref dcurr, dend))
SendAndReset();
}
return true;
}

Expand Down
9 changes: 8 additions & 1 deletion libs/server/Resp/CmdStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> ACL => "ACL"u8;
public static ReadOnlySpan<byte> AUTH => "AUTH"u8;
public static ReadOnlySpan<byte> auth => "auth"u8;
public static ReadOnlySpan<byte> SETNAME => "SETNAME"u8;
public static ReadOnlySpan<byte> INFO => "INFO"u8;
public static ReadOnlySpan<byte> info => "info"u8;
public static ReadOnlySpan<byte> DOCS => "DOCS"u8;
Expand All @@ -47,6 +48,7 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> HELP => "HELP"u8;
public static ReadOnlySpan<byte> help => "help"u8;
public static ReadOnlySpan<byte> PING => "PING"u8;
public static ReadOnlySpan<byte> HELLO => "HELLO"u8;
public static ReadOnlySpan<byte> TIME => "TIME"u8;
public static ReadOnlySpan<byte> RESET => "RESET"u8;
public static ReadOnlySpan<byte> reset => "reset"u8;
Expand Down Expand Up @@ -80,12 +82,13 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> RESP_RETURN_VAL_1 => ":1\r\n"u8;
public static ReadOnlySpan<byte> RESP_RETURN_VAL_0 => ":0\r\n"u8;
public static ReadOnlySpan<byte> RESP_RETURN_VAL_N1 => ":-1\r\n"u8;
public static ReadOnlySpan<byte> SUSCRIBE_PONG => "*2\r\n$4\r\npong\r\n$0\r\n\r\n"u8;
public static ReadOnlySpan<byte> RESP_PONG => "+PONG\r\n"u8;
public static ReadOnlySpan<byte> RESP_EMPTY => "$0\r\n\r\n"u8;
public static ReadOnlySpan<byte> RESP_QUEUED => "+QUEUED\r\n"u8;

/// <summary>
/// Simple error respone strings, i.e. these are of the form "-errorString\r\n"
/// Simple error response strings, i.e. these are of the form "-errorString\r\n"
/// </summary>
public static ReadOnlySpan<byte> RESP_ERR_NOAUTH => "NOAUTH Authentication required."u8;
public static ReadOnlySpan<byte> RESP_ERR_WRONG_TYPE => "WRONGTYPE Operation against a key holding the wrong kind of value."u8;
Expand Down Expand Up @@ -119,6 +122,10 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> RESP_ERR_GENERIC_INDEX_OUT_RANGE => "ERR index out of range"u8;
public static ReadOnlySpan<byte> RESP_ERR_GENERIC_SELECT_INVALID_INDEX => "ERR invalid database index."u8;
public static ReadOnlySpan<byte> RESP_ERR_GENERIC_SELECT_CLUSTER_MODE => "ERR SELECT is not allowed in cluster mode"u8;
public static ReadOnlySpan<byte> RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION => "ERR Unsupported protocol version"u8;
public static ReadOnlySpan<byte> RESP_WRONGPASS_INVALID_PASSWORD => "WRONGPASS Invalid password"u8;
public static ReadOnlySpan<byte> RESP_WRONGPASS_INVALID_USERNAME_PASSWORD => "WRONGPASS Invalid username/password combination"u8;


/// <summary>
/// Response string templates
Expand Down
5 changes: 5 additions & 0 deletions libs/server/Resp/RespCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public enum RespCommand : byte
FORCEGC = 0x3B,
FAILOVER = 0x3C,
ACL = 0x3D,
HELLO = 0x3E,

// Custom commands
CustomTxn = 0x29,
Expand Down Expand Up @@ -1040,6 +1041,10 @@ static RespCommand MatchedNone(RespServerSession session, int oldReadHead)
{
return (RespCommand.PING, 0);
}
else if (command.SequenceEqual(CmdStrings.HELLO))
{
return (RespCommand.HELLO, 0);
}
else if (command.SequenceEqual(CmdStrings.CLUSTER))
{
return (RespCommand.CLUSTER, 0);
Expand Down
15 changes: 15 additions & 0 deletions libs/server/Resp/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,21 @@
],
"SubCommands": null
},
{
"Command": "HELLO",
"ArrayCommand": null,
"Name": "HELLO",
"IsInternal": false,
"Arity": -1,
"Flags": "Fast, Loading, NoAuth, NoScript, Stale, AllowBusy",
"FirstKey": 0,
"LastKey": 0,
"Step": 0,
"AclCategories": "Connection, Fast",
"Tips": null,
"KeySpecifications": null,
"SubCommands": null
},
{
"Command": "Hash",
"ArrayCommand": 7,
Expand Down
4 changes: 4 additions & 0 deletions libs/server/Resp/RespServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase
/// </summary>
CustomObjectCommand currentCustomObjectCommand = null;


int respProtocolVersion = 2;
string clientName = null;

public RespServerSession(
INetworkSender networkSender,
StoreWrapper storeWrapper,
Expand Down
Loading
Loading