rasn1 0.14.0 → 0.15.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fca7c9a3f54b4fb4aea0ab91f4a7e243a4b5874eae10540a04390dcaff340218
4
- data.tar.gz: 77e0ef0cd540c787781bb9aae0aada5a3f7770e6060d1ace5c902afb4b212bcb
3
+ metadata.gz: 44b823b6a8e75b419ef38d27be1a31464667f4a2729ce514702b83a34210e7cb
4
+ data.tar.gz: 4e8f19d517834432999dac059bd175bc53330b224ffb554017efd6ebdca83e34
5
5
  SHA512:
6
- metadata.gz: 410feb2673c7667bd1efbab93e7e78e8cca4e0f9a815d6cd768a26acea42afe6bce2bbfa4b090c36f411753a77dec08671e0463600c94e12f21081ecc631a13d
7
- data.tar.gz: f373028c96806e3034471bd5833345303b572842215a02d7d3612c7614557894c8cf3029783af6b09120ce31db81ac8d84414eca28d5ec76991f39d749a7b801
6
+ metadata.gz: a44cdcea86cd6a7687cc0bb56475ee9325a5135acdd3b3408d5c1be936511279ed344d3811afa44a0eb658f01cd71a73fa49ea4c825e3d81d6c5746684e04076
7
+ data.tar.gz: fadc12bb059a416ae8716857ef91a402969ef78d80ca767bfefe09237794e7accffc4a6876ab90400047efb63442b065508ef3ff5768d4df1f18f8d6890f0334
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/rasn1.svg)](https://badge.fury.io/rb/rasn1)
2
- [![Action status](https://github.com/lemontree55/rasn1/workflows/ci/badge.svg?branch=master)](https://github.com/lemontree55/rasn1/actions?query=workflow%3Aci)
2
+ [![Action status](https://github.com/lemontree55/rasn1/actions/workflows/ci.yml/badge.svg)](https://github.com/lemontree55/rasn1/actions/workflows/ci.yml)
3
3
 
4
4
  # Rasn1
5
5
 
@@ -15,11 +15,15 @@ gem 'rasn1'
15
15
 
16
16
  And then execute:
17
17
 
18
- $ bundle install
18
+ ```bash
19
+ bundle install
20
+ ```
19
21
 
20
22
  Or install it yourself as:
21
23
 
22
- $ gem install rasn1
24
+ ```bash
25
+ gem install rasn1
26
+ ```
23
27
 
24
28
  ## Simple usage
25
29
 
@@ -31,9 +35,10 @@ decoded_ber = RASN1.parse(ber_string, ber: true)
31
35
  ```
32
36
 
33
37
  ## Advanced usage
38
+
34
39
  All examples below will be based on:
35
40
 
36
- ```
41
+ ```text
37
42
  Record ::= SEQUENCE {
38
43
  id INTEGER,
39
44
  room [0] INTEGER OPTIONAL,
@@ -70,6 +75,7 @@ end
70
75
  ```
71
76
 
72
77
  ### Parse a DER-encoded string
78
+
73
79
  ```ruby
74
80
  record = Record.parse(der_string)
75
81
  record[:id] # => RASN1::Types::Integer
@@ -92,6 +98,7 @@ cplx_record[:a_record] # => Record
92
98
  ```
93
99
 
94
100
  ### Generate a DER-encoded string
101
+
95
102
  ```ruby
96
103
  record = Record.new(id: 12)
97
104
  record[:id].to_i # => 12
@@ -110,8 +117,8 @@ record.to_der # => String
110
117
 
111
118
  ### More information
112
119
 
113
- see https://github.com/sdaubert/rasn1/wiki
120
+ see <https://github.com/sdaubert/rasn1/wiki>
114
121
 
115
122
  ## Contributing
116
123
 
117
- Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/rasn1.
124
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/sdaubert/rasn1>.
data/lib/rasn1/model.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'types/constrained'
4
-
5
3
  module RASN1
6
4
  # @abstract
7
5
  # {Model} class is a base class to define ASN.1 models.
@@ -64,39 +62,45 @@ module RASN1
64
62
  # @author Sylvain Daubert
65
63
  # @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
66
64
  class Model # rubocop:disable Metrics/ClassLength
67
- # @private
68
- Elem = Struct.new(:name, :proc_or_class, :content) do
65
+ # @private Base Element
66
+ BaseElem = Struct.new(:name, :proc, :content) do
69
67
  # @param [String,Symbol] name
70
- # @param [Proc,Class] proc_or_class
68
+ # @param [Proc] proc
71
69
  # @param [Array,nil] content
72
- def initialize(name, proc_or_class, content)
73
- if content.is_a?(Array)
74
- duplicate_names = find_all_duplicate_names(content.map(&:name) + [name])
75
- raise ModelValidationError, "Duplicate name #{duplicate_names.first} found" if duplicate_names.any?
76
- end
77
-
70
+ def initialize(name, proc, content)
71
+ check_duplicates(content.map(&:name) + [name]) unless content.nil?
78
72
  super
79
73
  end
80
74
 
81
75
  private
82
76
 
83
- # @param [Array<String>] names
84
77
  # @return [Array<String>] The duplicate names found in the array
85
78
  def find_all_duplicate_names(names)
86
79
  names.group_by { |name| name }
87
80
  .select { |_name, values| values.length > 1 }
88
81
  .keys
89
82
  end
83
+
84
+ def check_duplicates(names)
85
+ duplicates = find_all_duplicate_names(names)
86
+ raise ModelValidationError, "Duplicate name #{duplicates.first} found" if duplicates.any?
87
+ end
90
88
  end
91
89
 
92
- # @private
90
+ # @private Model Element
91
+ ModelElem = Struct.new(:name, :klass)
92
+
93
+ # @private Wrapper Element
93
94
  WrapElem = Struct.new(:element, :options) do
94
- # @return [String]
95
+ # @return [Symbol]
95
96
  def name
96
- "#{element.name}_wrapper"
97
+ :"#{element.name}_wrapper"
97
98
  end
98
99
  end
99
100
 
101
+ # @private Sequence types
102
+ SEQUENCE_TYPES = [Types::Sequence, Types::SequenceOf, Types::Set, Types::SetOf].freeze
103
+
100
104
  # Define helper methods to define models
101
105
  module Accel
102
106
  # @return [Hash]
@@ -107,7 +111,7 @@ module RASN1
107
111
  # @param [Class] model_klass
108
112
  # @return [Elem]
109
113
  def model(name, model_klass)
110
- @root = Elem.new(name, model_klass, nil)
114
+ @root = ModelElem.new(name, model_klass)
111
115
  end
112
116
 
113
117
  # Use a {Wrapper} around a {Types::Base} or a {Model} object
@@ -162,7 +166,7 @@ module RASN1
162
166
  proc = proc do |opts|
163
167
  #{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
164
168
  end
165
- @root = Elem.new(name, proc, options[:content])
169
+ @root = BaseElem.new(name, proc, options[:content])
166
170
  end
167
171
  EVAL
168
172
  end
@@ -179,7 +183,7 @@ module RASN1
179
183
  proc = proc do |opts|
180
184
  #{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
181
185
  end
182
- @root = Elem.new(name, proc, nil)
186
+ @root = BaseElem.new(name, proc, nil)
183
187
  end
184
188
  EVAL
185
189
  end
@@ -206,7 +210,7 @@ module RASN1
206
210
  def objectid(name, options={})
207
211
  options[:name] = name
208
212
  proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
209
- @root = Elem.new(name, proc, nil)
213
+ @root = BaseElem.new(name, proc, nil)
210
214
  end
211
215
 
212
216
  # @param [Symbol,String] name name of object in model
@@ -216,7 +220,7 @@ module RASN1
216
220
  def any(name, options={})
217
221
  options[:name] = name
218
222
  proc = proc { |opts| Types::Any.new(options.merge(opts)) }
219
- @root = Elem.new(name, proc, nil)
223
+ @root = BaseElem.new(name, proc, nil)
220
224
  end
221
225
 
222
226
  # Give type name (aka class name)
@@ -366,19 +370,26 @@ module RASN1
366
370
  self.define_type_accel_base(method_name, prim)
367
371
  end
368
372
 
373
+ # @return [Model, Wrapper, Types::Base]
374
+ attr_reader :root
375
+
369
376
  # Create a new instance of a {Model}
370
377
  # @param [Hash] args
371
378
  def initialize(args={})
372
- root = generate_root
373
- generate_elements(root)
374
- initialize_elements(self, args)
379
+ @elements = {}
380
+ generate_root(args)
381
+ lazy_initialize(args) unless args.empty?
375
382
  end
376
383
 
377
- # Give access to element +name+ in model
378
- # @param [String,Symbol] name
379
- # @return [Types::Base]
384
+ # Access an element of the model by its name
385
+ # @param [Symbol] name
386
+ # @return [Model, Types::Base, Wrapper]
380
387
  def [](name)
381
- @elements[name]
388
+ elt = @elements[name]
389
+ return elt unless elt.is_a?(Proc)
390
+
391
+ # Lazy element -> generate it
392
+ @elements[name] = elt.call
382
393
  end
383
394
 
384
395
  # Set value of element +name+. Element should be a {Types::Base}.
@@ -386,13 +397,20 @@ module RASN1
386
397
  # @param [Object] value
387
398
  # @return [Object] value
388
399
  def []=(name, value)
389
- raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
400
+ # Here, use #[] to force generation for lazy elements
401
+ raise Error, 'cannot set value for a Model' if self[name].is_a?(Model)
402
+
403
+ self[name].value = value
404
+ end
390
405
 
391
- @elements[name].value = value
406
+ # clone @elements and initialize @root from this new @element.
407
+ def initialize_copy(_other)
408
+ @elements = @elements.clone
409
+ @root = @elements[@root_name]
392
410
  end
393
411
 
394
- # Get name from root type
395
- # @return [String,Symbol]
412
+ # Give model name (a.k.a root name)
413
+ # @return [String]
396
414
  def name
397
415
  @root_name
398
416
  end
@@ -409,12 +427,6 @@ module RASN1
409
427
  private_to_h
410
428
  end
411
429
 
412
- # Get root element from model
413
- # @return [Types::Base,Model]
414
- def root
415
- @elements[@root_name]
416
- end
417
-
418
430
  # @return [String]
419
431
  def to_der
420
432
  root.to_der
@@ -427,12 +439,18 @@ module RASN1
427
439
  end
428
440
 
429
441
  # Parse a DER/BER encoded string, and modify object in-place.
430
- # @param [String] str
442
+ # @param [String] der
431
443
  # @param [Boolean] ber accept BER encoding or not
432
444
  # @return [Integer] number of parsed bytes
433
445
  # @raise [ASN1Error] error on parsing
434
- def parse!(str, ber: false)
435
- root.parse!(str.dup.force_encoding('BINARY'), ber: ber)
446
+ def parse!(der, ber: false)
447
+ root.parse!(der, ber: ber)
448
+ end
449
+
450
+ # @private
451
+ # @see Types::Base#do_parse
452
+ def do_parse(der, ber: false)
453
+ root.do_parse(der, ber: ber)
436
454
  end
437
455
 
438
456
  # @overload value
@@ -474,11 +492,13 @@ module RASN1
474
492
  end
475
493
  end
476
494
 
495
+ # Return a hash image of model
496
+ # @return [Hash]
477
497
  # Delegate some methods to root element
478
498
  # @param [Symbol] meth
479
- def method_missing(meth, *args)
480
- if root.respond_to? meth
481
- root.send meth, *args
499
+ def method_missing(meth, *args, **kwargs)
500
+ if root.respond_to?(meth)
501
+ root.send(meth, *args, **kwargs)
482
502
  else
483
503
  super
484
504
  end
@@ -491,7 +511,7 @@ module RASN1
491
511
 
492
512
  # @return [String]
493
513
  def inspect(level=0)
494
- ' ' * level + "(#{type}) #{root.inspect(-level)}"
514
+ "#{' ' * level}(#{type}) #{root.inspect(-level)}"
495
515
  end
496
516
 
497
517
  # Objects are equal if they have same class AND same DER
@@ -503,112 +523,97 @@ module RASN1
503
523
 
504
524
  protected
505
525
 
506
- # Give a (nested) element from its name
507
- # @param [String, Symbol] name
508
- # @return [Model, Types::Base, nil]
509
- def by_name(name)
510
- elt = self[name]
511
- return elt unless elt.nil?
526
+ # Initialize model elements from +args+
527
+ # @param [Hash,Array] args
528
+ # @return [void]
529
+ def lazy_initialize(args)
530
+ case args
531
+ when Hash
532
+ lazy_initialize_hash(args)
533
+ when Array
534
+ lazy_initialize_array(args)
535
+ end
536
+ end
512
537
 
513
- @elements.each_key do |subelt_name|
514
- subelt = self[subelt_name]
515
- if subelt.is_a?(Model)
516
- elt = subelt[name]
517
- return elt unless elt.nil?
538
+ # Initialize an element from a hash
539
+ # @param [Hash] args
540
+ # @return [void]
541
+ def lazy_initialize_hash(args)
542
+ args.each do |name, value|
543
+ element = self[name]
544
+ case element
545
+ when Model
546
+ element.lazy_initialize(value)
547
+ when nil
548
+ else
549
+ element.value = value
518
550
  end
519
551
  end
520
-
521
- nil
522
552
  end
523
553
 
524
- private
525
-
526
- def composed?(elt)
527
- [Types::Sequence, Types::Set].include? elt.class
528
- end
554
+ # Initialize an sequence element from an array
555
+ # @param [Array] args
556
+ # @return [void]
557
+ def lazy_initialize_array(ary)
558
+ raise Error, 'Only sequence types may be initialized with an array' unless SEQUENCE_TYPES.any? { |klass| root.is_a?(klass) }
529
559
 
530
- # proc_or_class:
531
- # * proc: a Types::Base subclass
532
- # * class: a model
533
- def get_type(proc_or_class, options={})
534
- case proc_or_class
535
- when Proc
536
- proc_or_class.call(options)
537
- when Class
538
- proc_or_class.new(options)
560
+ ary.each do |initializer|
561
+ root << initializer
539
562
  end
540
563
  end
541
564
 
542
- def generate_root
543
- class_element = self.class.class_eval { @root }
544
- @root_name = class_element.name
545
- @elements = {}
546
- @elements[@root_name] = case class_element
547
- when WrapElem
548
- generate_wrapper(class_element)
549
- else
550
- get_type(class_element.proc_or_class, self.class.options || {})
551
- end
552
- class_element
553
- end
554
-
555
- def generate_elements(element)
556
- if element.is_a?(WrapElem)
557
- generate_wrapper(element)
558
- return
559
- end
560
- return unless element.content.is_a? Array
565
+ # Give a (nested) element from its name
566
+ # @param [String, Symbol] name
567
+ # @return [Model, Types::Base, nil]
568
+ def by_name(name)
569
+ elt = self[name]
570
+ return elt unless elt.nil?
561
571
 
562
- @elements[name].value = element.content.map do |another_element|
563
- add_subelement(another_element)
572
+ @elements.each_value do |subelt|
573
+ next unless subelt.is_a?(Model)
574
+
575
+ value = subelt.by_name(name)
576
+ return value unless value.nil?
564
577
  end
565
- end
566
578
 
567
- def generate_wrapper(wrap_elem)
568
- inner_elem = wrap_elem.element
569
- subel = add_subelement(inner_elem)
570
- Wrapper.new(subel, wrap_elem.options)
579
+ nil
571
580
  end
572
581
 
573
- def add_subelement(subelement)
574
- case subelement
575
- when Elem
576
- subel = get_type(subelement.proc_or_class)
577
- @elements[subelement.name] = subel
578
- generate_elements(subelement) if composed?(subel) && subelement.content.is_a?(Array)
579
- subel
582
+ private
583
+
584
+ def generate_root(args)
585
+ opts = args.slice(:explicit, :implicit, :optional, :class, :default, :constructed, :tag_value)
586
+ root = self.class.class_eval { @root }
587
+ root_options = self.class.options || {}
588
+ root_options.merge!(opts)
589
+ @root = generate_element(root, root_options)
590
+ @root_name = root.name
591
+ @elements[root.name] = @root
592
+ end
593
+
594
+ def generate_element(elt, opts={})
595
+ case elt
596
+ when BaseElem
597
+ generate_base_element(elt, opts)
598
+ when ModelElem
599
+ elt.klass.new(opts)
580
600
  when WrapElem
581
- generate_wrapper(subelement)
601
+ wrapped = elt.element.is_a?(ModelElem) ? elt.element.klass : generate_element(elt.element)
602
+ wrapper = Wrapper.new(wrapped, elt.options.merge(opts))
603
+ @elements[elt.element.name] = proc { wrapper.element }
604
+ wrapper
582
605
  end
583
606
  end
584
607
 
585
- def initialize_elements(obj, args)
586
- args.each do |name, value|
587
- subobj = obj[name]
588
- next unless subobj
589
-
590
- case value
591
- when Hash
592
- raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless subobj.is_a?(Model)
593
-
594
- initialize_elements(subobj, value)
595
- when Array
596
- initialize_element_from_array(subobj, value)
597
- else
598
- subobj.value = value
599
- end
600
- end
601
- end
608
+ def generate_base_element(elt, opts)
609
+ element = elt.proc.call(opts)
610
+ return element if elt.content.nil?
602
611
 
603
- def initialize_element_from_array(obj, value)
604
- composed = obj.is_a?(Model) ? obj.root : obj
605
- if composed.of_type.is_a?(Model)
606
- value.each do |el|
607
- composed << initialize_elements(composed.of_type.class.new, el)
608
- end
609
- else
610
- value.each { |el| composed << el }
612
+ element.value = elt.content.map do |subel|
613
+ generated = generate_element(subel)
614
+ @elements[subel.name] = generated
611
615
  end
616
+ element
612
617
  end
613
618
 
614
619
  def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
@@ -638,29 +643,31 @@ module RASN1
638
643
 
639
644
  def sequence_of_to_h(elt)
640
645
  if elt.of_type < Model
641
- elt.value.map { |el| el.to_h.values.first }
646
+ elt.value&.map { |el| el.to_h.values.first }
642
647
  else
643
- elt.value.map { |el| private_to_h(el) }
648
+ elt.value&.map { |el| private_to_h(el) }
644
649
  end
645
650
  end
646
651
 
647
652
  def sequence_to_h(seq)
648
- ary = seq.value.map do |el|
653
+ ary = seq.value&.map do |el|
649
654
  next if el.optional? && el.value.nil?
650
655
 
651
656
  case el
652
657
  when Model
653
658
  hsh = el.to_h
654
- hsh = hsh[hsh.keys.first]
655
- [@elements.key(el), hsh]
659
+ [@elements.key(el), hsh[hsh.keys.first]]
656
660
  when Wrapper
657
- [@elements.key(el.element), wrapper_to_h(el)]
661
+ [unwrap_keyname(@elements.key(el)), wrapper_to_h(el)]
658
662
  else
659
663
  [el.name, private_to_h(el)]
660
664
  end
661
665
  end
662
- ary.compact!
663
- ary.to_h
666
+ ary.compact.to_h
667
+ end
668
+
669
+ def unwrap_keyname(key)
670
+ key.to_s.delete_suffix('_wrapper').to_sym
664
671
  end
665
672
 
666
673
  def wrapper_to_h(wrap)
data/lib/rasn1/tracer.rb CHANGED
@@ -83,8 +83,8 @@ module RASN1
83
83
  # @private
84
84
  # Parse +der+ with tracing abillity
85
85
  # @see #parse!
86
- def do_parse_with_tracing(der, ber)
87
- ret = do_parse_without_tracing(der, ber)
86
+ def do_parse_with_tracing(der, ber:)
87
+ ret = do_parse_without_tracing(der, ber: ber)
88
88
  RASN1.tracer.trace(self.trace)
89
89
  ret
90
90
  end
@@ -131,7 +131,7 @@ module RASN1
131
131
  end
132
132
 
133
133
  # @private
134
- # Unpatch {#der_to_value!} to remove tracing ability
134
+ # Unpatch {#der_to_value} to remove tracing ability
135
135
  def stop_tracing
136
136
  alias_method :der_to_value, :der_to_value_without_tracing
137
137
  end
@@ -156,7 +156,7 @@ module RASN1
156
156
  end
157
157
 
158
158
  # @private
159
- # Unpatch {#der_to_value!} to remove tracing ability
159
+ # Unpatch {#der_to_value} to remove tracing ability
160
160
  def stop_tracing
161
161
  alias_method :der_to_value, :der_to_value_without_tracing
162
162
  end
@@ -31,7 +31,7 @@ module RASN1
31
31
  # @param [Boolean] ber if +true+, accept BER encoding
32
32
  # @return [Integer] total number of parsed bytes
33
33
  def parse!(der, ber: false)
34
- total_length, _data = do_parse(der, ber)
34
+ total_length, _data = do_parse(der, ber: ber)
35
35
  total_length
36
36
  end
37
37
 
@@ -70,7 +70,9 @@ module RASN1
70
70
  str << '(ANY) '
71
71
  end
72
72
 
73
- def do_parse(der, ber)
73
+ # @private
74
+ # @see Types::Base#do_parse
75
+ def do_parse(der, ber: false)
74
76
  if der.empty?
75
77
  return [0, ''] if optional?
76
78
 
@@ -78,7 +80,7 @@ module RASN1
78
80
  end
79
81
 
80
82
  id_size = Types.decode_identifier_octets(der).last
81
- total_length, = get_data(der[id_size..-1], ber)
83
+ total_length, = get_data(der[id_size..], ber)
82
84
  total_length += id_size
83
85
 
84
86
  @no_value = false