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