From e15be5ca8211c41432f29ac5d4f86d6801efec65 Mon Sep 17 00:00:00 2001 From: James Reed Date: Sat, 20 Apr 2019 07:33:30 -0600 Subject: [PATCH] Vendor uuid library and use it to generate IDs --- README.md | 2 + init.lua | 16 ++--- uuid.lua | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 uuid.lua diff --git a/README.md b/README.md index 84f4039..de498f5 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,5 @@ descriptions of all functions. awesome-launch is licensed under the GNU General Public License v3.0 or later (see [LICENSE](LICENSE)). + +`uuid.lua` is licensed under the Apache License v2.0. diff --git a/init.lua b/init.lua index f067472..05b7851 100644 --- a/init.lua +++ b/init.lua @@ -6,6 +6,9 @@ local awful = require("awful") local gears = require("gears") +local uuid = require("uuid") + +uuid.seed() local pending = {} @@ -71,17 +74,6 @@ function launch.client.by_cmdline(cmd, filter) end end -local function gen_id() - local i = 1 - local id = "1" - local ids = get_ids() - while ids[id] do - i = i + 1 - id = string.format("%d", i) - end - return id -end - --- Spawn a client with wm-launch. -- -- @param cmd The command. @@ -95,7 +87,7 @@ end -- @function launch.spawn local function spawn(cmd, args) args = args or {} - local id = args.id or gen_id() + local id = args.id or uuid() local data = { props = args.props or {}, tags = args.tags, diff --git a/uuid.lua b/uuid.lua new file mode 100644 index 0000000..e65ddb8 --- /dev/null +++ b/uuid.lua @@ -0,0 +1,205 @@ +--------------------------------------------------------------------------------------- +-- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications) +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS-IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- see http://www.ietf.org/rfc/rfc4122.txt +-- +-- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard +-- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This +-- is solved by using the node field from a version 1 UUID. It represents the mac address. +-- +-- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module. +-- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket +-- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid). +-- +-- **6-nov-2015 Please take note of this issue**; [https://github.com/Mashape/kong/issues/478](https://github.com/Mashape/kong/issues/478) +-- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes. +-- So make sure to seed only once, application wide. And to not have multiple processes do that +-- simultaneously (like nginx does for example). + +local M = {} +local math = require('math') +local os = require('os') +local string = require('string') + +local bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below. +local lua_version = tonumber(_VERSION:match("%d%.*%d*")) -- grab Lua version used + +local MATRIX_AND = {{0,0},{0,1} } +local MATRIX_OR = {{0,1},{1,1}} +local HEXES = '0123456789abcdef' + +local math_floor = math.floor +local math_random = math.random +local math_abs = math.abs +local string_sub = string.sub +local to_number = tonumber +local assert = assert +local type = type + +-- performs the bitwise operation specified by truth matrix on two numbers. +local function BITWISE(x, y, matrix) + local z = 0 + local pow = 1 + while x > 0 or y > 0 do + z = z + (matrix[x%2+1][y%2+1] * pow) + pow = pow * 2 + x = math_floor(x/2) + y = math_floor(y/2) + end + return z +end + +local function INT2HEX(x) + local s,base = '',16 + local d + while x > 0 do + d = x % base + 1 + x = math_floor(x/base) + s = string_sub(HEXES, d, d)..s + end + while #s < 2 do s = "0" .. s end + return s +end + +---------------------------------------------------------------------------- +-- Creates a new uuid. Either provide a unique hex string, or make sure the +-- random seed is properly set. The module table itself is a shortcut to this +-- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`. +-- +-- For proper use there are 3 options; +-- +-- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no +-- parameter, eg. `my_uuid = uuid()` +-- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`, +-- and request a uuid using no parameter, eg. `my_uuid = uuid()` +-- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string, +-- eg. `my_uuid = uuid(my_networkcard_macaddress)` +-- +-- @return a properly formatted uuid string +-- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly! +-- @usage +-- local uuid = require("uuid") +-- print("here's a new uuid: ",uuid()) +function M.new(hwaddr) + -- bytes are treated as 8bit unsigned bytes. + local bytes = { + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255), + math_random(0, 255) + } + + if hwaddr then + assert(type(hwaddr)=="string", "Expected hex string, got "..type(hwaddr)) + -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters + local i,str = #hwaddr, hwaddr + hwaddr = "" + while i>0 and #hwaddr<12 do + local c = str:sub(i,i):lower() + if HEXES:find(c, 1, true) then + -- valid HEX character, so append it + hwaddr = c..hwaddr + end + i = i - 1 + end + assert(#hwaddr == 12, "Provided string did not contain at least 12 hex characters, retrieved '"..hwaddr.."' from '"..str.."'") + + -- no split() in lua. :( + bytes[11] = to_number(hwaddr:sub(1, 2), 16) + bytes[12] = to_number(hwaddr:sub(3, 4), 16) + bytes[13] = to_number(hwaddr:sub(5, 6), 16) + bytes[14] = to_number(hwaddr:sub(7, 8), 16) + bytes[15] = to_number(hwaddr:sub(9, 10), 16) + bytes[16] = to_number(hwaddr:sub(11, 12), 16) + end + + -- set the version + bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND) + bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR) + -- set the variant + bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND) + bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR) + return INT2HEX(bytes[1])..INT2HEX(bytes[2])..INT2HEX(bytes[3])..INT2HEX(bytes[4]).."-".. + INT2HEX(bytes[5])..INT2HEX(bytes[6]).."-".. + INT2HEX(bytes[7])..INT2HEX(bytes[8]).."-".. + INT2HEX(bytes[9])..INT2HEX(bytes[10]).."-".. + INT2HEX(bytes[11])..INT2HEX(bytes[12])..INT2HEX(bytes[13])..INT2HEX(bytes[14])..INT2HEX(bytes[15])..INT2HEX(bytes[16]) +end + +---------------------------------------------------------------------------- +-- Improved randomseed function. +-- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer +-- range. If this happens, the seed will be 0 or 1 and all randomness will +-- be gone (each application run will generate the same sequence of random +-- numbers in that case). This improved version drops the most significant +-- bits in those cases to get the seed within the proper range again. +-- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive) +-- @return the (potentially modified) seed used +-- @usage +-- local socket = require("socket") -- gettime() has higher precision than os.time() +-- local uuid = require("uuid") +-- -- see also example at uuid.seed() +-- uuid.randomseed(socket.gettime()*10000) +-- print("here's a new uuid: ",uuid()) +function M.randomseed(seed) + seed = math_floor(math_abs(seed)) + if seed >= (2^bitsize) then + -- integer overflow, so reduce to prevent a bad seed + seed = seed - math_floor(seed / 2^bitsize) * (2^bitsize) + end + if lua_version < 5.2 then + -- 5.1 uses (incorrect) signed int + math.randomseed(seed - 2^(bitsize-1)) + else + -- 5.2 uses (correct) unsigned int + math.randomseed(seed) + end + return seed +end + +---------------------------------------------------------------------------- +-- Seeds the random generator. +-- It does so in 2 possible ways; +-- +-- 1. use `os.time()`: this only offers resolution to one second (used when +-- LuaSocket hasn't been loaded yet +-- 2. use luasocket `gettime()` function, but it only does so when LuaSocket +-- has been required already. +-- @usage +-- local socket = require("socket") -- gettime() has higher precision than os.time() +-- -- LuaSocket loaded, so below line does the same as the example from randomseed() +-- uuid.seed() +-- print("here's a new uuid: ",uuid()) +function M.seed() + if package.loaded["socket"] and package.loaded["socket"].gettime then + return M.randomseed(package.loaded["socket"].gettime()*10000) + else + return M.randomseed(os.time()) + end +end + +return setmetatable( M, { __call = function(self, hwaddr) return self.new(hwaddr) end} )