Skip to content

Commit 8eca2c2

Browse files
matbesanconStefanKarpinski
authored andcommitted
UUIDs: add uuid5 function (#28761)
This function matches Python and Rust behavior but does not match the `uuidgen` command-line tool.
1 parent 493068b commit 8eca2c2

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

stdlib/UUIDs/docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DocTestSetup = :(using UUIDs, Random)
77
```@docs
88
UUIDs.uuid1
99
UUIDs.uuid4
10+
UUIDs.uuid5
1011
UUIDs.uuid_version
1112
```
1213

stdlib/UUIDs/src/UUIDs.jl

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ module UUIDs
44

55
using Random
66

7-
export UUID, uuid1, uuid4, uuid_version
7+
import SHA
8+
9+
export UUID, uuid1, uuid4, uuid5, uuid_version
810

911
import Base: UUID
1012

@@ -22,6 +24,13 @@ julia> uuid_version(uuid4())
2224
"""
2325
uuid_version(u::UUID) = Int((u.value >> 76) & 0xf)
2426

27+
# Some UUID namespaces provided in the appendix of RFC 4122
28+
# https://tools.ietf.org/html/rfc4122.html#appendix-C
29+
const namespace_dns = UUID(0x6ba7b8109dad11d180b400c04fd430c8) # 6ba7b810-9dad-11d1-80b4-00c04fd430c8
30+
const namespace_url = UUID(0x6ba7b8119dad11d180b400c04fd430c8) # 6ba7b811-9dad-11d1-80b4-00c04fd430c8
31+
const namespace_oid = UUID(0x6ba7b8129dad11d180b400c04fd430c8) # 6ba7b812-9dad-11d1-80b4-00c04fd430c8
32+
const namespace_x500 = UUID(0x6ba7b8149dad11d180b400c04fd430c8) # 6ba7b814-9dad-11d1-80b4-00c04fd430c8
33+
2534
"""
2635
uuid1([rng::AbstractRNG=GLOBAL_RNG]) -> UUID
2736
@@ -81,4 +90,40 @@ function uuid4(rng::AbstractRNG=Random.GLOBAL_RNG)
8190
UUID(u)
8291
end
8392

93+
"""
94+
uuid5(ns::UUID, name::String) -> UUID
95+
96+
Generates a version 5 (namespace and domain-based) universally unique identifier (UUID),
97+
as specified by RFC 4122.
98+
99+
# Examples
100+
```jldoctest
101+
julia> rng = MersenneTwister(1234);
102+
103+
julia> u4 = uuid4(rng)
104+
UUID("196f2941-2d58-45ba-9f13-43a2532b2fa8")
105+
106+
julia> u5 = uuid5(u4, "julia")
107+
UUID("b37756f8-b0c0-54cd-a466-19b3d25683bc")
108+
```
109+
"""
110+
function uuid5(ns::UUID, name::String)
111+
nsbytes = zeros(UInt8, 16)
112+
nsv = ns.value
113+
for idx in Base.OneTo(16)
114+
nsbytes[idx] = nsv >> 120
115+
nsv = nsv << 8
116+
end
117+
hash_result = SHA.sha1(append!(nsbytes, convert(Vector{UInt8}, codeunits(unescape_string(name)))))
118+
# set version number to 5
119+
hash_result[7] = (hash_result[7] & 0x0F) | (0x50)
120+
hash_result[9] = (hash_result[9] & 0x3F) | (0x80)
121+
v = zero(UInt128)
122+
#use only the first 16 bytes of the SHA1 hash
123+
for idx in Base.OneTo(16)
124+
v = (v << 0x08) | hash_result[idx]
125+
end
126+
return UUID(v)
127+
end
128+
84129
end

stdlib/UUIDs/test/runtests.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,53 @@ using Test, UUIDs, Random
44

55
u1 = uuid1()
66
u4 = uuid4()
7+
u5 = uuid5(u1, "julia")
78
@test uuid_version(u1) == 1
89
@test uuid_version(u4) == 4
10+
@test uuid_version(u5) == 5
911
@test u1 == UUID(string(u1)) == UUID(GenericString(string(u1)))
1012
@test u4 == UUID(string(u4)) == UUID(GenericString(string(u4)))
13+
@test u5 == UUID(string(u5)) == UUID(GenericString(string(u5)))
1114
@test u1 == UUID(UInt128(u1))
1215
@test u4 == UUID(UInt128(u4))
16+
@test u5 == UUID(UInt128(u5))
1317
@test uuid4(MersenneTwister(0)) == uuid4(MersenneTwister(0))
1418
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-446655440000")
1519
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-44665544000098")
1620
@test_throws ArgumentError UUID("z50e8400-e29b-41d4-a716-446655440000")
21+
22+
# results similar to Python builtin uuid
23+
# To reproduce the sequence
24+
#=
25+
import uuid
26+
uuids = [uuid.UUID("22b4a8a1-e548-4eeb-9270-60426d66a48e")]
27+
for _ in range(5):
28+
uuids.append(uuid.uuid5(uuids[-1], "julia"))
29+
=#
30+
31+
const following_uuids = [
32+
UUID("22b4a8a1-e548-4eeb-9270-60426d66a48e"),
33+
UUID("30ea6cfd-c270-569f-b4cb-795dead63686"),
34+
UUID("31099374-e3a0-5fde-9482-791c639bf29b"),
35+
UUID("6b34b357-a348-53aa-8c71-fb9b06c3a51e"),
36+
UUID("fdbd7d4d-c462-59cc-ae6a-0c3b010240e2"),
37+
UUID("d8cc6298-75d5-57e0-996c-279259ab365c"),
38+
]
39+
40+
for (idx, init_uuid) in enumerate(following_uuids[1:end-1])
41+
next_id = uuid5(init_uuid, "julia")
42+
@test next_id == following_uuids[idx+1]
43+
end
44+
45+
# Python-generated UUID following each of the standard namespaces
46+
const standard_namespace_uuids = [
47+
(UUIDs.namespace_dns, UUID("00ca23ad-40ef-500c-a910-157de3950d07")),
48+
(UUIDs.namespace_oid, UUID("b7bf72b0-fb4e-538b-952a-3be296f07f6d")),
49+
(UUIDs.namespace_url, UUID("997cd5be-4705-5439-9fe6-d77b18d612e5")),
50+
(UUIDs.namespace_x500, UUID("993c6684-82e7-5cdb-bd46-9bff0362e6a9")),
51+
]
52+
53+
for (init_uuid, next_uuid) in standard_namespace_uuids
54+
result = uuid5(init_uuid, "julia")
55+
@test next_uuid == result
56+
end

0 commit comments

Comments
 (0)