crockford32 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.standard.yml +2 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +63 -0
- data/README.md +167 -0
- data/Rakefile +24 -0
- data/bin/console +8 -0
- data/bin/setup +5 -0
- data/crockford32.gemspec +38 -0
- data/lib/crockford32/errors.rb +60 -0
- data/lib/crockford32/version.rb +6 -0
- data/lib/crockford32.rb +233 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f6e7d5926825497887afa8a356c29d3d858c8fd77c8885b926899b69e110b5d2
|
4
|
+
data.tar.gz: 1d012996acd856eed31ec4aa096c6f1c2ee193b2df081138670201546811363d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4027fea38aa6aeb2f627e203d09345afd53a0e7120a25aec1d52ac5b1a1d0e626a8c37a02677a8eb47d5bed0b45db3264f2c4e33a6d7175bddd9b0e09e0efafa
|
7
|
+
data.tar.gz: 7af4989d8267bef92f06347af2bdd8cfaa668bf15c4e142111549939bcd4b8b4fa97c845d7cc29e23d48a8c386734600858689d02877e84c9bef3e3712277ac6
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
crockford32 (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.2)
|
10
|
+
benchmark-ips (2.10.0)
|
11
|
+
debug (1.4.0)
|
12
|
+
irb (>= 1.3.6)
|
13
|
+
reline (>= 0.2.7)
|
14
|
+
io-console (0.5.11)
|
15
|
+
irb (1.4.1)
|
16
|
+
reline (>= 0.3.0)
|
17
|
+
minitest (5.15.0)
|
18
|
+
parallel (1.21.0)
|
19
|
+
parser (3.1.0.0)
|
20
|
+
ast (~> 2.4.1)
|
21
|
+
rainbow (3.1.1)
|
22
|
+
rake (13.0.6)
|
23
|
+
regexp_parser (2.2.1)
|
24
|
+
reline (0.3.1)
|
25
|
+
io-console (~> 0.5)
|
26
|
+
rexml (3.2.5)
|
27
|
+
rubocop (1.25.1)
|
28
|
+
parallel (~> 1.10)
|
29
|
+
parser (>= 3.1.0.0)
|
30
|
+
rainbow (>= 2.2.2, < 4.0)
|
31
|
+
regexp_parser (>= 1.8, < 3.0)
|
32
|
+
rexml
|
33
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
34
|
+
ruby-progressbar (~> 1.7)
|
35
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
36
|
+
rubocop-ast (1.15.2)
|
37
|
+
parser (>= 3.0.1.1)
|
38
|
+
rubocop-performance (1.13.2)
|
39
|
+
rubocop (>= 1.7.0, < 2.0)
|
40
|
+
rubocop-ast (>= 0.4.0)
|
41
|
+
ruby-progressbar (1.11.0)
|
42
|
+
standard (1.7.2)
|
43
|
+
rubocop (= 1.25.1)
|
44
|
+
rubocop-performance (= 1.13.2)
|
45
|
+
unicode-display_width (2.1.0)
|
46
|
+
webrick (1.7.0)
|
47
|
+
yard (0.9.27)
|
48
|
+
webrick (~> 1.7.0)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
x86_64-darwin-19
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
benchmark-ips (~> 2.10)
|
55
|
+
crockford32!
|
56
|
+
debug (~> 1.4)
|
57
|
+
minitest (~> 5.0)
|
58
|
+
rake (~> 13.0)
|
59
|
+
standard (~> 1.3)
|
60
|
+
yard (~> 0.9.27)
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
2.3.3
|
data/README.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# Crockford32
|
2
|
+
A fast little-endian implementation of [Douglas Crockford's Base32 specification](https://www.crockford.com/base32.html).
|
3
|
+
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
Add this line to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'crockford32'
|
10
|
+
```
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle install
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install crockford32
|
19
|
+
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
Encode data with the `encode` method:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
encoded = Crockford32.encode(1234) # encoded = "J61"
|
26
|
+
```
|
27
|
+
See the [the encode API documentation for error details](https://tinychameleon.github.io/crockford32/Crockford32.html#encode-class_method).
|
28
|
+
|
29
|
+
---
|
30
|
+
|
31
|
+
Decode a value with the `decode` method:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
decoded = Crockford32.decode("J61") # decoded = 1234
|
35
|
+
```
|
36
|
+
See the [the decode API documentation for error details](https://tinychameleon.github.io/crockford32/Crockford32.html#decode-class_method).
|
37
|
+
|
38
|
+
|
39
|
+
### Strings
|
40
|
+
You can also encode and decode strings by providing the `:string` value as the `into:` argument to `decode`.
|
41
|
+
Encoding a string requires no special consideration.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
encoded = Crockford32.encode("abc") # encoded = "1KR66"
|
45
|
+
decoded = Crockford32.decode(encoded, into: :string) # decoded = "abc"
|
46
|
+
```
|
47
|
+
|
48
|
+
When decoding you may receive a byte string if the encoded value is not alphanumeric:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
decoded = Crockford32.decode("J61", into: :string) # decoded = "\xD2\x04"
|
52
|
+
```
|
53
|
+
|
54
|
+
### Padding to a Length
|
55
|
+
You can ensure your encoded values are a specific length by specifying a `length:` on `encode`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
encoded = Crockford32.encode(1234, length: 5) # encoded = "J6100"
|
59
|
+
```
|
60
|
+
|
61
|
+
The padding is always "0" values and is always appended to the end of the encoded string.
|
62
|
+
Whatever you specify as the `length:` will be the length of the encoded string you receive.
|
63
|
+
|
64
|
+
### Check Symbols
|
65
|
+
If you wish to append a check symbol for simple modulus-based error detection both `encode` and `decode` support it with the `check:` keyword.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
encoded = Crockford32.encode(1234, check: true) # encoded = "J61D". "D" is the checksum symbol.
|
69
|
+
decoded = Crockford32.decode(encoded, check: true) # decoded = 1234. Checksum is tested.
|
70
|
+
```
|
71
|
+
|
72
|
+
An error will be raised if the decoding process does not pass the checksum:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
decoded = Crockford32.decode("J71D", check: true) # Notice the 6 changed to a 7.
|
76
|
+
~/.../crockford32/lib/crockford32.rb:43:in `decode': Value J71 has checksum 8 but requires 13 (Crockford32::ChecksumError)
|
77
|
+
from (irb):2:in `<main>'
|
78
|
+
from bin/console:8:in `<main>'
|
79
|
+
```
|
80
|
+
|
81
|
+
If you specify a `length:` and `check: true` when encoding, the checksum will be included as part of the `length:`.
|
82
|
+
|
83
|
+
### Encoding Type Support
|
84
|
+
This library currently supports encoding `Integer` and `String` values.
|
85
|
+
|
86
|
+
### Little-Endian Encoding
|
87
|
+
The encoding example above shows the little-endian results of encoding a number.
|
88
|
+
Some libraries encode using big-endian and will return `"16J"` when the number `1234` is encoded.
|
89
|
+
|
90
|
+
This library _always_ uses little-endian.
|
91
|
+
|
92
|
+
### More Information
|
93
|
+
For more detailed information about the library [see the API documentation](https://tinychameleon.github.io/crockford32/).
|
94
|
+
|
95
|
+
|
96
|
+
## Development
|
97
|
+
To get started development on this gem run the `bin/setup` command. This will install dependencies and run the tests and linting tasks to ensure everything is working.
|
98
|
+
|
99
|
+
For an interactive console with the gem loaded run `bin/console`.
|
100
|
+
|
101
|
+
|
102
|
+
## Testing
|
103
|
+
Use the `bundle exec rake test` command to run unit tests. To install the gem onto your local machine for general integration testing use `bundle exec rake install`.
|
104
|
+
|
105
|
+
|
106
|
+
## Releases
|
107
|
+
Do the following to release a new version of this gem:
|
108
|
+
|
109
|
+
- Update the version number in [lib/crockford32/version.rb](./lib/crockford32/version.rb)
|
110
|
+
- Ensure necessary documentation changes are complete
|
111
|
+
- Ensure changes are in the [CHANGELOG.md](./CHANGELOG.md)
|
112
|
+
- Create the new release using `bundle exec rake release`
|
113
|
+
|
114
|
+
After this is done the following side-effects should be visible:
|
115
|
+
|
116
|
+
- A new git tag for the version number should exist
|
117
|
+
- Commits for the new version should be pushed to GitHub
|
118
|
+
- The new gem should be available on [rubygems.org](https://rubygems.org).
|
119
|
+
|
120
|
+
Finally, update the documentation hosted on GitHub Pages:
|
121
|
+
|
122
|
+
- Check-out the `gh-pages` branch
|
123
|
+
- Merge `main` into the `gh-pages` branch
|
124
|
+
- Generate the documentation with `bundle exec rake yard`
|
125
|
+
- Commit the documentation on the `gh-pages` branch
|
126
|
+
- Push the new documentation so GitHub Pages can deploy it
|
127
|
+
|
128
|
+
|
129
|
+
## Benchmarks
|
130
|
+
Benchmarking is tricky and the goal of a benchmark should be clear before attempting performance improvements. The goal of this library for performance is as follows:
|
131
|
+
|
132
|
+
> This library should be capable of encoding and decoding IDs at a rate which does not make it a bottleneck for the majority of web APIs.
|
133
|
+
|
134
|
+
Given the above goal statement, these benchmarks run on the following environment:
|
135
|
+
|
136
|
+
| Attribute | Value |
|
137
|
+
|:--|--:|
|
138
|
+
| Ruby Version | 3.1.0 |
|
139
|
+
| MacOS Version | Catalina 10.15.7 (19H1615) |
|
140
|
+
| MacOS Model Identifier | MacBookPro10,1 |
|
141
|
+
| MacOS Processor Name | Quad-Core Intel Core i7 |
|
142
|
+
| MacOS Processor Speed | 2.7 GHz |
|
143
|
+
| MacOS Number of Processors | 1 |
|
144
|
+
| MacOS Total Number of Cores | 4 |
|
145
|
+
| MacOS L2 Cache (per Core) | 256 KB |
|
146
|
+
| MacOS L3 Cache | 6 MB |
|
147
|
+
| MacOS Hyper-Threading Technology | Enabled |
|
148
|
+
| MacOS Memory | 16 GB |
|
149
|
+
|
150
|
+
When run using a constant 16 bytes of data in the above environment the performance is approximately as follows:
|
151
|
+
|
152
|
+
```
|
153
|
+
~/…/crockford32› date && ruby test/benchmarks/current.rb
|
154
|
+
Fri Feb 18 15:35:49 PST 2022
|
155
|
+
Warming up --------------------------------------
|
156
|
+
encode 11.498k i/100ms
|
157
|
+
decode 10.550k i/100ms
|
158
|
+
encode string 7.686k i/100ms
|
159
|
+
decode string 6.798k i/100ms
|
160
|
+
Calculating -------------------------------------
|
161
|
+
encode 117.614k (± 3.5%) i/s - 597.896k in 5.090119s
|
162
|
+
decode 108.253k (± 3.2%) i/s - 548.600k in 5.073326s
|
163
|
+
encode string 78.690k (± 3.1%) i/s - 399.672k in 5.084255s
|
164
|
+
decode string 69.707k (± 3.5%) i/s - 353.496k in 5.077700s
|
165
|
+
```
|
166
|
+
|
167
|
+
Being conservative in estimation 30k i/s round trip decoding and encoding cycles should be possible. This achieves the performance goal.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "standard/rake"
|
6
|
+
require "yard"
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.libs << "lib"
|
11
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
YARD::Rake::YardocTask.new do |t|
|
15
|
+
t.files = ["lib/**/*.rb", "-", "CHANGELOG.md"]
|
16
|
+
t.options = ["--private", "-o", "./docs"]
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: %i[test standard]
|
20
|
+
|
21
|
+
desc "Benchmark the current implementation"
|
22
|
+
task benchmark: [] do |t|
|
23
|
+
sh "date; ruby test/benchmarks/current.rb"
|
24
|
+
end
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/crockford32.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/crockford32/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "crockford32"
|
7
|
+
spec.version = Crockford32::VERSION
|
8
|
+
spec.authors = ["Stephan Tarulli"]
|
9
|
+
spec.email = ["srt@tinychameleon.com"]
|
10
|
+
|
11
|
+
spec.summary = "A fast little-endian implementation of Crockford's Base32 specification."
|
12
|
+
spec.description = "A fast little-endian implementation of Crockford's Base32 specification."
|
13
|
+
spec.homepage = "https://github.com/tinychameleon/crockford32"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
spec.metadata["changelog_uri"] = File.join(spec.homepage, "blob/main/CHANGELOG.md")
|
19
|
+
spec.metadata["bug_tracker_uri"] = File.join(spec.homepage, "issues")
|
20
|
+
spec.metadata["documentation_uri"] = "https://tinychameleon.github.io/crockford32/"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Uncomment to register a new dependency of your gem
|
34
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
35
|
+
|
36
|
+
# For more information and examples about making a new gem, check out our
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Crockford32
|
4
|
+
# The base error for all possible errors from {Crockford32}
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# The base error for all errors from {Crockford32.encode}
|
8
|
+
class EncodeError < Error; end
|
9
|
+
|
10
|
+
# The base error for all errors from {Crockford32.decode}
|
11
|
+
class DecodeError < Error; end
|
12
|
+
|
13
|
+
# An error representing the length of an encoded value being larger than the
|
14
|
+
# requested maximum length.
|
15
|
+
class LengthTooSmallError < EncodeError
|
16
|
+
# @param value [Integer, String] the value to be encoded.
|
17
|
+
# @param needed [Integer] the needed length to encode the value.
|
18
|
+
# @param given [Integer] the specified length to encode the value.
|
19
|
+
def initialize(value, needed, given)
|
20
|
+
super("Encoding #{value} requires a minimum length of #{needed}, but received #{given}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# An error representing an attmept to encode an unsupported type.
|
25
|
+
class UnsupportedEncodingTypeError < EncodeError
|
26
|
+
# @param type [Type] the unsupported type of the encode attempt.
|
27
|
+
def initialize(type)
|
28
|
+
super("Encoding #{type} not supported")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# An error representing a decode operation finding an illegal symbol within a
|
33
|
+
# provided value.
|
34
|
+
class InvalidCharacterError < DecodeError
|
35
|
+
# @param string [String] the string to decode.
|
36
|
+
# @param index [Integer] the index within the string of the invalid character.
|
37
|
+
def initialize(string, index)
|
38
|
+
super("Invalid character '#{string[index]}' in '#{string}' at index #{index}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# An error representing an unsupported destination type for a decode operation.
|
43
|
+
class UnsupportedDecodingTypeError < DecodeError
|
44
|
+
# @param type [Symbol] the supplied destination type.
|
45
|
+
def initialize(type)
|
46
|
+
super("Decoding as :#{type} not supported")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# An error representing a checksum mismatch between the decoded value and the
|
51
|
+
# supplied check symbol.
|
52
|
+
class ChecksumError < DecodeError
|
53
|
+
# @param value [String] the Base32 value to decode.
|
54
|
+
# @param checksum [Integer] the checksum of the decoded value.
|
55
|
+
# @param checksum_required [Integer] the required checksum of the decoded value.
|
56
|
+
def initialize(value, checksum, checksum_required)
|
57
|
+
super("Value #{value} has checksum #{checksum} but requires #{checksum_required}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/crockford32.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "crockford32/version"
|
4
|
+
require_relative "crockford32/errors"
|
5
|
+
|
6
|
+
# A fast little-endian implementation of {https://www.crockford.com/base32.html Douglas Crockford’s Base32 specification}.
|
7
|
+
#
|
8
|
+
# @since 1.0.0
|
9
|
+
module Crockford32
|
10
|
+
# The number of bits encoded per symbol.
|
11
|
+
ENCODED_BITS = 0x05
|
12
|
+
|
13
|
+
# The minimum value of a check symbol.
|
14
|
+
CHECK_SYMBOL_MIN_VALUE = 0x20
|
15
|
+
|
16
|
+
# The prime number used to implement error detection.
|
17
|
+
CHECKSUM_PRIME = 0x25
|
18
|
+
|
19
|
+
# The ordinal value of an ASCII dash character.
|
20
|
+
DASH = "-".ord.freeze
|
21
|
+
|
22
|
+
# standard:disable Layout/ExtraSpacing,Layout/ArrayAlignment
|
23
|
+
|
24
|
+
# Symbol values in order by encoded ASCII ordinal values.
|
25
|
+
DECODE_ORDINALS = [
|
26
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
27
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
28
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
29
|
+
34, nil, nil, nil, nil, nil, 32, nil, nil, nil, nil, nil,
|
30
|
+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nil, nil,
|
31
|
+
nil, 35, nil, nil, nil, 10, 11, 12, 13, 14, 15, 16,
|
32
|
+
17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25,
|
33
|
+
26, 36, 27, 28, 29, 30, 31, nil, nil, nil, nil, nil,
|
34
|
+
nil, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19,
|
35
|
+
1, 20, 21, 0, 22, 23, 24, 25, 26, 36, 27, 28,
|
36
|
+
29, 30, 31, nil, nil, nil, 33
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
# Encoding symbols ordered by bit value.
|
40
|
+
ENCODE_SYMBOLS = [
|
41
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
|
42
|
+
"G", "H", "J", "K", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z",
|
43
|
+
"*", "~", "$", "=", "U"
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
# standard:enable Layout/ExtraSpacing,Layout/ArrayAlignment
|
47
|
+
|
48
|
+
# @!group Public Methods
|
49
|
+
|
50
|
+
# Decode a Base32 value.
|
51
|
+
#
|
52
|
+
# @since 1.0.0
|
53
|
+
#
|
54
|
+
# @param value [String] the Base32 value to decode.
|
55
|
+
# @param into [Symbol] the destination type to decode into. Can be +:integer+ or +:string+.
|
56
|
+
# @param check [Boolean] whether to validate the check symbol.
|
57
|
+
#
|
58
|
+
# @return [Integer, String] the decoded value.
|
59
|
+
#
|
60
|
+
# @raise [ChecksumError] when the check symbol does not match the decoded checksum.
|
61
|
+
# @raise [InvalidCharacterError] when the value being decoded has a character outside the
|
62
|
+
# Base32 symbol set or a misplaced check symbol.
|
63
|
+
# @raise [UnsupportedDecodingTypeError] when the requested +into:+ type is not supported.
|
64
|
+
def self.decode(value, into: :integer, check: false)
|
65
|
+
checksum = check ? value[-1] : nil
|
66
|
+
value = check ? value[0...-1] : value
|
67
|
+
|
68
|
+
result = le_decode_number value
|
69
|
+
|
70
|
+
if check
|
71
|
+
actual = result % CHECKSUM_PRIME
|
72
|
+
required = DECODE_ORDINALS[checksum.ord]
|
73
|
+
raise ChecksumError.new(value, actual, required) if actual != required
|
74
|
+
end
|
75
|
+
|
76
|
+
convert result, into
|
77
|
+
end
|
78
|
+
|
79
|
+
# Encode a value as Base32.
|
80
|
+
#
|
81
|
+
# @since 1.0.0
|
82
|
+
#
|
83
|
+
# @param value [Integer, String] the value to encode.
|
84
|
+
# @param length [Integer] the exact length of the Base32 string. Will be padded with "0" to meet length.
|
85
|
+
# @param check [Boolean] whether to include a check symbol. This symbol is included in the length.
|
86
|
+
#
|
87
|
+
# @return [String] the encoded value.
|
88
|
+
#
|
89
|
+
# @raise [LengthTooSmallError] when the requested +length:+ is not large enough to fit the encoded result.
|
90
|
+
# @raise [UnsupportedEncodingTypeError] when the value to encode is an unsupported type.
|
91
|
+
def self.encode(value, length: nil, check: false)
|
92
|
+
le_encode_number(raw_value_to_number(value), length, check)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!endgroup
|
96
|
+
|
97
|
+
# @!group Private Methods
|
98
|
+
# @!visibility private
|
99
|
+
|
100
|
+
# Decode a value with the expectation that it is in little-endian order.
|
101
|
+
#
|
102
|
+
# @param encoded_value [String] the value to decode.
|
103
|
+
# @return [Integer] the decoded value.
|
104
|
+
def self.le_decode_number(encoded_value)
|
105
|
+
symbol = -1
|
106
|
+
bits = -ENCODED_BITS
|
107
|
+
encoded_value.bytes.reduce(0) do |result, ch|
|
108
|
+
symbol += 1
|
109
|
+
next result if ch == DASH
|
110
|
+
val = DECODE_ORDINALS[ch.ord]
|
111
|
+
raise InvalidCharacterError.new(encoded_value, symbol) if val.nil? || val >= CHECK_SYMBOL_MIN_VALUE
|
112
|
+
bits += ENCODED_BITS
|
113
|
+
result | (val << bits)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Convert a decoded result into the destination type.
|
118
|
+
#
|
119
|
+
# @param result [Integer] the decoded value.
|
120
|
+
# @param type [Symbol] the destination type for the value. Can be +:integer+ or +:string+.
|
121
|
+
# @return [Integer, String] the decoded value converted to the destination type.
|
122
|
+
def self.convert(result, type)
|
123
|
+
case type
|
124
|
+
when :integer
|
125
|
+
result
|
126
|
+
when :string
|
127
|
+
into_string result
|
128
|
+
else
|
129
|
+
raise UnsupportedDecodingTypeError.new(type)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Convert an Integer into a String.
|
134
|
+
#
|
135
|
+
# Each 8-bit sequence is packed into a String in little-endian order.
|
136
|
+
#
|
137
|
+
# @param result [Integer] the decoded value.
|
138
|
+
# @return [String] the decoded value as a String.
|
139
|
+
def self.into_string(result)
|
140
|
+
q, r = result.bit_length.divmod(0x08)
|
141
|
+
q += 1 if r > 0
|
142
|
+
bytes = Array.new(q)
|
143
|
+
q.times do |i|
|
144
|
+
bytes[i] = result & 0xff
|
145
|
+
result >>= 0x08
|
146
|
+
end
|
147
|
+
bytes.pack("C*")
|
148
|
+
end
|
149
|
+
|
150
|
+
# Convert a raw value into an Integer for encoding.
|
151
|
+
#
|
152
|
+
# @param value [Integer, String] the value being encoded.
|
153
|
+
# @return [Integer] the value converte to an Integer.
|
154
|
+
def self.raw_value_to_number(value)
|
155
|
+
case value
|
156
|
+
when String
|
157
|
+
q, r = value.bytesize.divmod(8)
|
158
|
+
if r == 0
|
159
|
+
string_to_integer_unrolled value, q
|
160
|
+
else
|
161
|
+
string_to_integer value
|
162
|
+
end
|
163
|
+
when Integer
|
164
|
+
value
|
165
|
+
else
|
166
|
+
raise UnsupportedEncodingTypeError.new value.class
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Convert a String to an Integer one byte per iteration.
|
171
|
+
#
|
172
|
+
# @param s [String] the String to convert.
|
173
|
+
# @return [Integer] the String converted to an Integer in little-endian order.
|
174
|
+
def self.string_to_integer(s)
|
175
|
+
shift = -0x08
|
176
|
+
s.each_byte.reduce(0) do |n, b|
|
177
|
+
shift += 0x08
|
178
|
+
n + (b << shift)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Convert a String to an Integer 8 bytes per iteration.
|
183
|
+
# @param s [String] the String to convert.
|
184
|
+
# @param iterations [Integer] the number of iterations to perform.
|
185
|
+
# @return [Integer] the String converted to an Integer in little-endian order.
|
186
|
+
def self.string_to_integer_unrolled(s, iterations)
|
187
|
+
n = 0
|
188
|
+
bytes = s.bytes
|
189
|
+
while iterations > 0
|
190
|
+
o = (iterations - 1) * 0x40
|
191
|
+
i = iterations * 0x08
|
192
|
+
n += bytes[i - 1] << (o + 0x38)
|
193
|
+
n += bytes[i - 2] << (o + 0x30)
|
194
|
+
n += bytes[i - 3] << (o + 0x28)
|
195
|
+
n += bytes[i - 4] << (o + 0x20)
|
196
|
+
n += bytes[i - 5] << (o + 0x18)
|
197
|
+
n += bytes[i - 6] << (o + 0x10)
|
198
|
+
n += bytes[i - 7] << (o + 0x08)
|
199
|
+
n += bytes[i - 8] << (o + 0x00)
|
200
|
+
iterations -= 1
|
201
|
+
end
|
202
|
+
n
|
203
|
+
end
|
204
|
+
|
205
|
+
# Encode an Integer as a Base32 value.
|
206
|
+
#
|
207
|
+
# @see encode
|
208
|
+
# @return [String]
|
209
|
+
def self.le_encode_number(number, length, check)
|
210
|
+
result = +""
|
211
|
+
n = number
|
212
|
+
loop do
|
213
|
+
chunk = n & 0x1F
|
214
|
+
result << ENCODE_SYMBOLS[chunk]
|
215
|
+
n >>= ENCODED_BITS
|
216
|
+
break if n == 0
|
217
|
+
end
|
218
|
+
|
219
|
+
rlen = result.length + (check ? 1 : 0)
|
220
|
+
if length
|
221
|
+
raise LengthTooSmallError.new(number, rlen, length) if rlen > length
|
222
|
+
result << "0" * (length - rlen)
|
223
|
+
end
|
224
|
+
|
225
|
+
result << ENCODE_SYMBOLS[number % CHECKSUM_PRIME] if check
|
226
|
+
result.freeze
|
227
|
+
end
|
228
|
+
|
229
|
+
private_class_method [:le_decode_number, :convert, :into_string, :raw_value_to_number,
|
230
|
+
:string_to_integer, :string_to_integer_unrolled, :le_encode_number]
|
231
|
+
|
232
|
+
# @!endgroup
|
233
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crockford32
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephan Tarulli
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-02-20 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A fast little-endian implementation of Crockford's Base32 specification.
|
14
|
+
email:
|
15
|
+
- srt@tinychameleon.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".standard.yml"
|
21
|
+
- CHANGELOG.md
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- bin/console
|
27
|
+
- bin/setup
|
28
|
+
- crockford32.gemspec
|
29
|
+
- lib/crockford32.rb
|
30
|
+
- lib/crockford32/errors.rb
|
31
|
+
- lib/crockford32/version.rb
|
32
|
+
homepage: https://github.com/tinychameleon/crockford32
|
33
|
+
licenses: []
|
34
|
+
metadata:
|
35
|
+
homepage_uri: https://github.com/tinychameleon/crockford32
|
36
|
+
source_code_uri: https://github.com/tinychameleon/crockford32
|
37
|
+
changelog_uri: https://github.com/tinychameleon/crockford32/blob/main/CHANGELOG.md
|
38
|
+
bug_tracker_uri: https://github.com/tinychameleon/crockford32/issues
|
39
|
+
documentation_uri: https://tinychameleon.github.io/crockford32/
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.6.0
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubygems_version: 3.3.3
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: A fast little-endian implementation of Crockford's Base32 specification.
|
59
|
+
test_files: []
|