rasn1 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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