rasn1 0.10.0 → 0.12.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: 0d38c6038ef58e60eb51d02fc20416188918a736386c4273a713f1be2d2535a1
4
- data.tar.gz: fd3b045acdd39707540c3909f24aa8a2e2ae6ed11e46339f90903b7ef96da0df
3
+ metadata.gz: 552859c3f49caec5374c8db4459ad23e1f9c43e930b0d6511cab8f997c798d20
4
+ data.tar.gz: 29eaf2fc529b470c9bc6283efbaf82e580d5b4b86d5c46920ee4a882a18b0d72
5
5
  SHA512:
6
- metadata.gz: f21443bccf45e549a1999bedcef1858ff175ab9e65bdb09f048ad4723d4ac64be80338cbff491ddaf31654aabaa1ad567af4925edf24297e757a42c901684ff2
7
- data.tar.gz: ad0a89d6a6d634b83fe89f45f64f327f5740f68be8dfe6ca143bcfcf028e387c9859ec665da82e5f0f8d3576133ef74cf63b24d511da19e2589a53909bcf95ce
6
+ metadata.gz: 9e5df0e417db7e1f02accae9cc4f17b4a49975d1dc17b716dbf587bb3f5e8ca68ed842d22358d7de037041255c164df5a92c6ada52adba944f0ac635793a64b1
7
+ data.tar.gz: e193284ea13ca5f80a460b8b390839a0283a8424ca8201f24cbd6113919399fcd5b83a2fc39dd5b26925187e19930a1aceccb1485255de8301435e20a2c82761
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # Base error class
5
+ class Error < StandardError; end
6
+
7
+ # ASN.1 encoding/decoding error
8
+ class ASN1Error < Error; end
9
+
10
+ # ASN.1 class error
11
+ class ClassError < Error
12
+ # @return [String]
13
+ def message
14
+ "Tag class should be a symbol among: #{Types::Base::CLASSES.keys.join(', ')}"
15
+ end
16
+ end
17
+
18
+ # Enumerated error
19
+ class EnumeratedError < Error; end
20
+
21
+ # CHOICE error: #chosen not set
22
+ class ChoiceError < RASN1::Error
23
+ # @param [Types::Base] object
24
+ def initialize(object)
25
+ @object = object
26
+ super()
27
+ end
28
+
29
+ def message
30
+ "CHOICE #{@object.name}: #chosen not set"
31
+ end
32
+ end
33
+
34
+ # Exception raised when a constraint is not verified on a constrained type.
35
+ # @version 0.11.0
36
+ # @author Sylvain Daubert
37
+ class ConstraintError < Error
38
+ # @param [Types::Base] object
39
+ def initialize(object)
40
+ @object = object
41
+ super()
42
+ end
43
+
44
+ # @return [String]
45
+ def message
46
+ "Constraint not verified on #{@object.inspect}"
47
+ end
48
+ end
49
+
50
+ # Exception raised when model validation fails
51
+ # @since 0.12.0
52
+ class ModelValidationError < Error
53
+ end
54
+ end
data/lib/rasn1/model.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'types/constrained'
4
+
3
5
  module RASN1
4
6
  # @abstract
5
7
  # {Model} class is a base class to define ASN.1 models.
@@ -18,6 +20,8 @@ module RASN1
18
20
  # integer(:house, explicit: 1, default: 0)])
19
21
  # end
20
22
  #
23
+ # In a model, each element must have a unique name.
24
+ #
21
25
  # === Parse a DER-encoded string
22
26
  # record = Record.parse(der_string)
23
27
  # record[:id] # => RASN1::Types::Integer
@@ -58,11 +62,38 @@ module RASN1
58
62
  # All methods defined by root may be delegated by model, unless model also defines
59
63
  # this method.
60
64
  # @author Sylvain Daubert
65
+ # @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
61
66
  class Model # rubocop:disable Metrics/ClassLength
62
67
  # @private
63
- Elem = Struct.new(:name, :proc_or_class, :content)
68
+ Elem = Struct.new(:name, :proc_or_class, :content) do
69
+ def initialize(name, proc_or_class, content)
70
+ if content.is_a?(Array)
71
+ duplicate_names = find_all_duplicate_names(content.map(&:name) + [name])
72
+ raise ModelValidationError, "Duplicate name #{duplicate_names.first} found" if duplicate_names.any?
73
+ end
74
+
75
+ super
76
+ end
64
77
 
65
- class << self
78
+ private
79
+
80
+ # @param [Array<String>] names
81
+ # @return [Array<String>] The duplicate names found in the array
82
+ def find_all_duplicate_names(names)
83
+ names.group_by { |name| name }
84
+ .select { |_name, values| values.length > 1 }
85
+ .keys
86
+ end
87
+ end
88
+
89
+ # @private
90
+ WrapElem = Struct.new(:element, :options) do
91
+ def name
92
+ "#{element.name}_wrapper"
93
+ end
94
+ end
95
+
96
+ module Accel
66
97
  # @return [Hash]
67
98
  attr_reader :options
68
99
 
@@ -74,6 +105,15 @@ module RASN1
74
105
  @root = Elem.new(name, model_klass, nil)
75
106
  end
76
107
 
108
+ # Use a {Wrapper} around a {Types::Base} or a {Model} object
109
+ # @param [Types::Base,Model] element
110
+ # @param [Hash] options
111
+ # @return [WrapElem]
112
+ # @since 0.12
113
+ def wrapper(element, options={})
114
+ @root = WrapElem.new(element, options)
115
+ end
116
+
77
117
  # Update options of root element.
78
118
  # May be used when subclassing.
79
119
  # class Model1 < RASN1::Model
@@ -87,8 +127,13 @@ module RASN1
87
127
  # end
88
128
  # @param [Hash] options
89
129
  # @return [void]
130
+ # @since 0.12.0 may change name through +:name+
90
131
  def root_options(options)
91
132
  @options = options
133
+ return unless options.key?(:name)
134
+
135
+ @root = @root.dup
136
+ @root.name = options[:name]
92
137
  end
93
138
 
94
139
  # On inheritance, create +@root+ class variable
@@ -100,120 +145,44 @@ module RASN1
100
145
  klass.class_eval { @root = root }
101
146
  end
102
147
 
103
- # @method sequence(name, options)
104
- # @param [Symbol,String] name name of object in model
105
- # @param [Hash] options
106
- # @return [Elem]
107
- # @see Types::Sequence#initialize
108
- # @method set(name, options)
109
- # @param [Symbol,String] name name of object in model
110
- # @param [Hash] options
111
- # @return [Elem]
112
- # @see Types::Set#initialize
113
- # @method choice(name, options)
114
- # @param [Symbol,String] name name of object in model
115
- # @param [Hash] options
116
- # @return [Elem]
117
- # @see Types::Choice#initialize
118
- %w[sequence set choice].each do |type|
119
- class_eval "def #{type}(name, options={})\n" \
120
- " options.merge!(name: name)\n" \
121
- " proc = proc do |opts|\n" \
122
- " Types::#{type.capitalize}.new(options.merge(opts))\n" \
123
- " end\n" \
124
- " @root = Elem.new(name, proc, options[:content])\n" \
125
- 'end'
148
+ # @since 0.11.0
149
+ # @since 0.12.0 track source location on error (adfoster-r7)
150
+ def define_type_accel_base(accel_name, klass)
151
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
152
+ def #{accel_name}(name, options={}) # def sequence(name, type, options)
153
+ options[:name] = name
154
+ proc = proc do |opts|
155
+ #{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
156
+ end
157
+ @root = Elem.new(name, proc, options[:content])
158
+ end
159
+ EVAL
126
160
  end
127
161
 
128
- # @method sequence_of(name, type, options)
129
- # @param [Symbol,String] name name of object in model
130
- # @param [Model, Types::Base] type type for SEQUENCE OF
131
- # @param [Hash] options
132
- # @return [Elem]
133
- # @see Types::SequenceOf#initialize
134
- # @method set_of(name, type, options)
135
- # @param [Symbol,String] name name of object in model
136
- # @param [Model, Types::Base] type type for SET OF
137
- # @param [Hash] options
138
- # @return [Elem]
139
- # @see Types::SetOf#initialize
140
- %w[sequence set].each do |type|
141
- klass_name = "Types::#{type.capitalize}Of"
142
- class_eval "def #{type}_of(name, type, options={})\n" \
143
- " options.merge!(name: name)\n" \
144
- " proc = proc do |opts|\n" \
145
- " #{klass_name}.new(type, options.merge(opts))\n" \
146
- " end\n" \
147
- " @root = Elem.new(name, proc, nil)\n" \
148
- 'end'
162
+ # @since 0.11.0
163
+ # @since 0.12.0 track source location on error (adfoster-r7)
164
+ def define_type_accel_of(accel_name, klass)
165
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
166
+ def #{accel_name}_of(name, type, options={}) # def sequence_of(name, type, options)
167
+ options[:name] = name
168
+ proc = proc do |opts|
169
+ #{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
170
+ end
171
+ @root = Elem.new(name, proc, nil)
172
+ end
173
+ EVAL
149
174
  end
150
175
 
151
- # @method boolean(name, options)
152
- # @param [Symbol,String] name name of object in model
153
- # @param [Hash] options
154
- # @return [Elem]
155
- # @see Types::Boolean#initialize
156
- # @method integer(name, options)
157
- # @param [Symbol,String] name name of object in model
158
- # @param [Hash] options
159
- # @return [Elem]
160
- # @see Types::Integer#initialize
161
- # @method bit_string(name, options)
162
- # @param [Symbol,String] name name of object in model
163
- # @param [Hash] options
164
- # @return [Elem]
165
- # @see Types::BitString#initialize
166
- # @method octet_string(name, options)
167
- # @param [Symbol,String] name name of object in model
168
- # @param [Hash] options
169
- # @return [Elem]
170
- # @see Types::OctetString#initialize
171
- # @method null(name, options)
172
- # @param [Symbol,String] name name of object in model
173
- # @param [Hash] options
174
- # @return [Elem]
175
- # @see Types::Null#initialize
176
- # @method enumerated(name, options)
177
- # @param [Symbol,String] name name of object in model
178
- # @param [Hash] options
179
- # @return [Elem]
180
- # @see Types::Enumerated#initialize
181
- # @method utf8_string(name, options)
182
- # @param [Symbol,String] name name of object in model
183
- # @param [Hash] options
184
- # @return [Elem]
185
- # @see Types::Utf8String#initialize
186
- # @method numeric_string(name, options)
187
- # @param [Symbol,String] name name of object in model
188
- # @param [Hash] options
189
- # @return [Elem]
190
- # @see Types::NumericString#initialize
191
- # @method printable_string(name, options)
192
- # @param [Symbol,String] name name of object in model
193
- # @param [Hash] options
194
- # @return [Elem]
195
- # @see Types::PrintableString#initialize
196
- # @method visible_string(name, options)
197
- # @param [Symbol,String] name name of object in model
198
- # @param [Hash] options
199
- # @return [Elem]
200
- # @see Types::VisibleString#initialize
201
- # @method ia5_string(name, options)
202
- # @param [Symbol,String] name name of object in model
203
- # @param [Hash] options
204
- # @return [Elem]
205
- # @see Types::IA5String#initialize
206
- Types.primitives.each do |prim|
207
- next if prim == Types::ObjectId
208
-
209
- method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
210
- class_eval "def #{method_name}(name, options={})\n" \
211
- " options.merge!(name: name)\n" \
212
- " proc = proc do |opts|\n" \
213
- " #{prim}.new(options.merge(opts))\n" \
214
- " end\n" \
215
- " @root = Elem.new(name, proc, nil)\n" \
216
- 'end'
176
+ # Define an accelarator to access a type in a model definition
177
+ # @param [String] accel_name
178
+ # @param [Class] klass
179
+ # @since 0.11.0
180
+ def define_type_accel(accel_name, klass)
181
+ if klass < Types::SequenceOf
182
+ define_type_accel_of(accel_name, klass)
183
+ else
184
+ define_type_accel_base(accel_name, klass)
185
+ end
217
186
  end
218
187
 
219
188
  # @param [Symbol,String] name name of object in model
@@ -258,11 +227,126 @@ module RASN1
258
227
  end
259
228
  end
260
229
 
230
+ extend Accel
231
+
232
+ # @!method sequence(name, options)
233
+ # @!scope class
234
+ # @param [Symbol,String] name name of object in model
235
+ # @param [Hash] options
236
+ # @return [Elem]
237
+ # @see Types::Sequence#initialize
238
+ # @!method set(name, options)
239
+ # @!scope class
240
+ # @param [Symbol,String] name name of object in model
241
+ # @param [Hash] options
242
+ # @return [Elem]
243
+ # @see Types::Set#initialize
244
+ # @!method choice(name, options)
245
+ # @!scope class
246
+ # @param [Symbol,String] name name of object in model
247
+ # @param [Hash] options
248
+ # @return [Elem]
249
+ # @see Types::Choice#initialize
250
+ %w[sequence set choice].each do |type|
251
+ self.define_type_accel_base(type, Types.const_get(type.capitalize))
252
+ end
253
+
254
+ # @!method sequence_of(name, type, options)
255
+ # @!scope class
256
+ # @param [Symbol,String] name name of object in model
257
+ # @param [Model, Types::Base] type type for SEQUENCE OF
258
+ # @param [Hash] options
259
+ # @return [Elem]
260
+ # @see Types::SequenceOf#initialize
261
+ # @!method set_of(name, type, options)
262
+ # @!scope class
263
+ # @param [Symbol,String] name name of object in model
264
+ # @param [Model, Types::Base] type type for SET OF
265
+ # @param [Hash] options
266
+ # @return [Elem]
267
+ # @see Types::SetOf#initialize
268
+ %w[sequence set].each do |type|
269
+ define_type_accel_of(type, Types.const_get("#{type.capitalize}Of"))
270
+ end
271
+
272
+ # @!method boolean(name, options)
273
+ # @!scope class
274
+ # @param [Symbol,String] name name of object in model
275
+ # @param [Hash] options
276
+ # @return [Elem]
277
+ # @see Types::Boolean#initialize
278
+ # @!method integer(name, options)
279
+ # @!scope class
280
+ # @param [Symbol,String] name name of object in model
281
+ # @param [Hash] options
282
+ # @return [Elem]
283
+ # @see Types::Integer#initialize
284
+ # @!method bit_string(name, options)
285
+ # @!scope class
286
+ # @param [Symbol,String] name name of object in model
287
+ # @param [Hash] options
288
+ # @return [Elem]
289
+ # @see Types::BitString#initialize
290
+ # @!method octet_string(name, options)
291
+ # @!scope class
292
+ # @param [Symbol,String] name name of object in model
293
+ # @param [Hash] options
294
+ # @return [Elem]
295
+ # @see Types::OctetString#initialize
296
+ # @!method null(name, options)
297
+ # @!scope class
298
+ # @param [Symbol,String] name name of object in model
299
+ # @param [Hash] options
300
+ # @return [Elem]
301
+ # @see Types::Null#initialize
302
+ # @!method enumerated(name, options)
303
+ # @!scope class
304
+ # @param [Symbol,String] name name of object in model
305
+ # @param [Hash] options
306
+ # @return [Elem]
307
+ # @see Types::Enumerated#initialize
308
+ # @!method utf8_string(name, options)
309
+ # @!scope class
310
+ # @param [Symbol,String] name name of object in model
311
+ # @param [Hash] options
312
+ # @return [Elem]
313
+ # @see Types::Utf8String#initialize
314
+ # @!method numeric_string(name, options)
315
+ # @!scope class
316
+ # @param [Symbol,String] name name of object in model
317
+ # @param [Hash] options
318
+ # @return [Elem]
319
+ # @see Types::NumericString#initialize
320
+ # @!method printable_string(name, options)
321
+ # @!scope class
322
+ # @param [Symbol,String] name name of object in model
323
+ # @param [Hash] options
324
+ # @return [Elem]
325
+ # @see Types::PrintableString#initialize
326
+ # @!method visible_string(name, options)
327
+ # @!scope class
328
+ # @param [Symbol,String] name name of object in model
329
+ # @param [Hash] options
330
+ # @return [Elem]
331
+ # @see Types::VisibleString#initialize
332
+ # @!method ia5_string(name, options)
333
+ # @!scope class
334
+ # @param [Symbol,String] name name of object in model
335
+ # @param [Hash] options
336
+ # @return [Elem]
337
+ # @see Types::IA5String#initialize
338
+ Types.primitives.each do |prim|
339
+ next if prim == Types::ObjectId
340
+
341
+ method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
342
+ self.define_type_accel_base(method_name, prim)
343
+ end
344
+
261
345
  # Create a new instance of a {Model}
262
346
  # @param [Hash] args
263
347
  def initialize(args={})
264
348
  root = generate_root
265
- set_elements(root)
349
+ generate_elements(root)
266
350
  initialize_elements(self, args)
267
351
  end
268
352
 
@@ -273,7 +357,7 @@ module RASN1
273
357
  @elements[name]
274
358
  end
275
359
 
276
- # Set value of element +name+. Element should be a {Base}.
360
+ # Set value of element +name+. Element should be a {Types::Base}.
277
361
  # @param [String,Symbol] name
278
362
  # @param [Object] value
279
363
  # @return [Object] value
@@ -425,7 +509,7 @@ module RASN1
425
509
  when Proc
426
510
  proc_or_class.call(options)
427
511
  when Class
428
- proc_or_class.new
512
+ proc_or_class.new(options)
429
513
  end
430
514
  end
431
515
 
@@ -437,14 +521,33 @@ module RASN1
437
521
  class_element
438
522
  end
439
523
 
440
- def set_elements(element) # rubocop:disable Naming/AccessorMethodName
524
+ def generate_elements(element)
525
+ if element.is_a?(WrapElem)
526
+ generate_wrapper(element)
527
+ return
528
+ end
441
529
  return unless element.content.is_a? Array
442
530
 
443
531
  @elements[name].value = element.content.map do |another_element|
444
- subel = get_type(another_element.proc_or_class)
445
- @elements[another_element.name] = subel
446
- set_elements(another_element) if composed?(subel) && another_element.content.is_a?(Array)
532
+ add_subelement(another_element)
533
+ end
534
+ end
535
+
536
+ def generate_wrapper(wrap_elem)
537
+ inner_elem = wrap_elem.element
538
+ subel = add_subelement(inner_elem)
539
+ Wrapper.new(subel, wrap_elem.options)
540
+ end
541
+
542
+ def add_subelement(subelement)
543
+ case subelement
544
+ when Elem
545
+ subel = get_type(subelement.proc_or_class)
546
+ @elements[subelement.name] = subel
547
+ generate_elements(subelement) if composed?(subel) && subelement.content.is_a?(Array)
447
548
  subel
549
+ when WrapElem
550
+ generate_wrapper(subelement)
448
551
  end
449
552
  end
450
553
 
@@ -454,7 +557,7 @@ module RASN1
454
557
 
455
558
  case value
456
559
  when Hash
457
- raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model
560
+ raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a?(Model)
458
561
 
459
562
  initialize_elements obj[name], value
460
563
  when Array
@@ -476,7 +579,7 @@ module RASN1
476
579
  end
477
580
  end
478
581
 
479
- def private_to_h(element=nil)
582
+ def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
480
583
  my_element = element || root
481
584
  my_element = my_element.root if my_element.is_a?(Model)
482
585
  value = case my_element
@@ -484,6 +587,13 @@ module RASN1
484
587
  sequence_of_to_h(my_element)
485
588
  when Types::Sequence
486
589
  sequence_to_h(my_element)
590
+ # @author adfoster-r7
591
+ when Types::Choice
592
+ raise ChoiceError.new(my_element) if my_element.chosen.nil?
593
+
594
+ private_to_h(my_element.value[my_element.chosen])
595
+ when Wrapper
596
+ wrapper_to_h(my_element)
487
597
  else
488
598
  my_element.value
489
599
  end
@@ -506,11 +616,29 @@ module RASN1
506
616
  ary = seq.value.map do |el|
507
617
  next if el.optional? && el.value.nil?
508
618
 
509
- name = el.is_a?(Model) ? @elements.key(el) : el.name
510
- [name, private_to_h(el)]
619
+ case el
620
+ when Model
621
+ hsh = el.to_h
622
+ hsh = hsh[hsh.keys.first]
623
+ [@elements.key(el), hsh]
624
+ when Wrapper
625
+ [@elements.key(el.element), wrapper_to_h(el)]
626
+ else
627
+ [el.name, private_to_h(el)]
628
+ end
511
629
  end
512
630
  ary.compact!
513
631
  ary.to_h
514
632
  end
633
+
634
+ def wrapper_to_h(wrap)
635
+ case wrap.element
636
+ when Model
637
+ hsh = wrap.element.to_h
638
+ hsh[hsh.keys.first]
639
+ else
640
+ private_to_h(wrap.element)
641
+ end
642
+ end
515
643
  end
516
644
  end
@@ -21,6 +21,10 @@ module RASN1
21
21
  end
22
22
  end
23
23
 
24
+ def can_build?
25
+ value? || !optional?
26
+ end
27
+
24
28
  # Parse a DER string. This method updates object: {#value} will be a DER
25
29
  # string.
26
30
  # @param [String] der DER string