From 89ca41ce523e7bdae1b8504fbd7349148f7c4eb0 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Sun, 17 Dec 2023 02:13:07 +0100 Subject: [PATCH] hange(default): no longer set a default random generator safer to have the user explictly pick one --- .busted | 2 +- .editorconfig | 11 +- .github/workflows/lint.yml | 38 ++++ .github/workflows/unix_build.yml | 52 ++++++ .gitignore | 44 +++++ .luacheckrc | 26 +-- .luacov | 5 + .travis.yml | 30 ---- CHANGELOG.md | 50 ++++++ LICENSE.md | 204 ++++++++++++++++++++++ Makefile | 115 ++++++++++++ README.md | 52 ++---- config.ld | 15 +- doc_topics/01-introduction.md | 16 ++ doc_topics/ldoc.css | 291 +++++++++++++++++++++++++++++++ rockspecs/uuid-0.2-1.rockspec | 2 +- spec/uuid_spec.lua | 28 ++- src/uuid.lua | 186 ++++++++++---------- uuid-dev-1.rockspec | 7 +- 19 files changed, 983 insertions(+), 191 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/unix_build.yml create mode 100644 .luacov delete mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 doc_topics/01-introduction.md create mode 100644 doc_topics/ldoc.css diff --git a/.busted b/.busted index ca66496..313c185 100644 --- a/.busted +++ b/.busted @@ -1,7 +1,7 @@ return { default = { verbose = true, - coverage = false, + coverage = true, output = "gtest", }, } diff --git a/.editorconfig b/.editorconfig index c039bab..a1b3a15 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,13 +12,12 @@ indent_size = 2 [*.rockspec] indent_style = space -indent_size = 3 +indent_size = 2 -[*.c] -indent_style = tab +[*.md] +indent_style = space +indent_size = 2 [Makefile] indent_style = tab - -[*.md] -indent_style = space +indent_size = 4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5de37de --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,38 @@ +name: Lint + +concurrency: + # for PR's cancel the running task, if another commit is pushed + group: ${{ github.workflow }} ${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +on: + # build on PR and push-to-main. This works for short-lived branches, and saves + # CPU cycles on duplicated tests. + # For long-lived branches that diverge, you'll want to run on all pushes, not + # just on push-to-main. + pull_request: {} + push: + branches: + - main + + +jobs: + lint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: leafo/gh-actions-lua@v10 + with: + luaVersion: "5.4" + + - uses: leafo/gh-actions-luarocks@v4 + + - name: dependencies + run: | + make dev + + - name: lint + run: | + make lint diff --git a/.github/workflows/unix_build.yml b/.github/workflows/unix_build.yml new file mode 100644 index 0000000..c60df76 --- /dev/null +++ b/.github/workflows/unix_build.yml @@ -0,0 +1,52 @@ +name: "Unix build" + +concurrency: + # for PR's cancel the running task, if another commit is pushed + group: ${{ github.workflow }} ${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +on: + # build on PR and push-to-main. This works for short-lived branches, and saves + # CPU cycles on duplicated tests. + # For long-lived branches that diverge, you'll want to run on all pushes, not + # just on push-to-main. + pull_request: {} + push: + branches: + - main + + +jobs: + test: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: false + matrix: + luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3", "luajit-openresty"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: leafo/gh-actions-lua@v10 + with: + luaVersion: ${{ matrix.luaVersion }} + + - uses: leafo/gh-actions-luarocks@v4 + + - name: dependencies + run: | + make dev + luarocks install luacov-coveralls + + - name: test + run: | + make testinst BUSTED='--coverage --Xoutput "--color"' + + - name: Report test coverage + if: success() + continue-on-error: true + run: luacov-coveralls + env: + COVERALLS_REPO_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index 43c6239..eae1ed2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,45 @@ +# Compiled Lua sources +luac.out + +# LuaCov files +*.report.out +*.stats.out + +# luarocks build files *.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + diff --git a/.luacheckrc b/.luacheckrc index cf442da..4dc4ec4 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,30 +1,32 @@ -std = "ngx_lua" unused_args = false redefined = false max_line_length = false - globals = { +-- "ngx", } - not_globals = { + -- deprecated Lua 5.0 functions "string.len", "table.getn", } - -ignore = { - --"6.", -- ignore whitespace warnings +include_files = { + "**/*.lua", + "**/*.rockspec", + ".busted", + ".luacheckrc", } +files["spec/**/*.lua"] = { + std = "+busted", +} exclude_files = { - --"spec/fixtures/invalid-module.lua", - --"spec-old-api/fixtures/invalid-module.lua", + -- The Github Actions Lua Environment + ".lua", + ".luarocks", + ".install", } - -files["spec/**/*.lua"] = { - std = "ngx_lua+busted", -} diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..3267fca --- /dev/null +++ b/.luacov @@ -0,0 +1,5 @@ +modules = { + ["uuid.*"] = "src" +} + +runreport = true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cdfb872..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: python -sudo: false - -env: - - LUA="lua 5.1" - - LUA="lua 5.2" - - LUA="lua 5.3" - - LUA="luajit 2.0" - - LUA="luajit 2.0 --compat 5.2" - - LUA="luajit 2.1" - - LUA="luajit 2.1 --compat 5.2" - -before_install: - - pip install hererocks - - hererocks here -r^ --$LUA - - source here/bin/activate - - luarocks install luacheck - - luarocks install busted - - luarocks install luacov-coveralls - -install: - - luarocks install luasocket - - luarocks make - -script: - - luacheck . - - busted - -#after_success: - #- luacov-coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4aede0b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# CHANGELOG + +## Versioning + +This library is versioned based on Semantic Versioning ([SemVer](https://semver.org/)). + +#### Version scoping + +The scope of what is covered by the version number excludes: + +- error messages; the text of the messages can change, unless specifically documented. + +#### Releasing new versions + +- create a release branch +- update the changelog below +- update version and copyright-years in `./LICENSE.md` (bottom) and `./src/uuid.lua` (in + doc-comments header) +- create a new rockspec and update the version inside the new rockspec:
+ `cp uuid-dev-1.rockspec ./rockspecs/uuid-X.Y.Z-1.rockspec` +- test: run `make test` and `make lint` +- clean and render the docs: run `make clean` and `make docs` +- commit the changes as `release X.Y.Z` +- push the commit, and create a release PR +- after merging tag the release commit with `X.Y.Z` +- upload to LuaRocks:
+ `luarocks upload ./rockspecs/uuid-X.Y.Z-1.rockspec --api-key=ABCDEFGH` +- test the newly created rock:
+ `luarocks install uuid` + +## Version history + +### Version X.Y.Z, unreleased + +- bla bla + +### Version 0.3, released 11-Jul-2021 + +- Fix: set proper type for UUIDv4 type +- Feat: improve seeding for OpenResty +- Doc: fix link in readme + +### Version 0.2, released 09-May-2013 + +- Bugfix; 0-hex was displayed as "" instead of "00", making some uuids too short +- Bugfix; math.randomseed() overflow caused bad seeding + +### Version 0.1, released 28-Apr-2013 + + - initial version diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c13b4c6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,204 @@ +# Apache 2.0 License +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 Rackspace, 2013-2021 Thijs Schreijer + + 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. +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a05fc71 --- /dev/null +++ b/Makefile @@ -0,0 +1,115 @@ +# additional Busted options to pass +BUSTED:= + +# SCM rockspec label; scm/cvs/dev +SCM_LABEL:=$(shell cat *.rockspec | grep "local package_version" | sed "s/ //g" | sed "s/localpackage_version=//g" | sed "s/\"//g") +ROCK_REV:=$(shell cat *.rockspec | grep "local rockspec_revision" | sed "s/ //g" | sed "s/localrockspec_revision=//g" | sed "s/\"//g") +ROCK_NAME:=$(shell cat *.rockspec | grep "local package_name" | sed "s/ //g" | sed "s/localpackage_name=//g" | sed "s/\"//g") +ROCKSPEC:=${ROCK_NAME}-${SCM_LABEL}-${ROCK_REV}.rockspec +TAB=$(shell printf "\t") + +# dev/test dependencies; versions can be pinned. Example: "ldoc 1.4.6" +DEV_ROCKS = "busted" "luacheck" "ldoc" "luacov" + + +target_not_specified: help + @exit 1 + + +help: + @echo "Available make targets for ${ROCK_NAME}:" + @echo "" + @echo "install: uses LuaRocks to install ${ROCK_NAME}" + @echo "uninstall: uninstalls ALL versions of ${ROCK_NAME} (using LuaRocks with" + @echo " the '--force' flag)" + @echo "clean: removes LuaCov output, packed rocks, and restores docs to the" + @echo " last commited version" + @echo "test: runs the test suite using Busted" + @echo "testinst: installs ${ROCK_NAME} and runs tests using the installed version" + @echo " (this modifies the local installation, but also tests the" + @echo " .rockspec file). This is best used when testing in CI." + @echo "lint: will validate all 'rockspec' files using LuaRocks, and the" + @echo " '.lua' files with LuaCheck" + @echo "doc/docs: regenerates the documentation using LDoc" + @echo "deps: installs the module dependencies" + @echo "dev: installs the development dependencies (Busted, LuaCheck, etc.)" + @echo "help: displays this list of make targets" + @echo "" + + +install: luarocks + luarocks make + + +uninstall: luarocks + if (luarocks list --porcelain ${ROCK_NAME} | grep "^${ROCK_NAME}${TAB}" | grep -q "installed") ; then \ + luarocks remove ${ROCK_NAME} --force; \ + fi; + + +# note: restore the docs to the last committed version +clean: clean_luacov clean_luarocks clean_doc + git checkout docs + + +.PHONY: test +test: clean_luacov dev + busted ${BUSTED} + + +# test while having the code installed; also tests the rockspec, but +# this will modify the local luarocks installation/tree!! +.PHONY: testinst +testinst: clean_luacov dev uninstall install + busted --lpath="" --cpath="" ${BUSTED} + + +.PHONY: lint +lint: dev + @echo "luarocks lint ..." + @for spec in $(shell find . -type f -name "*.rockspec") ; do \ + (luarocks lint $$spec && echo "$$spec [OK]") || (echo "$$spec [NOK]"; exit 1); \ + done + luacheck . + + +.PHONY: doc +doc: clean_doc dev + mkdir -p ./docs + ldoc . --date="" + + +.PHONY: docs +docs: doc + + +.PHONY: deps +deps: luarocks + luarocks install $(ROCKSPEC) --deps-only + + +.PHONY: dev +dev: luarocks deps + @for rock in $(DEV_ROCKS) ; do \ + (luarocks list --porcelain $$rock | grep -q "installed") || (luarocks install $$rock || exit 1); \ + done; + + +.PHONY: clean_doc +clean_doc: + $(RM) -r docs + + +.PHONY: clean_luarocks +clean_luarocks: + $(RM) *.rock + + +.PHONY: clean_luacov +clean_luacov: + $(RM) luacov.report.out luacov.stats.out + + +.PHONY: luarocks +luarocks: + @which luarocks > /dev/null || (echo "LuaRocks was not found. Please install and/or make available in the path." && exit 1) diff --git a/README.md b/README.md index c4df1cb..6f8d1f0 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,21 @@ -[![Build Status](https://travis-ci.com/Tieske/uuid.svg?branch=master)](https://travis-ci.com/Tieske/uuid) +[![Unix build](https://img.shields.io/github/actions/workflow/status/Tieske/uuid/unix_build.yml?branch=main&label=Unix%20build&logo=linux)](https://github.com/Tieske/uuid/actions/workflows/unix_build.yml) +[![Coveralls code coverage](https://img.shields.io/coveralls/github/Tieske/uuid?logo=coveralls)](https://coveralls.io/github/Tieske/uuid) +[![Lint](https://github.com/Tieske/uuid/workflows/Lint/badge.svg)](https://github.com/Tieske/uuid/actions/workflows/lint.yml) +[![SemVer](https://img.shields.io/github/v/tag/Tieske/uuid?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) -uuid -==== +# uuid -Modified module from [Rackspace](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) original. Generates uuids in pure Lua. +Generates uuids in pure Lua, but requires a +good random generator/seed or a unique string. Please check the documentation. -Notes -===== -Please read [documentation](https://tieske.github.io/uuid/) carefully regarding random seeds or unique strings to be provided to get a decent randomized uuid value. +## License and copyright -Home -==== -[Source code](https://github.com/Tieske/uuid) is on github +See [LICENSE.md](LICENSE.md) -License & copyright -=================== -Rackspace (original) and Thijs Schreijer (modifications), Apache 2.0, see `uuid.lua` +## Documentation -Install -======= -Use LuaRocks. To fetch and install from a LuaRocks server do `luarocks install uuid`. -For a development installation from local source, do `luarocks make` from the main directory. +See [online documentation](https://Tieske.github.io/uuid/) -Test -==== -Tests are available and can be executed using [busted](http://olivinelabs.com/busted/), -and LuaCheck for linting. - -Changes -======= - -0.3 11-Jul-2021 - - - Fix: set proper type for UUIDv4 type - - Feat: improve seeding for OpenResty - - Doc: fix link in readme - -0.2 09-May-2013 - - - Bugfix; 0-hex was displayed as "" instead of "00", making some uuids too short - - Bugfix; math.randomseed() overflow caused bad seeding - -0.1 28-Apr-2013 - - - initial version +## Changelog & Versioning +See [CHANGELOG.md](CHANGELOG.md) diff --git a/config.ld b/config.ld index a3e59d8..95e989e 100644 --- a/config.ld +++ b/config.ld @@ -1,8 +1,15 @@ project='uuid' title='uuid generator' description='Module to generate uuids in pure Lua' -format='discount' -file='./src/' + +format='markdown' +use_markdown_titles = true +style="./doc_topics/" + +file={'./src/'} +topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'} + dir='docs' -readme='readme.md' -style='./docs/' +sort=true +sort_modules=true +all=false diff --git a/doc_topics/01-introduction.md b/doc_topics/01-introduction.md new file mode 100644 index 0000000..329ce30 --- /dev/null +++ b/doc_topics/01-introduction.md @@ -0,0 +1,16 @@ +# 1. Introduction + +High quality UUIDs v4 can only be generated if the source of the random data is good enough. +If it is predictable then it might become a security risk. + +Read up on the use of the `randomseed` and `seed` functions if you need a pure-Lua +implementation. Preferably the Lua-System module is used or an even stronger random +number generator. + +**Important:** the random seed is a global piece of data. Hence setting it is +an application level responsibility, libraries should never set it! + +See this issue; [https://github.com/Kong/kong/issues/478](https://github.com/Kong/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. diff --git a/doc_topics/ldoc.css b/doc_topics/ldoc.css new file mode 100644 index 0000000..5b9fbbf --- /dev/null +++ b/doc_topics/ldoc.css @@ -0,0 +1,291 @@ +body { + color: #47555c; + font-size: 16px; + font-family: "Open Sans", sans-serif; + margin: 0; + background: #eff4ff; +} + +a:link { color: #008fee; } +a:visited { color: #008fee; } +a:hover { color: #22a7ff; } + +h1 { font-size:26px; font-weight: normal; } +h2 { font-size:22px; font-weight: normal; } +h3 { font-size:18px; font-weight: normal; } +h4 { font-size:16px; font-weight: bold; } + +hr { + height: 1px; + background: #c1cce4; + border: 0px; + margin: 15px 0; +} + +code, tt { + font-family: monospace; +} +span.parameter { + font-family: monospace; + font-weight: bold; + color: rgb(99, 115, 131); +} +span.parameter:after { + content:":"; +} +span.types:before { + content:"("; +} +span.types:after { + content:")"; +} +.type { + font-weight: bold; font-style:italic +} + +p.name { + font-family: "Andale Mono", monospace; +} + +#navigation { + float: left; + background-color: white; + border-right: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; + + width: 14em; + vertical-align: top; + overflow: visible; +} + +#navigation br { + display: none; +} + +#navigation h1 { + background-color: white; + border-bottom: 1px solid #d3dbec; + padding: 15px; + margin-top: 0px; + margin-bottom: 0px; +} + +#navigation h2 { + font-size: 18px; + background-color: white; + border-bottom: 1px solid #d3dbec; + padding-left: 15px; + padding-right: 15px; + padding-top: 10px; + padding-bottom: 10px; + margin-top: 30px; + margin-bottom: 0px; +} + +#content h1 { + background-color: #2c3e67; + color: white; + padding: 15px; + margin: 0px; +} + +#content h2 { + background-color: #6c7ea7; + color: white; + padding: 15px; + padding-top: 15px; + padding-bottom: 15px; + margin-top: 0px; +} + +#content h2 a { + background-color: #6c7ea7; + color: white; + text-decoration: none; +} + +#content h2 a:hover { + text-decoration: underline; +} + +#content h3 { + font-style: italic; + padding-top: 15px; + padding-bottom: 4px; + margin-right: 15px; + margin-left: 15px; + margin-bottom: 5px; + border-bottom: solid 1px #bcd; +} + +#content h4 { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px #bcd; +} + +#content pre { + margin: 15px; +} + +pre { + background-color: rgb(50, 55, 68); + color: white; + border-radius: 3px; + /* border: 1px solid #C0C0C0; /* silver */ + padding: 15px; + overflow: auto; + font-family: "Andale Mono", monospace; +} + +#content ul pre.example { + margin-left: 0px; +} + +table.index { +/* border: 1px #00007f; */ +} +table.index td { text-align: left; vertical-align: top; } + +#navigation ul +{ + font-size:1em; + list-style-type: none; + margin: 1px 1px 10px 1px; + padding-left: 20px; +} + +#navigation li { + text-indent: -1em; + display: block; + margin: 3px 0px 0px 22px; +} + +#navigation li li a { + margin: 0px 3px 0px -1em; +} + +#content { + margin-left: 14em; +} + +#content p { + padding-left: 15px; + padding-right: 15px; +} + +#content table { + padding-left: 15px; + padding-right: 15px; + background-color: white; +} + +#content p, #content table, #content ol, #content ul, #content dl { + max-width: 900px; +} + +#about { + padding: 15px; + padding-left: 16em; + background-color: white; + border-top: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; +} + +table.module_list, table.function_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; + margin: 15px; +} +table.module_list td, table.function_list td { + border-width: 1px; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + border: solid 1px rgb(193, 204, 228); +} +table.module_list td.name, table.function_list td.name { + background-color: white; min-width: 200px; border-right-width: 0px; +} +table.module_list td.summary, table.function_list td.summary { + background-color: white; width: 100%; border-left-width: 0px; +} + +dl.function { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px rgb(193, 204, 228); + border-left: solid 1px rgb(193, 204, 228); + border-right: solid 1px rgb(193, 204, 228); + background-color: white; +} + +dl.function dt { + color: rgb(99, 123, 188); + font-family: monospace; + border-top: solid 1px rgb(193, 204, 228); + padding: 15px; +} + +dl.function dd { + margin-left: 15px; + margin-right: 15px; + margin-top: 5px; + margin-bottom: 15px; +} + +#content dl.function dd h3 { + margin-top: 0px; + margin-left: 0px; + padding-left: 0px; + font-size: 16px; + color: rgb(128, 128, 128); + border-bottom: solid 1px #def; +} + +#content dl.function dd ul, #content dl.function dd ol { + padding: 0px; + padding-left: 15px; + list-style-type: none; +} + +ul.nowrap { + overflow:auto; + white-space:nowrap; +} + +.section-description { + padding-left: 15px; + padding-right: 15px; +} + +/* stop sublists from having initial vertical space */ +ul ul { margin-top: 0px; } +ol ul { margin-top: 0px; } +ol ol { margin-top: 0px; } +ul ol { margin-top: 0px; } + +/* make the target distinct; helps when we're navigating to a function */ +a:target + * { + background-color: #FF9; +} + + +/* styles for prettification of source */ +pre .comment { color: #bbccaa; } +pre .constant { color: #a8660d; } +pre .escape { color: #844631; } +pre .keyword { color: #ffc090; font-weight: bold; } +pre .library { color: #0e7c6b; } +pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } +pre .string { color: #8080ff; } +pre .number { color: #f8660d; } +pre .operator { color: #2239a8; font-weight: bold; } +pre .preprocessor, pre .prepro { color: #a33243; } +pre .global { color: #c040c0; } +pre .user-keyword { color: #800080; } +pre .prompt { color: #558817; } +pre .url { color: #272fc2; text-decoration: underline; } diff --git a/rockspecs/uuid-0.2-1.rockspec b/rockspecs/uuid-0.2-1.rockspec index 17ad90a..d3ddba9 100644 --- a/rockspecs/uuid-0.2-1.rockspec +++ b/rockspecs/uuid-0.2-1.rockspec @@ -7,7 +7,7 @@ source = { description = { summary = "Generates uuids in pure Lua", detailed = [[ -Generates uuids in pure Lua, but requires a +Generates uuids in pure Lua, but requires a good random seed or a unique string. Please check the documentation. ]], license = "Apache 2.0", diff --git a/spec/uuid_spec.lua b/spec/uuid_spec.lua index 5323afa..043e569 100644 --- a/spec/uuid_spec.lua +++ b/spec/uuid_spec.lua @@ -1,14 +1,25 @@ -local uuid = require("uuid") -- start tests describe("Testing uuid library", function() - + local uuid + local old_get_random_bytes before_each(function() + uuid = require("uuid") + + old_get_random_bytes = uuid.get_random_bytes + uuid.get_random_bytes = function(n) + return string.char(0):rep(n) + end + end) + + after_each(function() + uuid.get_random_bytes = old_get_random_bytes end) it("tests generating a uuid", function() - assert.is_string(uuid.new()) - assert.is_string(uuid()) + local id = uuid.new() + assert.are.same('00000000-0000-4000-8000-000000000000', id) + assert.are.same(id, uuid()) end) it("tests the format of the generated uuid", function() @@ -27,9 +38,12 @@ describe("Testing uuid library", function() assert.has_error(function() uuid("123a4::xxyy;;590") end) -- too short after clean assert.has_error(function() uuid(true) end) -- not a string assert.has_error(function() uuid(123) end) -- not a string - assert.not_has_error(function() uuid("abcdefabcdef") end) -- hex only - assert.not_has_error(function() uuid("123456789012") end) -- right size - assert.not_has_error(function() uuid("1234567890123") end) -- oversize + assert.has_no.error(function() uuid("abcdefabcdef") end) -- hex only + assert.same('00000000-0000-4000-8000-abcdefabcdef', uuid("abcdefabcdef")) + assert.has_no.error(function() uuid("123456789012") end) -- right size + assert.same('00000000-0000-4000-8000-123456789012', uuid("123456789012")) + assert.has_no.error(function() uuid("1234567890123") end) -- oversize + assert.same('00000000-0000-4000-8000-123456789012', uuid("1234567890123")) end) it("tests uuid.seed() using luasocket gettime() if available, os.time() if unavailable", function() diff --git a/src/uuid.lua b/src/uuid.lua index f205a48..08a14c8 100644 --- a/src/uuid.lua +++ b/src/uuid.lua @@ -1,53 +1,28 @@ --------------------------------------------------------------------------------------- -- Copyright 2012 Rackspace (original), 2013-2021 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 +-- see [http://www.ietf.org/rfc/rfc4122.txt](http://www.ietf.org/rfc/rfc4122.txt) -- --- 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). --- --- **Important:** the random seed is a global piece of data. Hence setting it is --- an application level responsibility, libraries should never set it! --- --- See this issue; [https://github.com/Kong/kong/issues/478](https://github.com/Kong/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. +-- @license MIT, see `LICENSE.md`. local M = {} local math = require('math') local os = require('os') local string = require('string') +local format = string.format +local char = string.char +local byte = string.byte 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 @@ -65,18 +40,76 @@ local function BITWISE(x, y, matrix) 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 + +-- converts a HW identifier (mac address) to a string of byte values +local hwaddr_to_bytes do + -- typically there is 1 address, so cache that one + local hwaddr_stored, bytes_stored + + local function convert(hwaddr) + 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 hwaddr_clean = hwaddr:gsub("[^%x]",""):sub(1,12):lower() + assert(#hwaddr_clean == 12, "Provided string did not contain at least 12 hex characters, got '"..hwaddr.."'") + + return char( + to_number(hwaddr_clean:sub(1, 2), 16), + to_number(hwaddr_clean:sub(3, 4), 16), + to_number(hwaddr_clean:sub(5, 6), 16), + to_number(hwaddr_clean:sub(7, 8), 16), + to_number(hwaddr_clean:sub(9, 10), 16), + to_number(hwaddr_clean:sub(11, 12), 16) + ) + end + + function hwaddr_to_bytes(hwaddr) + if hwaddr_stored ~= hwaddr then + hwaddr_stored = hwaddr + bytes_stored = convert(hwaddr) + end + return bytes_stored end - while #s < 2 do s = "0" .. s end - return s end + +---------------------------------------------------------------------------- +-- [REPLACE] Should return a set of random bytes. +-- This function MUST be replaced by a proper implementation. This is done +-- purposely to force the user to think about the randomness of the bytes +-- generated. +-- @tparam integer n number of bytes to generate +-- @treturn string of random bytes +-- @usage +-- local ok, system = pcall(require, "system") +-- if ok then +-- -- set the Lua-System random generator as the one to use +-- uuid.get_random_bytes = system.get_random_bytes +-- else +-- -- use the weak one as a fallback +-- uuid.get_random_bytes = uuid.weak_random_bytes +-- end +function M.get_random_bytes(n) + assert(n, "Expected number of bytes to generate") + error("Not implemented, please set a function to generate random bytes") +end + +---------------------------------------------------------------------------- +-- Returns a set of random bytes. This implementation uses the default Lua +-- `math.random()` function, which is not very random. It is recommended to +-- replace this function with a better implementation. +-- @tparam integer n number of bytes to generate +-- @treturn string of random bytes +-- @usage +function M.weak_random_bytes(n) + assert(n, "Expected number of bytes to generate") + local bytes = {} + for i = 1, n do + bytes[i] = char(math_random(0, 255)) + end + return table.concat(bytes) +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 @@ -92,66 +125,41 @@ end -- 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! +-- @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) - } - + local bytes 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) + local hwaddr_clean = hwaddr:gsub("[^%x]",""):sub(1,12):lower() + assert(#hwaddr_clean == 12, "Provided string did not contain at least 12 hex characters, got '"..hwaddr.."'") + + bytes = hwaddr_to_bytes(hwaddr_clean) + else + bytes = M.get_random_bytes(6) end + local byte_7, byte_9 -- set the version - bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND) - bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR) + byte_7 = byte(M.get_random_bytes(1)) + byte_7 = BITWISE(byte_7, 0x0f, MATRIX_AND) + byte_7 = BITWISE(byte_7, 0x40, MATRIX_OR) + byte_7 = char(byte_7) -- set the variant - bytes[9] = BITWISE(bytes[9], 0x3f, MATRIX_AND) - bytes[9] = BITWISE(bytes[9], 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]) + byte_9 = byte(M.get_random_bytes(1)) + byte_9 = BITWISE(byte_9, 0x3f, MATRIX_AND) + byte_9 = BITWISE(byte_9, 0x80, MATRIX_OR) + byte_9 = char(byte_9) + + bytes = M.get_random_bytes(6) .. byte_7 .. M.get_random_bytes(1) .. byte_9 .. M.get_random_bytes(1) .. bytes + + return format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", byte(bytes, 1, 16)) end ---------------------------------------------------------------------------- @@ -205,7 +213,7 @@ end -- print("here's a new uuid: ",uuid()) function M.seed() if _G.ngx ~= nil then - return M.randomseed(ngx.time() + ngx.worker.pid()) + return M.randomseed(ngx.time() + ngx.worker.pid()) -- luacheck: ignore elseif package.loaded["socket"] and package.loaded["socket"].gettime then return M.randomseed(package.loaded["socket"].gettime()*10000) else diff --git a/uuid-dev-1.rockspec b/uuid-dev-1.rockspec index 6ca270f..069a156 100644 --- a/uuid-dev-1.rockspec +++ b/uuid-dev-1.rockspec @@ -8,7 +8,7 @@ local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { - url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "dev") and "master" or nil, tag = (package_version ~= "dev") and package_version or nil, } @@ -24,7 +24,7 @@ description = { } dependencies = { - "lua >= 5.1", + "lua >= 5.1, < 5.5", } build = { @@ -32,4 +32,7 @@ build = { modules = { ["uuid"] = "src/uuid.lua", }, + copy_directories = { + "docs", + }, }