Skip to content

Commit

Permalink
Support for Cache Digest in HTTP/2 Server Push
Browse files Browse the repository at this point in the history
  • Loading branch information
tpeczek committed Jan 4, 2017
1 parent 5152213 commit 20191a9
Show file tree
Hide file tree
Showing 7 changed files with 751 additions and 28 deletions.
44 changes: 44 additions & 0 deletions Lib.Web.Mvc/Http/CacheDigestHashAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Text;
using System.Security.Cryptography;

namespace Lib.Web.Mvc.Http
{
internal static class CacheDigestHashAlgorithm
{
#region Constants
private const int _hashValueLengthUpperBound = 32;
#endregion

#region Methods
internal static uint ComputeHash(string url, int count, uint probability, bool validators = false, string entityTag = null)
{
int hashValueLength = (int)Math.Log(count * probability, 2);
if (hashValueLength >= _hashValueLengthUpperBound)
{
throw new NotSupportedException("Only hash-values up to 31 bits are supported.");
}

// This assumes that URL is already converted to an ASCII string by percent-encoding as appropriate (RFC3986).
string key = url;

// If validators is true and ETag is not null.
if (validators && !String.IsNullOrWhiteSpace(entityTag))
{
// Append ETag to key as an ASCII string.
key += entityTag;
}

// Let hash-value be the SHA-256 message digest (RFC6234) of key, expressed as an integer.
uint hashValue = BitConverter.ToUInt32(new SHA256Managed().ComputeHash(Encoding.UTF8.GetBytes(key)), 0);
if (BitConverter.IsLittleEndian)
{
hashValue = (hashValue & 0x000000FFU) << 24 | (hashValue & 0x0000FF00U) << 8 | (hashValue & 0x00FF0000U) >> 8 | (hashValue & 0xFF000000U) >> 24;
}

// Truncate hash-value to log2(N*P) bits.
return (hashValue >> (_hashValueLengthUpperBound - hashValueLength)) & (uint)((1 << hashValueLength) - 1);
}
#endregion
}
}
118 changes: 118 additions & 0 deletions Lib.Web.Mvc/Http/CacheDigestHeaderValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Lib.Web.Mvc.Http
{
/// <summary>
/// Represents the value of the Cache-Digest header.
/// </summary>
public class CacheDigestHeaderValue
{
#region Constants
private const string ResetFlag = "RESET";
private const string CompleteFlag = "COMPLETE";
private const string ValidatorsFlag = "VALIDATORS";
private const string StaleFlag = "STALE";
private const string DigestValueSeparator = ";";
#endregion

#region Fields
private Lazy<CacheDigestValue> _lazyDigestValue;
#endregion

#region Properties
/// <summary>
/// Indicates that any and all cache digests for the applicable origin held by the recipient MUST be considered invalid.
/// </summary>
public bool Reset { get; private set; }

/// <summary>
/// Indicates that the currently valid set of cache digests held by the server constitutes a complete representation of the cache’s state regarding that origin, for the type of cached response indicated by the Stale property.
/// </summary>
public bool Complete { get; private set; }

/// <summary>
/// Indicates that the validators are included in the digest.
/// </summary>
public bool Validators { get; private set; }

/// <summary>
/// Indicates that all cached responses represented in the digest are stale.
/// </summary>
public bool Stale { get; private set; }

/// <summary>
/// The digest value.
/// </summary>
public CacheDigestValue DigestValue { get { return _lazyDigestValue.Value; } }
#endregion

#region Constructor
private CacheDigestHeaderValue()
{
Reset = false;
Complete = false;
Validators = false;
Stale = false;

_lazyDigestValue = new Lazy<CacheDigestValue>(() => CacheDigestValue.FromUrls(new Dictionary<string, string>()));
}

/// <summary>
/// Initializes new instance of CacheDigestHeaderValue class.
/// </summary>
/// <param name="value">The value of the header.</param>
public CacheDigestHeaderValue(string value)
: this()
{
if (!String.IsNullOrWhiteSpace(value))
{
Reset = CheckFlagSet(value, ResetFlag);
Complete = CheckFlagSet(value, CompleteFlag);
Validators = CheckFlagSet(value, ValidatorsFlag);
Stale = CheckFlagSet(value, StaleFlag);

string digestBase64String = value.Substring(0, value.IndexOf(DigestValueSeparator));
_lazyDigestValue = new Lazy<CacheDigestValue>(() => CacheDigestValue.FromBase64String(digestBase64String));
}
}

/// <summary>
/// Initializes new instance of CacheDigestHeaderValue class.
/// </summary>
/// <param name="digestValue">The digest value.</param>
/// <param name="reset">Flag idicating that any and all cache digests for the applicable origin held by the recipient MUST be considered invalid.</param>
/// <param name="validators">Flag indicating the validators are included in the digest.</param>
/// <param name="stale">Flag indicating that all cached responses represented in the digest are stale.</param>
public CacheDigestHeaderValue(CacheDigestValue digestValue, bool reset = false, bool validators = false, bool stale = false)
: this()
{
Reset = reset;
Complete = true;
Validators = validators;
Stale = stale;

_lazyDigestValue = new Lazy<CacheDigestValue>(() => digestValue);
}
#endregion

#region Methods
/// <summary>
/// Queries the digest in order to determine whether there is a match in the digest.
/// </summary>
/// <param name="url">The URL of the resource, converted to an ASCII string by percent-encoding as appropriate per RFC3986.</param>
/// <param name="entityTag">The ETag of the resource, including both the weak indicator (if present) and double quotes, as per RFC7232 Section 2.3.</param>
/// <returns>True if there is a match in the digest, otherwise false.</returns>
public bool QueryDigest(string url, string entityTag = null)
{
return DigestValue.Contains(url, Validators, entityTag);
}

private static bool CheckFlagSet(string value, string flag)
{
return (CultureInfo.InvariantCulture.CompareInfo.IndexOf(value, flag, CompareOptions.IgnoreCase) >= 0);
}
#endregion
}
}
Loading

0 comments on commit 20191a9

Please sign in to comment.