lib/rasn1/model.rb in rasn1-0.1.0 vs lib/rasn1/model.rb in rasn1-0.2.0

- old
+ new

@@ -38,45 +38,98 @@ # == Create a more complex model # Models may be nested. For example: # class Record2 < RASN1::Model # sequence(:record2, # content: [boolean(:rented, default: false), - # Record]) + # model(:a_record, Record)]) # end # Set values like this: # record2 = Record2.new # record2[:rented] = true - # record2[:record][:id] = 65537 - # record2[:record][:room] = 43 + # record2[:a_record][:id] = 65537 + # record2[:a_record][:room] = 43 # or like this: - # record2 = Record2.new(rented: true, record: { id: 65537, room: 43 }) + # record2 = Record2.new(rented: true, a_record: { id: 65537, room: 43 }) # @author Sylvain Daubert class Model class << self - # Define a SEQUENCE type. Should be used to define a new subclass of {Model}. - # @param [Hash] options - # @option options [Array] :content - # @see Types::Sequence#initialize - # @return [Types::Sequence] - def sequence(name, options={}) - @records ||= {} - @records[name] = Proc.new do - seq = Types::Sequence.new(name, options) - seq.value = options[:content] if options[:content] - seq - end + # Use another model in this model + # @param [String,Symbol] name + # @param [Class] model_klass + def model(name, model_klass) + @root = [name, model_klass] end - # define all class methods to instance a ASN.1 TAG + # @method sequence(name, options) + # @see Types::Sequence#initialize + # @method set(name, options) + # @see Types::Set#initialize + # @method choice(name, options) + # @see Types::Choice#initialize + %w(sequence set choice).each do |type| + class_eval "def #{type}(name, options={})\n" \ + " proc = Proc.new do\n" \ + " Types::#{type.capitalize}.new(name, options)\n" \ + " end\n" \ + " @root = [name, proc]\n" \ + " @root << options[:content] unless options[:content].nil?\n" \ + " @root\n" \ + "end" + end + + # @method sequence_of(name, type, options) + # @see Types::SequenceOf#initialize + # @method set_of(name, type, options) + # @see Types::SetOf#initialize + %w(sequence set).each do |type| + klass_name = "Types::#{type.capitalize}Of" + class_eval "def #{type}_of(name, type, options={})\n" \ + " proc = Proc.new do\n" \ + " #{klass_name}.new(name, type, options)\n" \ + " end\n" \ + " @root = [name, proc]\n" \ + "end" + end + + # @method boolean(name, options) + # @see Types::Boolean#initialize + # @method integer(name, options) + # @see Types::Integer#initialize + # @method bit_string(name, options) + # @see Types::BitString#initialize + # @method octet_string(name, options) + # @see Types::OctetString#initialize + # @method null(name, options) + # @see Types::Null#initialize + # @method enumerated(name, options) + # @see Types::Enumerated#initialize + # @method utf8_string(name, options) + # @see Types::Utf8String#initialize Types.primitives.each do |prim| + next if prim == Types::ObjectId class_eval "def #{prim.type.downcase.gsub(/\s+/, '_')}(name, options={})\n" \ - " Proc.new { #{prim.to_s}.new(name, options) }\n" \ + " proc = Proc.new { #{prim.to_s}.new(name, options) }\n" \ + " @root = [name, proc]\n" \ "end" end + # @note This method is named +objectid+ and not +object_id+ to not override + # +Object#object_id+. + # @see Types::ObjectId#initialize + def objectid(name, options={}) + proc = Proc.new { Types::ObjectId.new(name, options) } + @root = [name, proc] + end + + # @see Types::Any#initialize + def any(name, options={}) + proc = Proc.new { Types::Any.new(name, options) } + @root = [name, proc] + end + # Parse a DER/BER encoded string # @param [String] str # @param [Boolean] ber accept BER encoding or not # @return [Model] def parse(str, ber: false) @@ -87,11 +140,12 @@ end # Create a new instance of a {Model} # @param [Hash] args def initialize(args={}) - set_elements + root = generate_root + set_elements *root initialize_elements self, args end # Give access to element +name+ in model # @param [String,Symbol] name @@ -104,10 +158,16 @@ # @return [String,Symbol] def name @elements[@root].name end + # Get elements names + # @return [Array<Symbol,String>] + def keys + @elements.keys + end + # Return a hash image of model # @return [Hash] def to_h { @root => private_to_h(@elements[@root]) } end @@ -115,83 +175,100 @@ # @return [String] def to_der @elements[@root].to_der end + # Get root element from model + # @return [Types::Base,Model] + def root + @elements[@root] + end + # Parse a DER/BER encoded string, and modify object in-place. # @param [String] str # @param [Boolean] ber accept BER encoding or not # @return [Integer] number of parsed bytes def parse!(str, ber: false) - @elements[@root].parse!(str, ber: ber) + @elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber) end private - def set_elements(element=nil) - if element.nil? - records = self.class.class_eval { @records } - @root = records.keys.first - @elements = {} - @elements[@root] = records[@root].call - if @elements[@root].value.is_a? Array - @elements[@root].value = @elements[@root].value.map do |subel| - se = case subel - when Proc - subel.call - when Class - subel.new - end - @elements[se.name] = se - set_elements se if se.is_a? Types::Sequence - se + def is_composed?(el) + [Types::Sequence, Types::Set].include? el.class + end + + def is_of?(el) + [Types::SequenceOf, Types::SetOf].include? el.class + end + + def get_type(proc_or_class, name=nil) + case proc_or_class + when Proc + proc_or_class.call + when Class + proc_or_class.new + end + end + + def generate_root + root = self.class.class_eval { @root } + @root = root[0] + @elements = {} + @elements[@root] = get_type(root[1]) + root + end + + def set_elements(name, el, content=nil) + if content.is_a? Array + @elements[name].value = content.map do |name2, proc_or_class, content2| + subel = get_type(proc_or_class, name2) + @elements[name2] = subel + if is_composed?(subel) and content2.is_a? Array + set_elements(name2, proc_or_class, content2) end + subel end - else - element.value.map! do |subel| - se = case subel - when Proc - subel.call - when Class - subel.new - end - @elements[se.name] = se - set_elements se if se.is_a? Types::Sequence - se - end end end def initialize_elements(obj, args) args.each do |name, value| if obj[name] if value.is_a? Hash if obj[name].is_a? Model initialize_elements obj[name], value else - raise ArgumentError, "element #{name}: may only pass a Hash for Sequence elements" + raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" end else obj[name].value = value end end end end def private_to_h(element) - h = {} - element.value.each do |subel| - h[subel.name] = case subel - when Types::Sequence - private_to_h(subel) - when Model - subel.to_h[subel.name] - else - next if subel.value.nil? and subel.optional? - subel.value - end + if element.value.is_a? Array + h = {} + element.value.each do |subel| + case subel + when Types::Sequence, Types::Set + h[subel.name] = private_to_h(subel) + when Model + h[@elements.key(subel)] = subel.to_h[subel.name] + when Hash, Array + # Array of Hash for SequenceOf and SetOf + # Array of Array of... of Hash are nested SequenceOf or SetOf + return element.value + else + next if subel.value.nil? and subel.optional? + h[subel.name] = subel.value + end + end + h + else + element.value end - - h end end end