Class: RASN1::Model Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/rasn1/model.rb

Overview

This class is 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

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Model

Create a new instance of a RASN1::Model

Parameters:

  • args (Hash) (defaults to: {})


241
242
243
244
245
# File 'lib/rasn1/model.rb', line 241

def initialize(args={})
  root = generate_root
  set_elements(*root)
  initialize_elements self, args
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args) ⇒ Object

Delegate some methods to root element

Parameters:

  • meth (Symbol)


309
310
311
312
313
314
315
# File 'lib/rasn1/model.rb', line 309

def method_missing(meth, *args)
  if @elements[@root].respond_to? meth
    @elements[@root].send meth, *args
  else
    super
  end
end

Class Attribute Details

.optionsHash (readonly)

Returns:

  • (Hash)


64
65
66
# File 'lib/rasn1/model.rb', line 64

def options
  @options
end

Class Method Details

.any(name, options = {}) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash) (defaults to: {})

See Also:



214
215
216
217
218
# File 'lib/rasn1/model.rb', line 214

def any(name, options={})
  options.merge!(name: name)
  proc = Proc.new { |opts| Types::Any.new(options.merge(opts)) }
  @root = [name, proc]
end

.bit_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.boolean(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.choice(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rasn1/model.rb', line 111

%w(sequence set choice).each do |type|
  class_eval "def #{type}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    Types::#{type.capitalize}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "  @root << options[:content] unless options[:content].nil?\n" \
             "  @root\n" \
             "end"
end

.enumerated(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.ia5_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritance, create @root class variable

Parameters:

  • klass (Class)


93
94
95
96
97
# File 'lib/rasn1/model.rb', line 93

def inherited(klass)
  super
  root = defined?(@root )? @root : nil
  klass.class_eval { @root = root }
end

.integer(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.model(name, model_klass) ⇒ Object

Use another model in this model

Parameters:

  • name (String, Symbol)
  • model_klass (Class)


69
70
71
# File 'lib/rasn1/model.rb', line 69

def model(name, model_klass)
  @root = [name, model_klass]
end

.null(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.numeric_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.objectid(name, options = {}) ⇒ Object

Note:

This method is named objectid and not object_id to not override Object#object_id.

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash) (defaults to: {})

See Also:



205
206
207
208
209
# File 'lib/rasn1/model.rb', line 205

def objectid(name, options={})
  options.merge!(name: name)
  proc = Proc.new { |opts| Types::ObjectId.new(options.merge(opts)) }
  @root = [name, proc]
end

.octet_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.parse(str, ber: false) ⇒ Model

Parse a DER/BER encoded string

Parameters:

  • str (String)
  • ber (Boolean) (defaults to: false)

    accept BER encoding or not

Returns:

Raises:



232
233
234
235
236
# File 'lib/rasn1/model.rb', line 232

def parse(str, ber: false)
  model = new
  model.parse! str, ber: ber
  model
end

.printable_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.root_options(options) ⇒ void

This method returns an undefined value.

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

Parameters:

  • options (Hash)


86
87
88
# File 'lib/rasn1/model.rb', line 86

def root_options(options)
  @options = options
end

.sequence(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rasn1/model.rb', line 111

%w(sequence set choice).each do |type|
  class_eval "def #{type}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    Types::#{type.capitalize}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "  @root << options[:content] unless options[:content].nil?\n" \
             "  @root\n" \
             "end"
end

.sequence_of(name, type, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • type (Model, Types::Base)

    type for SEQUENCE OF

  • options (Hash)

See Also:



133
134
135
136
137
138
139
140
141
142
# File 'lib/rasn1/model.rb', line 133

%w(sequence set).each do |type|
  klass_name = "Types::#{type.capitalize}Of"
  class_eval "def #{type}_of(name, type, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{klass_name}.new(type, options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.set(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rasn1/model.rb', line 111

%w(sequence set choice).each do |type|
  class_eval "def #{type}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    Types::#{type.capitalize}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "  @root << options[:content] unless options[:content].nil?\n" \
             "  @root\n" \
             "end"
end

.set_of(name, type, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • type (Model, Types::Base)

    type for SET OF

  • options (Hash)

See Also:



133
134
135
136
137
138
139
140
141
142
# File 'lib/rasn1/model.rb', line 133

%w(sequence set).each do |type|
  klass_name = "Types::#{type.capitalize}Of"
  class_eval "def #{type}_of(name, type, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{klass_name}.new(type, options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.typeString

Give type name (aka class name)

Returns:

  • (String)


222
223
224
225
# File 'lib/rasn1/model.rb', line 222

def type
  return @type if defined? @type
  @type = self.to_s.gsub(/.*::/, '')
end

.utf8_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

.visible_string(name, options) ⇒ Object

Parameters:

  • name (Symbol, String)

    name of object in model

  • options (Hash)

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/rasn1/model.rb', line 188

Types.primitives.each do |prim|
  next if prim == Types::ObjectId
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
  class_eval "def #{method_name}(name, options={})\n" \
             "  options.merge!(name: name)\n" \
             "  proc = Proc.new do |opts|\n" \
             "    #{prim.to_s}.new(options.merge(opts))\n" \
             "  end\n" \
             "  @root = [name, proc]\n" \
             "end"
end

Instance Method Details

#==(other) ⇒ Boolean

Objects are equal if they have same class AND same DER

Parameters:

  • other (Base)

Returns:

  • (Boolean)


325
326
327
# File 'lib/rasn1/model.rb', line 325

def ==(other)
  (other.class == self.class) && (other.to_der == self.to_der)
end

#[](name) ⇒ Types::Base

Give access to element name in model

Parameters:

  • name (String, Symbol)

Returns:



250
251
252
# File 'lib/rasn1/model.rb', line 250

def [](name)
  @elements[name]
end

#[]=(name, value) ⇒ Object

Set value of element name. Element should be a Base.

Parameters:

  • name (String, Symbol)
  • value (Object)

Returns:

  • (Object)

    value

Raises:



258
259
260
261
# File 'lib/rasn1/model.rb', line 258

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) ⇒ String

Returns:

  • (String)


318
319
320
# File 'lib/rasn1/model.rb', line 318

def inspect(level=0)
  '  ' * level + "(#{type}) #{root.inspect(-level)}"
end

#keysArray<Symbol,String>

Get elements names

Returns:

  • (Array<Symbol,String>)


271
272
273
# File 'lib/rasn1/model.rb', line 271

def keys
  @elements.keys
end

#nameString, Symbol

Get name frm root type

Returns:

  • (String, Symbol)


265
266
267
# File 'lib/rasn1/model.rb', line 265

def name
  @root
end

#parse!(str, ber: false) ⇒ Integer

Parse a DER/BER encoded string, and modify object in-place.

Parameters:

  • str (String)
  • ber (Boolean) (defaults to: false)

    accept BER encoding or not

Returns:

  • (Integer)

    number of parsed bytes

Raises:



303
304
305
# File 'lib/rasn1/model.rb', line 303

def parse!(str, ber: false)
  @elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber)
end

#rootTypes::Base, Model

Get root element from model

Returns:



288
289
290
# File 'lib/rasn1/model.rb', line 288

def root
  @elements[@root]
end

#to_derString

Returns:

  • (String)


282
283
284
# File 'lib/rasn1/model.rb', line 282

def to_der
  @elements[@root].to_der
end

#to_hHash

Return a hash image of model

Returns:

  • (Hash)


277
278
279
# File 'lib/rasn1/model.rb', line 277

def to_h
  private_to_h
end

#typeString

Give type name (aka class name)

Returns:

  • (String)


294
295
296
# File 'lib/rasn1/model.rb', line 294

def type
  self.class.type
end