rasn2 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 +7 -0
- data/LICENSE +22 -0
- data/README.md +4 -0
- data/lib/rasn1/errors.rb +55 -0
- data/lib/rasn1/helpers/colorize.rb +76 -0
- data/lib/rasn1/model.rb +831 -0
- data/lib/rasn1/schema_parser.rb +470 -0
- data/lib/rasn1/tracer.rb +200 -0
- data/lib/rasn1/types/any.rb +98 -0
- data/lib/rasn1/types/base.rb +675 -0
- data/lib/rasn1/types/bit_string.rb +100 -0
- data/lib/rasn1/types/bmp_string.rb +22 -0
- data/lib/rasn1/types/boolean.rb +57 -0
- data/lib/rasn1/types/choice.rb +158 -0
- data/lib/rasn1/types/constrained.rb +51 -0
- data/lib/rasn1/types/constructed.rb +51 -0
- data/lib/rasn1/types/enumerated.rb +44 -0
- data/lib/rasn1/types/generalized_time.rb +156 -0
- data/lib/rasn1/types/ia5_string.rb +21 -0
- data/lib/rasn1/types/integer.rb +166 -0
- data/lib/rasn1/types/null.rb +43 -0
- data/lib/rasn1/types/numeric_string.rb +41 -0
- data/lib/rasn1/types/object_id.rb +64 -0
- data/lib/rasn1/types/octet_string.rb +52 -0
- data/lib/rasn1/types/primitive.rb +13 -0
- data/lib/rasn1/types/printable_string.rb +42 -0
- data/lib/rasn1/types/sequence.rb +105 -0
- data/lib/rasn1/types/sequence_of.rb +199 -0
- data/lib/rasn1/types/set.rb +28 -0
- data/lib/rasn1/types/set_of.rb +18 -0
- data/lib/rasn1/types/tag.rb +189 -0
- data/lib/rasn1/types/universal_string.rb +22 -0
- data/lib/rasn1/types/utc_time.rb +67 -0
- data/lib/rasn1/types/utf8_string.rb +21 -0
- data/lib/rasn1/types/visible_string.rb +30 -0
- data/lib/rasn1/types.rb +149 -0
- data/lib/rasn1/value_notation.rb +256 -0
- data/lib/rasn1/version.rb +6 -0
- data/lib/rasn1/wrapper.rb +279 -0
- data/lib/rasn1.rb +51 -0
- metadata +123 -0
data/lib/rasn1/model.rb
ADDED
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
# @abstract
|
|
5
|
+
# {Model} class is a base class to define ASN.1 models.
|
|
6
|
+
# == Create a simple ASN.1 model
|
|
7
|
+
# Given this ASN.1 example:
|
|
8
|
+
# Record ::= SEQUENCE {
|
|
9
|
+
# id INTEGER,
|
|
10
|
+
# room [0] IMPLICIT INTEGER OPTIONAL,
|
|
11
|
+
# house [1] EXPLICIT INTEGER DEFAULT 0
|
|
12
|
+
# }
|
|
13
|
+
# you may create your model like this:
|
|
14
|
+
# class Record < RASN1::Model
|
|
15
|
+
# sequence(:record,
|
|
16
|
+
# content: [integer(:id),
|
|
17
|
+
# integer(:room, implicit: 0, optional: true),
|
|
18
|
+
# integer(:house, explicit: 1, default: 0)])
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# Since 0.17.0, content may also be defined through a block. This is strictly
|
|
22
|
+
# equivalent to the +:content+ option:
|
|
23
|
+
# class Record < RASN1::Model
|
|
24
|
+
# sequence :record do
|
|
25
|
+
# integer :id
|
|
26
|
+
# integer :room, implicit: 0, optional: true
|
|
27
|
+
# integer :house, explicit: 1, default: 0
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
# Blocks may be nested, and may use all model helpers (+#model+, +#wrapper+, +#sequence_of+, ...):
|
|
31
|
+
# class PersonnelRecord < RASN1::Model
|
|
32
|
+
# sequence :personnelRecord do
|
|
33
|
+
# utf8_string :name
|
|
34
|
+
# utf8_string :title
|
|
35
|
+
# integer :age
|
|
36
|
+
# boolean :employed
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
# When both +:content+ option and a block are given, block elements are appended to
|
|
40
|
+
# those defined by the option.
|
|
41
|
+
#
|
|
42
|
+
# In a model, each element must have a unique name.
|
|
43
|
+
#
|
|
44
|
+
# === Parse a DER-encoded string
|
|
45
|
+
# record = Record.parse(der_string)
|
|
46
|
+
# record[:id] # => RASN1::Types::Integer
|
|
47
|
+
# record[:id].value # => Integer
|
|
48
|
+
# record[:id].to_i # => Integer
|
|
49
|
+
# record[:id].asn1_class # => Symbol
|
|
50
|
+
# record[:id].optional? # => false
|
|
51
|
+
# record[:id].default # => nil
|
|
52
|
+
# record[:room].optional # => true
|
|
53
|
+
# record[:house].default # => 0
|
|
54
|
+
#
|
|
55
|
+
# You may also parse a BER-encoded string this way:
|
|
56
|
+
# record = Record.parse(der_string, ber: true)
|
|
57
|
+
#
|
|
58
|
+
# === Generate a DER-encoded string
|
|
59
|
+
# record = Record.new(id: 12, room: 24)
|
|
60
|
+
# record.to_der
|
|
61
|
+
#
|
|
62
|
+
# == Create a more complex model
|
|
63
|
+
# Models may be nested. For example:
|
|
64
|
+
# class Record2 < RASN1::Model
|
|
65
|
+
# sequence(:record2,
|
|
66
|
+
# content: [boolean(:rented, default: false),
|
|
67
|
+
# model(:a_record, Record)])
|
|
68
|
+
# end
|
|
69
|
+
# Set values like this:
|
|
70
|
+
# record2 = Record2.new
|
|
71
|
+
# record2[:rented] = true
|
|
72
|
+
# record2[:a_record][:id] = 65537
|
|
73
|
+
# record2[:a_record][:room] = 43
|
|
74
|
+
# or like this:
|
|
75
|
+
# record2 = Record2.new(rented: true, a_record: { id: 65537, room: 43 })
|
|
76
|
+
# Same model, using a block:
|
|
77
|
+
# class Record2 < RASN1::Model
|
|
78
|
+
# sequence :record2 do
|
|
79
|
+
# boolean :rented, default: false
|
|
80
|
+
# model :a_record, Record
|
|
81
|
+
# end
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# == Delegation
|
|
85
|
+
# {Model} may delegate some methods to its root element. Thus, if root element
|
|
86
|
+
# is, for example, a {Types::Choice}, model may delegate +#chosen+ and +#chosen_value+.
|
|
87
|
+
#
|
|
88
|
+
# All methods defined by root may be delegated by model, unless model also defines
|
|
89
|
+
# this method.
|
|
90
|
+
# @author Sylvain Daubert
|
|
91
|
+
# @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
|
|
92
|
+
class Model # rubocop:disable Metrics/ClassLength
|
|
93
|
+
# @private Base Element
|
|
94
|
+
BaseElem = Struct.new(:name, :proc, :content) do
|
|
95
|
+
# @param [String,Symbol] name
|
|
96
|
+
# @param [Proc] proc
|
|
97
|
+
# @param [Array,nil] content
|
|
98
|
+
def initialize(name, proc, content)
|
|
99
|
+
check_duplicates(content.map(&:name) + [name]) unless content.nil?
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# @return [Array<String>] The duplicate names found in the array
|
|
106
|
+
def find_all_duplicate_names(names)
|
|
107
|
+
names.group_by { |name| name }
|
|
108
|
+
.select { |_name, values| values.length > 1 }
|
|
109
|
+
.keys
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def check_duplicates(names)
|
|
113
|
+
duplicates = find_all_duplicate_names(names)
|
|
114
|
+
raise ModelValidationError, "Duplicate name #{duplicates.first} found" if duplicates.any?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @private Model Element
|
|
119
|
+
ModelElem = Struct.new(:name, :klass)
|
|
120
|
+
|
|
121
|
+
# @private Wrapper Element
|
|
122
|
+
WrapElem = Struct.new(:element, :options) do
|
|
123
|
+
# @return [Symbol]
|
|
124
|
+
def name
|
|
125
|
+
:"#{element.name}_wrapper"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @private Sequence types
|
|
130
|
+
SEQUENCE_TYPES = [Types::Sequence, Types::SequenceOf, Types::Set, Types::SetOf].freeze
|
|
131
|
+
|
|
132
|
+
# Define helper methods to define models
|
|
133
|
+
module Accel # rubocop:disable Metrics/ModuleLength
|
|
134
|
+
# @return [Hash]
|
|
135
|
+
attr_reader :options
|
|
136
|
+
|
|
137
|
+
# Use another model in this model
|
|
138
|
+
# @param [String,Symbol] name
|
|
139
|
+
# @param [Class] model_klass
|
|
140
|
+
# @return [Elem]
|
|
141
|
+
def model(name, model_klass)
|
|
142
|
+
push_element(ModelElem.new(name, model_klass))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Use a {Wrapper} around a {Types::Base} or a {Model} object
|
|
146
|
+
# @overload wrapper(element, options={})
|
|
147
|
+
# @param [Types::Base,Model] element
|
|
148
|
+
# @param [Hash] options
|
|
149
|
+
# @overload wrapper(options={}, &block)
|
|
150
|
+
# Define the wrapped element through a block. The block must define exactly one element.
|
|
151
|
+
# @param [Hash] options
|
|
152
|
+
# @yieldreturn [void]
|
|
153
|
+
# @return [WrapElem]
|
|
154
|
+
# @since 0.12
|
|
155
|
+
# @since 0.17.0 block form
|
|
156
|
+
def wrapper(element=nil, options={}, &block)
|
|
157
|
+
if block
|
|
158
|
+
options = element if element.is_a?(Hash)
|
|
159
|
+
element = single_element_from_block(:wrapper, &block)
|
|
160
|
+
end
|
|
161
|
+
push_element(WrapElem.new(element, options))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @private Register +elem+ as a child of the block currently being evaluated, or as
|
|
165
|
+
# the root element of the model when no block is being evaluated.
|
|
166
|
+
# @param [BaseElem,ModelElem,WrapElem] elem
|
|
167
|
+
# @return [BaseElem,ModelElem,WrapElem] elem
|
|
168
|
+
# @since 0.17.0
|
|
169
|
+
def push_element(elem)
|
|
170
|
+
builder = @content_builders&.last
|
|
171
|
+
if builder.nil?
|
|
172
|
+
@root = elem
|
|
173
|
+
else
|
|
174
|
+
remove_consumed_elements(builder, elem)
|
|
175
|
+
builder << elem
|
|
176
|
+
end
|
|
177
|
+
elem
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @private Evaluate +block+ and collect all elements defined by it.
|
|
181
|
+
# @return [Array<BaseElem,ModelElem,WrapElem>]
|
|
182
|
+
# @since 0.17.0
|
|
183
|
+
def capture_content(&block)
|
|
184
|
+
@content_builders ||= []
|
|
185
|
+
@content_builders << []
|
|
186
|
+
begin
|
|
187
|
+
instance_eval(&block)
|
|
188
|
+
ensure
|
|
189
|
+
content = @content_builders.pop
|
|
190
|
+
end
|
|
191
|
+
content
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Update options of root element.
|
|
195
|
+
# May be used when subclassing.
|
|
196
|
+
# class Model1 < RASN1::Model
|
|
197
|
+
# sequence :seq, implicit: 0,
|
|
198
|
+
# content: [bool(:bool), integer(:int)]
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
# # same as Model1 but with implicit tag set to 1
|
|
202
|
+
# class Model2 < Model1
|
|
203
|
+
# root_options implicit: 1
|
|
204
|
+
# end
|
|
205
|
+
# @param [Hash] options
|
|
206
|
+
# @return [void]
|
|
207
|
+
# @since 0.12.0 may change name through +:name+
|
|
208
|
+
def root_options(options)
|
|
209
|
+
@options = options
|
|
210
|
+
return unless options.key?(:name)
|
|
211
|
+
|
|
212
|
+
@root = @root.dup
|
|
213
|
+
@root.name = options[:name]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# On inheritance, create +@root+ class variable
|
|
217
|
+
# @param [Class] klass
|
|
218
|
+
# @return [void]
|
|
219
|
+
def inherited(klass)
|
|
220
|
+
super
|
|
221
|
+
root = @root
|
|
222
|
+
klass.class_eval { @root = root }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @private
|
|
226
|
+
# @param [String,Symbol] accel_name
|
|
227
|
+
# @param [Class] klass
|
|
228
|
+
# @since 0.11.0
|
|
229
|
+
# @since 0.12.0 track source location on error (adfoster-r7)
|
|
230
|
+
# @since 0.17.0 accept a block to define content
|
|
231
|
+
def define_type_accel_base(accel_name, klass)
|
|
232
|
+
singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
|
233
|
+
def #{accel_name}(name, options={}, &block) # def sequence(name, type, options, &block)
|
|
234
|
+
options[:name] = name
|
|
235
|
+
options[:content] = merge_content(options[:content], capture_content(&block)) if block
|
|
236
|
+
proc = proc do |opts|
|
|
237
|
+
#{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
|
|
238
|
+
end
|
|
239
|
+
push_element(BaseElem.new(name, proc, options[:content]))
|
|
240
|
+
end
|
|
241
|
+
EVAL
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# @private
|
|
245
|
+
# @param [String,Symbol] accel_name
|
|
246
|
+
# @param [Class] klass
|
|
247
|
+
# @since 0.11.0
|
|
248
|
+
# @since 0.12.0 track source location on error (adfoster-r7)
|
|
249
|
+
def define_type_accel_of(accel_name, klass)
|
|
250
|
+
singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
|
251
|
+
def #{accel_name}_of(name, type, options={}) # def sequence_of(name, type, options)
|
|
252
|
+
options[:name] = name
|
|
253
|
+
proc = proc do |opts|
|
|
254
|
+
#{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
|
|
255
|
+
end
|
|
256
|
+
push_element(BaseElem.new(name, proc, nil))
|
|
257
|
+
end
|
|
258
|
+
EVAL
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Define an accelarator to access a type in a model definition
|
|
262
|
+
# @param [String] accel_name
|
|
263
|
+
# @param [Class] klass class to instanciate
|
|
264
|
+
# @since 0.11.0
|
|
265
|
+
# @since 0.12.0 track source location on error (adfoster-r7)
|
|
266
|
+
def define_type_accel(accel_name, klass)
|
|
267
|
+
if klass < Types::SequenceOf
|
|
268
|
+
define_type_accel_of(accel_name, klass)
|
|
269
|
+
else
|
|
270
|
+
define_type_accel_base(accel_name, klass)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# @param [Symbol,String] name name of object in model
|
|
275
|
+
# @param [Hash] options
|
|
276
|
+
# @return [Elem]
|
|
277
|
+
# @note This method is named +objectid+ and not +object_id+ to not override
|
|
278
|
+
# +Object#object_id+.
|
|
279
|
+
# @see Types::ObjectId#initialize
|
|
280
|
+
def objectid(name, options={})
|
|
281
|
+
options[:name] = name
|
|
282
|
+
proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
|
|
283
|
+
push_element(BaseElem.new(name, proc, nil))
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @param [Symbol,String] name name of object in model
|
|
287
|
+
# @param [Hash] options
|
|
288
|
+
# @return [Elem]
|
|
289
|
+
# @see Types::Any#initialize
|
|
290
|
+
def any(name, options={})
|
|
291
|
+
options[:name] = name
|
|
292
|
+
proc = proc { |opts| Types::Any.new(options.merge(opts)) }
|
|
293
|
+
push_element(BaseElem.new(name, proc, nil))
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Give type name (aka class name)
|
|
297
|
+
# @return [String]
|
|
298
|
+
def type
|
|
299
|
+
return @type if defined? @type
|
|
300
|
+
|
|
301
|
+
@type = self.to_s.gsub(/.*::/, '')
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Parse a DER/BER encoded string
|
|
305
|
+
# @param [String] str
|
|
306
|
+
# @param [Boolean] ber accept BER encoding or not
|
|
307
|
+
# @return [Model]
|
|
308
|
+
# @raise [ASN1Error] error on parsing
|
|
309
|
+
def parse(str, ber: false)
|
|
310
|
+
model = new
|
|
311
|
+
model.parse!(str, ber: ber)
|
|
312
|
+
model
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
# Remove from +builder+ the elements consumed by +elem+. This happens when sub-elements
|
|
318
|
+
# are defined as arguments (through +:content+ option or as +wrapper+ argument) inside a
|
|
319
|
+
# content block: they were already registered in +builder+ by their own definition.
|
|
320
|
+
# @return [void]
|
|
321
|
+
def remove_consumed_elements(builder, elem)
|
|
322
|
+
children = case elem
|
|
323
|
+
when BaseElem then elem.content
|
|
324
|
+
when WrapElem then [elem.element]
|
|
325
|
+
end
|
|
326
|
+
return if children.nil?
|
|
327
|
+
|
|
328
|
+
children.each do |child|
|
|
329
|
+
builder.delete_if { |registered| registered.equal?(child) }
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Merge content defined through +:content+ option with content defined through a block
|
|
334
|
+
# @return [Array]
|
|
335
|
+
def merge_content(option_content, block_content)
|
|
336
|
+
return block_content if option_content.nil?
|
|
337
|
+
|
|
338
|
+
option_content + block_content
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Evaluate +block+ and ensure it defines exactly one element
|
|
342
|
+
# @return [BaseElem,ModelElem,WrapElem]
|
|
343
|
+
def single_element_from_block(helper_name, &block)
|
|
344
|
+
content = capture_content(&block)
|
|
345
|
+
raise ModelValidationError, "#{helper_name} block must define exactly one element" unless content.size == 1
|
|
346
|
+
|
|
347
|
+
content.first
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
extend Accel
|
|
352
|
+
|
|
353
|
+
# @!method sequence(name, options)
|
|
354
|
+
# @!scope class
|
|
355
|
+
# @param [Symbol,String] name name of object in model
|
|
356
|
+
# @param [Hash] options
|
|
357
|
+
# @return [Elem]
|
|
358
|
+
# @see Types::Sequence#initialize
|
|
359
|
+
# @!method set(name, options)
|
|
360
|
+
# @!scope class
|
|
361
|
+
# @param [Symbol,String] name name of object in model
|
|
362
|
+
# @param [Hash] options
|
|
363
|
+
# @return [Elem]
|
|
364
|
+
# @see Types::Set#initialize
|
|
365
|
+
# @!method choice(name, options)
|
|
366
|
+
# @!scope class
|
|
367
|
+
# @param [Symbol,String] name name of object in model
|
|
368
|
+
# @param [Hash] options
|
|
369
|
+
# @return [Elem]
|
|
370
|
+
# @see Types::Choice#initialize
|
|
371
|
+
%w[sequence set choice tag].each do |type|
|
|
372
|
+
self.define_type_accel_base(type, Types.const_get(type.capitalize))
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# @!method sequence_of(name, type, options)
|
|
376
|
+
# @!scope class
|
|
377
|
+
# @param [Symbol,String] name name of object in model
|
|
378
|
+
# @param [Model, Types::Base] type type for SEQUENCE OF
|
|
379
|
+
# @param [Hash] options
|
|
380
|
+
# @return [Elem]
|
|
381
|
+
# @see Types::SequenceOf#initialize
|
|
382
|
+
# @!method set_of(name, type, options)
|
|
383
|
+
# @!scope class
|
|
384
|
+
# @param [Symbol,String] name name of object in model
|
|
385
|
+
# @param [Model, Types::Base] type type for SET OF
|
|
386
|
+
# @param [Hash] options
|
|
387
|
+
# @return [Elem]
|
|
388
|
+
# @see Types::SetOf#initialize
|
|
389
|
+
%w[sequence set].each do |type|
|
|
390
|
+
define_type_accel_of(type, Types.const_get(:"#{type.capitalize}Of"))
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# @!method boolean(name, options)
|
|
394
|
+
# @!scope class
|
|
395
|
+
# @param [Symbol,String] name name of object in model
|
|
396
|
+
# @param [Hash] options
|
|
397
|
+
# @return [Elem]
|
|
398
|
+
# @see Types::Boolean#initialize
|
|
399
|
+
# @!method integer(name, options)
|
|
400
|
+
# @!scope class
|
|
401
|
+
# @param [Symbol,String] name name of object in model
|
|
402
|
+
# @param [Hash] options
|
|
403
|
+
# @return [Elem]
|
|
404
|
+
# @see Types::Integer#initialize
|
|
405
|
+
# @!method bit_string(name, options)
|
|
406
|
+
# @!scope class
|
|
407
|
+
# @param [Symbol,String] name name of object in model
|
|
408
|
+
# @param [Hash] options
|
|
409
|
+
# @return [Elem]
|
|
410
|
+
# @see Types::BitString#initialize
|
|
411
|
+
# @!method bmp_string(name, options)
|
|
412
|
+
# @!scope class
|
|
413
|
+
# @param [Symbol,String] name name of object in model
|
|
414
|
+
# @param [Hash] options
|
|
415
|
+
# @return [Elem]
|
|
416
|
+
# @see Types::BmpString#initialize
|
|
417
|
+
# @!method octet_string(name, options)
|
|
418
|
+
# @!scope class
|
|
419
|
+
# @param [Symbol,String] name name of object in model
|
|
420
|
+
# @param [Hash] options
|
|
421
|
+
# @return [Elem]
|
|
422
|
+
# @see Types::OctetString#initialize
|
|
423
|
+
# @!method null(name, options)
|
|
424
|
+
# @!scope class
|
|
425
|
+
# @param [Symbol,String] name name of object in model
|
|
426
|
+
# @param [Hash] options
|
|
427
|
+
# @return [Elem]
|
|
428
|
+
# @see Types::Null#initialize
|
|
429
|
+
# @!method enumerated(name, options)
|
|
430
|
+
# @!scope class
|
|
431
|
+
# @param [Symbol,String] name name of object in model
|
|
432
|
+
# @param [Hash] options
|
|
433
|
+
# @return [Elem]
|
|
434
|
+
# @see Types::Enumerated#initialize
|
|
435
|
+
# @!method universal_string(name, options)
|
|
436
|
+
# @!scope class
|
|
437
|
+
# @param [Symbol,String] name name of object in model
|
|
438
|
+
# @param [Hash] options
|
|
439
|
+
# @return [Elem]
|
|
440
|
+
# @see Types::UniversalString#initialize
|
|
441
|
+
# @!method utf8_string(name, options)
|
|
442
|
+
# @!scope class
|
|
443
|
+
# @param [Symbol,String] name name of object in model
|
|
444
|
+
# @param [Hash] options
|
|
445
|
+
# @return [Elem]
|
|
446
|
+
# @see Types::Utf8String#initialize
|
|
447
|
+
# @!method numeric_string(name, options)
|
|
448
|
+
# @!scope class
|
|
449
|
+
# @param [Symbol,String] name name of object in model
|
|
450
|
+
# @param [Hash] options
|
|
451
|
+
# @return [Elem]
|
|
452
|
+
# @see Types::NumericString#initialize
|
|
453
|
+
# @!method printable_string(name, options)
|
|
454
|
+
# @!scope class
|
|
455
|
+
# @param [Symbol,String] name name of object in model
|
|
456
|
+
# @param [Hash] options
|
|
457
|
+
# @return [Elem]
|
|
458
|
+
# @see Types::PrintableString#initialize
|
|
459
|
+
# @!method visible_string(name, options)
|
|
460
|
+
# @!scope class
|
|
461
|
+
# @param [Symbol,String] name name of object in model
|
|
462
|
+
# @param [Hash] options
|
|
463
|
+
# @return [Elem]
|
|
464
|
+
# @see Types::VisibleString#initialize
|
|
465
|
+
# @!method ia5_string(name, options)
|
|
466
|
+
# @!scope class
|
|
467
|
+
# @param [Symbol,String] name name of object in model
|
|
468
|
+
# @param [Hash] options
|
|
469
|
+
# @return [Elem]
|
|
470
|
+
# @see Types::IA5String#initialize
|
|
471
|
+
Types.primitives.each do |prim|
|
|
472
|
+
next if prim == Types::ObjectId
|
|
473
|
+
|
|
474
|
+
method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
|
|
475
|
+
self.define_type_accel_base(method_name, prim)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# @return [Model, Wrapper, Types::Base]
|
|
479
|
+
attr_reader :root
|
|
480
|
+
|
|
481
|
+
# Create a new instance of a {Model}
|
|
482
|
+
# @param [Hash] args
|
|
483
|
+
def initialize(args={})
|
|
484
|
+
@elements = {}
|
|
485
|
+
generate_root(args)
|
|
486
|
+
lazy_initialize(args) unless args.empty?
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# @overload [](name)
|
|
490
|
+
# Access an element of the model by its name
|
|
491
|
+
# @param [Symbol] name
|
|
492
|
+
# @return [Model, Types::Base, Wrapper]
|
|
493
|
+
# @overload [](idx)
|
|
494
|
+
# Access an element of root element by its index. Root element must be a {Sequence} or {SequenceOf}.
|
|
495
|
+
# @param [Integer] idx
|
|
496
|
+
# @return [Model, Types::Base, Wrapper]
|
|
497
|
+
def [](name_or_idx)
|
|
498
|
+
case name_or_idx
|
|
499
|
+
when Symbol
|
|
500
|
+
elt = @elements[name_or_idx]
|
|
501
|
+
return elt unless elt.is_a?(Proc)
|
|
502
|
+
|
|
503
|
+
@elements[name_or_idx] = elt.call
|
|
504
|
+
when Integer
|
|
505
|
+
root[name_or_idx]
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Set value of element +name+. Element should be a {Types::Base}.
|
|
510
|
+
# @param [String,Symbol] name
|
|
511
|
+
# @param [Object] value
|
|
512
|
+
# @return [Object] value
|
|
513
|
+
def []=(name, value)
|
|
514
|
+
# Here, use #[] to force generation for lazy elements
|
|
515
|
+
raise Error, 'cannot set value for a Model' if self[name].is_a?(Model)
|
|
516
|
+
|
|
517
|
+
self[name].value = value
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# clone @elements and initialize @root from this new @element.
|
|
521
|
+
def initialize_copy(_other)
|
|
522
|
+
@elements = @elements.clone
|
|
523
|
+
@root = @elements[@root_name]
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Give model name (a.k.a root name)
|
|
527
|
+
# @return [String]
|
|
528
|
+
def name
|
|
529
|
+
@root_name
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Get elements names
|
|
533
|
+
# @return [Array<Symbol,String>]
|
|
534
|
+
def keys
|
|
535
|
+
@elements.keys
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Return a hash image of model
|
|
539
|
+
# @return [Hash]
|
|
540
|
+
def to_h
|
|
541
|
+
private_to_h
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# @return [String]
|
|
545
|
+
def to_der
|
|
546
|
+
root.to_der
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Generate ASN.1 value notation text from this model instance.
|
|
550
|
+
# @param [String] name the value name (e.g. 'myValue')
|
|
551
|
+
# @param [String] type_name the ASN.1 type name (e.g. 'PersonnelRecord')
|
|
552
|
+
# @return [String] value notation text
|
|
553
|
+
# @since 0.17.0
|
|
554
|
+
def to_asn1(name: nil, type_name: nil)
|
|
555
|
+
ValueNotation.emit(self, name: name, type_name: type_name)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Give type name (aka class name)
|
|
559
|
+
# @return [String]
|
|
560
|
+
def type
|
|
561
|
+
self.class.type
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Parse a DER/BER encoded string, and modify object in-place.
|
|
565
|
+
# @param [String] der
|
|
566
|
+
# @param [Boolean] ber accept BER encoding or not
|
|
567
|
+
# @return [Integer] number of parsed bytes
|
|
568
|
+
# @raise [ASN1Error] error on parsing
|
|
569
|
+
def parse!(der, ber: false)
|
|
570
|
+
root.parse!(der, ber: ber)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# @private
|
|
574
|
+
# @see Types::Base#do_parse
|
|
575
|
+
def do_parse(der, ber: false)
|
|
576
|
+
root.do_parse(der, ber: ber)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# @overload value
|
|
580
|
+
# Get value of root element
|
|
581
|
+
# @return [Object,nil]
|
|
582
|
+
# @overload value(name, *args)
|
|
583
|
+
# Direct access to the value of +name+ (nested) element of model.
|
|
584
|
+
# @param [String,Symbol] name
|
|
585
|
+
# @param [Array<Integer,String,Symbol>] args more argument to access element. May be
|
|
586
|
+
# used to access content of a SequenceOf or a SetOf
|
|
587
|
+
# @return [Object,nil]
|
|
588
|
+
# @return [Object,nil]
|
|
589
|
+
# @example
|
|
590
|
+
# class MyModel1 < RASN1::Model
|
|
591
|
+
# sequence('seq', content: [boolean('boolean'), integer('int')])
|
|
592
|
+
# end
|
|
593
|
+
# class MyModel2 < RASN1::Model
|
|
594
|
+
# sequence('root', content: [sequence_of('list', MyModel1)])
|
|
595
|
+
# end
|
|
596
|
+
# model = MyModel2.new
|
|
597
|
+
# model.parse!(der)
|
|
598
|
+
# # access to 2nd MyModel1.int in list
|
|
599
|
+
# model.value('list', 1, 'int')
|
|
600
|
+
def value(name=nil, *args)
|
|
601
|
+
if name.nil?
|
|
602
|
+
root.value
|
|
603
|
+
else
|
|
604
|
+
elt = by_name(name)
|
|
605
|
+
return nil if elt.nil?
|
|
606
|
+
|
|
607
|
+
unless args.empty?
|
|
608
|
+
args.each do |arg|
|
|
609
|
+
elt = elt.root if elt.is_a?(Model)
|
|
610
|
+
elt = elt[arg]
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
elt.value
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Return a hash image of model
|
|
619
|
+
# @return [Hash]
|
|
620
|
+
# Delegate some methods to root element
|
|
621
|
+
# @param [Symbol] meth
|
|
622
|
+
def method_missing(meth, *args, **kwargs)
|
|
623
|
+
if root.respond_to?(meth)
|
|
624
|
+
root.send(meth, *args, **kwargs)
|
|
625
|
+
else
|
|
626
|
+
super
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# @return [Boolean]
|
|
631
|
+
def respond_to_missing?(meth, *)
|
|
632
|
+
root.respond_to?(meth) || super
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# @return [String]
|
|
636
|
+
def inspect(level=0, color: true)
|
|
637
|
+
"#{' ' * level}(#{type}) #{root.inspect(-level)}"
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Objects are equal if they have same class AND same DER
|
|
641
|
+
# @param [Base] other
|
|
642
|
+
# @return [Boolean]
|
|
643
|
+
def ==(other)
|
|
644
|
+
(other.class == self.class) && (other.to_der == self.to_der)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
protected
|
|
648
|
+
|
|
649
|
+
# Initialize model elements from +args+
|
|
650
|
+
# @param [Hash,Array] args
|
|
651
|
+
# @return [void]
|
|
652
|
+
def lazy_initialize(args)
|
|
653
|
+
case args
|
|
654
|
+
when Hash
|
|
655
|
+
lazy_initialize_hash(args)
|
|
656
|
+
when Array
|
|
657
|
+
lazy_initialize_array(args)
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Initialize an element from a hash
|
|
662
|
+
# @param [Hash] args
|
|
663
|
+
# @return [void]
|
|
664
|
+
def lazy_initialize_hash(args)
|
|
665
|
+
args.each do |name, value|
|
|
666
|
+
element = self[name]
|
|
667
|
+
case element
|
|
668
|
+
when Model
|
|
669
|
+
element.lazy_initialize(value)
|
|
670
|
+
when nil
|
|
671
|
+
else
|
|
672
|
+
element.value = value
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Initialize an sequence element from an array
|
|
678
|
+
# @param [Array] args
|
|
679
|
+
# @return [void]
|
|
680
|
+
def lazy_initialize_array(ary)
|
|
681
|
+
raise Error, 'Only sequence types may be initialized with an array' unless SEQUENCE_TYPES.any? { |klass| root.is_a?(klass) }
|
|
682
|
+
|
|
683
|
+
ary.each do |initializer|
|
|
684
|
+
root << initializer
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# Give a (nested) element from its name
|
|
689
|
+
# @param [String, Symbol] name
|
|
690
|
+
# @return [Model, Types::Base, nil]
|
|
691
|
+
def by_name(name)
|
|
692
|
+
elt = self[name]
|
|
693
|
+
return elt unless elt.nil?
|
|
694
|
+
|
|
695
|
+
@elements.each_value do |subelt|
|
|
696
|
+
next unless subelt.is_a?(Model)
|
|
697
|
+
|
|
698
|
+
value = subelt.by_name(name)
|
|
699
|
+
return value unless value.nil?
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
nil
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
private
|
|
706
|
+
|
|
707
|
+
def generate_root(args)
|
|
708
|
+
opts = args.slice(:name, :explicit, :implicit, :optional, :class, :default, :constructed, :tag_value)
|
|
709
|
+
root = self.class.class_eval { @root }
|
|
710
|
+
root_options = self.class.options || {}
|
|
711
|
+
root_options.merge!(opts)
|
|
712
|
+
@root_name = args[:name] || root.name
|
|
713
|
+
@root = generate_element(root, root_options)
|
|
714
|
+
@elements[@root_name] = @root
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def generate_element(elt, opts={})
|
|
718
|
+
case elt
|
|
719
|
+
when BaseElem
|
|
720
|
+
generate_base_element(elt, opts)
|
|
721
|
+
when ModelElem
|
|
722
|
+
opts[:name] ||= elt.name
|
|
723
|
+
elt.klass.new(opts)
|
|
724
|
+
when WrapElem
|
|
725
|
+
generate_wrapper_element(elt, opts)
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def generate_wrapper_element(elt, opts)
|
|
730
|
+
wrapped = elt.element.is_a?(ModelElem) ? elt.element.klass : generate_element(elt.element)
|
|
731
|
+
options = elt.options.merge(opts)
|
|
732
|
+
options[:name] = elt.element.name if elt.element.is_a?(ModelElem)
|
|
733
|
+
wrapper = Wrapper.new(wrapped, options)
|
|
734
|
+
# Use a proc as wrapper may be lazy
|
|
735
|
+
@elements[elt.element.name] = proc { wrapper.element }
|
|
736
|
+
wrapper
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def generate_base_element(elt, opts)
|
|
740
|
+
element = elt.proc.call(opts)
|
|
741
|
+
return element if elt.content.nil?
|
|
742
|
+
|
|
743
|
+
element.value = elt.content.map do |subel|
|
|
744
|
+
generated = generate_element(subel)
|
|
745
|
+
@elements[subel.name] = generated
|
|
746
|
+
end
|
|
747
|
+
element
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# @author sdaubert
|
|
751
|
+
# @author lemontree55
|
|
752
|
+
# @author adfoster-r7
|
|
753
|
+
def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
|
|
754
|
+
my_element = element || root
|
|
755
|
+
value = case my_element
|
|
756
|
+
when Model
|
|
757
|
+
model_to_h(my_element)
|
|
758
|
+
when Types::SequenceOf
|
|
759
|
+
sequence_of_to_h(my_element)
|
|
760
|
+
when Types::Sequence, Types::Tag
|
|
761
|
+
sequence_to_h(my_element)
|
|
762
|
+
when Types::Choice
|
|
763
|
+
choice_to_h(my_element)
|
|
764
|
+
when Wrapper
|
|
765
|
+
wrapper_to_h(my_element)
|
|
766
|
+
else
|
|
767
|
+
my_element.value
|
|
768
|
+
end
|
|
769
|
+
if element.nil?
|
|
770
|
+
{ @root_name => value }
|
|
771
|
+
else
|
|
772
|
+
value
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def model_to_h(elt)
|
|
777
|
+
hsh = elt.to_h
|
|
778
|
+
if root.is_a?(Types::Choice)
|
|
779
|
+
hsh[hsh.keys.first]
|
|
780
|
+
else
|
|
781
|
+
{ @elements.key(elt) => hsh[hsh.keys.first] }
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
def sequence_of_to_h(elt)
|
|
786
|
+
if elt.of_type < Model
|
|
787
|
+
elt.value&.map { |el| el.to_h.values.first }
|
|
788
|
+
else
|
|
789
|
+
elt.value&.map { |el| private_to_h(el) }
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def sequence_to_h(seq)
|
|
794
|
+
ary = seq.value&.map do |el|
|
|
795
|
+
next if el.optional? && el.value.nil?
|
|
796
|
+
|
|
797
|
+
case el
|
|
798
|
+
when Model
|
|
799
|
+
model_to_h(el).to_a[0]
|
|
800
|
+
when Wrapper
|
|
801
|
+
[unwrap_keyname(@elements.key(el)), wrapper_to_h(el)]
|
|
802
|
+
else
|
|
803
|
+
[el.name, private_to_h(el)]
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
ary.compact.to_h
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
def choice_to_h(elt)
|
|
810
|
+
raise ChoiceError.new(elt) if elt.chosen.nil?
|
|
811
|
+
|
|
812
|
+
chosen = elt.value[elt.chosen]
|
|
813
|
+
{ chosen.name => private_to_h(chosen) }
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def unwrap_keyname(key)
|
|
817
|
+
key.to_s.delete_suffix('_wrapper').to_sym
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
def wrapper_to_h(wrap)
|
|
821
|
+
el = wrap.element
|
|
822
|
+
case el
|
|
823
|
+
when Model
|
|
824
|
+
hsh = el.to_h
|
|
825
|
+
hsh[hsh.keys.first]
|
|
826
|
+
else
|
|
827
|
+
private_to_h(el)
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
end
|