├── .github ├── FUNDING.yml └── workflows │ ├── docs.yml │ ├── linux.yml │ └── macos.yml ├── .gitignore ├── .swift-format ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── UniqueID │ ├── Foundation+UniqueID.swift │ ├── Time.swift │ ├── UniqueID+Components.swift │ ├── UniqueID+Parser.swift │ ├── UniqueID+Serialization.swift │ ├── UniqueID+v4.swift │ ├── UniqueID+v6.swift │ ├── UniqueID.docc │ └── UniqueID.md │ └── UniqueID.swift └── Tests └── UniqueIDTests ├── UUIDv6Tests.swift └── UniqueIDTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [karwa] 4 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate Documentation 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | # 1. Checkout the package (we need a full clone in order to get tags). 13 | # This must be the root folder for 'github-pages-deploy-action' to work. 14 | - name: Checkout Package 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | # 2. Download and Install Swift 5.5.1 20 | - name: Download Swift 5.5.1 21 | run: wget -q https://download.swift.org/swift-5.5.1-release/ubuntu2004/swift-5.5.1-RELEASE/swift-5.5.1-RELEASE-ubuntu20.04.tar.gz 22 | - name: Extract Swift 5.5.1 23 | run: tar xzf swift-5.5.1-RELEASE-ubuntu20.04.tar.gz 24 | - name: Add Swift toolchain to PATH 25 | run: | 26 | echo "$GITHUB_WORKSPACE/swift-5.5.1-RELEASE-ubuntu20.04/usr/bin" >> $GITHUB_PATH 27 | 28 | # 3. Checkout and build Swift-DocC (TODO: Remove once docc is included in the toolchain DL) 29 | - name: Checkout swift-docc 30 | uses: actions/checkout@v2 31 | with: 32 | repository: karwa/swift-docc 33 | ref: main 34 | path: swift-docc 35 | - name: Cache DocC 36 | id: cache-docc 37 | uses: actions/cache@v2 38 | with: 39 | key: uniqueid-docc-build 40 | path: swift-docc/.build 41 | - name: Build swift-docc 42 | if: ${{ !steps.cache-docc.outputs.cache-hit }} 43 | run: | 44 | cd swift-docc; swift build --product docc -c release; cd .. 45 | 46 | # 4. Checkout and build custom Swift-DocC-Render 47 | - name: Checkout swift-docc-render 48 | uses: actions/checkout@v2 49 | with: 50 | repository: karwa/swift-docc-render 51 | ref: main 52 | path: swift-docc-render 53 | - name: Build swift-docc-render 54 | run: | 55 | cd swift-docc-render; npm install && npm run build; cd .. 56 | 57 | # 5. Checkout the gh-pages branch (we want to add to what is already there) 58 | - name: Checkout gh-pages Branch 59 | uses: actions/checkout@v2 60 | with: 61 | ref: gh-pages 62 | path: docs-out 63 | 64 | # 6. Generate docs for main branch, replacing the existing docs for main. 65 | - name: (main) Clear existing docs 66 | run: rm -rf docs-out/.git && rm -rf docs-out/main && mkdir -p docs-out/main 67 | - name: (main) Generate SymbolGraph 68 | run: | 69 | mkdir -p .build/symbol-graphs && swift build --target UniqueID -Xswiftc -emit-symbol-graph -Xswiftc -emit-symbol-graph-dir -Xswiftc .build/symbol-graphs 70 | - name: (main) Build Documentation 71 | run: | 72 | export DOCC_HTML_DIR="$(pwd)/swift-docc-render/dist" && swift-docc/.build/release/docc convert Sources/UniqueID/UniqueID.docc --fallback-display-name UniqueID --fallback-bundle-identifier com.karwa.UniqueID --fallback-bundle-version 0.0.0 --additional-symbol-graph-dir .build/symbol-graphs --transform-for-static-hosting --static-hosting-base-path /uniqueid/main --output-path docs-out/main 73 | 74 | # 7. Find out if documentation has already been generated for the latest tag. 75 | - name: (latest-tag) Find the Latest Tag 76 | run: | 77 | echo "latest-tag=$(git tag -l | tail -1)" >> $GITHUB_ENV 78 | - name: (latest-tag) Check if Documentation Exists 79 | run: | 80 | if [ -d "docs-out/${{ env.latest-tag }}" ]; then echo "latest-tag-has-docs=1" >> "$GITHUB_ENV"; fi 81 | 82 | # 8. If there is no documentation for the latest tag, build it. 83 | - name: (latest-tag) Checkout Package 84 | if: ${{ !env.latest-tag-has-docs }} 85 | uses: actions/checkout@v2 86 | with: 87 | ref: ${{ env.latest-tag }} 88 | path: latest-tag 89 | clean: false 90 | - name: (latest tag) Generate SymbolGraph 91 | if: ${{ !env.latest-tag-has-docs }} 92 | run: | 93 | pushd latest-tag && mkdir -p .build/symbol-graphs && swift build --target UniqueID -Xswiftc -emit-symbol-graph -Xswiftc -emit-symbol-graph-dir -Xswiftc .build/symbol-graphs && popd 94 | - name: (latest tag) Create Output Directory 95 | if: ${{ !env.latest-tag-has-docs }} 96 | run: | 97 | mkdir -p docs-out/${{ env.latest-tag }} 98 | - name: (latest tag) Build Documentation 99 | if: ${{ !env.latest-tag-has-docs }} 100 | run: | 101 | pushd latest-tag && export DOCC_HTML_DIR="$(pwd)/../swift-docc-render/dist" && ../swift-docc/.build/release/docc convert Sources/UniqueID/UniqueID.docc --fallback-display-name UniqueID --fallback-bundle-identifier com.karwa.UniqueID --fallback-bundle-version ${{ env.latest-tag }} --additional-symbol-graph-dir .build/symbol-graphs --transform-for-static-hosting --static-hosting-base-path /uniqueid/${{ env.latest-tag }} --output-path ../docs-out/${{ env.latest-tag }} && popd 102 | - name: (latest tag) Update symlink 103 | if: ${{ !env.latest-tag-has-docs }} 104 | run: | 105 | pushd docs-out && ln -sfn ${{ env.latest-tag }} latest && popd 106 | 107 | # 9. Publish the result 108 | - name: Fix permissions 109 | run: 'sudo chown --recursive $USER docs-out' 110 | - name: Publish documentation to GitHub Pages 111 | uses: JamesIves/github-pages-deploy-action@4.1.7 112 | with: 113 | branch: gh-pages 114 | folder: docs-out 115 | single-commit: true -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Swift package tests (Linux) 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Download Swift 5.5.1 17 | run: wget -q https://download.swift.org/swift-5.5.1-release/ubuntu2004/swift-5.5.1-RELEASE/swift-5.5.1-RELEASE-ubuntu20.04.tar.gz 18 | - name: Extract Swift 5.5.1 19 | run: tar xzf swift-5.5.1-RELEASE-ubuntu20.04.tar.gz 20 | - name: Add Swift toolchain to PATH 21 | run: | 22 | echo "$GITHUB_WORKSPACE/swift-5.5.1-RELEASE-ubuntu20.04/usr/bin" >> $GITHUB_PATH 23 | - name: Build 24 | run: swift build -v 25 | - name: Run tests 26 | run: swift test --enable-test-discovery -v -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: Swift package tests (macOS) 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-11 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.swiftpm 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | .docc-build/ 8 | Package.resolved -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "lineLength" : 120, 3 | "maximumBlankLines" : 2, 4 | 5 | "blankLineBetweenMembers" : { 6 | "ignoreSingleLineProperties" : true 7 | }, 8 | "indentation" : { 9 | "spaces" : 2 10 | }, 11 | "indentConditionalCompilationBlocks" : true, 12 | "lineBreakBeforeControlFlowKeywords" : false, 13 | "lineBreakBeforeEachArgument" : false, 14 | "respectsExistingLineBreaks" : true, 15 | "rules" : { 16 | "AllPublicDeclarationsHaveDocumentation" : true, 17 | "AlwaysUseLowerCamelCase" : true, 18 | "AmbiguousTrailingClosureOverload" : true, 19 | "BeginDocumentationCommentWithOneLineSummary" : true, 20 | "BlankLineBetweenMembers" : true, 21 | "CaseIndentLevelEqualsSwitch" : true, 22 | "DoNotUseSemicolons" : true, 23 | "DontRepeatTypeInStaticProperties" : true, 24 | "FullyIndirectEnum" : true, 25 | "GroupNumericLiterals" : true, 26 | "IdentifiersMustBeASCII" : true, 27 | "MultiLineTrailingCommas" : true, 28 | "NeverForceUnwrap" : true, 29 | "NeverUseForceTry" : true, 30 | "NeverUseImplicitlyUnwrappedOptionals" : true, 31 | "NoAccessLevelOnExtensionDeclaration" : true, 32 | "NoBlockComments" : true, 33 | "NoCasesWithOnlyFallthrough" : true, 34 | "NoEmptyTrailingClosureParentheses" : true, 35 | "NoLabelsInCasePatterns" : true, 36 | "NoLeadingUnderscores" : true, 37 | "NoParensAroundConditions" : true, 38 | "NoVoidReturnOnFunctionSignature" : true, 39 | "OneCasePerLine" : true, 40 | "OneVariableDeclarationPerLine" : true, 41 | "OnlyOneTrailingClosureArgument" : true, 42 | "OrderedImports" : true, 43 | "ReturnVoidInsteadOfEmptyTuple" : true, 44 | "UseEnumForNamespacing" : true, 45 | "UseLetInEveryBoundCaseVariable" : true, 46 | "UseShorthandTypeNames" : false, 47 | "UseSingleLinePropertyGetter" : true, 48 | "UseSynthesizedInitializer" : true, 49 | "UseTripleSlashForDocumentationComments" : true, 50 | "ValidateDocumentationComments" : true 51 | }, 52 | "tabWidth" : 8, 53 | "version" : 1 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | // Copyright The swift-UniqueID Contributors. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | import PackageDescription 18 | 19 | // This package recognizes the conditional compilation flags listed below. 20 | // To use enable them, uncomment the corresponding lines or define them 21 | // from the package manager command line: 22 | // 23 | // swift build -Xswiftc -DNO_FOUNDATION_COMPAT 24 | var settings: [SwiftSetting]? = [ 25 | 26 | // Do not depend on Foundation. 27 | // 28 | // This removes: 29 | // - UUID <-> UniqueID conversion APIs. 30 | // - UUID timestamps as 'Date' (but Date will soon join the standard library). 31 | //.define("NO_FOUNDATION_COMPAT"), 32 | 33 | ] 34 | 35 | if settings?.isEmpty == true { settings = nil } 36 | 37 | let package = Package( 38 | name: "UniqueID", 39 | platforms: [.macOS(.v10_12), .iOS(.v10), .tvOS(.v10), .watchOS(.v3) /* for os_unfair_lock */], 40 | products: [ 41 | .library( 42 | name: "UniqueID", 43 | targets: ["UniqueID"]), 44 | ], 45 | targets: [ 46 | .target( 47 | name: "UniqueID", 48 | swiftSettings: settings), 49 | .testTarget( 50 | name: "UniqueIDTests", 51 | dependencies: ["UniqueID"], 52 | swiftSettings: settings), 53 | ] 54 | ) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UniqueID 2 | 3 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fkarwa%2Funiqueid%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/karwa/uniqueid) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fkarwa%2Funiqueid%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/karwa/uniqueid) 4 | 5 | UUIDv4 and v6\* generation in Swift. 6 | 7 | [[Documentation](https://karwa.github.io/uniqueid/main/documentation/uniqueid)] 8 | 9 | A UUID is an identifier that is unique across both space and time, with respect to the space of all UUIDs. They are used for multiple purposes, from tagging objects with an extremely short lifetime, to reliably identifying very persistent objects across a network. 10 | 11 | `UniqueID` supports any 128-bit UUID, and is fully compatible with Foundation's `UUID`. 12 | It also includes features to generate 2 kinds of ID: 13 | 14 | - **Random**: As defined in [RFC-4122][RFC-4122-UUIDv4] (UUIDv4). 15 | 16 | A 128-bit identifier, consisting of 122 random bits. 17 | These are the most common form of UUIDs; for example, they are the ones Foundation's `UUID` type creates 18 | by default. To generate a random UUID, call the static `random()` function. 19 | 20 | ```swift 21 | for _ in 0..<3 { 22 | print(UniqueID.random()) 23 | } 24 | 25 | "DFFC75B4-C92F-4DA9-97CA-7F0EEF067FF2" 26 | "67E5F28C-5083-4908-BD69-D7E27C8BABA4" 27 | "3BA8EEF0-DFBE-4AE0-A646-E165FCA9054C" 28 | ``` 29 | 30 | - **Time-Ordered**: Generated according to a [draft update of RFC-4122][UUIDv6-draft-02] (UUIDv6). 31 | 32 | A 128-bit identifier, consisting of a 60-bit timestamp with 100ns precision, a 14-bit sequencing number seeded 33 | from random bits, and a 48-bit node ID (which may also be random bits). To generate a time-ordered UUID, 34 | call the static `timeOrdered()` function. 35 | 36 | ```swift 37 | for _ in 0..<3 { 38 | print(UniqueID.timeOrdered()) 39 | } 40 | 41 | "1EC3C81E-A361-658C-BB38-65AAEF71CFCF" 42 | "1EC3C81E-A361-6F6E-BB38-6DE69B9BCA1B" 43 | "1EC3C81E-A362-698C-BB38-050642A95C73" 44 | |------------- --| |--| |----------| 45 | timestamp sq node 46 | ``` 47 | 48 | As you can see, time-ordered UUIDs generated in sequence share a common prefix (from the timestamp), yet 49 | retain high collision avoidance. This allows the use of sorted data structures and algorithms 50 | such as binary search, as an alternative to hash tables. They are far more efficient than random UUIDs for use 51 | as database primary keys. 52 | 53 | > Tip: 54 | > Random and Time-Ordered UUIDs may coexist in the same database. 55 | > They have different version numbers, so they are guaranteed to never collide. 56 | 57 | [RFC-4122-UUIDv4]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4 58 | [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 59 | 60 | # Using UniqueID in your project 61 | 62 | To use this package in a SwiftPM project, you need to set it up as a package dependency: 63 | 64 | ```swift 65 | // swift-tools-version:5.5 66 | import PackageDescription 67 | 68 | let package = Package( 69 | name: "MyPackage", 70 | dependencies: [ 71 | .package( 72 | url: "https://github.com/karwa/uniqueid", 73 | .upToNextMajor(from: "1.0.0") 74 | ) 75 | ], 76 | targets: [ 77 | .target( 78 | name: "MyTarget", 79 | dependencies: [ 80 | .product(name: "UniqueID", package: "uniqueid") 81 | ] 82 | ) 83 | ] 84 | ) 85 | ``` 86 | 87 | And with that, you're ready to start using `UniqueID`. One way to get easily experiment with time-ordered (v6) UUIDs is to use Foundation compatibility to simply change how you create UUIDs: 88 | 89 | ```swift 90 | import Foundation 91 | import UniqueID 92 | 93 | // Change from UUID() to UUID(.timeOrdered()). 94 | struct MyRecord { 95 | var id: UUID = UUID(.timeOrdered()) 96 | var name: String 97 | } 98 | 99 | // Read the timestamp by converting to UniqueID. 100 | let uniqueID = UniqueID(myRecord.id) 101 | print(uniqueID.components(.timeOrdered)?.timestamp) 102 | ``` 103 | 104 | Bear in mind that v6 UUIDs are not yet an official standard, and the layout may change before it becomes an approved internet standard. This implementation aligns with draft 02, from 7 October 2021. Check the latest status [here](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02). 105 | 106 | 107 | ## Why new UUIDs? 108 | 109 | The IETF draft has a really good summary of why using time-ordered UUIDs can be beneficial. You should read it - at least [the "Background" section](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02#section-2). 110 | 111 | > A lot of things have changed in the time since UUIDs were originally 112 | > created. Modern applications have a need to use (and many have 113 | > already implemented) UUIDs as database primary keys. 114 | > 115 | > The motivation for using UUIDs as database keys stems primarily from 116 | > the fact that applications are increasingly distributed in nature. 117 | > Simplistic "auto increment" schemes with integers in sequence do not 118 | > work well in a distributed system since the effort required to 119 | > synchronize such numbers across a network can easily become a burden. 120 | > The fact that UUIDs can be used to create unique and reasonably short 121 | > values in distributed systems without requiring synchronization makes 122 | > them a good candidate for use as a database key in such environments. 123 | > 124 | > However some properties of RFC4122 UUIDs are not well suited to 125 | > this task. First, most of the existing UUID versions such as UUIDv4 126 | > have poor database index locality. Meaning new values created in 127 | > succession are not close to each other in the index and thus require 128 | > inserts to be performed at random locations. The negative 129 | > performance effects of which on common structures used for this 130 | > (B-tree and its variants) can be dramatic. As such newly inserted 131 | > values SHOULD be time-ordered to address this. 132 | 133 | Previous time-ordered UUIDs, such as version 1 UUIDs from RFC-4122, store their timestamps in a convoluted format, so you can't just sort UUIDs based on their bytes and arrive at a time-sorted list of UUIDs. Version 6 improves on that. 134 | 135 | Let's compare 10 UUIDv4s against 10 UUIDv6s: 136 | 137 | ``` 138 | for _ in 0..<10 { 139 | print(UniqueID.random()) 140 | } 141 | 142 | DFFC75B4-C92F-4DA9-97CA-7F0EEF067FF2 143 | 67E5F28C-5083-4908-BD69-D7E27C8BABA4 144 | 3BA8EEF0-DFBE-4AE0-A646-E165FCA9054C 145 | DF92B4B0-F5EE-42E5-9577-A9FC373C71A4 146 | A2F8DD26-D513-4AE6-9E5C-58363885CCB6 147 | BB0B5841-2BC0-49E2-BC5C-362CC34D7225 148 | B08AF1F7-E2D3-4175-913D-369140612FF5 149 | A453FB62-DF71-436F-9AC1-0414793DFA16 150 | 485EEB84-A4BA-44FE-BE3B-AD90390B0523 151 | 8A9AE1FA-4104-442C-B459-8F682E77F2F4 152 | ``` 153 | 154 | ``` 155 | for _ in 0..<10 { 156 | print(UniqueID.timeOrdered()) 157 | } 158 | 159 | 1EC3C81E-A35C-69E2-BB38-EDDC5E7E5F5E 160 | 1EC3C81E-A361-658C-BB38-65AAEF71CFCF 161 | 1EC3C81E-A361-6F6E-BB38-6DE69B9BCA1B 162 | 1EC3C81E-A362-698C-BB38-050642A95C73 163 | 1EC3C81E-A363-6152-BB38-F105ED78927F 164 | 1EC3C81E-A363-6A94-BB38-4DAB2CAE46CD 165 | 1EC3C81E-A364-63D6-BB38-6114031916EF 166 | 1EC3C81E-A364-6D04-BB38-435A854C2E42 167 | 1EC3C81E-A365-66AA-BB38-03504FA2F6FE 168 | 1EC3C81E-A365-6F74-BB38-1F5AE9E10389 169 | ``` 170 | 171 | Both lists are unique, and unique with respect to each other, but the time-ordered ones, naturally, came out in order of creation time. We can even extract the embedded timestamp - in this case, it says the UUID was created on the 3rd of November, 2021 at 08:42:01 UTC (down to 100ns precision, theoretically). 172 | 173 | The combination of temporal and spacial components means these UUIDs are still robust to collisions - a new 60-bit universe exists every 100ns, and the IDs within that universe are still alloted based on random bits with high entropy. It's tempting to think you might be paying a high cost in collisions for the ease of use, but it's not as simple as that. -------------------------------------------------------------------------------- /Sources/UniqueID/Foundation+UniqueID.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #if !NO_FOUNDATION_COMPAT 16 | 17 | import Foundation 18 | 19 | extension UUID { 20 | 21 | /// Losslessly convert a `UniqueID` to a Foundation `UUID`. 22 | /// 23 | /// The bytes of the UniqueID are preserved exactly. 24 | /// Both random (v4) and time-ordered (v6) IDs are supported. 25 | /// 26 | @inlinable 27 | public init(_ uniqueID: UniqueID) { 28 | self.init(uuid: uniqueID.bytes) 29 | } 30 | } 31 | 32 | extension UniqueID { 33 | 34 | /// Losslessly convert a Foundation `UUID` to a `UniqueID`. 35 | /// 36 | /// The bytes of the Foundation UUID are preserved exactly. 37 | /// By default, Foundation generates random UUIDs (v4). 38 | /// 39 | @inlinable 40 | public init(_ uuid: Foundation.UUID) { 41 | self.init(bytes: uuid.uuid) 42 | } 43 | } 44 | 45 | // Note: 'Date' might move in to the standard library and increase precision to capture this timestamp exactly. 46 | // https://forums.swift.org/t/pitch-clock-instant-date-and-duration/52451 47 | 48 | extension UniqueID.TimeOrdered { 49 | 50 | /// The timestamp of the UUID. Note that this has at most 100ns precision. 51 | /// 52 | /// ```swift 53 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 54 | /// id.components(.timeOrdered)?.timestamp 55 | /// // ✅ "2021-12-18 09:24:31 +0000" 56 | /// ``` 57 | /// 58 | @inlinable 59 | public var timestamp: Date { 60 | Date(timeIntervalSince1970: TimeInterval(_uuid_timestamp_to_unix(timestamp: rawTimestamp)) / 10_000_000) 61 | } 62 | } 63 | 64 | #endif // NO_FOUNDATION_COMPAT 65 | -------------------------------------------------------------------------------- /Sources/UniqueID/Time.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #if canImport(Darwin) 16 | import Darwin 17 | #elseif canImport(Glibc) 18 | import Glibc 19 | #else 20 | #error("Unsupported platform") 21 | #endif 22 | 23 | /// Returns the number of 100ns intervals from the Unix epoch to the current instant. 24 | /// The value is limited to the least-significant 60 bits. 25 | /// 26 | @inlinable 27 | internal func _get_system_timestamp() -> UInt64 { 28 | let timestamp: UInt64 29 | if #available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) { 30 | var time = timespec() 31 | clock_gettime(CLOCK_REALTIME, &time) 32 | timestamp = 33 | (UInt64(bitPattern: Int64(time.tv_sec)) &* 10_000_000) &+ (UInt64(bitPattern: Int64(time.tv_nsec)) / 100) 34 | } else { 35 | var time = timeval() 36 | gettimeofday(&time, nil) 37 | timestamp = 38 | (UInt64(bitPattern: Int64(time.tv_sec)) &* 10_000_000) &+ (UInt64(bitPattern: Int64(time.tv_usec)) &* 10) 39 | } 40 | return timestamp & 0x0FFF_FFFF_FFFF_FFFF 41 | } 42 | 43 | /// Converts a 60-bit number of 100ns intervals from the Unix epoch (January 1, 1970) 44 | /// to the UUID epoch (October 15, 1582). 45 | /// 46 | @inlinable 47 | internal func _unix_to_uuid_timestamp(unix: UInt64) -> UInt64 { 48 | unix &+ 0x01B2_1DD2_1381_4000 49 | } 50 | 51 | /// Converts a 60-bit number of 100ns intervals from the UUID epoch (October 15, 1582) 52 | /// to the Unix epoch (January 1, 1970). 53 | /// 54 | @inlinable 55 | internal func _uuid_timestamp_to_unix(timestamp: UInt64) -> UInt64 { 56 | timestamp &- 0x01B2_1DD2_1381_4000 57 | } 58 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID+Components.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// A type which exposes a view of the embedded information within certain UUIDs. 16 | /// 17 | public protocol _UniqueIDComponents { 18 | init?(_ uuid: UniqueID) 19 | } 20 | 21 | // Note: Why '.components()' uses an @autoclosure: 22 | // 23 | // So, we want UniqueID.Components to be a protocol, and the user provides the type 24 | // and we construct an instance. Normally you'd write that like this: 25 | // 26 | // func components(_: ViewType.Type) -> ViewType? 27 | // 28 | // And the user would write: 29 | // 30 | // uuid.components(TimeOrderedComponents.self)?.timestamp 31 | // 32 | // But that kind of sucks. We'd like to take advantage of static-member syntax: 33 | // 34 | // uuid.components(.timeOrdered)?.timestamp 35 | // 36 | // Unfortunately, the regular way of expressing this doesn't work: 37 | // 38 | // extension UUID.Components where Self == TimeOrderedComponents { 39 | // public static var timeOrdered: Self { ... } 40 | // } 41 | // 42 | // We would need to provide a dummy instance. And changing the type of the computed property `timeOrdered` 43 | // to `Self.Type` or `TimeOrderedComponents.Type` doesn't work - the compiler doesn't like it. 44 | // Hence, the workaround: use an @autoclosure parameter, which to the type-checker looks like it returns 45 | // an instance (but really just fatalErrors). We don't need to create a dummy instance and 46 | // we get static member syntax: 47 | // 48 | // func components(_: @autoclosure () -> ViewType) -> ViewType? { ... } 49 | // 50 | // extension UniqueID.Components where Self == TimeOrderedComponents { 51 | // public static var timeOrdered: Self { fatalError("Not intended to be called") } 52 | // } 53 | // 54 | // components(.timeOrdered)?.timestamp // works. 55 | 56 | extension UniqueID { 57 | 58 | /// A view of the embedded information within certain UUIDs. 59 | /// 60 | public typealias Components = _UniqueIDComponents 61 | 62 | /// Returns a view of the embedded information within this UUID. 63 | /// 64 | /// The following example demonstrates extracting the timestamp from a time-ordered UUID. 65 | /// 66 | /// ```swift 67 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 68 | /// id.components(.timeOrdered)?.timestamp 69 | /// // ✅ "2021-12-18 09:24:31 +0000" 70 | /// ``` 71 | /// 72 | @inlinable 73 | public func components(_: @autoclosure () -> ViewType) -> ViewType? { 74 | ViewType(self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID+Parser.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extension UniqueID { 16 | 17 | /// Parses a UUID from its string representation. 18 | /// 19 | /// This parser accepts a string of 32 ASCII hex characters. 20 | /// It is quite lenient, accepting any number of dashes (`"-"`) to break the string up in to chunks of 21 | /// evenly-sized units. Additionally, the UUID may be surrounded by curly braces ("Microsoft style"). 22 | /// 23 | /// For example, all of the following parse successfully: 24 | /// 25 | /// - `1EC3A5FB-6FE9-64D8-8004-C750087BF2DB` (standard) 26 | /// - `{1ec3a5fb-6fe9-64d8-8004-c750087bf2db}` (curly braces) 27 | /// - `1ec3A5fB-6fE9-64d8-8004-c750087Bf2Db` (mixed case) 28 | /// - `1ec3a5fb6fe964d88004c750087bf2db` (no dashes) 29 | /// - `1E-C3-A5-FB-6F-E9-64-D8-80-04-C7-50-08-7B-F2-DB` (lots of dashes) 30 | /// 31 | @inlinable 32 | public init?( 33 | _ string: StringType 34 | ) where StringType: StringProtocol, StringType.UTF8View: BidirectionalCollection { 35 | let parsed = string.utf8.withContiguousStorageIfAvailable { UniqueID(utf8: $0) } ?? UniqueID(utf8: string.utf8) 36 | guard let parsed = parsed else { 37 | return nil 38 | } 39 | self = parsed 40 | } 41 | 42 | /// Parses a UUID from its string representation, provided as a collection of UTF-8 code-units. 43 | /// 44 | /// This parser accepts a string of 32 ASCII hex characters. 45 | /// It is quite lenient, accepting any number of dashes (`"-"`) to break the string up in to chunks of 46 | /// evenly-sized units. Additionally, the UUID may be surrounded by curly braces ("Microsoft style"). 47 | /// For example, all of the following parse successfully: 48 | /// 49 | /// - `1EC3A5FB-6FE9-64D8-8004-C750087BF2DB` (standard) 50 | /// - `{1ec3a5fb-6fe9-64d8-8004-c750087bf2db}` (curly braces) 51 | /// - `1ec3A5fB-6fE9-64d8-8004-c750087Bf2Db` (mixed case) 52 | /// - `1ec3a5fb6fe964d88004c750087bf2db` (no dashes) 53 | /// - `1E-C3-A5-FB-6F-E9-64-D8-80-04-C7-50-08-7B-F2-DB` (lots of dashes) 54 | /// 55 | /// > Note: 56 | /// > This is not the same as constructing a UUID from its raw bytes. 57 | /// > The bytes provided to this function must contain a formatted UUID string. 58 | /// 59 | @inlinable @inline(never) 60 | public init?( 61 | utf8: UTF8Bytes 62 | ) where UTF8Bytes: BidirectionalCollection, UTF8Bytes.Element == UInt8 { 63 | 64 | var utf8 = utf8[...] 65 | // Trim curly braces. 66 | if utf8.first == 0x7B /* "{" */ { 67 | guard utf8.last == 0x7D /* "}" */ else { 68 | return nil 69 | } 70 | utf8 = utf8.dropFirst().dropLast() 71 | } 72 | // Parse the bytes. 73 | var uuid = UniqueID.null.bytes 74 | let success = withUnsafeMutableBytes(of: &uuid) { uuidBytes -> Bool in 75 | var i = utf8.startIndex 76 | for storagePosition in 0..<16 { 77 | while i < utf8.endIndex, utf8[i] == 0x2D /* "-" */ { 78 | utf8.formIndex(after: &i) 79 | } 80 | guard let parsedByte = utf8.parseByte(at: &i) else { 81 | return false 82 | } 83 | uuidBytes[storagePosition] = parsedByte 84 | } 85 | return i == utf8.endIndex 86 | } 87 | guard success else { return nil } 88 | self = UniqueID(bytes: uuid) 89 | } 90 | } 91 | 92 | @usableFromInline internal let DC: Int8 = -1 93 | // swift-format-ignore 94 | @usableFromInline internal let _parseHex_table: [Int8] = [ 95 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 48 invalid chars. 96 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 97 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 98 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 99 | DC, DC, DC, DC, DC, DC, DC, DC, 100 | 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, // numbers 0-9 101 | DC, DC, DC, DC, DC, DC, DC, // 7 invalid chars from ':' to '@' 102 | 10, 11, 12, 13, 14, 15, // uppercase A-F 103 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 20 invalid chars G-Z 104 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 105 | DC, DC, DC, DC, DC, DC, // 6 invalid chars from '[' to '`' 106 | 10, 11, 12, 13, 14, 15, // lowercase a-f 107 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 20 invalid chars g-z 108 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 109 | DC, DC, DC, DC, DC, // 5 invalid chars from '{' to '(delete)' 110 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 128 non-ASCII chars. 111 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 112 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 113 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 114 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 115 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 116 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 117 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 118 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 119 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 120 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 121 | DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, 122 | DC, DC, DC, DC, DC, DC, DC, 123 | ] 124 | 125 | /// Returns the numeric value of the hex digit `ascii`, if it is a hex digit (0-9, A-F, a-f). 126 | /// 127 | @inlinable 128 | internal func asciiToHex(_ ascii: UInt8) -> UInt8? { 129 | let numericValue = _parseHex_table.withUnsafeBufferPointer { $0[Int(ascii)] } 130 | return numericValue < 0 ? nil : UInt8(bitPattern: numericValue) 131 | } 132 | 133 | extension Collection where Element == UInt8 { 134 | 135 | @inlinable 136 | internal func parseByte(at i: inout Index) -> UInt8? { 137 | guard i < endIndex, let firstNibble = asciiToHex(self[i]) else { return nil } 138 | formIndex(after: &i) 139 | guard i < endIndex, let secondNibble = asciiToHex(self[i]) else { return nil } 140 | formIndex(after: &i) 141 | return (firstNibble &<< 4) | secondNibble 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID+Serialization.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extension UniqueID { 16 | 17 | /// Returns a `String` representation of this UUID. 18 | /// 19 | /// By default, this function returns a standard 8-4-4-4-12 UUID string in uppercase. 20 | /// 21 | /// ```swift 22 | /// let id = UniqueID.random() 23 | /// 24 | /// print(id.serialized()) // "67E5F28C-5083-4908-BD69-D7E27C8BABA4" 25 | /// print(id) // Same as above. 26 | /// 27 | /// print(id.serialized(lowercase: true)) 28 | /// // "67e5f28c-5083-4908-bd69-d7e27c8baba4" 29 | /// 30 | /// print(id.serialized(separators: false)) 31 | /// // "67E5F28C50834908BD69D7E27C8BABA4" 32 | /// 33 | /// print(id.serialized(lowercase: true, separators: false)) 34 | /// // "67e5f28c50834908bd69d7E27c8baba4" 35 | /// ``` 36 | /// 37 | /// - parameters: 38 | /// - lowercase: Whether to use lowercase hexadecimal characters in the result. 39 | /// If `false`, the result will be uppercased. The default is `false`. 40 | /// - separators: Whether the result should be in the standard 8-4-4-4-12 format, with `"-"` separators 41 | /// between groups. The default is `true`. 42 | /// 43 | public func serialized( 44 | lowercase: Bool = false, separators: Bool = true 45 | ) -> String { 46 | let length = 32 + (separators ? 4 : 0) 47 | if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { 48 | return String(unsafeUninitializedCapacity: length) { buffer in 49 | serialize(into: buffer, lowercase: lowercase, separators: separators) 50 | } 51 | } else { 52 | let utf8 = Array(unsafeUninitializedCapacity: length) { buffer, count in 53 | count = serialize(into: buffer, lowercase: lowercase, separators: separators) 54 | } 55 | return String(decoding: utf8, as: UTF8.self) 56 | } 57 | } 58 | 59 | internal func serialize( 60 | into buffer: UnsafeMutableBufferPointer, 61 | lowercase: Bool, separators: Bool 62 | ) -> Int { 63 | // format = 8-4-4-4-12 64 | withUnsafeBytes { octets in 65 | var i = 0 66 | // 8: 67 | for octetPosition in 0..<4 { 68 | i = buffer.writeHex(octets[octetPosition], at: i, lowercase: lowercase) 69 | } 70 | if separators { 71 | i = buffer.writeDash(at: i) 72 | } 73 | // 4: 74 | for octetPosition in 4..<6 { 75 | i = buffer.writeHex(octets[octetPosition], at: i, lowercase: lowercase) 76 | } 77 | if separators { 78 | i = buffer.writeDash(at: i) 79 | } 80 | // 4: 81 | for octetPosition in 6..<8 { 82 | i = buffer.writeHex(octets[octetPosition], at: i, lowercase: lowercase) 83 | } 84 | if separators { 85 | i = buffer.writeDash(at: i) 86 | } 87 | // 4: 88 | for octetPosition in 8..<10 { 89 | i = buffer.writeHex(octets[octetPosition], at: i, lowercase: lowercase) 90 | } 91 | if separators { 92 | i = buffer.writeDash(at: i) 93 | } 94 | // 12: 95 | for octetPosition in 10..<16 { 96 | i = buffer.writeHex(octets[octetPosition], at: i, lowercase: lowercase) 97 | } 98 | return i 99 | } 100 | } 101 | } 102 | 103 | extension UnsafeMutableBufferPointer where Element == UInt8 { 104 | 105 | internal func writeHex_uppercase(_ value: UInt8, at i: Index) -> Index { 106 | let table: StaticString = "0123456789ABCDEF" 107 | table.withUTF8Buffer { table in 108 | self[i] = table[Int(value &>> 4)] 109 | self[i &+ 1] = table[Int(value & 0xF)] 110 | } 111 | return i &+ 2 112 | } 113 | 114 | internal func writeHex_lowercase(_ value: UInt8, at i: Index) -> Index { 115 | let table: StaticString = "0123456789abcdef" 116 | table.withUTF8Buffer { table in 117 | self[i] = table[Int(value &>> 4)] 118 | self[i &+ 1] = table[Int(value & 0xF)] 119 | } 120 | return i &+ 2 121 | } 122 | 123 | internal func writeHex(_ value: UInt8, at i: Index, lowercase: Bool) -> Index { 124 | lowercase ? writeHex_lowercase(value, at: i) : writeHex_uppercase(value, at: i) 125 | } 126 | 127 | internal func writeDash(at i: Index) -> Index { 128 | self[i] = 0x2D 129 | return i &+ 1 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID+v4.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extension UniqueID { 16 | 17 | /// Generates a new UUID with random bits from the system's random number generator. 18 | /// 19 | /// This function generates version 4 UUIDs, as defined by [RFC-4122][RFC-4122-UUIDv4]. 20 | /// They are 128-bit identifiers, consisting of 122 random or pseudo-random bits, and are the most common form of UUIDs; 21 | /// for example, they are the ones Foundation's `UUID` type creates by default. 22 | /// 23 | /// ```swift 24 | /// for _ in 0..<5 { 25 | /// print(UniqueID.random()) 26 | /// } 27 | /// 28 | /// "BB0B5841-2BC0-49E2-BC5C-362CC34D7225" 29 | /// "B08AF1F7-E2D3-4175-913D-369140612FF5" 30 | /// "A453FB62-DF71-436F-9AC1-0414793DFA16" 31 | /// "485EEB84-A4BA-44FE-BE3B-AD90390B0523" 32 | /// "8A9AE1FA-4104-442C-B459-8F682E77F2F4" 33 | /// ``` 34 | /// 35 | /// > Note: 36 | /// > The uniqueness of these IDs depends on the quality of the system's random number generator. 37 | /// > See [`SystemRandomNumberGenerator`](https://developer.apple.com/documentation/swift/systemrandomnumbergenerator) 38 | /// > for more information. 39 | /// 40 | /// [RFC-4122-UUIDv4]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4 41 | /// 42 | @inlinable 43 | public static func random() -> UniqueID { 44 | var rng = SystemRandomNumberGenerator() 45 | return random(using: &rng) 46 | } 47 | 48 | /// Generates a new UUID with random bits from the given random number generator. 49 | /// 50 | /// This function generates version 4 UUIDs, as defined by [RFC-4122][RFC-4122-UUIDv4]. 51 | /// They are 128-bit identifiers, consisting of 122 random or pseudo-random bits, and are the most common form of UUIDs; 52 | /// for example, they are the ones Foundation's `UUID` type creates by default. 53 | /// 54 | /// ```swift 55 | /// var rng = MyRandomNumberGenerator() 56 | /// for _ in 0..<5 { 57 | /// print(UniqueID.random(using: &rng)) 58 | /// } 59 | /// 60 | /// "BB0B5841-2BC0-49E2-BC5C-362CC34D7225" 61 | /// "B08AF1F7-E2D3-4175-913D-369140612FF5" 62 | /// "A453FB62-DF71-436F-9AC1-0414793DFA16" 63 | /// "485EEB84-A4BA-44FE-BE3B-AD90390B0523" 64 | /// "8A9AE1FA-4104-442C-B459-8F682E77F2F4" 65 | /// ``` 66 | /// 67 | /// > Note: 68 | /// > The uniqueness of these IDs depends on the properties of the given random number generator. 69 | /// > A poor-quality generator may result in more collisions, and a seedable generator can be used in testing 70 | /// > to generate repeatable IDs. 71 | /// 72 | /// [RFC-4122-UUIDv4]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4 73 | /// 74 | @inlinable 75 | public static func random(using rng: inout RNG) -> UniqueID where RNG: RandomNumberGenerator { 76 | var bytes = UniqueID.null.bytes 77 | withUnsafeMutableBytes(of: &bytes) { dest in 78 | var random = rng.next() 79 | Swift.withUnsafePointer(to: &random) { 80 | dest.baseAddress!.copyMemory(from: UnsafeRawPointer($0), byteCount: 8) 81 | } 82 | random = rng.next() 83 | Swift.withUnsafePointer(to: &random) { 84 | dest.baseAddress!.advanced(by: 8).copyMemory(from: UnsafeRawPointer($0), byteCount: 8) 85 | } 86 | } 87 | // octet 6 = time_hi_and_version (high octet). 88 | // high 4 bits = version number. 89 | bytes.6 = (bytes.6 & 0xF) | 0x40 90 | // octet 8 = clock_seq_high_and_reserved. 91 | // high 2 bits = variant (10 = standard). 92 | bytes.8 = (bytes.8 & 0x3F) | 0x80 93 | return UniqueID(bytes: bytes) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID+v6.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | // ------------------------------------- 17 | // MARK: - Creation 18 | // ------------------------------------- 19 | 20 | 21 | @usableFromInline 22 | internal struct UUIDv6GeneratorState { 23 | 24 | @usableFromInline 25 | internal var timestamp: UInt64 26 | 27 | @usableFromInline 28 | internal var sequence: UInt16 29 | 30 | init() { 31 | self.timestamp = 0 32 | // Seed the value, so sequence numbers start from a random spot. 33 | // Adds another little bit of spatial uniqueness, while preserving locality. 34 | var rng = SystemRandomNumberGenerator() 35 | self.sequence = rng.next() & 0x3FFF 36 | } 37 | } 38 | 39 | @usableFromInline 40 | internal var _uuidv6GeneratorState = UUIDv6GeneratorState() 41 | 42 | #if canImport(Darwin) 43 | 44 | import Darwin 45 | 46 | @usableFromInline 47 | internal var _uuidv6GeneratorStateLock: UnsafeMutablePointer = { 48 | let lock = UnsafeMutablePointer.allocate(capacity: 1) 49 | lock.initialize(to: os_unfair_lock()) 50 | return lock 51 | }() 52 | 53 | @inlinable 54 | internal func withExclusiveGeneratorState(_ body: (inout UUIDv6GeneratorState) -> T) -> T { 55 | os_unfair_lock_lock(_uuidv6GeneratorStateLock) 56 | let returnValue = body(&_uuidv6GeneratorState) 57 | os_unfair_lock_unlock(_uuidv6GeneratorStateLock) 58 | return returnValue 59 | } 60 | 61 | #elseif canImport(Glibc) 62 | 63 | import Glibc 64 | 65 | @usableFromInline 66 | internal var _uuidv6GeneratorStateLock: UnsafeMutablePointer = { 67 | let mutex = UnsafeMutablePointer.allocate(capacity: 1) 68 | mutex.initialize(to: pthread_mutex_t()) 69 | 70 | var attrs = pthread_mutexattr_t() 71 | guard pthread_mutexattr_init(&attrs) == 0 else { fatalError("Failed to create pthread_mutexattr_t") } 72 | // Use adaptive spinning before calling in to the kernel (GNU extension). 73 | let _ = pthread_mutexattr_settype(&attrs, CInt(PTHREAD_MUTEX_ADAPTIVE_NP)) 74 | guard pthread_mutex_init(mutex, &attrs) == 0 else { fatalError("Failed to create pthread_mutex_t") } 75 | pthread_mutexattr_destroy(&attrs) 76 | 77 | return mutex 78 | }() 79 | 80 | @inlinable 81 | internal func withExclusiveGeneratorState(_ body: (inout UUIDv6GeneratorState) -> T) -> T { 82 | pthread_mutex_lock(_uuidv6GeneratorStateLock) 83 | let returnValue = body(&_uuidv6GeneratorState) 84 | pthread_mutex_unlock(_uuidv6GeneratorStateLock) 85 | return returnValue 86 | } 87 | 88 | #else 89 | 90 | #error("Unsupported platform") 91 | 92 | #endif 93 | 94 | extension UniqueID { 95 | 96 | /// Generates a new UUID sortable by creation time, using the system's random number generator. 97 | /// 98 | /// This function generates version 6 UUIDs, which are currently in [draft form][UUIDv6-draft-02]. 99 | /// They ensure uniqueness in time and space using 3 components: 100 | /// 101 | /// 1. A 60-bit embedded timestamp, taken from the system clock with 100ns resolution. 102 | /// 2. A 14-bit sequence number, which is a synchronized counter ensuring uniqueness even if the clock is adjusted. 103 | /// 3. A 48-bit node identifier, which distinguishes UUIDs in space. 104 | /// Typically, these are random bits, but an application-specific identifier may also be used. 105 | /// 106 | /// ```swift 107 | /// for _ in 0..<5 { 108 | /// print(UniqueID.timeOrdered()) 109 | /// } 110 | /// 111 | /// "1EC5FEC5-F35E-6C08-B6CE-27381D0BBB75" 112 | /// "1EC5FEC5-F360-659E-B6CE-6F4CD8641BCC" 113 | /// "1EC5FEC5-F360-69EA-B6CE-212F47E81DBA" 114 | /// "1EC5FEC5-F360-6DA0-B6CE-13C650E0F431" 115 | /// "1EC5FEC5-F361-61A6-B6CE-3FD9EDD8DB87" 116 | /// |------------- --| |--| |----------| 117 | /// timestamp sq node 118 | /// ``` 119 | /// 120 | /// Version 6 UUIDs are similar to version 1 UUIDs from RFC-4122, except that their timestamps are stored 121 | /// in a sorting- and filtering-friendly manner, and their better locality improves the performance of 122 | /// many databases and data structures. They are highly resistant to collisions, and can incorporate 123 | /// externally-derived identifiers for even greater guarantees in distributed systems or applications. 124 | /// 125 | /// > Note: 126 | /// > The uniqueness of these IDs depends on the quality of the system clock, and the system's random number generator. 127 | /// > See [`SystemRandomNumberGenerator`](https://developer.apple.com/documentation/swift/systemrandomnumbergenerator) 128 | /// > for more information. 129 | /// 130 | /// [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 131 | /// 132 | @inlinable 133 | public static func timeOrdered() -> UniqueID { 134 | var rng = SystemRandomNumberGenerator() 135 | return timeOrdered(using: &rng) 136 | } 137 | 138 | /// Generates a new UUID sortable by creation time, using the given random number generator. 139 | /// 140 | /// This function generates version 6 UUIDs, which are currently in [draft form][UUIDv6-draft-02]. 141 | /// They ensure uniqueness in time and space using 3 components: 142 | /// 143 | /// 1. A 60-bit embedded timestamp, taken from the system clock with 100ns resolution. 144 | /// 2. A 14-bit sequence number, which is a synchronized counter ensuring uniqueness even if the clock is adjusted. 145 | /// 3. A 48-bit node identifier, which distinguishes UUIDs in space. 146 | /// Typically, these are random bits, but an application-specific identifier may also be used. 147 | /// 148 | /// ```swift 149 | /// var rng = MyRandomNumberGenerator() 150 | /// for _ in 0..<5 { 151 | /// print(UniqueID.timeOrdered(using: &rng)) 152 | /// } 153 | /// 154 | /// "1EC5FEC5-F35E-6C08-B6CE-27381D0BBB75" 155 | /// "1EC5FEC5-F360-659E-B6CE-6F4CD8641BCC" 156 | /// "1EC5FEC5-F360-69EA-B6CE-212F47E81DBA" 157 | /// "1EC5FEC5-F360-6DA0-B6CE-13C650E0F431" 158 | /// "1EC5FEC5-F361-61A6-B6CE-3FD9EDD8DB87" 159 | /// |------------- --| |--| |----------| 160 | /// timestamp sq node 161 | /// ``` 162 | /// 163 | /// Version 6 UUIDs are similar to version 1 UUIDs from RFC-4122, except that their timestamps are stored 164 | /// in a sorting- and filtering-friendly manner, and their better locality improves the performance of 165 | /// many databases and data structures. They are highly resistant to collisions, and can incorporate 166 | /// externally-derived identifiers for even greater guarantees in distributed systems or applications. 167 | /// 168 | /// > Note: 169 | /// > The uniqueness of these IDs depends on the properties of the given random number generator. 170 | /// > A poor-quality generator may result in more collisions, and a seedable generator can be used in testing 171 | /// > to generate repeatable IDs. 172 | /// 173 | /// [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 174 | /// 175 | /// - parameters: 176 | /// - rng: The random number generator used to create a node identifier. 177 | /// 178 | @inlinable 179 | public static func timeOrdered(using rng: inout RNG) -> UniqueID where RNG: RandomNumberGenerator { 180 | // Set the IEEE 802 multicast bit for random node-IDs, as recommended by RFC-4122. 181 | let node = rng.next() | 0x0000_0100_0000_0000 182 | return timeOrdered(node: node) 183 | } 184 | 185 | /// Generates a new UUID sortable by creation time, with a particular node identifier. 186 | /// 187 | /// This function generates version 6 UUIDs, which are currently in [draft form][UUIDv6-draft-02]. 188 | /// They ensure uniqueness in time and space using 3 components: 189 | /// 190 | /// 1. A 60-bit embedded timestamp, taken from the system clock with 100ns resolution. 191 | /// 2. A 14-bit sequence number, which is a synchronized counter ensuring uniqueness even if the clock is adjusted. 192 | /// 3. A 48-bit node identifier, which distinguishes UUIDs in space. 193 | /// Typically, these are random bits, but an application-specific identifier may also be used. 194 | /// 195 | /// The following example demonstrates using a custom node ID, which is derived from a user and device ID which 196 | /// our backend guarantees is unique. In this case, we use a 36-bit user ID (sufficient for more than 68 billion users), 197 | /// an 8-bit device ID (for 256 devices per user), and 4-bit process ID (for 16 processes per device per user). 198 | /// 199 | /// ```swift 200 | /// // Combine unique information in to a 48-bit number. 201 | /// // These IDs are guaranteed unique by a backend service. 202 | /// let nodeID = makeNodeID( 203 | /// userID: currentUserID, 204 | /// deviceID: currentDeviceID, 205 | /// processID: currentProcessID 206 | /// ) 207 | /// 208 | /// for _ in 0..<5 { 209 | /// print(UniqueID.timeOrdered(node: nodeID)) 210 | /// } 211 | /// 212 | /// "1EC5FEC5-F35E-6C08-B6CE-38FC91741020" 213 | /// "1EC5FEC5-F360-659E-B6CE-38FC91741020" 214 | /// "1EC5FEC5-F360-69EA-B6CE-38FC91741020" 215 | /// "1EC5FEC5-F360-6DA0-B6CE-38FC91741020" 216 | /// "1EC5FEC5-F361-61A6-B6CE-38FC91741020" 217 | /// |------------- --| |--| |-------|-|| 218 | /// timestamp sq user d p 219 | /// ``` 220 | /// 221 | /// Provided our backend system is reliable at keeping these user/device/process IDs unique, we are able 222 | /// to create far more robust IDs at much larger scale than is possible with random (v4) UUIDs, with all of the 223 | /// benefits to database performance that come with time-ordered IDs. Many distributed systems and applications 224 | /// are able to offer these kinds of IDs basically "for free" anyway. 225 | /// 226 | /// Note that using a stable node identifier limits the number of unique IDs within each 100ns timestamp to 16,384 227 | /// (the number of unique sequence numbers). This usually isn't a problem - 16K UUIDs per 100ns is 228 | /// an extremely high frequency. 229 | /// 230 | /// [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 231 | /// 232 | /// - parameters: 233 | /// - node: A node identifier, used to distinguish UUIDs in space 234 | /// (e.g. across processes/machines, tracking different sequence numbers). 235 | /// This may be an sequence of random or pseudo-random bits, or a value with meaning to your application 236 | /// (e.g. a combined user/device ID). Only the bottom 48 bits will be used. 237 | /// 238 | @inlinable 239 | public static func timeOrdered(node: UInt64) -> UniqueID { 240 | let timestamp = _get_system_timestamp() 241 | // TODO: Consider an API for stable node identifiers to use their own (perhaps Thread/Task-local) sequence counters. 242 | // This will stop the counter overflowing too quickly. Also, the sequence counter might want to work 243 | // differently if the clock goes back and we can't rely on the node-ID to provide uniqueness. 244 | let sequence = withExclusiveGeneratorState { state -> UInt16 in 245 | if state.timestamp >= timestamp { 246 | state.sequence &+= 1 247 | } 248 | state.timestamp = timestamp 249 | return state.sequence 250 | } 251 | return timeOrdered(rawTimestamp: _unix_to_uuid_timestamp(unix: timestamp), sequence: sequence, node: node) 252 | } 253 | 254 | /// Creates a UUID sortable by creation time, using the given component values. 255 | /// 256 | /// This function generates version 6 UUIDs, which are currently in [draft form][UUIDv6-draft-02]. 257 | /// They ensure uniqueness in time and space using 3 components: 258 | /// 259 | /// 1. A 60-bit embedded timestamp, taken from the system clock with 100ns resolution. 260 | /// 2. A 14-bit sequence number, which is a synchronized counter ensuring uniqueness even if the clock is adjusted. 261 | /// 3. A 48-bit node identifier, which distinguishes UUIDs in space. 262 | /// Typically, these are random bits, but an application-specific identifier may also be used. 263 | /// 264 | /// Version 6 UUIDs are similar to version 1 UUIDs from RFC-4122, except that their timestamps are stored 265 | /// in a sorting- and filtering-friendly manner, and their better locality improves the performance of 266 | /// many databases and data structures. They are highly resistant to collisions, and can incorporate 267 | /// externally-derived identifiers for even greater guarantees in distributed systems or applications. 268 | /// 269 | /// Creating a UUID with a particular timestamp can be helpful when filtering UUIDs to ranges of time. 270 | /// For example, all UUIDs greater than `1EC3B396-11B7-6818-8000-000000000000` were created after 271 | /// 2021-11-01 17:30:17 (UTC). 272 | /// 273 | /// [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 274 | /// 275 | /// - parameters: 276 | /// - rawTimestamp: The timestamp, as a number of 100ns intervals from 00:00:00 October 15, 1582. 277 | /// Only the least significant 60 bits will be used. 278 | /// - sequence: The sequence number. Only the least significant 12 bits will be used. The default is 0. 279 | /// - node: The node ID. Only the least significant 48 bits will be used. The default is 0. 280 | /// 281 | @inlinable 282 | public static func timeOrdered(rawTimestamp: UInt64, sequence: UInt16 = 0, node: UInt64 = 0) -> UniqueID { 283 | var timestampAndVersion = (rawTimestamp &<< 4).bigEndian 284 | Swift.withUnsafeMutableBytes(of: ×tampAndVersion) { timestamp_bytes in 285 | // Insert the 4 version bits in the top half of octet 6. 286 | timestamp_bytes[7] = timestamp_bytes[6] &<< 4 | timestamp_bytes[7] &>> 4 287 | timestamp_bytes[6] = 0x60 | timestamp_bytes[6] &>> 4 288 | } 289 | // Top 2 bits of octet 8 are the variant (0b10 = standard). 290 | let sequenceAndVariant = ((sequence & 0x3FFF) | 0x8000).bigEndian 291 | let nodeBE = node.bigEndian 292 | 293 | var _uuidStorage = UniqueID.null.bytes 294 | withUnsafeMutableBytes(of: &_uuidStorage) { bytes in 295 | Swift.withUnsafeBytes(of: timestampAndVersion) { 296 | bytes.baseAddress!.copyMemory(from: $0.baseAddress!, byteCount: 8) 297 | } 298 | Swift.withUnsafeBytes(of: sequenceAndVariant) { 299 | (bytes.baseAddress! + 8).copyMemory(from: $0.baseAddress!, byteCount: 2) 300 | } 301 | Swift.withUnsafeBytes(of: nodeBE) { 302 | (bytes.baseAddress! + 10).copyMemory(from: $0.baseAddress! + 2, byteCount: 6) 303 | } 304 | } 305 | return UniqueID(bytes: _uuidStorage) 306 | } 307 | } 308 | 309 | 310 | // ------------------------------------- 311 | // MARK: - UUID components 312 | // ------------------------------------- 313 | 314 | 315 | extension UniqueID.Components where Self == UniqueID.TimeOrdered { 316 | public static var timeOrdered: Self { fatalError("Not intended to be called") } 317 | } 318 | 319 | extension UniqueID { 320 | 321 | /// The components of a time-ordered (version 6) UUID. 322 | /// 323 | /// To construct a view, initialize a value using a `UniqueID`, or use the ``UniqueID/UniqueID/components(_:)`` 324 | /// function. The following example demonstrates using this view to read the timestamp from a time-ordered UUID. 325 | /// 326 | /// ```swift 327 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 328 | /// id.components(.timeOrdered)?.timestamp 329 | /// // ✅ "2021-12-18 09:24:31 +0000" 330 | /// ``` 331 | /// 332 | /// ## Topics 333 | /// 334 | /// ### UUID Components 335 | /// 336 | /// - ``timestamp`` 337 | /// - ``rawTimestamp`` 338 | /// - ``sequence`` 339 | /// - ``node`` 340 | /// 341 | public struct TimeOrdered: UniqueID.Components { 342 | 343 | public let uuid: UniqueID 344 | 345 | @inlinable 346 | public init?(_ uuid: UniqueID) { 347 | guard uuid.version == 6 else { return nil } 348 | self.uuid = uuid 349 | } 350 | 351 | /// The timestamp of this UUID, as a number of 100ns intervals since 00:00:00 October 15, 1582. 352 | /// 353 | /// Note that only the least-significant 60 bits are used. 354 | /// 355 | /// ```swift 356 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 357 | /// // ^^^^^^^^ ^^^^ ^^^ 358 | /// id.components(.timeOrdered)?.rawTimestamp 359 | /// // ✅ 138591122712762640 (0x1EC5FE44E511910) 360 | /// 361 | /// // To convert to the Unix epoch, subtract the magic number. 362 | /// // Note that this is still a number of 100ns intervals. 363 | /// let relativeTo1970 = id.components(.timeOrdered)!.rawTimestamp &- (0x01B2_1DD2_1381_4000 as UInt64) 364 | /// 365 | /// // To convert to a Foundation TimeInterval, divide by 10_000_000. 366 | /// let date = Date(timeIntervalSince1970: TimeInterval(relativeTo1970) / 10_000_000) 367 | /// // ✅ "2021-12-18 09:24:31 +0000" 368 | /// ``` 369 | /// 370 | @inlinable 371 | public var rawTimestamp: UInt64 { 372 | var timestamp: UInt64 = 0 373 | Swift.withUnsafeMutableBytes(of: ×tamp) { timestamp_bytes in 374 | uuid.withUnsafeBytes { uuidBytes in 375 | timestamp_bytes.copyMemory(from: UnsafeRawBufferPointer(start: uuidBytes.baseAddress, count: 8)) 376 | } 377 | // Remove the UUID version bits. 378 | timestamp_bytes[6] = timestamp_bytes[6] &<< 4 | timestamp_bytes[7] &>> 4 379 | timestamp_bytes[7] = timestamp_bytes[7] &<< 4 380 | } 381 | return (timestamp.bigEndian &>> 4) // Widen to 64 bits 382 | } 383 | 384 | /// The sequence number of this UUID. This is generally just an opaque number. 385 | /// 386 | /// Note that only the least-significant 14 bits are used. 387 | /// 388 | /// ```swift 389 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 390 | /// // ^^^^ 391 | /// id.components(.timeOrdered)?.sequence 392 | /// // ✅ 15354 (0x3BFA) 393 | /// ``` 394 | /// 395 | @inlinable 396 | public var sequence: UInt16 { 397 | var clk_seq: UInt16 = 0 398 | withUnsafeMutableBytes(of: &clk_seq) { clk_seq_bytes in 399 | uuid.withUnsafeBytes { uuid_bytes in 400 | clk_seq_bytes.copyMemory(from: UnsafeRawBufferPointer(start: uuid_bytes.baseAddress! + 8, count: 2)) 401 | } 402 | } 403 | return (clk_seq.bigEndian & 0x3FFF) // Remove the variant bits. 404 | } 405 | 406 | /// The node identifier of this UUID. This may be random, or it may have some context-specific meaning. 407 | /// 408 | /// Note that only the least-significant 48 bits are used. 409 | /// 410 | /// ```swift 411 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 412 | /// // ^^^^^^^^^^^^ 413 | /// id.components(.timeOrdered)?.node 414 | /// // ✅ 272341992305718 (0xF7B18FB57436) 415 | /// ``` 416 | /// 417 | @inlinable 418 | public var node: UInt64 { 419 | var node: UInt64 = 0 420 | Swift.withUnsafeMutableBytes(of: &node) { nodeID_bytes in 421 | uuid.withUnsafeBytes { uuidBytes in 422 | nodeID_bytes.baseAddress!.advanced(by: 2).copyMemory(from: uuidBytes.baseAddress! + 10, byteCount: 6) 423 | } 424 | } 425 | return node.bigEndian 426 | } 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID.docc/UniqueID.md: -------------------------------------------------------------------------------- 1 | # ``UniqueID`` 2 | 3 | UUIDv4 and v6\* generation in Swift. 4 | 5 | ## Overview 6 | 7 | A UUID is an identifier that is unique across both space and time, with respect to the space of all UUIDs. 8 | A UUID can be used for multiple purposes, from tagging objects with an extremely short lifetime, 9 | to reliably identifying very persistent objects across a network. 10 | 11 | `UniqueID` supports any 128-bit UUID, and is able to generate RFC-compliant random (v4) and time-ordered (v6) UUIDs. 12 | 13 | ➡️ **Visit the ``UniqueID/UniqueID`` type to get started.** 14 | 15 | > Note: 16 | > UUIDv6 is currently in draft form. This version of UniqueID aligns with [draft 2][UUIDv6-draft-02], 17 | > dated 7 October 2021. 18 | 19 | [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 20 | -------------------------------------------------------------------------------- /Sources/UniqueID/UniqueID.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// A Universally Unique IDentifier (UUID). 16 | /// 17 | /// A UUID is an identifier that is unique across both space and time, with respect to the space of all UUIDs. 18 | /// A UUID can be used for multiple purposes, from tagging objects with an extremely short lifetime, 19 | /// to reliably identifying very persistent objects across a network. 20 | /// 21 | /// `UniqueID` supports any 128-bit UUID, and includes features to generate 2 kinds of ID: 22 | /// 23 | /// - **Random**: As defined in [RFC-4122][RFC-4122-UUIDv4] (UUIDv4). 24 | /// 25 | /// A 128-bit identifier, consisting of 122 random bits. 26 | /// These are the most common form of UUIDs; for example, they are the ones Foundation's `UUID` type creates 27 | /// by default. To generate a random UUID, call the static ``random()`` function. 28 | /// 29 | /// ```swift 30 | /// for _ in 0..<3 { 31 | /// print(UniqueID.random()) 32 | /// } 33 | /// "DFFC75B4-C92F-4DA9-97CA-7F0EEF067FF2" 34 | /// "67E5F28C-5083-4908-BD69-D7E27C8BABA4" 35 | /// "3BA8EEF0-DFBE-4AE0-A646-E165FCA9054C" 36 | /// ``` 37 | /// 38 | /// - **Time-Ordered**: Generated according to a [draft update of RFC-4122][UUIDv6-draft-02] (UUIDv6). 39 | /// 40 | /// A 128-bit identifier, consisting of a 60-bit timestamp with 100ns precision, a 14-bit sequencing number seeded 41 | /// from random bits, and a 48-bit node ID (which may also be random bits). To generate a time-ordered UUID, 42 | /// call the static ``timeOrdered()`` function. 43 | /// 44 | /// ```swift 45 | /// for _ in 0..<3 { 46 | /// print(UniqueID.timeOrdered()) 47 | /// } 48 | /// 49 | /// "1EC3C81E-A361-658C-BB38-65AAEF71CFCF" 50 | /// "1EC3C81E-A361-6F6E-BB38-6DE69B9BCA1B" 51 | /// "1EC3C81E-A362-698C-BB38-050642A95C73" 52 | /// |------------- --| |--| |----------| 53 | /// timestamp sq node 54 | /// ``` 55 | /// 56 | /// As you can see, time-ordered UUIDs generated in sequence share a common prefix (from the timestamp), yet 57 | /// retain high collision avoidance. This allows the use of sorted data structures and algorithms 58 | /// such as binary search, as an alternative to hash tables. They are far more efficient than random UUIDs for use 59 | /// as database primary keys. 60 | /// 61 | /// > Tip: 62 | /// > Random and Time-Ordered UUIDs may coexist in the same database. 63 | /// > They have different version numbers, so they are guaranteed to never collide. 64 | /// 65 | /// 66 | /// ### Compatibility with Foundation UUID 67 | /// 68 | /// 69 | /// `UniqueID` is fully compatible with Foundation's `UUID` type, including being compatible with UUIDs in serialized 70 | /// JSON form. This makes it easy to experiment with time-ordered UUIDs in your application: 71 | /// 72 | /// ```swift 73 | /// // Change from `UUID()` to `UUID(.timeOrdered())`: 74 | /// import Foundation 75 | /// import UniqueID 76 | /// 77 | /// struct MyRecord { 78 | /// var id = UUID(.timeOrdered()) // <-- 79 | /// // Other properties... 80 | /// } 81 | /// ``` 82 | /// 83 | /// To construct a `UniqueID` from a Foundation `UUID`, simply initialize a value: 84 | /// 85 | /// ```swift 86 | /// import Foundation 87 | /// import UniqueID 88 | /// 89 | /// let foundationID = UUID() 90 | /// let swiftID = UniqueID(foundationID) // <-- 91 | /// ``` 92 | /// 93 | /// 94 | /// ### Reading UUID Components 95 | /// 96 | /// 97 | /// Time-ordered UUIDs include components with meaningful values - such as the time they were generated. 98 | /// To read these values, use the ``components(_:)`` function: 99 | /// 100 | /// ```swift 101 | /// let id = UniqueID("1EC5FE44-E511-6910-BBFA-F7B18FB57436")! 102 | /// id.components(.timeOrdered)?.timestamp 103 | /// // ✅ "2021-12-18 09:24:31 +0000" 104 | /// ``` 105 | /// 106 | /// 107 | /// [RFC-4122-UUIDv4]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4 108 | /// [UUIDv6-draft-02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 109 | /// 110 | /// ## Topics 111 | /// 112 | /// ### Generating a UUID 113 | /// 114 | /// - ``random()`` 115 | /// - ``random(using:)`` 116 | /// - ``timeOrdered()`` 117 | /// - ``timeOrdered(using:)`` 118 | /// 119 | /// ### Converting from a Foundation UUID 120 | /// 121 | /// - ``init(_:)-30hew`` 122 | /// 123 | /// ### Parsing a UUID String 124 | /// 125 | /// - ``init(_:)-7p61g`` 126 | /// - ``init(utf8:)`` 127 | /// 128 | /// ### Obtaining a UUID's String Representation 129 | /// 130 | /// - ``serialized(lowercase:separators:)`` 131 | /// 132 | /// ### UUIDs as Bytes 133 | /// 134 | /// - ``init(bytes:)-6y0j`` 135 | /// - ``init(bytes:)-bnh6`` 136 | /// - ``bytes-swift.property`` 137 | /// - ``withUnsafeBytes(_:)`` 138 | /// 139 | /// ### Reading a UUID's Components 140 | /// 141 | /// - ``components(_:)`` 142 | /// - ``TimeOrdered`` 143 | /// 144 | /// ### Advanced UUID Generation 145 | /// 146 | /// - ``timeOrdered(node:)`` 147 | /// - ``timeOrdered(rawTimestamp:sequence:node:)`` 148 | /// 149 | /// ### Other 150 | /// 151 | /// - ``version`` 152 | /// - ``null`` 153 | /// 154 | public struct UniqueID { 155 | 156 | public typealias Bytes = ( 157 | UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, 158 | UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8 159 | ) 160 | 161 | /// The bytes of this UUID. 162 | /// 163 | public let bytes: Bytes 164 | 165 | /// Creates a UUID with the given bytes. 166 | /// 167 | @inlinable 168 | public init(bytes: Bytes) { 169 | self.bytes = bytes 170 | } 171 | 172 | /// The null UUID, `00000000-0000-0000-0000-000000000000`. 173 | /// 174 | @inlinable 175 | public static var null: UniqueID { 176 | UniqueID(bytes: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) 177 | } 178 | 179 | /// Creates a UUID with the given sequence of bytes. The sequence must contain exactly 16 bytes. 180 | /// 181 | @inlinable 182 | public init?(bytes: Bytes) where Bytes: Sequence, Bytes.Element == UInt8 { 183 | var uuid = UniqueID.null.bytes 184 | let bytesCopied = withUnsafeMutableBytes(of: &uuid) { uuidBytes in 185 | UnsafeMutableBufferPointer( 186 | start: uuidBytes.baseAddress.unsafelyUnwrapped.assumingMemoryBound(to: UInt8.self), 187 | count: 16 188 | ).initialize(from: bytes).1 189 | } 190 | guard bytesCopied == 16 else { return nil } 191 | self.init(bytes: uuid) 192 | } 193 | } 194 | 195 | 196 | // ------------------------------------- 197 | // MARK: - Standard protocols 198 | // ------------------------------------- 199 | 200 | 201 | extension UniqueID: Equatable, Hashable, Comparable { 202 | 203 | @inlinable 204 | public static func == (lhs: Self, rhs: Self) -> Bool { 205 | lhs.withUnsafeBytes { lhsBytes in 206 | rhs.withUnsafeBytes { rhsBytes in 207 | lhsBytes.elementsEqual(rhsBytes) 208 | } 209 | } 210 | } 211 | 212 | @inlinable 213 | public func hash(into hasher: inout Hasher) { 214 | withUnsafeBytes { hasher.combine(bytes: $0) } 215 | } 216 | 217 | @inlinable 218 | public static func < (lhs: Self, rhs: Self) -> Bool { 219 | lhs.withUnsafeBytes { lhsBytes in 220 | rhs.withUnsafeBytes { rhsBytes in 221 | lhsBytes.lexicographicallyPrecedes(rhsBytes) 222 | } 223 | } 224 | } 225 | } 226 | 227 | #if swift(>=5.5) && canImport(_Concurrency) 228 | extension UniqueID: Sendable {} 229 | #endif 230 | 231 | extension UniqueID: CustomStringConvertible, LosslessStringConvertible { 232 | 233 | @inlinable 234 | public var description: String { 235 | serialized() 236 | } 237 | } 238 | 239 | extension UniqueID: Codable { 240 | 241 | @inlinable 242 | public init(from decoder: Decoder) throws { 243 | let container = try decoder.singleValueContainer() 244 | guard let decoded = UniqueID(try container.decode(String.self)) else { 245 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid UUID string") 246 | } 247 | self = decoded 248 | } 249 | 250 | @inlinable 251 | public func encode(to encoder: Encoder) throws { 252 | var container = encoder.singleValueContainer() 253 | try container.encode(serialized()) 254 | } 255 | } 256 | 257 | 258 | // ------------------------------------- 259 | // MARK: - Basic properties 260 | // ------------------------------------- 261 | 262 | 263 | extension UniqueID { 264 | 265 | /// The version of this UUID, if it can be determined. 266 | /// 267 | @inlinable 268 | public var version: Int? { 269 | // Check the variant. 270 | guard (bytes.8 &>> 6) == 0b00000010 else { return nil } 271 | // Extract the version bits. 272 | return Int((bytes.6 & 0b1111_0000) &>> 4) 273 | } 274 | 275 | /// Invokes `body` with a pointer to the bytes of this UUID. 276 | /// 277 | /// ```swift 278 | /// let id = UniqueID.random() 279 | /// id.withUnsafeBytes { bytes in 280 | /// // ... use 'bytes' 281 | /// } 282 | /// ``` 283 | /// 284 | /// > Important: 285 | /// > The pointer provided to `body` must not escape the closure. 286 | /// 287 | @inlinable 288 | public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { 289 | try Swift.withUnsafeBytes(of: bytes, body) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Tests/UniqueIDTests/UUIDv6Tests.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import XCTest 16 | 17 | @testable import UniqueID 18 | 19 | final class UUIDv6Tests: XCTestCase {} 20 | 21 | 22 | // ------------------------------------------ 23 | // MARK: - Creation API. 24 | // ------------------------------------------ 25 | 26 | 27 | extension UUIDv6Tests { 28 | 29 | func testCreateWithComponents() { 30 | 31 | do { 32 | let uuid = UniqueID.timeOrdered(rawTimestamp: 0, sequence: 0, node: 0) 33 | XCTAssertEqual(uuid.serialized(), "00000000-0000-6000-8000-000000000000") 34 | XCTAssertEqual(uuid.version, 6) 35 | XCTAssertEqual(uuid.components(.timeOrdered)?.rawTimestamp, 0 as UInt64) 36 | XCTAssertEqual(uuid.components(.timeOrdered)?.sequence, 0 as UInt16) 37 | XCTAssertEqual(uuid.components(.timeOrdered)?.node, 0 as UInt64) 38 | } 39 | do { 40 | let uuid = UniqueID.timeOrdered( 41 | rawTimestamp: 0xABCD_B0B0_FEED_FACE, sequence: 0xF9F9, node: 0xABCD_B0B0_FEED_FACE 42 | ) 43 | XCTAssertEqual(uuid.serialized(), "BCDB0B0F-EEDF-6ACE-B9F9-B0B0FEEDFACE") 44 | XCTAssertEqual(uuid.version, 6) 45 | // Bottom 60 bits of timestamp preserved. 46 | XCTAssertEqual(uuid.components(.timeOrdered)?.rawTimestamp, 0xBCD_B0B0_FEED_FACE as UInt64) 47 | // Bottom 14 bits of sequence preserved. 48 | XCTAssertEqual(uuid.components(.timeOrdered)?.sequence, 0x39F9 as UInt16) 49 | // Bottom 48 bits of node preserved. 50 | XCTAssertEqual(uuid.components(.timeOrdered)?.node, 0x0000_B0B0_FEED_FACE as UInt64) 51 | } 52 | } 53 | 54 | func testRandomNodeHasMulticastBit() { 55 | 56 | // [RFC-4122] 4.5. Node IDs that Do Not Identify the Host 57 | // 58 | // A better solution is to obtain a 47-bit cryptographic quality random 59 | // number and use it as the low 47 bits of the node ID, with the least 60 | // significant bit of the first octet of the node ID set to one. This 61 | // bit is the unicast/multicast bit, which will never be set in IEEE 802 62 | // addresses obtained from network cards. Hence, there can never be a 63 | // conflict between UUIDs generated by machines with and without network 64 | // cards. 65 | for _ in 0..<10_000 { 66 | let randomNode = UniqueID.timeOrdered().components(.timeOrdered)!.node 67 | XCTAssertTrue((randomNode &>> 40) & 1 == 1) 68 | } 69 | } 70 | } 71 | 72 | 73 | // ------------------------------------------ 74 | // MARK: - Creation functionality. 75 | // ------------------------------------------ 76 | 77 | 78 | extension UUIDv6Tests { 79 | 80 | func testTimestamp() { 81 | 82 | let beforeCreate = Date() 83 | let beforeCreateRaw = UInt64(beforeCreate.timeIntervalSince1970 * 10_000_000) + 0x01B2_1DD2_1381_4000 84 | Thread.sleep(forTimeInterval: 1 /* second */) 85 | 86 | let uniqueIDs = (0..<10).map { _ in UniqueID.timeOrdered() } 87 | Thread.sleep(forTimeInterval: 1 /* second */) 88 | 89 | let afterCreate = Date() 90 | let afterCreateRaw = UInt64(afterCreate.timeIntervalSince1970 * 10_000_000) + 0x01B2_1DD2_1381_4000 91 | 92 | for uuid in uniqueIDs { 93 | guard let components = uuid.components(.timeOrdered) else { 94 | XCTFail("Not a valid UUIDv6") 95 | continue 96 | } 97 | #if !NO_FOUNDATION_COMPAT 98 | XCTAssertGreaterThan(components.timestamp, beforeCreate) 99 | XCTAssertLessThan(components.timestamp, afterCreate) 100 | #endif // NO_FOUNDATION_COMPAT 101 | XCTAssertGreaterThan(components.rawTimestamp, beforeCreateRaw) 102 | XCTAssertLessThan(components.rawTimestamp, afterCreateRaw) 103 | } 104 | } 105 | 106 | func testParallelClockSequenceGeneration() { 107 | 108 | // Run a number of (hopefully) parallel tasks, each generating a lot of v6 UUIDs with a stable node ID. 109 | let numberOfTasks = 32 110 | let uuidsPerTask = 33_000 111 | 112 | let results = Array<[UniqueID]>(unsafeUninitializedCapacity: numberOfTasks) { buffer, count in 113 | let buffer = buffer 114 | let group = DispatchGroup() 115 | group.enter() 116 | DispatchQueue.global(qos: .userInitiated).async { 117 | DispatchQueue.concurrentPerform(iterations: numberOfTasks) { taskOffset in 118 | (buffer.baseAddress! + taskOffset).initialize( 119 | to: (0..() 136 | allUniqueIDs.reserveCapacity(numberOfTasks * uuidsPerTask) 137 | for uuid in results.joined() { 138 | XCTAssert(allUniqueIDs.insert(uuid).inserted, "Duplicate! \(uuid)") 139 | } 140 | XCTAssertEqual(allUniqueIDs.count, numberOfTasks * uuidsPerTask) 141 | 142 | // We would expect to see IDs with different sequence numbers, 143 | // indicating colliding timestamps. 144 | if let sampleSequence = allUniqueIDs.first?.components(.timeOrdered)?.sequence { 145 | let sequenceDidChange = allUniqueIDs.contains { 146 | ($0.components(.timeOrdered)?.sequence ?? sampleSequence) != sampleSequence 147 | } 148 | if !sequenceDidChange { 149 | print( 150 | """ 151 | ⚠️ Warning ⚠️ - all UUIDs had the same sequence number. 152 | This could indicate that tasks did not run in parallel. 153 | """ 154 | ) 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Tests/UniqueIDTests/UniqueIDTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright The swift-UniqueID Contributors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import XCTest 16 | 17 | @testable import UniqueID 18 | 19 | final class UniqueIDTests: XCTestCase {} 20 | 21 | 22 | // ------------------------------------------ 23 | // MARK: - Basic properties and conformances. 24 | // ------------------------------------------ 25 | 26 | 27 | extension UniqueIDTests { 28 | 29 | func testInitWithBytes() { 30 | 31 | // These are actually the same type. 32 | let src: UniqueID.Bytes = Foundation.UUID().uuid 33 | let uuid = UniqueID(bytes: src) 34 | withUnsafeBytes(of: src) { srcBytes in 35 | uuid.withUnsafeBytes { 36 | XCTAssert($0.elementsEqual(srcBytes)) 37 | } 38 | } 39 | // Also, we know that Foundation's UUIDs are version 4. 40 | XCTAssertEqual(uuid.version, 4) 41 | } 42 | 43 | func testInitWithBytesSequence() { 44 | 45 | let random = (0..<16).map { _ in UInt8.random(in: 0 ... .max) } 46 | guard let uuid = UniqueID(bytes: random) else { 47 | XCTFail() 48 | return 49 | } 50 | uuid.withUnsafeBytes { 51 | XCTAssert($0.elementsEqual(random)) 52 | } 53 | } 54 | 55 | func testVersion() { 56 | 57 | // v4 58 | for _ in 0..<100 { 59 | let uuid = UniqueID.random() 60 | XCTAssertEqual(uuid.version, 4) 61 | } 62 | // v6 63 | for _ in 0..<100 { 64 | let uuid = UniqueID.timeOrdered() 65 | XCTAssertEqual(uuid.version, 6) 66 | } 67 | // Fake UUIDv2 (manually set the version bits). 68 | do { 69 | var uuidBytes = UniqueID.random().bytes 70 | uuidBytes.6 = (uuidBytes.6 & 0xF) | 0x20 71 | let fakeUUID = UniqueID(bytes: uuidBytes) 72 | XCTAssertEqual(fakeUUID.version, 2) 73 | } 74 | // Non-standard variants should return 'nil' for version. 75 | do { 76 | var uuidBytes = UniqueID.random().bytes 77 | uuidBytes.8 = (uuidBytes.8 & 0x1F) | 0xC0 // 0b110X_XXXX is Microsoft backward compatibility variant. 78 | let fakeUUID = UniqueID(bytes: uuidBytes) 79 | XCTAssertNil(fakeUUID.version) 80 | } 81 | } 82 | 83 | func testEquatableHashable() { 84 | 85 | let n = 100_000 86 | 87 | var ids = [UniqueID]() 88 | ids.reserveCapacity(n) 89 | 90 | func testUniqueIDs(_ createID: () -> UniqueID) { 91 | withoutActuallyEscaping(createID) { createID in 92 | ids.append(contentsOf: (0..=5.5) && canImport(_Concurrency) 143 | func testSendable() { 144 | func requiresSendable(_: T) {} 145 | requiresSendable(UniqueID.timeOrdered()) 146 | } 147 | #endif 148 | 149 | func testCodable() throws { 150 | 151 | guard #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) else { 152 | throw XCTSkip("JSONEncoder.OutputFormatting.sortedKeys requires iOS 11 or newer") 153 | } 154 | 155 | struct TypeWithUniqueID: Equatable, Codable { 156 | var name: String 157 | var id: UniqueID 158 | } 159 | 160 | struct TypeWithFoundationUUID: Equatable, Codable { 161 | var name: String 162 | var id: UUID 163 | } 164 | 165 | let encoder = JSONEncoder() 166 | encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 167 | 168 | // Check that UniqueID encodes as expected. 169 | do { 170 | let value = TypeWithUniqueID( 171 | name: "some object", id: UniqueID("1EC3C488-F485-6F38-8000-7DA9862207F3")! 172 | ) 173 | XCTAssertEqual( 174 | String(decoding: try encoder.encode(value), as: UTF8.self), 175 | #""" 176 | { 177 | "id" : "1EC3C488-F485-6F38-8000-7DA9862207F3", 178 | "name" : "some object" 179 | } 180 | """# 181 | ) 182 | } 183 | // Check that we produce the same encoding as Foundation's UUID, 184 | // and that each can parse the other's output. 185 | do { 186 | for uniqueIDCreator in [UniqueID.random, UniqueID.timeOrdered] { 187 | for _ in 0..<100 { 188 | let uniqueID = uniqueIDCreator() 189 | 190 | let foundationValue = TypeWithFoundationUUID(name: "some object", id: UUID(uuid: uniqueID.bytes)) 191 | let foundationData = try encoder.encode(foundationValue) 192 | let uniqueIDValue = TypeWithUniqueID(name: "some object", id: uniqueID) 193 | let uniqueIDData = try encoder.encode(uniqueIDValue) 194 | XCTAssertEqual(foundationData, uniqueIDData) 195 | 196 | let uniqueIDFromFoundation = try JSONDecoder().decode(TypeWithUniqueID.self, from: foundationData) 197 | let foundationFromUniqueID = try JSONDecoder().decode(TypeWithFoundationUUID.self, from: uniqueIDData) 198 | XCTAssertEqual(uniqueIDValue, uniqueIDFromFoundation) 199 | XCTAssertEqual(foundationValue, foundationFromUniqueID) 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | 207 | // ------------------------------------- 208 | // MARK: - Parsing and serialization. 209 | // ------------------------------------- 210 | 211 | 212 | extension UniqueIDTests { 213 | 214 | func testParse() { 215 | 216 | // Stuff which should parse. 217 | 218 | let originUUID = UniqueID( 219 | bytes: (0x1E, 0xC3, 0xA5, 0xFB, 0x6F, 0xE9, 0x64, 0xD8, 0x80, 0x04, 0xC7, 0x50, 0x08, 0x7B, 0xF2, 0xDB) 220 | ) 221 | 222 | for stringRepresentation in [ 223 | "1EC3A5FB-6FE9-64D8-8004-C750087BF2DB", // Standard 8-4-4-4-12 224 | "1ec3A5fB-6fE9-64d8-8004-c750087Bf2Db", // Mixed case 225 | "{1EC3A5FB-6FE9-64D8-8004-C750087BF2DB}", // Curly braces 226 | "1ec3a5fb6fe964d88004c750087bf2db", // No dashes 227 | "1E-C3-A5-FB-6F-E9-64-D8-80-04-C7-50-08-7B-F2-DB", // Lots of dashes 228 | "1E-C3-A5-FB-6F-E9---64-D8-8004C7-50---08-7B-F2-DB", // Lots of dashes (2) 229 | ] { 230 | guard let parsed = UniqueID(stringRepresentation) else { 231 | XCTFail("Failed to parse \(stringRepresentation)") 232 | continue 233 | } 234 | XCTAssertEqual(originUUID, parsed) 235 | } 236 | 237 | // Stuff which shouldn't parse. 238 | 239 | for stringRepresentation in [ 240 | "hello", // Not a UUID 241 | "http://example.com/", // Not a UUID (2) 242 | "(1EC3A5FB-6FE9-64D8-8004-C750087BF2DB)", // Curved brackets 243 | "[1EC3A5FB-6FE9-64D8-8004-C750087BF2DB]", // Square brackets 244 | "0", // Not enough bytes 245 | "123", // Not enough bytes (2) 246 | "1ec3a5fb6fe964d88004c750087bf2d", // Not enough bytes (3) 247 | "1ec3a5fb6fe964d88004c750087bf2db0", // Too many bytes 248 | "1ec3a5fb6fe964d88004c750087bf2db03", // Too many bytes (2) 249 | "1E,C3,A5,FB,6F,E9,64,D8,80,04,C7,50,08,7B,F2,DB", // Other characters between bytes 250 | ] { 251 | XCTAssertNil(UniqueID(stringRepresentation)) 252 | } 253 | } 254 | 255 | func testSerialize() { 256 | 257 | let uniqueID = UniqueID( 258 | bytes: (0x1E, 0xC3, 0xA5, 0xFB, 0x6F, 0xE9, 0x64, 0xD8, 0x80, 0x04, 0xC7, 0x50, 0x08, 0x7B, 0xF2, 0xDB) 259 | ) 260 | // Default serialization is 8-4-4-4-12, uppercase. 261 | XCTAssertEqual(uniqueID.serialized(), "1EC3A5FB-6FE9-64D8-8004-C750087BF2DB") 262 | XCTAssertEqual(uniqueID.description, "1EC3A5FB-6FE9-64D8-8004-C750087BF2DB") 263 | // This is the same as Foundation. 264 | XCTAssertEqual(Foundation.UUID(uuid: uniqueID.bytes).uuidString, uniqueID.serialized()) 265 | XCTAssertEqual(Foundation.UUID(uuid: uniqueID.bytes).description, uniqueID.description) 266 | 267 | // Serialization options. 268 | XCTAssertEqual(uniqueID.serialized(lowercase: true), "1ec3a5fb-6fe9-64d8-8004-c750087bf2db") 269 | XCTAssertEqual(uniqueID.serialized(separators: false), "1EC3A5FB6FE964D88004C750087BF2DB") 270 | XCTAssertEqual(uniqueID.serialized(lowercase: true, separators: false), "1ec3a5fb6fe964d88004c750087bf2db") 271 | } 272 | 273 | func testReparse() { 274 | 275 | for _ in 0..<1000 { 276 | let id = UniqueID.random() 277 | func checkReparse(_ serialization: String) { 278 | let serialization = String(decoding: serialization.utf8, as: UTF8.self) 279 | let reparsed = UniqueID(serialization) 280 | XCTAssertEqual(id, reparsed) 281 | } 282 | checkReparse(id.serialized()) 283 | checkReparse(id.serialized(lowercase: true)) 284 | checkReparse(id.serialized(separators: false)) 285 | checkReparse(id.serialized(lowercase: true, separators: false)) 286 | } 287 | } 288 | } 289 | 290 | 291 | // ------------------------------------- 292 | // MARK: - Other 293 | // ------------------------------------- 294 | 295 | 296 | extension UniqueIDTests { 297 | 298 | #if !NO_FOUNDATION_COMPAT 299 | 300 | func testFoundationCompat() { 301 | do { 302 | let uuid: UUID = UUID(.timeOrdered()) 303 | let uniqueID: UniqueID = UniqueID(uuid) 304 | XCTAssertEqual(uniqueID.description, uuid.description) 305 | } 306 | do { 307 | let uuid = UUID() 308 | let uniqueID = UniqueID(uuid) 309 | XCTAssertEqual(uniqueID.description, uuid.description) 310 | } 311 | } 312 | 313 | #endif // NO_FOUNDATION_COMPAT 314 | 315 | func testParseHexTable() { 316 | XCTAssertEqual(_parseHex_table.count, Int(UInt8.max)) 317 | } 318 | } 319 | --------------------------------------------------------------------------------