Use a collision-resistant hash algorithm for untrusted data (#5)
* Fix conflict * Use safer unaligned-read methods --------- Co-authored-by: Andrew Arnott <andrew.arnott@microsoft.com>
This commit is contained in:
Родитель
95119056ee
Коммит
9aeb12b9bd
|
@ -75,6 +75,9 @@
|
|||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\MessagePackSecurity.cs">
|
||||
<Link>Code\MessagePackSecurity.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\SipHash.cs">
|
||||
<Link>Code\SipHash.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\MonoProtection.cs">
|
||||
<Link>Code\MonoProtection.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -6,7 +6,9 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using MessagePack.Formatters;
|
||||
using MessagePack.Internal;
|
||||
|
||||
|
@ -31,6 +33,8 @@ namespace MessagePack
|
|||
MaximumObjectGraphDepth = 500,
|
||||
};
|
||||
|
||||
private static readonly SipHash Hash = new();
|
||||
|
||||
private readonly ObjectFallbackEqualityComparer objectFallbackEqualityComparer;
|
||||
|
||||
private MessagePackSecurity()
|
||||
|
@ -61,7 +65,7 @@ namespace MessagePack
|
|||
/// This can mitigate some denial of service attacks when deserializing untrusted code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value is <c>false</c> for <see cref="TrustedData"/> and <c>true</c> for <see cref="UntrustedData"/>.
|
||||
/// The value is <see langword="false"/> for <see cref="TrustedData"/> and <see langword="true"/> for <see cref="UntrustedData"/>.
|
||||
/// </value>
|
||||
public bool HashCollisionResistant { get; private set; }
|
||||
|
||||
|
@ -138,6 +142,51 @@ namespace MessagePack
|
|||
return this.HashCollisionResistant ? GetHashCollisionResistantEqualityComparer() : EqualityComparer<object>.Default;
|
||||
}
|
||||
|
||||
private class HashResistantCache<T>
|
||||
{
|
||||
internal static readonly IEqualityComparer<T>? EqualityComparer;
|
||||
|
||||
static HashResistantCache()
|
||||
{
|
||||
// We have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
|
||||
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
|
||||
EqualityComparer =
|
||||
typeof(T) == typeof(bool) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<bool>.Instance :
|
||||
typeof(T) == typeof(char) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<char>.Instance :
|
||||
typeof(T) == typeof(sbyte) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<sbyte>.Instance :
|
||||
typeof(T) == typeof(byte) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<byte>.Instance :
|
||||
typeof(T) == typeof(short) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<short>.Instance :
|
||||
typeof(T) == typeof(ushort) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<ushort>.Instance :
|
||||
typeof(T) == typeof(int) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<int>.Instance :
|
||||
typeof(T) == typeof(uint) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<uint>.Instance :
|
||||
typeof(T) == typeof(long) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<long>.Instance :
|
||||
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<ulong>.Instance :
|
||||
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)CollisionResistantHasherUnmanaged<Guid>.Instance :
|
||||
|
||||
// Data types that are managed or have multiple in-memory representations for equivalent values:
|
||||
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
|
||||
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
|
||||
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
|
||||
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
|
||||
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
|
||||
|
||||
// Call out each primitive behind an enum explicitly to avoid dynamically generating code.
|
||||
typeof(T).GetTypeInfo().IsEnum && typeof(T).GetTypeInfo().GetEnumUnderlyingType() is Type underlying ? (
|
||||
underlying == typeof(byte) ? CollisionResistantEnumHasher<T, byte>.Instance :
|
||||
underlying == typeof(sbyte) ? CollisionResistantEnumHasher<T, sbyte>.Instance :
|
||||
underlying == typeof(ushort) ? CollisionResistantEnumHasher<T, ushort>.Instance :
|
||||
underlying == typeof(short) ? CollisionResistantEnumHasher<T, short>.Instance :
|
||||
underlying == typeof(uint) ? CollisionResistantEnumHasher<T, uint>.Instance :
|
||||
underlying == typeof(int) ? CollisionResistantEnumHasher<T, int>.Instance :
|
||||
underlying == typeof(ulong) ? CollisionResistantEnumHasher<T, ulong>.Instance :
|
||||
underlying == typeof(long) ? CollisionResistantEnumHasher<T, long>.Instance :
|
||||
null) :
|
||||
|
||||
// Failsafe. If we don't recognize the type, don't assume we have a good, secure hash function for it.
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash collision resistant equality comparer.
|
||||
/// </summary>
|
||||
|
@ -145,54 +194,20 @@ namespace MessagePack
|
|||
/// <returns>A hash collision resistant equality comparer.</returns>
|
||||
protected virtual IEqualityComparer<T> GetHashCollisionResistantEqualityComparer<T>()
|
||||
{
|
||||
IEqualityComparer<T> result = null;
|
||||
if (typeof(T).GetTypeInfo().IsEnum)
|
||||
if (HashResistantCache<T>.EqualityComparer is { } result)
|
||||
{
|
||||
Type underlyingType = typeof(T).GetTypeInfo().GetEnumUnderlyingType();
|
||||
result =
|
||||
underlyingType == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
|
||||
underlyingType == typeof(byte) ? CollisionResistantHasher<T>.Instance :
|
||||
underlyingType == typeof(short) ? CollisionResistantHasher<T>.Instance :
|
||||
underlyingType == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
|
||||
underlyingType == typeof(int) ? CollisionResistantHasher<T>.Instance :
|
||||
underlyingType == typeof(uint) ? CollisionResistantHasher<T>.Instance :
|
||||
null;
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For anything 32-bits and under, our fallback base secure hasher is usually adequate since it makes the hash unpredictable.
|
||||
// We should have special implementations for any value that is larger than 32-bits in order to make sure
|
||||
// that all the data gets hashed securely rather than trivially and predictably compressed into 32-bits before being hashed.
|
||||
// We also have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
|
||||
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
|
||||
result =
|
||||
// 32-bits or smaller:
|
||||
typeof(T) == typeof(bool) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(char) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(byte) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(short) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(int) ? CollisionResistantHasher<T>.Instance :
|
||||
typeof(T) == typeof(uint) ? CollisionResistantHasher<T>.Instance :
|
||||
|
||||
// Larger than 32-bits (or otherwise require special handling):
|
||||
typeof(T) == typeof(long) ? (IEqualityComparer<T>)Int64EqualityComparer.Instance :
|
||||
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)UInt64EqualityComparer.Instance :
|
||||
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
|
||||
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
|
||||
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
|
||||
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)GuidEqualityComparer.Instance :
|
||||
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
|
||||
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
|
||||
typeof(T) == typeof(object) ? (IEqualityComparer<T>)this.objectFallbackEqualityComparer :
|
||||
null;
|
||||
if (typeof(T) == typeof(object))
|
||||
{
|
||||
return (IEqualityComparer<T>)this.objectFallbackEqualityComparer;
|
||||
}
|
||||
|
||||
// Any type we don't explicitly whitelist here shouldn't be allowed to use as the key in a hash-based collection since it isn't known to be hash resistant.
|
||||
// This method can of course be overridden to add more hash collision resistant type support, or the deserializing party can indicate that the data is Trusted
|
||||
// so that this method doesn't even get called.
|
||||
return result ?? throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
|
||||
throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -232,25 +247,41 @@ namespace MessagePack
|
|||
/// </remarks>
|
||||
protected virtual MessagePackSecurity Clone() => new MessagePackSecurity(this);
|
||||
|
||||
private static int SecureHash<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Span<T> span = stackalloc T[1];
|
||||
span[0] = value;
|
||||
return unchecked((int)Hash.Compute(MemoryMarshal.Cast<T, byte>(span)));
|
||||
}
|
||||
|
||||
private static int SecureHash(ReadOnlySpan<byte> data) => unchecked((int)Hash.Compute(data));
|
||||
|
||||
/// <summary>
|
||||
/// A hash collision resistant implementation of <see cref="IEqualityComparer{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of key that will be hashed.</typeparam>
|
||||
private class CollisionResistantHasher<T> : IEqualityComparer<T>, IEqualityComparer
|
||||
private abstract class CollisionResistantHasher<T> : IEqualityComparer<T>, IEqualityComparer
|
||||
{
|
||||
internal static readonly CollisionResistantHasher<T> Instance = new CollisionResistantHasher<T>();
|
||||
public bool Equals(T? x, T? y) => EqualityComparer<T?>.Default.Equals(x, y);
|
||||
|
||||
public bool Equals(T x, T y) => EqualityComparer<T>.Default.Equals(x, y);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => ((IEqualityComparer)EqualityComparer<T>.Default).Equals(x, y);
|
||||
bool IEqualityComparer.Equals(object? x, object? y) => ((IEqualityComparer)EqualityComparer<T>.Default).Equals(x, y);
|
||||
|
||||
public int GetHashCode(object obj) => this.GetHashCode((T)obj);
|
||||
|
||||
public virtual int GetHashCode(T value) => HashCode.Combine(value);
|
||||
public abstract int GetHashCode(T value);
|
||||
}
|
||||
|
||||
private class CollisionResistantHasherUnmanaged<T> : CollisionResistantHasher<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
internal static readonly CollisionResistantHasherUnmanaged<T> Instance = new();
|
||||
|
||||
public override int GetHashCode(T value) => SecureHash(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A special hash-resistent equality comparer that defers picking the actual implementation
|
||||
/// A special hash-resistant equality comparer that defers picking the actual implementation
|
||||
/// till it can check the runtime type of each value to be hashed.
|
||||
/// </summary>
|
||||
private class ObjectFallbackEqualityComparer : IEqualityComparer<object>, IEqualityComparer
|
||||
|
@ -264,9 +295,9 @@ namespace MessagePack
|
|||
this.security = security ?? throw new ArgumentNullException(nameof(security));
|
||||
}
|
||||
|
||||
bool IEqualityComparer<object>.Equals(object x, object y) => EqualityComparer<object>.Default.Equals(x, y);
|
||||
bool IEqualityComparer<object>.Equals(object? x, object? y) => EqualityComparer<object?>.Default.Equals(x, y);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => ((IEqualityComparer)EqualityComparer<object>.Default).Equals(x, y);
|
||||
bool IEqualityComparer.Equals(object? x, object? y) => ((IEqualityComparer)EqualityComparer<object>.Default).Equals(x, y);
|
||||
|
||||
public int GetHashCode(object value)
|
||||
{
|
||||
|
@ -284,15 +315,16 @@ namespace MessagePack
|
|||
return value.GetHashCode();
|
||||
}
|
||||
|
||||
if (!equalityComparerCache.TryGetValue(valueType, out IEqualityComparer equalityComparer))
|
||||
if (!equalityComparerCache.TryGetValue(valueType, out IEqualityComparer? equalityComparer))
|
||||
{
|
||||
try
|
||||
{
|
||||
equalityComparer = (IEqualityComparer)GetHashCollisionResistantEqualityComparerOpenGenericMethod.Value.MakeGenericMethod(valueType).Invoke(this.security, Array.Empty<object>());
|
||||
equalityComparer = (IEqualityComparer)GetHashCollisionResistantEqualityComparerOpenGenericMethod.Value.MakeGenericMethod(valueType).Invoke(this.security, Array.Empty<object>())!;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
catch (TargetInvocationException ex) when (ex.InnerException is not null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
throw null!; // not reachable
|
||||
}
|
||||
|
||||
equalityComparerCache.TryAdd(valueType, equalityComparer);
|
||||
|
@ -302,116 +334,69 @@ namespace MessagePack
|
|||
}
|
||||
}
|
||||
|
||||
private class UInt64EqualityComparer : CollisionResistantHasher<ulong>
|
||||
private class SingleEqualityComparer : CollisionResistantHasherUnmanaged<float>
|
||||
{
|
||||
internal static new readonly UInt64EqualityComparer Instance = new UInt64EqualityComparer();
|
||||
|
||||
public override int GetHashCode(ulong value) => HashCode.Combine((uint)(value >> 32), unchecked((uint)value));
|
||||
}
|
||||
|
||||
private class Int64EqualityComparer : CollisionResistantHasher<long>
|
||||
{
|
||||
internal static new readonly Int64EqualityComparer Instance = new Int64EqualityComparer();
|
||||
|
||||
public override int GetHashCode(long value) => HashCode.Combine((int)(value >> 32), unchecked((int)value));
|
||||
}
|
||||
|
||||
private class SingleEqualityComparer : CollisionResistantHasher<float>
|
||||
{
|
||||
internal static new readonly SingleEqualityComparer Instance = new SingleEqualityComparer();
|
||||
internal static new readonly SingleEqualityComparer Instance = new();
|
||||
|
||||
public override unsafe int GetHashCode(float value)
|
||||
=> base.GetHashCode(value switch
|
||||
{
|
||||
// Special check for 0.0 so that the hash of 0.0 and -0.0 will equal.
|
||||
if (value == 0.0f)
|
||||
{
|
||||
return HashCode.Combine(0);
|
||||
0.0f => 0, // Special check for 0.0 so that the hash of 0.0 and -0.0 will equal.
|
||||
float.NaN => float.NaN, // Standardize on the binary representation of NaN prior to hashing.
|
||||
_ => value,
|
||||
});
|
||||
}
|
||||
|
||||
// Standardize on the binary representation of NaN prior to hashing.
|
||||
if (float.IsNaN(value))
|
||||
private class DoubleEqualityComparer : CollisionResistantHasherUnmanaged<double>
|
||||
{
|
||||
value = float.NaN;
|
||||
}
|
||||
|
||||
int l = *(int*)&value;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
private class DoubleEqualityComparer : CollisionResistantHasher<double>
|
||||
{
|
||||
internal static new readonly DoubleEqualityComparer Instance = new DoubleEqualityComparer();
|
||||
internal static new readonly DoubleEqualityComparer Instance = new();
|
||||
|
||||
public override unsafe int GetHashCode(double value)
|
||||
=> base.GetHashCode(value switch
|
||||
{
|
||||
// Special check for 0.0 so that the hash of 0.0 and -0.0 will equal.
|
||||
if (value == 0.0)
|
||||
{
|
||||
return HashCode.Combine(0);
|
||||
0.0 => 0, // Special check for 0.0 so that the hash of 0.0 and -0.0 will equal.
|
||||
double.NaN => double.NaN, // Standardize on the binary representation of NaN prior to hashing.
|
||||
_ => value,
|
||||
});
|
||||
}
|
||||
|
||||
// Standardize on the binary representation of NaN prior to hashing.
|
||||
if (double.IsNaN(value))
|
||||
private class DateTimeEqualityComparer : CollisionResistantHasherUnmanaged<DateTime>
|
||||
{
|
||||
value = double.NaN;
|
||||
internal static new readonly DateTimeEqualityComparer Instance = new();
|
||||
|
||||
public override unsafe int GetHashCode(DateTime value) => SecureHash(value.Ticks);
|
||||
}
|
||||
|
||||
long l = *(long*)&value;
|
||||
return HashCode.Combine((int)(l >> 32), unchecked((int)l));
|
||||
}
|
||||
}
|
||||
|
||||
private class GuidEqualityComparer : CollisionResistantHasher<Guid>
|
||||
private class DateTimeOffsetEqualityComparer : CollisionResistantHasherUnmanaged<DateTimeOffset>
|
||||
{
|
||||
internal static new readonly GuidEqualityComparer Instance = new GuidEqualityComparer();
|
||||
internal static new readonly DateTimeOffsetEqualityComparer Instance = new();
|
||||
|
||||
public override unsafe int GetHashCode(Guid value)
|
||||
{
|
||||
var hash = default(HashCode);
|
||||
int* pGuid = (int*)&value;
|
||||
for (int i = 0; i < sizeof(Guid) / sizeof(int); i++)
|
||||
{
|
||||
hash.Add(pGuid[i]);
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
public override unsafe int GetHashCode(DateTimeOffset value) => SecureHash(value.UtcDateTime.Ticks);
|
||||
}
|
||||
|
||||
private class StringEqualityComparer : CollisionResistantHasher<string>
|
||||
{
|
||||
internal static new readonly StringEqualityComparer Instance = new StringEqualityComparer();
|
||||
internal static readonly StringEqualityComparer Instance = new();
|
||||
|
||||
public override int GetHashCode(string value)
|
||||
{
|
||||
#if NETCOREAPP
|
||||
// .NET Core already has a secure string hashing function. Just use it.
|
||||
return value?.GetHashCode() ?? 0;
|
||||
#else
|
||||
var hash = default(HashCode);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
// The Cast call could result in OverflowException at runtime if value is greater than 1bn chars in length.
|
||||
return SecureHash(MemoryMarshal.Cast<char, byte>(value.AsSpan()));
|
||||
}
|
||||
}
|
||||
|
||||
private class CollisionResistantEnumHasher<TEnum, TUnderlying> : IEqualityComparer<TEnum>, IEqualityComparer
|
||||
where TUnderlying : unmanaged
|
||||
{
|
||||
hash.Add(value[i]);
|
||||
}
|
||||
internal static readonly CollisionResistantEnumHasher<TEnum, TUnderlying> Instance = new();
|
||||
|
||||
return hash.ToHashCode();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
public bool Equals(TEnum? x, TEnum? y) => EqualityComparer<TEnum?>.Default.Equals(x, y);
|
||||
|
||||
private class DateTimeEqualityComparer : CollisionResistantHasher<DateTime>
|
||||
{
|
||||
internal static new readonly DateTimeEqualityComparer Instance = new DateTimeEqualityComparer();
|
||||
public int GetHashCode(TEnum obj) => SecureHash(Unsafe.As<TEnum, TUnderlying>(ref obj));
|
||||
|
||||
public override unsafe int GetHashCode(DateTime value) => HashCode.Combine((int)(value.Ticks >> 32), unchecked((int)value.Ticks), value.Kind);
|
||||
}
|
||||
bool IEqualityComparer.Equals(object? x, object? y) => x is TEnum e1 && y is TEnum e2 && Equals(e1, e2);
|
||||
|
||||
private class DateTimeOffsetEqualityComparer : CollisionResistantHasher<DateTimeOffset>
|
||||
{
|
||||
internal static new readonly DateTimeOffsetEqualityComparer Instance = new DateTimeOffsetEqualityComparer();
|
||||
|
||||
public override unsafe int GetHashCode(DateTimeOffset value) => HashCode.Combine((int)(value.UtcTicks >> 32), unchecked((int)value.UtcTicks));
|
||||
int IEqualityComparer.GetHashCode(object obj) => GetHashCode((TEnum)obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
// Copyright (c) All contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
//// Originally from: https://github.com/paya-cz/siphash
|
||||
//// Author: Pavel Werl
|
||||
//// License: Public Domain
|
||||
//// SipHash website: https://131002.net/siphash/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace MessagePack
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the <see href="https://en.wikipedia.org/wiki/SipHash">SipHash pseudo-random function</see>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is immutable and thread-safe.
|
||||
/// </remarks>
|
||||
internal class SipHash
|
||||
{
|
||||
/// <summary>
|
||||
/// Part of the initial 256-bit internal state.
|
||||
/// </summary>
|
||||
private readonly ulong initialState0;
|
||||
|
||||
/// <summary>
|
||||
/// Part of the initial 256-bit internal state.
|
||||
/// </summary>
|
||||
private readonly ulong initialState1;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SipHash"/> class using a random key.</summary>
|
||||
public SipHash()
|
||||
{
|
||||
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
|
||||
#if SPAN_BUILTIN
|
||||
Span<byte> key = stackalloc byte[16];
|
||||
rng.GetBytes(key);
|
||||
#else
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(16);
|
||||
rng.GetBytes(buffer, 0, 16);
|
||||
Span<byte> key = buffer;
|
||||
#endif
|
||||
|
||||
this.initialState0 = 0x736f6d6570736575UL ^ BinaryPrimitives.ReadUInt64LittleEndian(key);
|
||||
this.initialState1 = 0x646f72616e646f6dUL ^ BinaryPrimitives.ReadUInt64LittleEndian(key.Slice(sizeof(ulong)));
|
||||
|
||||
#if !SPAN_BUILTIN
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SipHash"/> class using the specified 128-bit key.</summary>
|
||||
/// <param name="key">Key for the SipHash pseudo-random function. Must be exactly 16 bytes long.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="key"/> is not exactly 16 bytes long (128 bits).</exception>
|
||||
public SipHash(ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
{
|
||||
throw new ArgumentException("SipHash key must be exactly 128-bit long (16 bytes).", nameof(key));
|
||||
}
|
||||
|
||||
this.initialState0 = 0x736f6d6570736575UL ^ BinaryPrimitives.ReadUInt64LittleEndian(key);
|
||||
this.initialState1 = 0x646f72616e646f6dUL ^ BinaryPrimitives.ReadUInt64LittleEndian(key.Slice(sizeof(ulong)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a 128-bit SipHash key.
|
||||
/// </summary>
|
||||
/// <param name="key">The 16-byte buffer that receives the key originally provided to the constructor.</param>
|
||||
public void GetKey(Span<byte> key)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
{
|
||||
throw new ArgumentException("SipHash key must be exactly 128-bit long (16 bytes).", nameof(key));
|
||||
}
|
||||
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(key, this.initialState0 ^ 0x736f6d6570736575UL);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(key.Slice(sizeof(ulong)), this.initialState1 ^ 0x646f72616e646f6dUL);
|
||||
}
|
||||
|
||||
/// <summary>Computes 64-bit SipHash tag for the specified message.</summary>
|
||||
/// <param name="data">The byte array for which to computer SipHash tag.</param>
|
||||
/// <returns>Returns 64-bit (8 bytes) SipHash tag.</returns>
|
||||
public long Compute(ReadOnlySpan<byte> data)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// SipHash internal state
|
||||
ulong v0 = this.initialState0;
|
||||
ulong v1 = this.initialState1;
|
||||
|
||||
// It is faster to load the initialStateX fields from memory again than to reference v0 and v1:
|
||||
ulong v2 = 0x1F160A001E161714UL ^ this.initialState0;
|
||||
ulong v3 = 0x100A160317100A1EUL ^ this.initialState1;
|
||||
|
||||
// We process data in 64-bit blocks
|
||||
ulong block;
|
||||
|
||||
// The last 64-bit block of data
|
||||
int finalBlockPosition = data.Length & ~7;
|
||||
|
||||
// Process the input data in blocks of 64 bits
|
||||
for (int blockPosition = 0; blockPosition < finalBlockPosition; blockPosition += sizeof(ulong))
|
||||
{
|
||||
block = MemoryMarshal.Read<ulong>(data.Slice(blockPosition));
|
||||
|
||||
v3 ^= block;
|
||||
|
||||
// Round 1
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
// Round 2
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
v0 ^= block;
|
||||
}
|
||||
|
||||
// Load the remaining bytes
|
||||
block = (ulong)data.Length << 56;
|
||||
switch (data.Length & 7)
|
||||
{
|
||||
case 7:
|
||||
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition + 4)) << 32 | (ulong)data[finalBlockPosition + 6] << 48;
|
||||
break;
|
||||
case 6:
|
||||
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition + 4)) << 32;
|
||||
break;
|
||||
case 5:
|
||||
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)data[finalBlockPosition + 4] << 32;
|
||||
break;
|
||||
case 4:
|
||||
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition));
|
||||
break;
|
||||
case 3:
|
||||
block |= MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition)) | (ulong)data[finalBlockPosition + 2] << 16;
|
||||
break;
|
||||
case 2:
|
||||
block |= MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition));
|
||||
break;
|
||||
case 1:
|
||||
block |= data[finalBlockPosition];
|
||||
break;
|
||||
}
|
||||
|
||||
// Process the final block
|
||||
{
|
||||
v3 ^= block;
|
||||
|
||||
// Round 1
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
// Round 2
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
v0 ^= block;
|
||||
v2 ^= 0xff;
|
||||
}
|
||||
|
||||
// 4 finalization rounds
|
||||
{
|
||||
// Round 1
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
// Round 2
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
// Round 3
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
|
||||
// Round 4
|
||||
v0 += v1;
|
||||
v2 += v3;
|
||||
v1 = v1 << 13 | v1 >> 51;
|
||||
v3 = v3 << 16 | v3 >> 48;
|
||||
v1 ^= v0;
|
||||
v3 ^= v2;
|
||||
v0 = v0 << 32 | v0 >> 32;
|
||||
v2 += v1;
|
||||
v0 += v3;
|
||||
v1 = v1 << 17 | v1 >> 47;
|
||||
v3 = v3 << 21 | v3 >> 43;
|
||||
v1 ^= v2;
|
||||
v3 ^= v0;
|
||||
v2 = v2 << 32 | v2 >> 32;
|
||||
}
|
||||
|
||||
return (long)((v0 ^ v1) ^ (v2 ^ v3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c78b7a26485c4131ae8bcfae61e01435
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -125,11 +125,8 @@ public class MessagePackSecurityTests
|
|||
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt16Enum>());
|
||||
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt32Enum>());
|
||||
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt32Enum>());
|
||||
|
||||
// Supporting enums with backing integers that exceed 32-bits would likely require Ref.Emit of new types
|
||||
// since C# doesn't let us cast T to the underlying int type.
|
||||
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt64Enum>());
|
||||
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt64Enum>());
|
||||
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt64Enum>());
|
||||
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt64Enum>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) All contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MessagePack;
|
||||
using Xunit;
|
||||
|
||||
public class SipHashTests
|
||||
{
|
||||
// Test battery taken from: https://github.com/veorq/SipHash/blob/master/main.c
|
||||
private static readonly uint[][] Vectors = new[]
|
||||
{
|
||||
new uint[] { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72 },
|
||||
new uint[] { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74 },
|
||||
new uint[] { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d },
|
||||
new uint[] { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85 },
|
||||
new uint[] { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf },
|
||||
new uint[] { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18 },
|
||||
new uint[] { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb },
|
||||
new uint[] { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab },
|
||||
new uint[] { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93 },
|
||||
new uint[] { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e },
|
||||
new uint[] { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a },
|
||||
new uint[] { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4 },
|
||||
new uint[] { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75 },
|
||||
new uint[] { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14 },
|
||||
new uint[] { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7 },
|
||||
new uint[] { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1 },
|
||||
new uint[] { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f },
|
||||
new uint[] { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69 },
|
||||
new uint[] { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b },
|
||||
new uint[] { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb },
|
||||
new uint[] { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe },
|
||||
new uint[] { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0 },
|
||||
new uint[] { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93 },
|
||||
new uint[] { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8 },
|
||||
new uint[] { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8 },
|
||||
new uint[] { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc },
|
||||
new uint[] { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17 },
|
||||
new uint[] { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f },
|
||||
new uint[] { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde },
|
||||
new uint[] { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6 },
|
||||
new uint[] { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad },
|
||||
new uint[] { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32 },
|
||||
new uint[] { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71 },
|
||||
new uint[] { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7 },
|
||||
new uint[] { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12 },
|
||||
new uint[] { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15 },
|
||||
new uint[] { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31 },
|
||||
new uint[] { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02 },
|
||||
new uint[] { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca },
|
||||
new uint[] { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a },
|
||||
new uint[] { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e },
|
||||
new uint[] { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad },
|
||||
new uint[] { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18 },
|
||||
new uint[] { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4 },
|
||||
new uint[] { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9 },
|
||||
new uint[] { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9 },
|
||||
new uint[] { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb },
|
||||
new uint[] { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0 },
|
||||
new uint[] { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6 },
|
||||
new uint[] { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7 },
|
||||
new uint[] { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee },
|
||||
new uint[] { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1 },
|
||||
new uint[] { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a },
|
||||
new uint[] { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81 },
|
||||
new uint[] { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f },
|
||||
new uint[] { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24 },
|
||||
new uint[] { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7 },
|
||||
new uint[] { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea },
|
||||
new uint[] { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60 },
|
||||
new uint[] { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66 },
|
||||
new uint[] { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c },
|
||||
new uint[] { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f },
|
||||
new uint[] { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5 },
|
||||
new uint[] { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95 },
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void TestBattery_Aligned() => TestHelper(false);
|
||||
|
||||
[Fact]
|
||||
public void TestBattery_Unaligned() => TestHelper(true);
|
||||
|
||||
private void TestHelper(bool unaligned)
|
||||
{
|
||||
// 128-bit key
|
||||
byte[] key = Enumerable.Range(0, 16).Select(x => (byte)x).ToArray();
|
||||
|
||||
// SipHash initialized with the key
|
||||
SipHash prf = new SipHash(key);
|
||||
|
||||
// Perform the test battery
|
||||
Span<byte> message = new byte[Vectors.Length + 1];
|
||||
if (unaligned)
|
||||
{
|
||||
message = message.Slice(1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Vectors.Length; i++)
|
||||
{
|
||||
message[i] = (byte)i;
|
||||
|
||||
// Compute the tag
|
||||
long actual = prf.Compute(message.Slice(0, i));
|
||||
|
||||
// Get the target tag
|
||||
long expected = BitConverter.ToInt64(Vectors[i].Select(x => (byte)x).ToArray(), 0);
|
||||
|
||||
if (expected != actual)
|
||||
{
|
||||
throw new Exception($"Test vector failed for {i:N}-byte message!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c71df5af28304fe2991b6aaba359db90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -11,6 +11,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\Internal\FarmHash.cs" Link="Link\FarmHash.cs" />
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\Internal\ILGeneratorExtensions.cs" Link="Link\ILGeneratorExtensions.cs" />
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\SipHash.cs" Link="SipHash.cs" />
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\StringEncoding.cs" Link="Link\StringEncoding.cs" />
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\ThisLibraryExtensionTypeCodes.cs" Link="ExtensionTests\ThisLibraryExtensionTypeCodes.cs" />
|
||||
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\Tests\ShareTests\**\*.cs" Exclude="..\..\src\MessagePack.UnityClient\Assets\Scripts\Tests\ShareTests\T4\**\*.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче