# frozen_string_literal: true module RASN1 module Types # ASN.1 Integer # @author Sylvain Daubert class Integer < Primitive # @return [Hash,nil] attr_reader :enum # Integer id value ID = 2 # @option options [Hash] :enum enumeration hash. Keys are names, and values # are integers. # @raise [EnumeratedError] +:default+ value is unknown when +:enum+ key is present # @see Base#initialize common options to all ASN.1 types def initialize(options={}) super initialize_enum(@options[:enum]) end # @param [Integer,String,Symbol,nil] v # @return [void] def value=(val) @no_value = false case val when String, Symbol value_from_string_or_symbol(val) when ::Integer value_from_integer(val) when nil @no_value = true @value = void_value else raise EnumeratedError, "#{@name}: not in enumeration" end end # @return [Integer] def void_value 0 end # Integer value # @return [Integer] def to_i if @enum.empty? value? ? @value : @default || 0 else @enum[value? ? @value : @default] || 0 end end private def initialize_enum(enum) @enum = enum || {} return if @enum.empty? # To ensure @value has the correct type self.value = @value if value? check_enum_default end def check_enum_default case @default when String, Symbol raise EnumeratedError, "#{@name}: unknwon enumerated default value #@{default}" unless @enum.key?(@default) when ::Integer raise EnumeratedError, "#{@name}: default value #@{default} not in enumeration" unless @enum.value?(@default) @default = @enum.key(@default) when nil else raise TypeError, "#{@name}: #{@default.class} not handled as default value" end end def value_from_string_or_symbol(val) raise EnumeratedError, "#{@name} has no :enum" if @enum.empty? raise EnumeratedError, "#{@name}: unknwon enumerated value #{val}" unless @enum.key? val @value = val end def value_from_integer(val) if @enum.empty? @value = val elsif @enum.value? val @value = @enum.key(val) else raise EnumeratedError, "#{@name}: #{val} not in enumeration" end end def int_value_to_der(value) size = compute_size(value) comp_value = value >= 0 ? value : (~(-value) + 1) & ((1 << (size * 8)) - 1) ary = comp_value.digits(256) # v is > 0 and its MSBit is 1. Add a 0 byte to mark it as positive ary << 0 if value.positive? && (value >> (size * 8 - 1) == 1) ary.reverse.pack('C*') end def compute_size(value) size = value.bit_length / 8 + ((value.bit_length % 8).positive? ? 1 : 0) size = 1 if size.zero? size end def value_to_der case @value when String, Symbol int_value_to_der(@enum[@value]) when ::Integer int_value_to_der(@value) else raise TypeError, "#{@name}: #{@value.class} not handled" end end def der_to_int_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument ary = der.unpack('C*') v = ary.reduce(0) { |len, b| (len << 8) | b } v = -((~v & ((1 << v.bit_length) - 1)) + 1) if ary[0] & 0x80 == 0x80 v end def der_to_value(der, ber: false) @value = der_to_int_value(der, ber: ber) return if @enum.empty? int_value = @value raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless @enum.value?(@value) @value = @enum.key(@value) end def explicit_type self.class.new(name: @name, enum: @enum) end end end end