aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--VNLib.Data.Caching.Extensions/ActiveServer.cs4
-rw-r--r--VNLib.Data.Caching.Extensions/BrokerRegistrationRequest.cs40
-rw-r--r--VNLib.Data.Caching.Extensions/ClientCacheConfiguration.cs50
-rw-r--r--VNLib.Data.Caching.Extensions/FBMDataCacheExtensions.cs172
-rw-r--r--VNLib.Data.Caching.Extensions/ListServerRequest.cs116
-rw-r--r--VNLib.Data.Caching/src/ClientExtensions.cs39
6 files changed, 270 insertions, 151 deletions
diff --git a/VNLib.Data.Caching.Extensions/ActiveServer.cs b/VNLib.Data.Caching.Extensions/ActiveServer.cs
index 1d56169..edaf812 100644
--- a/VNLib.Data.Caching.Extensions/ActiveServer.cs
+++ b/VNLib.Data.Caching.Extensions/ActiveServer.cs
@@ -34,5 +34,9 @@ namespace VNLib.Data.Caching.Extensions
public string? ServerId { get; set; }
[JsonPropertyName("ip_address")]
public string? Ip { get; set; }
+ ///<inheritdoc/>
+ public override int GetHashCode() => ServerId!.GetHashCode(StringComparison.OrdinalIgnoreCase);
+ ///<inheritdoc/>
+ public override bool Equals(object? obj) => obj is ActiveServer s && GetHashCode() == s.GetHashCode();
}
}
diff --git a/VNLib.Data.Caching.Extensions/BrokerRegistrationRequest.cs b/VNLib.Data.Caching.Extensions/BrokerRegistrationRequest.cs
index e874520..97108ae 100644
--- a/VNLib.Data.Caching.Extensions/BrokerRegistrationRequest.cs
+++ b/VNLib.Data.Caching.Extensions/BrokerRegistrationRequest.cs
@@ -23,8 +23,10 @@
*/
using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
using VNLib.Utils;
+using VNLib.Hashing.IdentityUtility;
namespace VNLib.Data.Caching.Extensions
@@ -35,6 +37,9 @@ namespace VNLib.Data.Caching.Extensions
/// </summary>
public sealed class BrokerRegistrationRequest : VnDisposeable
{
+ private bool ownsKey;
+ private ReadOnlyJsonWebKey? SigningKey;
+
/// <summary>
/// The cache server node id
/// </summary>
@@ -52,20 +57,19 @@ namespace VNLib.Data.Caching.Extensions
/// The address for remote clients to use to
/// connect to this server
/// </summary>
- public string? RegistrationAddress { get; private set; }
+ public string? RegistrationAddress { get; private set; }
+
/// <summary>
- /// The token signature algorithm
+ /// Recovers the private key from the supplied certificate
/// </summary>
- public ECDsa SiginingAlg { get; }
-
- public BrokerRegistrationRequest()
- {
- SiginingAlg = ECDsa.Create(FBMDataCacheExtensions.CacheCurve);
- }
-
- public BrokerRegistrationRequest WithPrivateKey(ReadOnlySpan<byte> privateKey)
+ /// <param name="jwk">The private key used to sign messages</param>
+ /// <param name="ownsKey">A value that indicates if the current instance owns the key</param>
+ /// <returns></returns>
+ /// <exception cref="ArgumentException"></exception>
+ public BrokerRegistrationRequest WithSigningKey(ReadOnlyJsonWebKey jwk, bool ownsKey)
{
- SiginingAlg.ImportPkcs8PrivateKey(privateKey, out _);
+ this.ownsKey = ownsKey;
+ SigningKey = jwk ?? throw new ArgumentNullException(nameof(jwk));
return this;
}
@@ -86,17 +90,27 @@ namespace VNLib.Data.Caching.Extensions
HeartbeatToken = token;
return this;
}
-
+
public BrokerRegistrationRequest WithNodeId(string nodeId)
{
NodeId = nodeId;
return this;
}
+ internal void SignJwt(JsonWebToken jwt)
+ {
+ jwt.SignFromJwk(SigningKey);
+ }
+
+ internal IReadOnlyDictionary<string, string?> JsonHeader => SigningKey!.JwtHeader;
+
///<inheritdoc/>
protected override void Free()
{
- SiginingAlg.Dispose();
+ if (ownsKey)
+ {
+ SigningKey?.Dispose();
+ }
}
}
}
diff --git a/VNLib.Data.Caching.Extensions/ClientCacheConfiguration.cs b/VNLib.Data.Caching.Extensions/ClientCacheConfiguration.cs
index 96f54a7..a9225a1 100644
--- a/VNLib.Data.Caching.Extensions/ClientCacheConfiguration.cs
+++ b/VNLib.Data.Caching.Extensions/ClientCacheConfiguration.cs
@@ -25,6 +25,7 @@
using System.Security.Cryptography;
using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
using VNLib.Net.Messaging.FBM.Client;
namespace VNLib.Data.Caching.Extensions
@@ -35,45 +36,47 @@ namespace VNLib.Data.Caching.Extensions
/// </summary>
public sealed class ClientCacheConfiguration
{
- internal ECDsa SigningKey { get; init; }
- internal ECDsa VerificationKey { get; init; }
- internal string ServerChallenge { get; init; }
+ internal ReadOnlyJsonWebKey? SigningKey { get; private set; }
+ internal ReadOnlyJsonWebKey? VerificationKey { get; private set; }
+ internal ReadOnlyJsonWebKey? BrokerVerificationKey { get; private set; }
+
+ internal string ServerChallenge { get; } = RandomHash.GetRandomBase32(24);
internal string? NodeId { get; set; }
internal Uri? BrokerAddress { get; set; }
internal bool UseTls { get; set; }
internal ActiveServer[]? CacheServers { get; set; }
- public ClientCacheConfiguration()
- {
- //Init the algorithms
- SigningKey = ECDsa.Create(FBMDataCacheExtensions.CacheCurve);
- VerificationKey = ECDsa.Create(FBMDataCacheExtensions.CacheCurve);
- ServerChallenge = RandomHash.GetRandomBase32(24);
- }
+ internal IReadOnlyDictionary<string, string?> JwtHeader => SigningKey!.JwtHeader;
/// <summary>
/// Imports the private key used to sign messages
/// </summary>
- /// <param name="pkcs8PrivKey">The pkcs8 encoded private key to sign messages</param>
+ /// <param name="jwk">The <see cref="ReadOnlyJsonWebKey"/> with a private key loaded</param>
/// <returns>Chainable fluent object</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="CryptographicException"></exception>
- public ClientCacheConfiguration ImportSigningKey(ReadOnlySpan<byte> pkcs8PrivKey)
+ public ClientCacheConfiguration WithSigningCertificate(ReadOnlyJsonWebKey jwk)
{
- SigningKey.ImportPkcs8PrivateKey(pkcs8PrivKey, out _);
+ SigningKey = jwk ?? throw new ArgumentNullException(nameof(jwk));
return this;
}
/// <summary>
/// Imports the public key used to verify messages from the remote server
/// </summary>
- /// <param name="spkiPublicKey">The subject-public-key-info formatted cache public key</param>
+ /// <param name="jwk">The <see cref="ReadOnlyJsonWebKey"/> public key only used for message verification</param>
/// <returns>Chainable fluent object</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="CryptographicException"></exception>
- public ClientCacheConfiguration ImportVerificationKey(ReadOnlySpan<byte> spkiPublicKey)
+ public ClientCacheConfiguration WithVerificationKey(ReadOnlyJsonWebKey jwk)
+ {
+ VerificationKey = jwk ?? throw new ArgumentNullException(nameof(jwk));
+ return this;
+ }
+
+ public ClientCacheConfiguration WithBrokerVerificationKey(ReadOnlyJsonWebKey jwk)
{
- VerificationKey.ImportSubjectPublicKeyInfo(spkiPublicKey, out _);
+ BrokerVerificationKey = jwk ?? throw new ArgumentNullException(nameof(jwk));
return this;
}
@@ -113,10 +116,19 @@ namespace VNLib.Data.Caching.Extensions
return this;
}
- ~ClientCacheConfiguration()
+ internal void SignJwt(JsonWebToken jwt)
+ {
+ jwt.SignFromJwk(SigningKey);
+ }
+
+ internal bool VerifyCache(JsonWebToken jwt)
+ {
+ return jwt.VerifyFromJwk(VerificationKey);
+ }
+
+ internal bool VerifyBroker(JsonWebToken jwt)
{
- SigningKey.Clear();
- VerificationKey.Clear();
+ return jwt.VerifyFromJwk(BrokerVerificationKey);
}
}
}
diff --git a/VNLib.Data.Caching.Extensions/FBMDataCacheExtensions.cs b/VNLib.Data.Caching.Extensions/FBMDataCacheExtensions.cs
index 3cf7832..43279fb 100644
--- a/VNLib.Data.Caching.Extensions/FBMDataCacheExtensions.cs
+++ b/VNLib.Data.Caching.Extensions/FBMDataCacheExtensions.cs
@@ -31,15 +31,16 @@ using System.Security.Cryptography;
using System.Runtime.CompilerServices;
using RestSharp;
+
+using VNLib.Net.Http;
+using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
-using VNLib.Hashing.IdentityUtility;
-using VNLib.Net.Http;
using VNLib.Net.Rest.Client;
-using VNLib.Net.Messaging.FBM.Client;
using VNLib.Net.Messaging.FBM;
-
+using VNLib.Net.Messaging.FBM.Client;
namespace VNLib.Data.Caching.Extensions
{
@@ -59,18 +60,6 @@ namespace VNLib.Data.Caching.Extensions
/// </summary>
public const int MAX_FBM_MESSAGE_HEADER_SIZE = 1024;
- private static readonly IReadOnlyDictionary<string, string> BrokerJwtHeader = new Dictionary<string, string>()
- {
- { "alg", "ES384" }, //Must match alg name
- { "typ", "JWT"}
- };
-
-
- /// <summary>
- /// The raw JWT message header
- /// </summary>
- public static ReadOnlyMemory<byte> JwtMessageHeader { get; } = JsonSerializer.SerializeToUtf8Bytes(BrokerJwtHeader);
-
private static readonly RestClientPool ClientPool = new(2,new RestClientOptions()
{
MaxTimeout = 10 * 1000,
@@ -80,17 +69,7 @@ namespace VNLib.Data.Caching.Extensions
ThrowOnAnyError = true,
});
- /// <summary>
- /// The default hashing algorithm used to sign an verify connection
- /// tokens
- /// </summary>
- public static readonly HashAlgorithmName CacheJwtAlgorithm = HashAlgorithmName.SHA384;
-
- //using the es384 algorithm for signing (friendlyname is secp384r1)
- /// <summary>
- /// The default ECCurve used by the connection library
- /// </summary>
- public static readonly ECCurve CacheCurve = ECCurve.CreateFromFriendlyName("secp384r1");
+ private static readonly ConditionalWeakTable<FBMClient, ClientCacheConfiguration> ClientCacheConfig = new();
/// <summary>
/// Gets a <see cref="FBMClientConfig"/> preconfigured object caching
@@ -119,91 +98,82 @@ namespace VNLib.Data.Caching.Extensions
DebugLog = debugLog
};
}
-
- /// <summary>
- /// Contacts the cache broker to get a list of active servers to connect to
- /// </summary>
- /// <param name="brokerAddress">The broker server to connec to</param>
- /// <param name="clientPrivKey">The private key used to sign messages sent to the broker</param>
- /// <param name="brokerPubKey">The broker public key used to verify broker messages</param>
- /// <param name="cancellationToken">A token to cancel the operationS</param>
- /// <returns>The list of active servers</returns>
- /// <exception cref="SecurityException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- public static async Task<ActiveServer[]?> ListServersAsync(Uri brokerAddress, ReadOnlyMemory<byte> clientPrivKey, ReadOnlyMemory<byte> brokerPubKey, CancellationToken cancellationToken = default)
+ private static void LogDebug(this FBMClient client, string message)
{
- using ECDsa client = ECDsa.Create(CacheCurve);
- using ECDsa broker = ECDsa.Create(CacheCurve);
-
- //Import client private key
- client.ImportPkcs8PrivateKey(clientPrivKey.Span, out _);
- //Broker public key to verify broker messages
- broker.ImportSubjectPublicKeyInfo(brokerPubKey.Span, out _);
-
- return await ListServersAsync(brokerAddress, client, broker, cancellationToken);
+ client.Config.DebugLog?.Debug("{debug}: {data}", "[CACHE]", message);
}
/// <summary>
/// Contacts the cache broker to get a list of active servers to connect to
/// </summary>
- /// <param name="brokerAddress">The broker server to connec to</param>
- /// <param name="signingAlg">The signature algorithm used to sign messages to the broker</param>
- /// <param name="verificationAlg">The signature used to verify broker messages</param>
+ /// <param name="request">The request message used to connecto the broker server</param>
/// <param name="cancellationToken">A token to cancel the operationS</param>
/// <returns>The list of active servers</returns>
/// <exception cref="SecurityException"></exception>
/// <exception cref="ArgumentNullException"></exception>
- public static async Task<ActiveServer[]?> ListServersAsync(Uri brokerAddress, ECDsa signingAlg, ECDsa verificationAlg, CancellationToken cancellationToken = default)
+ public static async Task<ActiveServer[]?> ListServersAsync(ListServerRequest request, CancellationToken cancellationToken = default)
{
- _ = brokerAddress ?? throw new ArgumentNullException(nameof(brokerAddress));
- _ = signingAlg ?? throw new ArgumentNullException(nameof(signingAlg));
- _ = verificationAlg ?? throw new ArgumentNullException(nameof(verificationAlg));
+ _ = request ?? throw new ArgumentNullException(nameof(request));
string jwtBody;
//Build request jwt
using (JsonWebToken requestJwt = new())
{
- requestJwt.WriteHeader(JwtMessageHeader.Span);
+ requestJwt.WriteHeader(request.JwtHeader);
requestJwt.InitPayloadClaim()
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
+ .AddClaim("nonce", RandomHash.GetRandomBase32(16))
.CommitClaims();
//sign the jwt
- requestJwt.Sign(signingAlg, in CacheJwtAlgorithm, 512);
+ request.SignJwt(requestJwt);
//Compile the jwt
jwtBody = requestJwt.Compile();
}
+
//New list request
- RestRequest listRequest = new(brokerAddress, Method.Post);
+ RestRequest listRequest = new(request.BrokerAddress, Method.Post);
+
//Add the jwt as a string to the request body
listRequest.AddStringBody(jwtBody, DataFormat.None);
+ listRequest.AddHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Text));
listRequest.AddHeader("Content-Type", HttpHelpers.GetContentTypeString(ContentType.Text));
+
+ byte[] data;
+
//Rent client
- using ClientContract client = ClientPool.Lease();
- //Exec list request
- RestResponse response = await client.Resource.ExecuteAsync(listRequest, cancellationToken);
- if (!response.IsSuccessful)
+ using (ClientContract client = ClientPool.Lease())
{
- throw response.ErrorException!;
+ //Exec list request
+ RestResponse response = await client.Resource.ExecuteAsync(listRequest, cancellationToken);
+
+ if (!response.IsSuccessful)
+ {
+ throw response.ErrorException!;
+ }
+
+ data = response.RawBytes ?? throw new InvalidOperationException("No data returned from broker");
}
//Response is jwt
- using JsonWebToken responseJwt = JsonWebToken.ParseRaw(response.RawBytes);
+ using JsonWebToken responseJwt = JsonWebToken.ParseRaw(data);
+
//Verify the jwt
- if (!responseJwt.Verify(verificationAlg, in CacheJwtAlgorithm))
+ if (!request.VerifyJwt(responseJwt))
{
throw new SecurityException("Failed to verify the broker's challenge, cannot continue");
}
+
using JsonDocument doc = responseJwt.GetPayload();
return doc.RootElement.GetProperty("servers").Deserialize<ActiveServer[]>();
}
-
/// <summary>
/// Registers the current server as active with the specified broker
/// </summary>
/// <param name="registration">The registration request</param>
public static async Task ResgisterWithBrokerAsync(BrokerRegistrationRequest registration)
{
+ _ = registration ?? throw new ArgumentNullException(nameof(registration));
_ = registration.HeartbeatToken ?? throw new ArgumentException("Missing required heartbeat access token");
_ = registration.NodeId ?? throw new ArgumentException("Missing required cache server NodeId");
_ = registration.BrokerAddress ?? throw new ArgumentException("Broker server address has not been configured");
@@ -214,7 +184,7 @@ namespace VNLib.Data.Caching.Extensions
using (JsonWebToken jwt = new())
{
//Shared jwt header
- jwt.WriteHeader(JwtMessageHeader.Span);
+ jwt.WriteHeader(registration.JsonHeader);
//build jwt claim
jwt.InitPayloadClaim()
.AddClaim("address", registration.RegistrationAddress)
@@ -223,7 +193,7 @@ namespace VNLib.Data.Caching.Extensions
.CommitClaims();
//Sign the jwt
- jwt.Sign(registration.SiginingAlg, in CacheJwtAlgorithm, 512);
+ registration.SignJwt(jwt);
//Compile and save
requestData = jwt.Compile();
}
@@ -240,9 +210,7 @@ namespace VNLib.Data.Caching.Extensions
throw response.ErrorException!;
}
}
-
-
- private static readonly ConditionalWeakTable<FBMClient, ClientCacheConfiguration> ClientCacheConfig = new();
+
/// <summary>
/// Allows for configuration of an <see cref="FBMClient"/>
@@ -259,6 +227,7 @@ namespace VNLib.Data.Caching.Extensions
/// <param name="token">A token to cancel the discovery</param>
/// <returns>A task the resolves the list of active servers on the broker server</returns>
public static Task<ActiveServer[]?> DiscoverCacheNodesAsync(this FBMClientWorkerBase client, CancellationToken token = default) => client.Client.DiscoverCacheNodesAsync(token);
+
/// <summary>
/// Discovers cache nodes in the broker configured for the current client.
/// </summary>
@@ -268,8 +237,10 @@ namespace VNLib.Data.Caching.Extensions
public static async Task<ActiveServer[]?> DiscoverCacheNodesAsync(this FBMClient client, CancellationToken token = default)
{
ClientCacheConfiguration conf = ClientCacheConfig.GetOrCreateValue(client);
+ //Request from config
+ using ListServerRequest req = ListServerRequest.FromConfig(conf);
//List servers async
- ActiveServer[]? servers = await ListServersAsync(conf.BrokerAddress!, conf.SigningKey, conf.VerificationKey, token);
+ ActiveServer[]? servers = await ListServersAsync(req, token);
conf.CacheServers = servers;
return servers;
}
@@ -332,16 +303,15 @@ namespace VNLib.Data.Caching.Extensions
/// <exception cref="ObjectDisposedException"></exception>
public static Task ConnectToCacheAsync(this FBMClient client, ActiveServer server, CancellationToken token = default)
{
+ _ = client ?? throw new ArgumentNullException(nameof(client));
+ _ = server ?? throw new ArgumentNullException(nameof(server));
+
//Get stored config
ClientCacheConfiguration conf = ClientCacheConfig.GetOrCreateValue(client);
//Connect to server (no server id because client not replication server)
return ConnectToCacheAsync(client, conf, server, token);
}
-
- private static void LogDebug(this FBMClient client, string message)
- {
- client.Config.DebugLog.Debug("{debug}: {data}","[CACHE]", message);
- }
+
private static async Task ConnectToCacheAsync(FBMClient client, ClientCacheConfiguration request, ActiveServer server, CancellationToken token = default)
{
@@ -358,10 +328,13 @@ namespace VNLib.Data.Caching.Extensions
//Init jwt for connecting to server
using (JsonWebToken jwt = new())
{
- jwt.WriteHeader(JwtMessageHeader.Span);
+ jwt.WriteHeader(request.JwtHeader);
+
//Init claim
JwtPayload claim = jwt.InitPayloadClaim();
+
claim.AddClaim("chl", request.ServerChallenge);
+
if (!string.IsNullOrWhiteSpace(request.NodeId))
{
/*
@@ -370,10 +343,11 @@ namespace VNLib.Data.Caching.Extensions
*/
claim.AddClaim("sub", request.NodeId);
}
+
claim.CommitClaims();
//Sign jwt
- jwt.Sign(request.SigningKey, in CacheJwtAlgorithm, 512);
+ request.SignJwt(jwt);
//Compile to string
jwtMessage = jwt.Compile();
@@ -382,16 +356,19 @@ namespace VNLib.Data.Caching.Extensions
RestRequest negotation = new(serverUri, Method.Get);
//Set the jwt auth header for negotiation
negotation.AddHeader("Authorization", jwtMessage);
+ negotation.AddHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Text));
client.LogDebug("Negotiating with cache server");
-
+
+ string authToken;
+
//rent client
using (ClientContract clientContract = ClientPool.Lease())
{
//Execute the request
- RestResponse response = await clientContract.Resource.ExecuteAsync(negotation, token);
+ RestResponse response = await clientContract.Resource.ExecuteGetAsync(negotation, token);
+
//Check verify the response
-
if (!response.IsSuccessful)
{
throw response.ErrorException!;
@@ -401,27 +378,28 @@ namespace VNLib.Data.Caching.Extensions
{
throw new FBMServerNegiationException("Failed to negotiate with the server, no response");
}
-
+
//Raw content
- string authToken = response.Content;
-
- //Parse the jwt
- using JsonWebToken jwt = JsonWebToken.Parse(authToken);
+ authToken = response.Content;
+ }
+ //Parse the jwt
+ using (JsonWebToken jwt = JsonWebToken.Parse(authToken))
+ {
//Verify the jwt
- if (!jwt.Verify(request.VerificationKey, in CacheJwtAlgorithm))
+ if (!request.VerifyCache(jwt))
{
- throw new SecurityException("Failed to verify the broker's negotiation message, cannot continue");
+ throw new SecurityException("Failed to verify the cache server's negotiation message, cannot continue");
}
//Confirm the server's buffer configuration
ValidateServerNegotation(client, request.ServerChallenge, jwt);
-
- client.LogDebug("Server negotiation validated, connecting to server");
-
- //The client authorization header is the exact response
- client.ClientSocket.Headers[HttpRequestHeader.Authorization] = authToken;
}
+
+ client.LogDebug("Server negotiation validated, connecting to server");
+
+ //The client authorization header is the exact response
+ client.ClientSocket.Headers[HttpRequestHeader.Authorization] = authToken;
//Connect async
await client.ConnectAsync(uriBuilder.Uri, token);
@@ -442,15 +420,15 @@ namespace VNLib.Data.Caching.Extensions
string challengeResponse = args["chl"].GetString()!;
//Check the challenge response
- if (challenge.Equals(challengeResponse, StringComparison.Ordinal))
+ if (!challenge.Equals(challengeResponse, StringComparison.Ordinal))
{
throw new FBMServerNegiationException("Failed to negotiate with the server, challenge response does not match");
}
//Get the negiation values
uint recvBufSize = args[FBMClient.REQ_RECV_BUF_QUERY_ARG].GetUInt32();
- uint headerBufSize= args[FBMClient.REQ_HEAD_BUF_QUERY_ARG].GetUInt32();
- uint maxMessSize= args[FBMClient.REQ_MAX_MESS_QUERY_ARG].GetUInt32();
+ uint headerBufSize = args[FBMClient.REQ_HEAD_BUF_QUERY_ARG].GetUInt32();
+ uint maxMessSize = args[FBMClient.REQ_MAX_MESS_QUERY_ARG].GetUInt32();
//Verify the values
if (client.Config.RecvBufferSize > recvBufSize)
diff --git a/VNLib.Data.Caching.Extensions/ListServerRequest.cs b/VNLib.Data.Caching.Extensions/ListServerRequest.cs
new file mode 100644
index 0000000..4d0d0ea
--- /dev/null
+++ b/VNLib.Data.Caching.Extensions/ListServerRequest.cs
@@ -0,0 +1,116 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: ListServerRequest.cs
+*
+* ListServerRequest.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Data.Caching.Extensions. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+using VNLib.Utils;
+using VNLib.Hashing.IdentityUtility;
+
+namespace VNLib.Data.Caching.Extensions
+{
+ /// <summary>
+ /// A request container for a ListServer request
+ /// </summary>
+ public sealed class ListServerRequest : VnDisposeable
+ {
+ private readonly bool _ownsKeys;
+
+ private ReadOnlyJsonWebKey? VerificationKey;
+ private ReadOnlyJsonWebKey? SigningAlg;
+
+ /// <summary>
+ /// The address of the broker server to connect to
+ /// </summary>
+ public Uri BrokerAddress { get; }
+
+ public ListServerRequest(Uri brokerAddress)
+ {
+ BrokerAddress = brokerAddress;
+ _ownsKeys = true;
+ }
+
+ private ListServerRequest(ClientCacheConfiguration conf)
+ {
+ //Broker verification key is required
+ VerificationKey = conf.BrokerVerificationKey;
+ SigningAlg = conf.SigningKey;
+ BrokerAddress = conf.BrokerAddress ?? throw new ArgumentException("Broker address must be specified");
+ _ownsKeys = false;
+ }
+
+ internal static ListServerRequest FromConfig(ClientCacheConfiguration conf) => new (conf);
+
+ /// <summary>
+ /// Sets the public key used to verify the signature of the response.
+ /// </summary>
+ /// <param name="jwk">The key used to verify messages </param>
+ public ListServerRequest WithVerificationKey(ReadOnlyJsonWebKey jwk)
+ {
+ VerificationKey = jwk ?? throw new ArgumentNullException(nameof(jwk));
+ return this;
+ }
+ /// <summary>
+ /// Sets the private key used to sign the request.
+ /// </summary>
+ /// <param name="jwk">The <see cref="ReadOnlyJsonWebKey"/> containing the private key used to sign the message</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public ListServerRequest WithSigningKey(ReadOnlyJsonWebKey jwk)
+ {
+ SigningAlg = jwk ?? throw new ArgumentNullException(nameof(jwk));
+ return this;
+ }
+
+ /// <summary>
+ /// Signs the <see cref="JsonWebToken"/> using the private key.
+ /// </summary>
+ /// <param name="jwt">The message to sign</param>
+ internal void SignJwt(JsonWebToken jwt)
+ {
+ jwt.SignFromJwk(SigningAlg);
+ }
+
+ /// <summary>
+ /// Verifies the signature of the <see cref="JsonWebToken"/>
+ /// </summary>
+ /// <param name="jwt"></param>
+ /// <returns>A value that indicates if the signature is verified</returns>
+ internal bool VerifyJwt(JsonWebToken jwt)
+ {
+ return jwt.VerifyFromJwk(VerificationKey);
+ }
+
+ internal IReadOnlyDictionary<string, string?> JwtHeader => SigningAlg!.JwtHeader;
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ if (_ownsKeys)
+ {
+ VerificationKey?.Dispose();
+ SigningAlg?.Dispose();
+ }
+ }
+ }
+}
diff --git a/VNLib.Data.Caching/src/ClientExtensions.cs b/VNLib.Data.Caching/src/ClientExtensions.cs
index 1bd2174..6787d4e 100644
--- a/VNLib.Data.Caching/src/ClientExtensions.cs
+++ b/VNLib.Data.Caching/src/ClientExtensions.cs
@@ -64,13 +64,13 @@ namespace VNLib.Data.Caching
DefaultBufferSize = 128
};
-
- private static readonly ConditionalWeakTable<FBMClient, SemaphoreSlim> GetLock = new();
- private static readonly ConditionalWeakTable<FBMClient, SemaphoreSlim> UpdateLock = new();
-
- private static SemaphoreSlim GetLockCtor(FBMClient client) => new (50);
-
- private static SemaphoreSlim UpdateLockCtor(FBMClient client) => new (25);
+ private static void LogDebug(this FBMClient client, string message, params object?[] args)
+ {
+ if (client.Config.DebugLog != null)
+ {
+ client.Config.DebugLog.Debug($"[CACHE] : {message}", args);
+ }
+ }
/// <summary>
/// Gets an object from the server if it exists
@@ -87,10 +87,10 @@ namespace VNLib.Data.Caching
/// <exception cref="InvalidResponseException"></exception>
public static async Task<T?> GetObjectAsync<T>(this FBMClient client, string objectId, CancellationToken cancellationToken = default)
{
- client.Config.DebugLog?.Debug("[DEBUG] Getting object {id}", objectId);
- SemaphoreSlim getLock = GetLock.GetValue(client, GetLockCtor);
- //Wait for entry
- await getLock.WaitAsync(cancellationToken);
+ _ = client ?? throw new ArgumentNullException(nameof(client));
+
+ client.LogDebug("Getting object {id}", objectId);
+
//Rent a new request
FBMRequest request = client.RentRequest();
try
@@ -119,7 +119,6 @@ namespace VNLib.Data.Caching
}
finally
{
- getLock.Release();
client.ReturnRequest(request);
}
}
@@ -144,10 +143,10 @@ namespace VNLib.Data.Caching
/// <exception cref="ObjectNotFoundException"></exception>
public static async Task AddOrUpdateObjectAsync<T>(this FBMClient client, string objectId, string? newId, T data, CancellationToken cancellationToken = default)
{
- client.Config.DebugLog?.Debug("[DEBUG] Updating object {id}, newid {nid}", objectId, newId);
- SemaphoreSlim updateLock = UpdateLock.GetValue(client, UpdateLockCtor);
- //Wait for entry
- await updateLock.WaitAsync(cancellationToken);
+ _ = client ?? throw new ArgumentNullException(nameof(client));
+
+ client.LogDebug("Updating object {id}, newid {nid}", objectId, newId);
+
//Rent a new request
FBMRequest request = client.RentRequest();
try
@@ -189,7 +188,6 @@ namespace VNLib.Data.Caching
}
finally
{
- updateLock.Release();
//Return the request(clears data and reset)
client.ReturnRequest(request);
}
@@ -208,11 +206,9 @@ namespace VNLib.Data.Caching
/// <exception cref="ObjectNotFoundException"></exception>
public static async Task DeleteObjectAsync(this FBMClient client, string objectId, CancellationToken cancellationToken = default)
{
- client.Config.DebugLog?.Debug("[DEBUG] Deleting object {id}", objectId);
+ _ = client ?? throw new ArgumentNullException(nameof(client));
- SemaphoreSlim updateLock = UpdateLock.GetValue(client, UpdateLockCtor);
- //Wait for entry
- await updateLock.WaitAsync(cancellationToken);
+ client.LogDebug("Deleting object {id}", objectId);
//Rent a new request
FBMRequest request = client.RentRequest();
try
@@ -240,7 +236,6 @@ namespace VNLib.Data.Caching
}
finally
{
- updateLock.Release();
client.ReturnRequest(request);
}
}