defmodule UUID do use Bitwise, only_operators: true @moduledoc """ UUID generator and utilities for [Elixir](http://elixir-lang.org/). See [RFC 4122](http://www.ietf.org/rfc/rfc4122.txt). """ @nanosec_intervals_offset 122192928000000000 # 15 Oct 1582 to 1 Jan 1970. @nanosec_intervals_factor 10 # Microseconds to nanoseconds factor. @variant10 2 # Variant, corresponds to variant 1 0 of RFC 4122. @uuid_v1 1 # UUID v1 identifier. @uuid_v3 3 # UUID v3 identifier. @uuid_v4 4 # UUID v4 identifier. @uuid_v5 5 # UUID v5 identifier. @urn "urn:uuid:" # UUID URN prefix. @doc """ Inspect a UUID and return information about its type, version and variant. Timestamp portion is not checked to see if it's in the future, and therefore not yet assignable. See "Validation mechanism" in section 3 of RFC 4122. ## Examples iex> UUID.info("4995555a-1361-4b45-5803-9ef16250956c") [uuid: "4995555a-1361-4b45-5803-9ef16250956c", type: :default, version: 4, variant: :rfc4122] iex> UUID.info("8ea1513df8a14dea9bea6b8f4b5b6e73") [uuid: "8ea1513df8a14dea9bea6b8f4b5b6e73", type: :hex, version: 4, variant: :rfc4122] iex> UUID.info("urn:uuid:ef1b1a28-ee34-11e3-8813-14109ff1a304") [uuid: "urn:uuid:ef1b1a28-ee34-11e3-8813-14109ff1a304", type: :urn, version: 1, variant: :rfc4122] """ def info(<> = original) do {type, <>} = uuid_string_to_hex_pair(uuid) <<_::48, version::4, _::12, v0::1, v1::1, v2::1, _::61>> = <> [uuid: original, type: type, version: version, variant: variant(<>)] end def info(_) do raise ArgumentError, message: "Invalid argument; Expected: String" end @doc """ Generate a new UUID v1. This version uses a combination of one or more of: unix epoch, random bytes, pid hash, and hardware address. ## Examples iex> UUID.uuid1() "cdfdaf44-ee35-11e3-846b-14109ff1a304" iex> UUID.uuid1(:default) "cdfdaf44-ee35-11e3-846b-14109ff1a304" iex> UUID.uuid1(:hex) "cdfdaf44ee3511e3846b14109ff1a304" iex> UUID.uuid1(:urn) "urn:uuid:cdfdaf44-ee35-11e3-846b-14109ff1a304" """ def uuid1(format \\ :default) do <> = uuid1_time() <> = uuid1_clockseq() <> = uuid1_node() <> |> uuid_to_string format end @doc """ Generate a new UUID v3. This version uses an MD5 hash of fixed value (chosen based on a namespace atom - see Appendix C of RFC 4122) and a name value. Can also be given an existing UUID String instead of a namespace atom. Accepted arguments are: :dns|:url|:oid|:x500|:nil OR uuid, String ## Examples iex> UUID.uuid3(:dns, "my.domain.com") "eecf4c2b-f6e5-3ae3-bef7-1ea09f91d3e7" iex> UUID.uuid3(:dns, "my.domain.com", :default) "eecf4c2b-f6e5-3ae3-bef7-1ea09f91d3e7" iex> UUID.uuid3(:dns, "my.domain.com", :hex) "eecf4c2bf6e53ae3bef71ea09f91d3e7" iex> UUID.uuid3(:dns, "my.domain.com", :urn) "urn:uuid:eecf4c2b-f6e5-3ae3-bef7-1ea09f91d3e7" iex> UUID.uuid3("cdfdaf44-ee35-11e3-846b-14109ff1a304", "my.domain.com") "8808f33a-3e11-3708-919e-15fba88908db" """ def uuid3(:dns, <>, format \\ :default) do namebased_uuid(:md5, <<"6ba7b8109dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid3(:url, <>, format) do namebased_uuid(:md5, <<"6ba7b8119dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid3(:oid, <>, format) do namebased_uuid(:md5, <<"6ba7b8129dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid3(:x500, <>, format) do namebased_uuid(:md5, <<"6ba7b8149dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid3(:nil, <>, format) do namebased_uuid(:md5, <<0::128, name::binary>>) |> uuid_to_string format end def uuid3(<>, <>, format) do {_type, <>} = uuid_string_to_hex_pair(uuid) namebased_uuid(:md5, <>) |> uuid_to_string format end def uuid3(_, _, _) do raise ArgumentError, message: "Invalid argument; Expected: :dns|:url|:oid|:x500|:nil OR String, String" end @doc """ Generate a new UUID v4. This version uses pseudo-random bytes generated by the `crypto` module. ## Examples iex> UUID.uuid4() "fb49a0ec-d60c-4d20-9264-3b4cfe272106" iex> UUID.uuid4(:default) "fb49a0ec-d60c-4d20-9264-3b4cfe272106" iex> UUID.uuid4(:hex) "fb49a0ecd60c4d2092643b4cfe272106" iex> UUID.uuid4(:urn) "urn:uuid:fb49a0ec-d60c-4d20-9264-3b4cfe272106" """ def uuid4(format \\ :default) do <> = :crypto.rand_bytes(16) <> |> uuid_to_string format end @doc """ Generate a new UUID v5. This version uses an SHA1 hash of fixed value (chosen based on a namespace atom - see Appendix C of RFC 4122) and a name value. Can also be given an existing UUID String instead of a namespace atom. Accepted arguments are: :dns|:url|:oid|:x500|:nil OR uuid, String ## Examples iex> UUID.uuid5(:dns, "my.domain.com") "ae119419-7776-563d-b6e8-8a177abccc7a" iex> UUID.uuid5(:dns, "my.domain.com", :default) "ae119419-7776-563d-b6e8-8a177abccc7a" iex> UUID.uuid5(:dns, "my.domain.com", :hex) "ae1194197776563db6e88a177abccc7a" iex> UUID.uuid5(:dns, "my.domain.com", :urn) "urn:uuid:ae119419-7776-563d-b6e8-8a177abccc7a" iex> UUID.uuid5("fb49a0ec-d60c-4d20-9264-3b4cfe272106", "my.domain.com") "822cab19-df58-5eb4-98b5-c96c15c76d32" """ def uuid5(:dns, <>, format \\ :default) do namebased_uuid(:sha1, <<"6ba7b8109dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid5(:url, <>, format) do namebased_uuid(:sha1, <<"6ba7b8119dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid5(:oid, <>, format) do namebased_uuid(:sha1, <<"6ba7b8129dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid5(:x500, <>, format) do namebased_uuid(:sha1, <<"6ba7b8149dad11d180b400c04fd430c8", name::binary>>) |> uuid_to_string format end def uuid5(:nil, <>, format) do namebased_uuid(:sha1, <<0::128, name::binary>>) |> uuid_to_string format end def uuid5(<>, <>, format) do {_type, <>} = uuid_string_to_hex_pair(uuid) namebased_uuid(:sha1, <>) |> uuid_to_string format end def uuid5(_, _, _) do raise ArgumentError, message: "Invalid argument; Expected: :dns|:url|:oid|:x500|:nil OR String, String" end # # Internal utility functions. # # Convert UUID bytes to String. defp uuid_to_string(<>, :default) do :io_lib.format("~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", [u0, u1, u2, u3, u4]) |> to_string end defp uuid_to_string(<>, :hex) do :io_lib.format("~32.16.0b", [u]) |> to_string end defp uuid_to_string(<>, :urn) do @urn <> uuid_to_string(<>, :default) end defp uuid_to_string(_u, format) do raise ArgumentError, message: "Invalid format " <> to_string(format) <> "; Expected: :default|:hex|:urn" end # Extract the type (:default etc) and pure byte value from a UUID String. defp uuid_string_to_hex_pair(<>) do uuid = String.downcase(uuid) {type, hex_str} = case uuid do <> -> {:default, <>} <> -> {:hex, <>} <<@urn, u0::64, "-", u1::32, "-", u2::32, "-", u3::32, "-", u4::96>> -> {:urn, <>} _ -> raise ArgumentError, message: "Invalid argument; Not a valid UUID: " <> uuid end fread = :io_lib.fread('~16u', to_char_list(hex_str)) case fread do {:ok, [hex_int], []} -> {type, <>} _ -> raise ArgumentError, message: "Invalid argument; Not a valid UUID: " <> uuid end end # Get unix epoch as a 60-bit timestamp. defp uuid1_time() do {mega_sec, sec, micro_sec} = :erlang.now() epoch = (mega_sec * 1000000000000 + sec * 1000000 + micro_sec) timestamp = @nanosec_intervals_offset + @nanosec_intervals_factor * epoch <> end # Generate random clock sequence. defp uuid1_clockseq() do pid_sum = :erlang.phash2(:erlang.self()) <> = :crypto.rand_bytes(12) now_xor_pid = {n0 ^^^ pid_sum, n1 ^^^ pid_sum, n2 ^^^ pid_sum} :random.seed(now_xor_pid) rnd = :random.uniform(2 <<< 14 - 1) <> end # Get local IEEE 802 (MAC) address, or a random node id if it can't be found. defp uuid1_node() do {:ok, ifs0} = :inet.getifaddrs() uuid1_node(ifs0) end # Skip loopback adapter. defp uuid1_node([{"lo", _if_config} | rest]) do uuid1_node(rest) end defp uuid1_node([{_if_name, if_config} | rest]) do case :lists.keyfind(:hwaddr, 1, if_config) do {:hwaddr, hw_addr} -> :erlang.list_to_binary(hw_addr) :false -> uuid1_node(rest) end end defp uuid1_node(_) do <> = :crypto.rand_bytes(6) <> end # Generate a hash of the given data. defp namebased_uuid(:md5, data) do md5 = :crypto.hash(:md5, data) compose_namebased_uuid(@uuid_v3, md5) end defp namebased_uuid(:sha1, data) do <> = :crypto.hash(:sha, data) compose_namebased_uuid(@uuid_v5, <>) end # Format the given hash as a UUID. defp compose_namebased_uuid(version, hash) do <> = hash <> end # Identify the UUID variant according to section 4.1.1 of RFC 4122. defp variant(<<1, 1, 1>>) do :reserved_future end defp variant(<<1, 1, _v>>) do :reserved_microsoft end defp variant(<<1, 0, _v>>) do :rfc4122 end defp variant(<<0, _v::[2, binary]>>) do :reserved_ncs end defp variant(_) do raise ArgumentError, message: "Invalid argument; Not valid variant bits" end end