diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5c94816e..21b02470 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 5.2.31 +* fixed Hash Computations for non-primitive types +* added `AsByteSpan` extension method for System.Array/string in >=net6.0 + ### 5.2.30 * removed UnsafeCoerce usages and several other net6.0+ fixes diff --git a/src/Aardvark.Base/Extensions/ArrayExtensions.cs b/src/Aardvark.Base/Extensions/ArrayExtensions.cs index 17a6bd28..cc1c8a85 100644 --- a/src/Aardvark.Base/Extensions/ArrayExtensions.cs +++ b/src/Aardvark.Base/Extensions/ArrayExtensions.cs @@ -2272,8 +2272,8 @@ public static void CopyTo(this Array input, int offset, int length, IntPtr targe try { - var dataSize = Buffer.ByteLength(input); - var typeSize = dataSize / input.Length; + var typeSize = input.GetType().GetElementType().GetCLRSize(); + var dataSize = input.Length * typeSize; unsafe { @@ -2304,8 +2304,8 @@ public static void CopyTo(this IntPtr input, Array target, int offset, int lengt try { - var dataSize = Buffer.ByteLength(target); - var typeSize = dataSize / target.Length; + var typeSize = target.GetType().GetElementType().GetCLRSize(); + var dataSize = target.Length * typeSize; unsafe { @@ -2338,6 +2338,63 @@ public static void CopyTo(this IntPtr input, IntPtr target, int size) }; } +#if NET6_0_OR_GREATER + public static Span AsByteSpan(this Array data) + { + var elementSize = data.GetType().GetElementType().GetCLRSize(); + var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(data), data.Length * elementSize); + return span; + } + + public static Span AsByteSpan(this T[] data) where T : struct + { + var span = new Span(data); + return MemoryMarshal.AsBytes(span); + } + + public static ReadOnlySpan AsByteSpan(this string data) + { + return MemoryMarshal.AsBytes(data.AsSpan()); + } +#endif + + internal static unsafe T UseAsStream(this Array data, Func action) + { + var gc = GCHandle.Alloc(data, GCHandleType.Pinned); + var l = data.GetType().GetElementType().GetCLRSize() * data.Length; + try + { + using (var stream = + new UnmanagedMemoryStream(((byte*)gc.AddrOfPinnedObject())!, l, l, FileAccess.Read)) + { + return action(stream); + } + } + finally + { + gc.Free(); + } + } + + internal static unsafe void UseAsStream(this Array data, Action action) + { + var gc = GCHandle.Alloc(data, GCHandleType.Pinned); + var l = data.GetType().GetElementType().GetCLRSize() * data.Length; + try + { + using (var stream = + new UnmanagedMemoryStream(((byte*)gc.AddrOfPinnedObject())!, l, l, FileAccess.Read)) + { + action(stream); + } + } + finally + { + gc.Free(); + } + } + + #endregion #region Hashes @@ -2348,45 +2405,41 @@ public static void CopyTo(this IntPtr input, IntPtr target, int size) /// 128bit/16byte data hash public static byte[] ComputeMD5Hash(this byte[] data) { - using (var md5 = SHA1.Create()) - { - var bytes = md5.ComputeHash(data); - Array.Resize(ref bytes, 16); - return bytes; +#if NET6_0_OR_GREATER + var hash = SHA1.HashData(data); + Array.Resize(ref hash, 16); + return hash; +#else + using (var sha = SHA1.Create()) + { + var hash = sha.ComputeHash(data); + Array.Resize(ref hash, 16); + return hash; } +#endif } + + /// /// Computes the MD5 hash of the data array. /// /// 128bit/16byte data hash - public static unsafe byte[] ComputeMD5Hash(this Array data) + public static byte[] ComputeMD5Hash(this Array data) { - if (data == null) return null; +#if NET6_0_OR_GREATER + var hash = SHA1.HashData(data.AsByteSpan()); + Array.Resize(ref hash, 16); + return hash; +#else - var gc = GCHandle.Alloc(data, GCHandleType.Pinned); - - var byteLength = Buffer.ByteLength(data); - try + using(var sha = SHA1.Create()) { - var ptr = gc.AddrOfPinnedObject(); - byte[] hash = null; - using (var md5 = SHA1.Create()) - { - using (var s = new UnmanagedMemoryStream((byte*)ptr, byteLength, byteLength, - FileAccess.Read)) - { - hash = md5.ComputeHash(s); - } - } - + var hash = data.UseAsStream((stream) => sha.ComputeHash(stream)); Array.Resize(ref hash, 16); return hash; } - finally - { - gc.Free(); - } +#endif } /// @@ -2395,7 +2448,13 @@ public static unsafe byte[] ComputeMD5Hash(this Array data) /// public static byte[] ComputeMD5Hash(this string s) { +#if NET6_0_OR_GREATER + var hash = SHA1.HashData(s.AsByteSpan()); + Array.Resize(ref hash, 16); + return hash; +#else return Encoding.Unicode.GetBytes(s).ComputeMD5Hash(); +#endif } /// @@ -2404,38 +2463,32 @@ public static byte[] ComputeMD5Hash(this string s) /// 160bit/20byte data hash public static byte[] ComputeSHA1Hash(this byte[] data) { - using (var sha1 = SHA1.Create()) - return sha1.ComputeHash(data); +#if NET6_0_OR_GREATER + return SHA1.HashData(data); +#else + using (var sha = SHA1.Create()) + { + var hash = sha.ComputeHash(data); + return hash; + } +#endif } /// /// Computes the SHA1 hash of the data array. /// /// 160bit/20byte data hash - public static unsafe byte[] ComputeSHA1Hash(this Array data) + public static byte[] ComputeSHA1Hash(this Array data) { - if (data == null) return null; - - var gc = GCHandle.Alloc(data, GCHandleType.Pinned); - var byteLength = Buffer.ByteLength(data); - try +#if NET6_0_OR_GREATER + return SHA1.HashData(data.AsByteSpan()); +#else + using(var sha = SHA1.Create()) { - var ptr = gc.AddrOfPinnedObject(); - byte[] hash = null; - using (var sha1 = SHA1.Create()) - { - using (var s = new UnmanagedMemoryStream((byte*)ptr, byteLength, byteLength, - FileAccess.Read)) - { - hash = sha1.ComputeHash(s); - } - } - return hash; - } - finally - { - gc.Free(); + return data.UseAsStream((stream) => sha.ComputeHash(stream)); } +#endif + } /// @@ -2444,7 +2497,11 @@ public static unsafe byte[] ComputeSHA1Hash(this Array data) /// public static byte[] ComputeSHA1Hash(this string s) { +#if NET6_0_OR_GREATER + return SHA1.HashData(s.AsByteSpan()); +#else return Encoding.Unicode.GetBytes(s).ComputeSHA1Hash(); +#endif } /// @@ -2453,38 +2510,31 @@ public static byte[] ComputeSHA1Hash(this string s) /// 256bit/32byte data hash public static byte[] ComputeSHA256Hash(this byte[] data) { - using (var sha256 = SHA256.Create()) - return sha256.ComputeHash(data); +#if NET6_0_OR_GREATER + return SHA256.HashData(data); +#else + using (var sha = SHA256.Create()) + { + var hash = sha.ComputeHash(data); + return hash; + } +#endif } /// /// Computes the SHA256 hash of the data array. /// /// 256bit/32byte data hash - public static unsafe byte[] ComputeSHA256Hash(this Array data) + public static byte[] ComputeSHA256Hash(this Array data) { - if (data == null) return null; - - var gc = GCHandle.Alloc(data, GCHandleType.Pinned); - var byteLength = Buffer.ByteLength(data); - try +#if NET6_0_OR_GREATER + return SHA256.HashData(data.AsByteSpan()); +#else + using(var sha = SHA256.Create()) { - var ptr = gc.AddrOfPinnedObject(); - byte[] hash = null; - using (var sha256 = SHA256.Create()) - { - using (var s = new UnmanagedMemoryStream((byte*)ptr, byteLength, byteLength, - FileAccess.Read)) - { - hash = sha256.ComputeHash(s); - } - } - return hash; - } - finally - { - gc.Free(); + return data.UseAsStream((stream) => sha.ComputeHash(stream)); } +#endif } /// @@ -2493,7 +2543,11 @@ public static unsafe byte[] ComputeSHA256Hash(this Array data) /// 256bit/32byte data hash public static byte[] ComputeSHA256Hash(this string s) { +#if NET6_0_OR_GREATER + return SHA256.HashData(s.AsByteSpan()); +#else return Encoding.Unicode.GetBytes(s).ComputeSHA256Hash(); +#endif } /// @@ -2502,38 +2556,31 @@ public static byte[] ComputeSHA256Hash(this string s) /// 512bit/64byte data hash public static byte[] ComputeSHA512Hash(this byte[] data) { - using (var sha512 = SHA512.Create()) - return sha512.ComputeHash(data); +#if NET6_0_OR_GREATER + return SHA512.HashData(data); +#else + using (var sha = SHA512.Create()) + { + var hash = sha.ComputeHash(data); + return hash; + } +#endif } /// /// Computes the SHA512 hash of the data array. /// /// 512bit/64byte data hash - public static unsafe byte[] ComputeSHA512Hash(this Array data) + public static byte[] ComputeSHA512Hash(this Array data) { - if (data == null) return null; - - var gc = GCHandle.Alloc(data, GCHandleType.Pinned); - var byteLength = Buffer.ByteLength(data); - try +#if NET6_0_OR_GREATER + return SHA512.HashData(data.AsByteSpan()); +#else + using(var sha = SHA512.Create()) { - var ptr = gc.AddrOfPinnedObject(); - byte[] hash = null; - using (var sha512 = SHA512.Create()) - { - using (var s = new UnmanagedMemoryStream((byte*)ptr, byteLength, byteLength, - FileAccess.Read)) - { - hash = sha512.ComputeHash(s); - } - } - return hash; - } - finally - { - gc.Free(); + return data.UseAsStream((stream) => sha.ComputeHash(stream)); } +#endif } /// @@ -2542,7 +2589,11 @@ public static unsafe byte[] ComputeSHA512Hash(this Array data) /// 512bit/64byte data hash public static byte[] ComputeSHA512Hash(this string s) { +#if NET6_0_OR_GREATER + return SHA512.HashData(s.AsByteSpan()); +#else return Encoding.Unicode.GetBytes(s).ComputeSHA512Hash(); +#endif } /// @@ -2558,29 +2609,17 @@ public static uint ComputeAdler32Checksum(this byte[] data) /// /// Computes a checksum of the data array using the Adler-32 algorithm (). /// - public static unsafe uint ComputeAdler32Checksum(this Array data) + public static uint ComputeAdler32Checksum(this Array data) { var a = new Adler32(); - if (data != null) { - var gc = GCHandle.Alloc(data, GCHandleType.Pinned); - var byteLength = Buffer.ByteLength(data); - try - { - var ptr = gc.AddrOfPinnedObject(); - using (var s = new UnmanagedMemoryStream((byte*)ptr.ToPointer(), byteLength, byteLength, - FileAccess.Read)) - { - a.Update(s); - } - } - finally - { - gc.Free(); - } +#if NET6_0_OR_GREATER + a.Update(data.AsByteSpan()); +#else + data.UseAsStream((stream) => a.Update(stream)); +#endif } - return a.Checksum; } @@ -2589,7 +2628,14 @@ public static unsafe uint ComputeAdler32Checksum(this Array data) /// public static uint ComputeAdler32Checksum(this string s) { - return Encoding.Unicode.GetBytes(s).ComputeAdler32Checksum(); + var a = new Adler32(); + +#if NET6_0_OR_GREATER + a.Update(s.AsByteSpan()); +#else + a.Update(Encoding.Unicode.GetBytes(s)); +#endif + return a.Checksum; } #endregion diff --git a/src/Aardvark.Base/Extensions/UnsafeCoerce.cs b/src/Aardvark.Base/Extensions/UnsafeCoerce.cs index 59a07b7b..e76e365c 100644 --- a/src/Aardvark.Base/Extensions/UnsafeCoerce.cs +++ b/src/Aardvark.Base/Extensions/UnsafeCoerce.cs @@ -32,8 +32,7 @@ private static IntPtr GetTypeId() return typeId; } - [Obsolete("breaks net8.0+")] - internal static int GetCLRSize(Type t) + public static int GetCLRSize(this Type t) { // TODO: somehow make use of sizeof operator -> requires compile time type -> cannot use ILGenerator in .net standard if (t == typeof(char)) return 2; // Marshal.SizeOf = 1 diff --git a/src/Aardvark.Base/Math/Base/Adler32.cs b/src/Aardvark.Base/Math/Base/Adler32.cs index 6c70e686..9edeabf2 100644 --- a/src/Aardvark.Base/Math/Base/Adler32.cs +++ b/src/Aardvark.Base/Math/Base/Adler32.cs @@ -149,5 +149,44 @@ public void Update(byte[] buffer, int offset, int length) m_checksum = (s2 << 16) | s1; } + + + /// + /// Updates the checksum with the bytes taken from the array. + /// + public void Update(ReadOnlySpan buffer) + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + uint s1 = m_checksum & 0xFFFF; + uint s2 = m_checksum >> 16; + var offset = 0; + var length = buffer.Length; + + while (length > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + int n = 3800; + if (n > length) n = length; + + length -= n; + + while (--n >= 0) + { + s1 = s1 + (uint)(buffer[offset++] & 0xFF); + s2 = s2 + s1; + } + + s1 %= BASE; + s2 %= BASE; + } + m_checksum = (s2 << 16) | s1; + + } } } diff --git a/src/Tests/Aardvark.Base.Tests/Extensions/Hashes.cs b/src/Tests/Aardvark.Base.Tests/Extensions/Hashes.cs index 2b2bf312..1976c738 100644 --- a/src/Tests/Aardvark.Base.Tests/Extensions/Hashes.cs +++ b/src/Tests/Aardvark.Base.Tests/Extensions/Hashes.cs @@ -16,6 +16,17 @@ public static void ArrayHashExtensions() { var floats = new float[10].SetByIndex(i => i); + + var inty = new int[20].SetByIndex(i => i); + var v2iy = new V2i[10].SetByIndex(i => new V2i(2 * i, 2 * i + 1)); + + var intHash = inty.ComputeSHA1Hash().ToHex(); + var v2iHash = v2iy.ComputeSHA1Hash().ToHex(); + + Assert.IsTrue(intHash == v2iHash); + + + var md5 = floats.ComputeMD5Hash(); Report.Line("MD5: {0}", md5.ToHex()); Assert.IsTrue(md5.Length == 16);