rasn1 0.10.0 → 0.12.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: 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