Skip to content

Repository files navigation

ThirtyTo

ThirtyTo

Swift Package for using Base32Crockford Encoding for Data and Identifiers.

SwiftPMTwitterGitHubGitHub issuesGitHub Workflow Status

CodecovCodeFactor Gradecodebeat badgeCode Climate maintainabilityCode Climate technical debtCode Climate issuesReviewed by Hound

Table of Contents

Introduction

ThirtyTo provides a way to encode data and create identifiers which is both efficient and human-readable. While Base64 is more efficient it is not very human-readable with both both upper case and lower case letters as well as punctuation.

Requirements

Apple Platforms

  • Xcode 13.3 or later
  • Swift 5.5.2 or later
  • iOS 14 / watchOS 6 / tvOS 14 / macOS 12 or later deployment targets

Linux

  • Ubuntu 18.04 or later
  • Swift 5.5.2 or later

Installation

Use the Swift Package Manager to install this library via the repository url:

https://github.com/brightdigit/ThirtyTo.git

Use version up to 1.0.

Why use Base32Crockford

Base32Crockford offers the most reasonable compromise when it comes to encoding data. Being a super set of Base16, it uses all ten digits and 22 of the 26 Latin upper case characters.

Symbol ValueDecode SymbolEncode Symbol
00 O o0
11 I i L l1
222
333
444
555
666
777
888
999
10A aA
11B bB
12C cC
13D dD
14E eE
15F fF
16G gG
17H hH
18J jJ
19K kK
20M mM
21N nN
22P pP
23Q qQ
24R rR
25S sS
26T tT
27V vV
28W wW
29X xX
30Y yY
31Z zZ

Usage

ThirtyTo enables the encoding and decoding data in Base32Crockford as well as creation of unique identifiers. There are a variety of options available for encoding and decoding.

Encoding and Decoding Data

All encoding and decoding is done through the Base32CrockfordEncoding.encoding object. This provides an interface into encoding and decoding data as well standardizing.

To encode any data call:

publicfunc encode(
data:Data,
options:Base32CrockfordEncodingOptions=.none
)->String

Therefore to encode a Data object, simply call via:

letdata:Data // 0x00b003786a8d4aa28411f4e268c43629 letbase32String=Base32CrockfordEncoding.encoding.encode(data: data)print(base32String) // P01QGTMD9AH884FMW9MC8DH9

If you wish to decode the string you can call Base32CrockfordEncoding.decode:

letdata=tryBase32CrockfordEncoding.encoding.decode(
base32Encoded:"P01QGTMD9AH884FMW9MC8DH9") // 0x00b003786a8d4aa28411f4e268c43629

The Base32CrockfordEncoding.encode object provides the ability to specify options on formatting and a checksum.

How Checksum Works

You can optionally provide a checksum character at the end which allows for detecting transmission and entry errors early and inexpensively.

According to the specification:

The check symbol encodes the number modulo 37, 37 being the least prime number greater than 32. We introduce 5 additional symbols that are used only for encoding or decoding the check symbol.

The additional 5 symbols are:

Symbol ValueDecode SymbolEncode Symbol
32**
33~~
34$$
35==
36U uU

If you wish to include the checksum, pass true for the withChecksum property on the Base32CrockfordEncodingOptions object:

letdata:Data // 0xb63d798c4329401684d1d41d3becc95c
letbase32String=Base32CrockfordEncoding.encoding.encode(
data: data,
options:.init(withChecksum:true))print(base32String) // 5P7NWRRGS980B89MEM3MXYSJAW5

When decoding a string wtih a checksum, you must specify true for the withChecksum property on Base32CrockfordDecodingOptions:

letdata=tryBase32CrockfordEncoding.encoding.decode(
base32Encoded:"5P7NWRRGS980B89MEM3MXYSJAW5"a,
options:.init(withChecksum:true)) // 0xb63d798c4329401684d1d41d3becc95c

Besides adding a checksum, Base32CrockfordEncodingOptions also provides the ability to add a grouping separator.

Using Group Separators

According to the Base32Crockford specification:

Hyphens (-) can be inserted into symbol strings. This can partition a string into manageable pieces, improving readability by helping to prevent confusion. Hyphens are ignored during decoding.

To insert hyphens to the encoded string, provide the GroupingOptions object to Base32CrockfordEncodingOptions:

letdata:Data // 00c9c37484b85a42e8b3e7fbf806f2661b
letbase32StringGroupedBy3=Base32CrockfordEncoding.encoding.encode(
data: data,
options:.init(groupingBy:.init(maxLength:3)))letbase32StringGroupedBy9=Base32CrockfordEncoding.encoding.encode(
data: data,
options:.init(groupingBy:.init(maxLength:9)))print(base32StringGroupedBy3) // 69R-DT8-9E2-T8B-MB7-SZV-Z03-F4S-GV2
print(base32StringGroupedBy9) // 69RDT89E2-T8BMB7SZV-Z03F4SGV2

When decoding, hyphens are ignored:

letdataNoHyphens=tryBase32CrockfordEncoding.encoding.decode(
base32Encoded:"69RDT89E2T8BMB7SZVZ03F4SGV2") // 00c9c37484b85a42e8b3e7fbf806f2661b
letdataGroupedBy3=tryBase32CrockfordEncoding.encoding.decode(
base32Encoded:"69R-DT8-9E2-T8B-MB7-SZV-Z03-F4S-GV2") // 00c9c37484b85a42e8b3e7fbf806f2661b
letdataGroupedBy9=tryBase32CrockfordEncoding.encoding.decode(
base32Encoded:"69RDT89E2-T8BMB7SZV-Z03F4SGV2") // 00c9c37484b85a42e8b3e7fbf806f2661b
assert(dataNoHyphens == dataGroupedBy3) // true
assert(dataNoHyphens == dataGroupedBy9) // true
assert(dataGroupedBy3 == dataGroupedBy9) // true

Creating an Identifier

ThirtyTo offers the ability to create identifiers of different types in a universal factory. While UUID has been available to developers, this library provides a interface for creating two other types. To do this you can call IdentifierFactory/createIdentifier(with:) on the Identifier/factory to create any of the three types provided:

Each type has corresponding ComposableIdentifier/Specifications:

  • UUID takes no specifications and be created with IdentifierFactory.createIdentifier(_:)
  • ULID takes ULID/Specifications
  • UDID takes AnyIdentifierSpecifications

Here's an example with UDID:

// create an identifier for 1 million unique IDs that is a factor of 5.
let specs = AnyIdentifierSpecifications(
size: .minimumCount(1_000_000, factorOf: 5)
)
let identifier: UDID = Identifier.factory.createIdentifier(with: specs)

What is ULID?

ULID serves the purpose of solving several issues with UUID while being compatible:

  • 1.21e+24 unique ULIDs per millisecond
  • Lexicographically sortable!
  • Monotonic sort order (correctly detects and handles the same millisecond)

Most importantly it Uses Base32Crockford for better efficiency and readability.

To create a ULID you can either use IdentifierFactory.createIdentifier(with:) on Identifier.factory:

let ulid : ULID = Identifier.factory.createIdentifier(with: .parts(nil, .random(nil)))

or a constructor:

let ulid = ULID(specifications: .parts(nil, .random(nil)))

For most cases the default ULID.Specifications.default specification is sufficient. The follows the canonical spec which uses the first 6 bytes for a the timestamp and the last 10 bytes are random. Otherwise you can specify all 16 bytes with ULID.Specifications.data(_:) or specify which Date to use for the timestamp and the RandomDataGenerator to use for the ULID.randomPart of the data.

References

License

This code is distributed under the MIT license. See the LICENSE file for more info.

About

Encode, Decode and Generate Random String in Base32Crockford Format

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages