class FFXCodec::Encoder

Encode two integers into one larger integer and decode back

Attributes

a_max[R]

@return [Fixnum] maximum unsigned value representable by left integer

b_max[R]

@return [Fixnum] maximum unsigned value representable by right integer

size[R]

@return [Fixnum] size of encoded integer in bits (32 or 64)

Public Class Methods

new(a_size = 32, b_size = 32) click to toggle source

@param [Fixnum] a_size the number of bits allocated to the left integer @param [Fixnum] b_size the number of bits allocated to the right integer

# File lib/ffxcodec/encoder.rb, line 15
def initialize(a_size = 32, b_size = 32)
  @a_size = a_size
  @b_size = b_size
  @a_max, @b_max = maximums(a_size, b_size)
  @size = a_size + b_size
  check_size
end

Public Instance Methods

decode(c) click to toggle source

Separate an unsigned integer into two smaller unsigned integers

@example Decode an encoded 64-bit integer into 40 and 24-bit integers

i = Encoder.new(40, 24)
i.decode(20712612157194244)  #=> [1234567890, 4]

@param [Fixnum, Bignum] c encoded value to decode @return [Array<Fixnum>] decoded integers

# File lib/ffxcodec/encoder.rb, line 47
def decode(c)
  i = interlace(c)
  a = i >> @b_size
  b = (i ^ (a << @b_size))
  [a, b]
end
encode(a, b) click to toggle source

Combine two unsigned integers into a single, larger unsigned integer

@param [Fixnum] a value to encode @param [Fixnum] b value to encode

@example Encode 40 and 24-bit integers into a single 64-bit integer

i = Encoder.new(40, 24)
i.encode(1234567890, 4)      #=> 20712612157194244

@return [Fixnum, Bignum] encoded integer

# File lib/ffxcodec/encoder.rb, line 33
def encode(a, b)
  check_ab_bounds(a, b)
  i = (a << @b_size) ^ b
  interlace(i)
end

Private Instance Methods

check_ab_bounds(a, b) click to toggle source

@param [Fixnum] a left integer to be encoded @param [Fixnum] b right integer to be encoded @raise [ArgumentError] if the given values fall outside our maximums @return [void]

# File lib/ffxcodec/encoder.rb, line 105
def check_ab_bounds(a, b)
  if a > @a_max || a < 0
    fail ArgumentError, "LHS #{@a_size}-bit value out of bounds: #{a}"
  elsif b > @b_max || b < 0
    fail ArgumentError, "RHS #{@b_size}-bit value out of bounds: #{b}"
  end
end
check_size() click to toggle source

@raise [ArgumentError] if the combined bit count isn’t 32 or 64 bits @return [void]

# File lib/ffxcodec/encoder.rb, line 115
def check_size
  return if @size == 32 || @size == 64
  fail ArgumentError, "Combined size must be 32 or 64 bits"
end
interlace(n) click to toggle source

Interlace / deinterlace bytes

Running this on a number toggles interlacing (a reorder of the bits).

This reorders the bytes in an integer. We do this to avoid a possible reduction in the effectiveness of our encryption resulting from the fact that we encode input by concatenating the bits of two potentially equally sized integers and the way a maximally-balanced Feistel splits the input in half.

Technically we only need to do this when encryption is enabled and the integer is evenly divided. However, this imparts almost no performance penalty. Ruby can perform ~1 million of these operations in 2 sec for random 64-bit values and 0.5 sec for random 32-bit values.

@param [Fixnum, Bignum] n interlaced or uninterlaced value @return [Fixnum, Bignum] interlaced value if input wasn’t interlaced @return [Fixnum, Bignum] deinterlaced value if input was interlaced rubocop:disable AbcSize

# File lib/ffxcodec/encoder.rb, line 75
def interlace(n)
  # rubocop:disable SpaceAroundOperators, MultilineOperationIndentation
  if @size == 32
    n        & 0xFF0000FF | # 0, 3
    (n >> 8) & 0x0000FF00 | # 1
    (n << 8) & 0x00FF0000   # 2
  else
    n         & 0xFF00FF0000FF00FF | # 0, 2, 5, 7
    (n >> 40) & 0x000000000000FF00 | # 1
    (n >> 8)  & 0x00000000FF000000 | # 3
    (n << 8)  & 0x000000FF00000000 | # 4
    (n << 40) & 0x00FF000000000000   # 6
  end
end
maximums(a_size, b_size) click to toggle source

Calculate the maximum values representable in the given number of bits

@param [Fixnum] a_size number of bits allocated to left integer @param [Fixnum] b_size number of bits allocated to right integer @return [Array<Fixnum>] maximum representable values for each integer

# File lib/ffxcodec/encoder.rb, line 95
def maximums(a_size, b_size)
  a_max = (1 << a_size) - 1
  b_max = (1 << b_size) - 1
  [a_max, b_max]
end