Created
September 14, 2015 17:56
-
-
Save dwilliamson/59d4e90b3270ded4e09a to your computer and use it in GitHub Desktop.
GUID Generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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