File: System\ServiceModel\Security\InMemoryNonceCache.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.Collections;
using System.Globalization;
using System.IO;
using System.Runtime;
 
namespace System.ServiceModel.Security
{
    /// <summary>
    /// This is the in-memory nonce-cache used for turnkey replay detection.
    /// The nonce cache is based on a hashtable implementation for fast lookups.
    /// The hashcode is computed based on the nonce byte array.
    /// The nonce cache periodically purges stale nonce entries.
    /// </summary>
    internal sealed class InMemoryNonceCache : NonceCache
    {
        private NonceCacheImpl _cacheImpl;
 
        public InMemoryNonceCache(TimeSpan cachingTime, int maxCachedNonces)
        {
            CacheSize = maxCachedNonces;
            CachingTimeSpan = cachingTime;
            _cacheImpl = new NonceCacheImpl(cachingTime, maxCachedNonces);
        }
 
        public override bool CheckNonce(byte[] nonce)
        {
            return _cacheImpl.CheckNonce(nonce);
        }
 
        public override bool TryAddNonce(byte[] nonce)
        {
            return _cacheImpl.TryAddNonce(nonce);
        }
 
        public override string ToString()
        {
            StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
            writer.WriteLine("NonceCache:");
            writer.WriteLine("   Caching Timespan: {0}", CachingTimeSpan);
            writer.WriteLine("   Capacity: {0}", CacheSize);
            return writer.ToString();
        }
 
        internal sealed class NonceCacheImpl : TimeBoundedCache
        {
            private static NonceKeyComparer s_comparer = new NonceKeyComparer();
            private static object s_dummyItem = new Object();
            // if there are less than lowWaterMark entries, no purging is done
            private static int s_lowWaterMark = 50;
            // We created a key for the nonce using the first 4 bytes, and hence the minimum length of nonce
            // that can be added to the cache.
            private static int s_minimumNonceLength = 4;
            private TimeSpan _cachingTimeSpan;
 
            public NonceCacheImpl(TimeSpan cachingTimeSpan, int maxCachedNonces)
                : base(s_lowWaterMark, maxCachedNonces, s_comparer, PurgingMode.AccessBasedPurge, TimeSpan.FromTicks(cachingTimeSpan.Ticks >> 2), false)
            {
                _cachingTimeSpan = cachingTimeSpan;
            }
 
            public bool TryAddNonce(byte[] nonce)
            {
                if (nonce == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(nonce));
                }
 
                if (nonce.Length < s_minimumNonceLength)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.NonceLengthTooShort);
                }
 
                DateTime expirationTime = TimeoutHelper.Add(DateTime.UtcNow, _cachingTimeSpan);
                return base.TryAddItem(nonce, s_dummyItem, expirationTime, false);
            }
 
            public bool CheckNonce(byte[] nonce)
            {
                if (nonce == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(nonce));
                }
 
                if (nonce.Length < s_minimumNonceLength)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.NonceLengthTooShort);
                }
 
                if (base.GetItem(nonce) != null)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
 
            /// <summary>
            /// This class provides the hash-code value for the key (nonce) of the nonce cache.
            /// The hash code is obtained from the nonce byte array  by making an int of
            /// the first 4 bytes
            /// </summary>
            internal sealed class NonceKeyComparer : IEqualityComparer, Collections.Generic.IEqualityComparer<byte[]>
            {
                public int GetHashCode(object o)
                {
                    return GetHashCode((byte[])o);
                }
                public int GetHashCode(byte[] o)
                {
                    byte[] nonce = (byte[])o;
 
                    return (((int)nonce[0]) | (((int)nonce[1]) << 8) | (((int)nonce[2]) << 16) | (((int)nonce[3]) << 24));
                }
 
                public int Compare(object x, object y)
                {
                    return Compare((byte[])x, (byte[])y);
                }
 
                public int Compare(byte[] x, byte[] y)
                {
                    if (Object.ReferenceEquals(x, y))
                    {
                        return 0;
                    }
 
                    if (x == null)
                    {
                        return -1;
                    }
                    else if (y == null)
                    {
                        return 1;
                    }
 
                    byte[] nonce1 = (byte[])x;
                    int length1 = nonce1.Length;
                    byte[] nonce2 = (byte[])y;
                    int length2 = nonce2.Length;
 
                    if (length1 == length2)
                    {
                        for (int i = 0; i < length1; ++i)
                        {
                            int diff = ((int)nonce1[i] - (int)nonce2[i]);
 
                            if (diff != 0)
                            {
                                return diff;
                            }
                        }
 
                        return 0;
                    }
                    else if (length1 > length2)
                    {
                        return 1;
                    }
                    else
                    {
                        return -1;
                    }
                }
 
                public new bool Equals(object x, object y)
                {
                    return (Compare(x, y) == 0);
                }
 
                public bool Equals(byte[] x, byte[] y)
                {
                    return (Compare(x, y) == 0);
                }
            }
        }
    }
}