!> Create a random (version 4) Universally Unique Identifier (UUID) !> !> A UUID looks like "4042E716-2556-4715-90F0-C6518463B4E5" and is a !> random 128-bit number. This is sufficiently random that we can !> except zero collisions in any relevant timeframe, and so is useful !> for uniquely identifing simulations, for example. !> !> If compiled with the preprocessor macro `GK_HAS_LIBUUID > 0`, then !> it uses a wrapper around the C `libuuid` library (which must be !> linked). Otherwise, uses a fallback random number generator. !> !> This module contains its own implementation of the MT19937 !> psuedo-random number generator (PRNG) so that it doesn't intefere !> with the PRNG state used in the rest of the program. module uuid_mod use, intrinsic :: iso_c_binding, only: c_signed_char implicit none private public :: generate_uuid public :: uuid_len !> Length of the UUID character integer, parameter :: uuid_len = 36 contains !> Convert a 1-byte integer into hexadecimal elemental function c_signed_char_to_hex(char) result(hex) integer(c_signed_char), intent(in) :: char character(len=2) :: hex ! Note this almost certainly writes the result in uppercase write(hex, '(z2.2)') char end function c_signed_char_to_hex !> Convert an array of 1-byte integers into UUID format: !> "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" function format_uuid(uuid_in) character(len=uuid_len) :: format_uuid character(2), dimension(0:15) :: uuid_as_hex character(32) :: uuid_unformatted integer(c_signed_char), dimension(16), intent(in) :: uuid_in integer :: i uuid_as_hex = c_signed_char_to_hex(uuid_in) do i = 0, 15 uuid_unformatted(2*i+1:2*i+2) = uuid_as_hex(i) end do format_uuid = & uuid_unformatted(1:8) // "-" // uuid_unformatted(9:12) // "-" // & uuid_unformatted(13:16) // "-" // uuid_unformatted(17:20) // "-" //& uuid_unformatted(21:) end function format_uuid !> Generate a version 4 UUID using either the libuuid library, or !> our own random number generator wrapper function generate_uuid() #if !GK_HAS_LIBUUID use mt19937, only: mt19937_type #endif character(len=uuid_len) :: generate_uuid integer(c_signed_char), dimension(16) :: uuid_version_4 #if GK_HAS_LIBUUID ! Use a wrapper for the C library libuuid interface subroutine uuid_generate_c(uuid_out) bind(C, name="uuid_generate") import integer(c_signed_char), dimension(16) :: uuid_out end subroutine uuid_generate_c end interface call uuid_generate_c(uuid_version_4) #else type(mt19937_type) :: prng integer :: i ! Always re-start the PRNG call prng%set_seed(get_random_seed()) ! Generate random numbers between 0 and 255 do i = 1, size(uuid_version_4) uuid_version_4(i) = int(prng%generate() * huge(uuid_version_4), kind=c_signed_char) end do ! Version must be 0100xxxx (i.e. 0x4X) uuid_version_4(7) = iand(uuid_version_4(7), 79_c_signed_char) uuid_version_4(7) = ior(uuid_version_4(7), 64_c_signed_char) ! Variant must be 10xxxxxx (i.e 0x8X - 0xBX) uuid_version_4(9) = iand(uuid_version_4(9), -65_c_signed_char) uuid_version_4(9) = ior(uuid_version_4(9), -127_c_signed_char) #endif generate_uuid = format_uuid(uuid_version_4) end function generate_uuid #if !GK_HAS_LIBUUID !> Uses a method for getting a nice seed for the PRNG taken from !> [gfortran !> documentation](https://gcc.gnu.org/onlinedocs/gcc-6.4.0/gfortran/RANDOM_005fSEED.html) function get_random_seed() result(seed) use, intrinsic :: iso_fortran_env, only: int64 implicit none integer :: seed integer(int64) :: time integer :: time_values(8) ! Convert from milliseconds to larger units integer, parameter :: second = 1000 integer, parameter :: minute = 60*second integer, parameter :: hour = 60*minute integer(int64), parameter :: day = 24*hour integer(int64), parameter :: month = 31*day integer(int64), parameter :: year = 365_int64*day integer, parameter :: un = 6544 integer :: istat ! First try if the OS provides a random number generator open(unit=un, file="/dev/urandom", access="stream", & form="unformatted", action="read", status="old", iostat=istat) if (istat == 0) then read(un) seed close(un) else ! Fallback to using current system time call system_clock(time) if (time == 0) then ! It's plausible the system_clock isn't POSIX/epoch time so ! let's try a different method to get the time call date_and_time(values=time_values) time = (time_values(1) - 1970)*year & + time_values(2)*month & + time_values(3)*day & + time_values(5)*hour & + time_values(6)*minute & + time_values(7)*second & + time_values(8) end if ! Ideally here we'd also XOR the PID, which would help when ! launching multiple processes at the same time. Note that if ! you need a consistent UUID across MPI ranks, you'll need ! another function to generate one UUID and broadcast that ! One step of linear congruential generator time = mod(time, 4294967296_int64) time = mod(time*279470273_int64, 4294967291_int64) seed = int(mod(time, int(huge(0), int64)), kind(seed)) end if end function get_random_seed #endif end module uuid_mod