crockford32 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ ## v1.0.0 - 2022-02-19
4
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rake", "~> 13.0"
8
+ gem "minitest", "~> 5.0"
9
+ gem "benchmark-ips", "~> 2.10"
10
+ gem "debug", "~> 1.4"
11
+ gem "standard", "~> 1.3"
12
+ gem "yard", "~> 0.9.27"
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "crockford32"
6
+
7
+ require "irb"
8
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ bundle install
5
+ bundle exec rake test standard
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crockford32
4
+ # The library version.
5
+ VERSION = "1.0.0"
6
+ end
@@ -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: []