File: System\IO\Hashing\Adler32.cs
Web Access
Project: src\src\libraries\System.IO.Hashing\src\System.IO.Hashing.csproj (System.IO.Hashing)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
 
namespace System.IO.Hashing
{
    /// <summary>
    ///   Provides an implementation of the Adler-32 algorithm, as used in
    ///   RFC1950.
    /// </summary>
    /// <remarks>
    ///   <para>
    ///     The Adler-32 algorithm is designed for fast, lightweight integrity checking and is commonly used in
    ///     data compression and transmission scenarios. This class is not suitable for cryptographic purposes.
    ///   </para>
    ///   <para>
    ///     Adler-32 is not as robust as other checksum algorithms like CRC32, but it is faster to compute.
    ///   </para>
    /// </remarks>
    public sealed partial class Adler32 : NonCryptographicHashAlgorithm
    {
        private const uint InitialState = 1u;
        private const int Size = sizeof(uint);
        private uint _adler = InitialState;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="Adler32"/> class.
        /// </summary>
        public Adler32()
            : base(Size)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="Adler32"/> class using the state from another instance.
        /// </summary>
        private Adler32(uint adler)
            : base(Size)
            => _adler = adler;
 
        /// <summary>
        /// Returns a clone of the current instance, with a copy of the current instance's internal state.
        /// </summary>
        /// <returns>
        /// A new instance that will produce the same sequence of values as the current instance.
        /// </returns>
        public Adler32 Clone()
            => new(_adler);
 
        /// <summary>
        /// Appends the contents of <paramref name="source"/> to the data already
        /// processed for the current hash computation.
        /// </summary>
        /// <param name="source">The data to process.</param>
        public override void Append(ReadOnlySpan<byte> source)
            => _adler = Update(_adler, source);
 
        /// <summary>
        /// Resets the hash computation to the initial state.
        /// </summary>
        public override void Reset()
            => _adler = InitialState;
 
        /// <summary>
        /// Writes the computed hash value to <paramref name="destination"/>
        /// without modifying accumulated state.
        /// </summary>
        /// <param name="destination">The buffer that receives the computed hash value.</param>
        protected override void GetCurrentHashCore(Span<byte> destination)
            => BinaryPrimitives.WriteUInt32BigEndian(destination, _adler);
 
        /// <summary>
        /// Writes the computed hash value to <paramref name="destination"/>
        /// then clears the accumulated state.
        /// </summary>
        protected override void GetHashAndResetCore(Span<byte> destination)
        {
            BinaryPrimitives.WriteUInt32BigEndian(destination, _adler);
            _adler = InitialState;
        }
 
        /// <summary>
        /// Gets the current computed hash value without modifying accumulated state.
        /// </summary>
        /// <returns>
        /// The hash value for the data already provided.
        /// </returns>
        [CLSCompliant(false)]
        public uint GetCurrentHashAsUInt32()
            => _adler;
 
        /// <summary>
        /// Computes the Adler-32 hash of the provided data.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>The Adler-32 hash of the provided data.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="source"/> is <see langword="null"/>.
        /// </exception>
        public static byte[] Hash(byte[] source)
        {
            ArgumentNullException.ThrowIfNull(source);
            return Hash(new ReadOnlySpan<byte>(source));
        }
 
        /// <summary>
        /// Computes the Adler-32 hash of the provided data.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>The Adler-32 hash of the provided data.</returns>
        public static byte[] Hash(ReadOnlySpan<byte> source)
        {
            byte[] ret = new byte[Size];
            uint hash = HashToUInt32(source);
            BinaryPrimitives.WriteUInt32BigEndian(ret, hash);
            return ret;
        }
 
        /// <summary>
        /// Attempts to compute the Adler-32 hash of the provided data into the provided destination.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <param name="destination">The buffer that receives the computed hash value.</param>
        /// <param name="bytesWritten">
        /// On success, receives the number of bytes written to <paramref name="destination"/>.
        /// </param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="destination"/> is long enough to receive
        /// the computed hash value (4 bytes); otherwise, <see langword="false"/>.
        /// </returns>
        public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
        {
            if (destination.Length < Size)
            {
                bytesWritten = 0;
                return false;
            }
 
            uint hash = HashToUInt32(source);
            BinaryPrimitives.WriteUInt32BigEndian(destination, hash);
            bytesWritten = Size;
            return true;
        }
 
        /// <summary>
        /// Computes the Adler-32 hash of the provided data into the provided destination.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <param name="destination">The buffer that receives the computed hash value.</param>
        /// <returns>
        /// The number of bytes written to <paramref name="destination"/>.
        /// </returns>
        public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination)
        {
            if (destination.Length < Size)
            {
                ThrowDestinationTooShort();
            }
 
            uint hash = HashToUInt32(source);
            BinaryPrimitives.WriteUInt32BigEndian(destination, hash);
            return Size;
        }
 
        /// <summary>
        /// Computes the Adler-32 hash of the provided data.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>
        /// The computed Adler-32 hash.
        /// </returns>
        [CLSCompliant(false)]
        public static uint HashToUInt32(ReadOnlySpan<byte> source)
            => Update(InitialState, source);
 
        private static uint Update(uint adler, ReadOnlySpan<byte> buf)
        {
            if (buf.IsEmpty)
            {
                return adler;
            }
 
            return UpdateScalar(adler, buf);
        }
 
        private static uint UpdateScalar(uint adler, ReadOnlySpan<byte> buf)
        {
            const uint Base = 65521; // largest prime smaller than 65536
            const int NMax = 5552; // NMax is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
 
            uint s1 = adler & 0xFFFF;
            uint s2 = (adler >> 16) & 0xFFFF;
            while (buf.Length > 0)
            {
                int k = buf.Length < NMax ? buf.Length : NMax;
                foreach (byte b in buf.Slice(0, k))
                {
                    s1 += b;
                    s2 += s1;
                }
                s1 %= Base;
                s2 %= Base;
                buf = buf.Slice(k);
            }
 
            return (s2 << 16) | s1;
        }
    }
}