mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-28 13:07:44 +00:00
[PTRun][ValueGenerator]Add support for UUIDv7 (#35757)
* add Run support for UUIDv7 generation * simplify comments and maybe satisfy spell check * fix endianess * prefer stack allocation for temporary fixed-size buffer * perhaps the async test caused the pipeline to hang * switch to .NET 9 BCL implementation of UUIDv7 * add UUIDv7 to input query suggestions + update exception messages to include v7 * simplify Guid description switch + update devdocs
This commit is contained in:
parent
3dc491339a
commit
fc29fc7426
@ -1,6 +1,6 @@
|
|||||||
# Value Generator Plugin
|
# Value Generator Plugin
|
||||||
|
|
||||||
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings, escape and encode URLs/URIs and to generate GUIDs versions 1, 3, 4 and 5.
|
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings, escape and encode URLs/URIs and to generate GUIDs of version 1, 3, 4, 5, and 7.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -34,7 +34,10 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
|
|||||||
|
|
||||||
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDGenerator.cs)
|
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDGenerator.cs)
|
||||||
- Utility class for generating or calculating GUIDs
|
- Utility class for generating or calculating GUIDs
|
||||||
- Generating GUID versions 1 and 4 is done using builtin APIs. [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1 and `System.Guid.NewGuid()` for version 4
|
- Generating GUID versions 1, 4, and 7 is done using builtin APIs:
|
||||||
|
- [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1
|
||||||
|
- `System.Guid.NewGuid()` for version 4
|
||||||
|
- `System.Guid.CreateVersion7()` for version 7
|
||||||
- Versions 3 and 5 take two parameters, a namespace and a name
|
- Versions 3 and 5 take two parameters, a namespace and a name
|
||||||
- The namespace must be a valid GUID or one of the [predefined ones](https://datatracker.ietf.org/doc/html/rfc4122#appendix-C)
|
- The namespace must be a valid GUID or one of the [predefined ones](https://datatracker.ietf.org/doc/html/rfc4122#appendix-C)
|
||||||
- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces
|
- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
||||||
@ -56,6 +57,39 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
|||||||
Assert.AreEqual(0x5000, GetGUIDVersion(guid));
|
Assert.AreEqual(0x5000, GetGUIDVersion(guid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GUIDv7Generator()
|
||||||
|
{
|
||||||
|
var guidRequest = new GUID.GUIDRequest(7);
|
||||||
|
guidRequest.Compute();
|
||||||
|
var guid = guidRequest.Result;
|
||||||
|
|
||||||
|
Assert.IsNotNull(guid);
|
||||||
|
Assert.AreEqual(0x7000, GetGUIDVersion(guid));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GUIDv7GeneratorTimeOrdered()
|
||||||
|
{
|
||||||
|
const int numberOfSamplesToCheck = 10;
|
||||||
|
ulong previousTimestampWithTrailingRandomData = 0uL;
|
||||||
|
for (int i = 0; i < numberOfSamplesToCheck; i++)
|
||||||
|
{
|
||||||
|
var guidRequest = new GUID.GUIDRequest(7);
|
||||||
|
guidRequest.Compute();
|
||||||
|
var guid = guidRequest.Result;
|
||||||
|
|
||||||
|
// can't hurt to assert invariants again
|
||||||
|
Assert.IsNotNull(guid);
|
||||||
|
Assert.AreEqual(0x7000, GetGUIDVersion(guid));
|
||||||
|
ulong timestampWithTrailingRandomData = BinaryPrimitives.ReadUInt64BigEndian(guid.AsSpan());
|
||||||
|
Assert.IsTrue(timestampWithTrailingRandomData > previousTimestampWithTrailingRandomData, "UUIDv7 wasn't time-ordered");
|
||||||
|
|
||||||
|
// ensure at least one millisecond passes for consistent time-ordering. we wait 10 ms just to be sure.
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(3, "ns:DNS", "abc", "5bd670ce-29c8-3369-a8a1-10ce44c7259e")]
|
[DataRow(3, "ns:DNS", "abc", "5bd670ce-29c8-3369-a8a1-10ce44c7259e")]
|
||||||
[DataRow(3, "ns:URL", "abc", "874a8cb4-4e91-3055-a476-3d3e2ffe375f")]
|
[DataRow(3, "ns:URL", "abc", "874a8cb4-4e91-3055-a476-3d3e2ffe375f")]
|
||||||
|
@ -12,7 +12,7 @@ using Wox.Plugin;
|
|||||||
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class InputParserTests
|
public partial class InputParserTests
|
||||||
{
|
{
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow("md5 abc", typeof(Hashing.HashRequest))]
|
[DataRow("md5 abc", typeof(Hashing.HashRequest))]
|
||||||
@ -27,6 +27,8 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
|||||||
[DataRow("uUiD5 ns:URL abc", typeof(GUID.GUIDRequest))]
|
[DataRow("uUiD5 ns:URL abc", typeof(GUID.GUIDRequest))]
|
||||||
[DataRow("Guidvv ns:DNS abc", null)]
|
[DataRow("Guidvv ns:DNS abc", null)]
|
||||||
[DataRow("guidv4", typeof(GUID.GUIDRequest))]
|
[DataRow("guidv4", typeof(GUID.GUIDRequest))]
|
||||||
|
[DataRow("guidv7", typeof(GUID.GUIDRequest))]
|
||||||
|
[DataRow("GUIDv7", typeof(GUID.GUIDRequest))]
|
||||||
[DataRow("base64 abc", typeof(Base64.Base64Request))]
|
[DataRow("base64 abc", typeof(Base64.Base64Request))]
|
||||||
[DataRow("base99 abc", null)]
|
[DataRow("base99 abc", null)]
|
||||||
[DataRow("base64s abc", null)]
|
[DataRow("base64s abc", null)]
|
||||||
@ -90,25 +92,22 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
|
|||||||
|
|
||||||
private static bool CommandIsKnown(string command)
|
private static bool CommandIsKnown(string command)
|
||||||
{
|
{
|
||||||
string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d" };
|
string[] hashes = ["md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d"];
|
||||||
if (hashes.Contains(command.ToLowerInvariant()))
|
if (hashes.Contains(command.ToLowerInvariant()))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Regex regexGuiUUID = new Regex("^(guid|uuid)([1345]{0,1}|v[1345]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
if (GetKnownUuidImplementations().IsMatch(command))
|
||||||
if (regexGuiUUID.IsMatch(command))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] uriCommands = new string[] { "url", "urld", "esc:hex", "uesc:hex", "esc:data", "uesc:data" };
|
string[] uriCommands = ["url", "urld", "esc:hex", "uesc:hex", "esc:data", "uesc:data"];
|
||||||
if (uriCommands.Contains(command.ToLowerInvariant()))
|
return uriCommands.Contains(command.ToLowerInvariant());
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("^(guid|uuid)([13457]{0,1}|v[13457]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
|
||||||
|
private static partial Regex GetKnownUuidImplementations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,11 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
|||||||
return V3AndV5(uuidNamespace, uuidName, 5);
|
return V3AndV5(uuidNamespace, uuidName, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Guid V7()
|
||||||
|
{
|
||||||
|
return Guid.CreateVersion7();
|
||||||
|
}
|
||||||
|
|
||||||
private static Guid V3AndV5(Guid uuidNamespace, string uuidName, short version)
|
private static Guid V3AndV5(Guid uuidNamespace, string uuidName, short version)
|
||||||
{
|
{
|
||||||
byte[] namespaceBytes = uuidNamespace.ToByteArray();
|
byte[] namespaceBytes = uuidNamespace.ToByteArray();
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
using Wox.Plugin.Logger;
|
using Wox.Plugin.Logger;
|
||||||
|
|
||||||
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
||||||
@ -19,34 +18,15 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
|||||||
|
|
||||||
private int Version { get; set; }
|
private int Version { get; set; }
|
||||||
|
|
||||||
public string Description
|
public string Description => Version switch
|
||||||
{
|
{
|
||||||
get
|
1 => "Version 1: Time base GUID",
|
||||||
{
|
3 => $"Version 3 ({HashAlgorithmName.MD5}): Namespace and name based GUID.",
|
||||||
switch (Version)
|
4 => "Version 4: Randomly generated GUID",
|
||||||
{
|
5 => $"Version 5 ({HashAlgorithmName.SHA1}): Namespace and name based GUID.",
|
||||||
case 1:
|
7 => "Version 7: Time-ordered randomly generated GUID",
|
||||||
return "Version 1: Time base GUID";
|
_ => string.Empty,
|
||||||
case 3:
|
};
|
||||||
case 5:
|
|
||||||
string hashAlgorithm;
|
|
||||||
if (Version == 3)
|
|
||||||
{
|
|
||||||
hashAlgorithm = HashAlgorithmName.MD5.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hashAlgorithm = HashAlgorithmName.SHA1.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"Version {Version} ({hashAlgorithm}): Namespace and name based GUID.";
|
|
||||||
case 4:
|
|
||||||
return "Version 4: Randomly generated GUID";
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Guid? GuidNamespace { get; set; }
|
private Guid? GuidNamespace { get; set; }
|
||||||
|
|
||||||
@ -60,20 +40,19 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
|||||||
{
|
{
|
||||||
Version = version;
|
Version = version;
|
||||||
|
|
||||||
if (Version < 1 || Version > 5 || Version == 2)
|
if (Version is < 1 or > 7 or 2 or 6)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Unsupported GUID version. Supported versions are 1, 3, 4 and 5");
|
throw new ArgumentException("Unsupported GUID version. Supported versions are 1, 3, 4, 5, and 7");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version == 3 || version == 5)
|
if (version is 3 or 5)
|
||||||
{
|
{
|
||||||
if (guidNamespace == null)
|
if (guidNamespace == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(null, NullNamespaceError);
|
throw new ArgumentNullException(null, NullNamespaceError);
|
||||||
}
|
}
|
||||||
|
|
||||||
Guid guid;
|
if (GUIDGenerator.PredefinedNamespaces.TryGetValue(guidNamespace.ToLowerInvariant(), out Guid guid))
|
||||||
if (GUIDGenerator.PredefinedNamespaces.TryGetValue(guidNamespace.ToLowerInvariant(), out guid))
|
|
||||||
{
|
{
|
||||||
GuidNamespace = guid;
|
GuidNamespace = guid;
|
||||||
}
|
}
|
||||||
@ -108,20 +87,18 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
|
|||||||
IsSuccessful = true;
|
IsSuccessful = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (Version)
|
Guid guid = Version switch
|
||||||
{
|
{
|
||||||
case 1:
|
1 => GUIDGenerator.V1(),
|
||||||
GuidResult = GUIDGenerator.V1();
|
3 => GUIDGenerator.V3(GuidNamespace.Value, GuidName),
|
||||||
break;
|
4 => GUIDGenerator.V4(),
|
||||||
case 3:
|
5 => GUIDGenerator.V5(GuidNamespace.Value, GuidName),
|
||||||
GuidResult = GUIDGenerator.V3(GuidNamespace.Value, GuidName);
|
7 => GUIDGenerator.V7(),
|
||||||
break;
|
_ => default,
|
||||||
case 4:
|
};
|
||||||
GuidResult = GUIDGenerator.V4();
|
if (guid != default)
|
||||||
break;
|
{
|
||||||
case 5:
|
GuidResult = guid;
|
||||||
GuidResult = GUIDGenerator.V5(GuidNamespace.Value, GuidName);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result = GuidResult.ToByteArray();
|
Result = GuidResult.ToByteArray();
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Community.PowerToys.Run.Plugin.ValueGenerator.Properties;
|
using Community.PowerToys.Run.Plugin.ValueGenerator.Properties;
|
||||||
|
|
||||||
@ -23,6 +22,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.Helper
|
|||||||
private static readonly string GeneratorDescriptionUuidv3 = Resources.generator_description_uuidv3;
|
private static readonly string GeneratorDescriptionUuidv3 = Resources.generator_description_uuidv3;
|
||||||
private static readonly string GeneratorDescriptionUuidv4 = Resources.generator_description_uuidv4;
|
private static readonly string GeneratorDescriptionUuidv4 = Resources.generator_description_uuidv4;
|
||||||
private static readonly string GeneratorDescriptionUuidv5 = Resources.generator_description_uuidv5;
|
private static readonly string GeneratorDescriptionUuidv5 = Resources.generator_description_uuidv5;
|
||||||
|
private static readonly string GeneratorDescriptionUuidv7 = Resources.generator_description_uuidv7;
|
||||||
private static readonly string GeneratorDescriptionHash = Resources.generator_description_hash;
|
private static readonly string GeneratorDescriptionHash = Resources.generator_description_hash;
|
||||||
private static readonly string GeneratorDescriptionBase64 = Resources.generator_description_base64;
|
private static readonly string GeneratorDescriptionBase64 = Resources.generator_description_base64;
|
||||||
private static readonly string GeneratorDescriptionBase64d = Resources.generator_description_base64d;
|
private static readonly string GeneratorDescriptionBase64d = Resources.generator_description_base64d;
|
||||||
@ -92,6 +92,12 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.Helper
|
|||||||
Example = $"uuidv5 ns:<DNS, URL, OID, {GetStringFormat(Or)} X500> <{GetStringFormat(GeneratorDescriptionYourInput)}>",
|
Example = $"uuidv5 ns:<DNS, URL, OID, {GetStringFormat(Or)} X500> <{GetStringFormat(GeneratorDescriptionYourInput)}>",
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
|
{
|
||||||
|
Keyword = "uuidv7",
|
||||||
|
Description = GetStringFormat(GeneratorDescriptionUuidv7),
|
||||||
|
Example = $"uuidv7 {GetStringFormat(Or)} uuid7",
|
||||||
|
},
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
Keyword = "md5",
|
Keyword = "md5",
|
||||||
Description = GetStringFormat(GeneratorDescriptionHash, "MD5"),
|
Description = GetStringFormat(GeneratorDescriptionHash, "MD5"),
|
||||||
|
@ -94,7 +94,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
|
|||||||
|
|
||||||
if (!int.TryParse(versionQuery, null, out version))
|
if (!int.TryParse(versionQuery, null, out version))
|
||||||
{
|
{
|
||||||
throw new FormatException("Could not determine requested GUID version. Supported versions are 1, 3, 4 and 5");
|
throw new FormatException("Could not determine requested GUID version. Supported versions are 1, 3, 4, 5, and 7");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +213,15 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generate a version 7: Time-ordered randomly generated UUID.
|
||||||
|
/// </summary>
|
||||||
|
public static string generator_description_uuidv7 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("generator_description_uuidv7", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to your input.
|
/// Looks up a localized string similar to your input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -168,6 +168,9 @@
|
|||||||
<data name="generator_description_uuidv5" xml:space="preserve">
|
<data name="generator_description_uuidv5" xml:space="preserve">
|
||||||
<value>Generate a version 5 (SHA1): Namespace and name based UUID</value>
|
<value>Generate a version 5 (SHA1): Namespace and name based UUID</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="generator_description_uuidv7" xml:space="preserve">
|
||||||
|
<value>Generate a version 7: Time-ordered randomly generated UUID</value>
|
||||||
|
</data>
|
||||||
<data name="generator_description_your_input" xml:space="preserve">
|
<data name="generator_description_your_input" xml:space="preserve">
|
||||||
<value>your input</value>
|
<value>your input</value>
|
||||||
<comment>Usage example: "md5 <your input>"</comment>
|
<comment>Usage example: "md5 <your input>"</comment>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user