public Boolean authenticate(String username, String password)
        {
            _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: start");

            HTSMessage helloMessage = new HTSMessage();

            helloMessage.Method = "hello";
            helloMessage.putField("clientname", _clientName);
            helloMessage.putField("clientversion", _clientVersion);
            helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
            helloMessage.putField("username", username);

            LoopBackResponseHandler loopBackResponseHandler = new LoopBackResponseHandler();

            sendMessage(helloMessage, loopBackResponseHandler);
            HTSMessage helloResponse = loopBackResponseHandler.getResponse();

            if (helloResponse != null)
            {
                _serverProtocolVersion = helloResponse.getInt("htspversion");
                _servername            = helloResponse.getString("servername");
                _serverversion         = helloResponse.getString("serverversion");

                byte[] salt   = helloResponse.getByteArray("challenge");
                byte[] digest = SHA1helper.GenerateSaltedSHA1(password, salt);

                HTSMessage authMessage = new HTSMessage();
                authMessage.Method = "authenticate";
                authMessage.putField("username", username);
                authMessage.putField("digest", digest);
                sendMessage(authMessage, loopBackResponseHandler);
                HTSMessage authResponse = loopBackResponseHandler.getResponse();
                if (authResponse != null)
                {
                    Boolean auth = authResponse.getInt("noaccess", 0) != 1;
                    if (auth)
                    {
                        HTSMessage getDiskSpaceMessage = new HTSMessage();
                        getDiskSpaceMessage.Method = "getDiskSpace";
                        sendMessage(getDiskSpaceMessage, loopBackResponseHandler);
                        HTSMessage diskSpaceResponse = loopBackResponseHandler.getResponse();
                        if (diskSpaceResponse != null)
                        {
                            _diskSpace = (diskSpaceResponse.getLong("freediskspace") / BytesPerGiga) + "GB / "
                                         + (diskSpaceResponse.getLong("totaldiskspace") / BytesPerGiga) + "GB";
                        }

                        HTSMessage enableAsyncMetadataMessage = new HTSMessage();
                        enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
                        sendMessage(enableAsyncMetadataMessage, null);
                    }

                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: authenticated = " + auth);
                    return(auth);
                }
            }
            _logger.Error("[TVHclient] HTSConnectionAsync.authenticate: no hello response");
            return(false);
        }
Example #2
0
        public async Task Authenticate(String username, String password, CancellationToken cancellationToken)
        {
            _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: start");

            HTSMessage helloMessage = new HTSMessage();

            helloMessage.Method = "hello";
            helloMessage.putField("clientname", _clientName);
            helloMessage.putField("clientversion", _clientVersion);
            helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
            helloMessage.putField("username", username);

            var helloResponse = await SendMessage(helloMessage, cancellationToken).ConfigureAwait(false);

            byte[] salt = null;
            if (helloResponse.containsField("challenge"))
            {
                salt = helloResponse.getByteArray("challenge");
            }
            else
            {
                salt = new byte[0];
                _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'challenge' - htsp wrong implemented on tvheadend side.");
            }

            byte[]     digest      = SHA1helper.GenerateSaltedSHA1(password, salt);
            HTSMessage authMessage = new HTSMessage();

            authMessage.Method = "authenticate";
            authMessage.putField("username", username);
            authMessage.putField("digest", digest);
            var authResponse = await SendMessage(authMessage, cancellationToken).ConfigureAwait(false);

            Boolean auth = authResponse.getInt("noaccess", 0) != 1;

            if (auth)
            {
                HTSMessage enableAsyncMetadataMessage = new HTSMessage();
                enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
                await SendMessage(enableAsyncMetadataMessage, cancellationToken).ConfigureAwait(false);
            }

            _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: authenticated = " + auth);
            if (!auth)
            {
                throw new Exception("TVH authentication failed");
            }
        }
Example #3
0
        public Task <HTSMessage> SendMessage(HTSMessage message, CancellationToken cancellationToken)
        {
            // loop the sequence number
            if (_seq == int.MaxValue)
            {
                _seq = int.MinValue;
            }
            else
            {
                _seq++;
            }

            message.putField("seq", _seq);

            var taskCompletionSource = new TaskCompletionSource <HTSMessage>();

            cancellationToken.Register(() => taskCompletionSource.TrySetCanceled());
            _responseHandlers.Add(_seq, taskCompletionSource);

            try
            {
                byte[] data2send = message.BuildBytes();
                int    bytesSent = _socket.Send(data2send);
                if (bytesSent != data2send.Length)
                {
                    _logger.Error("[TVHclient] SendingHandler: Sending not complete! \nBytes sent: " + bytesSent + "\nMessage bytes: " +
                                  data2send.Length + "\nMessage: " + message.ToString());
                }

                return(taskCompletionSource.Task);
            }
            catch (Exception ex)
            {
                _logger.Error("[TVHclient] SendingHandler caught exception : {0}", ex.ToString());
                if (_listener != null)
                {
                    _listener.onError(ex);
                }
                else
                {
                    _logger.ErrorException("[TVHclient] SendingHandler caught exception : {0} but no error listener is configured!!!", ex, ex.ToString());
                }
                throw;
            }
        }
        public void sendMessage(HTSMessage message, HTSResponseHandler responseHandler)
        {
            // loop the sequence number
            if (_seq == int.MaxValue)
            {
                _seq = int.MinValue;
            }
            else
            {
                _seq++;
            }

            // housekeeping very old response handlers
            if (_responseHandlers.ContainsKey(_seq))
            {
                _responseHandlers.Remove(_seq);
            }

            message.putField("seq", _seq);
            _messagesForSendQueue.Enqueue(message);
            _responseHandlers.Add(_seq, responseHandler);
        }
        public Boolean authenticate(String username, String password)
        {
            _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: start");

            HTSMessage helloMessage = new HTSMessage();

            helloMessage.Method = "hello";
            helloMessage.putField("clientname", _clientName);
            helloMessage.putField("clientversion", _clientVersion);
            helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
            helloMessage.putField("username", username);

            LoopBackResponseHandler loopBackResponseHandler = new LoopBackResponseHandler();

            sendMessage(helloMessage, loopBackResponseHandler);
            HTSMessage helloResponse = loopBackResponseHandler.getResponse();

            if (helloResponse != null)
            {
                if (helloResponse.containsField("htspversion"))
                {
                    _serverProtocolVersion = helloResponse.getInt("htspversion");
                }
                else
                {
                    _serverProtocolVersion = -1;
                    _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: hello didn't include required field 'htspversion' - htsp incorrectly implemented by tvheadend");
                }

                if (helloResponse.containsField("servername"))
                {
                    _servername = helloResponse.getString("servername");
                }
                else
                {
                    _servername = "n/a";
                    _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: hello didn't include required field 'servername' - htsp incorrectly implemented by tvheadend");
                }

                if (helloResponse.containsField("serverversion"))
                {
                    _serverversion = helloResponse.getString("serverversion");
                }
                else
                {
                    _serverversion = "n/a";
                    _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: hello didn't include required field 'serverversion' - htsp incorrectly implemented by tvheadend");
                }

                byte[] salt = null;
                if (helloResponse.containsField("challenge"))
                {
                    salt = helloResponse.getByteArray("challenge");
                }
                else
                {
                    salt = new byte[0];
                    _logger.LogInformation("[TVHclient] HTSConnectionAsync.authenticate: hello didn't include required field 'challenge' - htsp incorrectly implemented by tvheadend");
                }

                byte[]     digest      = SHA1helper.GenerateSaltedSHA1(password, salt);
                HTSMessage authMessage = new HTSMessage();
                authMessage.Method = "authenticate";
                authMessage.putField("username", username);
                authMessage.putField("digest", digest);
                sendMessage(authMessage, loopBackResponseHandler);
                HTSMessage authResponse = loopBackResponseHandler.getResponse();
                if (authResponse != null)
                {
                    Boolean auth = authResponse.getInt("noaccess", 0) != 1;
                    if (auth)
                    {
                        HTSMessage getDiskSpaceMessage = new HTSMessage();
                        getDiskSpaceMessage.Method = "getDiskSpace";
                        sendMessage(getDiskSpaceMessage, loopBackResponseHandler);
                        HTSMessage diskSpaceResponse = loopBackResponseHandler.getResponse();
                        if (diskSpaceResponse != null)
                        {
                            long freeDiskSpace  = -1;
                            long totalDiskSpace = -1;
                            if (diskSpaceResponse.containsField("freediskspace"))
                            {
                                freeDiskSpace = diskSpaceResponse.getLong("freediskspace") / BytesPerGiga;
                            }
                            else
                            {
                                _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace didn't include required field 'freediskspace' - htsp incorrectly implemented by tvheadend");
                            }
                            if (diskSpaceResponse.containsField("totaldiskspace"))
                            {
                                totalDiskSpace = diskSpaceResponse.getLong("totaldiskspace") / BytesPerGiga;
                            }
                            else
                            {
                                _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace didn't include required field 'totaldiskspace' - htsp incorrectly implemented by tvheadend");
                            }

                            _diskSpace = freeDiskSpace + "GB / " + totalDiskSpace + "GB";
                        }

                        HTSMessage enableAsyncMetadataMessage = new HTSMessage();
                        enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
                        sendMessage(enableAsyncMetadataMessage, null);
                    }

                    _logger.LogDebug("[TVHclient] HTSConnectionAsync.authenticate: authenticated = {m}", auth);
                    return(auth);
                }
            }
            _logger.LogError("[TVHclient] HTSConnectionAsync.authenticate: no hello response");
            return(false);
        }
        private static HTSMessage deserializeBinary(byte[] messageData)
        {
            byte type, namelen;
            long datalen;

            HTSMessage msg = new HTSMessage();
            int cnt = 0;

            ByteBuffer buf = new ByteBuffer(messageData);
            while (buf.hasRemaining())
            {
                type = buf.get();
                namelen = buf.get();
                datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get());

                if (buf.Length() < namelen + datalen)
                {
                    throw new IOException("Buffer limit exceeded");
                }

                //Get the key for the map (the name)
                string name = null;
                if (namelen == 0)
                {
                    name = Convert.ToString(cnt++);
                }
                else
                {
                    byte[] bName = new byte[namelen];
                    buf.get(bName);
                    name = NewString(bName);
                }

                //Get the actual content
                object obj = null;
                byte[] bData = new byte[datalen];
                buf.get(bData);

                switch (type)
                {
                    case HTSMessage.HMF_STR:
                        {
                            obj = NewString(bData);
                            break;
                        }
                    case HMF_BIN:
                        {
                            obj = bData;
                            break;
                        }
                    case HMF_S64:
                        {
                            obj = toBigInteger(bData);
                            break;
                        }
                    case HMF_MAP:
                        {
                            obj = deserializeBinary(bData);
                            break;
                        }
                    case HMF_LIST:
                        {
                            obj = new List<object>(deserializeBinary(bData)._dict.Values);
                            break;
                        }
                    default:
                        throw new IOException("Unknown data type");
                }
                msg.putField(name, obj);
            }
            return msg;
        }
Example #7
0
        private static HTSMessage deserializeBinary(byte[] messageData)
        {
            byte type, namelen;
            long datalen;

            HTSMessage msg = new HTSMessage();
            int        cnt = 0;

            ByteBuffer buf = new ByteBuffer(messageData);

            while (buf.hasRemaining())
            {
                type    = buf.get();
                namelen = buf.get();
                datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get());

                if (buf.Length() < namelen + datalen)
                {
                    throw new IOException("Buffer limit exceeded");
                }

                //Get the key for the map (the name)
                string name = null;
                if (namelen == 0)
                {
                    name = Convert.ToString(cnt++);
                }
                else
                {
                    byte[] bName = new byte[namelen];
                    buf.get(bName);
                    name = NewString(bName);
                }

                //Get the actual content
                object obj   = null;
                byte[] bData = new byte[datalen];
                buf.get(bData);

                switch (type)
                {
                case HTSMessage.HMF_STR:
                {
                    obj = NewString(bData);
                    break;
                }

                case HMF_BIN:
                {
                    obj = bData;
                    break;
                }

                case HMF_S64:
                {
                    obj = toBigInteger(bData);
                    break;
                }

                case HMF_MAP:
                {
                    obj = deserializeBinary(bData);
                    break;
                }

                case HMF_LIST:
                {
                    obj = new List <object>(deserializeBinary(bData)._dict.Values);
                    break;
                }

                default:
                    throw new IOException("Unknown data type");
                }
                msg.putField(name, obj);
            }
            return(msg);
        }
Example #8
0
        public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
        {
            ensureConnection();

            int timeOut = await WaitForInitialLoadTask(cancellationToken);
            if (timeOut == -1 || cancellationToken.IsCancellationRequested)
            {
                _logger.Info("[TVHclient] GetProgramsAsync, call canceled or timed out - returning empty list.");
                return new List<ProgramInfo>();
            }

            GetEventsResponseHandler currGetEventsResponseHandler = new GetEventsResponseHandler(startDateUtc, endDateUtc, _logger, cancellationToken);

            HTSMessage queryEvents = new HTSMessage();
            queryEvents.Method = "getEvents";
            queryEvents.putField("channelId", Convert.ToInt32(channelId));
            _htsConnection.sendMessage(queryEvents, currGetEventsResponseHandler);

            //IEnumerable<ProgramInfo> pi = await currGetEventsResponseHandler.GetEvents(cancellationToken);
            //return pi;

            TaskWithTimeoutRunner<IEnumerable<ProgramInfo>> twtr = new TaskWithTimeoutRunner<IEnumerable<ProgramInfo>>(TIMEOUT);
            TaskWithTimeoutResult<IEnumerable<ProgramInfo>> twtRes = await
                twtr.RunWithTimeout(currGetEventsResponseHandler.GetEvents(cancellationToken));

            if (twtRes.HasTimeout)
            {
                return new List<ProgramInfo>();
            }

            return twtRes.Result;
        }
Example #9
0
        public async Task<MediaSourceInfo> GetRecordingStream(string recordingId, string mediaSourceId, CancellationToken cancellationToken)
        {
            ensureConnection();

            HTSMessage getTicketMessage = new HTSMessage();
            getTicketMessage.Method = "getTicket";
            getTicketMessage.putField("dvrId", recordingId);

            //HTSMessage getTicketResponse = await Task.Factory.StartNew<HTSMessage>(() =>
            //{
            //    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
            //    _htsConnection.sendMessage(getTicketMessage, lbrh);
            //    return lbrh.getResponse();
            //});

            TaskWithTimeoutRunner<HTSMessage> twtr = new TaskWithTimeoutRunner<HTSMessage>(TIMEOUT);
            TaskWithTimeoutResult<HTSMessage> twtRes = await twtr.RunWithTimeout(Task.Factory.StartNew<HTSMessage>(() =>
                {
                    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
                    _htsConnection.sendMessage(getTicketMessage, lbrh);
                    return lbrh.getResponse();
                }));

            if (twtRes.HasTimeout)
            {
                _logger.Error("[TVHclient] Can't delete recording because of timeout");
            }
            else
            {
                HTSMessage getTicketResponse = twtRes.Result;

                if (_subscriptionId == int.MaxValue)
                {
                    _subscriptionId = 0;
                }
                int currSubscriptionId = _subscriptionId++;

                return new MediaSourceInfo
                {
                    Id = "" + currSubscriptionId,
                    Path = _httpBaseUrl + getTicketResponse.getString("path") + "?ticket=" + getTicketResponse.getString("ticket"),
                    Protocol = MediaProtocol.Http,
                    MediaStreams = new List<MediaStream>
                        {
                            new MediaStream
                            {
                                Type = MediaStreamType.Video,
                                // Set the index to -1 because we don't know the exact index of the video stream within the container
                                Index = -1,
                                // Set to true if unknown to enable deinterlacing
                                IsInterlaced = true
                            },
                            new MediaStream
                            {
                                Type = MediaStreamType.Audio,
                                // Set the index to -1 because we don't know the exact index of the audio stream within the container
                                Index = -1
                            }
                        }
                };
            }

            throw new TimeoutException();
        }
Example #10
0
        /// <summary>
        /// Cancel the Series Timer
        /// </summary>
        /// <param name="timerId">The Timer Id</param>
        /// <param name="cancellationToken">The CancellationToken</param>
        /// <returns></returns>
        public async Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
        {
            ensureConnection();

            int timeOut = await WaitForInitialLoadTask(cancellationToken);
            if (timeOut == -1 || cancellationToken.IsCancellationRequested)
            {
                _logger.Info("[TVHclient] CancelSeriesTimerAsync, call canceled or timed out.");
                return;
            }

            HTSMessage deleteAutorecMessage = new HTSMessage();
            deleteAutorecMessage.Method = "deleteAutorecEntry";
            deleteAutorecMessage.putField("id", timerId);

            //HTSMessage deleteAutorecResponse = await Task.Factory.StartNew<HTSMessage>(() =>
            //{
            //    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
            //    _htsConnection.sendMessage(deleteAutorecMessage, lbrh);
            //    return lbrh.getResponse();
            //});

            TaskWithTimeoutRunner<HTSMessage> twtr = new TaskWithTimeoutRunner<HTSMessage>(TIMEOUT);
            TaskWithTimeoutResult<HTSMessage> twtRes = await twtr.RunWithTimeout(Task.Factory.StartNew<HTSMessage>(() =>
                {
                    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
                    _htsConnection.sendMessage(deleteAutorecMessage, lbrh);
                    return lbrh.getResponse();
                }));

            if (twtRes.HasTimeout)
            {
                _logger.Error("[TVHclient] Can't delete recording because of timeout");
            }
            else
            {
                HTSMessage deleteAutorecResponse = twtRes.Result;
                Boolean success = deleteAutorecResponse.getInt("success", 0) == 1;
                if (!success)
                {
                    _logger.Error("[TVHclient] Can't cancel timer: '" + deleteAutorecResponse.getString("error") + "'");
                }
            }
        }
Example #11
0
        /// <summary>
        /// Update a single Timer
        /// </summary>
        /// <param name="info">The program info</param>
        /// <param name="cancellationToken">The CancellationToken</param>
        /// <returns></returns>
        public async Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
        {
            ensureConnection();

            int timeOut = await WaitForInitialLoadTask(cancellationToken);
            if (timeOut == -1 || cancellationToken.IsCancellationRequested)
            {
                _logger.Info("[TVHclient] UpdateTimerAsync, call canceled or timed out.");
                return;
            }

            HTSMessage updateTimerMessage = new HTSMessage();
            updateTimerMessage.Method = "updateDvrEntry";
            updateTimerMessage.putField("id", info.Id);
            updateTimerMessage.putField("startExtra", (long)(info.PrePaddingSeconds / 60));
            updateTimerMessage.putField("stopExtra", (long)(info.PostPaddingSeconds / 60));

            //HTSMessage updateTimerResponse = await Task.Factory.StartNew<HTSMessage>(() =>
            //{
            //    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
            //    _htsConnection.sendMessage(updateTimerMessage, lbrh);
            //    return lbrh.getResponse();
            //});

            TaskWithTimeoutRunner<HTSMessage> twtr = new TaskWithTimeoutRunner<HTSMessage>(TIMEOUT);
            TaskWithTimeoutResult<HTSMessage> twtRes = await twtr.RunWithTimeout(Task.Factory.StartNew<HTSMessage>(() =>
            {
                LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
                _htsConnection.sendMessage(updateTimerMessage, lbrh);
                return lbrh.getResponse();
            }));

            if (twtRes.HasTimeout)
            {
                _logger.Error("[TVHclient] Can't update timer because of timeout");
            }
            else
            {
                HTSMessage updateTimerResponse = twtRes.Result;
                Boolean success = updateTimerResponse.getInt("success", 0) == 1;
                if (!success)
                {
                    _logger.Error("[TVHclient] Can't update timer: '" + updateTimerResponse.getString("error") + "'");
                }
            }
        }
Example #12
0
        /// <summary>
        /// Create a new recording
        /// </summary>
        /// <param name="info">The TimerInfo</param>
        /// <param name="cancellationToken">The cancellationToken</param>
        /// <returns></returns>
        public async Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
        {
            ensureConnection();

            int timeOut = await WaitForInitialLoadTask(cancellationToken);
            if (timeOut == -1 || cancellationToken.IsCancellationRequested)
            {
                _logger.Info("[TVHclient] CreateTimerAsync, call canceled or timed out.");
                return;
            }

            HTSMessage createTimerMessage = new HTSMessage();
            createTimerMessage.Method = "addDvrEntry";
            createTimerMessage.putField("channelId", info.ChannelId);
            createTimerMessage.putField("start", DateTimeHelper.getUnixUTCTimeFromUtcDateTime(info.StartDate));
            createTimerMessage.putField("stop", DateTimeHelper.getUnixUTCTimeFromUtcDateTime(info.EndDate));
            createTimerMessage.putField("startExtra", (long)(info.PrePaddingSeconds / 60));
            createTimerMessage.putField("stopExtra", (long)(info.PostPaddingSeconds / 60));
            createTimerMessage.putField("priority", _priority); // info.Priority delivers always 0 - no GUI
            createTimerMessage.putField("configName", _profile);
            createTimerMessage.putField("description", info.Overview);
            createTimerMessage.putField("title", info.Name);
            createTimerMessage.putField("creator", Plugin.Instance.Configuration.Username);

            //HTSMessage createTimerResponse = await Task.Factory.StartNew<HTSMessage>(() =>
            //{
            //    LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
            //    _htsConnection.sendMessage(createTimerMessage, lbrh);
            //    return lbrh.getResponse();
            //});

            TaskWithTimeoutRunner<HTSMessage> twtr = new TaskWithTimeoutRunner<HTSMessage>(TIMEOUT);
            TaskWithTimeoutResult<HTSMessage> twtRes = await twtr.RunWithTimeout(Task.Factory.StartNew<HTSMessage>(() =>
            {
                LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
                _htsConnection.sendMessage(createTimerMessage, lbrh);
                return lbrh.getResponse();
            }));

            if (twtRes.HasTimeout)
            {
                _logger.Error("[TVHclient] Can't create timer because of timeout");
            }
            else
            {
                HTSMessage createTimerResponse = twtRes.Result;
                Boolean success = createTimerResponse.getInt("success", 0) == 1;
                if (!success)
                {
                    _logger.Error("[TVHclient] Can't create timer: '" + createTimerResponse.getString("error") + "'");
                }
            }
        }
        public void sendMessage(HTSMessage message, HTSResponseHandler responseHandler)
        {
            // loop the sequence number
            if (_seq == int.MaxValue)
            {
                _seq = int.MinValue;
            }
            else
            {
                _seq++;
            }

            // housekeeping verry old response handlers
            if (_responseHandlers.ContainsKey(_seq))
            {
                _responseHandlers.Remove(_seq);
            }

            message.putField("seq", _seq);
            _messagesForSendQueue.Enqueue(message);
            _responseHandlers.Add(_seq, responseHandler);
        }
        public Boolean authenticate(String username, String password)
        {
            _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: start");

            HTSMessage helloMessage = new HTSMessage();
            helloMessage.Method = "hello";
            helloMessage.putField("clientname", _clientName);
            helloMessage.putField("clientversion", _clientVersion);
            helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
            helloMessage.putField("username", username);

            LoopBackResponseHandler loopBackResponseHandler = new LoopBackResponseHandler();
            sendMessage(helloMessage, loopBackResponseHandler);
            HTSMessage helloResponse = loopBackResponseHandler.getResponse();
            if (helloResponse != null)
            {
                if (helloResponse.containsField("htspversion"))
                {
                    _serverProtocolVersion = helloResponse.getInt("htspversion");
                }
                else
                {
                    _serverProtocolVersion = -1;
                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'htspversion' - htsp wrong implemented on tvheadend side.");
                }

                if (helloResponse.containsField("servername"))
                {
                    _servername = helloResponse.getString("servername");
                }
                else
                {
                    _servername = "n/a";
                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'servername' - htsp wrong implemented on tvheadend side.");
                }
                
                if (helloResponse.containsField("serverversion"))
                {
                    _serverversion = helloResponse.getString("serverversion");
                }
                else
                {
                    _serverversion = "n/a";
                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'serverversion' - htsp wrong implemented on tvheadend side.");
                }
                
                byte[] salt = null;
                if (helloResponse.containsField("challenge"))
                {
                    salt = helloResponse.getByteArray("challenge");
                }
                else
                {
                    salt = new byte[0];
                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'challenge' - htsp wrong implemented on tvheadend side.");
                }

                byte[] digest = SHA1helper.GenerateSaltedSHA1(password, salt);
                HTSMessage authMessage = new HTSMessage();
                authMessage.Method = "authenticate";
                authMessage.putField("username", username);
                authMessage.putField("digest", digest);
                sendMessage(authMessage, loopBackResponseHandler);
                HTSMessage authResponse = loopBackResponseHandler.getResponse();
                if (authResponse != null)
                {
                    Boolean auth = authResponse.getInt("noaccess", 0) != 1;
                    if (auth)
                    {
                        HTSMessage getDiskSpaceMessage = new HTSMessage();
                        getDiskSpaceMessage.Method = "getDiskSpace";
                        sendMessage(getDiskSpaceMessage, loopBackResponseHandler);
                        HTSMessage diskSpaceResponse = loopBackResponseHandler.getResponse();
                        if (diskSpaceResponse != null)
                        {
                            long freeDiskSpace = -1;
                            long totalDiskSpace = -1;
                            if (diskSpaceResponse.containsField("freediskspace"))
                            {
                                freeDiskSpace = diskSpaceResponse.getLong("freediskspace") / BytesPerGiga;
                            }
                            else
                            {
                                _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace don't deliver required field 'freediskspace' - htsp wrong implemented on tvheadend side.");
                            }
                            if (diskSpaceResponse.containsField("totaldiskspace"))
                            {
                                totalDiskSpace = diskSpaceResponse.getLong("totaldiskspace") / BytesPerGiga;
                            }
                             else
                            {
                                _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace don't deliver required field 'totaldiskspace' - htsp wrong implemented on tvheadend side.");
                            }

                            _diskSpace = freeDiskSpace  + "GB / "  + totalDiskSpace + "GB";
                        }

                        HTSMessage enableAsyncMetadataMessage = new HTSMessage();
                        enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
                        sendMessage(enableAsyncMetadataMessage, null);
                    }

                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: authenticated = " + auth);
                    return auth;
                }
            }
            _logger.Error("[TVHclient] HTSConnectionAsync.authenticate: no hello response");
            return false;
        }
        public async Task<MediaSourceInfo> GetRecordingStream(string recordingId, string mediaSourceId, CancellationToken cancellationToken)
        {
            HTSMessage getTicketMessage = new HTSMessage();
            getTicketMessage.Method = "getTicket";
            getTicketMessage.putField("dvrId", recordingId);

            TaskWithTimeoutRunner<HTSMessage> twtr = new TaskWithTimeoutRunner<HTSMessage>(TIMEOUT);
            TaskWithTimeoutResult<HTSMessage> twtRes = await twtr.RunWithTimeout(Task.Factory.StartNew<HTSMessage>(() =>
            {
                LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
                _htsConnectionHandler.SendMessage(getTicketMessage, lbrh);
                return lbrh.getResponse();
            }));

            if (twtRes.HasTimeout)
            {
                _logger.Error("[TVHclient] Timeout obtaining playback authentication ticket from TVH");
            }
            else
            {
                HTSMessage getTicketResponse = twtRes.Result;

                if (_subscriptionId == int.MaxValue)
                {
                    _subscriptionId = 0;
                }
                int currSubscriptionId = _subscriptionId++;

                if (_htsConnectionHandler.GetEnableSubsMaudios())
                {

                    _logger.Info("[TVHclient] Support for live TV subtitles and multiple audio tracks is enabled.");

                    MediaSourceInfo recordingasset = new MediaSourceInfo();

                    recordingasset.Id = "" + currSubscriptionId;

                    // Use HTTP basic auth instead of TVH ticketing system for authentication to allow the users to switch subs or audio tracks at any time
                    recordingasset.Path = _htsConnectionHandler.GetHttpBaseUrl() + getTicketResponse.getString("path");
                    recordingasset.Protocol = MediaProtocol.Http;

                    // Set asset source and type for stream probing and logging
                    string recordingasset_probeUrl = "" + recordingasset.Path;
                    string recordingasset_source = "Recording";

                    // Probe the asset stream to determine available sub-streams
                    await ProbeStream(recordingasset, recordingasset_probeUrl, recordingasset_source, cancellationToken);

                    // If enabled, force video deinterlacing for recordings
                    if (_htsConnectionHandler.GetForceDeinterlace())
                    {

                        _logger.Info("[TVHclient] Force video deinterlacing for all channels and recordings is enabled.");

                        foreach (MediaStream i in recordingasset.MediaStreams)
                        {
                            if (i.Type == MediaStreamType.Video && i.IsInterlaced == false)
                            {
                                i.IsInterlaced = true;
                            }
                        }

                    }

                    return recordingasset;

                }
                else
                {

                    return new MediaSourceInfo
                    {
                        Id = "" + currSubscriptionId,
                        Path = _htsConnectionHandler.GetHttpBaseUrl() + getTicketResponse.getString("path") + "?ticket=" + getTicketResponse.getString("ticket"),
                        Protocol = MediaProtocol.Http,
                        MediaStreams = new List<MediaStream>
                            {
                                new MediaStream
                                {
                                    Type = MediaStreamType.Video,
                                    // Set the index to -1 because we don't know the exact index of the video stream within the container
                                    Index = -1,
                                    // Set to true if unknown to enable deinterlacing
                                    IsInterlaced = true
                                },
                                new MediaStream
                                {
                                    Type = MediaStreamType.Audio,
                                    // Set the index to -1 because we don't know the exact index of the audio stream within the container
                                    Index = -1
                                }
                            }
                    };

                }
            }

            throw new TimeoutException();
        }
        public Boolean authenticate(String username, String password)
        {
            _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: start");

            HTSMessage helloMessage = new HTSMessage();
            helloMessage.Method = "hello";
            helloMessage.putField("clientname", _clientName);
            helloMessage.putField("clientversion", _clientVersion);
            helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
            helloMessage.putField("username", username);

            LoopBackResponseHandler loopBackResponseHandler = new LoopBackResponseHandler();
            sendMessage(helloMessage, loopBackResponseHandler);
            HTSMessage helloResponse = loopBackResponseHandler.getResponse();
            if (helloResponse != null)
            {
                _serverProtocolVersion = helloResponse.getInt("htspversion");
                _servername = helloResponse.getString("servername");
                _serverversion = helloResponse.getString("serverversion");

                byte[] salt = helloResponse.getByteArray("challenge");
                byte[] digest = SHA1helper.GenerateSaltedSHA1(password, salt);

                HTSMessage authMessage = new HTSMessage();
                authMessage.Method = "authenticate";
                authMessage.putField("username", username);
                authMessage.putField("digest", digest);
                sendMessage(authMessage, loopBackResponseHandler);
                HTSMessage authResponse = loopBackResponseHandler.getResponse();
                if (authResponse != null)
                {
                    Boolean auth = authResponse.getInt("noaccess", 0) != 1;
                    if (auth)
                    {
                        HTSMessage getDiskSpaceMessage = new HTSMessage();
                        getDiskSpaceMessage.Method = "getDiskSpace";
                        sendMessage(getDiskSpaceMessage, loopBackResponseHandler);
                        HTSMessage diskSpaceResponse = loopBackResponseHandler.getResponse();
                        if (diskSpaceResponse != null)
                        {
                            _diskSpace = (diskSpaceResponse.getLong("freediskspace") / BytesPerGiga) + "GB / "
                                + (diskSpaceResponse.getLong("totaldiskspace") / BytesPerGiga) + "GB";
                        }

                        HTSMessage enableAsyncMetadataMessage = new HTSMessage();
                        enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
                        sendMessage(enableAsyncMetadataMessage, null);
                    }

                    _logger.Info("[TVHclient] HTSConnectionAsync.authenticate: authenticated = " + auth);
                    return auth;
                }
            }
            _logger.Error("[TVHclient] HTSConnectionAsync.authenticate: no hello response");
            return false;
        }