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,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'types/constrained'
4
+
5
+ module RASN1
6
+ # This modules is a namesapce for all ASN.1 type classes.
7
+ # @author Sylvain Daubert
8
+ module Types
9
+ @primitives = []
10
+ @constructed = []
11
+
12
+ # Give all primitive types
13
+ # @return [Array<Types::Primitive>]
14
+ def self.primitives
15
+ return @primitives unless @primitives.empty?
16
+
17
+ @primitives = self.constants.map { |c| Types.const_get(c) }
18
+ .select { |klass| klass < Primitive }
19
+ end
20
+
21
+ # Give all constructed types
22
+ # @return [Array<Types::Constructed>]
23
+ def self.constructed
24
+ return @constructed unless @constructed.empty?
25
+
26
+ @constructed = self.constants.map { |c| Types.const_get(c) }
27
+ .select { |klass| klass < Constructed }
28
+ end
29
+
30
+ # @private
31
+ # Decode a DER string to extract identifier octets.
32
+ # @param [String] der
33
+ # @return [Array] Return ASN.1 class as Symbol, contructed/primitive as Symbol,
34
+ # ID and size of identifier octets
35
+ def self.decode_identifier_octets(der)
36
+ first_octet = der.unpack1('C').to_i
37
+ asn1_class = Types::Base::CLASSES.key(first_octet & Types::Base::CLASS_MASK) || :universal
38
+ pc = first_octet.anybits?(Types::Constructed::ASN1_PC) ? :constructed : :primitive
39
+ id = first_octet & Types::Base::MULTI_OCTETS_ID
40
+
41
+ size = if id == Types::Base::MULTI_OCTETS_ID
42
+ id = 0
43
+ count = 1
44
+ der[1..].to_s.bytes.each do |octet|
45
+ count += 1
46
+
47
+ id = (id << 7) | (octet & 0x7f)
48
+ break if octet.nobits?(0x80)
49
+ end
50
+ count
51
+ else
52
+ 1
53
+ end
54
+
55
+ [asn1_class, pc, id, size]
56
+ end
57
+
58
+ # Give ASN.1 type from a DER string. If ID is unknown, return a {Types::Base}
59
+ # object.
60
+ # @param [String] der
61
+ # @return [Types::Base]
62
+ # @raise [ASN1Error] +tag+ is out of range
63
+ def self.id2type(der)
64
+ # Define a cache for well-known ASN.1 types
65
+ self.generate_id2type_cache unless defined? @id2types
66
+
67
+ asn1class, pc, id, = self.decode_identifier_octets(der)
68
+ # cache_id: check versus class and 5 LSB bits
69
+ cache_id = der.unpack1('C') & 0xdf
70
+ klass = cache_id < Types::Base::MULTI_OCTETS_ID ? @id2types[id] : Types::Base
71
+ is_constructed = (pc == :constructed)
72
+ options = { class: asn1class, constructed: is_constructed }
73
+ options[:tag_value] = id if klass == Types::Base
74
+ klass.new(options)
75
+ end
76
+
77
+ # @private Generate cache for {.id2type}
78
+ def self.generate_id2type_cache
79
+ constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
80
+ primitives = self.primitives - [Types::Enumerated]
81
+ ary = (primitives + constructed).select { |type| type.const_defined?(:ID) }
82
+ .map { |type| [type.const_get(:ID), type] }
83
+ @id2types = ary.to_h
84
+ @id2types.default = Types::Base
85
+ @id2types.freeze
86
+ end
87
+
88
+ # Define a new ASN.1 type from a base one.
89
+ # This new type may have a constraint defines on it.
90
+ # @param [Symbol,String] name new type name. Must start with a capital letter.
91
+ # @param [Types::Base] from class from which inherits
92
+ # @param [Module] in_module module in which creates new type (default to {RASN1::Types})
93
+ # @return [Class] newly created class
94
+ # @yieldparam [Object] value value to set to type, or infered at parsing
95
+ # @yieldreturn [Boolean]
96
+ # @since 0.11.0
97
+ # @since 0.12.0 in_module parameter
98
+ # @example
99
+ # # Define a new UInt32 type
100
+ # # UInt32 ::= INTEGER (0 .. 4294967295)
101
+ # RASN1::Types.define_type('UInt32', from: RASN1::Types::Integer) do |value|
102
+ # (value >= 0) && (value < 2**32)
103
+ # end
104
+ def self.define_type(name, from:, in_module: self, &block)
105
+ constraint = block&.to_proc
106
+
107
+ new_klass = Class.new(from)
108
+ new_klass.include(Constrained)
109
+ new_klass.extend(Constrained::ClassMethods)
110
+ new_klass.constraint = constraint
111
+
112
+ in_module.const_set(name, new_klass)
113
+ accel_name = name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
114
+ Model.define_type_accel(accel_name, new_klass)
115
+
116
+ # Empty type caches
117
+ @primitives = []
118
+ @constructed = []
119
+
120
+ new_klass
121
+ end
122
+ end
123
+ end
124
+
125
+ require_relative 'types/base'
126
+ require_relative 'types/primitive'
127
+ require_relative 'types/boolean'
128
+ require_relative 'types/integer'
129
+ require_relative 'types/bit_string'
130
+ require_relative 'types/octet_string'
131
+ require_relative 'types/null'
132
+ require_relative 'types/object_id'
133
+ require_relative 'types/enumerated'
134
+ require_relative 'types/bmp_string'
135
+ require_relative 'types/universal_string'
136
+ require_relative 'types/utf8_string'
137
+ require_relative 'types/numeric_string'
138
+ require_relative 'types/printable_string'
139
+ require_relative 'types/ia5_string'
140
+ require_relative 'types/constructed'
141
+ require_relative 'types/sequence'
142
+ require_relative 'types/sequence_of'
143
+ require_relative 'types/set'
144
+ require_relative 'types/set_of'
145
+ require_relative 'types/choice'
146
+ require_relative 'types/any'
147
+ require_relative 'types/tag'
148
+ require_relative 'types/utc_time'
149
+ require_relative 'types/generalized_time'
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # Support for ASN.1 value notation (text representation of ASN.1 data instances).
5
+ #
6
+ # ASN.1 value notation allows defining values for ASN.1 types in a human-readable
7
+ # text format. For example:
8
+ # myPerson PersonnelRecord ::= {
9
+ # name "John Doe",
10
+ # title "Engineer",
11
+ # age 30,
12
+ # employed TRUE
13
+ # }
14
+ #
15
+ # This module provides methods to parse such text into {Model} instances and
16
+ # to generate such text from {Model} instances.
17
+ #
18
+ # @example Parse a value notation string
19
+ # models = RASN1::SchemaParser.parse_file('schema.asn')
20
+ # record = RASN1::ValueNotation.parse(
21
+ # File.read('instance.asn'),
22
+ # models: models
23
+ # )
24
+ # record[:name].value # => "John Doe"
25
+ #
26
+ # @example Generate value notation from a model
27
+ # record = PersonnelRecord.new(name: 'John Doe', title: 'Engineer', age: 30, employed: true)
28
+ # text = RASN1::ValueNotation.emit(record, name: 'myPerson', type_name: 'PersonnelRecord')
29
+ #
30
+ # @author rickmark
31
+ # @since 0.17.0
32
+ module ValueNotation
33
+ # Error raised when parsing or emitting value notation fails
34
+ class Error < RASN1::Error; end
35
+
36
+ class << self
37
+ # Parse an ASN.1 value notation string and populate a model instance.
38
+ # @param [String] text the value notation text
39
+ # @param [Hash{String => Class}] models hash mapping type names to Model subclasses
40
+ # @return [Model] populated model instance
41
+ # @raise [Error] if the text cannot be parsed
42
+ def parse(text, models:)
43
+ text = text.strip
44
+ match = text.match(/\A(\w+)\s+(\w+)\s*::=\s*(.*)\z/m)
45
+ raise Error, 'Invalid value notation format' unless match
46
+
47
+ value_name = match[1]
48
+ type_name = match[2]
49
+ body = match[3].strip
50
+
51
+ model_class = models[type_name]
52
+ raise Error, "Unknown type: #{type_name}" unless model_class
53
+
54
+ values = parse_constructed_value(body)
55
+ instance = model_class.new(values)
56
+ instance.define_singleton_method(:value_name) { value_name }
57
+ instance.define_singleton_method(:type_name) { type_name }
58
+ instance
59
+ end
60
+
61
+ # Parse an ASN.1 value notation file.
62
+ # @param [String] filename path to the value notation file
63
+ # @param [Hash{String => Class}] models hash mapping type names to Model subclasses
64
+ # @return [Model] populated model instance
65
+ # @raise [Error] if the file cannot be parsed
66
+ def parse_file(filename, models:)
67
+ parse(File.read(filename), models: models)
68
+ end
69
+
70
+ # Emit ASN.1 value notation text from a model instance.
71
+ # @param [Model] model the model instance to emit
72
+ # @param [String] name the value name (e.g. 'myPerson')
73
+ # @param [String] type_name the type name (e.g. 'PersonnelRecord')
74
+ # @param [Integer] indent indentation level (number of spaces per level)
75
+ # @return [String] value notation text
76
+ def emit(model, name: nil, type_name: nil)
77
+ name ||= model.respond_to?(:value_name) ? model.value_name : 'value'
78
+ type_name ||= model.respond_to?(:type_name) ? model.type_name : model.class.type
79
+ body = emit_constructed(model, indent_level: 1)
80
+ "#{name} #{type_name} ::= {\n#{body}\n}\n"
81
+ end
82
+
83
+ private
84
+
85
+ # Parse a constructed value body: { field1 value1, field2 value2, ... }
86
+ # @param [String] text
87
+ # @return [Hash{Symbol => Object}]
88
+ def parse_constructed_value(text)
89
+ text = text.strip
90
+ raise Error, "Expected '{' at start of constructed value" unless text.start_with?('{')
91
+ raise Error, "Expected '}' at end of constructed value" unless text.end_with?('}')
92
+
93
+ inner = text[1..-2].strip
94
+ result = {}
95
+ remaining = inner
96
+
97
+ until remaining.nil? || remaining.strip.empty?
98
+ remaining = remaining.strip
99
+ field_name, value, remaining = parse_field(remaining)
100
+ result[field_name.to_sym] = value
101
+ # consume optional comma
102
+ remaining = remaining&.strip
103
+ remaining = remaining.sub(/\A,\s*/, '') if remaining&.start_with?(',')
104
+ end
105
+
106
+ result
107
+ end
108
+
109
+ # Parse a single field: name value
110
+ # @param [String] text
111
+ # @return [Array(String, Object, String)] field name, parsed value, remaining text
112
+ def parse_field(text)
113
+ # Match field name
114
+ match = text.match(/\A(\w+)\s+/)
115
+ raise Error, "Expected field name in: #{text[0..40]}" unless match
116
+
117
+ field_name = match[1]
118
+ rest = match.post_match
119
+
120
+ value, rest = parse_value(rest)
121
+ [field_name, value, rest]
122
+ end
123
+
124
+ # Parse a single value (string, integer, boolean, null, or nested constructed)
125
+ # @param [String] text
126
+ # @return [Array(Object, String)] parsed value and remaining text
127
+ def parse_value(text)
128
+ text = text.strip
129
+ case text
130
+ when /\A"/ then parse_string_value(text)
131
+ when /\ATRUE\b/i then [true, text.sub(/\ATRUE/i, '')]
132
+ when /\AFALSE\b/i then [false, text.sub(/\AFALSE/i, '')]
133
+ when /\ANULL\b/i then [nil, text.sub(/\ANULL/i, '')]
134
+ when /\A\{/ then parse_nested_constructed(text)
135
+ when /\A-?\d/ then parse_integer_value(text)
136
+ else
137
+ raise Error, "Unexpected value at: #{text[0..40]}"
138
+ end
139
+ end
140
+
141
+ # Parse a quoted string value
142
+ # @param [String] text
143
+ # @return [Array(String, String)]
144
+ def parse_string_value(text)
145
+ # Handle escaped quotes within the string
146
+ pos = 1
147
+ result = +''
148
+ while pos < text.length
149
+ ch = text[pos]
150
+ if ch == '"'
151
+ # Check for escaped quote (doubled)
152
+ return [result, text[(pos + 1)..]] unless pos + 1 < text.length && text[pos + 1] == '"'
153
+
154
+ result << '"'
155
+ pos += 2
156
+
157
+ else
158
+ result << ch
159
+ pos += 1
160
+ end
161
+ end
162
+ raise Error, 'Unterminated string value'
163
+ end
164
+
165
+ # Parse an integer value
166
+ # @param [String] text
167
+ # @return [Array(Integer, String)]
168
+ def parse_integer_value(text)
169
+ match = text.match(/\A(-?\d+)/)
170
+ raise Error, "Expected integer at: #{text[0..20]}" unless match
171
+
172
+ [match[1].to_i, match.post_match]
173
+ end
174
+
175
+ # Parse a nested constructed value { ... }
176
+ # @param [String] text
177
+ # @return [Array(Hash, String)]
178
+ def parse_nested_constructed(text)
179
+ # Find matching closing brace
180
+ depth = 0
181
+ pos = 0
182
+ text.each_char.with_index do |ch, idx|
183
+ depth += 1 if ch == '{'
184
+ depth -= 1 if ch == '}'
185
+ if depth.zero?
186
+ pos = idx
187
+ break
188
+ end
189
+ end
190
+ raise Error, 'Unmatched braces in constructed value' if depth != 0
191
+
192
+ inner_text = text[0..pos]
193
+ rest = text[(pos + 1)..]
194
+ values = parse_constructed_value(inner_text)
195
+ [values, rest]
196
+ end
197
+
198
+ # Emit the fields of a constructed model value
199
+ # @param [Model] model
200
+ # @param [Integer] indent_level
201
+ # @return [String]
202
+ def emit_constructed(model, indent_level: 1)
203
+ indent = ' ' * indent_level
204
+ fields = collect_fields(model)
205
+
206
+ lines = fields.map do |field_name, value|
207
+ formatted = format_value(value, indent_level: indent_level)
208
+ "#{indent}#{field_name} #{formatted}"
209
+ end
210
+
211
+ lines.join(",\n")
212
+ end
213
+
214
+ # Collect the named fields from a model's root sequence
215
+ # @param [Model] model
216
+ # @return [Array<Array(Symbol, Object)>]
217
+ def collect_fields(model)
218
+ root = model.root
219
+ return [] unless root.is_a?(Types::Sequence) || root.is_a?(Types::Set)
220
+ return [] unless root.value.is_a?(Array)
221
+
222
+ fields = []
223
+ root.value.each do |element|
224
+ case element
225
+ when Model
226
+ fields << [element.name, element]
227
+ when Types::Base
228
+ next if element.optional? && !element.value?
229
+
230
+ fields << [element.name, element.value]
231
+ end
232
+ end
233
+ fields
234
+ end
235
+
236
+ # Format a value for value notation output
237
+ # @param [Object] value
238
+ # @param [Integer] indent_level
239
+ # @return [String]
240
+ def format_value(value, indent_level: 0)
241
+ case value
242
+ when Model
243
+ body = emit_constructed(value, indent_level: indent_level + 1)
244
+ "{\n#{body}\n#{' ' * indent_level}}"
245
+ when true then 'TRUE'
246
+ when false then 'FALSE'
247
+ when ::Integer then value.to_s
248
+ when String then "\"#{value.gsub('"', '""')}\""
249
+ when nil then 'NULL'
250
+ else
251
+ value.to_s
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # RASN1 version number
5
+ VERSION = '0.16.0'
6
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module RASN1
6
+ # This class is used to wrap a {Types::Base} or {Model} instance to force its options.
7
+ #
8
+ # == Usage
9
+ # This class may be used to wrap another RASN1 object by 4 ways:
10
+ # * wrap an object to modify its options,
11
+ # * implicitly wrap an object (i.e. change its tag),
12
+ # * explicitly wrap an object (i.e wrap the object in another explicit ASN.1 tag),
13
+ # * wrap a choice model to reuse it in its own definition.
14
+ #
15
+ # @example Wrapping object
16
+ # # object to wrap
17
+ # int = RASN1::Types::Integer.new(implicit: 1) # its tag is 0x81
18
+ # # simple wrapper, change an option
19
+ # wrapper = RASN1::Wrapper.new(int, default: 1)
20
+ # # implicit wrapper
21
+ # wrapper = RASN1::Wrapper.new(int, implicit: 3) # wrapped int tag is now 0x83
22
+ # # explicit wrapper
23
+ # wrapper = RASN1::Wrapper.new(int, explicit: 4) # int tag is always 0x81, but it is wrapped in a 0x84 tag
24
+ # @example Wrapping a choice to make it recursive
25
+ # class RecursiveChoice < RASN1::Model
26
+ # choice :choice,
27
+ # content: [
28
+ # integer(:value, implicit: 1),
29
+ # wrapper(model(:recursive, RecursiveChoice), implicit:2)
30
+ # ]
31
+ # end
32
+ # @since 0.12.0
33
+ # @since 0.15.0 Wrappers are lazy. They allocate their inner element only when needed (i.e. when calling {#to_der}, {#parse!} or {#element})
34
+ # @author Sylvain Daubert
35
+ # @author LemonTree55
36
+ class Wrapper < SimpleDelegator
37
+ # @private Private class used to build/parse explicit wrappers
38
+ class ExplicitWrapper < Types::Base
39
+ ID = 0 # not used
40
+ ASN1_PC = 0 # not constructed
41
+
42
+ def self.type
43
+ ''
44
+ end
45
+
46
+ # @return [Boolean]
47
+ # @see Types::Base#can_build?
48
+ def can_build?
49
+ ok = super
50
+ return ok unless optional?
51
+
52
+ ok && @value.can_build?
53
+ end
54
+
55
+ private
56
+
57
+ def value_to_der
58
+ @value.is_a?(String) ? @value : @value.to_der
59
+ end
60
+
61
+ def inspect_value
62
+ ''
63
+ end
64
+ end
65
+
66
+ # @param [Types::Base,Model] element element to wrap
67
+ # @param [Hash] options
68
+ def initialize(element, options={})
69
+ @lazy = true
70
+ opts = explicit_implicit(options)
71
+
72
+ if explicit?
73
+ generate_explicit_wrapper(opts)
74
+ @element_options_to_merge = generate_explicit_wrapper_options(opts)
75
+ @options = opts
76
+ else
77
+ opts[:value] = element.value if element.respond_to?(:value)
78
+ @element_options_to_merge = opts
79
+ @options = {}
80
+ end
81
+ raise RASN1::Error, 'Cannot be implicit and explicit' if explicit? && implicit?
82
+
83
+ super(element)
84
+ end
85
+
86
+ # Say if wrapper is an explicit one (i.e. add tag and length to its element)
87
+ # @return [Boolean]
88
+ def explicit?
89
+ !!@explicit
90
+ end
91
+
92
+ # Say if wrapper is an implicit one (i.e. change tag of its element)
93
+ # @return [Boolean]
94
+ def implicit?
95
+ !!@implicit
96
+ end
97
+
98
+ # Convert wrapper and its element to a DER string
99
+ # @return [String]
100
+ # @since 0.12.0
101
+ # @since 0.15.0 Allocate element (lazy wrapper)
102
+ def to_der
103
+ lazy_generation
104
+ if implicit?
105
+ el = generate_implicit_element
106
+ el.to_der
107
+ elsif explicit?
108
+ @explicit_wrapper.value = element
109
+ @explicit_wrapper.to_der
110
+ else
111
+ element.to_der
112
+ end
113
+ end
114
+
115
+ # Parse a DER string. This method updates object.
116
+ # @param [String] der DER string
117
+ # @param [Boolean] ber if +true+, accept BER encoding
118
+ # @return [Integer] total number of parsed bytes
119
+ # @raise [ASN1Error] error on parsing
120
+ # @since 0.12.0
121
+ # @since 0.15.0 Allocate element (lazy wrapper)
122
+ def parse!(der, ber: false)
123
+ lazy_generation
124
+ if implicit?
125
+ el = generate_implicit_element
126
+ parsed = el.parse!(der, ber: ber)
127
+ element.value = el.value
128
+ parsed
129
+ elsif explicit?
130
+ parsed = @explicit_wrapper.parse!(der, ber: ber)
131
+ element.parse!(@explicit_wrapper.value, ber: ber) if parsed.positive?
132
+ parsed
133
+ else
134
+ element.parse!(der, ber: ber)
135
+ end
136
+ end
137
+
138
+ # @private
139
+ # @see Types::Base#do_parse
140
+ def do_parse(der, ber: false)
141
+ if implicit?
142
+ generate_implicit_element(Types::Base.new(constructed: element.constructed?)).do_parse(der, ber: ber)
143
+ elsif explicit?
144
+ @explicit_wrapper.do_parse(der, ber: ber)
145
+ else
146
+ element.do_parse(der, ber: ber)
147
+ end
148
+ end
149
+
150
+ # @return [Boolean]
151
+ # @see Types::Base#value?
152
+ def value?
153
+ if explicit?
154
+ @explicit_wrapper.value?
155
+ elsif __getobj__.is_a?(Class)
156
+ false
157
+ else
158
+ __getobj__.value?
159
+ end
160
+ end
161
+
162
+ # Return Wrapped element
163
+ # @return [Types::Base,Model]
164
+ # @since 0.12.0
165
+ # @since 0.15.0 Allocate element (lazy wrapper)
166
+ def element
167
+ lazy_generation
168
+ end
169
+
170
+ # @return [::Integer]
171
+ # @see Types::Base#id
172
+ def id
173
+ if implicit?
174
+ @implicit
175
+ elsif explicit?
176
+ @explicit
177
+ else
178
+ lazy_generation(register: false).id
179
+ end
180
+ end
181
+
182
+ # @return [Symbol]
183
+ # @see Types::Base#asn1_class
184
+ def asn1_class
185
+ return lazy_generation(register: false).asn1_class unless @options.key?(:class)
186
+
187
+ @options[:class]
188
+ end
189
+
190
+ # @return [Boolean]
191
+ # @see Types::Base#constructed
192
+ def constructed?
193
+ return lazy_generation(register: false).constructed? unless @options.key?(:constructed)
194
+
195
+ @options[:constructed]
196
+ end
197
+
198
+ # @return [Boolean]
199
+ # @see Types::Base#primitive
200
+ def primitive?
201
+ !constructed?
202
+ end
203
+
204
+ # @param [::Integer] level
205
+ # @return [String]
206
+ def inspect(level=0)
207
+ return super() unless explicit?
208
+
209
+ @explicit_wrapper.inspect(level) << ' ' << super()
210
+ end
211
+
212
+ private
213
+
214
+ def explicit_implicit(options)
215
+ opts = options.dup
216
+ @explicit = tag_id_to_integer(opts.delete(:explicit))
217
+ @implicit = tag_id_to_integer(opts.delete(:implicit))
218
+ opts
219
+ end
220
+
221
+ # Convert a tag id to an integer.
222
+ # If given a String or Symbol, interprets the characters as big-endian octets
223
+ # (like Apple 4-character codes / 4CCs). Integers and +nil+ are returned as-is.
224
+ # @param [::Integer, String, Symbol, nil] value tag value
225
+ # @return [::Integer, nil]
226
+ def tag_id_to_integer(value)
227
+ case value
228
+ when ::Integer, NilClass
229
+ value
230
+ when ::String, ::Symbol
231
+ value.to_s.bytes.reduce(0) { |acc, b| (acc << 8) | b }
232
+ else
233
+ value
234
+ end
235
+ end
236
+
237
+ def generate_explicit_wrapper(options)
238
+ # ExplicitWrapper is a hand-made explicit tag, but we have to use its implicit option
239
+ # to force its tag value.
240
+ @explicit_wrapper = ExplicitWrapper.new(options.merge(implicit: @explicit))
241
+ end
242
+
243
+ def generate_explicit_wrapper_options(options)
244
+ new_opts = {}
245
+ new_opts[:default] = options[:default] if options.key?(:default)
246
+ new_opts[:optional] = options[:optional] if options.key?(:optional)
247
+ new_opts
248
+ end
249
+
250
+ def generate_implicit_element(from=nil)
251
+ el = (from || element).dup
252
+ el.options = if element.explicit?
253
+ el.options.merge(explicit: @implicit)
254
+ else
255
+ el.options.merge(implicit: @implicit)
256
+ end
257
+ el
258
+ end
259
+
260
+ def lazy_generation(register: true)
261
+ return __getobj__ unless @lazy
262
+
263
+ real_element = __getobj__
264
+ case real_element
265
+ when Types::Base, Model
266
+ real_element.options = real_element.options.merge(@element_options_to_merge)
267
+ @lazy = false
268
+ real_element
269
+ else
270
+ el = real_element.new(@element_options_to_merge)
271
+ if register
272
+ @lazy = false
273
+ __setobj__(el)
274
+ end
275
+ el
276
+ end
277
+ end
278
+ end
279
+ end