Class: Junkfood::Base32

Inherits:
Object
  • Object
show all
Defined in:
lib/junkfood/base32.rb

Overview

Base32 (RFC4668/3548) encodes and decodes strings of data.

Requires at least Ruby 1.9

Constant Summary collapse

ALPHABET =

The Base32 alphabet, all caps.

'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
ALPHABET_DOWNCASE =

The Base32 alphabet, all lowercase.

ALPHABET.downcase
BYTE_MAP =

Once populated, this is Base32 alphabet to byte mapping.

{}
IGNORED =

Spacer characters to ignore when parsing a Base32 encoded string.

"\r\n-_\s".bytes.to_a
SPLITS =

The hash of available break strings that can be inserted between X number of Base32 characters (during the encoding process).

{
  dash: '-', 
  newline: "\n",
  space: ' ',
  underscore: '_'
}.freeze

Class Method Summary collapse

Class Method Details

.decode(input, options = {}) ⇒ Object

Base32 decodes the input object and writes to the output io object.

Parameters:

  • input (#each_byte)
  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :output (#putc) — default: StringIO.new

    of the output IO. before inserting a split character.

Returns:

  • IO, StringIO instance of object to which decoded data was written.

Raises:

  • Base32DecodeError



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/junkfood/base32.rb', line 175

def self.decode(input, options={})
  output = options[:output] || StringIO.new(''.force_encoding('BINARY'))
  buffer = 0
  bits_left = 0
  input.each_byte do |byte|
    next if IGNORED.include? byte
    raise Base32DecodeError.new("Invalid input byte: #{byte}") unless(
      BYTE_MAP.key? byte)
    buffer = (buffer << 5) | BYTE_MAP[byte]
    bits_left += 5
    if bits_left >= 8
      bits_left -= 8
      output.putc(buffer >> bits_left)
      buffer &= (2 ** bits_left - 1)
    end
  end
  # We ignore remaining bits in the buffer in cases where there is an
  # incomplete Base32 quantum (ie, the number of characters are unaligned
  # with the 40-bit boundries).

  return output
end

.encode(input, options = {}) ⇒ Object

Base32 encodes the input object and writes to the output io object.

Parameters:

  • input (#each_byte)
  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :output (#putc) — default: StringIO.new

    of the output IO.

  • :split (String)

    :dash, :newline, :space, or :underscore

  • :split_length (Fixnum) — default: 79

    number of Base32 characters before inserting a split character.

Returns:

  • IO, StringIO instance of object to which encoded data was written.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/junkfood/base32.rb', line 76

def self.encode(input, options={})
  output = options[:output] || StringIO.new(''.force_encoding('US-ASCII'))
  alphabet = options[:use_downcase] ? ALPHABET_DOWNCASE : ALPHABET

  split = SPLITS[options[:split]]
  split_length = options[:split_length] || 79

  # Set up the lambda that does the actual work of writing the
  # quintet to the output stream.
  if split
    # This lambda will split up the output stream with a "break" character
    # for every N quintets (where N is the split_length).
    split_count = 0
    write = lambda do |quintet|
      output.putc alphabet.getbyte(quintet)
      split_count += 1
      if split_count >= split_length
        output.putc split 
        split_count = 0
      end
    end
  else
    # This lambda just writes out the quintets
    write = lambda do |quintet|
      output.putc alphabet.getbyte(quintet)
    end
  end

  position = 0
  buffer = 0
  input.each_byte do |byte|
    case position
    when 0
      # Current Buffer: 0 bits from previous byte
      # Quintet: 5 bits from current byte
      # New Buffer: Lowest 3 bits of current byte
      write.call(byte >> 3)
      buffer = byte & 0x07
    when 1
      # Current Buffer: 3 bits from previous byte
      # Quintet 1: 3 bits of buffer and first 2 bits of current byte
      # Quintet 2: next 5 bits of byte
      # New Buffer: Lowest 1 bit of current byte
      write.call((buffer << 2) | (byte >> 6))
      write.call((byte >> 1) & 0x1f)
      buffer = byte & 0x01
    when 2
      # Current Buffer: 1 bits from previous byte
      # Quintet 1: 1 bits of buffer and 4 bits of current byte
      # New Buffer: Lowest 4 bits of current byte
      write.call((buffer << 4) | (byte >> 4))
      buffer = byte & 0x0f
    when 3
      # Current Buffer: 4 bits from previous byte
      # Quintet 1: 4 bits of buffer and top bit of byte
      # Quintet 2: next 5 bits of byte
      # New Buffer: bottom 2 bit of byte
      write.call((buffer << 1) | (byte >> 7))
      write.call((byte >> 2) & 0x1f)
      buffer = byte & 0x03
    when 4
      # Current Buffer: 2 bits from previous byte
      # Quintet 1: 2 bits of buffer and top 3 bits of byte
      # Quintet 2: bottom 5 bits of byte
      write.call((buffer << 3) | (byte >> 5))
      write.call(byte & 0x1f) 
      buffer = 0
    end
    position = (position + 1) % 5
  end
  case position
  when 0
    # We are 40-bit aligned, so nothing to do.
  when 1
    # 3 bits in buffer
    write.call(buffer << 2)
  when 2
    # 1 bit in buffer
    write.call(buffer << 4)
  when 3
    # 4 bits in buffer
    write.call(buffer << 1)
  when 4
    # 2 bits in buffer
    write.call(buffer << 3)
  end

  return output
end