Class: Multibases::BaseX

Inherits:
Object
  • Object
show all
Defined in:
lib/multibases/base_x.rb

Defined Under Namespace

Classes: Table

Instance Method Summary collapse

Constructor Details

#initialize(alphabet, strict: false, encoding: nil) ⇒ BaseX

Returns a new instance of BaseX.



26
27
28
# File 'lib/multibases/base_x.rb', line 26

def initialize(alphabet, strict: false, encoding: nil)
  @table = Table.from(alphabet, strict: strict, encoding: encoding)
end

Instance Method Details

#decodable?(encoded) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/multibases/base_x.rb', line 125

def decodable?(encoded)
  (encoded.uniq - @table.tr_ords).length.zero?
end

#decode(encoded) ⇒ DecodedByteArray

Decode encoded to a byte array

Parameters:

  • encoded (String, Array, ByteArray)

    encoded string or byte array

Returns:



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
# File 'lib/multibases/base_x.rb', line 83

def decode(encoded)
  return DecodedByteArray::EMPTY if encoded.empty?

  unless encoded.is_a?(Array)
    encoded = encoded.force_encoding(@table.encoding).bytes
  end

  unless decodable?(encoded)
    raise ArgumentError, "'#{encoded}' contains unknown characters'"
  end

  # Find leading zeroes
  zeroes_count = [
    0,
    encoded.find_index { |b| b.ord != @table.zero } || encoded.length
  ].max
  encoded = encoded.drop(zeroes_count)

  # Decode number from encoding base to base 10
  encoded_big_number = 0

  encoded.reverse.each_with_index do |char, i|
    table_i = @table.index(char)
    encoded_big_number += @table.base**i * table_i
  end

  # Build the output by reversing the bytes. Because the encoding is "lost"
  # the result might not be correct just yet. This is up to the caller to
  # fix. The algorithm **can not know** what the encoding was.
  output = 1.upto((Math.log2(encoded_big_number) / 8).ceil).collect do
    encoded_big_number, character_byte = encoded_big_number.divmod 256
    character_byte
  end.reverse

  # Prepend the leading zeroes
  @table.decoded_zeroes_length(zeroes_count).times do
    output.unshift(0x00)
  end

  DecodedByteArray.new(output)
end

#encode(plain) ⇒ EncodedByteArray

Encode plain to an encoded string

Parameters:

  • plain (String, Array)

    plain string or byte array

Returns:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/multibases/base_x.rb', line 36

def encode(plain)
  return EncodedByteArray::EMPTY if plain.empty?

  plain = plain.bytes unless plain.is_a?(Array)
  expected_length = @table.encoded_length(plain)

  # Find leading zeroes
  zeroes_count = [
    0,
    plain.find_index { |b| b.ord != 0 } || plain.length
  ].max
  plain = plain.drop(zeroes_count)
  expected_length = @table.encoded_length(plain) unless @table.pad_to_power?

  # Encode number into destination base as byte array
  output = []
  plain_big_number = plain.inject { |a, b| (a << 8) + b.ord }

  while plain_big_number >= @table.base
    mod = plain_big_number % @table.base
    output.unshift(@table.ord_at(mod))
    plain_big_number = (plain_big_number - mod) / @table.base
  end

  output.unshift(@table.ord_at(plain_big_number))

  # Prepend the leading zeroes
  @table.encoded_zeroes_length(zeroes_count).times do
    output.unshift(@table.zero)
  end

  # Padding at the front (to match expected length). Because of the
  if @table.pad_to_power?
    (expected_length - output.length).times do
      output.unshift(@table.zero)
    end
  end

  EncodedByteArray.new(output, encoding: @table.encoding)
end

#inspectObject



8
9
10
11
12
13
# File 'lib/multibases/base_x.rb', line 8

def inspect
  "[Multibases::Base#{@table.base} " \
    "alphabet=\"#{@table.alphabet}\"" \
    "#{@table.strict? ? ' strict' : ''}" \
  ']'
end