rasn1 0.14.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fca7c9a3f54b4fb4aea0ab91f4a7e243a4b5874eae10540a04390dcaff340218
4
- data.tar.gz: 77e0ef0cd540c787781bb9aae0aada5a3f7770e6060d1ace5c902afb4b212bcb
3
+ metadata.gz: f4b0dda0fa6e06b6371e74782ecc5b5cc5f9d30dab886a759aa5b9345bd80908
4
+ data.tar.gz: cfad20040d45598fffda3132bb9c417cdf4fca9dd78dda9616a4b7a0a6a98b97
5
5
  SHA512:
6
- metadata.gz: 410feb2673c7667bd1efbab93e7e78e8cca4e0f9a815d6cd768a26acea42afe6bce2bbfa4b090c36f411753a77dec08671e0463600c94e12f21081ecc631a13d
7
- data.tar.gz: f373028c96806e3034471bd5833345303b572842215a02d7d3612c7614557894c8cf3029783af6b09120ce31db81ac8d84414eca28d5ec76991f39d749a7b801
6
+ metadata.gz: a99c4ee238423b561470182c3ac415d9a9ff8f11d626af843d203d5c6aedff6969ec0e4b47f68844e4c62153dd238ba4d852f3855e63a091e8c2cf0643d39d89
7
+ data.tar.gz: 9614d3434f7b05fe01e80bb36cbd82914b565a6e5087e48bc5e44aa7f47d70076ac3b1f542062bf9bc203d8101f4afd21f2ad5b9865011cdff1359e8178c8b19
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,35 @@ 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)
375
- end
376
-
377
- # Give access to element +name+ in model
378
- # @param [String,Symbol] name
379
- # @return [Types::Base]
380
- def [](name)
381
- @elements[name]
379
+ @elements = {}
380
+ generate_root(args)
381
+ lazy_initialize(args) unless args.empty?
382
+ end
383
+
384
+ # @overload [](name)
385
+ # Access an element of the model by its name
386
+ # @param [Symbol] name
387
+ # @return [Model, Types::Base, Wrapper]
388
+ # @overload [](idx)
389
+ # Access an element of root element by its index. Root element must be a {Sequence} or {SequenceOf}.
390
+ # @param [Integer] idx
391
+ # @return [Model, Types::Base, Wrapper]
392
+ def [](name_or_idx)
393
+ case name_or_idx
394
+ when Symbol
395
+ elt = @elements[name_or_idx]
396
+ return elt unless elt.is_a?(Proc)
397
+
398
+ @elements[name_or_idx] = elt.call
399
+ when Integer
400
+ root[name_or_idx]
401
+ end
382
402
  end
383
403
 
384
404
  # Set value of element +name+. Element should be a {Types::Base}.
@@ -386,13 +406,20 @@ module RASN1
386
406
  # @param [Object] value
387
407
  # @return [Object] value
388
408
  def []=(name, value)
389
- raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
409
+ # Here, use #[] to force generation for lazy elements
410
+ raise Error, 'cannot set value for a Model' if self[name].is_a?(Model)
411
+
412
+ self[name].value = value
413
+ end
390
414
 
391
- @elements[name].value = value
415
+ # clone @elements and initialize @root from this new @element.
416
+ def initialize_copy(_other)
417
+ @elements = @elements.clone
418
+ @root = @elements[@root_name]
392
419
  end
393
420
 
394
- # Get name from root type
395
- # @return [String,Symbol]
421
+ # Give model name (a.k.a root name)
422
+ # @return [String]
396
423
  def name
397
424
  @root_name
398
425
  end
@@ -409,12 +436,6 @@ module RASN1
409
436
  private_to_h
410
437
  end
411
438
 
412
- # Get root element from model
413
- # @return [Types::Base,Model]
414
- def root
415
- @elements[@root_name]
416
- end
417
-
418
439
  # @return [String]
419
440
  def to_der
420
441
  root.to_der
@@ -427,12 +448,18 @@ module RASN1
427
448
  end
428
449
 
429
450
  # Parse a DER/BER encoded string, and modify object in-place.
430
- # @param [String] str
451
+ # @param [String] der
431
452
  # @param [Boolean] ber accept BER encoding or not
432
453
  # @return [Integer] number of parsed bytes
433
454
  # @raise [ASN1Error] error on parsing
434
- def parse!(str, ber: false)
435
- root.parse!(str.dup.force_encoding('BINARY'), ber: ber)
455
+ def parse!(der, ber: false)
456
+ root.parse!(der, ber: ber)
457
+ end
458
+
459
+ # @private
460
+ # @see Types::Base#do_parse
461
+ def do_parse(der, ber: false)
462
+ root.do_parse(der, ber: ber)
436
463
  end
437
464
 
438
465
  # @overload value
@@ -474,11 +501,13 @@ module RASN1
474
501
  end
475
502
  end
476
503
 
504
+ # Return a hash image of model
505
+ # @return [Hash]
477
506
  # Delegate some methods to root element
478
507
  # @param [Symbol] meth
479
- def method_missing(meth, *args)
480
- if root.respond_to? meth
481
- root.send meth, *args
508
+ def method_missing(meth, *args, **kwargs)
509
+ if root.respond_to?(meth)
510
+ root.send(meth, *args, **kwargs)
482
511
  else
483
512
  super
484
513
  end
@@ -491,7 +520,7 @@ module RASN1
491
520
 
492
521
  # @return [String]
493
522
  def inspect(level=0)
494
- ' ' * level + "(#{type}) #{root.inspect(-level)}"
523
+ "#{' ' * level}(#{type}) #{root.inspect(-level)}"
495
524
  end
496
525
 
497
526
  # Objects are equal if they have same class AND same DER
@@ -503,127 +532,121 @@ module RASN1
503
532
 
504
533
  protected
505
534
 
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?
535
+ # Initialize model elements from +args+
536
+ # @param [Hash,Array] args
537
+ # @return [void]
538
+ def lazy_initialize(args)
539
+ case args
540
+ when Hash
541
+ lazy_initialize_hash(args)
542
+ when Array
543
+ lazy_initialize_array(args)
544
+ end
545
+ end
512
546
 
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?
547
+ # Initialize an element from a hash
548
+ # @param [Hash] args
549
+ # @return [void]
550
+ def lazy_initialize_hash(args)
551
+ args.each do |name, value|
552
+ element = self[name]
553
+ case element
554
+ when Model
555
+ element.lazy_initialize(value)
556
+ when nil
557
+ else
558
+ element.value = value
518
559
  end
519
560
  end
520
-
521
- nil
522
561
  end
523
562
 
524
- private
525
-
526
- def composed?(elt)
527
- [Types::Sequence, Types::Set].include? elt.class
528
- end
563
+ # Initialize an sequence element from an array
564
+ # @param [Array] args
565
+ # @return [void]
566
+ def lazy_initialize_array(ary)
567
+ raise Error, 'Only sequence types may be initialized with an array' unless SEQUENCE_TYPES.any? { |klass| root.is_a?(klass) }
529
568
 
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)
569
+ ary.each do |initializer|
570
+ root << initializer
539
571
  end
540
572
  end
541
573
 
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
574
+ # Give a (nested) element from its name
575
+ # @param [String, Symbol] name
576
+ # @return [Model, Types::Base, nil]
577
+ def by_name(name)
578
+ elt = self[name]
579
+ return elt unless elt.nil?
561
580
 
562
- @elements[name].value = element.content.map do |another_element|
563
- add_subelement(another_element)
581
+ @elements.each_value do |subelt|
582
+ next unless subelt.is_a?(Model)
583
+
584
+ value = subelt.by_name(name)
585
+ return value unless value.nil?
564
586
  end
565
- end
566
587
 
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)
588
+ nil
571
589
  end
572
590
 
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
591
+ private
592
+
593
+ def generate_root(args)
594
+ opts = args.slice(:name, :explicit, :implicit, :optional, :class, :default, :constructed, :tag_value)
595
+ root = self.class.class_eval { @root }
596
+ root_options = self.class.options || {}
597
+ root_options.merge!(opts)
598
+ @root_name = args[:name] || root.name
599
+ @root = generate_element(root, root_options)
600
+ @elements[@root_name] = @root
601
+ end
602
+
603
+ def generate_element(elt, opts={})
604
+ case elt
605
+ when BaseElem
606
+ generate_base_element(elt, opts)
607
+ when ModelElem
608
+ opts[:name] ||= elt.name
609
+ elt.klass.new(opts)
580
610
  when WrapElem
581
- generate_wrapper(subelement)
611
+ generate_wrapper_element(elt, opts)
582
612
  end
583
613
  end
584
614
 
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
615
+ def generate_wrapper_element(elt, opts)
616
+ wrapped = elt.element.is_a?(ModelElem) ? elt.element.klass : generate_element(elt.element)
617
+ options = elt.options.merge(opts)
618
+ options[:name] = elt.element.name if elt.element.is_a?(ModelElem)
619
+ wrapper = Wrapper.new(wrapped, options)
620
+ # Use a proc as wrapper may be lazy
621
+ @elements[elt.element.name] = proc { wrapper.element }
622
+ wrapper
601
623
  end
602
624
 
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 }
625
+ def generate_base_element(elt, opts)
626
+ element = elt.proc.call(opts)
627
+ return element if elt.content.nil?
628
+
629
+ element.value = elt.content.map do |subel|
630
+ generated = generate_element(subel)
631
+ @elements[subel.name] = generated
611
632
  end
633
+ element
612
634
  end
613
635
 
636
+ # @author sdaubert
637
+ # @author lemontree55
638
+ # @author adfoster-r7
614
639
  def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
615
640
  my_element = element || root
616
- my_element = my_element.root if my_element.is_a?(Model)
617
641
  value = case my_element
642
+ when Model
643
+ model_to_h(my_element)
618
644
  when Types::SequenceOf
619
645
  sequence_of_to_h(my_element)
620
646
  when Types::Sequence
621
647
  sequence_to_h(my_element)
622
- # @author adfoster-r7
623
648
  when Types::Choice
624
- raise ChoiceError.new(my_element) if my_element.chosen.nil?
625
-
626
- private_to_h(my_element.value[my_element.chosen])
649
+ choice_to_h(my_element)
627
650
  when Wrapper
628
651
  wrapper_to_h(my_element)
629
652
  else
@@ -636,31 +659,48 @@ module RASN1
636
659
  end
637
660
  end
638
661
 
662
+ def model_to_h(elt)
663
+ hsh = elt.to_h
664
+ if root.is_a?(Types::Choice)
665
+ hsh[hsh.keys.first]
666
+ else
667
+ { @elements.key(elt) => hsh[hsh.keys.first] }
668
+ end
669
+ end
670
+
639
671
  def sequence_of_to_h(elt)
640
672
  if elt.of_type < Model
641
- elt.value.map { |el| el.to_h.values.first }
673
+ elt.value&.map { |el| el.to_h.values.first }
642
674
  else
643
- elt.value.map { |el| private_to_h(el) }
675
+ elt.value&.map { |el| private_to_h(el) }
644
676
  end
645
677
  end
646
678
 
647
679
  def sequence_to_h(seq)
648
- ary = seq.value.map do |el|
680
+ ary = seq.value&.map do |el|
649
681
  next if el.optional? && el.value.nil?
650
682
 
651
683
  case el
652
684
  when Model
653
- hsh = el.to_h
654
- hsh = hsh[hsh.keys.first]
655
- [@elements.key(el), hsh]
685
+ model_to_h(el).to_a[0]
656
686
  when Wrapper
657
- [@elements.key(el.element), wrapper_to_h(el)]
687
+ [unwrap_keyname(@elements.key(el)), wrapper_to_h(el)]
658
688
  else
659
689
  [el.name, private_to_h(el)]
660
690
  end
661
691
  end
662
- ary.compact!
663
- ary.to_h
692
+ ary.compact.to_h
693
+ end
694
+
695
+ def choice_to_h(elt)
696
+ raise ChoiceError.new(elt) if elt.chosen.nil?
697
+
698
+ chosen = elt.value[elt.chosen]
699
+ { chosen.name => private_to_h(chosen) }
700
+ end
701
+
702
+ def unwrap_keyname(key)
703
+ key.to_s.delete_suffix('_wrapper').to_sym
664
704
  end
665
705
 
666
706
  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