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

options[R]

@return [Hash]

Public Class Methods

any(name, options={}) click to toggle source

@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
inherited(klass) click to toggle source

On inheritance, create +@root+ class variable @param [Class] klass @return [void]

Calls superclass method
# File lib/rasn1/model.rb, line 93
def inherited(klass)
  super
  root = @root
  klass.class_eval { @root = root }
end
model(name, model_klass) click to toggle source

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
new(args={}) click to toggle source

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
objectid(name, options={}) click to toggle source

@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(str, ber: false) click to toggle source

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
root_options(options) click to toggle source

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
type() click to toggle source

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

==(other) click to toggle source

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
[](name) click to toggle source

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
[]=(name, value) click to toggle source

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
inspect(level=0) click to toggle source

@return [String]

# File lib/rasn1/model.rb, line 352
def inspect(level=0)
  '  ' * level + "(#{type}) #{root.inspect(-level)}"
end
keys() click to toggle source

Get elements names @return [Array<Symbol,String>]

# File lib/rasn1/model.rb, line 274
def keys
  @elements.keys
end
method_missing(meth, *args) click to toggle source

Delegate some methods to root element @param [Symbol] meth

Calls superclass method
# 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
name() click to toggle source

Get name frm root type @return [String,Symbol]

# File lib/rasn1/model.rb, line 268
def name
  @root
end
parse!(str, ber: false) click to toggle source

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
respond_to_missing?(meth, *) click to toggle source

@return [Boolean]

Calls superclass method
# File lib/rasn1/model.rb, line 347
def respond_to_missing?(meth, *)
  @elements[@root].respond_to?(meth) || super
end
root() click to toggle source

Get root element from model @return [Types::Base,Model]

# File lib/rasn1/model.rb, line 291
def root
  @elements[@root]
end
to_der() click to toggle source

@return [String]

# File lib/rasn1/model.rb, line 285
def to_der
  @elements[@root].to_der
end
to_h() click to toggle source

Return a hash image of model @return [Hash]

# File lib/rasn1/model.rb, line 280
def to_h
  private_to_h
end
type() click to toggle source

Give type name (aka class name) @return [String]

# File lib/rasn1/model.rb, line 297
def type
  self.class.type
end
value(name=nil, *args) click to toggle source

@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

by_name(name) click to toggle source

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

composed?(elt) click to toggle source
# File lib/rasn1/model.rb, line 384
def composed?(elt)
  [Types::Sequence, Types::Set].include? elt.class
end
generate_root() click to toggle source
# 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
get_type(proc_or_class, options={}) click to toggle source
# 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
initialize_elements(obj, args) click to toggle source
# 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
private_to_h(element=nil) click to toggle source
# 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
set_elements(name, _elt, content=nil) click to toggle source
# 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