Changed license to Apache License 2.0
This commit is contained in:
parent
d3bf0be9d7
commit
9f36d71529
144 changed files with 32422 additions and 32544 deletions
|
@ -1,142 +1,141 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Licensing {
|
||||
|
||||
/// <summary>Unit test for the license key class</summary>
|
||||
[TestFixture]
|
||||
internal class LicenseKeyTest {
|
||||
|
||||
/// <summary>Tests the default constructor of the license key class</summary>
|
||||
[Test]
|
||||
public void DefaultConstructorCanBeUsed() {
|
||||
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
|
||||
}
|
||||
|
||||
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
|
||||
[Test]
|
||||
public void LicenseKeysCanBeConvertedToGuidsAndBack() {
|
||||
for(int i = 0; i < 128; ++i) {
|
||||
|
||||
// Create a new BitArray with the n.th bit set
|
||||
BitArray guidBits = new BitArray(128);
|
||||
guidBits[i] = true;
|
||||
|
||||
// Create a GUID from this Bitarray
|
||||
byte[] guidBytes = new byte[16];
|
||||
guidBits.CopyTo(guidBytes, 0);
|
||||
Guid originalGuid = new Guid(guidBytes);
|
||||
|
||||
// Convert the GUID into a license key and back to a GUID
|
||||
string licenseKey = new LicenseKey(originalGuid).ToString();
|
||||
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
|
||||
|
||||
// Verify that the original GUID matches the fore-and-back converted one
|
||||
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||
[Test]
|
||||
public void LicenseKeysCanBeModified() {
|
||||
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
for(int j = 0; j < 8; ++j) {
|
||||
|
||||
LicenseKey testKey = new LicenseKey(
|
||||
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
|
||||
);
|
||||
|
||||
string originalString = testKey.ToString();
|
||||
testKey[i] &= ~(1 << j);
|
||||
string modifiedString = testKey.ToString();
|
||||
|
||||
Assert.IsTrue(
|
||||
originalString != modifiedString, "Modified string differs from original"
|
||||
);
|
||||
|
||||
testKey[i] |= (1 << j);
|
||||
string revertedString = testKey.ToString();
|
||||
|
||||
Assert.AreEqual(
|
||||
originalString, revertedString, "Original state restorable"
|
||||
);
|
||||
|
||||
} // for j
|
||||
} // for i
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||
[Test]
|
||||
public void ParsingInvalidLicenseKeyThrowsArgumentException() {
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { LicenseKey.Parse("hello world"); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||
/// with an invalid index to retrieve a component of the key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||
LicenseKey key = new LicenseKey();
|
||||
Assert.Throws<IndexOutOfRangeException>(
|
||||
delegate() { Console.WriteLine(key[-1]); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||
/// with an invalid index to set a component of the key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||
LicenseKey key = new LicenseKey();
|
||||
Assert.Throws<IndexOutOfRangeException>(
|
||||
delegate() { key[-1] = 0; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a license key can be converted into a byte array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LicenseKeyCanBeConvertedToByteArray() {
|
||||
Guid someGuid = Guid.NewGuid();
|
||||
LicenseKey someKey = new LicenseKey(someGuid);
|
||||
|
||||
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Licensing
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Licensing {
|
||||
|
||||
/// <summary>Unit test for the license key class</summary>
|
||||
[TestFixture]
|
||||
internal class LicenseKeyTest {
|
||||
|
||||
/// <summary>Tests the default constructor of the license key class</summary>
|
||||
[Test]
|
||||
public void DefaultConstructorCanBeUsed() {
|
||||
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
|
||||
}
|
||||
|
||||
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
|
||||
[Test]
|
||||
public void LicenseKeysCanBeConvertedToGuidsAndBack() {
|
||||
for(int i = 0; i < 128; ++i) {
|
||||
|
||||
// Create a new BitArray with the n.th bit set
|
||||
BitArray guidBits = new BitArray(128);
|
||||
guidBits[i] = true;
|
||||
|
||||
// Create a GUID from this Bitarray
|
||||
byte[] guidBytes = new byte[16];
|
||||
guidBits.CopyTo(guidBytes, 0);
|
||||
Guid originalGuid = new Guid(guidBytes);
|
||||
|
||||
// Convert the GUID into a license key and back to a GUID
|
||||
string licenseKey = new LicenseKey(originalGuid).ToString();
|
||||
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
|
||||
|
||||
// Verify that the original GUID matches the fore-and-back converted one
|
||||
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||
[Test]
|
||||
public void LicenseKeysCanBeModified() {
|
||||
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
for(int j = 0; j < 8; ++j) {
|
||||
|
||||
LicenseKey testKey = new LicenseKey(
|
||||
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
|
||||
);
|
||||
|
||||
string originalString = testKey.ToString();
|
||||
testKey[i] &= ~(1 << j);
|
||||
string modifiedString = testKey.ToString();
|
||||
|
||||
Assert.IsTrue(
|
||||
originalString != modifiedString, "Modified string differs from original"
|
||||
);
|
||||
|
||||
testKey[i] |= (1 << j);
|
||||
string revertedString = testKey.ToString();
|
||||
|
||||
Assert.AreEqual(
|
||||
originalString, revertedString, "Original state restorable"
|
||||
);
|
||||
|
||||
} // for j
|
||||
} // for i
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||
[Test]
|
||||
public void ParsingInvalidLicenseKeyThrowsArgumentException() {
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { LicenseKey.Parse("hello world"); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||
/// with an invalid index to retrieve a component of the key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||
LicenseKey key = new LicenseKey();
|
||||
Assert.Throws<IndexOutOfRangeException>(
|
||||
delegate() { Console.WriteLine(key[-1]); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||
/// with an invalid index to set a component of the key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||
LicenseKey key = new LicenseKey();
|
||||
Assert.Throws<IndexOutOfRangeException>(
|
||||
delegate() { key[-1] = 0; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a license key can be converted into a byte array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LicenseKeyCanBeConvertedToByteArray() {
|
||||
Guid someGuid = Guid.NewGuid();
|
||||
LicenseKey someKey = new LicenseKey(someGuid);
|
||||
|
||||
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Licensing
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
@ -1,257 +1,256 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Licensing {
|
||||
|
||||
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class manages a license key like it is used in Microsoft products.
|
||||
/// Althought it is probably not the exact same technique used by Microsoft,
|
||||
/// the textual representation of the license keys looks identical,
|
||||
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Available storage space is used efficiently and allows for up to four
|
||||
/// 32 bit integers to be stored within the key, that's enough for a full GUID.
|
||||
/// The four integers can be modified directly, for example to store feature
|
||||
/// lists, checksums or other data within the key.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class LicenseKey {
|
||||
|
||||
/// <summary>Parses the license key contained in a string</summary>
|
||||
/// <param name="key">String containing a license key that is to be parsed</param>
|
||||
/// <returns>The license key parsed from provided string</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// When the provided string is not a license key
|
||||
/// </exception>
|
||||
public static LicenseKey Parse(string key) {
|
||||
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
|
||||
if(key.Length != 25)
|
||||
throw new ArgumentException("This is not a license key");
|
||||
|
||||
BitArray bits = new BitArray(128);
|
||||
uint sequence;
|
||||
|
||||
// Convert the first 4 sequences of 6 chars into 124 bits
|
||||
for(int j = 0; j < 4; j++) {
|
||||
|
||||
sequence =
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 0]);
|
||||
|
||||
for(int i = 0; i < 31; i++)
|
||||
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
|
||||
|
||||
}
|
||||
|
||||
// Append the remaining character's 4 bits
|
||||
sequence = (uint)codeTable.IndexOf(key[24]);
|
||||
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
|
||||
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
|
||||
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
|
||||
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
|
||||
|
||||
// Revert the mangling that was applied to the key when encoding...
|
||||
unmangle(bits);
|
||||
|
||||
// ...and we've got our GUID back!
|
||||
byte[] guidBytes = new byte[16];
|
||||
bits.CopyTo(guidBytes, 0);
|
||||
|
||||
return new LicenseKey(new Guid(guidBytes));
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new, empty license key</summary>
|
||||
public LicenseKey() : this(Guid.Empty) { }
|
||||
|
||||
/// <summary>Initializes the license key from a GUID</summary>
|
||||
/// <param name="source">GUID that is used to create the license key</param>
|
||||
public LicenseKey(Guid source) {
|
||||
this.guid = source;
|
||||
}
|
||||
|
||||
/// <summary>Accesses the four integer values within a license key</summary>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// When the index lies outside of the key's fields
|
||||
/// </exception>
|
||||
public int this[int index] {
|
||||
get {
|
||||
if((index < 0) || (index > 3))
|
||||
throw new IndexOutOfRangeException("Index out of range");
|
||||
|
||||
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
|
||||
}
|
||||
set {
|
||||
if((index < 0) || (index > 3))
|
||||
throw new IndexOutOfRangeException("Index out of range");
|
||||
|
||||
// Convert the GUID into binary data so we can replace one of its values
|
||||
byte[] guidBytes = this.guid.ToByteArray();
|
||||
|
||||
// Overwrite the section at the index specified by the user with the new value
|
||||
Array.Copy(
|
||||
BitConverter.GetBytes(value), 0, // source and start index
|
||||
guidBytes, index * 4, // destination and start index
|
||||
4 // length
|
||||
);
|
||||
|
||||
// Replacement finished, now we can reconstruct our guid
|
||||
this.guid = new Guid(guidBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key into a GUID</summary>
|
||||
/// <returns>The GUID created from the license key</returns>
|
||||
public Guid ToGuid() {
|
||||
return this.guid;
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key into a byte array</summary>
|
||||
/// <returns>A byte array containing the converted license key</returns>
|
||||
public byte[] ToByteArray() {
|
||||
return this.guid.ToByteArray();
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key to a string</summary>
|
||||
/// <returns>A string containing the converted license key</returns>
|
||||
public override string ToString() {
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
|
||||
// Build a bit array from the input data
|
||||
BitArray bits = new BitArray(this.guid.ToByteArray());
|
||||
mangle(bits);
|
||||
|
||||
int sequence = 0;
|
||||
|
||||
// Build 4 sequences of 6 characters from the first 124 bits
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
|
||||
// We take the next 31 bits from the buffer
|
||||
for(int j = 0; j < 31; ++j)
|
||||
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
||||
|
||||
// Using 31 bits, a number up to 2.147.483.648 can be represented,
|
||||
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
||||
// which means we can fit 31 bits into every 6 alpha-numerical characters.
|
||||
for(int j = 0; j < 6; ++j) {
|
||||
resultBuilder.Append(codeTable[sequence % 36]);
|
||||
sequence /= 36;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Use the remaining 4 bits to build the final character
|
||||
resultBuilder.Append(
|
||||
codeTable[
|
||||
(int)(
|
||||
powersOfTwo[4, bits[124] ? 1 : 0] |
|
||||
powersOfTwo[3, bits[125] ? 1 : 0] |
|
||||
powersOfTwo[2, bits[126] ? 1 : 0] |
|
||||
powersOfTwo[1, bits[127] ? 1 : 0] |
|
||||
powersOfTwo[0, 1] // One bit remains unused :)
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// Now build a nice, readable string from the decoded characters
|
||||
resultBuilder.Insert(5, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(11, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(17, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(23, keyDelimiter, 0, 1);
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Mangles a bit array</summary>
|
||||
/// <param name="bits">Bit array that will be mangled</param>
|
||||
private static void mangle(BitArray bits) {
|
||||
BitArray temp = new BitArray(bits);
|
||||
|
||||
for(int i = 0; i < temp.Length; ++i) {
|
||||
bits[i] = temp[shuffle[i]];
|
||||
|
||||
if((i & 1) != 0)
|
||||
bits[i] = !bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unmangles a bit array</summary>
|
||||
/// <param name="bits">Bit array that will be unmangled</param>
|
||||
private static void unmangle(BitArray bits) {
|
||||
BitArray temp = new BitArray(bits);
|
||||
|
||||
for(int i = 0; i < temp.Length; ++i) {
|
||||
if((i & 1) != 0)
|
||||
temp[i] = !temp[i];
|
||||
|
||||
bits[shuffle[i]] = temp[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
|
||||
/// <remarks>
|
||||
/// Required to be a char array because the .NET Compact Framework only provides
|
||||
/// an overload for char[] in the StringBuilder.Insert() method.
|
||||
/// </remarks>
|
||||
private static char[] keyDelimiter = new char[] { '-' };
|
||||
|
||||
/// <summary>Table with the individual characters in a key</summary>
|
||||
private static readonly string codeTable =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
/// <summary>Helper array containing the precalculated powers of two</summary>
|
||||
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
|
||||
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
|
||||
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
|
||||
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
|
||||
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
|
||||
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
|
||||
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
|
||||
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
|
||||
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
|
||||
};
|
||||
|
||||
/// <summary>Index list for rotating the bit arrays</summary>
|
||||
private static readonly byte[] shuffle = new byte[128] {
|
||||
99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48,
|
||||
33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14,
|
||||
92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108,
|
||||
5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7,
|
||||
56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9,
|
||||
73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17,
|
||||
112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63,
|
||||
11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58
|
||||
};
|
||||
|
||||
/// <summary>GUID in which the key is stored</summary>
|
||||
private Guid guid;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Licensing
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Licensing {
|
||||
|
||||
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class manages a license key like it is used in Microsoft products.
|
||||
/// Althought it is probably not the exact same technique used by Microsoft,
|
||||
/// the textual representation of the license keys looks identical,
|
||||
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Available storage space is used efficiently and allows for up to four
|
||||
/// 32 bit integers to be stored within the key, that's enough for a full GUID.
|
||||
/// The four integers can be modified directly, for example to store feature
|
||||
/// lists, checksums or other data within the key.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class LicenseKey {
|
||||
|
||||
/// <summary>Parses the license key contained in a string</summary>
|
||||
/// <param name="key">String containing a license key that is to be parsed</param>
|
||||
/// <returns>The license key parsed from provided string</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// When the provided string is not a license key
|
||||
/// </exception>
|
||||
public static LicenseKey Parse(string key) {
|
||||
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
|
||||
if(key.Length != 25)
|
||||
throw new ArgumentException("This is not a license key");
|
||||
|
||||
BitArray bits = new BitArray(128);
|
||||
uint sequence;
|
||||
|
||||
// Convert the first 4 sequences of 6 chars into 124 bits
|
||||
for(int j = 0; j < 4; j++) {
|
||||
|
||||
sequence =
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
|
||||
(uint)codeTable.IndexOf(key[j * 6 + 0]);
|
||||
|
||||
for(int i = 0; i < 31; i++)
|
||||
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
|
||||
|
||||
}
|
||||
|
||||
// Append the remaining character's 4 bits
|
||||
sequence = (uint)codeTable.IndexOf(key[24]);
|
||||
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
|
||||
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
|
||||
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
|
||||
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
|
||||
|
||||
// Revert the mangling that was applied to the key when encoding...
|
||||
unmangle(bits);
|
||||
|
||||
// ...and we've got our GUID back!
|
||||
byte[] guidBytes = new byte[16];
|
||||
bits.CopyTo(guidBytes, 0);
|
||||
|
||||
return new LicenseKey(new Guid(guidBytes));
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new, empty license key</summary>
|
||||
public LicenseKey() : this(Guid.Empty) { }
|
||||
|
||||
/// <summary>Initializes the license key from a GUID</summary>
|
||||
/// <param name="source">GUID that is used to create the license key</param>
|
||||
public LicenseKey(Guid source) {
|
||||
this.guid = source;
|
||||
}
|
||||
|
||||
/// <summary>Accesses the four integer values within a license key</summary>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// When the index lies outside of the key's fields
|
||||
/// </exception>
|
||||
public int this[int index] {
|
||||
get {
|
||||
if((index < 0) || (index > 3))
|
||||
throw new IndexOutOfRangeException("Index out of range");
|
||||
|
||||
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
|
||||
}
|
||||
set {
|
||||
if((index < 0) || (index > 3))
|
||||
throw new IndexOutOfRangeException("Index out of range");
|
||||
|
||||
// Convert the GUID into binary data so we can replace one of its values
|
||||
byte[] guidBytes = this.guid.ToByteArray();
|
||||
|
||||
// Overwrite the section at the index specified by the user with the new value
|
||||
Array.Copy(
|
||||
BitConverter.GetBytes(value), 0, // source and start index
|
||||
guidBytes, index * 4, // destination and start index
|
||||
4 // length
|
||||
);
|
||||
|
||||
// Replacement finished, now we can reconstruct our guid
|
||||
this.guid = new Guid(guidBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key into a GUID</summary>
|
||||
/// <returns>The GUID created from the license key</returns>
|
||||
public Guid ToGuid() {
|
||||
return this.guid;
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key into a byte array</summary>
|
||||
/// <returns>A byte array containing the converted license key</returns>
|
||||
public byte[] ToByteArray() {
|
||||
return this.guid.ToByteArray();
|
||||
}
|
||||
|
||||
/// <summary>Converts the license key to a string</summary>
|
||||
/// <returns>A string containing the converted license key</returns>
|
||||
public override string ToString() {
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
|
||||
// Build a bit array from the input data
|
||||
BitArray bits = new BitArray(this.guid.ToByteArray());
|
||||
mangle(bits);
|
||||
|
||||
int sequence = 0;
|
||||
|
||||
// Build 4 sequences of 6 characters from the first 124 bits
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
|
||||
// We take the next 31 bits from the buffer
|
||||
for(int j = 0; j < 31; ++j)
|
||||
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
||||
|
||||
// Using 31 bits, a number up to 2.147.483.648 can be represented,
|
||||
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
||||
// which means we can fit 31 bits into every 6 alpha-numerical characters.
|
||||
for(int j = 0; j < 6; ++j) {
|
||||
resultBuilder.Append(codeTable[sequence % 36]);
|
||||
sequence /= 36;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Use the remaining 4 bits to build the final character
|
||||
resultBuilder.Append(
|
||||
codeTable[
|
||||
(int)(
|
||||
powersOfTwo[4, bits[124] ? 1 : 0] |
|
||||
powersOfTwo[3, bits[125] ? 1 : 0] |
|
||||
powersOfTwo[2, bits[126] ? 1 : 0] |
|
||||
powersOfTwo[1, bits[127] ? 1 : 0] |
|
||||
powersOfTwo[0, 1] // One bit remains unused :)
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// Now build a nice, readable string from the decoded characters
|
||||
resultBuilder.Insert(5, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(11, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(17, keyDelimiter, 0, 1);
|
||||
resultBuilder.Insert(23, keyDelimiter, 0, 1);
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Mangles a bit array</summary>
|
||||
/// <param name="bits">Bit array that will be mangled</param>
|
||||
private static void mangle(BitArray bits) {
|
||||
BitArray temp = new BitArray(bits);
|
||||
|
||||
for(int i = 0; i < temp.Length; ++i) {
|
||||
bits[i] = temp[shuffle[i]];
|
||||
|
||||
if((i & 1) != 0)
|
||||
bits[i] = !bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unmangles a bit array</summary>
|
||||
/// <param name="bits">Bit array that will be unmangled</param>
|
||||
private static void unmangle(BitArray bits) {
|
||||
BitArray temp = new BitArray(bits);
|
||||
|
||||
for(int i = 0; i < temp.Length; ++i) {
|
||||
if((i & 1) != 0)
|
||||
temp[i] = !temp[i];
|
||||
|
||||
bits[shuffle[i]] = temp[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
|
||||
/// <remarks>
|
||||
/// Required to be a char array because the .NET Compact Framework only provides
|
||||
/// an overload for char[] in the StringBuilder.Insert() method.
|
||||
/// </remarks>
|
||||
private static char[] keyDelimiter = new char[] { '-' };
|
||||
|
||||
/// <summary>Table with the individual characters in a key</summary>
|
||||
private static readonly string codeTable =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
/// <summary>Helper array containing the precalculated powers of two</summary>
|
||||
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
|
||||
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
|
||||
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
|
||||
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
|
||||
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
|
||||
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
|
||||
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
|
||||
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
|
||||
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
|
||||
};
|
||||
|
||||
/// <summary>Index list for rotating the bit arrays</summary>
|
||||
private static readonly byte[] shuffle = new byte[128] {
|
||||
99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48,
|
||||
33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14,
|
||||
92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108,
|
||||
5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7,
|
||||
56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9,
|
||||
73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17,
|
||||
112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63,
|
||||
11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58
|
||||
};
|
||||
|
||||
/// <summary>GUID in which the key is stored</summary>
|
||||
private Guid guid;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Licensing
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue