module Sixword::Lib
The Lib
module contains various internal utility functions. They are not really part of the public API and will probably not be useful to external callers.
Public Class Methods
Given an array of bytes, pack them into a single Integer.
@example
>> byte_array_to_int([1, 2]) => 258
@param byte_array [Array<Fixnum>]
@return [Integer]
# File lib/sixword/lib.rb, line 218 def self.byte_array_to_int(byte_array) int = 0 byte_array.each do |byte| int <<= 8 int |= byte end int end
Decode an array of 6 words into a 64-bit integer (representing 8 bytes).
@param word_array [Array<String>] A 6 element array of String words @param padding_ok [Boolean]
@return [Array(Integer, Integer)] a 64-bit integer (the data) and the length of the byte array that it represents (will always be 8 unless padding_ok)
@example
>> Sixword::Lib.decode_6_words(%w{COAT ACHE A A A ACT6}, true) => [26729, 2] >> Sixword::Lib.decode_6_words(%w{ACRE ADEN INN SLID MAD PAP}, false) => [5217737340628397156, 8]
# File lib/sixword/lib.rb, line 63 def self.decode_6_words(word_array, padding_ok) unless word_array.length == 6 raise ArgumentError.new("Must pass a six-word array") end bits_array = [] padding = 0 # extract padding, if any if padding_ok && word_array[-1][-1] =~ /[1-7]/ word, padding = extract_padding(word_array[-1]) word_array[-1] = word end bits_array = word_array.map {|w| word_to_bits(w) } bits_array.each do |bits| if bits >= 2048 || bits < 0 raise RuntimeError.new("Somehow got bits of #{bits.inspect}") end end int = 0 (0..4).each do |i| int <<= 11 int += bits_array.fetch(i) end # slice out parity from last word parity = bits_array.fetch(5) & 0b11 int <<= 9 int += bits_array.fetch(5) >> 2 # check parity unless parity_int(int) == parity raise InvalidParity.new("Parity bits do not match: " + word_array.join(' ').inspect) end # omit padding bits, if any int >>= padding * 8 [int, 8 - padding] end
Decode an array of 6 words into a String of bytes.
@param word_array [Array<String>] A 6 element array of String words @param padding_ok [Boolean]
@return [String]
@see Sixword.decode_6_words @see Sixword.int_to_byte_array
@example
>> Lib.decode_6_words_to_bstring(%w{COAT ACHE A A A ACT6}, true) => "hi" >> Lib.decode_6_words_to_bstring(%w{ACRE ADEN INN SLID MAD PAP}, false) => "Hi world"
# File lib/sixword/lib.rb, line 143 def self.decode_6_words_to_bstring(word_array, padding_ok) int_to_byte_array(*decode_6_words(word_array, padding_ok)). map(&:chr).join end
Encode an array of 8 bytes as an array of 6 words.
@param byte_array [Array<Fixnum>] An array of length 8 containing
integers in 0..255
@return [Array<String>] An array of length 6 containing String words from
{Sixword::WORDS}
@example
>> Sixword::Lib.encode_64_bits([0] * 8) => ["A", "A", "A", "A", "A", "A"]
@example
>> Sixword::Lib.encode_64_bits([0xff] * 8) => ["YOKE", "YOKE", "YOKE", "YOKE", "YOKE", "YEAR"]
# File lib/sixword/lib.rb, line 24 def self.encode_64_bits(byte_array) unless byte_array.length == 8 raise ArgumentError.new("Must pass an 8-byte array") end int = byte_array_to_int(byte_array) parity_bits = parity_int(int) encoded = Array.new(6) last_index = ((int & 511) << 2) | parity_bits encoded[5] = WORDS.fetch(last_index).dup int >>= 9 4.downto(0) do |i| encoded[i] = WORDS.fetch(int & 2047).dup int >>= 11 end encoded end
Extract the numeric padding from a word.
@param word [String] @return [Array(String, Integer)] The String word, the Integer padding
@example
>> Sixword::Lib.extract_padding("WORD3") => ["WORD", 3]
# File lib/sixword/lib.rb, line 118 def self.extract_padding(word) unless word[-1] =~ /[1-7]/ raise ArgumentError.new("Not a valid padded word: #{word.inspect}") end return word[0...-1], Integer(word[-1]) end
Given an Integer, unpack it into an array of bytes.
@example
>> int_to_byte_array(258) => [1, 2]
@example
>> int_to_byte_array(258, 3) => [0, 1, 2]
@param int [Integer] @param length [Integer] Left zero padded size of byte array to return. If
not provided, no leading zeroes will be added.
@return [Array<Fixnum>]
# File lib/sixword/lib.rb, line 243 def self.int_to_byte_array(int, length=nil) unless int >= 0 raise ArgumentError.new("Not sure what to do with negative numbers") end arr = [] while int > 0 arr << (int & 255) int >>= 8 end # pad to appropriate length with leading zeroes if length raise ArgumentError.new("Cannot pad to length < 0") if length < 0 while arr.length < length arr << 0 end end arr.reverse! arr end
Compute two-bit parity on a byte array by summing each pair of bits. TODO: figure out which is faster
@param byte_array [Array<Fixnum>] @return [Fixnum] An integer 0..3
@see parity_int
# File lib/sixword/lib.rb, line 173 def self.parity_array(byte_array) # sum pairs of bits through the whole array parity = 0 byte_array.each do |byte| while byte > 0 parity += byte & 0b11 byte >>= 2 end end # return the least significant two bits parity & 0b11 end
Compute two-bit parity on a 64-bit integer representing an 8-byte array by summing each pair of bits. TODO: figure out which is faster
@param int [Integer] A 64-bit integer representing 8 bytes @return [Fixnum] An integer 0..3
@see parity_array
# File lib/sixword/lib.rb, line 197 def self.parity_int(int) parity = 0 while int > 0 parity += int & 0b11 int >>= 2 end parity & 0b11 end
Given a word, return the 11 bits it represents as an integer (i.e. its index in the WORDS list).
@param word [String] @return [Fixnum] An integer 0..2047
# File lib/sixword/lib.rb, line 154 def self.word_to_bits(word) word = word.upcase return WORDS_HASH.fetch(word) rescue KeyError if (1..4).include?(word.length) raise UnknownWord.new("Unknown word: #{word.inspect}") else raise InvalidWord.new("Word must be 1-4 chars, not #{word.inspect}") end end