diff --git a/src/Cryptography/Secret`1.cs b/src/Cryptography/Secret`1.cs index 7d79dee..f6e5fdd 100644 --- a/src/Cryptography/Secret`1.cs +++ b/src/Cryptography/Secret`1.cs @@ -56,7 +56,11 @@ public sealed class Secret : Secret, IEquatable>, IComp /// /// A secret as array of type /// is +#if NET6_0_OR_GREATER + public Secret(ReadOnlySpan secretSource) +#else public Secret(byte[] secretSource) +#endif { if (secretSource == null) { @@ -76,7 +80,11 @@ public Secret(byte[] secretSource) byte maxMarkByte = secretSource.Length == 1 ? MinMarkByte : MaxMarkByte; byte markByte = (byte)(new Random(buffer[0]).Next(0x01, maxMarkByte) % maxMarkByte); +#if NET6_0_OR_GREATER + byte[] bytes = secretSource.ToArray(); +#else byte[] bytes = (byte[])secretSource.Clone(); +#endif this.secretNumber = bytes.Concat(new[] {markByte}).ToArray(); } @@ -93,7 +101,11 @@ public Secret(Calculator secretSource) : this(secretSource.ByteRepresentation.To /// Secret encoded as base-64 /// For normal text use the implicit cast from to /// is , empty or consists exclusively of white-space characters +#if NET6_0_OR_GREATER + public Secret(ReadOnlySpan encoded) : this(FromBase64CharSpan(encoded)){ } +#else public Secret(string encoded) : this(Convert.FromBase64String(encoded)) { } +#endif /// /// Gets the byte size. @@ -133,25 +145,46 @@ public static implicit operator Calculator(Secret secret) } /// - /// Casts the instance to an instance + /// Casts the instance to an instance. /// public static implicit operator Secret(Calculator calculator) => new Secret(calculator.ByteRepresentation.ToArray()); /// - /// Casts the array instance to an instance + /// Casts the array instance to an instance. /// public static implicit operator Secret(byte[] array) => new Secret(array); +#if NET6_0_OR_GREATER /// - /// Casts the instance to an array instance + /// Casts the to an instance. + /// + public static implicit operator Secret(ReadOnlySpan buffer) => new Secret(buffer); +#endif + + /// + /// Casts the instance to an array instance. /// public static implicit operator byte[](Secret secret) => secret.ToByteArray(); +#if NET6_0_OR_GREATER + /// + /// Casts the instance to a . + /// + public static implicit operator ReadOnlySpan(Secret secret) => secret.AsReadOnlySpan(); +#endif + /// /// Casts the instance to an instance /// public static implicit operator Secret(string secret) => new Secret(Encoding.Unicode.GetBytes(secret)); +#if NET6_0_OR_GREATER + /// + /// Casts the instance to an instance + /// + // public static implicit operator Secret(ReadOnlySpan secret) => new Secret(Encoding.Unicode.GetBytes(secret)); +#endif + /// /// Equality operator /// @@ -257,14 +290,25 @@ public override string ToString() } /// - /// Converts the secret to a byte array + /// Converts the secret to a byte array. /// /// Array of type public byte[] ToByteArray() { - return this.secretNumber.Subset(0, this.secretNumber.Length - MarkByteCount).Clone() as byte[]; + return this.secretNumber.Subset(0, this.secretNumber.Length - MarkByteCount); } +#if NET6_0_OR_GREATER + /// + /// Converts the secret to a . + /// + /// + public ReadOnlySpan AsReadOnlySpan() + { + return this.secretNumber.AsSpan(0, this.secretNumber.Length - MarkByteCount); + } +#endif + /// /// Converts the value of structure to its equivalent representation /// that is encoded with base-64 digits. @@ -274,5 +318,18 @@ public string ToBase64() { return Convert.ToBase64String(this.secretNumber, 0, this.secretNumber.Length - MarkByteCount); } + +#if NET6_0_OR_GREATER + private static ReadOnlySpan FromBase64CharSpan(ReadOnlySpan encoded) + { + if (encoded.IsEmpty || encoded.IsWhiteSpace()) + { + throw new ArgumentException(ErrorMessages.EmptyCollection, nameof(encoded)); + } + + var bytes = new Span(new byte[((encoded.Length * 3) + 3) / 4]); + return Convert.TryFromBase64Chars(encoded, bytes, out int bytesWritten) ? bytes[..bytesWritten] : Span.Empty; + } +#endif } } diff --git a/tests/SecretTest.cs b/tests/SecretTest.cs index d2ecb2b..9ef3e27 100644 --- a/tests/SecretTest.cs +++ b/tests/SecretTest.cs @@ -33,6 +33,7 @@ namespace SecretSharingDotNet.Test { using Cryptography; using Math; + using System; using System.Linq; using System.Numerics; using Xunit; @@ -184,6 +185,17 @@ public void TestSecretToString() Secret secret = secretText; Assert.Equal(secretText, secret.ToString()); } + + [Theory] + [InlineData("UG9seWZvbiB6d2l0c2NoZXJuZCBhw59lbiBNw6R4Y2hlbnMgVsO2Z2VsIFLDvGJlbiwgSm9naHVydCB1bmQgUXVhcms=")] + [InlineData("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu")] + [InlineData("bGlnaHQgd29yaw==")] + [InlineData("bGlnaHQgd29yay4=")] + public void TestSecretToBase64String(string base64Secret) + { + Secret secret = new Secret(base64Secret); + Assert.Equal(base64Secret, secret.ToBase64()); + } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] [Theory] @@ -218,5 +230,16 @@ public void TestSecretSourceConversion(object secretSource) Secret secret2 = new Secret(base64); Assert.Equal(base64, secret2.ToBase64()); } + +#if NET6_0_OR_GREATER + [Fact] + public void TestSecretAsReadOnlySpan() + { + byte[] bytes = {0x1, 0x2, 0x3, 0x4}; + var secret = new Secret(bytes); + ReadOnlySpan readOnlySpan = secret; + Assert.Equal(bytes, readOnlySpan.ToArray()); + } +#endif } } \ No newline at end of file