class RASN1::Model
@abstract {Model} class is a base class to define ASN.1 models.
Create a simple ASN.1 model¶ ↑
Given this ASN.1 example:
Record ::= SEQUENCE { id INTEGER, room [0] IMPLICIT INTEGER OPTIONAL, house [1] EXPLICIT INTEGER DEFAULT 0 }
you may create your model like this:
class Record < RASN1::Model sequence(:record, content: [integer(:id), integer(:room, implicit: 0, optional: true), integer(:house, explicit: 1, default: 0)]) end
Parse a DER-encoded string¶ ↑
record = Record.parse(der_string) record[:id] # => RASN1::Types::Integer record[:id].value # => Integer record[:id].to_i # => Integer record[:id].asn1_class # => Symbol record[:id].optional? # => false record[:id].default # => nil record[:room].optional # => true record[:house].default # => 0
You may also parse a BER-encoded string this way:
record = Record.parse(der_string, ber: true)
Generate a DER-encoded string¶ ↑
record = Record.new(id: 12, room: 24) record.to_der
Create a more complex model¶ ↑
Models may be nested. For example:
class Record2 < RASN1::Model sequence(:record2, content: [boolean(:rented, default: false), model(:a_record, Record)]) end
Set values like this:
record2 = Record2.new record2[:rented] = true record2[:a_record][:id] = 65537 record2[:a_record][:room] = 43
or like this:
record2 = Record2.new(rented: true, a_record: { id: 65537, room: 43 })
Delegation¶ ↑
{Model} may delegate some methods to its root element. Thus, if root element is, for example, a {Types::Choice}, model may delegate #chosen
and #chosen_value
.
All methods defined by root may be delegated by model, unless model also defines this method. @author Sylvain Daubert
Attributes
@return [Hash]
Public Class Methods
@param [Symbol,String] name name of object in model @param [Hash] options @see Types::Any#initialize
# File lib/rasn1/model.rb, line 215 def any(name, options={}) options[:name] = name proc = proc { |opts| Types::Any.new(options.merge(opts)) } @root = [name, proc] end
On inheritance, create +@root+ class variable @param [Class] klass @return [void]
# File lib/rasn1/model.rb, line 93 def inherited(klass) super root = @root klass.class_eval { @root = root } end
Use another model in this model @param [String,Symbol] name @param [Class] model_klass
# File lib/rasn1/model.rb, line 69 def model(name, model_klass) @root = [name, model_klass] end
Create a new instance of a {Model} @param [Hash] args
# File lib/rasn1/model.rb, line 243 def initialize(args={}) root = generate_root set_elements(*root) initialize_elements self, args end
@param [Symbol,String] name name of object in model @param [Hash] options @note This method is named objectid
and not object_id
to not override
+Object#object_id+.
@see Types::ObjectId#initialize
# File lib/rasn1/model.rb, line 206 def objectid(name, options={}) options[:name] = name proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) } @root = [name, proc] end
Parse a DER/BER encoded string @param [String] str @param [Boolean] ber accept BER encoding or not @return [Model] @raise [ASN1Error] error on parsing
# File lib/rasn1/model.rb, line 234 def parse(str, ber: false) model = new model.parse! str, ber: ber model end
Update options of root element. May be used when subclassing.
class Model1 < RASN1::Model sequence :seq, implicit: 0, content: [bool(:bool), integer(:int)] end # same as Model1 but with implicit tag set to 1 class Model2 < Model1 root_options implicit: 1 end
@param [Hash] options @return [void]
# File lib/rasn1/model.rb, line 86 def root_options(options) @options = options end
Give type name (aka class name) @return [String]
# File lib/rasn1/model.rb, line 223 def type return @type if defined? @type @type = self.to_s.gsub(/.*::/, '') end
Public Instance Methods
Objects are equal if they have same class AND same DER @param [Base] other @return [Boolean]
# File lib/rasn1/model.rb, line 359 def ==(other) (other.class == self.class) && (other.to_der == self.to_der) end
Give access to element name
in model @param [String,Symbol] name @return [Types::Base]
# File lib/rasn1/model.rb, line 252 def [](name) @elements[name] end
Set value of element name
. Element should be a {Base}. @param [String,Symbol] name @param [Object] value @return [Object] value
# File lib/rasn1/model.rb, line 260 def []=(name, value) raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model @elements[name].value = value end
@return [String]
# File lib/rasn1/model.rb, line 352 def inspect(level=0) ' ' * level + "(#{type}) #{root.inspect(-level)}" end
Get elements names @return [Array<Symbol,String>]
# File lib/rasn1/model.rb, line 274 def keys @elements.keys end
Delegate some methods to root element @param [Symbol] meth
# File lib/rasn1/model.rb, line 338 def method_missing(meth, *args) if @elements[@root].respond_to? meth @elements[@root].send meth, *args else super end end
Get name frm root type @return [String,Symbol]
# File lib/rasn1/model.rb, line 268 def name @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 @raise [ASN1Error] error on parsing
# File lib/rasn1/model.rb, line 306 def parse!(str, ber: false) @elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber) end
@return [Boolean]
# File lib/rasn1/model.rb, line 347 def respond_to_missing?(meth, *) @elements[@root].respond_to?(meth) || super end
Get root element from model @return [Types::Base,Model]
# File lib/rasn1/model.rb, line 291 def root @elements[@root] end
@return [String]
# File lib/rasn1/model.rb, line 285 def to_der @elements[@root].to_der end
Return a hash image of model @return [Hash]
# File lib/rasn1/model.rb, line 280 def to_h private_to_h end
Give type name (aka class name) @return [String]
# File lib/rasn1/model.rb, line 297 def type self.class.type end
@overload value
Get value of root element @return [Object,nil]
@overload value(name)
Direct access to the value of +name+ (nested) element of model. @param [String,Symbol] name @return [Object,nil]
@return [Object,nil]
# File lib/rasn1/model.rb, line 318 def value(name=nil, *args) if name.nil? @elements[@root].value else elt = by_name(name) unless args.empty? elt = elt.root if elt.is_a?(Model) args.each do |arg| elt = elt.root if elt.is_a?(Model) elt = elt[arg] end end elt.value end end
Protected Instance Methods
Give a (nested) element from its name @param [String, Symbol] name @return [Model, Types::Base]
# File lib/rasn1/model.rb, line 368 def by_name(name) elt = self[name] return elt unless elt.nil? @elements.each_key do |subelt_name| if self[subelt_name].is_a?(Model) elt = self[subelt_name][name] return elt unless elt.nil? end end nil end
Private Instance Methods
# File lib/rasn1/model.rb, line 384 def composed?(elt) [Types::Sequence, Types::Set].include? elt.class end
# File lib/rasn1/model.rb, line 397 def generate_root root = self.class.class_eval { @root } @root = root[0] @elements = {} @elements[@root] = get_type(root[1], self.class.options || {}) root end
# File lib/rasn1/model.rb, line 388 def get_type(proc_or_class, options={}) case proc_or_class when Proc proc_or_class.call(options) when Class proc_or_class.new end end
# File lib/rasn1/model.rb, line 416 def initialize_elements(obj, args) args.each do |name, value| next unless obj[name] case value when Hash raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model initialize_elements obj[name], value when Array composed = if obj[name].is_a? Model obj[name].root else obj[name] end if composed.of_type.is_a? Model value.each do |el| composed << initialize_elements(composed.of_type.class.new, el) end else value.each do |el| obj[name] << el end end else obj[name].value = value end end end
# File lib/rasn1/model.rb, line 446 def private_to_h(element=nil) my_element = element my_element = root if my_element.nil? my_element = my_element.root if my_element.is_a?(Model) value = case my_element when Types::SequenceOf if my_element.of_type < Model my_element.value.map { |el| el.to_h.values.first } else my_element.value.map { |el| private_to_h(el) } end when Types::Sequence seq = my_element.value.map do |el| next if el.optional? && el.value.nil? name = el.is_a?(Model) ? @elements.key(el) : el.name [name, private_to_h(el)] end seq.compact! Hash[seq] else my_element.value end if element.nil? { @root => value } else value end end
# File lib/rasn1/model.rb, line 405 def set_elements(name, _elt, content=nil) return unless content.is_a? Array @elements[name].value = content.map do |name2, proc_or_class, content2| subel = get_type(proc_or_class) @elements[name2] = subel set_elements(name2, proc_or_class, content2) if composed?(subel) && content2.is_a?(Array) subel end end