Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ iex> UUID.uuid5("fcfe5f21-8a08-4c9a-9f97-29d2fd6a27b9", "my.domain.com")
"b8e85535-761a-586f-9c04-0fb0df2cbe84"
```

### UUID v6

Generated using a combination of time since the west adopted the gregorian calendar and either the node id MAC address or random bytes.
Valid node types are `:mac_address` or `:random_bytes` and defaults to `:mac_address`.

```elixir
iex> UUID.uuid6()
"1eb0d1d0-126a-6495-9a93-171634969e27"

iex> UUID.uuid6(:random_bytes)
"1eb0d1d5-c3fa-6b2e-8d7a-ef182baf6b94"
```

### Formatting

All UUID generator functions have an optional format parameter as the last argument.
Expand Down
9 changes: 9 additions & 0 deletions bench/uuid_bench.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ defmodule UUIDBench do
UUID.uuid5(:dns, "test.example.com")
end

bench "uuid6 mac_address" do
UUID.uuid6(:mac_address)
:ok
end

bench "uuid6 random_bytes" do
UUID.uuid6(:random_bytes)
:ok
end
end
176 changes: 174 additions & 2 deletions lib/uuid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,47 @@ defmodule UUID do
See [RFC 4122](http://www.ietf.org/rfc/rfc4122.txt).
"""

@typedoc "One of representations of UUID."
@type t :: str | raw | hex | urn

@typedoc "String representation of UUID."
@type str :: <<_ :: 288>>

@typedoc "Raw binary representation of UUID."
@type raw :: <<_ :: 128>>

@typedoc "Hex representation of UUID."
@type hex :: <<_ :: 256>>

@typedoc "URN representation of UUID."
@type urn :: <<_ :: 360>>

@typedoc "Type of UUID representation."
@type type :: :default | :raw | :hex | :urn

@typedoc "UUID version."
@type version :: 1 | 3 | 4 | 5

@typedoc "Variant of UUID: see RFC for the details."
@type variant :: :reserved_future
| :reserved_microsoft
| :rfc4122
| :reserved_ncs

@typedoc """
Namespace for UUID v3 and v5 (with some predefined UUIDs as atom aliases).
"""
@type namespace :: :dns | :url | :oid | :x500 | :nil | str

@typedoc "Information about given UUID (see `info/1`)"
@type info :: [
uuid: str,
binary: raw,
type: type,
version: version,
variant: variant
]

@nanosec_intervals_offset 122_192_928_000_000_000 # 15 Oct 1582 to 1 Jan 1970.
@nanosec_intervals_factor 10 # Microseconds to nanoseconds factor.

Expand All @@ -13,6 +54,7 @@ defmodule UUID do
@uuid_v3 3 # UUID v3 identifier.
@uuid_v4 4 # UUID v4 identifier.
@uuid_v5 5 # UUID v5 identifier.
@uuid_v6 6 # UUID v6 identifier.

@urn "urn:uuid:" # UUID URN prefix.

Expand Down Expand Up @@ -69,6 +111,7 @@ defmodule UUID do
```

"""
@spec info(str) :: {:ok, info} | {:error, any}
def info(uuid) do
try do
{:ok, UUID.info!(uuid)}
Expand Down Expand Up @@ -126,6 +169,7 @@ defmodule UUID do
```

"""
@spec info!(str) :: info
def info!(<<uuid::binary>> = uuid_string) do
{type, <<uuid::128>>} = uuid_string_to_hex_pair(uuid)
<<_::48, version::4, _::12, v0::1, v1::1, v2::1, _::61>> = <<uuid::128>>
Expand Down Expand Up @@ -167,6 +211,8 @@ defmodule UUID do
```

"""
@spec binary_to_string!(raw) :: str
@spec binary_to_string!(raw, type) :: t
def binary_to_string!(uuid, format \\ :default)
def binary_to_string!(<<uuid::binary>>, format) do
uuid_to_string(<<uuid::binary>>, format)
Expand Down Expand Up @@ -203,6 +249,7 @@ defmodule UUID do
```

"""
@spec string_to_binary!(str) :: raw
def string_to_binary!(<<uuid::binary>>) do
{_type, <<uuid::128>>} = uuid_string_to_hex_pair(uuid)
<<uuid::128>>
Expand Down Expand Up @@ -238,6 +285,8 @@ defmodule UUID do
```

"""
@spec uuid1() :: str
@spec uuid1(type) :: t
def uuid1(format \\ :default) do
uuid1(uuid1_clockseq(), uuid1_node(), format)
end
Expand Down Expand Up @@ -270,6 +319,8 @@ defmodule UUID do
```

"""
@spec uuid1(clock_seq :: <<_::14>>, node :: <<_::48>>) :: str
@spec uuid1(clock_seq :: <<_::14>>, node :: <<_::48>>, type) :: t
def uuid1(clock_seq, node, format \\ :default)
def uuid1(<<clock_seq::14>>, <<node::48>>, format) do
<<time_hi::12, time_mid::16, time_low::32>> = uuid1_time()
Expand Down Expand Up @@ -317,6 +368,8 @@ defmodule UUID do
```

"""
@spec uuid3(namespace, name :: binary) :: str
@spec uuid3(namespace, name :: binary, type) :: t
def uuid3(namespace_or_uuid, name, format \\ :default)
def uuid3(:dns, <<name::binary>>, format) do
namebased_uuid(:md5, <<0x6ba7b8109dad11d180b400c04fd430c8::128, name::binary>>)
Expand Down Expand Up @@ -375,8 +428,10 @@ defmodule UUID do
```

"""
@spec uuid4() :: str
def uuid4(), do: uuid4(:default)

@spec uuid4(type | :strong | :weak) :: t
def uuid4(:strong), do: uuid4(:default) # For backwards compatibility.
def uuid4(:weak), do: uuid4(:default) # For backwards compatibility.
def uuid4(format) do
Expand Down Expand Up @@ -419,6 +474,8 @@ defmodule UUID do
```

"""
@spec uuid5(namespace, binary) :: str
@spec uuid5(namespace, name :: binary, type) :: t
def uuid5(namespace_or_uuid, name, format \\ :default)
def uuid5(:dns, <<name::binary>>, format) do
namebased_uuid(:sha1, <<0x6ba7b8109dad11d180b400c04fd430c8::128, name::binary>>)
Expand Down Expand Up @@ -450,6 +507,114 @@ defmodule UUID do
"Invalid argument; Expected: :dns|:url|:oid|:x500|:nil OR String, String"
end

@doc """
Generate a new UUID v6. This version uses a combination of one or more of:
unix epoch, random bytes, pid hash, and hardware address.

Accepts a `node_type` argument that can be either `:mac_address` or
`:random_bytes`. Defaults to `:mac_address`. However, if there is a security
concern with using a MAC address, use `:random_bytes`.

See the [RFC draft, section 3.3](https://tools.ietf.org/html/draft-peabody-dispatch-new-uuid-format-00#section-3.3)
for more information on the node parts.

## Examples

iex> UUID.uuid6()
"1eb0d28f-da4c-6eb2-adc1-0242ac120002"

iex> UUID.uuid6(:random_bytes, :default)
"1eb0d297-eb1e-62a6-a37f-a55eda5dd6e4"

iex> UUID.uuid6(:random_bytes, :hex)
"1eb0d298502563fcadcd25e5d0a44c1a"

iex> UUID.uuid6(:random_bytes, :urn)
"urn:uuid:1eb0d298-ca10-6914-ab0e-7d7e1e6e1808"

iex> UUID.uuid6(:random_bytes, :raw)
<<30, 176, 210, 153, 52, 23, 102, 230, 164, 146, 99, 66, 4, 72, 220, 114>>

iex> UUID.uuid6(:random_bytes, :slug)
"HrDSmab8ZnqR4SKw4LN-UA"

"""
def uuid6(node_type \\ :mac_address, format \\ :default)
when node_type in [:mac_address, :random_bytes] do
uuid6(uuid1_clockseq(), uuid6_node(node_type), format)
end

@doc """
Generate a new UUID v6, with an existing clock sequence and node address. This
version uses a combination of one or more of: unix epoch, random bytes,
pid hash, and hardware address.
"""
def uuid6(<<clock_seq::14>>, <<node::48>>, format) do
<<time_hi::12, time_mid::16, time_low::32>> = uuid1_time()
<<time_low1::20, time_low2::12>> = <<time_low::32>>
<<clock_seq_hi::6, clock_seq_low::8>> = <<clock_seq::14>>

<<time_hi::12, time_mid::16, time_low1::20, @uuid_v6::4, time_low2::12,
@variant10::2, clock_seq_hi::6, clock_seq_low::8, node::48>>
|> uuid_to_string(format)
end
def uuid6(_, _, _) do
raise ArgumentError, message:
"Invalid argument; Expected: <<clock_seq::14>>, <<node::48>>"
end

@doc """
Convert a UUID v1 to a UUID v6 in the same format.

## Examples

iex> UUID.uuid1_to_uuid6("dafc431a-0d21-11eb-adc1-0242ac120002")
"1eb0d21d-afc4-631a-adc1-0242ac120002"

iex> UUID.uuid1_to_uuid6("2vxDGg0hEeutwQJCrBIAAg")
"HrDSHa_EYxqtwQJCrBIAAg"

iex> UUID.uuid1_to_uuid6(<<218, 252, 67, 26, 13, 33, 17, 235, 173, 193, 2, 66, 172, 18, 0, 2>>)
<<30, 176, 210, 29, 175, 196, 99, 26, 173, 193, 2, 66, 172, 18, 0, 2>>

"""
def uuid1_to_uuid6(uuid1) do
{format, ub1} = uuid_string_to_hex_pair(uuid1)

<<time_low::32, time_mid::16, @uuid_v1::4, time_hi::12, rest::binary>> = ub1
<<time_low1::20, time_low2::12>> = <<time_low::32>>

<<time_hi::12, time_mid::16, time_low1::20, @uuid_v6::4, time_low2::12,
rest::binary>>
|> uuid_to_string(format)
end

@doc """
Convert a UUID v6 to a UUID v1 in the same format.

## Examples

iex> UUID.uuid6_to_uuid1("1eb0d21d-afc4-631a-adc1-0242ac120002")
"dafc431a-0d21-11eb-adc1-0242ac120002"

iex> UUID.uuid6_to_uuid1("HrDSHa_EYxqtwQJCrBIAAg")
"2vxDGg0hEeutwQJCrBIAAg"

iex> UUID.uuid6_to_uuid1(<<30, 176, 210, 29, 175, 196, 99, 26, 173, 193, 2, 66, 172, 18, 0, 2>>)
<<218, 252, 67, 26, 13, 33, 17, 235, 173, 193, 2, 66, 172, 18, 0, 2>>

"""
def uuid6_to_uuid1(uuid6) do
{format, ub6} = uuid_string_to_hex_pair(uuid6)

<<time_hi::12, time_mid::16, time_low1::20, @uuid_v6::4, time_low2::12,
rest::binary>> = ub6
<<time_low::32>> = <<time_low1::20, time_low2::12>>

<<time_low::32, time_mid::16, @uuid_v1::4, time_hi::12, rest::binary>>
|> uuid_to_string(format)
end

#
# Internal utility functions.
#
Expand Down Expand Up @@ -584,6 +749,13 @@ defmodule UUID do
<<rnd_hi::7, 1::1, rnd_low::40>>
end

defp uuid6_node(:mac_address) do
uuid1_node()
end
defp uuid6_node(:random_bytes) do
:crypto.strong_rand_bytes(6)
end

# Generate a hash of the given data.
defp namebased_uuid(:md5, data) do
md5 = :crypto.hash(:md5, data)
Expand Down Expand Up @@ -618,7 +790,7 @@ defmodule UUID do
defp variant(_) do
raise ArgumentError, message: "Invalid argument; Not valid variant bits"
end

defp hex_str_to_binary(<< a1, a2, a3, a4, a5, a6, a7, a8,
b1, b2, b3, b4,
c1, c2, c3, c4,
Expand All @@ -633,7 +805,7 @@ defmodule UUID do
d(e5)::4, d(e6)::4, d(e7)::4, d(e8)::4,
d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4 >>
end

@compile {:inline, d: 1}

defp d(?0), do: 0
Expand Down
9 changes: 6 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ defmodule UUID.Mixfile do

# List of dependencies.
defp deps do
[{:ex_doc, "~> 0.19", only: :dev},
{:earmark, "~> 1.2", only: :dev},
{:benchfella, "~> 0.3", only: :dev}]
[
{:ex_doc, "~> 0.19", only: :dev},
{:earmark, "~> 1.2", only: :dev},
{:benchfella, "~> 0.3", only: :dev},
{:dialyxir, only: :dev}
]
end

# Description.
Expand Down
15 changes: 9 additions & 6 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
%{
"benchfella": {:hex, :benchfella, "0.3.5", "b2122c234117b3f91ed7b43b6e915e19e1ab216971154acd0a80ce0e9b8c05f5", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"benchfella": {:hex, :benchfella, "0.3.5", "b2122c234117b3f91ed7b43b6e915e19e1ab216971154acd0a80ce0e9b8c05f5", [:mix], [], "hexpm", "23f27cbc482cbac03fc8926441eb60a5e111759c17642bac005c3225f5eb809d"},
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
"earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"},
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
}
2 changes: 1 addition & 1 deletion test/doc_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule UUID.DocTest do
use ExUnit.Case, async: true

doctest UUID, except: [uuid1: 1, uuid1: 3, uuid4: 0, uuid4: 1, uuid4: 2]
doctest UUID, except: [uuid1: 1, uuid1: 3, uuid4: 0, uuid4: 1, uuid4: 2, uuid6: 2, uuid6: 3]
end
3 changes: 3 additions & 0 deletions test/info_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ urn v4 rfc4122 || [uuid: "urn:uuid:184064df-820d-4fd2-9301-4749098cb786", binary
default v5 rfc4122 || [uuid: "dda8df72-e4a1-5b98-a88d-8197e539c0bf", binary: <<221, 168, 223, 114, 228, 161, 91, 152, 168, 141, 129, 151, 229, 57, 192, 191>>, type: :default, version: 5, variant: :rfc4122] || dda8df72-e4a1-5b98-a88d-8197e539c0bf
hex v5 rfc4122 || [uuid: "dda8df72e4a15b98a88d8197e539c0bf", binary: <<221, 168, 223, 114, 228, 161, 91, 152, 168, 141, 129, 151, 229, 57, 192, 191>>, type: :hex, version: 5, variant: :rfc4122] || dda8df72e4a15b98a88d8197e539c0bf
urn v5 rfc4122 || [uuid: "urn:uuid:dda8df72-e4a1-5b98-a88d-8197e539c0bf", binary: <<221, 168, 223, 114, 228, 161, 91, 152, 168, 141, 129, 151, 229, 57, 192, 191>>, type: :urn, version: 5, variant: :rfc4122] || urn:uuid:dda8df72-e4a1-5b98-a88d-8197e539c0bf
default v6 rfc4122 || [uuid: "1e65da3a-36e8-617e-9fcc-c8bcc8a0b17d", binary: <<30, 101, 218, 58, 54, 232, 97, 126, 159, 204, 200, 188, 200, 160, 177, 125>>, type: :default, version: 6, variant: :rfc4122] || 1e65da3a-36e8-617e-9fcc-c8bcc8a0b17d
hex v6 rfc4122 || [uuid: "1e65da3a36e8617e9fccc8bcc8a0b17d", binary: <<30, 101, 218, 58, 54, 232, 97, 126, 159, 204, 200, 188, 200, 160, 177, 125>>, type: :hex, version: 6, variant: :rfc4122] || 1e65da3a36e8617e9fccc8bcc8a0b17d
urn v6 rfc4122 || [uuid: "urn:uuid:1e65da3a-36e8-617e-9fcc-c8bcc8a0b17d", binary: <<30, 101, 218, 58, 54, 232, 97, 126, 159, 204, 200, 188, 200, 160, 177, 125>>, type: :urn, version: 6, variant: :rfc4122] || urn:uuid:1e65da3a-36e8-617e-9fcc-c8bcc8a0b17d
Loading