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 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: []