crockford32 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|