aboutsummaryrefslogtreecommitdiffstats
path: root/lib/asn1kit/types/enumerated.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/asn1kit/types/enumerated.rb')
-rw-r--r--lib/asn1kit/types/enumerated.rb160
1 files changed, 160 insertions, 0 deletions
diff --git a/lib/asn1kit/types/enumerated.rb b/lib/asn1kit/types/enumerated.rb
new file mode 100644
index 0000000..79a26c7
--- /dev/null
+++ b/lib/asn1kit/types/enumerated.rb
@@ -0,0 +1,160 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Enumerated < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 10
+ asn1_alias "ENUMERATED"
+
+ class << self
+ def [](hash, extensible: false)
+ ret = Class.new(self)
+ ret.const_set(:EXTENSIBILITY, extensible)
+ ret.const_set(:ENUMERATIONS, hash)
+ ret
+ end
+
+ def enumerations
+ self::ENUMERATIONS.keys
+ end
+
+ def name?(name)
+ self::ENUMERATIONS.key?(name)
+ end
+
+ def value_for(name)
+ self::ENUMERATIONS[name]
+ end
+
+ def extensible?
+ self::EXTENSIBILITY
+ end
+
+ # Will be used during the parsing as well as from #[]
+ private def set_enumerations(root, additional)
+ last = -1
+ root.map! do |name, value|
+ if value
+ raise TypeError, "value must be an Integer" if !value.is_a?(::Integer)
+ [name, last = value]
+ else
+ [name, last += 1]
+ end
+ end
+ additional.map! do |name, value|
+ if value
+ raise TypeError, "value must be an Integer" if !value.is_a?(::Integer)
+ if last >= value
+ raise ArgumentError, "AdditionalEnumeration must be ordered"
+ end
+ [name, last = value]
+ else
+ [name, last += 1]
+ end
+ end
+
+ hash = (root + additional).to_h
+ if hash.size != root.size + additional.size
+ raise ArgumentError, "duplicate identifier in enumerations"
+ end
+ if hash.values.uniq.size != hash.size
+ raise ArgumentError, "duplicate value in enumerations"
+ end
+ const_set(:ENUMERATIONS, hash)
+ end
+ end
+
+ attr_reader :name
+
+ def initialize(name)
+ unless defined?(self.class::ENUMERATIONS)
+ raise TypeError, "uninitialized ENUMERATED"
+ end
+ self.class::ENUMERATIONS[name]
+ @name = name
+ end
+
+ def value
+ self.class::ENUMERATIONS[@name]
+ end
+
+ def to_der
+ dig = value.digits(256)
+ dig << 0x00 if dig[-1] >= 0x80
+ s = dig.pack("C*").reverse
+ der_header(s.bytesize) << s
+ end
+
+ def ==(other)
+ return false unless other.is_a?(ASN1Kit::Enumerated)
+ return false unless other.class::ENUMERATIONS.equal?(self.class::ENUMERATIONS)
+ @name == other.name
+ end
+end
+
+module ASN1Kit::Internal::CompileEnumerated
+ refine ASN1Kit::Enumerated.singleton_class do
+ # Basically same as ASN1Kit::Enumerated::[], except this won't
+ # do validation of the enumerations.
+ def _new_internal(root, additional, extensible:)
+ ret = Class.new(self)
+ ret.const_set(:EXTENSIBILITY, extensible)
+
+ # Let #enumerations work
+ hash = (root + (additional ? additional : [])).to_h
+ if hash.size != root.size + (additional ? additional.size : 0)
+ raise ASN1Kit::ParseError, "duplicate identifier in Enumerations"
+ end
+ ret.const_set(:ENUMERATIONS, hash)
+
+ # For #_compile_fixup
+ ret.instance_variable_set(:@_compile_root, root)
+ ret.instance_variable_set(:@_compile_additional, additional)
+ ret
+ end
+
+ def _compile_fixup(state)
+ root = @_compile_root
+ remove_instance_variable(:@_compile_root)
+ additional = @_compile_additional
+ remove_instance_variable(:@_compile_additional)
+
+ hash = {}
+ values_seen = {}
+ unspecified = []
+ root.each do |name, value|
+ if value
+ v = value.unwrap_as_number(state)
+ if values_seen[v]
+ raise ASN1Kit::ParseError, "duplicate value in RootEnumeration: %p" % v
+ end
+ values_seen[v] = true
+ else
+ unspecified << name
+ end
+ hash[name] = v
+ end
+ next_value = 0
+ unspecified.each do |name|
+ while values_seen[next_value] do next_value += 1 end
+ hash[name] = next_value
+ next_value += 1
+ end
+
+ additional.each do |name, value|
+ if value
+ v = value.unwrap_as_number(state)
+ if values_seen[v]
+ raise ASN1Kit::ParseError, "duplicate value in AdditionalEnumeration: %p" % v
+ end
+ if next_value > v
+ raise ASN1Kit::ParseError, "AdditionalEnumeration must be ordered"
+ end
+ else
+ v = next_value + 1
+ end
+ next_value = hash[name] = v
+ values_seen[v] = true
+ end
+ self::ENUMERATIONS.replace(hash)
+ end
+ end
+end