v1.16.7
ADDED: * uuidx FIXED: * sprigx docs consistency
This commit is contained in:
461
uuidx/funcs.go
Normal file
461
uuidx/funcs.go
Normal file
@@ -0,0 +1,461 @@
|
||||
package uuidx
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
/*
|
||||
DetectMsGuid tries to guess if a given [uuid.UUID] is actually a Microsoft GUID or not.
|
||||
|
||||
Note that there are technically *two* types of Microsoft GUIDs:
|
||||
|
||||
* One is predictable, and defined in RFC 9562 [§ 4.2] as a known variant.
|
||||
Detecting this is very easy and (assuming an RFC-compliant UUID is originally passed) is detectable with 100% confidence.
|
||||
It's also legacy, and Microsoft no longer uses this format. Because they are insane and enjoy the suffering of others.
|
||||
* The other, MODERN Microsoft GUID currently in use is the endianness-flipped version (see [ToggleUuidMsGuid]).
|
||||
This is impossible to 100% determine, but analysis can get *pretty* close.
|
||||
|
||||
cs is a confidence scoring. As more logic is added, it *is* mathematically possible
|
||||
(though unlikely) that cs == 0, so the caller is then responsible for making further
|
||||
guesswork based on contextual analysis ("Did I get this UUID/GUID from an Active Directory attribute?"
|
||||
"Is it a SID constant?" etc.).
|
||||
|
||||
A score > 0 indicates a confidence leaning towards the provided UUID/GUID being a Microsoft GUID.
|
||||
A score < 0 indicates a confidence leaning towards the provided UUID/GUID *not* being a Microsoft GUID.
|
||||
Note that a score of < 0 does not necessarily indicate it is a *proper, standard RFC-compliant UUID*,
|
||||
simply that it is likely NOT a Microsoft GUID. [IsRfc] will be of further help in these cases.
|
||||
|
||||
csFlip indicates a score for the [ToggleUuidMsGuid]-flipped version of u.
|
||||
It follows the same rules for thresholds and such as cs, but may be awarded different confidence levels
|
||||
internally due to different chances of false positives.
|
||||
If both cs and csFlip are > 0 but csFlip > cs, it is better to assume that u is *not* in the flipped-endian format
|
||||
but *is* a Microsoft GUID (in other words, it is likely that u has *already been flipped* to proper/consistent endianness
|
||||
instead of being a mixed-endian GUID).
|
||||
|
||||
In some cases where flipped-endianness does not matter (e.g. [IsNilUUID], [IsMaxUUID]),
|
||||
cs and csFlip will be equal.
|
||||
|
||||
*Randomly-generated* GUIDs on Windows Server 2000-family and up are almost always UUIDv4.
|
||||
Pre-Windows Server 2000 family *OR* any *statically-defined* GUIDs (schemaIDGUID, rightsGUID, CLSID constants, etc.)
|
||||
are all over the place - TYPICALLY UUIDv1, but it's nothing predictable enough to be useful in definitive classification.
|
||||
COM interfaces are all OVER the place in UUID version, but usually *not* UUIDv4.
|
||||
|
||||
A target/expected UUID version can be provided via tgtVer. To disable version analysis, use 0 (or 0x00, etc.).
|
||||
It is *highly* recommended to provide a tgtVer if it is known; it can significantly boost confidence in the correct direction.
|
||||
A warning, though - if a *wrong* tgtVer IS specified, it can negatively affect confidence accuracy.
|
||||
Thus if you aren't ABSOLUTELY certain of the target UUID version, it's better to use 0/0x00 to disable the check.
|
||||
Providing a target version is key to breaking some ties (e.g. both cs and csFlip are equal).
|
||||
For example, the given RFC-compliant UUIDv4:
|
||||
|
||||
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
||||
|
||||
when flipped evaluates to an RFC-compliant UUIDv2:
|
||||
|
||||
ae358e8d-d258-284d-b09d-ffffffffffff
|
||||
|
||||
and in this case, cs and csFlip will both end up as 0.
|
||||
Providing a tgtVer of 4 shifts this to a proper "tie-breaker" of cs == -3 and csFlip == 0.
|
||||
Similarly, the endian-flipped UUIDv4 evaluates as a UUIDv2:
|
||||
|
||||
9856ea36-c2ca-2347-af0c-3b42f76c9eca
|
||||
|
||||
from the original unflipped UUIDv4:
|
||||
|
||||
36ea5698-cac2-4723-af0c-3b42f76c9eca
|
||||
|
||||
which results in a cs == 1 and csFlip == 0 - not very high confidence (but at least a correct and non-zero lean).
|
||||
Providing a tgtVer == 4 changes this to cs == 7 and csFlip == 0, which is *much* more decisive.
|
||||
|
||||
UUIDs/GUIDs found to be strictly RFC-conforming (via [IsRfc], which returns false for Microsoft GUIDs)
|
||||
are *heavily* weighted negatively.
|
||||
|
||||
Confidence levels can be generally considered as the following:
|
||||
|
||||
cs >= 7: Likely Microsoft GUID (mixed-endian)
|
||||
cs >= 4: Likely Microsoft GUID
|
||||
0 < cs < 4: Leans Microsoft GUID, but untrusted
|
||||
cs == 0: Entirely ambiguous/indeterminate
|
||||
-4 < cs < 0: Leans UUID/non-Microsoft GUID but untrusted
|
||||
cs <= -5: Likely UUID/not Microsoft GUID
|
||||
csFlip >=cs && csFlip >= 4: Likely a pre-flipped (ToggleUuidMsGuid'd) Microsoft GUID
|
||||
|
||||
[§ 4.2]: https://datatracker.ietf.org/doc/html/rfc9562#section-4.2
|
||||
*/
|
||||
|
||||
func DetectMsGuid(u uuid.UUID, tgtVer uuid.Version) (cs, csFlip int) {
|
||||
|
||||
var isRfc bool
|
||||
var flippedRfc bool
|
||||
var flipped uuid.UUID = ToggleUuidMsGuid(u)
|
||||
|
||||
// These are the exact same when flipped, and are statically defined.
|
||||
if IsNilUUID(u) || IsMaxUUID(u) {
|
||||
cs = -12
|
||||
csFlip = -12
|
||||
return
|
||||
}
|
||||
|
||||
// Most/all(?) Microsoft GUIDs are not NCS.
|
||||
if IsNcs(u) {
|
||||
cs -= 2
|
||||
}
|
||||
if IsNcs(flipped) {
|
||||
// The flipped has a higher likelihood of false-pos, so we don't score it as confidently.
|
||||
csFlip -= 1
|
||||
}
|
||||
|
||||
if u.Version() == 0 {
|
||||
if u.Variant() == uuid.Microsoft {
|
||||
cs += 10
|
||||
} else {
|
||||
cs -= 2
|
||||
}
|
||||
}
|
||||
if flipped.Version() == 0 {
|
||||
if flipped.Variant() == uuid.Microsoft {
|
||||
csFlip += 4
|
||||
} else {
|
||||
csFlip -= 1
|
||||
}
|
||||
}
|
||||
|
||||
// Valid RFC version and variant. IsRfc returns false for the Microsoft Variant and version == 0.
|
||||
// Modern MS uses an RFC 4122 variant indicator but flips the endianness.
|
||||
isRfc, _ = IsRfc(u)
|
||||
flippedRfc, _ = IsRfc(flipped)
|
||||
if u.Variant() == uuid.RFC4122 { // This might be the strongest indicator.
|
||||
if isRfc && !flippedRfc {
|
||||
// This is *very* strong of being an MS GUID.
|
||||
cs -= 8
|
||||
csFlip += 4
|
||||
} else if !isRfc && flippedRfc {
|
||||
// It probably is an MS GUID but was already flipped.
|
||||
csFlip += 6
|
||||
} else if isRfc && flippedRfc {
|
||||
/*
|
||||
If both are RFC-compat, it's a weird case where
|
||||
it actually IS RFC compliant and by chance the flipped is *also* RFC compat.
|
||||
An example of this is:
|
||||
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
||||
Which has the flipped version of:
|
||||
ae358e8d-d258-284d-b09d-ffffffffffff
|
||||
The original is a v4, the flipped evaluates as a v2!
|
||||
|
||||
Providing a target version breaks this away to a definitive score.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// *HEAVILY* weigh a provided version.
|
||||
if tgtVer != 0 {
|
||||
// NCS does some weird things to the versioning field. We return early on it though.
|
||||
// MS GUIDs have a pretty small chance of matching,
|
||||
// but their flipped counterpart SHOULD match versions.
|
||||
if flipped.Version() == tgtVer {
|
||||
cs += 7
|
||||
} else {
|
||||
cs -= 3
|
||||
}
|
||||
} else {
|
||||
// Give a *very small* boost to flippedRfc and flipped.Version() == 4, since it's so common.
|
||||
// Don't make this too high though since the version is explicitly specified as unknown.
|
||||
if flippedRfc && flipped.Version() == 4 {
|
||||
cs += 1
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Equal returns `true` if [uuid.UUID] the two provided [uuid.UUID] are the same.
|
||||
|
||||
Currently it just wraps:
|
||||
|
||||
eq = a == b
|
||||
|
||||
but is provided as a safety guarantee if the underlying structures/types should change.
|
||||
*/
|
||||
func Equal(a, b uuid.UUID) (eq bool) {
|
||||
|
||||
eq = a == b
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsFlippedEndian can be used to check if [uuid.UUID] is a direct endian-flipped ([ToggleUuidMsGuid])
|
||||
of b (or vice versa, obviously).
|
||||
|
||||
It simply wraps:
|
||||
|
||||
isFlipped = Equal(a, ToggleUuidMsGuid(b))
|
||||
|
||||
but can be useful for shorthand/readability.
|
||||
*/
|
||||
func IsFlippedEndian(a, b uuid.UUID) (isFlipped bool) {
|
||||
|
||||
isFlipped = Equal(a, ToggleUuidMsGuid(b))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsMaxUUID returns `true` if the specified UUID is explicitly an RFC-defined
|
||||
"Max UUID". (You may also see it specified in some places as the "Omni UUID".)
|
||||
|
||||
For details, see RFC 9562 [§ 5.10].
|
||||
|
||||
[§ 5.10]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.10
|
||||
*/
|
||||
func IsMaxUUID(u uuid.UUID) (isMax bool) {
|
||||
|
||||
isMax = u == uuid.Max
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsMsGuid wraps
|
||||
|
||||
if cmp, _ = DetectMsGuid(msGUID, tgtVer); cmp < -3 {
|
||||
isMs = true
|
||||
}
|
||||
|
||||
Note that [uuid.Microsoft] is an actual RFC-defined variant, but *Microsoft no longer uses it*
|
||||
and in MODERN implementations do the endianness flip [ToggleUuidMsGuid] of (USUALLY) a UUIDv4.
|
||||
|
||||
See [DetectMsGuid] for a more in-depth result that will let you use the confidence level directly,
|
||||
and for details on the weird things that can go wrong with this guesswork.
|
||||
|
||||
Note that this won't be 100% reliable due to math things, but it should be reliable enough most of the time.
|
||||
|
||||
See also [MsGuidToUuid] and [UuidToMsGuid].
|
||||
*/
|
||||
func IsMsGuid(msGUID uuid.UUID, tgtVer uuid.Version) (isMs bool) {
|
||||
|
||||
var cmp int
|
||||
|
||||
if cmp, _ = DetectMsGuid(msGUID, tgtVer); cmp < -3 {
|
||||
isMs = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsNcs is shorthand for:
|
||||
|
||||
isNcs = u.Variant() == uuid.Reserved
|
||||
|
||||
See also the notes in [IsRfc].
|
||||
*/
|
||||
func IsNcs(u uuid.UUID) (isNcs bool) {
|
||||
|
||||
// https://archive.org/details/networkcomputing0000zahn/page/10/mode/1up
|
||||
|
||||
isNcs = u.Variant() == uuid.Reserved
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsNilUUID returns `true` if the specified UUID is explicitly an RFC-defined
|
||||
"Nil UUID".
|
||||
|
||||
For details, see RFC 9562 [§ 5.9].
|
||||
|
||||
[§ 5.9]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.9
|
||||
*/
|
||||
func IsNilUUID(u uuid.UUID) (isNil bool) {
|
||||
|
||||
isNil = u == uuid.Nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsRfc returns `true` if the specified UUID is a proper standard RFC UUID.
|
||||
|
||||
Because Microsoft is insane, rfc will be false even if it's a (legacy) Microsoft form
|
||||
of an RFC UUID. Use [IsMsGuid] for that.
|
||||
|
||||
In the special case of u being a valid NCS UUID, rfc will be false but gen will be [Rfc4122].
|
||||
This is because RFC 9652 deprecates the NCS UUID. See [IsNcs].
|
||||
(You are highly unlikely to encounter an NCS UUID "in the wild" unless you are receiving
|
||||
a UUID from someone who severely misunderstands that UUIDs are structured/versioned/typed
|
||||
and thinks they're just random byes in hex with hyphens in certain places.)
|
||||
(They aren't that, if you're one of those someones.)
|
||||
|
||||
Nil UUID ([IsNilUUID]) and Max UUID ([IsMaxUUID]) return true with RFCs 4122 and RFC 9562 respectively.
|
||||
*/
|
||||
func IsRfc(u uuid.UUID) (rfc bool, gen RfcGen) {
|
||||
|
||||
if IsNilUUID(u) {
|
||||
rfc = true
|
||||
gen = Rfc4122
|
||||
return
|
||||
}
|
||||
if IsMaxUUID(u) {
|
||||
rfc = true
|
||||
gen = Rfc9562
|
||||
return
|
||||
}
|
||||
if IsNcs(u) {
|
||||
gen = Rfc4122
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Are there any sub-version checks that can be applied?
|
||||
switch u.Variant() {
|
||||
case uuid.Invalid, uuid.Microsoft, uuid.Future:
|
||||
return
|
||||
case uuid.RFC4122:
|
||||
if !(0x01 <= u.Version() && u.Version() <= 0x08) {
|
||||
return
|
||||
}
|
||||
rfc = true
|
||||
gen = Rfc4122
|
||||
// 4122 only covers UUIDv1 through UUIDv5.
|
||||
if 0x06 <= u.Version() && u.Version() <= 0x08 {
|
||||
gen = Rfc9562
|
||||
}
|
||||
default: // Safety net in case upstream adds a uuid.RFC9562 variant or something.
|
||||
if !(0x01 <= u.Version() && u.Version() <= 0x08) {
|
||||
return
|
||||
}
|
||||
if u.Variant() < uuid.Future {
|
||||
return
|
||||
}
|
||||
rfc = true
|
||||
gen = RfcNone
|
||||
// 4122 only covers UUIDv1 through UUIDv5.
|
||||
if 0x06 <= u.Version() && u.Version() <= 0x08 {
|
||||
gen = Rfc9562
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
IsValid indicates if the given [uuid.UUID] strictly conforms to RFC.
|
||||
|
||||
A Nil UUID (as in RFC 9562 [§ 5.9], not a `nil` *uuid.UUID) will return `true`
|
||||
as it IS technically defined per RFC despite not conforming to a version.
|
||||
Use [IsNilUUID] to further determine that.
|
||||
|
||||
Likewise, a Max UUID (RFC 9562 [§ 5.10]) will return `true` as it is also
|
||||
defined per RFC despite not conforming to a version.
|
||||
Use [IsMaxUUID] to further determine that.
|
||||
|
||||
Microsoft GUIDs will always return false since they defy RFC.
|
||||
Use [IsMsGuid] to check for that condition.
|
||||
|
||||
[§ 5.9]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.9
|
||||
[§ 5.10]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.10
|
||||
*/
|
||||
func IsValid(u uuid.UUID) (valid bool) {
|
||||
|
||||
if IsNilUUID(u) {
|
||||
valid = true
|
||||
return
|
||||
}
|
||||
if IsMaxUUID(u) {
|
||||
valid = true
|
||||
return
|
||||
}
|
||||
|
||||
switch u.Variant() {
|
||||
case uuid.Invalid, uuid.Reserved, uuid.Microsoft, uuid.Future:
|
||||
return
|
||||
case uuid.RFC4122:
|
||||
valid = true
|
||||
// TODO: If they add an RFC9562 or something, need a case here.
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// If we got here, it *should* be RFC.
|
||||
if valid, _ = IsRfc(u); !valid {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
MsGuidToUuid converts a Microsoft GUID to a UUID.
|
||||
|
||||
If [IsMsGuid] is false for msGUID, u will be equal to msGUID.
|
||||
|
||||
See [UuidToMsGuid] for the inverse, and [IsRfc] to check
|
||||
if the result is a strictly conforming UUID.
|
||||
*/
|
||||
func MsGuidToUuid(msGUID uuid.UUID) (u uuid.UUID) {
|
||||
|
||||
if !IsMsGuid(msGUID, 0x00) {
|
||||
u = msGUID
|
||||
return
|
||||
}
|
||||
u = ToggleUuidMsGuid(msGUID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ToggleUuidMsGuid switches the src to it's "other" format:
|
||||
|
||||
* if it's a Microsoft GUID, it will be converted to a UUID
|
||||
* if it's a UUID, it will be converted to a Microsoft GUID
|
||||
|
||||
No detection ([IsRfc], [IsMsGuid], etc.) nor validation/verification ([IsValid]) is performed,
|
||||
which is why this is a "toggle" - it just flips some endianness for certain byte ranges.
|
||||
|
||||
If you prefer something a little more explicit, see [MsGuidToUuid] and/or [UuidToMsGuid].
|
||||
Alternatively call [IsMsGuid] or [IsRfc] directly.
|
||||
*/
|
||||
func ToggleUuidMsGuid(orig uuid.UUID) (converted uuid.UUID) {
|
||||
|
||||
var cb [16]byte
|
||||
var ob [16]byte = orig
|
||||
|
||||
// Can just directly map the allocations;
|
||||
// the operation is the exact same regardless of whether the original is RFC and target is MS or vice versa.
|
||||
cb = [16]byte{
|
||||
// THESE GET ENDIAN-SWAPPED
|
||||
ob[3], ob[2], ob[1], ob[0], // "A"
|
||||
ob[5], ob[4], // "B"
|
||||
ob[7], ob[6], // "C"
|
||||
// THESE STAY THE SAME (should be BE for both)
|
||||
ob[8], ob[9], ob[10], ob[11], // "D"
|
||||
ob[12], ob[13], ob[14], ob[15], // "E"
|
||||
}
|
||||
|
||||
converted = uuid.UUID(cb)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
UuidToMsGuid converts a UUID to a Microsoft GUID.
|
||||
|
||||
If [DetectMsGuid] indicates a good likelihood for u already being a Microsoft GUID
|
||||
(greater than or equal) to [MsGuidThreshold], msGUID will be equal to u.
|
||||
(If it detects it as unflipped endianness, it will automatically be flipped by this function.)
|
||||
|
||||
See [MsGuidToUuid] for the inverse.
|
||||
*/
|
||||
func UuidToMsGuid(u uuid.UUID) (msGUID uuid.UUID) {
|
||||
|
||||
var msCmp int
|
||||
var flipped int
|
||||
|
||||
if msCmp, flipped = DetectMsGuid(u, 0x00); msCmp >= MsGuidThreshold && msCmp > flipped {
|
||||
msGUID = u
|
||||
return
|
||||
}
|
||||
msGUID = ToggleUuidMsGuid(u)
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user