Skip to content

Instantly share code, notes, and snippets.

@dwilliamson
Created September 14, 2015 17:56
Show Gist options
  • Save dwilliamson/59d4e90b3270ded4e09a to your computer and use it in GitHub Desktop.
Save dwilliamson/59d4e90b3270ded4e09a to your computer and use it in GitHub Desktop.
GUID Generator
namespace core
{
//
// A globally unique identifier with a generator that should allow multiple machines/users
// to never generate the same ID.
//
struct clcpp_attr(reflect) Guid
{
Guid()
: mac_address_hash(0)
, user_name_hash(0)
, time_id(0)
, time_random(0)
{
}
// Hash of the MAC address of whathever network adapter is found
u32 mac_address_hash;
// Hash of the user name logged into the OS
u32 user_name_hash;
// A combination of all components of the current time
u32 time_id;
// A random
u32 time_random;
core::String256 GetHexString() const;
static Guid Generate();
};
}
namespace
{
struct AdapterRef
{
IP_ADAPTER_INFO* info;
u32 sort_key;
};
int CompareAdapters(const void* a, const void* b)
{
AdapterRef& adp_a = *(AdapterRef*)a;
AdapterRef& adp_b = *(AdapterRef*)b;
unsigned int ka = adp_a.sort_key;
unsigned int kb = adp_b.sort_key;
return (ka < kb) - (ka > kb);
}
u32 GetMACAddressHash()
{
// Return cached hash?
static u32 last_hash = UINT_MAX;
if (last_hash != UINT_MAX)
return last_hash;
IP_ADAPTER_INFO* info = nullptr;
// Make an initial call to get size of required buffer
// Rely on API returning an error -- with valid size -- if first parameter is NULL
DWORD size = 0;
if (GetAdaptersInfo(nullptr, &size) == ERROR_BUFFER_OVERFLOW)
info = (IP_ADAPTER_INFO*)malloc(size);
if (info == nullptr)
{
last_hash = 0;
return 0;
}
// Get adapter linked list
if (GetAdaptersInfo(info, &size) != NO_ERROR)
{
last_hash = 0;
free(info);
return 0;
}
// Build adapter list
core::Vector<AdapterRef> adapters;
for (IP_ADAPTER_INFO* cur_info = info; cur_info != nullptr; cur_info = cur_info->Next)
{
// Skip adapters that don't contain a MAC address
if (cur_info->AddressLength < 4)
continue;
AdapterRef adapter;
adapter.info = cur_info;
// Use 2 bits to prioritise ethernet adapters
unsigned int type_priority = 0;
switch (cur_info->Type)
{
case MIB_IF_TYPE_ETHERNET: type_priority = 3; break;
case IF_TYPE_IEEE80211: type_priority = 2; break;
case MIB_IF_TYPE_PPP: type_priority = 1; break;
default: type_priority = 0; break;
}
// Add to collection, giving highest priority to connected adapters
adapter.sort_key = (cur_info->LeaseObtained != 0) << 2 | type_priority;
adapters.push_back(adapter);
}
// No adapters?
if (adapters.size() == 0)
{
last_hash = 0;
free(info);
return 0;
}
// Sort by priority and hash the MAC address of the highest
qsort(adapters.data(), adapters.size(), sizeof(AdapterRef), CompareAdapters);
IP_ADAPTER_INFO* adp_info = adapters[0].info;
u32 hash = core::Murmur3_HashData(adp_info->Address, adp_info->AddressLength);
last_hash = hash;
free(info);
return hash;
}
}
core::Guid core::Guid::Generate()
{
Guid guid;
guid.mac_address_hash = GetMACAddressHash();
// Hash the user name
const char* user_name = getenv("USERNAME");
core::Assert(user_name && user_name[0]);
guid.user_name_hash = Murmur3_HashText(user_name);
union
{
u32 time_id;
struct
{
u32 year : 6;
u32 month : 4;
u32 day : 5;
u32 hours : 5;
u32 minutes : 6;
u32 seconds : 6;
};
} time;
// Generate the time id
SYSTEMTIME system_time;
GetSystemTime(&system_time);
time.year = system_time.wYear - 2000;
time.month = system_time.wMonth;
time.day = system_time.wDay;
time.hours = system_time.wHour;
time.minutes = system_time.wMinute;
time.seconds = system_time.wSecond;
guid.time_id = time.time_id;
// Use microseconds in the current second as the time random
// Guaranteed not to collide as long as GUID requests can't come through faster than
// a million every second. Recent measurements on my Intel i5 can only squeeze 285
// through a second.
// TODO: Protect against faster-than-a-microsecond requests.
// TODO: Make thread-safe. Although statistically next to impossible, multiple threads
// can request the same GUID if they land on the same microsecond. Filter all requests
// through a minimum-wait spin lock or something.
core::Microseconds us = GetHighResTimer();
u32 us_this_second = u32(us.value) % 1000000;
guid.time_random = us_this_second;
// Assume the MAC address and user hashes resolve to the same on each call
// Ensure the last 64-bits of the GUID are unique on each call
static u32 last_time_id = 0, last_time_random = 0;
core::Assert(last_time_id != guid.time_id || last_time_random != guid.time_random);
last_time_id = guid.time_id;
last_time_random = guid.time_random;
return guid;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment