duse-io/secret_sharing_ruby

View on GitHub
lib/secret_sharing/charset.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "set"

module SecretSharing
  # Charset can represent any charset which does not have the null byte
  class Charset
    class NotInCharset < ArgumentError; end
    class NotPositiveInteger < ArgumentError; end

    # @return [Array] internal representation of the charset
    attr_reader :charset

    # The "null-byte" character is prepended to be the first character in the
    # charset to avoid loosing the first character of the charset when it is
    # also the first character in a string to convert.
    #
    # Example
    #
    #   SecretSharing::Charset::DynamicCharset.new ["a", "b", "c"] # =>
    #   #<SecretSharing::Charset::DynamicCharset @charset=[...]>
    #
    # @param charset [Array] array of characters to use for the charset.
    def initialize(charset)
      @charset = ["\u0000"] + charset
      @charset.freeze
    end

    # Calculate a string from an integer.
    #
    # Example
    #
    #   charset = SecretSharing::Charset.by_charset_string "abc"
    #   charset.i_to_s 6
    #   # => "ab"
    #
    # @param input [Integer] integer to convert to string
    # @return [String] converted string
    def i_to_s(input)
      if !input.is_a?(Integer) || input < 0
        fail NotPositiveInteger, "input must be a non-negative integer"
      end

      output = ""
      while input > 0
        input, codepoint = input.divmod(charset.length)
        output.prepend(codepoint_to_char(codepoint))
      end
      output
    end

    # Calculate an integer from a string.
    #
    # Example
    #
    #   charset = SecretSharing::Charset.by_charset_string "abc"
    #   charset.s_to_i "ab"
    #   # => 6
    #
    # @param string [Integer] integer to convert to string
    # @return [String] converted string
    def s_to_i(string)
      string.chars.reduce(0) do |output, char|
        output * charset.length + char_to_codepoint(char)
      end
    end

    # Convert an integer into its string representation according to the
    # charset. (only one character)
    #
    # Example
    #
    #   charset = SecretSharing::Charset.by_charset_string "abc"
    #   charset.codepoint_to_char 1
    #   # => "a"
    #
    # @param codepoint [Integer] Codepoint to retrieve the character for
    # @return [String] Retrieved character
    def codepoint_to_char(codepoint)
      if charset.at(codepoint).nil?
        fail NotInCharset, "Codepoint #{codepoint} does not exist in charset"
      end
      charset.at(codepoint)
    end

    # Convert a single character into its integer representation according to
    # the charset.
    #
    # Example
    #
    #   charset = SecretSharing::Charset.by_charset_string "abc"
    #   charset.char_to_codepoint "a"
    #   # => 1
    #
    # @param c [String] Character to retrieve its codepoint in the charset
    # @return [Integer] Codepoint within the charset
    def char_to_codepoint(c)
      codepoint = charset.index c
      if codepoint.nil?
        fail NotInCharset, "Char \"#{c}\" not part of the supported charset"
      end
      codepoint
    end

    # Check if the provided string can be represented by the charset.
    #
    # Example
    #
    #   charset = SecretSharing::Charset.by_charset_string "abc"
    #   charset.subset? "d"
    #   # => false
    #   charset.subset? "a"
    #   # => true
    #
    # @param string [String] Character to retrieve the for codepoint
    # @return [TrueClass|FalseClass]
    def subset?(string)
      (Set.new(string.chars) - Set.new(charset)).empty?
    end

    # Charset that can represent any string that only consists of ASCII
    # characters.
    ASCIICharset = new((1..127).to_a.map(&:chr))
  end
end