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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +4 -0
  4. data/lib/rasn1/errors.rb +55 -0
  5. data/lib/rasn1/helpers/colorize.rb +76 -0
  6. data/lib/rasn1/model.rb +831 -0
  7. data/lib/rasn1/schema_parser.rb +470 -0
  8. data/lib/rasn1/tracer.rb +200 -0
  9. data/lib/rasn1/types/any.rb +98 -0
  10. data/lib/rasn1/types/base.rb +675 -0
  11. data/lib/rasn1/types/bit_string.rb +100 -0
  12. data/lib/rasn1/types/bmp_string.rb +22 -0
  13. data/lib/rasn1/types/boolean.rb +57 -0
  14. data/lib/rasn1/types/choice.rb +158 -0
  15. data/lib/rasn1/types/constrained.rb +51 -0
  16. data/lib/rasn1/types/constructed.rb +51 -0
  17. data/lib/rasn1/types/enumerated.rb +44 -0
  18. data/lib/rasn1/types/generalized_time.rb +156 -0
  19. data/lib/rasn1/types/ia5_string.rb +21 -0
  20. data/lib/rasn1/types/integer.rb +166 -0
  21. data/lib/rasn1/types/null.rb +43 -0
  22. data/lib/rasn1/types/numeric_string.rb +41 -0
  23. data/lib/rasn1/types/object_id.rb +64 -0
  24. data/lib/rasn1/types/octet_string.rb +52 -0
  25. data/lib/rasn1/types/primitive.rb +13 -0
  26. data/lib/rasn1/types/printable_string.rb +42 -0
  27. data/lib/rasn1/types/sequence.rb +105 -0
  28. data/lib/rasn1/types/sequence_of.rb +199 -0
  29. data/lib/rasn1/types/set.rb +28 -0
  30. data/lib/rasn1/types/set_of.rb +18 -0
  31. data/lib/rasn1/types/tag.rb +189 -0
  32. data/lib/rasn1/types/universal_string.rb +22 -0
  33. data/lib/rasn1/types/utc_time.rb +67 -0
  34. data/lib/rasn1/types/utf8_string.rb +21 -0
  35. data/lib/rasn1/types/visible_string.rb +30 -0
  36. data/lib/rasn1/types.rb +149 -0
  37. data/lib/rasn1/value_notation.rb +256 -0
  38. data/lib/rasn1/version.rb +6 -0
  39. data/lib/rasn1/wrapper.rb +279 -0
  40. data/lib/rasn1.rb +51 -0
  41. metadata +123 -0
@@ -0,0 +1,470 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # Parser for ASN.1 text schema definitions.
5
+ #
6
+ # Parses ASN.1 module definitions and generates {Model} subclasses from them.
7
+ #
8
+ # @example Parse a schema file
9
+ # models = RASN1::SchemaParser.parse_file('path/to/schema.asn')
10
+ # # => { 'PersonnelRecord' => PersonnelRecord (class < RASN1::Model) }
11
+ #
12
+ # @example Parse a schema string
13
+ # schema = <<~ASN1
14
+ # MyModule DEFINITIONS ::=
15
+ # BEGIN
16
+ # MyRecord ::= SEQUENCE {
17
+ # name PrintableString,
18
+ # age INTEGER
19
+ # }
20
+ # END
21
+ # ASN1
22
+ # models = RASN1::SchemaParser.parse(schema)
23
+ #
24
+ # @author rickmark
25
+ # @since 0.16.0
26
+ module SchemaParser
27
+ # ASN.1 type name to RASN1 type DSL method mapping
28
+ TYPE_MAP = {
29
+ 'BOOLEAN' => :boolean,
30
+ 'INTEGER' => :integer,
31
+ 'BIT STRING' => :bit_string,
32
+ 'OCTET STRING' => :octet_string,
33
+ 'NULL' => :null,
34
+ 'OBJECT IDENTIFIER' => :objectid,
35
+ 'ENUMERATED' => :enumerated,
36
+ 'UTF8String' => :utf8_string,
37
+ 'PrintableString' => :printable_string,
38
+ 'IA5String' => :ia5_string,
39
+ 'VisibleString' => :visible_string,
40
+ 'NumericString' => :numeric_string,
41
+ 'BMPString' => :bmp_string,
42
+ 'UniversalString' => :universal_string,
43
+ 'UTCTime' => :utc_time,
44
+ 'GeneralizedTime' => :generalized_time
45
+ }.freeze
46
+
47
+ # Constructed type names
48
+ CONSTRUCTED_TYPES = %w[SEQUENCE SET].freeze
49
+
50
+ # Error raised when parsing an ASN.1 schema fails
51
+ class ParseError < RASN1::Error; end
52
+
53
+ class << self
54
+ # Parse an ASN.1 schema from a file and generate Model classes.
55
+ # @param [String] filename path to the ASN.1 schema file
56
+ # @param [Module] namespace module in which to define the generated classes (default: Object)
57
+ # @return [Hash{String => Class}] hash mapping type names to generated Model subclasses
58
+ # @raise [ParseError] if the schema cannot be parsed
59
+ def parse_file(filename, namespace: Object)
60
+ parse(File.read(filename), namespace: namespace)
61
+ end
62
+
63
+ # Parse an ASN.1 schema string and generate Model classes.
64
+ # @param [String] schema the ASN.1 schema text
65
+ # @param [Module] namespace module in which to define the generated classes (default: Object)
66
+ # @return [Hash{String => Class}] hash mapping type names to generated Model subclasses
67
+ # @raise [ParseError] if the schema cannot be parsed
68
+ def parse(schema, namespace: Object)
69
+ mod = parse_module(schema)
70
+ build_models(mod, namespace: namespace)
71
+ end
72
+
73
+ private
74
+
75
+ # Parse the module header and body
76
+ # @return [Hash] parsed module structure with :name, :tag_default, :definitions
77
+ def parse_module(schema)
78
+ lines = schema.strip
79
+ # Match module header: ModuleName DEFINITIONS [tag_default] ::=
80
+ header_match = lines.match(/\A(\w+)\s+DEFINITIONS\s*(.*?)\s*::=\s*\n\s*BEGIN\s*\n(.*)\nEND\s*\z/m)
81
+ raise ParseError, 'Invalid ASN.1 module format' unless header_match
82
+
83
+ mod_name = header_match[1]
84
+ tag_options = header_match[2].strip
85
+ body = header_match[3]
86
+
87
+ tag_default = parse_tag_default(tag_options)
88
+
89
+ definitions = parse_definitions(body)
90
+
91
+ { name: mod_name, tag_default: tag_default, definitions: definitions }
92
+ end
93
+
94
+ # Parse tag default from module header
95
+ # @return [Symbol,nil] :implicit, :explicit, or nil
96
+ def parse_tag_default(tag_str)
97
+ case tag_str
98
+ when /IMPLICIT\s+TAGS/i then :implicit
99
+ when /EXPLICIT\s+TAGS/i then :explicit
100
+ when /AUTOMATIC\s+TAGS/i then :automatic
101
+ end
102
+ end
103
+
104
+ # Parse all type definitions from the module body
105
+ # @return [Array<Hash>] list of type definitions
106
+ def parse_definitions(body)
107
+ definitions = []
108
+ # Split on type assignments: TypeName ::= ...
109
+ # We need to handle multi-line definitions
110
+ remaining = body.strip
111
+
112
+ while remaining && !remaining.empty?
113
+ # Match: TypeName ::= TypeDefinition
114
+ match = remaining.match(/\A\s*(\w+)\s*::=\s*/m)
115
+ break unless match
116
+
117
+ type_name = match[1]
118
+ rest = match.post_match
119
+
120
+ type_def, rest = parse_type_definition(rest)
121
+ definitions << { name: type_name, type: type_def }
122
+ remaining = rest&.strip
123
+ end
124
+
125
+ definitions
126
+ end
127
+
128
+ # Parse a single type definition
129
+ # @return [Array(Hash, String)] the parsed type and remaining text
130
+ def parse_type_definition(text)
131
+ text = text.strip
132
+
133
+ # Check for constructed types: SEQUENCE { ... }, SET { ... }
134
+ case text
135
+ when /\A(SEQUENCE|SET)\s+OF\b/
136
+ parse_of_type(text)
137
+ when /\A(SEQUENCE|SET)\s*\{/
138
+ parse_constructed_type(text)
139
+ when /\A(CHOICE)\s*\{/
140
+ parse_choice_type(text)
141
+ else
142
+ parse_simple_type_definition(text)
143
+ end
144
+ end
145
+
146
+ # Parse SEQUENCE { ... } or SET { ... }
147
+ # @return [Array(Hash, String)]
148
+ def parse_constructed_type(text)
149
+ match = text.match(/\A(SEQUENCE|SET)\s*\{/m)
150
+ raise ParseError, "Expected SEQUENCE or SET, got: #{text[0..20]}" unless match
151
+
152
+ kind = match[1].downcase.to_sym
153
+ rest = match.post_match
154
+ members, rest = parse_members(rest)
155
+
156
+ [{ kind: kind, members: members }, rest]
157
+ end
158
+
159
+ # Parse CHOICE { ... }
160
+ # @return [Array(Hash, String)]
161
+ def parse_choice_type(text)
162
+ match = text.match(/\A(CHOICE)\s*\{/m)
163
+ raise ParseError, "Expected CHOICE, got: #{text[0..20]}" unless match
164
+
165
+ rest = match.post_match
166
+ members, rest = parse_members(rest)
167
+
168
+ [{ kind: :choice, members: members }, rest]
169
+ end
170
+
171
+ # Parse SEQUENCE OF or SET OF
172
+ # @return [Array(Hash, String)]
173
+ def parse_of_type(text)
174
+ match = text.match(/\A(SEQUENCE|SET)\s+OF\s+/m)
175
+ raise ParseError, 'Expected SEQUENCE OF or SET OF' unless match
176
+
177
+ kind = :"#{match[1].downcase}_of"
178
+ rest = match.post_match
179
+ element_type, rest = parse_type_reference(rest)
180
+
181
+ [{ kind: kind, element_type: element_type }, rest]
182
+ end
183
+
184
+ # Parse a simple (non-constructed) top-level type definition (type alias)
185
+ # @return [Array(Hash, String)]
186
+ def parse_simple_type_definition(text)
187
+ type_name, = parse_type_reference(text)
188
+ raise ParseError, "Unsupported simple type alias at top level: #{type_name}" unless TYPE_MAP.key?(type_name) || type_name.is_a?(Hash)
189
+
190
+ # Simple type aliases are not directly supported as Model subclasses
191
+ # They would need to be handled as constrained types
192
+ raise ParseError, 'Simple type aliases are not yet supported as top-level definitions'
193
+ end
194
+
195
+ # Parse the members inside { ... }
196
+ # @return [Array(Array<Hash>, String)]
197
+ def parse_members(text)
198
+ members = []
199
+ rest = text.strip
200
+
201
+ loop do
202
+ # Check for closing brace
203
+ if rest.match?(/\A\s*\}/)
204
+ rest = rest.sub(/\A\s*\}/, '')
205
+ break
206
+ end
207
+
208
+ # Remove leading comma
209
+ rest = rest.sub(/\A\s*,\s*/, '') unless members.empty?
210
+
211
+ member, rest = parse_member(rest.strip)
212
+ members << member if member
213
+ end
214
+
215
+ [members, rest]
216
+ end
217
+
218
+ # Parse a single member: name Type [constraints] [DEFAULT value | OPTIONAL]
219
+ # @return [Array(Hash, String)]
220
+ def parse_member(text)
221
+ text = text.strip
222
+ return [nil, text] if text.empty? || text.start_with?('}')
223
+
224
+ # Match: fieldName Type
225
+ match = text.match(/\A(\w+)\s+/)
226
+ raise ParseError, "Expected field name, got: #{text[0..30]}" unless match
227
+
228
+ field_name = match[1]
229
+ rest = match.post_match
230
+
231
+ type_info, rest = parse_type_with_constraints(rest)
232
+
233
+ member = { name: field_name, type: type_info[:type], options: type_info[:options] }
234
+ [member, rest]
235
+ end
236
+
237
+ # Parse a type followed by optional constraints, DEFAULT, or OPTIONAL
238
+ # @return [Array(Hash, String)]
239
+ def parse_type_with_constraints(text)
240
+ text = text.strip
241
+ type_name, rest = parse_type_reference(text)
242
+
243
+ options = {}
244
+
245
+ # Parse constraints like (0..120)
246
+ rest = rest.strip
247
+ if rest.start_with?('(')
248
+ _constraint, rest = parse_constraint(rest)
249
+ # Constraints are noted but not enforced at model level currently
250
+ end
251
+
252
+ # Parse DEFAULT or OPTIONAL
253
+ rest = rest.strip
254
+ if rest.match?(/\ADEFAULT\b/)
255
+ rest = rest.sub(/\ADEFAULT\s+/, '')
256
+ default_value, rest = parse_default_value(rest)
257
+ options[:default] = default_value
258
+ elsif rest.match?(/\AOPTIONAL\b/)
259
+ rest = rest.delete_prefix('OPTIONAL')
260
+ options[:optional] = true
261
+ end
262
+
263
+ [{ type: type_name, options: options }, rest]
264
+ end
265
+
266
+ # Parse a type reference (simple type name or constructed inline type)
267
+ # @return [Array(String, String)]
268
+ def parse_type_reference(text)
269
+ text = text.strip
270
+
271
+ # Check for inline constructed types
272
+ case text
273
+ when /\A(SEQUENCE|SET)\s*\{/
274
+ type_def, rest = parse_constructed_type(text)
275
+ return [type_def, rest]
276
+ when /\A(SEQUENCE|SET)\s+OF\b/
277
+ type_def, rest = parse_of_type(text)
278
+ return [type_def, rest]
279
+ when /\A(CHOICE)\s*\{/
280
+ type_def, rest = parse_choice_type(text)
281
+ return [type_def, rest]
282
+ end
283
+
284
+ # Multi-word types
285
+ ['BIT STRING', 'OCTET STRING', 'OBJECT IDENTIFIER'].each do |multi|
286
+ return [multi, text[multi.length..]] if text.start_with?(multi)
287
+ end
288
+
289
+ # Simple type name
290
+ match = text.match(/\A([A-Za-z][\w-]*)/)
291
+ raise ParseError, "Expected type name, got: #{text[0..20]}" unless match
292
+
293
+ [match[1], match.post_match]
294
+ end
295
+
296
+ # Parse a constraint expression like (0..120)
297
+ # @return [Array(String, String)]
298
+ def parse_constraint(text)
299
+ depth = 0
300
+ idx = 0
301
+ text.each_char do |c|
302
+ depth += 1 if c == '('
303
+ depth -= 1 if c == ')'
304
+ idx += 1
305
+ break if depth.zero?
306
+ end
307
+ [text[0...idx], text[idx..]]
308
+ end
309
+
310
+ # Parse a default value
311
+ # @return [Array(Object, String)]
312
+ def parse_default_value(text)
313
+ text = text.strip
314
+
315
+ case text
316
+ when /\ATRUE\b/
317
+ [true, text.delete_prefix('TRUE')]
318
+ when /\AFALSE\b/
319
+ [false, text.delete_prefix('FALSE')]
320
+ when /\ANULL\b/
321
+ [nil, text.delete_prefix('NULL')]
322
+ when /\A(-?\d+)\b/
323
+ [Regexp.last_match(1).to_i, Regexp.last_match.post_match]
324
+ when /\A"([^"]*)"/
325
+ [Regexp.last_match(1), Regexp.last_match.post_match]
326
+ else
327
+ # Try to capture an identifier (enum value, etc.)
328
+ match = text.match(/\A(\w+)/)
329
+ raise ParseError, "Cannot parse default value: #{text[0..20]}" unless match
330
+
331
+ [match[1], match.post_match]
332
+ end
333
+ end
334
+
335
+ # Build Model classes from parsed definitions
336
+ # @return [Hash{String => Class}]
337
+ def build_models(mod, namespace:)
338
+ models = {}
339
+
340
+ mod[:definitions].each do |defn|
341
+ klass = build_model_class(defn[:name], defn[:type], mod, models, namespace)
342
+ models[defn[:name]] = klass
343
+ end
344
+
345
+ models
346
+ end
347
+
348
+ # Build a single Model subclass
349
+ # @return [Class]
350
+ def build_model_class(type_name, type_def, mod, existing_models, namespace)
351
+ klass = Class.new(RASN1::Model)
352
+
353
+ root_name = type_name.gsub(/([a-z])([A-Z])/, '\1_\2').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').downcase.to_sym
354
+
355
+ case type_def[:kind]
356
+ when :sequence, :set
357
+ build_constructed(klass, root_name, type_def, mod, existing_models)
358
+ when :choice
359
+ build_choice(klass, root_name, type_def, mod, existing_models)
360
+ when :sequence_of, :set_of
361
+ build_of(klass, root_name, type_def, mod, existing_models)
362
+ else
363
+ raise ParseError, "Unsupported top-level type: #{type_def.inspect}"
364
+ end
365
+
366
+ # Define the class in the namespace
367
+ namespace.const_set(type_name.to_sym, klass) unless namespace.const_defined?(type_name.to_sym, false)
368
+
369
+ klass
370
+ end
371
+
372
+ # Build a SEQUENCE or SET model
373
+ def build_constructed(klass, root_name, type_def, mod, existing_models)
374
+ members = type_def[:members]
375
+ mod[:tag_default]
376
+
377
+ klass.class_eval do
378
+ content_elems = members.map do |member|
379
+ field_name = member[:name].to_sym
380
+ type_ref = member[:type]
381
+ opts = (member[:options] || {}).dup
382
+
383
+ if type_ref.is_a?(Hash)
384
+ # Inline constructed type - not supported as a simple field
385
+ raise SchemaParser::ParseError, "Inline constructed types not yet supported for field #{field_name}"
386
+ end
387
+
388
+ method_sym = SchemaParser.type_to_method(type_ref)
389
+ if method_sym
390
+ send(method_sym, field_name, **opts)
391
+ elsif existing_models[type_ref]
392
+ model(field_name, existing_models[type_ref])
393
+ else
394
+ raise SchemaParser::ParseError, "Unknown type: #{type_ref}"
395
+ end
396
+ end
397
+
398
+ send(type_def[:kind], root_name, content: content_elems)
399
+ end
400
+ end
401
+
402
+ # Build a CHOICE model
403
+ def build_choice(klass, root_name, type_def, _mod, existing_models)
404
+ members = type_def[:members]
405
+
406
+ klass.class_eval do
407
+ content_elems = members.map do |member|
408
+ field_name = member[:name].to_sym
409
+ type_ref = member[:type]
410
+ opts = (member[:options] || {}).dup
411
+
412
+ method_sym = SchemaParser.type_to_method(type_ref)
413
+ if method_sym
414
+ send(method_sym, field_name, **opts)
415
+ elsif existing_models[type_ref]
416
+ model(field_name, existing_models[type_ref])
417
+ else
418
+ raise SchemaParser::ParseError, "Unknown type: #{type_ref}"
419
+ end
420
+ end
421
+
422
+ choice(root_name, content: content_elems)
423
+ end
424
+ end
425
+
426
+ # Build a SEQUENCE OF or SET OF model
427
+ def build_of(klass, root_name, type_def, _mod, existing_models)
428
+ element_type = type_def[:element_type]
429
+
430
+ klass.class_eval do
431
+ method_sym = SchemaParser.type_to_method(element_type)
432
+ if method_sym
433
+ # For OF types with primitive elements, use the type class
434
+ type_class = SchemaParser.type_to_class(element_type)
435
+ send(type_def[:kind], root_name, type_class)
436
+ elsif existing_models[element_type]
437
+ send(type_def[:kind], root_name, existing_models[element_type])
438
+ else
439
+ raise SchemaParser::ParseError, "Unknown type for #{type_def[:kind]}: #{element_type}"
440
+ end
441
+ end
442
+ end
443
+
444
+ # Build Model classes from parsed definitions
445
+ # (end of private section)
446
+ end
447
+
448
+ # Convert an ASN.1 type name to the corresponding RASN1 Model DSL method symbol
449
+ # @param [String] type_name
450
+ # @return [Symbol, nil]
451
+ def self.type_to_method(type_name)
452
+ TYPE_MAP[type_name]
453
+ end
454
+
455
+ # Convert an ASN.1 type name to the corresponding RASN1::Types class
456
+ # @param [String] type_name
457
+ # @return [Class, nil]
458
+ def self.type_to_class(type_name)
459
+ method_sym = TYPE_MAP[type_name]
460
+ return nil unless method_sym
461
+
462
+ class_name = type_name.gsub(/\s+/, '')
463
+ begin
464
+ Types.const_get(class_name)
465
+ rescue NameError
466
+ nil
467
+ end
468
+ end
469
+ end
470
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+
5
+ module RASN1
6
+ # @private
7
+ class Tracer
8
+ # @return [IO]
9
+ attr_reader :io
10
+ # @return [Integer]
11
+ attr_accessor :tracing_level
12
+
13
+ def color
14
+ @rainbow.enabled
15
+ end
16
+
17
+ def color=(enabled)
18
+ @rainbow.enabled = enabled
19
+ end
20
+
21
+ def colorize(str)
22
+ @rainbow ? @rainbow.wrap(str) : Rainbow.new.wrap(str)
23
+ end
24
+
25
+ TRACED_CLASSES = [Types::Any, Types::Choice, Types::Sequence, Types::SequenceOf, Types::Base].freeze
26
+
27
+ # @param [IO] io
28
+ # @param [Boolean] color enable colorized output using pastel gem
29
+ def initialize(io, color: false)
30
+ @io = io
31
+ @tracing_level = 0
32
+ @rainbow = Rainbow.new
33
+ @rainbow.enabled = color
34
+ end
35
+
36
+ # Puts +msg+ onto {#io}.
37
+ # @param [String] msg
38
+ # @return [void]
39
+ def trace(msg)
40
+ @io.puts(indent << msg)
41
+ end
42
+
43
+ # Return identation for given +level+. If +nil+, use {#tracing_level}.
44
+ # @param [Integer,nil] level
45
+ # @return [String]
46
+ def indent(level=nil)
47
+ level ||= @tracing_level
48
+ ' ' * level
49
+ end
50
+ end
51
+
52
+ # Trace RASN1 parsing to +io+.
53
+ # All parsing methods called in block are traced to +io+. Each ASN.1 element is
54
+ # traced in a line showing element's id, its length and its data.
55
+ # @param [IO] io
56
+ # @param [Boolean] color enable colorized output using pastel gem (requires pastel to be installed)
57
+ # @example
58
+ # RASN1.trace do
59
+ # RASN1.parse("\x02\x01\x01") # puts "INTEGER id: 2 (0x02), len: 1 (0x01), data: 0x01"
60
+ # end
61
+ # RASN1.parse("\x01\x01\xff") # puts nothing onto STDOUT
62
+ # @example with color
63
+ # RASN1.trace(color: true) do
64
+ # RASN1.parse("\x02\x01\x01") # same output but with ANSI colors
65
+ # end
66
+ # @return [void]
67
+ def self.trace(io=$stdout, color: false)
68
+ self.tracer = Tracer.new(io, color: color)
69
+ Tracer::TRACED_CLASSES.each(&:start_tracing)
70
+
71
+ begin
72
+ yield self.tracer
73
+ ensure
74
+ Tracer::TRACED_CLASSES.reverse.each(&:stop_tracing)
75
+ self.tracer.io.flush
76
+ self.tracer = nil
77
+ end
78
+ end
79
+
80
+ # @private
81
+ def self.tracer
82
+ @tracer
83
+ end
84
+
85
+ def self.tracer=(tracer)
86
+ @tracer = tracer
87
+ end
88
+
89
+ module Types
90
+ class Base
91
+ class << self
92
+ # @private
93
+ # Patch {#do_parse} to add tracing ability
94
+ def start_tracing
95
+ alias_method :do_parse_without_tracing, :do_parse
96
+ alias_method :do_parse, :do_parse_with_tracing
97
+ alias_method :do_parse_explicit_without_tracing, :do_parse_explicit
98
+ alias_method :do_parse_explicit, :do_parse_explicit_with_tracing
99
+ end
100
+
101
+ # @private
102
+ # Unpatch {#do_parse} to remove tracing ability
103
+ def stop_tracing
104
+ alias_method :do_parse, :do_parse_without_tracing
105
+ alias_method :do_parse_explicit, :do_parse_explicit_without_tracing
106
+ end
107
+ end
108
+
109
+ # @private
110
+ # Parse +der+ with tracing abillity
111
+ # @see #parse!
112
+ def do_parse_with_tracing(der, ber:)
113
+ ret = do_parse_without_tracing(der, ber: ber)
114
+ tracer.trace(self.trace)
115
+ ret
116
+ end
117
+
118
+ def do_parse_explicit_with_tracing(data)
119
+ tracer.tracing_level += 1
120
+ do_parse_explicit_without_tracing(data)
121
+ tracer.tracing_level -= 1
122
+ end
123
+ end
124
+
125
+ class Choice
126
+ class << self
127
+ # @private
128
+ # Patch {#parse!} to add tracing ability
129
+ def start_tracing
130
+ alias_method :parse_without_tracing, :parse!
131
+ alias_method :parse!, :parse_with_tracing
132
+ end
133
+
134
+ # @private
135
+ # Unpatch {#parse!} to remove tracing ability
136
+ def stop_tracing
137
+ alias_method :parse!, :parse_without_tracing
138
+ end
139
+ end
140
+
141
+ # @private
142
+ # Parse +der+ with tracing abillity
143
+ # @see #parse!
144
+ def parse_with_tracing(der, ber: false)
145
+ RASN1.tracer.trace(self.trace)
146
+ parse_without_tracing(der, ber: ber)
147
+ end
148
+ end
149
+
150
+ class Sequence
151
+ class << self
152
+ # @private
153
+ # Patch {#der_to_value} to add tracing ability
154
+ def start_tracing
155
+ alias_method :der_to_value_without_tracing, :der_to_value
156
+ alias_method :der_to_value, :der_to_value_with_tracing
157
+ end
158
+
159
+ # @private
160
+ # Unpatch {#der_to_value} to remove tracing ability
161
+ def stop_tracing
162
+ alias_method :der_to_value, :der_to_value_without_tracing
163
+ end
164
+ end
165
+
166
+ # @private
167
+ # der_to_value +der+ with tracing abillity
168
+ def der_to_value_with_tracing(der, ber: false)
169
+ RASN1.tracer.tracing_level += 1
170
+ der_to_value_without_tracing(der, ber: ber)
171
+ RASN1.tracer.tracing_level -= 1
172
+ end
173
+ end
174
+
175
+ class SequenceOf
176
+ class << self
177
+ # @private
178
+ # Patch {#der_to_value} to add tracing ability
179
+ def start_tracing
180
+ alias_method :der_to_value_without_tracing, :der_to_value
181
+ alias_method :der_to_value, :der_to_value_with_tracing
182
+ end
183
+
184
+ # @private
185
+ # Unpatch {#der_to_value} to remove tracing ability
186
+ def stop_tracing
187
+ alias_method :der_to_value, :der_to_value_without_tracing
188
+ end
189
+ end
190
+
191
+ # @private
192
+ # der_to_value +der+ with tracing abillity
193
+ def der_to_value_with_tracing(der, ber: false)
194
+ RASN1.tracer.tracing_level += 1
195
+ der_to_value_without_tracing(der, ber: ber)
196
+ RASN1.tracer.tracing_level -= 1
197
+ end
198
+ end
199
+ end
200
+ end