Skip to content

Commit

Permalink
UUIDs: add uuid5 function (#28761)
Browse files Browse the repository at this point in the history
This function matches Python and Rust behavior but does not match the `uuidgen` command-line tool.
  • Loading branch information
matbesancon authored and StefanKarpinski committed Sep 26, 2018
1 parent 493068b commit 8eca2c2
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 1 deletion.
1 change: 1 addition & 0 deletions stdlib/UUIDs/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ DocTestSetup = :(using UUIDs, Random)
```@docs
UUIDs.uuid1
UUIDs.uuid4
UUIDs.uuid5
UUIDs.uuid_version
```

Expand Down
47 changes: 46 additions & 1 deletion stdlib/UUIDs/src/UUIDs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ module UUIDs

using Random

export UUID, uuid1, uuid4, uuid_version
import SHA

export UUID, uuid1, uuid4, uuid5, uuid_version

import Base: UUID

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

# Some UUID namespaces provided in the appendix of RFC 4122
# https://tools.ietf.org/html/rfc4122.html#appendix-C
const namespace_dns = UUID(0x6ba7b8109dad11d180b400c04fd430c8) # 6ba7b810-9dad-11d1-80b4-00c04fd430c8
const namespace_url = UUID(0x6ba7b8119dad11d180b400c04fd430c8) # 6ba7b811-9dad-11d1-80b4-00c04fd430c8
const namespace_oid = UUID(0x6ba7b8129dad11d180b400c04fd430c8) # 6ba7b812-9dad-11d1-80b4-00c04fd430c8
const namespace_x500 = UUID(0x6ba7b8149dad11d180b400c04fd430c8) # 6ba7b814-9dad-11d1-80b4-00c04fd430c8

"""
uuid1([rng::AbstractRNG=GLOBAL_RNG]) -> UUID
Expand Down Expand Up @@ -81,4 +90,40 @@ function uuid4(rng::AbstractRNG=Random.GLOBAL_RNG)
UUID(u)
end

"""
uuid5(ns::UUID, name::String) -> UUID
Generates a version 5 (namespace and domain-based) universally unique identifier (UUID),
as specified by RFC 4122.
# Examples
```jldoctest
julia> rng = MersenneTwister(1234);
julia> u4 = uuid4(rng)
UUID("196f2941-2d58-45ba-9f13-43a2532b2fa8")
julia> u5 = uuid5(u4, "julia")
UUID("b37756f8-b0c0-54cd-a466-19b3d25683bc")
```
"""
function uuid5(ns::UUID, name::String)
nsbytes = zeros(UInt8, 16)
nsv = ns.value
for idx in Base.OneTo(16)
nsbytes[idx] = nsv >> 120
nsv = nsv << 8
end
hash_result = SHA.sha1(append!(nsbytes, convert(Vector{UInt8}, codeunits(unescape_string(name)))))
# set version number to 5
hash_result[7] = (hash_result[7] & 0x0F) | (0x50)
hash_result[9] = (hash_result[9] & 0x3F) | (0x80)
v = zero(UInt128)
#use only the first 16 bytes of the SHA1 hash
for idx in Base.OneTo(16)
v = (v << 0x08) | hash_result[idx]
end
return UUID(v)
end

end
40 changes: 40 additions & 0 deletions stdlib/UUIDs/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,53 @@ using Test, UUIDs, Random

u1 = uuid1()
u4 = uuid4()
u5 = uuid5(u1, "julia")
@test uuid_version(u1) == 1
@test uuid_version(u4) == 4
@test uuid_version(u5) == 5
@test u1 == UUID(string(u1)) == UUID(GenericString(string(u1)))
@test u4 == UUID(string(u4)) == UUID(GenericString(string(u4)))
@test u5 == UUID(string(u5)) == UUID(GenericString(string(u5)))
@test u1 == UUID(UInt128(u1))
@test u4 == UUID(UInt128(u4))
@test u5 == UUID(UInt128(u5))
@test uuid4(MersenneTwister(0)) == uuid4(MersenneTwister(0))
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-446655440000")
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-44665544000098")
@test_throws ArgumentError UUID("z50e8400-e29b-41d4-a716-446655440000")

# results similar to Python builtin uuid
# To reproduce the sequence
#=
import uuid
uuids = [uuid.UUID("22b4a8a1-e548-4eeb-9270-60426d66a48e")]
for _ in range(5):
uuids.append(uuid.uuid5(uuids[-1], "julia"))
=#

const following_uuids = [
UUID("22b4a8a1-e548-4eeb-9270-60426d66a48e"),
UUID("30ea6cfd-c270-569f-b4cb-795dead63686"),
UUID("31099374-e3a0-5fde-9482-791c639bf29b"),
UUID("6b34b357-a348-53aa-8c71-fb9b06c3a51e"),
UUID("fdbd7d4d-c462-59cc-ae6a-0c3b010240e2"),
UUID("d8cc6298-75d5-57e0-996c-279259ab365c"),
]

for (idx, init_uuid) in enumerate(following_uuids[1:end-1])
next_id = uuid5(init_uuid, "julia")
@test next_id == following_uuids[idx+1]
end

# Python-generated UUID following each of the standard namespaces
const standard_namespace_uuids = [
(UUIDs.namespace_dns, UUID("00ca23ad-40ef-500c-a910-157de3950d07")),
(UUIDs.namespace_oid, UUID("b7bf72b0-fb4e-538b-952a-3be296f07f6d")),
(UUIDs.namespace_url, UUID("997cd5be-4705-5439-9fe6-d77b18d612e5")),
(UUIDs.namespace_x500, UUID("993c6684-82e7-5cdb-bd46-9bff0362e6a9")),
]

for (init_uuid, next_uuid) in standard_namespace_uuids
result = uuid5(init_uuid, "julia")
@test next_uuid == result
end

0 comments on commit 8eca2c2

Please sign in to comment.