rasn2 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +4 -0
  4. data/lib/rasn1/errors.rb +55 -0
  5. data/lib/rasn1/helpers/colorize.rb +76 -0
  6. data/lib/rasn1/model.rb +831 -0
  7. data/lib/rasn1/schema_parser.rb +470 -0
  8. data/lib/rasn1/tracer.rb +200 -0
  9. data/lib/rasn1/types/any.rb +98 -0
  10. data/lib/rasn1/types/base.rb +675 -0
  11. data/lib/rasn1/types/bit_string.rb +100 -0
  12. data/lib/rasn1/types/bmp_string.rb +22 -0
  13. data/lib/rasn1/types/boolean.rb +57 -0
  14. data/lib/rasn1/types/choice.rb +158 -0
  15. data/lib/rasn1/types/constrained.rb +51 -0
  16. data/lib/rasn1/types/constructed.rb +51 -0
  17. data/lib/rasn1/types/enumerated.rb +44 -0
  18. data/lib/rasn1/types/generalized_time.rb +156 -0
  19. data/lib/rasn1/types/ia5_string.rb +21 -0
  20. data/lib/rasn1/types/integer.rb +166 -0
  21. data/lib/rasn1/types/null.rb +43 -0
  22. data/lib/rasn1/types/numeric_string.rb +41 -0
  23. data/lib/rasn1/types/object_id.rb +64 -0
  24. data/lib/rasn1/types/octet_string.rb +52 -0
  25. data/lib/rasn1/types/primitive.rb +13 -0
  26. data/lib/rasn1/types/printable_string.rb +42 -0
  27. data/lib/rasn1/types/sequence.rb +105 -0
  28. data/lib/rasn1/types/sequence_of.rb +199 -0
  29. data/lib/rasn1/types/set.rb +28 -0
  30. data/lib/rasn1/types/set_of.rb +18 -0
  31. data/lib/rasn1/types/tag.rb +189 -0
  32. data/lib/rasn1/types/universal_string.rb +22 -0
  33. data/lib/rasn1/types/utc_time.rb +67 -0
  34. data/lib/rasn1/types/utf8_string.rb +21 -0
  35. data/lib/rasn1/types/visible_string.rb +30 -0
  36. data/lib/rasn1/types.rb +149 -0
  37. data/lib/rasn1/value_notation.rb +256 -0
  38. data/lib/rasn1/version.rb +6 -0
  39. data/lib/rasn1/wrapper.rb +279 -0
  40. data/lib/rasn1.rb +51 -0
  41. metadata +123 -0
@@ -0,0 +1,831 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # @abstract
5
+ # {Model} class is a base class to define ASN.1 models.
6
+ # == Create a simple ASN.1 model
7
+ # Given this ASN.1 example:
8
+ # Record ::= SEQUENCE {
9
+ # id INTEGER,
10
+ # room [0] IMPLICIT INTEGER OPTIONAL,
11
+ # house [1] EXPLICIT INTEGER DEFAULT 0
12
+ # }
13
+ # you may create your model like this:
14
+ # class Record < RASN1::Model
15
+ # sequence(:record,
16
+ # content: [integer(:id),
17
+ # integer(:room, implicit: 0, optional: true),
18
+ # integer(:house, explicit: 1, default: 0)])
19
+ # end
20
+ #
21
+ # Since 0.17.0, content may also be defined through a block. This is strictly
22
+ # equivalent to the +:content+ option:
23
+ # class Record < RASN1::Model
24
+ # sequence :record do
25
+ # integer :id
26
+ # integer :room, implicit: 0, optional: true
27
+ # integer :house, explicit: 1, default: 0
28
+ # end
29
+ # end
30
+ # Blocks may be nested, and may use all model helpers (+#model+, +#wrapper+, +#sequence_of+, ...):
31
+ # class PersonnelRecord < RASN1::Model
32
+ # sequence :personnelRecord do
33
+ # utf8_string :name
34
+ # utf8_string :title
35
+ # integer :age
36
+ # boolean :employed
37
+ # end
38
+ # end
39
+ # When both +:content+ option and a block are given, block elements are appended to
40
+ # those defined by the option.
41
+ #
42
+ # In a model, each element must have a unique name.
43
+ #
44
+ # === Parse a DER-encoded string
45
+ # record = Record.parse(der_string)
46
+ # record[:id] # => RASN1::Types::Integer
47
+ # record[:id].value # => Integer
48
+ # record[:id].to_i # => Integer
49
+ # record[:id].asn1_class # => Symbol
50
+ # record[:id].optional? # => false
51
+ # record[:id].default # => nil
52
+ # record[:room].optional # => true
53
+ # record[:house].default # => 0
54
+ #
55
+ # You may also parse a BER-encoded string this way:
56
+ # record = Record.parse(der_string, ber: true)
57
+ #
58
+ # === Generate a DER-encoded string
59
+ # record = Record.new(id: 12, room: 24)
60
+ # record.to_der
61
+ #
62
+ # == Create a more complex model
63
+ # Models may be nested. For example:
64
+ # class Record2 < RASN1::Model
65
+ # sequence(:record2,
66
+ # content: [boolean(:rented, default: false),
67
+ # model(:a_record, Record)])
68
+ # end
69
+ # Set values like this:
70
+ # record2 = Record2.new
71
+ # record2[:rented] = true
72
+ # record2[:a_record][:id] = 65537
73
+ # record2[:a_record][:room] = 43
74
+ # or like this:
75
+ # record2 = Record2.new(rented: true, a_record: { id: 65537, room: 43 })
76
+ # Same model, using a block:
77
+ # class Record2 < RASN1::Model
78
+ # sequence :record2 do
79
+ # boolean :rented, default: false
80
+ # model :a_record, Record
81
+ # end
82
+ # end
83
+ #
84
+ # == Delegation
85
+ # {Model} may delegate some methods to its root element. Thus, if root element
86
+ # is, for example, a {Types::Choice}, model may delegate +#chosen+ and +#chosen_value+.
87
+ #
88
+ # All methods defined by root may be delegated by model, unless model also defines
89
+ # this method.
90
+ # @author Sylvain Daubert
91
+ # @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
92
+ class Model # rubocop:disable Metrics/ClassLength
93
+ # @private Base Element
94
+ BaseElem = Struct.new(:name, :proc, :content) do
95
+ # @param [String,Symbol] name
96
+ # @param [Proc] proc
97
+ # @param [Array,nil] content
98
+ def initialize(name, proc, content)
99
+ check_duplicates(content.map(&:name) + [name]) unless content.nil?
100
+ super
101
+ end
102
+
103
+ private
104
+
105
+ # @return [Array<String>] The duplicate names found in the array
106
+ def find_all_duplicate_names(names)
107
+ names.group_by { |name| name }
108
+ .select { |_name, values| values.length > 1 }
109
+ .keys
110
+ end
111
+
112
+ def check_duplicates(names)
113
+ duplicates = find_all_duplicate_names(names)
114
+ raise ModelValidationError, "Duplicate name #{duplicates.first} found" if duplicates.any?
115
+ end
116
+ end
117
+
118
+ # @private Model Element
119
+ ModelElem = Struct.new(:name, :klass)
120
+
121
+ # @private Wrapper Element
122
+ WrapElem = Struct.new(:element, :options) do
123
+ # @return [Symbol]
124
+ def name
125
+ :"#{element.name}_wrapper"
126
+ end
127
+ end
128
+
129
+ # @private Sequence types
130
+ SEQUENCE_TYPES = [Types::Sequence, Types::SequenceOf, Types::Set, Types::SetOf].freeze
131
+
132
+ # Define helper methods to define models
133
+ module Accel # rubocop:disable Metrics/ModuleLength
134
+ # @return [Hash]
135
+ attr_reader :options
136
+
137
+ # Use another model in this model
138
+ # @param [String,Symbol] name
139
+ # @param [Class] model_klass
140
+ # @return [Elem]
141
+ def model(name, model_klass)
142
+ push_element(ModelElem.new(name, model_klass))
143
+ end
144
+
145
+ # Use a {Wrapper} around a {Types::Base} or a {Model} object
146
+ # @overload wrapper(element, options={})
147
+ # @param [Types::Base,Model] element
148
+ # @param [Hash] options
149
+ # @overload wrapper(options={}, &block)
150
+ # Define the wrapped element through a block. The block must define exactly one element.
151
+ # @param [Hash] options
152
+ # @yieldreturn [void]
153
+ # @return [WrapElem]
154
+ # @since 0.12
155
+ # @since 0.17.0 block form
156
+ def wrapper(element=nil, options={}, &block)
157
+ if block
158
+ options = element if element.is_a?(Hash)
159
+ element = single_element_from_block(:wrapper, &block)
160
+ end
161
+ push_element(WrapElem.new(element, options))
162
+ end
163
+
164
+ # @private Register +elem+ as a child of the block currently being evaluated, or as
165
+ # the root element of the model when no block is being evaluated.
166
+ # @param [BaseElem,ModelElem,WrapElem] elem
167
+ # @return [BaseElem,ModelElem,WrapElem] elem
168
+ # @since 0.17.0
169
+ def push_element(elem)
170
+ builder = @content_builders&.last
171
+ if builder.nil?
172
+ @root = elem
173
+ else
174
+ remove_consumed_elements(builder, elem)
175
+ builder << elem
176
+ end
177
+ elem
178
+ end
179
+
180
+ # @private Evaluate +block+ and collect all elements defined by it.
181
+ # @return [Array<BaseElem,ModelElem,WrapElem>]
182
+ # @since 0.17.0
183
+ def capture_content(&block)
184
+ @content_builders ||= []
185
+ @content_builders << []
186
+ begin
187
+ instance_eval(&block)
188
+ ensure
189
+ content = @content_builders.pop
190
+ end
191
+ content
192
+ end
193
+
194
+ # Update options of root element.
195
+ # May be used when subclassing.
196
+ # class Model1 < RASN1::Model
197
+ # sequence :seq, implicit: 0,
198
+ # content: [bool(:bool), integer(:int)]
199
+ # end
200
+ #
201
+ # # same as Model1 but with implicit tag set to 1
202
+ # class Model2 < Model1
203
+ # root_options implicit: 1
204
+ # end
205
+ # @param [Hash] options
206
+ # @return [void]
207
+ # @since 0.12.0 may change name through +:name+
208
+ def root_options(options)
209
+ @options = options
210
+ return unless options.key?(:name)
211
+
212
+ @root = @root.dup
213
+ @root.name = options[:name]
214
+ end
215
+
216
+ # On inheritance, create +@root+ class variable
217
+ # @param [Class] klass
218
+ # @return [void]
219
+ def inherited(klass)
220
+ super
221
+ root = @root
222
+ klass.class_eval { @root = root }
223
+ end
224
+
225
+ # @private
226
+ # @param [String,Symbol] accel_name
227
+ # @param [Class] klass
228
+ # @since 0.11.0
229
+ # @since 0.12.0 track source location on error (adfoster-r7)
230
+ # @since 0.17.0 accept a block to define content
231
+ def define_type_accel_base(accel_name, klass)
232
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
233
+ def #{accel_name}(name, options={}, &block) # def sequence(name, type, options, &block)
234
+ options[:name] = name
235
+ options[:content] = merge_content(options[:content], capture_content(&block)) if block
236
+ proc = proc do |opts|
237
+ #{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
238
+ end
239
+ push_element(BaseElem.new(name, proc, options[:content]))
240
+ end
241
+ EVAL
242
+ end
243
+
244
+ # @private
245
+ # @param [String,Symbol] accel_name
246
+ # @param [Class] klass
247
+ # @since 0.11.0
248
+ # @since 0.12.0 track source location on error (adfoster-r7)
249
+ def define_type_accel_of(accel_name, klass)
250
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
251
+ def #{accel_name}_of(name, type, options={}) # def sequence_of(name, type, options)
252
+ options[:name] = name
253
+ proc = proc do |opts|
254
+ #{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
255
+ end
256
+ push_element(BaseElem.new(name, proc, nil))
257
+ end
258
+ EVAL
259
+ end
260
+
261
+ # Define an accelarator to access a type in a model definition
262
+ # @param [String] accel_name
263
+ # @param [Class] klass class to instanciate
264
+ # @since 0.11.0
265
+ # @since 0.12.0 track source location on error (adfoster-r7)
266
+ def define_type_accel(accel_name, klass)
267
+ if klass < Types::SequenceOf
268
+ define_type_accel_of(accel_name, klass)
269
+ else
270
+ define_type_accel_base(accel_name, klass)
271
+ end
272
+ end
273
+
274
+ # @param [Symbol,String] name name of object in model
275
+ # @param [Hash] options
276
+ # @return [Elem]
277
+ # @note This method is named +objectid+ and not +object_id+ to not override
278
+ # +Object#object_id+.
279
+ # @see Types::ObjectId#initialize
280
+ def objectid(name, options={})
281
+ options[:name] = name
282
+ proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
283
+ push_element(BaseElem.new(name, proc, nil))
284
+ end
285
+
286
+ # @param [Symbol,String] name name of object in model
287
+ # @param [Hash] options
288
+ # @return [Elem]
289
+ # @see Types::Any#initialize
290
+ def any(name, options={})
291
+ options[:name] = name
292
+ proc = proc { |opts| Types::Any.new(options.merge(opts)) }
293
+ push_element(BaseElem.new(name, proc, nil))
294
+ end
295
+
296
+ # Give type name (aka class name)
297
+ # @return [String]
298
+ def type
299
+ return @type if defined? @type
300
+
301
+ @type = self.to_s.gsub(/.*::/, '')
302
+ end
303
+
304
+ # Parse a DER/BER encoded string
305
+ # @param [String] str
306
+ # @param [Boolean] ber accept BER encoding or not
307
+ # @return [Model]
308
+ # @raise [ASN1Error] error on parsing
309
+ def parse(str, ber: false)
310
+ model = new
311
+ model.parse!(str, ber: ber)
312
+ model
313
+ end
314
+
315
+ private
316
+
317
+ # Remove from +builder+ the elements consumed by +elem+. This happens when sub-elements
318
+ # are defined as arguments (through +:content+ option or as +wrapper+ argument) inside a
319
+ # content block: they were already registered in +builder+ by their own definition.
320
+ # @return [void]
321
+ def remove_consumed_elements(builder, elem)
322
+ children = case elem
323
+ when BaseElem then elem.content
324
+ when WrapElem then [elem.element]
325
+ end
326
+ return if children.nil?
327
+
328
+ children.each do |child|
329
+ builder.delete_if { |registered| registered.equal?(child) }
330
+ end
331
+ end
332
+
333
+ # Merge content defined through +:content+ option with content defined through a block
334
+ # @return [Array]
335
+ def merge_content(option_content, block_content)
336
+ return block_content if option_content.nil?
337
+
338
+ option_content + block_content
339
+ end
340
+
341
+ # Evaluate +block+ and ensure it defines exactly one element
342
+ # @return [BaseElem,ModelElem,WrapElem]
343
+ def single_element_from_block(helper_name, &block)
344
+ content = capture_content(&block)
345
+ raise ModelValidationError, "#{helper_name} block must define exactly one element" unless content.size == 1
346
+
347
+ content.first
348
+ end
349
+ end
350
+
351
+ extend Accel
352
+
353
+ # @!method sequence(name, options)
354
+ # @!scope class
355
+ # @param [Symbol,String] name name of object in model
356
+ # @param [Hash] options
357
+ # @return [Elem]
358
+ # @see Types::Sequence#initialize
359
+ # @!method set(name, options)
360
+ # @!scope class
361
+ # @param [Symbol,String] name name of object in model
362
+ # @param [Hash] options
363
+ # @return [Elem]
364
+ # @see Types::Set#initialize
365
+ # @!method choice(name, options)
366
+ # @!scope class
367
+ # @param [Symbol,String] name name of object in model
368
+ # @param [Hash] options
369
+ # @return [Elem]
370
+ # @see Types::Choice#initialize
371
+ %w[sequence set choice tag].each do |type|
372
+ self.define_type_accel_base(type, Types.const_get(type.capitalize))
373
+ end
374
+
375
+ # @!method sequence_of(name, type, options)
376
+ # @!scope class
377
+ # @param [Symbol,String] name name of object in model
378
+ # @param [Model, Types::Base] type type for SEQUENCE OF
379
+ # @param [Hash] options
380
+ # @return [Elem]
381
+ # @see Types::SequenceOf#initialize
382
+ # @!method set_of(name, type, options)
383
+ # @!scope class
384
+ # @param [Symbol,String] name name of object in model
385
+ # @param [Model, Types::Base] type type for SET OF
386
+ # @param [Hash] options
387
+ # @return [Elem]
388
+ # @see Types::SetOf#initialize
389
+ %w[sequence set].each do |type|
390
+ define_type_accel_of(type, Types.const_get(:"#{type.capitalize}Of"))
391
+ end
392
+
393
+ # @!method boolean(name, options)
394
+ # @!scope class
395
+ # @param [Symbol,String] name name of object in model
396
+ # @param [Hash] options
397
+ # @return [Elem]
398
+ # @see Types::Boolean#initialize
399
+ # @!method integer(name, options)
400
+ # @!scope class
401
+ # @param [Symbol,String] name name of object in model
402
+ # @param [Hash] options
403
+ # @return [Elem]
404
+ # @see Types::Integer#initialize
405
+ # @!method bit_string(name, options)
406
+ # @!scope class
407
+ # @param [Symbol,String] name name of object in model
408
+ # @param [Hash] options
409
+ # @return [Elem]
410
+ # @see Types::BitString#initialize
411
+ # @!method bmp_string(name, options)
412
+ # @!scope class
413
+ # @param [Symbol,String] name name of object in model
414
+ # @param [Hash] options
415
+ # @return [Elem]
416
+ # @see Types::BmpString#initialize
417
+ # @!method octet_string(name, options)
418
+ # @!scope class
419
+ # @param [Symbol,String] name name of object in model
420
+ # @param [Hash] options
421
+ # @return [Elem]
422
+ # @see Types::OctetString#initialize
423
+ # @!method null(name, options)
424
+ # @!scope class
425
+ # @param [Symbol,String] name name of object in model
426
+ # @param [Hash] options
427
+ # @return [Elem]
428
+ # @see Types::Null#initialize
429
+ # @!method enumerated(name, options)
430
+ # @!scope class
431
+ # @param [Symbol,String] name name of object in model
432
+ # @param [Hash] options
433
+ # @return [Elem]
434
+ # @see Types::Enumerated#initialize
435
+ # @!method universal_string(name, options)
436
+ # @!scope class
437
+ # @param [Symbol,String] name name of object in model
438
+ # @param [Hash] options
439
+ # @return [Elem]
440
+ # @see Types::UniversalString#initialize
441
+ # @!method utf8_string(name, options)
442
+ # @!scope class
443
+ # @param [Symbol,String] name name of object in model
444
+ # @param [Hash] options
445
+ # @return [Elem]
446
+ # @see Types::Utf8String#initialize
447
+ # @!method numeric_string(name, options)
448
+ # @!scope class
449
+ # @param [Symbol,String] name name of object in model
450
+ # @param [Hash] options
451
+ # @return [Elem]
452
+ # @see Types::NumericString#initialize
453
+ # @!method printable_string(name, options)
454
+ # @!scope class
455
+ # @param [Symbol,String] name name of object in model
456
+ # @param [Hash] options
457
+ # @return [Elem]
458
+ # @see Types::PrintableString#initialize
459
+ # @!method visible_string(name, options)
460
+ # @!scope class
461
+ # @param [Symbol,String] name name of object in model
462
+ # @param [Hash] options
463
+ # @return [Elem]
464
+ # @see Types::VisibleString#initialize
465
+ # @!method ia5_string(name, options)
466
+ # @!scope class
467
+ # @param [Symbol,String] name name of object in model
468
+ # @param [Hash] options
469
+ # @return [Elem]
470
+ # @see Types::IA5String#initialize
471
+ Types.primitives.each do |prim|
472
+ next if prim == Types::ObjectId
473
+
474
+ method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
475
+ self.define_type_accel_base(method_name, prim)
476
+ end
477
+
478
+ # @return [Model, Wrapper, Types::Base]
479
+ attr_reader :root
480
+
481
+ # Create a new instance of a {Model}
482
+ # @param [Hash] args
483
+ def initialize(args={})
484
+ @elements = {}
485
+ generate_root(args)
486
+ lazy_initialize(args) unless args.empty?
487
+ end
488
+
489
+ # @overload [](name)
490
+ # Access an element of the model by its name
491
+ # @param [Symbol] name
492
+ # @return [Model, Types::Base, Wrapper]
493
+ # @overload [](idx)
494
+ # Access an element of root element by its index. Root element must be a {Sequence} or {SequenceOf}.
495
+ # @param [Integer] idx
496
+ # @return [Model, Types::Base, Wrapper]
497
+ def [](name_or_idx)
498
+ case name_or_idx
499
+ when Symbol
500
+ elt = @elements[name_or_idx]
501
+ return elt unless elt.is_a?(Proc)
502
+
503
+ @elements[name_or_idx] = elt.call
504
+ when Integer
505
+ root[name_or_idx]
506
+ end
507
+ end
508
+
509
+ # Set value of element +name+. Element should be a {Types::Base}.
510
+ # @param [String,Symbol] name
511
+ # @param [Object] value
512
+ # @return [Object] value
513
+ def []=(name, value)
514
+ # Here, use #[] to force generation for lazy elements
515
+ raise Error, 'cannot set value for a Model' if self[name].is_a?(Model)
516
+
517
+ self[name].value = value
518
+ end
519
+
520
+ # clone @elements and initialize @root from this new @element.
521
+ def initialize_copy(_other)
522
+ @elements = @elements.clone
523
+ @root = @elements[@root_name]
524
+ end
525
+
526
+ # Give model name (a.k.a root name)
527
+ # @return [String]
528
+ def name
529
+ @root_name
530
+ end
531
+
532
+ # Get elements names
533
+ # @return [Array<Symbol,String>]
534
+ def keys
535
+ @elements.keys
536
+ end
537
+
538
+ # Return a hash image of model
539
+ # @return [Hash]
540
+ def to_h
541
+ private_to_h
542
+ end
543
+
544
+ # @return [String]
545
+ def to_der
546
+ root.to_der
547
+ end
548
+
549
+ # Generate ASN.1 value notation text from this model instance.
550
+ # @param [String] name the value name (e.g. 'myValue')
551
+ # @param [String] type_name the ASN.1 type name (e.g. 'PersonnelRecord')
552
+ # @return [String] value notation text
553
+ # @since 0.17.0
554
+ def to_asn1(name: nil, type_name: nil)
555
+ ValueNotation.emit(self, name: name, type_name: type_name)
556
+ end
557
+
558
+ # Give type name (aka class name)
559
+ # @return [String]
560
+ def type
561
+ self.class.type
562
+ end
563
+
564
+ # Parse a DER/BER encoded string, and modify object in-place.
565
+ # @param [String] der
566
+ # @param [Boolean] ber accept BER encoding or not
567
+ # @return [Integer] number of parsed bytes
568
+ # @raise [ASN1Error] error on parsing
569
+ def parse!(der, ber: false)
570
+ root.parse!(der, ber: ber)
571
+ end
572
+
573
+ # @private
574
+ # @see Types::Base#do_parse
575
+ def do_parse(der, ber: false)
576
+ root.do_parse(der, ber: ber)
577
+ end
578
+
579
+ # @overload value
580
+ # Get value of root element
581
+ # @return [Object,nil]
582
+ # @overload value(name, *args)
583
+ # Direct access to the value of +name+ (nested) element of model.
584
+ # @param [String,Symbol] name
585
+ # @param [Array<Integer,String,Symbol>] args more argument to access element. May be
586
+ # used to access content of a SequenceOf or a SetOf
587
+ # @return [Object,nil]
588
+ # @return [Object,nil]
589
+ # @example
590
+ # class MyModel1 < RASN1::Model
591
+ # sequence('seq', content: [boolean('boolean'), integer('int')])
592
+ # end
593
+ # class MyModel2 < RASN1::Model
594
+ # sequence('root', content: [sequence_of('list', MyModel1)])
595
+ # end
596
+ # model = MyModel2.new
597
+ # model.parse!(der)
598
+ # # access to 2nd MyModel1.int in list
599
+ # model.value('list', 1, 'int')
600
+ def value(name=nil, *args)
601
+ if name.nil?
602
+ root.value
603
+ else
604
+ elt = by_name(name)
605
+ return nil if elt.nil?
606
+
607
+ unless args.empty?
608
+ args.each do |arg|
609
+ elt = elt.root if elt.is_a?(Model)
610
+ elt = elt[arg]
611
+ end
612
+ end
613
+
614
+ elt.value
615
+ end
616
+ end
617
+
618
+ # Return a hash image of model
619
+ # @return [Hash]
620
+ # Delegate some methods to root element
621
+ # @param [Symbol] meth
622
+ def method_missing(meth, *args, **kwargs)
623
+ if root.respond_to?(meth)
624
+ root.send(meth, *args, **kwargs)
625
+ else
626
+ super
627
+ end
628
+ end
629
+
630
+ # @return [Boolean]
631
+ def respond_to_missing?(meth, *)
632
+ root.respond_to?(meth) || super
633
+ end
634
+
635
+ # @return [String]
636
+ def inspect(level=0, color: true)
637
+ "#{' ' * level}(#{type}) #{root.inspect(-level)}"
638
+ end
639
+
640
+ # Objects are equal if they have same class AND same DER
641
+ # @param [Base] other
642
+ # @return [Boolean]
643
+ def ==(other)
644
+ (other.class == self.class) && (other.to_der == self.to_der)
645
+ end
646
+
647
+ protected
648
+
649
+ # Initialize model elements from +args+
650
+ # @param [Hash,Array] args
651
+ # @return [void]
652
+ def lazy_initialize(args)
653
+ case args
654
+ when Hash
655
+ lazy_initialize_hash(args)
656
+ when Array
657
+ lazy_initialize_array(args)
658
+ end
659
+ end
660
+
661
+ # Initialize an element from a hash
662
+ # @param [Hash] args
663
+ # @return [void]
664
+ def lazy_initialize_hash(args)
665
+ args.each do |name, value|
666
+ element = self[name]
667
+ case element
668
+ when Model
669
+ element.lazy_initialize(value)
670
+ when nil
671
+ else
672
+ element.value = value
673
+ end
674
+ end
675
+ end
676
+
677
+ # Initialize an sequence element from an array
678
+ # @param [Array] args
679
+ # @return [void]
680
+ def lazy_initialize_array(ary)
681
+ raise Error, 'Only sequence types may be initialized with an array' unless SEQUENCE_TYPES.any? { |klass| root.is_a?(klass) }
682
+
683
+ ary.each do |initializer|
684
+ root << initializer
685
+ end
686
+ end
687
+
688
+ # Give a (nested) element from its name
689
+ # @param [String, Symbol] name
690
+ # @return [Model, Types::Base, nil]
691
+ def by_name(name)
692
+ elt = self[name]
693
+ return elt unless elt.nil?
694
+
695
+ @elements.each_value do |subelt|
696
+ next unless subelt.is_a?(Model)
697
+
698
+ value = subelt.by_name(name)
699
+ return value unless value.nil?
700
+ end
701
+
702
+ nil
703
+ end
704
+
705
+ private
706
+
707
+ def generate_root(args)
708
+ opts = args.slice(:name, :explicit, :implicit, :optional, :class, :default, :constructed, :tag_value)
709
+ root = self.class.class_eval { @root }
710
+ root_options = self.class.options || {}
711
+ root_options.merge!(opts)
712
+ @root_name = args[:name] || root.name
713
+ @root = generate_element(root, root_options)
714
+ @elements[@root_name] = @root
715
+ end
716
+
717
+ def generate_element(elt, opts={})
718
+ case elt
719
+ when BaseElem
720
+ generate_base_element(elt, opts)
721
+ when ModelElem
722
+ opts[:name] ||= elt.name
723
+ elt.klass.new(opts)
724
+ when WrapElem
725
+ generate_wrapper_element(elt, opts)
726
+ end
727
+ end
728
+
729
+ def generate_wrapper_element(elt, opts)
730
+ wrapped = elt.element.is_a?(ModelElem) ? elt.element.klass : generate_element(elt.element)
731
+ options = elt.options.merge(opts)
732
+ options[:name] = elt.element.name if elt.element.is_a?(ModelElem)
733
+ wrapper = Wrapper.new(wrapped, options)
734
+ # Use a proc as wrapper may be lazy
735
+ @elements[elt.element.name] = proc { wrapper.element }
736
+ wrapper
737
+ end
738
+
739
+ def generate_base_element(elt, opts)
740
+ element = elt.proc.call(opts)
741
+ return element if elt.content.nil?
742
+
743
+ element.value = elt.content.map do |subel|
744
+ generated = generate_element(subel)
745
+ @elements[subel.name] = generated
746
+ end
747
+ element
748
+ end
749
+
750
+ # @author sdaubert
751
+ # @author lemontree55
752
+ # @author adfoster-r7
753
+ def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
754
+ my_element = element || root
755
+ value = case my_element
756
+ when Model
757
+ model_to_h(my_element)
758
+ when Types::SequenceOf
759
+ sequence_of_to_h(my_element)
760
+ when Types::Sequence, Types::Tag
761
+ sequence_to_h(my_element)
762
+ when Types::Choice
763
+ choice_to_h(my_element)
764
+ when Wrapper
765
+ wrapper_to_h(my_element)
766
+ else
767
+ my_element.value
768
+ end
769
+ if element.nil?
770
+ { @root_name => value }
771
+ else
772
+ value
773
+ end
774
+ end
775
+
776
+ def model_to_h(elt)
777
+ hsh = elt.to_h
778
+ if root.is_a?(Types::Choice)
779
+ hsh[hsh.keys.first]
780
+ else
781
+ { @elements.key(elt) => hsh[hsh.keys.first] }
782
+ end
783
+ end
784
+
785
+ def sequence_of_to_h(elt)
786
+ if elt.of_type < Model
787
+ elt.value&.map { |el| el.to_h.values.first }
788
+ else
789
+ elt.value&.map { |el| private_to_h(el) }
790
+ end
791
+ end
792
+
793
+ def sequence_to_h(seq)
794
+ ary = seq.value&.map do |el|
795
+ next if el.optional? && el.value.nil?
796
+
797
+ case el
798
+ when Model
799
+ model_to_h(el).to_a[0]
800
+ when Wrapper
801
+ [unwrap_keyname(@elements.key(el)), wrapper_to_h(el)]
802
+ else
803
+ [el.name, private_to_h(el)]
804
+ end
805
+ end
806
+ ary.compact.to_h
807
+ end
808
+
809
+ def choice_to_h(elt)
810
+ raise ChoiceError.new(elt) if elt.chosen.nil?
811
+
812
+ chosen = elt.value[elt.chosen]
813
+ { chosen.name => private_to_h(chosen) }
814
+ end
815
+
816
+ def unwrap_keyname(key)
817
+ key.to_s.delete_suffix('_wrapper').to_sym
818
+ end
819
+
820
+ def wrapper_to_h(wrap)
821
+ el = wrap.element
822
+ case el
823
+ when Model
824
+ hsh = el.to_h
825
+ hsh[hsh.keys.first]
826
+ else
827
+ private_to_h(el)
828
+ end
829
+ end
830
+ end
831
+ end