File: System\ServiceModel\Channels\ReliableInputConnection.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Runtime;
using System.Threading.Tasks;
 
namespace System.ServiceModel.Channels
{
    internal sealed class ReliableInputConnection
    {
        private bool _isLastKnown = false;
        private ReliableMessagingVersion _reliableMessagingVersion;
        private InterruptibleWaitObject _shutdownWaitObject = new InterruptibleWaitObject(false);
        private bool _terminated = false;
        private InterruptibleWaitObject _terminateWaitObject = new InterruptibleWaitObject(false, false);
 
        public ReliableInputConnection()
        {
        }
 
        public bool AllAdded
        {
            get
            {
                return (Ranges.Count == 1
                    && Ranges[0].Lower == 1
                    && Ranges[0].Upper == Last)
                    || _isLastKnown;
            }
        }
 
        public bool IsLastKnown
        {
            get
            {
                return Last != 0 || _isLastKnown;
            }
        }
 
        public bool IsSequenceClosed { get; private set; } = false;
 
        public long Last { get; private set; } = 0;
 
        public SequenceRangeCollection Ranges { get; private set; } = SequenceRangeCollection.Empty;
 
        public ReliableMessagingVersion ReliableMessagingVersion
        {
            set
            {
                _reliableMessagingVersion = value;
            }
        }
 
        public void Abort(ChannelBase channel)
        {
            _shutdownWaitObject.Abort(channel);
            _terminateWaitObject.Abort(channel);
        }
 
        public bool CanMerge(long sequenceNumber)
        {
            return CanMerge(sequenceNumber, Ranges);
        }
 
        // Returns true if merging the number will not increase the number of ranges past MaxSequenceRanges.
        public static bool CanMerge(long sequenceNumber, SequenceRangeCollection ranges)
        {
            if (ranges.Count < ReliableMessagingConstants.MaxSequenceRanges)
            {
                return true;
            }
 
            ranges = ranges.MergeWith(sequenceNumber);
            return ranges.Count <= ReliableMessagingConstants.MaxSequenceRanges;
        }
 
        public void Fault(ChannelBase channel)
        {
            _shutdownWaitObject.Fault(channel);
            _terminateWaitObject.Fault(channel);
        }
 
        public bool IsValid(long sequenceNumber, bool isLast)
        {
            if (_reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
            {
                if (isLast)
                {
                    if (Last == 0)
                    {
                        if (Ranges.Count > 0)
                        {
                            return sequenceNumber > Ranges[Ranges.Count - 1].Upper;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else
                    {
                        return sequenceNumber == Last;
                    }
                }
                else if (Last > 0)
                {
                    return sequenceNumber < Last;
                }
            }
            else
            {
                if (_isLastKnown)
                {
                    return Ranges.Contains(sequenceNumber);
                }
            }
 
            return true;
        }
 
        public void Merge(long sequenceNumber, bool isLast)
        {
            Ranges = Ranges.MergeWith(sequenceNumber);
 
            if (isLast)
                Last = sequenceNumber;
 
            if (AllAdded)
                _shutdownWaitObject.Set();
        }
 
        public bool SetCloseSequenceLast(long last)
        {
            WsrmUtilities.AssertWsrm11(_reliableMessagingVersion);
            bool validLast;
 
            if ((last < 1) || (Ranges.Count == 0))
            {
                validLast = true;
            }
            else
            {
                validLast = last >= Ranges[Ranges.Count - 1].Upper;
            }
 
            if (validLast)
            {
                IsSequenceClosed = true;
                SetLast(last);
            }
 
            return validLast;
        }
 
        private void SetLast(long last)
        {
            if (_isLastKnown)
            {
                throw Fx.AssertAndThrow("Last can only be set once.");
            }
 
            Last = last;
            _isLastKnown = true;
            _shutdownWaitObject.Set();
        }
 
        // Two error cases:
        // (1) The sequence contains holes.
        // (2) TerminateSequence.LastMsgNumber < last received message number.
        // In both cases the channel should be faulted. In case (2) the channel should send a fault.
        public bool SetTerminateSequenceLast(long last, out bool isLastLargeEnough)
        {
            WsrmUtilities.AssertWsrm11(_reliableMessagingVersion);
            isLastLargeEnough = true;
 
            // unspecified last
            if (last < 1)
            {
                return false;
            }
 
            int rangeCount = Ranges.Count;
            long lastReceived = (rangeCount > 0) ? Ranges[rangeCount - 1].Upper : 0;
 
            // last is too small to be valid
            if (last < lastReceived)
            {
                isLastLargeEnough = false;
                return false;
            }
 
            // there is a hole in the sequence
            if ((rangeCount > 1) || (last > lastReceived))
            {
                return false;
            }
 
            SetLast(last);
            return true;
        }
 
        public bool Terminate()
        {
            if ((_reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
                || IsSequenceClosed)
            {
                if (!_terminated && AllAdded)
                {
                    _terminateWaitObject.Set();
                    _terminated = true;
                }
 
                return _terminated;
            }
 
            return _isLastKnown;
        }
 
        public async Task CloseAsync(TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            await _shutdownWaitObject.WaitAsync(timeoutHelper.RemainingTime());
            await _terminateWaitObject.WaitAsync(timeoutHelper.RemainingTime());
        }
    }
}