emitter-io / csharp

Client library for emitter.io compatible with .Net, .Net MicroFramework and WinRT
http://emitter.io
Eclipse Public License 1.0
29 stars 21 forks source link

Added async KeyGen function #11

Closed postacik closed 5 years ago

postacik commented 5 years ago

Hi @Florimond ,

I added an async KeyGen function to Emitter.

        #if FX40 != true
        /// <summary>
        /// Sends a key generation request to the emitter.io service and returns the generated key
        /// Generates a timeout exception if the request times out
        /// </summary>
        /// <param name="secretKey">The secret key for this request.</param>
        /// <param name="channel">The target channel for the requested key.</param>
        /// <param name="securityAccess">The security access of the requested key.</param>
        /// <param name="ttl">The number of seconds for which this key will be usable.</param>
        /// <param name="timeout">Timout value. (Default value is 5 seconds)</param>
        public async Task<string> KeyGen(string secretKey, string channel, SecurityAccess securityAccess, int ttl, TimeSpan? timeout = null)
        {
            // Prepare the request
            var request = new KeygenRequest();
            request.Key = secretKey;
            request.Channel = channel;
            request.Type = securityAccess;
            request.Ttl = ttl;
            if (timeout == null) timeout = _defaultTimeout;
            // Execute the request
            var response = await ExecuteAsync(timeout.Value, "emitter/keygen/", Encoding.UTF8.GetBytes(request.ToJson()), CancellationToken.None);
            if (response != null)
            {
                var map = JsonSerializer.DeserializeString(Encoding.UTF8.GetString(response)) as Hashtable;
                if (map.ContainsKey("key")) return map["key"].ToString();
            }
            return "";
        }
        #endif

Similar functions can be added for presence and me functions using the private ExecuteAsync method.

        #if (FX40 != true)
        private async Task<byte[]> ExecuteAsync(TimeSpan timeout, string requestName, byte[] payload, CancellationToken cancellationToken)
        {
            if (requestName == null) throw new ArgumentNullException(nameof(requestName));
            int messageId = 0;
            try
            {
                if (_requestNames.ContainsKey(requestName))
                {
                    _requestNames[requestName] += 1;
                }
                else
                {
                    _requestNames[requestName] = 1;
                }

                var tcs = new TaskCompletionSource<byte[]>();
                messageId = this.Client.Publish(requestName, payload, MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, false);
                if (!_waitingRequests.TryAdd(messageId, tcs))
                {
                    throw new InvalidOperationException();
                }

                using (var timeoutCts = new CancellationTokenSource(timeout))
                using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token))
                {
                    linkedCts.Token.Register(() =>
                    {
                        if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled)
                        {
                            tcs.TrySetCanceled();
                        }
                    });

                    try
                    {
                        var result = await tcs.Task.ConfigureAwait(false);
                        timeoutCts.Cancel(false);
                        return result;
                    }
                    catch (OperationCanceledException)
                    {
                        if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
                        {
                            throw new MqttTimeoutException();
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            }
            finally
            {
                _waitingRequests.TryRemove(messageId, out _);
            }
        }
        #endif

Regards

postacik commented 5 years ago

@Florimond , did you have a chance to look at this?

Florimond commented 5 years ago

Hello @postacik, thank you for your pull request. I've seen it, but indeed I wish to have a closer look at it before merging. I'm just quite busy for the time being. Hopefully, I'll have time to review it this week-end. I hope to finalize the new version of the C# lib soon...

postacik commented 5 years ago

Made some of the changes @kelindar requested.

PS: Current implementation does not handle key permission errors ideally. It simply returns an empty key if the secretkey is not valid.