# Common behavior for Class-InterDomain Routing (`
/`) notation under # {MetasploitDataModels::IPAddress}, module MetasploitDataModels::IPAddress::CIDR # so that translations for errors messages can be filed under metasploit_data_models/ip_address/cidr extend ActiveModel::Naming extend ActiveSupport::Concern # # CONSTANTS # # Separator between the {#address} and {#prefix_length} SEPARATOR = '/' # # Attributes # # @!attribute address # The IP address being masked by {#prefix_length} `1` bits. # # @return [Object] an instance of {address_class} attr_reader :address # @!attribute prefix_length # The significant number of bits in {#address}. # # @return [Integer] number of `1` bits in the netmask of {#address} attr_reader :prefix_length included do include ActiveModel::Validations # # # Validations # # # # Validation Methods # validate :address_valid # # Attribute Validations # validates :address, presence: true end # Class methods added to the including `Class`. module ClassMethods include MetasploitDataModels::Match::Child # # Attributes # # @!attribute address_class # The Class` whose instance are usd for {MetasploitDataModels::IPAddress::CIDR#address}. # # @return [Class] attr_reader :address_class # # Methods # # @note `address_class` must respond to `#segment_class` and `#segment_count` so {#maximum_prefix_length} can be # calculated. # # Sets up the address class and allowed {#maximum_prefix_length} for the including `Class`. # # @param options [Hash{Symbol => Class}] # @option options [Class, #segment_class, #segment_count] :address_class The `Class` whose instances will be used # for {#address}. def cidr(options={}) options.assert_valid_keys(:address_class) @address_class = options.fetch(:address_class) # # Validations # validates :prefix_length, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: maximum_prefix_length } end # Regular expression that matches a string that contains only a CIDR IP address. # # @return [Regexp] def match_regexp @match_regexp ||= /\A#{regexp}\z/ end # The maximum number of bits in a prefix for the {#address_class}. # # @return [Integer] the number of bits across all segments of {#address_class}. def maximum_prefix_length @maximum_prefix_length ||= address_class.segment_count * address_class.segment_class.bits end # Regular expression that matches a portion of string that contains a CIDR IP address. # # @return [Regexp] def regexp @regexp ||= /(?
#{address_class.regexp})#{Regexp.escape(SEPARATOR)}(?\d+)/ end end # # Instance Methods # # Set {#address}. # # @param formatted_address [#to_s] def address=(formatted_address) @address = self.class.address_class.new(value: formatted_address) end # Set {#prefix_length}. # # @param formatted_prefix_length [#to_s] def prefix_length=(formatted_prefix_length) @prefix_length_before_type_cast = formatted_prefix_length begin # use Integer() instead of String#to_i as String#to_i will ignore trailing letters (i.e. '1two' -> 1) and turn all # string without an integer in it to 0. @prefix_length = Integer(formatted_prefix_length.to_s) rescue ArgumentError @prefix_length = formatted_prefix_length end end # The formatted_prefix_length passed to {#prefix_length=} # # @return [#to_s] def prefix_length_before_type_cast @prefix_length_before_type_cast end # Parses the `formatted_value` into an {#address} and {#prefix_length}. # # @param formatted_value [#to_s] def value=(formatted_value) formatted_address, formatted_prefix_length = formatted_value.to_s.split(SEPARATOR, 2) self.address = formatted_address self.prefix_length = formatted_prefix_length [address, prefix_length] end private # Validates that {#address} is valid. # # @return [void] def address_valid if address && !address.valid? errors.add(:address, :invalid) end end end