diff --git a/sky_phone/source/server/phone.lua b/sky_phone/source/server/phone.lua index 6c3f800..02aab46 100644 --- a/sky_phone/source/server/phone.lua +++ b/sky_phone/source/server/phone.lua @@ -25,24 +25,25 @@ local function affected_rows(result) end local function reserve_imei() - for _ = 1, 20 do - local uuid_rows = Sky.Query("SELECT UUID() AS `id`", {}) - local uuid = uuid_rows[1] and uuid_rows[1].id + local imei = SkyPhoneImei.Reserve(function() + local rows = Sky.Query("SELECT UUID() AS `id`", {}) + local uuid = rows[1] and rows[1].id if type(uuid) ~= "string" then error("[sky_phone] Database did not generate entropy for an IMEI.") end - - local imei = SkyPhoneImei.FromEntropy(uuid) + return uuid + end, function(candidate) local result = Sky.Query([[ INSERT IGNORE INTO `sky_phone_devices` (`imei`, `device_name`) VALUES (?, ?) - ]], { imei, Config.Phone.DeviceName }) - if affected_rows(result) > 0 then - return imei - end - end + ]], { candidate, Config.Phone.DeviceName }) + return affected_rows(result) > 0 + end) - error("[sky_phone] Could not generate a unique IMEI after 20 attempts.") + if not imei then + error("[sky_phone] Could not generate a unique IMEI after 20 attempts.") + end + return imei end local function find_device_slots(source, imei) diff --git a/sky_phone/source/shared/imei.lua b/sky_phone/source/shared/imei.lua index 95b8fe1..54c0a57 100644 --- a/sky_phone/source/shared/imei.lua +++ b/sky_phone/source/shared/imei.lua @@ -45,3 +45,14 @@ function SkyPhoneImei.IsValid(value) end return value:sub(15, 15) == SkyPhoneImei.CheckDigit(value:sub(1, 14)) end + +function SkyPhoneImei.Reserve(entropy_factory, reserve) + for _ = 1, 20 do + local imei = SkyPhoneImei.FromEntropy(entropy_factory()) + if imei and reserve(imei) then + return imei + end + end + + return nil +end diff --git a/tests/imei.lua b/tests/imei.lua index 56df8bc..317bafa 100644 --- a/tests/imei.lua +++ b/tests/imei.lua @@ -7,4 +7,20 @@ assert(SkyPhoneImei.IsValid(imei), "generated IMEI must pass its check digit") assert(not SkyPhoneImei.IsValid(imei:sub(1, 14) .. tostring((tonumber(imei:sub(15, 15)) + 1) % 10)), "invalid check digit must fail") assert(not SkyPhoneImei.IsValid("123"), "short IMEI must fail") +local entropy = { + "550e8400-e29b-41d4-a716-446655440000", + "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +} +local entropy_index = 0 +local reserve_attempts = 0 +local reserved = SkyPhoneImei.Reserve(function() + entropy_index = entropy_index + 1 + return entropy[entropy_index] +end, function() + reserve_attempts = reserve_attempts + 1 + return reserve_attempts == 2 +end) +assert(reserve_attempts == 2, "IMEI reservation must retry database collisions") +assert(reserved == SkyPhoneImei.FromEntropy(entropy[2]), "IMEI reservation must return the accepted candidate") + print("IMEI tests passed")