public static async Task <Subscription> LoadSubscriptionAsync(string accountId, string subscriptionId)
        {
            if (_redisClient == null)
            {
                _redisClient = await Singletons.GetRedisClientAsync();
            }

            string fullyQualifiedSubscriptionKey = REDIS_PREFIX_SUBSCRIPTION + REDIS_PREFIX_SEPARATOR + (accountId != null ? accountId.ToLowerInvariant() : REDIS_ASTERISK) + REDIS_SLASH + subscriptionId.ToLowerInvariant();
            bool   subscriptionExists            = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedSubscriptionKey }) > 0);

            if (subscriptionExists)
            {
                Dictionary <string, string> subscriptionDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedSubscriptionKey);

                // extract serverId from subscriptionId
                string serverId = ParsingHelper.ExtractServerDetailsFromAccountServerIdIdentifier(subscriptionId).Value.ToServerTypeServerIdIdentifierString();
                //
                string clientId = subscriptionDictionary.ContainsKey("client-id") ? subscriptionDictionary["client-id"] : null;
                //
                string userId = subscriptionDictionary.ContainsKey("user-id") ? subscriptionDictionary["user-id"] : null;

                Subscription resultSubscription = new Subscription();
                // account-id
                resultSubscription._accountId = accountId;
                // subscription-id
                resultSubscription._subscriptionId = subscriptionId;
                // server-id
                resultSubscription._serverId = serverId;
                // client-id
                resultSubscription._clientId = clientId;
                // user-id
                resultSubscription._userId = userId;

                return(resultSubscription);
            }

            // subscription (or its owner) could not be found
            return(null);
        }
        public static async Task <OAuth2InitialAccessToken> LoadInitialAccessTokenAsync(string tokenId, LoadTokenOptions options)
        {
            if ((options & LoadTokenOptions.LocalTokens) == LoadTokenOptions.LocalTokens)
            {
                if (_redisClient == null)
                {
                    _redisClient = await Singletons.GetRedisClientAsync();
                }

                string fullyQualifiedTokenKey = REDIS_PREFIX_OAUTH2_TOKEN + REDIS_PREFIX_SEPARATOR + tokenId;
                bool   localTokenExists       = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedTokenKey }) > 0);
                if (localTokenExists)
                {
                    Dictionary <string, string> tokenDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedTokenKey);

                    string tokenType = tokenDictionary.ContainsKey("type") ? tokenDictionary["type"] : null;
                    if (tokenType == null || tokenType != TOKEN_TYPE_INITIAL_ACCESS_TOKEN)
                    {
                        return(null);
                    }

                    string tokenIsCachedAsString = tokenDictionary.ContainsKey("cached") ? tokenDictionary["cached"] : null;
                    bool   tokenIsCached         = (tokenIsCachedAsString != null && tokenIsCachedAsString != "0");

                    string timeCreatedAsString           = tokenDictionary.ContainsKey("time-created") ? tokenDictionary["time-created"] : null;
                    Int64? timeCreatedInUnixMicroseconds = null;
                    Int64  timeCreatedAsInt64;
                    if (timeCreatedAsString != null && Int64.TryParse(timeCreatedAsString, out timeCreatedAsInt64))
                    {
                        timeCreatedInUnixMicroseconds = timeCreatedAsInt64;
                    }

                    string timeUpdatedAsString           = tokenDictionary.ContainsKey("time-updated") ? tokenDictionary["time-updated"] : null;
                    Int64? timeUpdatedInUnixMicroseconds = null;
                    Int64  timeUpdatedAsInt64;
                    if (timeUpdatedAsString != null && Int64.TryParse(timeUpdatedAsString, out timeUpdatedAsInt64))
                    {
                        timeUpdatedInUnixMicroseconds = timeUpdatedAsInt64;
                    }

                    OAuth2InitialAccessToken resultToken = new OAuth2InitialAccessToken();
                    resultToken._softwareId = tokenDictionary.ContainsKey("software-id") ? tokenDictionary["software-id"] : null;
                    if (resultToken._softwareId == null)
                    {
                        return(null);
                    }
                    resultToken._accountId = tokenDictionary.ContainsKey("account-id") ? tokenDictionary["account-id"] : null;

                    // if our result token could be loaded, populate the default fields common to all OAuth2Tokens.
                    resultToken._id = tokenId;
                    ParsingHelper.ServerDetails?loginServerDetails = ParsingHelper.ExtractServerDetailsFromAccountServerIdIdentifier(tokenId);
                    if (loginServerDetails == null)
                    {
                        throw new Exception();
                    }
                    resultToken._loginServerDetails            = loginServerDetails.Value;
                    resultToken._isCached                      = tokenIsCached;
                    resultToken._timeCreatedInUnixMicroseconds = timeCreatedInUnixMicroseconds;
                    resultToken._timeUpdatedInUnixMicroseconds = timeUpdatedInUnixMicroseconds;

                    return(resultToken);
                }
            }

            // valid token could not be found
            return(null);
        }
        public static async Task <OAuth2Client> LoadClientAsync(string clientId, LoadClientOptions options)
        {
            if ((options & LoadClientOptions.LocalClients) == LoadClientOptions.LocalClients)
            {
                if (_redisClient == null)
                {
                    _redisClient = await Singletons.GetRedisClientAsync();
                }

                string fullyQualifiedClientKey = REDIS_PREFIX_CLIENT + REDIS_PREFIX_SEPARATOR + clientId;
                bool   localClientExists       = (await _redisClient.ExistsAsync(new string[] { fullyQualifiedClientKey }) > 0);
                if (localClientExists)
                {
                    Dictionary <string, string> clientDictionary = await _redisClient.HashGetAllASync <string, string, string>(fullyQualifiedClientKey);

                    string clientIsCachedAsString = clientDictionary.ContainsKey("cached") ? clientDictionary["cached"] : null;
                    bool   clientIsCached         = (clientIsCachedAsString != null && clientIsCachedAsString != "0");

                    string timeCreatedAsString           = clientDictionary.ContainsKey("time-created") ? clientDictionary["time-created"] : null;
                    Int64? timeCreatedInUnixMicroseconds = null;
                    Int64  timeCreatedAsInt64;
                    if (timeCreatedAsString != null && Int64.TryParse(timeCreatedAsString, out timeCreatedAsInt64))
                    {
                        timeCreatedInUnixMicroseconds = timeCreatedAsInt64;
                    }

                    string timeUpdatedAsString           = clientDictionary.ContainsKey("time-updated") ? clientDictionary["time-updated"] : null;
                    Int64? timeUpdatedInUnixMicroseconds = null;
                    Int64  timeUpdatedAsInt64;
                    if (timeUpdatedAsString != null && Int64.TryParse(timeUpdatedAsString, out timeUpdatedAsInt64))
                    {
                        timeUpdatedInUnixMicroseconds = timeUpdatedAsInt64;
                    }

                    OAuth2Client resultClient = new OAuth2Client();
                    resultClient._id = clientId;
                    ParsingHelper.ServerDetails?loginServerDetails = ParsingHelper.ExtractServerDetailsFromAccountServerIdIdentifier(clientId);
                    if (loginServerDetails == null)
                    {
                        throw new Exception();
                    }
                    resultClient._loginServerDetails = loginServerDetails.Value;
                    //
                    resultClient._accountId = clientDictionary.ContainsKey("account-id") ? clientDictionary["account-id"] : null;
                    //
                    if (clientDictionary.ContainsKey("issued-at"))
                    {
                        long issuedAtAsLong;
                        if (long.TryParse(clientDictionary["issued-at"], out issuedAtAsLong))
                        {
                            resultClient._issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtAsLong);
                        }
                    }
                    //
                    if (clientDictionary.ContainsKey("secret-hash"))
                    {
                        // load the base64-encoded binary hash of the client secret
                        resultClient._secret         = clientDictionary["secret-hash"];
                        resultClient._secretIsHashed = true;
                    }
                    else
                    {
                        resultClient._secret         = null;
                        resultClient._secretIsHashed = false;
                    }
                    //
                    if (resultClient._secret != null)
                    {
                        if (clientDictionary.ContainsKey("expires-at"))
                        {
                            long expiresAtAsLong;
                            if (long.TryParse(clientDictionary["expires-at"], out expiresAtAsLong))
                            {
                                resultClient._expiresAt = DateTimeOffset.FromUnixTimeSeconds(expiresAtAsLong);
                            }
                        }
                    }
                    //
                    resultClient._softwareId = clientDictionary.ContainsKey("software-id") ? clientDictionary["software-id"] : null;
                    //
                    resultClient._softwareVersion = clientDictionary.ContainsKey("software-version") ? clientDictionary["software-version"] : null;
                    //
                    if (!clientDictionary.ContainsKey("token-endpoint-auth-method"))
                    {
                        // this field is required; return null if it is not present.
                        return(null);
                    }
                    resultClient._tokenEndpointAuthMethod = OAuth2Convert.ConvertStringToTokenEndpointAuthMethod(clientDictionary["token-endpoint-auth-method"]).Value;
                    //
                    resultClient._redirectUris = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_REDIRECT_URIS).ConfigureAwait(false);

                    //
                    List <string> grantTypesAsStrings = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_GRANT_TYPES).ConfigureAwait(false);

                    if (grantTypesAsStrings != null)
                    {
                        resultClient._grantTypes = new ListWithDirtyFlag <OAuth2GrantType>();
                        foreach (string grantTypeAsString in grantTypesAsStrings)
                        {
                            resultClient._grantTypes.Add(OAuth2Convert.ConvertStringToGrantType(grantTypeAsString).Value);
                        }
                    }
                    //
                    List <string> responseTypesAsStrings = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_RESPONSE_TYPES).ConfigureAwait(false);

                    if (responseTypesAsStrings != null)
                    {
                        resultClient._responseTypes = new ListWithDirtyFlag <OAuth2ResponseType>();
                        foreach (string responseTypeAsString in responseTypesAsStrings)
                        {
                            resultClient._responseTypes.Add(OAuth2Convert.ConvertStringToResponseType(responseTypeAsString).Value);
                        }
                    }
                    resultClient._scopes = await _redisClient.SetMembersAsync <string, string>(fullyQualifiedClientKey + REDIS_SUFFIX_SEPARATOR + REDIS_SUFFIX_SCOPES).ConfigureAwait(false);

                    //
                    if (clientDictionary.ContainsKey("registration-token-hash"))
                    {
                        // load the base64-encoded binary hash of the client registration token
                        resultClient._registrationToken         = clientDictionary["registration-token-hash"];
                        resultClient._registrationTokenIsHashed = true;
                    }
                    else
                    {
                        resultClient._registrationToken         = null;
                        resultClient._registrationTokenIsHashed = false;
                    }
                    resultClient._isCached = clientIsCached;
                    resultClient._timeCreatedInUnixMicroseconds = timeCreatedInUnixMicroseconds;
                    resultClient._timeUpdatedInUnixMicroseconds = timeUpdatedInUnixMicroseconds;

                    return(resultClient);
                }
            }

            // client could not be found
            return(null);
        }