Module: Crockford32

Defined in:
lib/crockford32.rb,
lib/crockford32/errors.rb,
lib/crockford32/version.rb

Overview

A fast little-endian implementation of Douglas Crockford’s Base32 specification.

Since:

  • 1.0.0

Defined Under Namespace

Classes: ChecksumError, DecodeError, EncodeError, Error, InvalidCharacterError, LengthTooSmallError, UnsupportedDecodingTypeError, UnsupportedEncodingTypeError

Constant Summary collapse

ENCODED_BITS =

The number of bits encoded per symbol.

Since:

  • 1.0.0

0x05
CHECK_SYMBOL_MIN_VALUE =

The minimum value of a check symbol.

Since:

  • 1.0.0

0x20
CHECKSUM_PRIME =

The prime number used to implement error detection.

Since:

  • 1.0.0

0x25
DASH =

The ordinal value of an ASCII dash character.

Since:

  • 1.0.0

"-".ord.freeze
DECODE_ORDINALS =

Symbol values in order by encoded ASCII ordinal values.

Since:

  • 1.0.0

[
  nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
   34, nil, nil, nil, nil, nil,  32, nil, nil, nil, nil, nil,
    0,   1,   2,   3,   4,   5,   6,   7,   8,   9, nil, nil,
  nil,  35, nil, nil, nil,  10,  11,  12,  13,  14,  15,  16,
   17,   1,  18,  19,   1,  20,  21,   0,  22,  23,  24,  25,
   26,  36,  27,  28,  29,  30,  31, nil, nil, nil, nil, nil,
  nil,  10,  11,  12,  13,  14,  15,  16,  17,   1,  18,  19,
    1,  20,  21,   0,  22,  23,  24,  25,  26,  36,  27,  28,
   29,  30,  31, nil, nil, nil,  33
].freeze
ENCODE_SYMBOLS =

Encoding symbols ordered by bit value.

Since:

  • 1.0.0

[
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
  "G", "H", "J", "K", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z",
  "*", "~", "$", "=", "U"
].freeze
VERSION =

The library version.

Since:

  • 1.0.0

"1.1.0"

Public Methods collapse

Private Methods collapse

Class Method Details

.convert(result, type, length) ⇒ Integer, String (private)

Convert a decoded result into the destination type.

Parameters:

  • result (Integer)

    the decoded value.

  • type (Symbol)

    the destination type for the value. Can be :integer or :string.

  • length (Integer, nil)

    the length to pad the string to.

Returns:

  • (Integer, String)

    the decoded value converted to the destination type.

Since:

  • 1.0.0



124
125
126
127
128
129
130
131
132
133
# File 'lib/crockford32.rb', line 124

def self.convert(result, type, length)
  case type
  when :integer
    result
  when :string
    into_string result, length
  else
    raise UnsupportedDecodingTypeError.new(type)
  end
end

.decode(value, into: :integer, check: false, length: nil) ⇒ Integer, String

Decode a Base32 value.

Parameters:

  • value (String)

    the Base32 value to decode.

  • into (Symbol) (defaults to: :integer)

    the destination type to decode into. Can be :integer or :string.

  • check (Boolean) (defaults to: false)

    whether to validate the check symbol.

  • length (Integer, nil) (defaults to: nil)

    the length of the resulting string when right padded.

Returns:

  • (Integer, String)

    the decoded value.

Raises:

Since:

  • 1.0.0



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/crockford32.rb', line 65

def self.decode(value, into: :integer, check: false, length: nil)
  checksum = check ? value[-1] : nil
  value = check ? value[0...-1] : value

  result = le_decode_number value

  if check
    actual = result % CHECKSUM_PRIME
    required = DECODE_ORDINALS[checksum.ord]
    raise ChecksumError.new(value, actual, required) if actual != required
  end

  convert result, into, length
end

.encode(value, length: nil, check: false) ⇒ String

Encode a value as Base32.

Parameters:

  • value (Integer, String)

    the value to encode.

  • length (Integer) (defaults to: nil)

    the exact length of the Base32 string. Will be padded with “0” to meet length.

  • check (Boolean) (defaults to: false)

    whether to include a check symbol. This symbol is included in the length.

Returns:

  • (String)

    the encoded value.

Raises:

Since:

  • 1.0.0



92
93
94
# File 'lib/crockford32.rb', line 92

def self.encode(value, length: nil, check: false)
  le_encode_number(raw_value_to_number(value), length, check)
end

.into_string(result, length) ⇒ String (private)

Convert an Integer into a String.

Each 8-bit sequence is packed into a String in little-endian order.

Parameters:

  • result (Integer)

    the decoded value.

  • length (Integer, nil)

    the length of the decoded value with right padding.

Returns:

  • (String)

    the decoded value as a String.

Since:

  • 1.0.0



142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/crockford32.rb', line 142

def self.into_string(result, length)
  q, r = result.bit_length.divmod(0x08)
  q += 1 if r > 0
  bytes = Array.new(q)
  q.times do |i|
    bytes[i] = result & 0xff
    result >>= 0x08
  end

  bstr = bytes.pack("C*")
  return bstr if length.nil?

  bstr.ljust(length, "\x00")
end

.le_decode_number(encoded_value) ⇒ Integer (private)

Decode a value with the expectation that it is in little-endian order.

Parameters:

  • encoded_value (String)

    the value to decode.

Returns:

  • (Integer)

    the decoded value.

Since:

  • 1.0.0



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/crockford32.rb', line 105

def self.le_decode_number(encoded_value)
  symbol = -1
  bits = -ENCODED_BITS
  encoded_value.bytes.reduce(0) do |result, ch|
    symbol += 1
    next result if ch == DASH
    val = DECODE_ORDINALS[ch.ord]
    raise InvalidCharacterError.new(encoded_value, symbol) if val.nil? || val >= CHECK_SYMBOL_MIN_VALUE
    bits += ENCODED_BITS
    result | (val << bits)
  end
end

.le_encode_number(number, length, check) ⇒ String (private)

Encode an Integer as a Base32 value.

Returns:

  • (String)

See Also:

Since:

  • 1.0.0



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/crockford32.rb', line 216

def self.le_encode_number(number, length, check)
  result = +""
  n = number
  loop do
    chunk = n & 0x1F
    result << ENCODE_SYMBOLS[chunk]
    n >>= ENCODED_BITS
    break if n == 0
  end

  rlen = result.length + (check ? 1 : 0)
  if length
    raise LengthTooSmallError.new(number, rlen, length) if rlen > length
    result << "0" * (length - rlen)
  end

  result << ENCODE_SYMBOLS[number % CHECKSUM_PRIME] if check
  result.freeze
end

.raw_value_to_number(value) ⇒ Integer (private)

Convert a raw value into an Integer for encoding.

Parameters:

  • value (Integer, String)

    the value being encoded.

Returns:

  • (Integer)

    the value converte to an Integer.

Since:

  • 1.0.0



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/crockford32.rb', line 161

def self.raw_value_to_number(value)
  case value
  when String
    q, r = value.bytesize.divmod(8)
    if r == 0
      string_to_integer_unrolled value, q
    else
      string_to_integer value
    end
  when Integer
    value
  else
    raise UnsupportedEncodingTypeError.new value.class
  end
end

.string_to_integer(s) ⇒ Integer (private)

Convert a String to an Integer one byte per iteration.

Parameters:

  • s (String)

    the String to convert.

Returns:

  • (Integer)

    the String converted to an Integer in little-endian order.

Since:

  • 1.0.0



181
182
183
184
185
186
187
# File 'lib/crockford32.rb', line 181

def self.string_to_integer(s)
  shift = -0x08
  s.each_byte.reduce(0) do |n, b|
    shift += 0x08
    n + (b << shift)
  end
end

.string_to_integer_unrolled(s, iterations) ⇒ Integer (private)

Convert a String to an Integer 8 bytes per iteration.

Parameters:

  • s (String)

    the String to convert.

  • iterations (Integer)

    the number of iterations to perform.

Returns:

  • (Integer)

    the String converted to an Integer in little-endian order.

Since:

  • 1.0.0



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/crockford32.rb', line 193

def self.string_to_integer_unrolled(s, iterations)
  n = 0
  bytes = s.bytes
  while iterations > 0
    o = (iterations - 1) * 0x40
    i = iterations * 0x08
    n += bytes[i - 1] << (o + 0x38)
    n += bytes[i - 2] << (o + 0x30)
    n += bytes[i - 3] << (o + 0x28)
    n += bytes[i - 4] << (o + 0x20)
    n += bytes[i - 5] << (o + 0x18)
    n += bytes[i - 6] << (o + 0x10)
    n += bytes[i - 7] << (o + 0x08)
    n += bytes[i - 8] << (o + 0x00)
    iterations -= 1
  end
  n
end