From 997ea490503a42b17571e8e5539f9b04ef88d3a6 Mon Sep 17 00:00:00 2001 From: Matthew Endsley Date: Thu, 17 Dec 2020 15:55:07 -0800 Subject: [PATCH] Add ByteSpan utility --- Hazel/ByteSpan.cs | 182 ++++++++++++++++++++++++++++++++++++ Hazel/ByteSpanExtensions.cs | 112 ++++++++++++++++++++++ Hazel/Hazel.csproj | 2 + 3 files changed, 296 insertions(+) create mode 100644 Hazel/ByteSpan.cs create mode 100644 Hazel/ByteSpanExtensions.cs diff --git a/Hazel/ByteSpan.cs b/Hazel/ByteSpan.cs new file mode 100644 index 0000000..9d33193 --- /dev/null +++ b/Hazel/ByteSpan.cs @@ -0,0 +1,182 @@ +using System; + +namespace Hazel +{ + /// + /// This is a minimal implementation of `System.Span` in .NET 5.0 + /// + public struct ByteSpan + { + private readonly byte[] array_; + + /// + /// Createa a new span object containing an entire array + /// + public ByteSpan(byte[] array) + { + if (array == null) + { + this.array_ = null; + this.Offset = 0; + this.Length = 0; + } + else + { + this.array_ = array; + this.Offset = 0; + this.Length = array.Length; + } + } + + /// + /// Creates a new span object containing a subset of an array + /// + public ByteSpan(byte[] array, int offset, int length) + { + if (array == null) + { + if (offset != 0) + { + throw new ArgumentException("Invalid offset", nameof(offset)); + } + if (length != 0) + { + throw new ArgumentException("Invalid length", nameof(offset)); + } + + this.array_ = null; + this.Offset = 0; + this.Length = 0; + } + else + { + if (offset < 0 || offset > array.Length) + { + throw new ArgumentException("Invalid offset", nameof(offset)); + } + if (length < 0 || (offset + length) > array.Length) + { + throw new ArgumentException("Invalid length", nameof(length)); + } + + this.array_ = array; + this.Offset = offset; + this.Length = length; + } + } + + /// + /// Returns the underlying array. + /// + /// WARNING: This does not return the span, but the entire underlying storage block + /// + public byte[] GetUnderlyingArray() + { + return this.array_; + } + + /// + /// Returns the offset into the underlying array + /// + public int Offset { get; } + + /// + /// Returns the length of the current span + /// + public int Length { get; } + + /// + /// Gets the span element at the specified index + /// + public byte this[int index] + { + get + { + if (index < 0 || index >= this.Length) + { + throw new IndexOutOfRangeException(); + } + + return this.array_[this.Offset + index]; + } + set + { + if (index < 0 || index >= this.Length) + { + throw new IndexOutOfRangeException(); + } + + this.array_[this.Offset + index] = value; + } + } + + /// + /// Create a new span that is a subset of this span [offset, this.Length-offset) + /// + public ByteSpan Slice(int offset) + { + return Slice(offset, this.Length - offset); + } + + /// + /// Create a new span that is a subset of this span [offset, length) + /// + public ByteSpan Slice(int offset, int length) + { + return new ByteSpan(this.array_, this.Offset + offset, length); + } + + /// + /// Copies the contents of the span to an array + /// + public void CopyTo(byte[] array, int offset) + { + CopyTo(new ByteSpan(array, offset, array.Length - offset)); + } + + /// + /// Copies the contents of the span to another span + /// + public void CopyTo(ByteSpan destination) + { + if (destination.Length < this.Length) + { + throw new ArgumentException("Destination span is shorter than source", nameof(destination)); + } + + if (Length > 0) + { + Buffer.BlockCopy(this.array_, this.Offset, destination.array_, destination.Offset, this.Length); + } + } + + /// + /// Create a new array with the contents of this span + /// + public byte[] ToArray() + { + byte[] result = new byte[Length]; + CopyTo(result); + return result; + } + + /// + /// Implicit conversion from byte[] -> ByteSpan + /// + public static implicit operator ByteSpan(byte[] array) + { + return new ByteSpan(array); + } + + /// + /// Retuns an empty span object + /// + public static ByteSpan Empty + { + get + { + return new ByteSpan(null); + } + } + } +} diff --git a/Hazel/ByteSpanExtensions.cs b/Hazel/ByteSpanExtensions.cs new file mode 100644 index 0000000..2749822 --- /dev/null +++ b/Hazel/ByteSpanExtensions.cs @@ -0,0 +1,112 @@ +namespace Hazel +{ + /// + /// Extension functions for (en/de)coding integer values + /// + public static class ByteSpanBigEndianExtensions + { + // Write a 16-bit integer in big-endian format to output[0..2) + public static void WriteBigEndian16(this ByteSpan output, ushort value, int offset = 0) + { + output[offset + 0] = (byte)(value >> 8); + output[offset + 1] = (byte)(value >> 0); + } + + // Write a 24-bit integer in big-endian format to output[0..3) + public static void WriteBigEndian24(this ByteSpan output, uint value, int offset = 0) + { + output[offset + 0] = (byte)(value >> 16); + output[offset + 1] = (byte)(value >> 8); + output[offset + 2] = (byte)(value >> 0); + } + + // Write a 32-bit integer in big-endian format to output[0..4) + public static void WriteBigEndian32(this ByteSpan output, uint value, int offset) + { + output[offset + 0] = (byte)(value >> 24); + output[offset + 1] = (byte)(value >> 16); + output[offset + 2] = (byte)(value >> 8); + output[offset + 3] = (byte)(value >> 0); + } + + // Write a 48-bit integer in big-endian format to output[0..6) + public static void WriteBigEndian48(this ByteSpan output, ulong value, int offset = 0) + { + output[offset + 0] = (byte)(value >> 40); + output[offset + 1] = (byte)(value >> 32); + output[offset + 2] = (byte)(value >> 24); + output[offset + 3] = (byte)(value >> 16); + output[offset + 4] = (byte)(value >> 8); + output[offset + 5] = (byte)(value >> 0); + } + + // Write a 64-bit integer in big-endian format to output[0..8) + public static void WriteBigEndian64(this ByteSpan output, ulong value, int offset = 0) + { + output[offset + 0] = (byte)(value >> 56); + output[offset + 1] = (byte)(value >> 48); + output[offset + 2] = (byte)(value >> 40); + output[offset + 3] = (byte)(value >> 32); + output[offset + 4] = (byte)(value >> 24); + output[offset + 5] = (byte)(value >> 16); + output[offset + 6] = (byte)(value >> 8); + output[offset + 7] = (byte)(value >> 0); + } + + // Read a 16-bit integer in big-endian format from input[0..2) + public static ushort ReadBigEndian16(this ByteSpan input, int offset = 0) + { + ushort value = 0; + value |= (ushort)(input[offset + 0] << 8); + value |= (ushort)(input[offset + 1] << 0); + return value; + } + + // Read a 24-bit integer in big-endian format from input[0..3) + public static uint ReadBigEndian24(this ByteSpan input, int offset = 0) + { + uint value = 0; + value |= (uint)input[offset + 0] << 16; + value |= (uint)input[offset + 1] << 8; + value |= (uint)input[offset + 2] << 0; + return value; + } + + // Read a 48-bit integer in big-endian format from input[0..3) + public static ulong ReadBigEndian48(this ByteSpan input, int offset = 0) + { + ulong value = 0; + value |= (ulong)input[offset + 0] << 40; + value |= (ulong)input[offset + 1] << 32; + value |= (ulong)input[offset + 2] << 24; + value |= (ulong)input[offset + 3] << 16; + value |= (ulong)input[offset + 4] << 8; + value |= (ulong)input[offset + 5] << 0; + return value; + } + } + + public static class ByteSpanLittleEndianExtensions + { + // Read a 24-bit integer in little-endian format from input[0..3) + public static uint ReadLittleEndian24(this ByteSpan input, int offset = 0) + { + uint value = 0; + value |= (uint)input[offset + 0]; + value |= (uint)input[offset + 1] << 8; + value |= (uint)input[offset + 2] << 16; + return value; + } + + // Read a 24-bit integer in little-endian format from input[0..4) + public static uint ReadLittleEndian32(this ByteSpan input, int offset = 0) + { + uint value = 0; + value |= (uint)input[offset + 0]; + value |= (uint)input[offset + 1] << 8; + value |= (uint)input[offset + 2] << 16; + value |= (uint)input[offset + 3] << 24; + return value; + } + } +} diff --git a/Hazel/Hazel.csproj b/Hazel/Hazel.csproj index f6b10e6..79ad6e8 100644 --- a/Hazel/Hazel.csproj +++ b/Hazel/Hazel.csproj @@ -68,6 +68,8 @@ + + -- 2.39.5