aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2017-02-28 02:16:33 +0900
committerKazuki Yamaguchi <k@rhe.jp>2017-04-09 23:20:02 +0900
commitbc98bd8adb81c9b98ddb81fc2b7e96ceb83343ad (patch)
tree3941ccdaecda7f96c7d1d62790764f221ec35d88
downloadasn1kit-bc98bd8adb81c9b98ddb81fc2b7e96ceb83343ad.tar.gz
wip
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml9
-rw-r--r--README.md33
-rw-r--r--Rakefile24
-rw-r--r--asn1kit.gemspec16
-rw-r--r--lib/asn1kit.rb25
-rw-r--r--lib/asn1kit/ber.rb6
-rw-r--r--lib/asn1kit/berstring.rb70
-rw-r--r--lib/asn1kit/compile.rb707
-rw-r--r--lib/asn1kit/module.rb25
-rw-r--r--lib/asn1kit/parse.ry861
-rw-r--r--lib/asn1kit/types.rb183
-rw-r--r--lib/asn1kit/types/bit_string.rb112
-rw-r--r--lib/asn1kit/types/boolean.rb41
-rw-r--r--lib/asn1kit/types/character_string_types.rb61
-rw-r--r--lib/asn1kit/types/choice.rb18
-rw-r--r--lib/asn1kit/types/enumerated.rb160
-rw-r--r--lib/asn1kit/types/integer.rb87
-rw-r--r--lib/asn1kit/types/null.rb18
-rw-r--r--lib/asn1kit/types/object_identifier.rb76
-rw-r--r--lib/asn1kit/types/octet_string.rb21
-rw-r--r--lib/asn1kit/types/real.rb46
-rw-r--r--lib/asn1kit/types/relative_oid.rb67
-rw-r--r--lib/asn1kit/types/sequence.rb121
-rw-r--r--lib/asn1kit/types/sequence_of.rb37
-rw-r--r--lib/asn1kit/types/set.rb28
-rw-r--r--lib/asn1kit/types/set_of.rb37
-rw-r--r--lib/asn1kit/types/useful_types.rb36
-rw-r--r--test/helper.rb25
-rw-r--r--test/test_builtin_types.rb132
-rw-r--r--test/test_helper.rb50
-rw-r--r--test/test_parser.rb413
32 files changed, 3548 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24affdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/tmp
+/pkg
+/lib/asn1kit/parse.rb
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1e8e6ea
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: ruby
+rvm:
+ - 2.3.3
+ - 2.4.0
+script:
+ - gem install test-unit -v '~> 3.2.0'
+ - gem install racc -v '~> 1.4'
+ - rake parser
+ - rake test
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4aa9e60
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+asn1kit
+=======
+
+asn1kit is an ASN.1 library written for Ruby.
+
+asn1kit parses a subset of the ASN.1:2015 [see below for what is supported] and
+supports encoding of the BER (Basic Encoding Rules) and DER (Distinguished
+Encoding Rules).
+
+What is supported by ASN1Kit
+----------------------------
+
+* Compiling ASN.1 module definition into Ruby objects
+* BER/DER decoding and DER encoding
+* TBA
+
+The level of support is sufficient to compile and process most of the PKIX
+modules.
+
+What is not supported by ASN1Kit
+--------------------------------
+
+* Constraints are not supported except the SizeConstraint.
+* XML value notation is not supported.
+* TBA
+
+How does the compiler work
+--------------------------
+
+The most part of the compiler is available in `lib/asn1kit/parse.ry` and
+`lib/asn1kit/compile.rb`.
+
+TBA
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..4bb6ed3
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,24 @@
+require "rake/clean"
+require "rake/testtask"
+require "rubygems/package_task"
+require "shellwords"
+
+task :default => :test
+
+CLEAN.include(FileList["tmp", "lib/asn1kit/parse.rb"])
+CLOBBER.include(FileList["*.gem"])
+
+task :test => :parser
+Rake::TestTask.new(:test) do |t|
+ t.warning = true
+end
+
+spec = eval(File.read("asn1kit.gemspec"))
+Gem::PackageTask.new(spec).define
+
+task :parser => "lib/asn1kit/parse.rb"
+
+directory "tmp"
+file "lib/asn1kit/parse.rb" => ["tmp", "lib/asn1kit/parse.ry"] do |t|
+ sh "racc -vt -Otmp/parse.output -o#{t.name} #{t.name.sub(/\.rb$/, ".ry")}"
+end
diff --git a/asn1kit.gemspec b/asn1kit.gemspec
new file mode 100644
index 0000000..4b869f9
--- /dev/null
+++ b/asn1kit.gemspec
@@ -0,0 +1,16 @@
+Gem::Specification.new do |spec|
+ spec.name = "asn1kit"
+ spec.version = "0.1.0"
+ spec.license = "BSD-2-Clause"
+ spec.summary = "An ASN.1 library for Ruby."
+ spec.description = spec.summary
+ spec.homepage = "https://git.rhe.jp/asn1kit.git"
+ spec.authors = ["Kazuki Yamaguchi"]
+ spec.email = ["k@rhe.jp"]
+
+ spec.files = Dir["lib/**/*.rb", "COPYING", "README.md"]
+
+ spec.required_ruby_version = ">= 2.3"
+ spec.add_development_dependency "racc", "~> 1.4"
+ spec.add_development_dependency "test-unit", "~> 3.2"
+end
diff --git a/lib/asn1kit.rb b/lib/asn1kit.rb
new file mode 100644
index 0000000..75dc3ee
--- /dev/null
+++ b/lib/asn1kit.rb
@@ -0,0 +1,25 @@
+require "set"
+require "pp"
+require "strscan"
+
+module ASN1Kit
+ class ParseError < StandardError
+ end
+
+ class EncodingError < StandardError
+ end
+
+ module_function
+
+ def parse(str)
+ parser = Parser.new
+ ret = parser.parse(str)
+ ret.compile
+ end
+end
+
+require_relative "asn1kit/berstring"
+require_relative "asn1kit/module"
+require_relative "asn1kit/types"
+require_relative "asn1kit/compile"
+require_relative "asn1kit/parse"
diff --git a/lib/asn1kit/ber.rb b/lib/asn1kit/ber.rb
new file mode 100644
index 0000000..bf7b4d8
--- /dev/null
+++ b/lib/asn1kit/ber.rb
@@ -0,0 +1,6 @@
+require_relative "types"
+
+module ASN1Kit
+ module Encoding
+ end
+end
diff --git a/lib/asn1kit/berstring.rb b/lib/asn1kit/berstring.rb
new file mode 100644
index 0000000..23b9e93
--- /dev/null
+++ b/lib/asn1kit/berstring.rb
@@ -0,0 +1,70 @@
+module ASN1Kit
+ module BERString
+ TAG_CLASS_MAP = {
+ UNIVERSAL: 0x00,
+ APPLICATION: 0x40,
+ CONTEXT_SPECIFIC: 0x80,
+ PRIVATE: 0xC0
+ }
+
+ refine(String) do
+ def put_object(tc, tn, len, encoding)
+ force_encoding("BINARY")
+ constructed = encoding == :constructed
+ if tn < 0x1f
+ self << (TAG_CLASS_MAP[tc] | (constructed ? 0x20 : 0x00) | tn)
+ else
+ self << (TAG_CLASS_MAP[tc] | (constructed ? 0x20 : 0x00) | 0x1f)
+ self << [tn].pack("w")
+ end
+ if len && len < 0x80
+ self << len
+ elsif !len
+ self << 0x80
+ else
+ d = len.digits(256)
+ self << (0x80 | d.size)
+ self << d.reverse.pack("C")
+ end
+ end
+
+ def put_eoc
+ force_encoding("BINARY")
+ self << 0x00
+ self << 0x00
+ end
+
+ # Returns an array: [tag_class, is_constructed, tag_number, hlen, len]
+ # where
+ # tag_class:: one of :UNIVERSAL, :APPLICATION, :CONTEXT_SPECIFIC, :PRIVATE
+ # is_constructed:: true of false
+ # tag_number:: an Integer
+ # hlen:: header octets length
+ # len:: content octets length
+ def get_object(offset = 0)
+ h = getbyte(offset) or
+ raise RangeError, "end of string reached"
+ hlen = 1
+ tag_class = [:UNIVERSAL, :APPLICATION, :CONTEXT_SPECIFIC, :PRIVATE][h >> 6]
+ constructed = h & 0x20 == 0x20
+ tag_number = h & 0x1f
+ if tag_number == 0x1f
+ tag_number = unpack("@#{offset + hlen} w")[0] or
+ raise RangeError, "end of string reached"
+ hlen += (tag_number.bit_length / 8r).ceil
+ end
+ len = getbyte(offset + hlen) or
+ raise RangeError, "end of string reached"
+ hlen += 1
+ if len > 0x80
+ blen = len - 0x80
+ d = unpack("@#{offset + hlen} C#{blen}")
+ d[-1] or raise RangeError, "end of string reached"
+ len = d.inject(0) { |sum, i| (sum << 8) + i }
+ hlen += blen
+ end
+ [tag_class, tag_number, constructed, hlen, len]
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/compile.rb b/lib/asn1kit/compile.rb
new file mode 100644
index 0000000..fd95a6b
--- /dev/null
+++ b/lib/asn1kit/compile.rb
@@ -0,0 +1,707 @@
+module ASN1Kit
+ module Internal
+ using ::Module.new {
+ refine(Object) do
+ def unreachable
+ raise "internal error: reached unreachable"
+ end
+ end
+
+ include ASN1Kit::Internal::CompileType
+ include ASN1Kit::Internal::CompileBitString
+ include ASN1Kit::Internal::CompileEnumerated
+ include ASN1Kit::Internal::CompileSequence
+ include ASN1Kit::Internal::CompileSequenceOf
+ include ASN1Kit::Internal::CompileSetOf
+ include ASN1Kit::Internal::CompileObjectIdentifier
+ include ASN1Kit::Internal::CompileRelativeOID
+ }
+
+ class TypeReference
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def inspect_abbrev
+ inspect
+ end
+
+ def tagging(tagging, tag_class, tag_number)
+ if defined?(@tagging)
+ unreachable
+ end
+ @tagging = tagging
+ @tag_class = tag_class
+ @tag_number = tag_number
+ self
+ end
+
+ def unwrap(state)
+ type = state.type_symbols[@name] or
+ raise "unresolved typereference: %s" % @name
+ if type.is_a?(TypeReference)
+ unreachable
+ end
+
+ if defined?(@tagging)
+ orig = type
+ type = type.tagging(@tagging, @tag_class, @tag_number)
+ state.mark_resolved(type) if orig.alias
+ end
+ type
+ end
+ end
+
+ class UnresolvedValue
+ attr_accessor :type, :value
+
+ def initialize(type, value)
+ @type = type
+ @value = value
+ end
+
+ private def unwrap_reference(ret, state)
+ while true
+ case ret
+ when Nodes::ValuereferenceNode
+ name = ret.name
+ ret = state.value_symbols[name] or
+ raise ParseError, "unresolved value: %p" % name
+ else
+ break
+ end
+ end
+ ret
+ end
+
+ def unwrap_as_number(state)
+ case ret = unwrap_reference(@value, state)
+ when ::Integer
+ ret
+ when ASN1Kit::Integer
+ ret.value
+ else
+ raise ParseError, "valuereference %p does not resolve to number" %
+ @value.name
+ end
+ end
+
+ def unwrap(state)
+ if @value.is_a?(Nodes::ValuereferenceNode)
+ # DefinedValue, INTEGER, or ENUMERATED
+ if @type <= ASN1Kit::Integer
+ num = @type.named_number(@value.name)
+ return @type.new(num) if num
+ elsif @type <= ASN1Kit::Enumerated
+ return @type.new(@value.name) if @type.name?(@value.name)
+ end
+
+ ret = unwrap_reference(@value, state)
+ state.resolve(ret)
+ if ret.class != @type
+ # Duplicating already-resolved value
+ ret = ret.cast_to(@type)
+ state.mark_resolved(ret)
+ # TODO: Should casted new instance have the same alias?
+ # ret.alias = @value.name
+ end
+ ret
+ else
+ # Not a reference value, but haven't been resolved
+ ret = @value.shallow_compile_to(@type, state)
+ state.resolve(ret)
+ ret
+ end
+ end
+
+ def inspect_abbrev
+ inspect
+ end
+
+ private def inspect_inner
+ "#<$UnresolvedValue #{@type.inspect} #{@value.inspect}>"
+ end
+ end
+
+ class CompileState
+ attr_reader :type_symbols, :value_symbols
+
+ def initialize(extensibility_implied:, tag_default:)
+ @type_symbols = {}
+ @value_symbols = {}
+ @extensibility_implied = extensibility_implied
+ @tag_default = tag_default
+ @done = ::Set.new
+ end
+
+ def mark_resolved(obj)
+ return true if @done.include?(obj)
+ @done << obj
+ nil
+ end
+
+ def resolve(obj)
+ return obj if mark_resolved(obj)
+ obj._compile_fixup(self)
+ obj
+ end
+
+ def merge
+ @type_symbols.merge(@value_symbols)
+ end
+
+ def extensibility_implied?
+ @extensibility_implied
+ end
+
+ def tag_default
+ @tag_default
+ end
+ end
+
+ module Nodes
+ class ModuleDefinitionNode
+ attr_reader :assignments
+
+ def initialize(name, ident, tag_default, extension_default, (exports, imports, assignments))
+ @name = name
+ @ident = ident
+ @tag_default = tag_default
+ @extension_default = extension_default
+
+ @exports = exports
+ @imports = imports
+ @assignments = assignments || []
+ end
+
+ # Compile nodes to Module
+ def compile
+ state = CompileState.new(extensibility_implied: @extension_default, tag_default: @tag_default)
+
+ @assignments.each do |name, val1, val2|
+ # raise "duplicate symbol name: #{@name}.#{name}" if value_symbols[name]
+ if val2
+ # ValueAssignment
+ state.value_symbols[name] = [val1, val2]
+ else
+ # TypeAssignment
+ state.type_symbols[name] = val1
+ end
+ end
+
+ # Step 1. Resolve top-level types
+ state.type_symbols.transform_values! { |typenode|
+ # Here the result is either a (incomplete) subclass of
+ # ASN1Kit::Type or an instance of Internal::TypeReference.
+ typenode.shallow_compile(state)
+ }
+ state.type_symbols.each { |name, type|
+ if type.is_a?(Internal::TypeReference)
+ while type.is_a?(Internal::TypeReference)
+ case type
+ when Internal::TypeReference
+ type = state.type_symbols[type.name] ||
+ raise("unresolved type: %p", type)
+ else break
+ end
+ end
+ # Subclassing to assign a new alias
+ type = Class.new(type)
+ type.alias = name
+ state.mark_resolved(type) # Mark as resolved
+ state.type_symbols[name] = type
+ else
+ # |type| is a builtin type; subclassing
+ if type.alias
+ type = Class.new(type)
+ state.mark_resolved(type) # Mark as resolved
+ state.type_symbols[name] = type
+ end
+ type.alias = name
+ end
+ }
+
+ # Step 2. Compile top-level values
+ state.value_symbols.transform_values! { |typenode, valuenode|
+ type = typenode.shallow_compile(state)
+ type = type.unwrap(state) if type.is_a?(Internal::TypeReference)
+ valuenode.shallow_compile_to(type, state)
+ }
+ state.value_symbols.each { |name, value|
+ if value.is_a?(Internal::UnresolvedValue)
+ # Duplicating to assign a new alias
+ value = value.unwrap(state).dup
+ value.alias = name
+ state.mark_resolved(value) # Mark as resolved
+ state.value_symbols[name] = value
+ else
+ value.alias = name
+ end
+ }
+
+ # Step 3. Fixup incomplete resulting instances
+ state.type_symbols.each { |name, type|
+ state.resolve(type)
+ }
+ state.value_symbols.each { |name, value|
+ state.resolve(value.class) # immediate types
+ state.resolve(value)
+ }
+
+ if @ident
+ oid = @ident.shallow_compile_to(ASN1Kit::ObjectIdentifier, state)
+ state.resolve(oid)
+ else
+ oid = nil
+ end
+ mod = ASN1Kit::Module.new(@name, state.merge, oid: oid)
+
+ mod
+ end
+ end
+
+ class Constant
+ attr_reader :value
+
+ def initialize(value, name: nil)
+ @value = value
+ @name = name
+ end
+ end
+
+ class ValueNode
+ end
+
+ # A 'Value' which is an immediate value
+ class SimpleValueNode < ValueNode
+ def initialize(type, value)
+ @type = type
+ @value = value
+ end
+
+ def shallow_compile_to(type, state)
+ case @type
+ when :bstring
+ v = @value.gsub(/[^01]/, "")
+ decoded = [v].pack("B*")
+ if type <= ASN1Kit::BitString
+ type.new(decoded, v.size)
+ elsif type <= ASN1Kit::OctetString
+ type.new(decoded)
+ else
+ raise "unable to compile bstring into %p" % type
+ end
+ when :hstring
+ v = @value.gsub(/[^0-9A-F]/, "")
+ decoded = [v].pack("H*")
+ if type <= ASN1Kit::BitString
+ type.new(decoded, v.size * 4)
+ elsif type <= ASN1Kit::OctetString
+ type.new(decoded)
+ else
+ raise "unable to compile hstring into %p" % type
+ end
+ when :boolean
+ unless type <= ASN1Kit::Boolean
+ raise "unable to compile BooleanValue into %p" % type
+ end
+ type.new(@value)
+ when :null
+ unless type <= ASN1Kit::Null
+ raise "unable to compile NULL into %p" % type
+ end
+ type.new
+ when :integer
+ # may be RealValue
+ if type <= ASN1Kit::Integer
+ type.new(@value)
+ elsif type <= ASN1Kit::Real
+ type.new(@value)
+ else
+ raise "unable to compile IntegerValue into %p" % type
+ end
+ when :real
+ unless type <= ASN1Kit::Real
+ raise "unable to compile RealValue into %p" % type
+ end
+ v = @value == :PLUS_INFINITY ? ASN1Kit::Real::PLUS_INFINITY :
+ @value == :MINUS_INFINITY ? ASN1Kit::Real::MINUS_INFINITY :
+ @value == :NOT_A_NUMBER ? ASN1Kit::Real::NOT_A_NUMBER : @value
+ type.new(v)
+ else
+ raise "unknown simple value node type: %p" % @type
+ end
+ end
+ end
+
+ # A Value surrounded by "{" and "}"
+ #
+ # value Type ::= { a, b }
+ #
+ # cannot be parsed by the LALR(1) parser, because this requires knowledge
+ # about 'Type'.
+ class UnparsedValueNode < ValueNode
+ def initialize(body)
+ @body = body
+ end
+
+ def shallow_compile_to(type, state)
+ case
+ when type <= ASN1Kit::BitString
+ list = Parser.new.parse("__CompoundBitStringValue " << @body)
+ type.new_with_names(*list)
+ when type <= ASN1Kit::ObjectIdentifier || type <= ASN1Kit::RelativeOID
+ # ObjectIdentifierValueNode
+ oid_value = Parser.new.parse("__ObjectIdentifierValue " << @body)
+ oid_value.shallow_compile_to(type, state)
+ when type <= ASN1Kit::Sequence || type <= ASN1Kit::Set
+ cvl = Parser.new.parse("__SequenceValue " << @body)
+ hash = {}
+ cvl.each do |ident, val|
+ if hash[ident]
+ raise ParseError, "duplicate name in ComponentValueList: %p" %
+ ident
+ end
+ t = type.component_type(ident)
+ unless t
+ raise ParseError, "invalid name in ComponentValueList: %p" %
+ ident
+ end
+ hash[ident] = Internal::UnresolvedValue.new(t, val)
+ end
+ type.new(hash)
+ when type <= ASN1Kit::SequenceOf || type <= ASN1Kit::SetOf
+ # FIXME: iff |type| uses SEQUENCE OF NamedType form,
+ # "{" namedValueList "}" form should be used
+ cvl = Parser.new.parse("__SequenceOfValue " << @body)
+ type.new(cvl)
+ when false
+ # Object
+ else
+ raise ParseError, "unable to parse value as type %p" % type
+ end
+ end
+ end
+
+ # Represents an ObjectIdentifierValue or a RelativeOIDValue
+ class ObjectIdentifierValueNode < ValueNode
+ def initialize(components)
+ @components = components
+ end
+
+ def shallow_compile_to(type, state)
+ unless type <= ASN1Kit::ObjectIdentifier || type <= ASN1Kit::RelativeOID
+ raise ParseError, "ObjectIdentifierValue is incompatible with %p" % type
+ end
+
+ if type <= ASN1Kit::ObjectIdentifier
+ # TODO: This is not implementing the spec properly: e.g.
+ #
+ # a OBJECT IDENTIFIER ::= { 1 data }
+ #
+ # is not supported at the moment.
+ if !@components[0][1]
+ case @components[0][0]
+ when "itu-t", "ccitt"
+ @components[0][1] = 0
+ when "iso"
+ @components[0][1] = 1
+ when "joint-iso-itu-t", "joint-iso-ccitt"
+ @components[0][1] = 2
+ else
+ # The first component seems to be DefinedValue
+ n, = @components.shift
+ node = ValuereferenceNode.new(n)
+ base = Internal::UnresolvedValue.new(type, node)
+ end
+ end
+ end
+
+ arys = [ary = []]
+ @components.map do |name, value|
+ if value
+ # NameAndNumberForm/NumberForm
+ ary << Internal::UnresolvedValue.new(:number, value)
+ else
+ # DefinedValue (a kind of RelativeOID)
+ node = ValuereferenceNode.new(name)
+ arys << Internal::UnresolvedValue.new(ASN1Kit::RelativeOID, node)
+ arys << ary = []
+ end
+ end
+
+ if type <= ASN1Kit::ObjectIdentifier
+ type._new_internal(base, arys)
+ else
+ type._new_internal(arys)
+ end
+ end
+ end
+
+ class DefinedValueNode
+ attr_reader :module
+ attr_reader :name
+
+ def initialize(name, mod = nil)
+ @name = name
+ @module = mod if mod # External
+ end
+ end
+
+ # May be INTEGER or ENUMERATED item identifier
+ class ValuereferenceNode < DefinedValueNode
+ def compile
+ self
+ end
+
+ def shallow_compile_to(type, state)
+ Internal::UnresolvedValue.new(type, self)
+ end
+ end
+
+ class TypeNode
+ def subtype
+ @subtype ||= {}
+ end
+
+ def update(hash)
+ subtype.merge!(hash)
+ self
+ end
+ end
+
+ class ComponentTypeNode
+ attr_reader :name, :type, :default, :optional
+
+ def initialize(name, type, default: nil, optional: nil)
+ @name = name
+ @type = type
+ @default = default
+ @optional = optional
+ end
+ end
+
+ class ComponentTypeListsTypeNode < TypeNode
+ def initialize(component_type_lists)
+ @component_type_lists = component_type_lists
+ end
+
+ def shallow_compile_internal(klass, state)
+ flattened = @component_type_lists.flat_map do |obj|
+ case obj
+ when :extension
+ [:extension]
+ when Array
+ obj.map { |ctnode|
+ type = ctnode.type.shallow_compile(state)
+ if ctnode.default
+ default = Internal::UnresolvedValue.new(type, ctnode.default)
+ else
+ default = nil
+ end
+ ASN1Kit::Sequence::ComponentType.new(ctnode.name, type,
+ default: default,
+ optional: ctnode.optional)
+ }
+ else unreachable
+ end
+ end
+
+ klass[*flattened]
+ end
+ end
+
+ class SequenceTypeNode < ComponentTypeListsTypeNode
+ def shallow_compile(state)
+ shallow_compile_internal(ASN1Kit::Sequence, state)
+ end
+ end
+
+ class SetTypeNode < ComponentTypeListsTypeNode
+ def shallow_compile(state)
+ shallow_compile_internal(ASN1Kit::Set, state)
+ end
+ end
+
+ class SequenceOfTypeNode < TypeNode
+ def initialize(type, name: nil)
+ @type = type
+ # TODO: How NamedType alternative should be handled?
+ end
+
+ def shallow_compile(state)
+ ASN1Kit::SequenceOf[@type.shallow_compile(state)]
+ end
+ end
+
+ class SetOfTypeNode < TypeNode
+ def initialize(type, name: nil)
+ @type = type
+ end
+
+ def shallow_compile(state)
+ ASN1Kit::SetOf[@type.shallow_compile(state)]
+ end
+ end
+
+ class SimpleTypeNode < TypeNode
+ attr_reader :type, :arg1
+
+ def initialize(type, arg1 = nil)
+ @type = type
+ @arg1 = arg1
+ end
+
+ def [](name)
+ @named_numbers[name]
+ end
+
+ def shallow_compile(state)
+ case @type
+ when :BOOLEAN
+ ASN1Kit::Boolean
+ when :INTEGER
+ ASN1Kit::Integer[*@arg1]
+ when :REAL
+ ASN1Kit::Real
+ when :BIT_STRING
+ if @arg1
+ nbs = @arg1.map { |id, val|
+ val = Internal::UnresolvedValue.new(nil, val) unless val.is_a?(::Integer)
+ [id, val]
+ }
+ end
+ ASN1Kit::BitString[*nbs]
+ when :OCTET_STRING
+ ASN1Kit::OctetString
+ when :NULL
+ ASN1Kit::Null
+ when :OBJECT_IDENTIFIER
+ ASN1Kit::ObjectIdentifier
+ when :RELATIVE_OID
+ ASN1Kit::RelativeOID
+ when :ENUMERATED
+ # FIXME UGLY
+ root, additional = @arg1
+ [root, additional].compact.each do |ary|
+ ary.map! { |name, value|
+ if value
+ [name, Internal::UnresolvedValue.new(nil, value)]
+ else
+ [name, nil]
+ end
+ }
+ end
+ extensible = !!additional || state.extensibility_implied?
+ ASN1Kit::Enumerated._new_internal(root, additional||[], extensible: extensible)
+ when :CHOICE
+ ASN1Kit::Choice
+
+ # CharacterStringType
+ when :UTF8String
+ ASN1Kit::UTF8String
+ when :NumericString
+ ASN1Kit::NumericString
+ when :PrintableString
+ ASN1Kit::PrintableString
+ when :TeletexString, :T61String
+ ASN1Kit::TeletexString
+ when :VideotexString
+ ASN1Kit::VideotexString
+ when :IA5String
+ ASN1Kit::IA5String
+ when :GraphicString
+ ASN1Kit::GraphicString
+ when :VisibleString, :ISO646String
+ ASN1Kit::VisibleString
+ when :GeneralString
+ ASN1Kit::GeneralString
+ when :UniversalString
+ ASN1Kit::UniversalString
+ when :BMPString
+ ASN1Kit::BMPString
+ when :CHARACTER_STRING
+ ASN1Kit::CharacterString
+
+ # UsefulType
+ when :GeneralizedTime
+ ASN1Kit::GeneralizedTime
+ when :UTCTime
+ ASN1Kit::UTCTime
+ when :ObjectDescriptor
+ ASN1Kit::ObjectDescriptor
+ else
+ raise "unknown builtin type type: %p" % @type
+ end
+ end
+ end
+
+ class TaggedTypeNode < TypeNode
+ def initialize(type, tagging, class_name, class_number)
+ @type = type
+ @tagging = tagging
+ @class_name = class_name
+ @class_number = class_number
+ end
+
+ def shallow_compile(state)
+ tagging = @tagging || state.tag_default
+ base = @type.shallow_compile(state)
+ base.tagging(tagging, @class_name, @class_number)
+ end
+ end
+
+ NamedTypeNode = Struct.new(:name, :type)
+
+ class ReferencedTypeNode < TypeNode
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ class DefinedTypeNode < ReferencedTypeNode
+ end
+
+ class TypereferenceNode < DefinedTypeNode
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def compile(state)
+ ret = state.resolved(@name)
+ return ret if ret
+ state.push()
+ self
+ end
+
+ def shallow_compile(state)
+ Internal::TypeReference.new(@name)
+ end
+ end
+
+ class Constraint
+ def initialize(name, arg1)
+ @name = name
+ @arg1 = arg1
+ end
+ end
+
+ class SizeConstraint < Constraint
+ def initialize(constraint)
+ @constraint = constraint
+ end
+ end
+
+ class ValueRange
+ def initialize(lower_endpoint, upper_endpoint)
+ @lower = lower_endpoint
+ @upper = upper_endpoint
+ end
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/module.rb b/lib/asn1kit/module.rb
new file mode 100644
index 0000000..09808d9
--- /dev/null
+++ b/lib/asn1kit/module.rb
@@ -0,0 +1,25 @@
+module ASN1Kit
+ class Module
+ attr_reader :name, :oid
+
+ def initialize(name, symbols = {}, oid: nil)
+ @name = name
+ @oid = oid if oid
+ @symbols = symbols
+ end
+
+ def add_type(name, type)
+ raise "invalid type: #{type.inspect}" unless type.is_a?(Class) && type < Type
+ @symbols[name] = type
+ end
+
+ def add_value(name, value)
+ raise "invalid value: #{value.inspect}" unless value.is_a?(Type)
+ @symbols[name] = value
+ end
+
+ def [](name)
+ @symbols[name]
+ end
+ end
+end
diff --git a/lib/asn1kit/parse.ry b/lib/asn1kit/parse.ry
new file mode 100644
index 0000000..d5ffd76
--- /dev/null
+++ b/lib/asn1kit/parse.ry
@@ -0,0 +1,861 @@
+class ASN1Kit::Parser
+prechigh
+ nonassoc EXCEPT
+ left "^" INTERSECTION
+ left "|" UNION
+preclow
+
+token
+ Cstring Hstring Bstring Number Negative_number Realnumber
+ Identifier Typereference Objectclassreference
+ Unparsed
+
+ # For parsing UnparsedValueNode
+ __CompoundBitStringValue __ObjectIdentifierValue __SequenceValue
+ __SequenceOfValue
+
+ # Reserved words
+ ABSENT ENCODED INTERS ECTION SEQUENCE ABSTRACT_SYNTAX ENCODING_CONTROL
+ ISO646String SET ALL END MAX SETTINGS APPLICATION ENUMERATED MIN SIZE
+ AUTOMATIC EXCEPT MINUS_INFINITY STRING BEGIN EXPLICIT NOT_A_NUMBER
+ SYNTAX BIT EXPORTS NULL T61String BMPString EXTENSIBILITY NumericString
+ TAGS BOOLEAN EXTERNAL OBJECT TeletexString BY FALSE ObjectDescriptor
+ TIME CHARACTER FROM OCTET TIME_OF_DAY CHOICE GeneralizedTime OF TRUE
+ CLASS GeneralString OID_IRI COMPONENT GraphicString
+ OPTIONAL UNION INTERSECTION COMPONENTS IA5String PATTERN UNIQUE CONSTRAINED
+ IDENTIFIER PDV UNIVERSAL CONTAINING IMPLICIT PLUS_INFINITY
+ UniversalString DATE IMPLIED PRESENT UTCTime DATE_TIME IMPORTS
+ PrintableString UTF8String DEFAULT INCLUDES PRIVATE VideotexString
+ DEFINITIONS INSTANCE REAL VisibleString DURATION INSTRUCTIONS
+ RELATIVE_OID WITH EMBEDDED INTEGER RELATIVE_OID_IRI
+
+start root
+
+rule
+ # X.680 13. Module definition
+ moduleDefinition
+ : modulereference definitiveIdentification
+ DEFINITIONS
+ tagDefault
+ extensionDefault
+ "::="
+ BEGIN
+ moduleBody
+ END
+ { result = D::ModuleDefinitionNode.new(val[0], val[1], val[3], val[4], val[7]) }
+ ;
+
+ definitiveIdentification
+ : # empty
+ # DefinitiveOID
+ | "{" definitiveObjIdComponentList "}"
+ { result = D::ObjectIdentifierValueNode.new(val[1]) }
+ # DefinitiveOIDAndIRI form is not supported
+ ;
+
+ definitiveObjIdComponentList
+ : definitiveObjIdComponent
+ { result = [val[0]] }
+ | definitiveObjIdComponentList definitiveObjIdComponent
+ { result << val[1] }
+ ;
+
+ definitiveObjIdComponent # FIXME: type?
+ : Identifier # NameForm
+ { result = [val[0], nil] }
+ | Number # DefinitiveNumberForm
+ { result = [nil, val[0]] }
+ | Identifier "(" Number ")" # DefinitiveNameAndNumberForm
+ { result = [val[0], val[2]] }
+ ;
+
+ tagDefault
+ : # empty
+ { result = :EXPLICIT }
+ | EXPLICIT TAGS
+ { result = :EXPLICIT }
+ | IMPLICIT TAGS
+ { result = :IMPLICIT }
+ | AUTOMATIC TAGS
+ { result = :AUTOMATIC }
+ ;
+
+ extensionDefault
+ : # empty
+ { result = false }
+ | EXTENSIBILITY IMPLIED
+ { result = true }
+ ;
+
+ moduleBody
+ : # empty
+ { result = [] }
+ | exports imports assignmentList
+ { result = val }
+ ;
+
+ exports
+ : # empty
+ | EXPORTS ";"
+ | EXPORTS symbolList ";"
+ | EXPORTS ALL ";"
+ ;
+
+ imports
+ : # empty
+ { result = [] }
+ | IMPORTS ";"
+ { result = [] }
+ | IMPORTS symbolsFromModuleList ";"
+ { raise ParseError, "Imports not supported" }
+ ;
+
+ symbolsFromModuleList
+ : symbolsFromModule
+ | symbolsFromModuleList symbolsFromModule
+ ;
+
+ symbolsFromModule
+ : symbolList FROM modulereference assignedIdentifier;
+ ;
+
+ assignedIdentifier
+ : # empty
+ | objectIdentifierValue
+ # FIXME: Resolve shift/reduce conflict
+ # | definedValue
+ ;
+
+ symbolList
+ : reference
+ | symbolList "," reference
+ ;
+
+ reference
+ : typereference # including objectclassreference, objectsetreference
+ | Identifier # valuereference, objectreference
+ ;
+
+ assignmentList
+ : assignment
+ { result = [val[0]] }
+ | assignmentList assignment
+ { result << val[1] }
+ ;
+
+ # 14. Referencing type and value definitions
+ assignment
+ # TypeAssignment
+ : typereference "::=" type
+ { result = [val[0], val[2]] }
+ # ValueAssignment
+ | Identifier type "::=" value
+ { result = [val[0], val[1], val[3]] }
+ ;
+
+ definedType
+ : typereference
+ { result = D::TypereferenceNode.new(val[0]) }
+ | modulereference "." typereference
+ { raise ParseError, "ExternalTypeReference not supported" }
+ ;
+
+ definedValue
+ : Identifier
+ # May be INTEGER/ENUMERATED identifier
+ { result = D::ValuereferenceNode.new(val[0]) }
+ | modulereference "." Identifier
+ { raise ParseError, "ExternalValueReference not supported" }
+ ;
+
+ # 17. Definition of types and values
+ type
+ : builtinType
+ | referencedType
+ | constrainedType
+ ;
+
+ builtinType
+ # 18. BooleanType
+ : BOOLEAN
+ { result = D::SimpleTypeNode.new(:BOOLEAN) }
+ # 19. IntegerType
+ | INTEGER
+ { result = D::SimpleTypeNode.new(:INTEGER) }
+ | INTEGER '{' namedNumberList '}'
+ { result = D::SimpleTypeNode.new(:INTEGER, val[2]) }
+ # 20. EnumeratedType
+ | ENUMERATED "{" enumerations "}"
+ { result = D::SimpleTypeNode.new(:ENUMERATED, val[2]) }
+ # 21. RealType
+ | REAL
+ { result = D::SimpleTypeNode.new(:REAL) }
+ # 22. BitStringType
+ | BIT STRING
+ { result = D::SimpleTypeNode.new(:BIT_STRING) }
+ | BIT STRING "{" namedBitList "}"
+ { result = D::SimpleTypeNode.new(:BIT_STRING, val[3]) }
+ # 23. OctetStringType
+ | OCTET STRING
+ { result = D::SimpleTypeNode.new(:OCTET_STRING) }
+ # 24. NullType
+ | NULL
+ { result = D::SimpleTypeNode.new(:NULL) }
+ # 25. SequenceType
+ | SEQUENCE "{" "}"
+ { result = D::SequenceTypeNode.new([]) }
+ | SEQUENCE "{" componentTypeLists "}"
+ { result = D::SequenceTypeNode.new(val[2]) }
+ # 26. SequenceOfType
+ | SEQUENCE OF type
+ { result = D::SequenceOfTypeNode.new(val[2]) }
+ | SEQUENCE OF namedType
+ { result = D::SequenceOfTypeNode.new(val[2].type, type_name: val[2].name) }
+ # 27. SetType
+ | SET "{" "}"
+ { result = D::SetTypeNode.new([]) }
+ | SET "{" componentTypeLists "}"
+ { result = D::SetTypeNode.new(val[2]) }
+ # 28. SetOfType
+ | SET OF type
+ { result = D::SetOfTypeNode.new(val[2]) }
+ | SET OF namedType
+ { result = D::SetOfTypeNode.new(val[2].type, type_name: val[2].name) }
+ # 29. ChoiceType
+ | CHOICE "{" alternativeTypeList "}"
+ { result = D::SimpleTypeNode.new(:CHOICE, val[2]) }
+ # 31. PrefixedType
+ | prefixedType
+ # 32. ObjectIdentifierType
+ | OBJECT IDENTIFIER
+ { result = D::SimpleTypeNode.new(:OBJECT_IDENTIFIER) }
+ # 33. RelativeOIDType
+ | RELATIVE_OID
+ { result = D::SimpleTypeNode.new(:RELATIVE_OID) }
+ # 34. IRIType
+ | OID_IRI
+ { raise ParseError, "OID-IRI type not supported" }
+ # 35. RelativeIRIType
+ | RELATIVE_OID_IRI
+ { raise ParseError, "RELATIVE-OID-IRI type not supported" }
+ # 36. EmbeddedPDVType
+ | EMBEDDED PDV
+ { raise ParseError, "EMBEDDED PDV type not supported" }
+ # 37. ExternalType
+ | EXTERNAL
+ { raise ParseError, "EXTERNAL type not supported" }
+ # 38.1.1. TimeType
+ | TIME
+ { raise ParseError, "TIME type not supported" }
+ # 38.4.1. DateType
+ | DATE
+ { raise ParseError, "DATE type not supported" }
+ # 38.4.2. TimeOfDayType
+ | TIME_OF_DAY
+ { raise ParseError, "TIME-OF-DAY type not supported" }
+ # 38.4.3. DateTimeType
+ | DATE_TIME
+ { raise ParseError, "DATE-TIME type not supported" }
+ # 38.4.4. DurationType
+ | DURATION
+ { raise ParseError, "DURATION type not supported" }
+ # 40. CharacterStringType
+ | characterStringType
+ # X.681 Annex C. InstanceOfType
+ # X.681 14.1. ObjectClassFieldType
+ ;
+
+ referencedType
+ : definedType
+ # UsefulType; actually typeidentifier, but only these three are possible
+ | GeneralizedTime
+ { result = D::SimpleTypeNode.new(:GeneralizedTime) }
+ | UTCTime
+ { result = D::SimpleTypeNode.new(:UTCTime) }
+ | ObjectDescriptor
+ { result = D::SimpleTypeNode.new(:ObjectDescriptor) }
+ ;
+
+ namedType
+ : Identifier type
+ { result = D::NamedTypeNode.new(val[0], val[1]) }
+ ;
+
+ value
+ : builtinValue
+ # ReferencedValue and identifier alternative of IntegerValue
+ # and EnumeratedValue
+ | referencedValue
+ ;
+
+ builtinValue
+ : TRUE
+ { result = D::SimpleValueNode.new(:boolean, true) }
+ | FALSE
+ { result = D::SimpleValueNode.new(:boolean, false) }
+ | signedNumber
+ { result = D::SimpleValueNode.new(:integer, val[0]) }
+ | realValue
+ | Bstring
+ { result = D::SimpleValueNode.new(:bstring, val[0]) }
+ | Hstring
+ { result = D::SimpleValueNode.new(:hstring, val[0]) }
+ | NULL
+ { result = D::SimpleValueNode.new(:null, nil) }
+ # BitStringValue, {Sequence,Set}{Of,}Value, ObjectIdentifierValue,
+ # RelativeOIDValue, and RestrictedCharacterStringValue.
+ | unparsedValue
+ # IRIValue, RelativeIRIValue, and TimeValue are not supported
+ ;
+
+ # "{" something "}" in value is ambiguous. Let's parse after determining the
+ # type.
+ unparsedValue
+ : "{" { @lex_state << :unparsed_value } unparsed # "}"
+ { result = D::UnparsedValueNode.new("{" << val[2]) }
+ ;
+
+ unparsed
+ : Unparsed
+ | unparsed Unparsed
+ { result << val[1] }
+ ;
+
+ referencedValue
+ : definedValue
+ ;
+
+ namedNumberList
+ : namedNumber {
+ result = [val[0]] }
+ | namedNumberList ',' namedNumber
+ { result << val[2] }
+ ;
+
+ namedNumber
+ : Identifier '(' signedNumber ')' {
+ result = [val[0], val[2]] }
+ | Identifier '(' definedValue ')' {
+ result = [val[0], val[2]] }
+ ;
+
+ signedNumber
+ : Number
+ | Negative_number
+ ;
+
+ # X.680 20 Notation for the enumerated type
+ enumerations
+ : enumeration
+ { result = [val[0], nil] }
+ | enumeration "," "..." exceptionSpec
+ { result = [val[0], []] }
+ | enumeration "," "..." exceptionSpec "," enumeration
+ { result = [val[0], val[5]] }
+ ;
+
+ enumeration
+ : enumerationItem
+ { result = [val[0]] }
+ | enumeration "," enumerationItem
+ { result << val[2] }
+ ;
+
+ enumerationItem
+ : Identifier
+ { result = [val[0], nil] }
+ | namedNumber
+ ;
+
+ # 21. Notation for the real type
+ realValue
+ # NumericRealValue - SequenceValue form is omitted
+ : Realnumber # Includes '"-" realnumber' form
+ { result = D::SimpleValueNode.new(:real, val[0]) }
+ # SpecialRealValue
+ | PLUS_INFINITY
+ { result = D::SimpleValueNode.new(:real, :PLUS_INFINITY) }
+ | MINUS_INFINITY
+ { result = D::SimpleValueNode.new(:real, :MINUS_INFINITY) }
+ | NOT_A_NUMBER
+ { result = D::SimpleValueNode.new(:real, :NOT_A_NUMBER) }
+ ;
+
+
+ # X.680 22 Notation for the bitstring type
+ namedBit
+ : Identifier "(" Number ")"
+ { result = [val[0], val[2]] }
+ | Identifier "(" definedValue ")"
+ { result = [val[0], val[2]] }
+ ;
+
+ namedBitList
+ : namedBit
+ { result = [val[0]] }
+ | namedBitList "," namedBit
+ { result << val[2] }
+ ;
+
+ # X.680 25 Notation for sequence types
+ extensionAndException
+ | "..." exceptionSpec
+ { raise ParseError, "ExceptionSpec in ComponentTypeLists not supported" if val[1] }
+ ;
+
+ componentTypeLists
+ : componentTypeList
+ { result = [val[0]] }
+ | componentTypeList "," extensionAndException extensionAdditions
+ { result = [val[0], :extension, val[3]] }
+ | componentTypeList "," extensionAndException extensionAdditions "," "..."
+ { result = [val[0], :extension, val[3], :extension] }
+ | componentTypeList "," extensionAndException extensionAdditions "," "..." "," componentTypeList
+ { result = [val[0], :extension, val[3], :extension, val[7]] }
+ | extensionAndException extensionAdditions
+ { result = [:extension, val[1]] }
+ | extensionAndException extensionAdditions "," "..."
+ { result = [:extension, val[1], :extension] }
+ | extensionAndException extensionAdditions "," "..." "," componentTypeList
+ { result = [:extension, val[1], :extension, val[5]] }
+ ;
+
+ extensionAdditions
+ : # empty
+ { result = [] }
+ | "," extensionAdditionList
+ { result = val[1] }
+ ;
+
+ extensionAdditionList
+ : extensionAddition
+ { result = [val[0]] }
+ | extensionAdditionList "," extensionAddition
+ { result << val[2] }
+ ;
+
+ extensionAddition
+ : componentType
+ | extensionAdditionGroup
+ { raise ParseError, "ExtensionAdditionGroup not supported" }
+ ;
+
+ extensionAdditionGroup
+ : "[[" componentTypeLists "]]"
+ | "[[" Number ":" componentTypeLists "]]"
+ ;
+
+ componentTypeList
+ : componentType
+ { result = [val[0]] }
+ | componentTypeList ',' componentType
+ { result << val[2] }
+ ;
+
+ componentType
+ : Identifier type
+ { result = D::ComponentTypeNode.new(val[0], val[1]) }
+ | Identifier type DEFAULT value
+ { result = D::ComponentTypeNode.new(val[0], val[1], default: val[3]) }
+ | Identifier type OPTIONAL
+ { result = D::ComponentTypeNode.new(val[0], val[1], optional: true) }
+ ;
+
+ alternativeTypeList
+ : namedType
+ { result = [val[0]] }
+ | alternativeTypeList "," namedType
+ { result << val[2] }
+ ;
+
+
+
+ prefixedType
+ : taggedType
+ ;
+
+ taggedType
+ : "[" class classNumber "]" type
+ { result = D::TaggedTypeNode.new(val[4], nil, val[1], val[2]) }
+ | "[" class classNumber "]" IMPLICIT type
+ { result = D::TaggedTypeNode.new(val[5], :implicit, val[1], val[2]) }
+ | "[" class classNumber "]" EXPLICIT type
+ { result = D::TaggedTypeNode.new(val[5], :explicit, val[1], val[2]) }
+ ;
+
+ classNumber
+ : Number
+ | definedValue
+ ;
+
+ class
+ : # empty
+ { result = :CONTEXT_SPECIFIC }
+ | UNIVERSAL
+ { result = :UNIVERSAL }
+ | APPLICATION
+ { result = :APPLICATION }
+ | PRIVATE
+ { result = :PRIVATE }
+ ;
+
+
+ objectIdentifierValue
+ # Includes "{" definedValue objIdComponentsList "}" pattern
+ : "{" objIdComponentsList "}"
+ { result = D::ObjectIdentifierValueNode.new(val[1]) }
+ ;
+
+ objIdComponentsList
+ : objIdComponents
+ { result = [val[0]] }
+ | objIdComponentsList objIdComponents
+ { result << val[1] }
+ ;
+
+ objIdComponents
+ : Number
+ { result = [nil, val[0]] }
+ | Identifier
+ { result = [val[0], nil] }
+ | Identifier "(" Number ")"
+ { result = [val[0], val[2]] }
+ | Identifier "(" Identifier ")"
+ { result = [val[0], val[2]] }
+ ;
+
+
+ characterStringType
+ : restrictedCharacterStringType
+ { result = D::SimpleTypeNode.new(val[0].intern) }
+ # unrestrictedCharacterStringType
+ | CHARACTER STRING
+ { result = D::SimpleTypeNode.new(:CHARACTER_STRING) }
+ ;
+
+ restrictedCharacterStringType
+ : BMPString
+ | GeneralString
+ | GraphicString
+ | IA5String
+ | ISO646String
+ | NumericString
+ | PrintableString
+ | TeletexString
+ | T61String
+ | UniversalString
+ | UTF8String
+ | VideotexString
+ | VisibleString
+ ;
+
+
+
+
+ constrainedType
+ : type constraint
+ { result = val[0].update(constraint: val[1]) }
+ | typeWithConstraint
+ ;
+
+ typeWithConstraint
+ : SET constraint OF type
+ { result = D::SetOfTypeNode.new(val[3]).update(constraint: val[1]) }
+ | SET sizeConstraint OF type
+ { result = D::SetOfTypeNode.new(val[3]).update(constraint: val[1]) }
+ | SEQUENCE constraint OF type
+ { result = D::SequenceOfTypeNode.new(val[3]).update(constraint: val[1]) }
+ | SEQUENCE sizeConstraint OF type
+ { result = D::SequenceOfTypeNode.new(val[3]).update(constraint: val[1]) }
+ | SET constraint OF namedType
+ { result = D::SetOfTypeNode.new(val[3].type, type_name: val[3].name).update(constraint: val[1]) }
+ | SET sizeConstraint OF namedType
+ { result = D::SetOfTypeNode.new(val[3].type, type_name: val[3].name).update(constraint: val[1]) }
+ | SEQUENCE constraint OF namedType
+ { result = D::SequenceOfTypeNode.new(val[3].type, type_name: val[3].name).update(constraint: val[1]) }
+ | SEQUENCE sizeConstraint OF namedType
+ { result = D::SequenceOfTypeNode.new(val[3].type, type_name: val[3].name).update(constraint: val[1]) }
+ ;
+
+ constraint
+ : "(" constraintSpec exceptionSpec ")"
+ { result = val[1] }
+ ;
+
+ constraintSpec
+ # SubtypeConstraint
+ : elementSetSpecs
+ ;
+
+ elementSetSpecs
+ : elementSetSpec
+ | elementSetSpec "," "..."
+ | elementSetSpec "," "..." "," elementSetSpec
+ ;
+
+ elementSetSpec
+ : unions
+ | ALL EXCEPT elements
+ ;
+
+ unions
+ : intersections
+ | unions unionMark intersections
+ ;
+
+ intersections
+ : intersectionElements
+ | intersections intersectionMark intersectionElements
+ ;
+
+ intersectionElements
+ : elements
+ | elements EXCEPT elements
+ ;
+
+ unionMark : "|" | UNION ;
+
+ intersectionMark : "^" | INTERSECTION ;
+
+ elements
+ : subtypeElements
+ | "(" elementSetSpec ")"
+ ;
+
+ subtypeElements
+ # SingleValue
+ : value
+ { result = D::Constraint.new(:SingleValue, val[0]) }
+ # ValueRange
+ | lowerEndpoint ".." upperEndpoint
+ { result = D::ValueRange.new(val[0], val[2]) }
+ # SizeConstraint
+ | sizeConstraint
+ ;
+
+ lowerEndpoint
+ : lowerEndValue
+ { result = [val[0], :inclusive] }
+ | lowerEndValue "<"
+ { result = [val[0], :exclusive] }
+ ;
+
+ upperEndpoint
+ : upperEndValue
+ { result = [val[0], :inclusive] }
+ | "<" upperEndValue
+ { result = [val[1], :exclusive] }
+ ;
+
+ lowerEndValue : value | MIN ;
+ upperEndValue : value | MAX ;
+
+ sizeConstraint
+ : SIZE constraint
+ { result = D::SizeConstraint.new(val[1]) }
+ ;
+
+ # 53
+ exceptionSpec
+ : # empty
+ | "!" exceptionIdentification
+ { raise ParseError, "ExceptionSpec not supported yet" }
+ ;
+
+ exceptionIdentification
+ : signedNumber
+ | definedValue
+ | type ":" value
+ ;
+
+ typereference : Objectclassreference | Typereference ;
+ modulereference : Objectclassreference | Typereference ;
+
+ # Hack for UnparsedValueNode. A valid ASN.1 module never starts with the
+ # character '_' so I think it is safe.... though ugly
+ root
+ : moduleDefinition
+ | __CompoundBitStringValue compoundBitStringValue
+ { result = val[1] }
+ | __ObjectIdentifierValue objectIdentifierValue
+ { result = val[1] }
+ | __SequenceValue sequenceValue
+ { result = val[1] }
+ | __SequenceOfValue sequenceOfValue
+ { result = val[1] }
+ ;
+
+ compoundBitStringValue
+ : "{" "}"
+ { result = [] }
+ | "{" identifierList "}"
+ { result = val[1] }
+ ;
+
+ identifierList
+ : Identifier
+ { result = [val[0]] }
+ | identifierList "," Identifier
+ { result << val[2] }
+ ;
+
+ sequenceValue
+ : "{" "}"
+ { result = [] }
+ | "{" componentValueList "}"
+ { result = val[1] }
+ ;
+
+ componentValueList
+ : namedValue
+ { result = [val[0]] }
+ | componentValueList "," namedValue
+ { result << val[2] }
+ ;
+
+ namedValue
+ : Identifier value
+ { result = val }
+ ;
+
+ sequenceOfValue
+ : "{" "}"
+ { result = [] }
+ | "{" valueList "}"
+ { result = val[1] }
+ # Equivalent to NamedValueList
+ | "{" componentValueList "}"
+ { result = val[1] }
+ ;
+
+ valueList
+ : value
+ { result = [[nil, val[0]]] }
+ | valueList "," value
+ { result << [nil, val[2]] }
+ ;
+
+---- inner
+
+ D = ASN1Kit::Internal::Nodes
+ private_constant :D
+
+ LEXICAL_KEYWORDS = %w{
+ ANY ABSENT ALL APPLICATION AUTOMATIC BEGIN BIT BMPString BOOLEAN BY
+ CHARACTER CHOICE CLASS COMPONENT COMPONENTS CONSTRAINED CONTAINING DATE
+ DATE-TIME DEFAULT DEFINITIONS DURATION EMBEDDED ENCODED ENCODING-CONTROL
+ END ENUMERATED EXCEPT EXPLICIT EXPORTS EXTENSIBILITY EXTERNAL FALSE FROM
+ GeneralizedTime GeneralString GraphicString IA5String IDENTIFIER
+ IMPLICIT IMPLIED IMPORTS INCLUDES INSTANCE INSTRUCTIONS INTEGER
+ INTERSECTION ISO646String MAX MINUS-INFINITY MIN NOT-A-NUMBER NULL
+ NumericString OBJECT ObjectDescriptor OCTET OF OID-IRI OPTIONAL PATTERN
+ PDV PLUS-INFINITY PRESENT PrintableString PRIVATE REAL RELATIVE-OID
+ RELATIVE-OID-IRI SEQUENCE SET SETTINGS SIZE STRING SYNTAX T61String TAGS
+ TeletexString TIME TIME-OF-DAY TRUE UNION UNIQUE UNIVERSAL
+ UniversalString UTCTime UTF8String VideotexString VisibleString WITH
+ }
+
+ WS = "[\n\v\f\r ]"
+
+ def next_token
+ token = nil
+ s = @scanner
+ state = @lex_state
+
+ cstring = nil
+
+ until token or s.eos?
+ @lineno += 1 if s.peek(1) == ?\n
+ case state.last
+ when nil
+ case
+ when s.skip(/--.*?(?:--|$)/)
+ when s.skip(/\/\*/)
+ state << :block_comment
+ when t = s.scan(/__\w+/) # Internal
+ token = [t.intern, t]
+ when t = s.scan(/"[^"]*/)
+ cstring = text[1..-1]
+ state << :cstring
+ when t = s.scan(/'(?:[0-9A-F]|#{WS})+'H/o)
+ token = [:Hstring, t]
+ when t = s.scan(/'(?:[01]|#{WS})+'B/o)
+ token = [:Bstring, t]
+ when t = s.scan(/-[1-9][0-9]*/)
+ token = [:Negative_number, t.to_i]
+ when t = s.scan(/(?:0|[1-9][0-9]*)/)
+ token = [:Number, t.to_i]
+ when t = s.scan(/[-+]?[0-9]+\.?(?:[eE][-+]?)?[0-9]+/)
+ token = [:Realnumber, t.to_i]
+ when t = s.scan(/(?:#{LEXICAL_KEYWORDS.join("|")})/o)
+ token = [t.tr("-", "_").intern, t]
+ when t = s.scan(/[a-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*/)
+ token = [:Identifier, t]
+ when t = s.scan(/[A-Z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*/)
+ token = [:Typereference, t]
+ when t = s.scan(/[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*/)
+ token = [:Objectclassreference, t]
+ when t = s.scan(/(?:::=|\.\.\.|\.\.|\[\[|\]\])/)
+ token = [t, t]
+ when t = s.scan(/[(){},.;:!|&@\[\]^]/)
+ token = [t, t]
+ when s.skip(/#{WS}+/o)
+ else unreachable
+ end
+ when :block_comment
+ case
+ when s.skip(/[^*\/]+/)
+ when s.skip(/\*\//)
+ state.pop
+ when s.skip(/\/\*/)
+ state << :block_comment
+ when s.skip(/./)
+ else unreachable
+ end
+ when :cstring
+ case
+ when s.skip(/""/)
+ cstring << ?"
+ when t = s.scan(/[^"]+/)
+ cstring << t
+ when s.skip(/"/)
+ state.pop
+ token = [:Cstring, cstring]
+ else unreachable
+ end
+ when :unparsed_value
+ # This state is pushed from parser on demand.
+ case
+ when s.skip(/{/)
+ state << :unparsed_value
+ token = [:Unparsed, "{"]
+ when s.skip(/}/)
+ state.pop
+ token = [:Unparsed, "}"]
+ when t = s.scan(/[^{}]+/)
+ token = [:Unparsed, t]
+ else unreachable
+ end
+ else unreachable
+ end
+ end
+
+ token || [false, "$"]
+ end
+
+ def on_error(a, b, c)
+ p [a, b, c]
+ p @lex_state
+ raise "on error"
+ end
+
+ def parse(str)
+ @scanner = StringScanner.new(str)
+ @lineno = 1
+ @lex_state = [nil]
+ do_parse
+ end
+
+ private def unreachable
+ raise "internal error: unreachable"
+ end
diff --git a/lib/asn1kit/types.rb b/lib/asn1kit/types.rb
new file mode 100644
index 0000000..817c842
--- /dev/null
+++ b/lib/asn1kit/types.rb
@@ -0,0 +1,183 @@
+# coding: ASCII-8BIT
+using ASN1Kit::BERString
+
+module ASN1Kit
+ class Type
+ CONSTRAINT = nil
+
+ class Tag < Struct.new(:tagging, :tag_class, :tag_number)
+ def acceptable_ber_header?(str, offset = 0)
+ tc, tn, constructed, hlen, len = str.get_object(offset)
+ if tc == tag_class && tn == tag_number
+ [constructed, hlen, len]
+ else
+ nil
+ end
+ end
+ end
+ TAG = nil
+
+ class << self
+ def tagging!(tagging, tag_class, tag_number)
+ const_set(:TAG, Tag.new(tagging, tag_class, tag_number))
+ end
+
+ def asn1_tag(tagging, tag_class, tag_number)
+ const_set(:TAG, Tag.new(tagging, tag_class, tag_number))
+ end
+
+ def asn1_alias(name)
+ instance_variable_set(:@alias, name)
+ end
+
+ def tagging(tagging, tag_class, tag_number)
+ ret = Class.new(self)
+ ret.tagging!(tagging, tag_class, tag_number)
+ ret
+ end
+
+ def from_ber(str)
+ list = self::TAG.acceptable_ber_header?(str) or
+ raise EncodingError, "invalid object: %p, %p, %p, %p, %p" % str.get_object(str)
+
+ constructed, hlen, len = list
+ if self::TAG.tagging == :EXPLICIT
+ raise EncodingError, "invalid EXPLICIT tagging encoding" unless constructed
+ c = self
+ while c = c.superclass
+ raise EncodingError, "invalid EXPLICIT tagging: %p" % self unless c < ASN1Kit::Type
+ break unless c::TAG.equal?(self::TAG)
+ end
+ list_inner = c::TAG.acceptable_ber_header?(str, hlen) or
+ raise EncodingError, "invalid EXPLICIT tagging inner object"
+ end
+
+ if len == 0x80
+ from_ber_content(str, 0, nil, true)
+ else
+ from_ber_content(str, 0, len, constructed)
+ end
+ end
+
+ private def from_ber_content(str, offset, len, constructed)
+ raise NotImplementedError, "BER decoding for %p not implemented" % self
+ end
+
+ def check_ber_header(str, expect_con = false, pos: 0)
+ tc, con, tn, len, pos = str.get_object(pos)
+ if tc != self::TAG.tag_class || expect_con != con || tn != self::TAG.tag_number
+ raise "invalid object: %p, %p, %p, %p, %p" % [tc, con, tn, len, pos]
+ end
+ [len, pos]
+ end
+
+ def builtin?
+ false
+ end
+
+ def inspect_abbrev
+ defined?(@alias) ? "<#{@alias}>" : inspect
+ end
+
+ def inspect
+ name = defined?(@alias) ? "(#{@alias}) " : ""
+ type_ancestors = ancestors.drop(1).select { |x| x < Type }
+ baseclass = type_ancestors.find { |x| x.instance_variable_defined?(:@alias) }
+ baseclass ||= type_ancestors.reverse_each.find { |x| x < ASN1Kit::Type }
+ if baseclass
+ if self::TAG != baseclass::TAG
+ prefix = "["
+ if self::TAG.tag_class != :CONTEXT_SPECIFIC
+ prefix << self::TAG.tag_class.to_s << " "
+ end
+ prefix << self::TAG.tag_number.to_s << "] "
+ end
+ bname = baseclass.instance_variable_get(:@alias) || baseclass.name
+ end
+ if body = inspect_inner
+ "<#{name}#{prefix}#{bname} #{body}>"
+ else
+ "<#{name}#{prefix}#{bname}>"
+ end
+ end
+
+ private def inspect_inner() nil end
+ end
+
+ def to_der
+ raise NotImplementedError, "DER encoding for %p not implemented" % self.class
+ end
+
+ private def der_header(len, encoding = :primitive)
+ "".put_object(self.class::TAG.tag_class, self.class::TAG.tag_number, len, encoding)
+ end
+
+ def cast_to(type)
+ raise TypeError, "casting %p value into %p undefined" % [self.class, type]
+ end
+
+ def inspect_abbrev
+ defined?(@alias) ? "{#{@alias}}" : inspect
+ end
+
+ def inspect
+ name = defined?(@alias) ? "(#{@alias})" : ""
+ if body = inspect_inner
+ "{#{name}#{self.class.inspect_abbrev} #{body}}"
+ else
+ "{#{name}#{self.class.inspect_abbrev}}"
+ end
+ end
+
+ def initialize_copy(orig)
+ super
+ # UGHH; Duplicated instances should not have an alias
+ remove_instance_variable(:@alias) if defined?(@alias)
+ end
+
+ private def inspect_inner() defined?(@value) ? @value.inspect : nil end
+ end
+end
+
+module ASN1Kit::Internal
+ module CompileType
+ refine ASN1Kit::Type.singleton_class do
+ def _compile_fixup(state)
+ end
+
+ attr_reader :alias
+ def alias=(value)
+ unreachable if defined?(@alias)
+ @alias = value
+ end
+ end
+
+ refine ASN1Kit::Type do
+ def _compile_fixup(state)
+ end
+
+ attr_reader :alias
+ def alias=(value)
+ unreachable if defined?(@alias)
+ @alias = value
+ end
+ end
+ end
+end
+
+require_relative "types/boolean"
+require_relative "types/integer"
+require_relative "types/bit_string"
+require_relative "types/octet_string"
+require_relative "types/null"
+require_relative "types/object_identifier"
+require_relative "types/real"
+require_relative "types/enumerated"
+require_relative "types/relative_oid"
+require_relative "types/sequence"
+require_relative "types/sequence_of"
+require_relative "types/set"
+require_relative "types/set_of"
+require_relative "types/choice"
+require_relative "types/character_string_types"
+require_relative "types/useful_types"
diff --git a/lib/asn1kit/types/bit_string.rb b/lib/asn1kit/types/bit_string.rb
new file mode 100644
index 0000000..53426dd
--- /dev/null
+++ b/lib/asn1kit/types/bit_string.rb
@@ -0,0 +1,112 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::BitString < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 3
+ asn1_alias "BIT STRING"
+
+ NAMED_BITS = {}
+ NAMED_BITS_INVERTED = {}
+
+ class << self
+ def [](*named_bits)
+ return self if named_bits.empty?
+ ret = Class.new(self)
+ hash = named_bits.to_h
+ hash_inverted = hash.invert
+ raise "duplicate name(s) in named bit list" if named_bits.size != hash.size
+ raise "duplicate value(s) in named bit list" if hash.size != hash_inverted.size
+ ret.const_set(:NAMED_BITS, hash)
+ ret.const_set(:NAMED_BITS_INVERTED, hash.invert)
+ ret
+ end
+
+ def named_bit(name)
+ self::NAMED_BITS[name]
+ end
+
+ def named_bits
+ self::NAMED_BITS.keys
+ end
+
+ private def inspect_inner
+ return nil if self::NAMED_BITS.empty?
+ nns = self::NAMED_BITS.map { |name, value|
+ "#{name}(#{value})"
+ }.join(", ")
+ "{ #{nns} }"
+ end
+
+ def new_with_names(*list)
+ if self::NAMED_BITS.empty?
+ raise TypeError, "BIT STRING without NamedBits defined cannot be " \
+ "instantiated by #from_names"
+ end
+ return new(String.new, 0) if list.empty?
+
+ bits = list.map { |name|
+ named_bit(name) or
+ raise ArgumentError, "invalid name: %p" % name
+ }
+ bit_length = bits.max + 1
+ str = "0".b * bit_length
+ bits.each { |pos| str[pos] = "1" }
+ new([str].pack("B*"), bit_length)
+ end
+ end
+
+ attr_reader :string, :bit_length
+
+ def initialize(str, bit_length)
+ if bit_length <= (str.bytesize - 1) * 8 || bit_length > str.bytesize * 8
+ raise ArgumentError, "invalid bit_length"
+ end
+ @string = str
+ @bit_length = bit_length
+ end
+
+ def set?(name_or_pos)
+ if name_or_pos.is_a?(String)
+ name_or_pos = self.class.named_bit(name_or_pos)
+ end
+ unless name_or_pos.is_a?(::Integer)
+ raise TypeError, "invalid argument type: %p" % name_or_pos
+ end
+ byte = @string.getbyte(name_or_pos / 8)
+ byte[7 - name_or_pos % 8] == 1
+ end
+
+ def to_der
+ byte_length = (bit_length / 8r).ceil
+ unused_bits = byte_length * 8 - bit_length
+ content = [unused_bits].pack("C") << @string
+ der_header(byte_length + 1) << content
+ end
+
+ def ==(other)
+ return false unless other.is_a?(ASN1Kit::BitString)
+ return false unless self.class::NAMED_BITS.equal?(other.class::NAMED_BITS)
+ value == other.value && bit_length == other.bit_length
+ end
+end
+
+module ASN1Kit::Internal::CompileBitString
+ refine ASN1Kit::BitString.singleton_class do
+ def _compile_fixup(state)
+ self::NAMED_BITS.transform_values! do |value|
+ if value.is_a?(ASN1Kit::Internal::UnresolvedValue)
+ vname = value.value.name
+ value = state.value_symbols[vname] ||
+ raise("unresolved value: %s" % vname)
+ if !value.is_a?(ASN1Kit::Integer)
+ raise ASN1Kit::ParseError, "DefinedValue in NamedBit resolved to non-integer: %s" % vname
+ end
+ if value.value < 0
+ raise ASN1Kit::ParseError, "DefinedValue in NamedBit resolved to negative integer: %s" % vname
+ end
+ value = value.value
+ end
+ value
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/types/boolean.rb b/lib/asn1kit/types/boolean.rb
new file mode 100644
index 0000000..b8a1c73
--- /dev/null
+++ b/lib/asn1kit/types/boolean.rb
@@ -0,0 +1,41 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Boolean < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 1
+ asn1_alias "BOOLEAN"
+
+ def initialize(value)
+ self.value = value
+ end
+
+ def value
+ @value
+ end
+
+ def value=(value)
+ if value != true && value != false
+ raise TypeError, "illegal value for BOOLEAN: %p" % value
+ end
+ @value = value
+ end
+
+ def to_der
+ der_header(1) << (value ? "\xff" : "\x00")
+ end
+
+ def self.from_ber(ber)
+ content = check_ber_header(ber)
+ if content.bytesize != 1
+ raise EncodingError, "invalid content length for BOOLEAN"
+ end
+ new(content != "\x00")
+ end
+
+ def ==(other)
+ other.class <= ASN1Kit::Boolean && value == other.value
+ end
+
+ private def inspect_inner
+ @value ? "TRUE" : "FALSE"
+ end
+end
diff --git a/lib/asn1kit/types/character_string_types.rb b/lib/asn1kit/types/character_string_types.rb
new file mode 100644
index 0000000..93a5682
--- /dev/null
+++ b/lib/asn1kit/types/character_string_types.rb
@@ -0,0 +1,61 @@
+# coding: ASCII-8BIT
+
+module ASN1Kit
+ class CharacterStringType < Type
+ attr_reader :value
+
+ def initialize(string)
+ @value = string
+ end
+
+ def to_der
+ der_header(@value.bytesize) << @value
+ end
+ end
+
+ class UTF8String < CharacterStringType
+ asn1_tag :IMPLICIT, :UNIVERSAL, 12
+ end
+
+ class NumericString < CharacterStringType
+ asn1_tag :IMPLICIT, :UNIVERSAL, 18
+ end
+
+ class PrintableString < CharacterStringType
+ TAG_NUMBER = 19
+ end
+
+ class TeletexString < CharacterStringType
+ TAG_NUMBER = 20
+ end
+ T61String = TeletexString
+
+ class VideotexString < CharacterStringType
+ TAG_NUMBER = 21
+ end
+
+ class IA5String < CharacterStringType
+ TAG_NUMBER = 22
+ end
+
+ class GraphicString < CharacterStringType
+ TAG_NUMBER = 25
+ end
+
+ class VisibleString < CharacterStringType
+ TAG_NUMBER = 26
+ end
+ ISO646String = VisibleString
+
+ class GeneralString < CharacterStringType
+ TAG_NUMBER = 27
+ end
+
+ class UniversalString < CharacterStringType
+ TAG_NUMBER = 28
+ end
+
+ class BMPString < CharacterStringType
+ TAG_NUMBER = 30
+ end
+end
diff --git a/lib/asn1kit/types/choice.rb b/lib/asn1kit/types/choice.rb
new file mode 100644
index 0000000..04edef4
--- /dev/null
+++ b/lib/asn1kit/types/choice.rb
@@ -0,0 +1,18 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Choice < ASN1Kit::Type
+ class << self
+ def [](*alternative_types)
+ # FIXME
+ ret = Class.new(self)
+ hash = alternative_types.to_h
+ ret.const_set(:ALTERNATIVE_TYPES, hash)
+ ret
+ end
+ end
+
+ def to_der
+ @value.to_der
+ # replace tag
+ end
+end
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
diff --git a/lib/asn1kit/types/integer.rb b/lib/asn1kit/types/integer.rb
new file mode 100644
index 0000000..9104a5d
--- /dev/null
+++ b/lib/asn1kit/types/integer.rb
@@ -0,0 +1,87 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Integer < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 2
+ asn1_alias "INTEGER"
+
+ NAMED_NUMBERS = {}
+ NAMED_NUMBERS_INVERTED = {}
+
+ class << self
+ def [](*named_numbers)
+ return self if named_numbers.empty?
+ ret = Class.new(self)
+ hash = named_numbers.to_h
+ hash_inverted = hash.invert
+ raise "duplicate name(s) in named number list" if named_numbers.size != hash.size
+ raise "duplicate value(s) in named number list" if hash.size != hash_inverted.size
+ ret.const_set(:NAMED_NUMBERS, hash)
+ ret.const_set(:NAMED_NUMBERS_INVERTED, hash.invert)
+ ret
+ end
+
+ def named_number(name)
+ self::NAMED_NUMBERS[name]
+ end
+
+ def named_numbers
+ self::NAMED_NUMBERS.keys
+ end
+
+ private def inspect_inner
+ return nil if self::NAMED_NUMBERS.empty?
+ nns = self::NAMED_NUMBERS.map { |name, value| "#{name}(#{value})" }.join(", ")
+ "{ #{nns} }"
+ end
+ end
+
+ attr_reader :value
+
+ def initialize(value)
+ case value
+ when Integer
+ @value = value
+ when String
+ @value = self.class::NAMED_NUMBERS[value] or
+ raise ArgumentError, "invalid name %p" % value
+ else
+ raise TypeError, "invalid value: %p (expected an Integer or a NamedValue label)" % value
+ end
+ end
+
+ def name
+ self.class::NAMED_NUMBERS_INVERTED[@value]
+ end
+
+ def to_der
+ v = @value
+ if v >= 0
+ dig = v.digits(256)
+ dig << 0x00 if dig[-1] >= 0x80
+ s = dig.pack("C*").reverse
+ else
+ v = ~v
+ dig = v.digits(256)
+ dig << 0x00 if dig[-1] >= 0x80
+ s = dig.map!(&:~).pack("C*").reverse
+ end
+ der_header(s.bytesize) << s
+ end
+
+ def ==(other)
+ other.class <= ASN1Kit::Integer && value == other.value
+ end
+
+ def cast_to(type)
+ return type.new(@value) if type <= ASN1Kit::Integer
+ super
+ end
+
+ private def inspect_inner
+ if n = name
+ "#{@value}(#{n})"
+ else
+ "#{@value}"
+ end
+ end
+end
diff --git a/lib/asn1kit/types/null.rb b/lib/asn1kit/types/null.rb
new file mode 100644
index 0000000..db37744
--- /dev/null
+++ b/lib/asn1kit/types/null.rb
@@ -0,0 +1,18 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Null < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 5
+ asn1_alias "NULL"
+
+ def der_encoded_value
+ [:primitive, String.new]
+ end
+
+ def to_der
+ der_header(0)
+ end
+
+ def ==(other)
+ other.is_a?(ASN1Kit::Null)
+ end
+end
diff --git a/lib/asn1kit/types/object_identifier.rb b/lib/asn1kit/types/object_identifier.rb
new file mode 100644
index 0000000..1c8f69c
--- /dev/null
+++ b/lib/asn1kit/types/object_identifier.rb
@@ -0,0 +1,76 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::ObjectIdentifier < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 6
+ asn1_alias "OBJECT IDENTIFIER"
+
+ attr_reader :components
+
+ def initialize(components)
+ @components = components
+ end
+
+ def self.from_dot_notation(string)
+ new(string.split(".").map { |s| Integer(s) })
+ end
+
+ def dot_notation
+ @components.join(".")
+ end
+
+ def to_der
+ first, second, rest = @components
+ content = [first * 40 + second, *rest].pack("w*")
+ der_header(content.bytesize) << content
+ end
+
+ private def inspect_inner
+ "{ #{@components.join(" ")} }"
+ end
+
+ def cast_to(type)
+ return type.new(@components) if type <= ASN1Kit::ObjectIdentifier
+ super
+ end
+
+ def ==(other)
+ other.is_a?(ASN1Kit::ObjectIdentifier) && @components == other.components
+ end
+end
+
+module ASN1Kit::Internal::CompileObjectIdentifier
+ refine ASN1Kit::ObjectIdentifier.singleton_class do
+ def _new_internal(base, arys)
+ obj = allocate
+ obj.instance_variable_set(:@_compile_base, base)
+ obj.instance_variable_set(:@_compile_arys, arys)
+ obj
+ end
+ end
+
+ refine ASN1Kit::ObjectIdentifier do
+ def _compile_fixup(state)
+ base_value = remove_instance_variable(:@_compile_base)
+ arys = remove_instance_variable(:@_compile_arys)
+
+ if base_value
+ base = base_value.unwrap(state)
+ components = base.components.dup
+ else
+ components = []
+ end
+
+ arys.each do |ary|
+ case ary
+ when ASN1Kit::Internal::UnresolvedValue
+ rel_oid = ary.unwrap(state)
+ components.concat(rel_oid.components)
+ else
+ components.concat(ary.map { |v| v.unwrap_as_number(state) })
+ end
+ end
+
+ @components = components
+ end
+ end
+end
diff --git a/lib/asn1kit/types/octet_string.rb b/lib/asn1kit/types/octet_string.rb
new file mode 100644
index 0000000..1b8a89e
--- /dev/null
+++ b/lib/asn1kit/types/octet_string.rb
@@ -0,0 +1,21 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::OctetString < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 4
+ asn1_alias "OCTET STRING"
+
+ attr_reader :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def to_der
+ der_header(@value.bytesize) << @value
+ end
+
+ def ==(other)
+ return false unless other.is_a?(ASN1Kit::OctetString)
+ value == other.value
+ end
+end
diff --git a/lib/asn1kit/types/real.rb b/lib/asn1kit/types/real.rb
new file mode 100644
index 0000000..3b2a108
--- /dev/null
+++ b/lib/asn1kit/types/real.rb
@@ -0,0 +1,46 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Real < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 9
+ asn1_alias "REAL"
+
+ PLUS_INFINITY = :plus_infinity
+ MINUS_INFINITY = :minus_infinity
+ NOT_A_NUMBER = :not_a_number
+ MINUS_ZERO = :minus_zero
+
+ SPECIAL_VALUES = Set[
+ PLUS_INFINITY, MINUS_INFINITY, NOT_A_NUMBER, MINUS_ZERO
+ ]
+ private_constant :SPECIAL_VALUES
+
+ def initialize(value)
+ case value
+ when PLUS_INFINITY, MINUS_INFINITY, NOT_A_NUMBER, MINUS_ZERO
+ @value = value
+ when Integer
+ # TODO: Consider constraints
+ @value = { mantissa: value, base: 10, exponent: 0 }
+ when Float, Rational
+ raise NotImplementedError
+ else
+ # Assuming value is a { mantissa: M, base: B, exponent: E } value
+ @value = value
+ end
+ end
+
+ def cast_to(type)
+ return type.new(@value) if type <= ASN1Kit::Real
+ super
+ end
+
+ def plus_infinity?() @value == PLUS_INFINITY end
+ def minus_infinity?() @value == MINUS_INFINITY end
+ def not_a_number?() @value == NOT_A_NUMBER end
+ def minus_zero?() @value == MINUS_ZERO end
+ def raw_value() @value end
+
+ def ==(other)
+ other.is_a?(ASN1Kit::Real) && @value == other.raw_value
+ end
+end
diff --git a/lib/asn1kit/types/relative_oid.rb b/lib/asn1kit/types/relative_oid.rb
new file mode 100644
index 0000000..8485010
--- /dev/null
+++ b/lib/asn1kit/types/relative_oid.rb
@@ -0,0 +1,67 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::RelativeOID < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 13
+ asn1_alias "RELATIVE-OID"
+
+ attr_reader :components
+
+ def initialize(components)
+ @components = components
+ end
+
+ def dot_notation
+ @components.join(".")
+ end
+
+ def self.from_dot_notation(string)
+ new(string.split(".").map { |s| Integer(s) })
+ end
+
+ def to_der
+ content = @components.pack("w*")
+ der_header(content.bytesize) << content
+ end
+
+ private def inspect_inner
+ "{ #{@components.join(" ")} }"
+ end
+
+ def cast_to(type)
+ return type.new(@components) if type <= ASN1Kit::RelativeOID
+ super
+ end
+
+ def ==(other)
+ other.is_a?(ASN1Kit::RelativeOID) && @components == other.components
+ end
+end
+
+module ASN1Kit::Internal::CompileRelativeOID
+ refine ASN1Kit::RelativeOID.singleton_class do
+ def _new_internal(arys)
+ obj = allocate
+ obj.instance_variable_set(:@_compile_arys, arys)
+ obj
+ end
+ end
+
+ refine ASN1Kit::RelativeOID do
+ def _compile_fixup(state)
+ arys = remove_instance_variable(:@_compile_arys)
+
+ components = []
+ arys.each do |ary|
+ case ary
+ when ASN1Kit::Internal::UnresolvedValue
+ rel_oid = ary.unwrap(state)
+ components.concat(rel_oid.components)
+ else
+ components.concat(ary.map { |v| v.unwrap_as_number(state) })
+ end
+ end
+
+ @components = components
+ end
+ end
+end
diff --git a/lib/asn1kit/types/sequence.rb b/lib/asn1kit/types/sequence.rb
new file mode 100644
index 0000000..2fbf530
--- /dev/null
+++ b/lib/asn1kit/types/sequence.rb
@@ -0,0 +1,121 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Sequence < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 16
+ asn1_alias "SEQUENCE"
+
+ class ComponentType
+ attr_reader :name, :type, :default
+
+ def optional?
+ @optional
+ end
+
+ def initialize(name, type, default: nil, optional: false)
+ @name = name
+ @type = type
+ @default = default
+ @optional = optional
+ end
+
+ def inspect
+ str = "#{name} #{type.inspect_abbrev}"
+ str << " DEFAULT #{default.inspect_abbrev}" if default
+ str << " OPTIONAL" if optional?
+ str
+ end
+ end
+
+ class << self
+ def [](*component_types)
+ ret = Class.new(self)
+ hash = component_types.map { |c| [c.name, c] }.to_h
+ ret.const_set(:COMPONENT_TYPES, hash)
+ ret
+ end
+
+ def component_types
+ self::COMPONENT_TYPES.keys
+ end
+
+ def component_type(name)
+ self::COMPONENT_TYPES[name]&.type
+ end
+
+ private def inspect_inner
+ ctypes = self::COMPONENT_TYPES.values.map { |c| c.inspect }.join(", ")
+ "{ #{ctypes} }"
+ end
+
+ # def pretty_print(q)
+ # nil
+ # end
+
+ def from_ber(str)
+ ssize = str.bytesize
+ , pos = check_ber_header(str, constructed: true)
+ hash = {}
+ last = nil
+ self::COMPONENT_TYPES.each do |c|
+ last ||= str.get_object(pos)
+ if c.optional?
+ next if a
+ end
+ end
+ end
+ end
+
+ def initialize(value)
+ unless defined?(self.class::COMPONENT_TYPES)
+ raise "unable to instantiate uninitialized SEQUENCE"
+ end
+ @value = value
+ end
+
+ def [](name)
+ unless self.class::COMPONENT_TYPES[name]
+ raise ArgumentError, "invalid component type name: %p" % name
+ end
+ @value[name]
+ end
+
+ def to_der
+ content = self.class::COMPONENT_TYPES.map { |name, type, opts|
+ next nil unless @value[name]
+ @value[name].to_der
+ }.compact.join
+ der_header(content.bytesize, :constructed) << content
+ end
+end
+
+module ASN1Kit::Internal::CompileSequence
+ refine ASN1Kit::Sequence::ComponentType do
+ attr_writer :name, :type, :default, :optional
+ end
+
+ refine ASN1Kit::Sequence.singleton_class do
+ def _compile_fixup(state)
+ self::COMPONENT_TYPES.each do |name, c|
+ if c.type.is_a?(ASN1Kit::Internal::TypeReference)
+ c.type = c.type.unwrap(state)
+ end
+ state.resolve(c.type)
+ if c.default
+ c.default.type = c.type # opts[:default] may be still TypeReference at this time
+ c.default = c.default.unwrap(state)
+ state.resolve(c.default)
+ end
+ end
+ end
+ end
+
+ refine ASN1Kit::Sequence do
+ def _compile_fixup(state)
+ @value.transform_values! do |value|
+ value = value.unwrap(state)
+ state.resolve(value)
+ value
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/types/sequence_of.rb b/lib/asn1kit/types/sequence_of.rb
new file mode 100644
index 0000000..93e6e61
--- /dev/null
+++ b/lib/asn1kit/types/sequence_of.rb
@@ -0,0 +1,37 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::SequenceOf < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 16
+ asn1_alias "SEQUENCE OF"
+
+ class << self
+ def [](type)
+ ret = Class.new(self)
+ ret.const_set(:COMPONENT_TYPE, type)
+ ret
+ end
+
+ private def inspect_inner
+ self::COMPONENT_TYPE.inspect_abbrev
+ end
+ end
+
+ def to_der
+ content = @components.map { |c| c.to_der }.join
+ der_header(content.bytesize, :constructed) << content
+ end
+end
+
+module ASN1Kit::Internal::CompileSequenceOf
+ refine ASN1Kit::SequenceOf.singleton_class do
+ def _compile_fixup(state)
+ type = self::COMPONENT_TYPE
+ if type.is_a?(ASN1Kit::Internal::TypeReference)
+ type = type.unwrap(state)
+ state.resolve(type)
+ remove_const(:COMPONENT_TYPE)
+ const_set(:COMPONENT_TYPE, type)
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/types/set.rb b/lib/asn1kit/types/set.rb
new file mode 100644
index 0000000..a2eaf1a
--- /dev/null
+++ b/lib/asn1kit/types/set.rb
@@ -0,0 +1,28 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::Set < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 17
+ asn1_alias "SET"
+
+ class << self
+ def [](*component_types)
+ ret = Class.new(self)
+ hash = component_types.map { |c| [c.name, c] }.to_h
+ ret.const_set(:COMPONENT_TYPES, hash)
+ ret
+ end
+
+ private def inspect_inner
+ ctypes = self::COMPONENT_TYPES.values.map { |c| c.inspect }.join(", ")
+ "{ #{ctypes} }"
+ end
+ end
+
+ def to_der
+ content = self.class::COMPONENT_TYPES.map { |name, type, opts|
+ next nil unless @value[name]
+ @value[name].to_der
+ }.compact.join
+ der_header(content.bytesize, :constructed) << content
+ end
+end
diff --git a/lib/asn1kit/types/set_of.rb b/lib/asn1kit/types/set_of.rb
new file mode 100644
index 0000000..341810c
--- /dev/null
+++ b/lib/asn1kit/types/set_of.rb
@@ -0,0 +1,37 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::SetOf < ASN1Kit::Type
+ asn1_tag :IMPLICIT, :UNIVERSAL, 17
+ asn1_alias "SET OF"
+
+ class << self
+ def [](type)
+ ret = Class.new(self)
+ ret.const_set(:COMPONENT_TYPE, type)
+ ret
+ end
+
+ private def inspect_inner
+ self::COMPONENT_TYPE.inspect_abbrev
+ end
+ end
+
+ def to_der
+ content = @components.map { |c| c.to_der }.join
+ der_header(content.bytesize, :constructed) << content
+ end
+end
+
+module ASN1Kit::Internal::CompileSetOf
+ refine ASN1Kit::SetOf.singleton_class do
+ def _compile_fixup(state)
+ type = self::COMPONENT_TYPE
+ if type.is_a?(ASN1Kit::Internal::TypeReference)
+ type = type.unwrap(state)
+ state.resolve(type)
+ remove_const(:COMPONENT_TYPE)
+ const_set(:COMPONENT_TYPE, type)
+ end
+ end
+ end
+end
diff --git a/lib/asn1kit/types/useful_types.rb b/lib/asn1kit/types/useful_types.rb
new file mode 100644
index 0000000..f868879
--- /dev/null
+++ b/lib/asn1kit/types/useful_types.rb
@@ -0,0 +1,36 @@
+# coding: ASCII-8BIT
+
+class ASN1Kit::UTCTime < ASN1Kit::VisibleString
+ TAG_NUMBER = 23
+
+ def initialize(value)
+ case value
+ when String
+ @value = value
+ when Time
+ if value.utc_offset == 0
+ @value = value.strftime("%y%m%d%H%M%SZ")
+ else
+ @value = value.strftime("%y%m%d%H%M%S%z")
+ end
+ else
+ raise ArgumentError, "%p cannot be parsed" % value
+ end
+ end
+
+ def to_time
+ raise NotImplementedError
+ end
+
+ def to_der
+ raise NotImplementedError
+ end
+end
+
+class ASN1Kit::GeneralizedTime < ASN1Kit::VisibleString
+ TAG_NUMBER = 24
+end
+
+class ASN1Kit::ObjectDescriptor < ASN1Kit::Type
+ TAG_NUMBER = 7
+end
diff --git a/test/helper.rb b/test/helper.rb
new file mode 100644
index 0000000..d628122
--- /dev/null
+++ b/test/helper.rb
@@ -0,0 +1,25 @@
+require "asn1kit"
+require "test/unit"
+require "pp"
+
+class ASN1KitTestCase < Test::Unit::TestCase
+ def B(ary)
+ [ary.join].pack("H*")
+ end
+
+ def assert_raise_with_message(exception, pattern, msg = nil, &block)
+ unless pattern.is_a?(String) or pattern.is_a?(Regexp)
+ raise TypeError, "expected message must be a kind of String or Regexp"
+ end
+
+ msg ||= "Exception %s with message %p is raised" % [exception, pattern]
+ raised = assert_raise(exception, msg) { yield }
+
+ if pattern.is_a?(Regexp)
+ assert_match(pattern, raised.message, msg)
+ else
+ assert_equal(pattern, raised.message, msg)
+ end
+ raised
+ end
+end
diff --git a/test/test_builtin_types.rb b/test/test_builtin_types.rb
new file mode 100644
index 0000000..89cdf0b
--- /dev/null
+++ b/test/test_builtin_types.rb
@@ -0,0 +1,132 @@
+# coding: ASCII-8BIT
+require_relative "helper"
+
+class BuiltinTypesTest < ASN1KitTestCase
+ def test_boolean
+ v1 = ASN1Kit::Boolean.new(true)
+ assert_equal true, v1.value
+ assert_equal B(%w{ 01 01 FF }), v1.to_der
+
+ v2 = ASN1Kit::Boolean.new(false)
+ assert_equal false, v2.value
+ assert_equal B(%w{ 01 01 00 }), v2.to_der
+ end
+
+ def test_integer
+ assert_equal B(%w{ 02 01 00 }), ASN1Kit::Integer.new(0).to_der
+ assert_equal B(%w{ 02 01 01 }), ASN1Kit::Integer.new(1).to_der
+ assert_equal B(%w{ 02 02 00 80 }), ASN1Kit::Integer.new(128).to_der
+ assert_equal B(%w{ 02 02 01 00 }), ASN1Kit::Integer.new(256).to_der
+ assert_equal B(%w{ 02 03 0F 42 40 }), ASN1Kit::Integer.new(1_000_000).to_der
+ assert_equal B(%w{ 02 01 FF }), ASN1Kit::Integer.new(-1).to_der
+ assert_equal B(%w{ 02 01 80 }), ASN1Kit::Integer.new(-128).to_der
+ assert_equal B(%w{ 02 02 FF 00 }), ASN1Kit::Integer.new(-256).to_der
+ assert_equal B(%w{ 02 03 F0 BD C0 }), ASN1Kit::Integer.new(-1_000_000).to_der
+
+ v1 = ASN1Kit::Integer.new(123)
+ assert_equal 123, v1.value
+ assert_equal nil, v1.name
+
+ t1 = ASN1Kit::Integer[["a", 123], ["b", 234]]
+ assert_compare t1, :<, ASN1Kit::Integer
+ assert_equal ["a", "b"], t1.named_numbers.sort
+ assert_equal 123, t1.named_number("a")
+ assert_equal 234, t1.named_number("b")
+
+ v2 = t1.new("a")
+ assert_equal 123, v2.value
+ assert_equal "a", v2.name
+
+ v3 = t1.new(234)
+ assert_equal 234, v3.value
+ assert_equal "b", v3.name
+ end
+
+ def test_enumerated
+ t1 = ASN1Kit::Enumerated["a" => 0, "b" => 1_000_000]
+ assert_equal ["a", "b"], t1.enumerations.sort
+ assert_equal 0, t1.value_for("a")
+ assert_equal 1_000_000, t1.value_for("b")
+
+ v1 = t1.new("a")
+ assert_equal "a", v1.name
+ assert_equal 0, v1.value
+ assert_equal B(%w{ 0A 01 00 }), v1.to_der
+
+ v2 = t1.new("b")
+ assert_equal B(%w{ 0A 03 0F 42 40 }), v2.to_der
+ end
+
+ def test_real
+ end
+
+ def test_bitstring
+ v1 = ASN1Kit::BitString.new("\xC0", 2)
+ assert_equal "\xC0", v1.string
+ assert_equal 2, v1.bit_length
+ assert_equal B(%w{ 03 02 06 C0 }), v1.to_der
+
+ v2 = ASN1Kit::BitString.new("\xF0\xFF", 16)
+ assert_equal "\xF0\xFF", v2.string
+ assert_equal 16, v2.bit_length
+ assert_equal B(%w{ 03 03 00 F0 FF }), v2.to_der
+
+ t1 = ASN1Kit::BitString[["a", 0], ["b", 3]]
+ assert_compare t1, :<, ASN1Kit::BitString
+
+ v3 = t1.new("\xC0", 8)
+ assert_equal true, v3.set?("a")
+ assert_equal true, v3.set?(0)
+ assert_equal true, v3.set?(1)
+ assert_equal false, v3.set?(2)
+ assert_equal false, v3.set?("b")
+ assert_equal false, v3.set?(3)
+ assert_equal B(%w{ 03 02 00 C0 }), v3.to_der
+
+ v4 = t1.new_with_names("b")
+ assert_equal 4, v4.bit_length
+ assert_equal B(%w{ 03 02 04 10 }), v4.to_der
+ end
+
+ def test_octetstring
+ v1 = ASN1Kit::OctetString.new("abc")
+ assert_equal "abc", v1.value
+ assert_equal B(%w{ 04 03 61 62 63 }), v1.to_der
+ end
+
+ def test_null
+ v1 = ASN1Kit::Null.new
+ assert_equal B(%w{ 05 00 }), v1.to_der
+ end
+
+ def test_sequence
+ seq = ASN1Kit::Sequence[
+ ASN1Kit::Sequence::ComponentType.new("a", ASN1Kit::Integer),
+ ASN1Kit::Sequence::ComponentType.new("b", ASN1Kit::Boolean),
+ ]
+ obj = seq.new("a" => ASN1Kit::Integer.new(1),
+ "b" => ASN1Kit::Boolean.new(true))
+ assert_equal B(%w{ 30 06 02 01 01 01 01 FF }), obj.to_der
+ end
+
+ def test_object_identifier
+ v1 = ASN1Kit::ObjectIdentifier.new([2, 999, 3])
+ assert_equal "2.999.3", v1.dot_notation
+ assert_equal B(%w{ 06 03 88 37 03 }), v1.to_der
+
+ v2 = ASN1Kit::ObjectIdentifier.from_dot_notation("2.999.3")
+ assert_equal "2.999.3", v2.dot_notation
+ assert_equal B(%w{ 06 03 88 37 03 }), v2.to_der
+ end
+
+ def test_relative_object_identifier
+ v1 = ASN1Kit::RelativeOID.new([8571, 3, 2])
+ assert_equal "8571.3.2", v1.dot_notation
+ assert_equal B(%w{ 0D 04 C2 7B 03 02 }), v1.to_der
+
+ v2 = ASN1Kit::RelativeOID.from_dot_notation("8571.3.2")
+ assert_equal "8571.3.2", v1.dot_notation
+ assert_equal B(%w{ 0D 04 C2 7B 03 02 }), v2.to_der
+ end
+
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..e60deda
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,50 @@
+require_relative "helper"
+
+class HelperTest < ASN1KitTestCase
+ def test_B
+ assert_equal "\x40\x80".b, B(%w{ 40 80 })
+ end
+
+ TestError = Class.new(StandardError)
+ def test_assert_raise_with_message
+ assert_nothing_raised {
+ assert_raise_with_message(TestError, "abc") {
+ raise TestError, "abc"
+ }
+ }
+
+ assert_nothing_raised {
+ assert_raise_with_message(TestError, /c$/) {
+ raise TestError, "abc"
+ }
+ }
+
+ klass = Test::Unit::AssertionFailedError
+ ex = assert_raise {
+ assert_raise_with_message(TestError, "abc") {
+ raise RuntimeError, "abc"
+ }
+ }
+ assert_kind_of klass, ex
+ assert_match (/TestError/), ex.message
+ assert_match (/RuntimeError/), ex.message
+
+ ex = assert_raise {
+ assert_raise_with_message(TestError, "abc") {
+ raise TestError, "xyz"
+ }
+ }
+ assert_kind_of klass, ex
+ assert_match (/abc/), ex.message
+ assert_match (/xyz/), ex.message
+
+ ex = assert_raise {
+ assert_raise_with_message(TestError, /^a/) {
+ raise TestError, "xbc"
+ }
+ }
+ assert_kind_of klass, ex
+ assert_match (/\/\^a\//), ex.message
+ assert_match (/xbc/), ex.message
+ end
+end
diff --git a/test/test_parser.rb b/test/test_parser.rb
new file mode 100644
index 0000000..86b6c6d
--- /dev/null
+++ b/test/test_parser.rb
@@ -0,0 +1,413 @@
+require_relative "helper"
+
+class ParserTest < ASN1KitTestCase
+ def test_comment
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ -- T ::= BOOLEAN
+ END
+ EOS
+ assert_equal nil, mod["T"]
+
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ /*
+ * T1 ::= BOOLEAN
+ * T2 ::= INTEGER
+ */
+ END
+ EOS
+ assert_equal nil, mod["T1"]
+ assert_equal nil, mod["T2"]
+
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ /*
+ * /*
+ * * T ::= BOOLEAN
+ * */
+ */
+ END
+ EOS
+ assert_equal nil, mod["T"]
+ end
+
+ def test_module
+ mod1 = ASN1Kit.parse(<<~EOS)
+ M1 DEFINITIONS ::= BEGIN END
+ EOS
+ assert_equal "M1", mod1.name
+ assert_equal nil, mod1.oid
+
+ mod2 = ASN1Kit.parse(<<~EOS)
+ M2 { 1 2 3 abc(4) } DEFINITIONS ::= BEGIN END
+ EOS
+ assert_equal "M2", mod2.name
+ assert_instance_of ASN1Kit::ObjectIdentifier, mod2.oid
+ assert_equal "1.2.3.4", mod2.oid.dot_notation
+ end
+
+ def test_boolean
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= BOOLEAN
+ v1 BOOLEAN ::= TRUE
+ v2 T ::= FALSE
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::Boolean
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::Boolean, v1
+ assert_equal true, v1.value
+
+ v2 = mod["v2"]
+ assert_instance_of t, v2
+ assert_equal false, v2.value
+ end
+
+ def test_integer
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T1 ::= INTEGER
+ T2 ::= INTEGER { a(123), b(-2) }
+ v1 INTEGER ::= 123
+ v2 INTEGER ::= -123
+ v3 INTEGER { v1(3) } ::= v1
+ v4 T2 ::= v1
+ END
+ EOS
+
+ t1 = mod["T1"]
+ assert_compare t1, :<, ASN1Kit::Integer
+ assert_equal [], t1.named_numbers
+
+ t2 = mod["T2"]
+ assert_compare t2, :<, ASN1Kit::Integer
+ assert_equal ["a", "b"], t2.named_numbers.sort
+ assert_equal 123, t2.named_number("a")
+ assert_equal (-2), t2.named_number("b")
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::Integer, v1
+ assert_equal 123, v1.value
+
+ v2 = mod["v2"]
+ assert_instance_of ASN1Kit::Integer, v2
+ assert_equal (-123), v2.value
+
+ v3 = mod["v3"]
+ assert_compare v3.class, :<, ASN1Kit::Integer
+ assert_equal ["v1"], v3.class.named_numbers
+ assert_equal 3, v3.value
+ assert_equal "v1", v3.name
+
+ v4 = mod["v4"]
+ assert_instance_of t2, v4
+ assert_equal 123, v4.value
+ assert_equal "a", v4.name
+ end
+
+ def test_enumerated
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T1 ::= ENUMERATED { a, b }
+ T2 ::= ENUMERATED { a(2), b(1) }
+ T3 ::= ENUMERATED { a, b(0) }
+ T4 ::= ENUMERATED { a, b, ..., c(5) }
+ T5 ::= ENUMERATED { a, b(3), ..., c }
+ T6 ::= ENUMERATED { a, b(3), ..., c(1) }
+ T7 ::= ENUMERATED { a, b(3), ..., c(1), d }
+ v1 T1 ::= b
+ v2 T1 ::= v1
+ END
+ EOS
+
+ t1 = mod["T1"]
+ assert_compare t1, :<, ASN1Kit::Enumerated
+ assert_equal ["a", "b"], t1.enumerations.sort
+ assert_equal 0, t1.value_for("a")
+ assert_equal 1, t1.value_for("b")
+ assert_equal false, t1.extensible?
+
+ t2 = mod["T2"]
+ assert_equal 2, t2.value_for("a")
+ assert_equal 1, t2.value_for("b")
+
+ t3 = mod["T3"]
+ assert_equal 1, t3.value_for("a")
+ assert_equal 0, t3.value_for("b")
+
+ t4 = mod["T4"]
+ assert_equal 0, t4.value_for("a")
+ assert_equal 1, t4.value_for("b")
+ assert_equal 5, t4.value_for("c")
+ assert_equal true, t4.extensible?
+
+ t5 = mod["T5"]
+ assert_equal 0, t5.value_for("a")
+ assert_equal 3, t5.value_for("b")
+ assert_equal 2, t5.value_for("c")
+
+ t6 = mod["T6"]
+ assert_equal 0, t6.value_for("a")
+ assert_equal 3, t6.value_for("b")
+ assert_equal 1, t6.value_for("c")
+
+ t7 = mod["T7"]
+ assert_equal 0, t7.value_for("a")
+ assert_equal 3, t7.value_for("b")
+ assert_equal 1, t7.value_for("c")
+ assert_equal 2, t7.value_for("d")
+
+ v1 = mod["v1"]
+ assert_instance_of t1, v1
+ assert_equal 1, v1.value
+ assert_equal "b", v1.name
+
+ v2 = mod["v2"]
+ assert_instance_of t1, v2
+ assert_equal 1, v2.value
+
+
+ mod2 = ASN1Kit.parse(<<~EOS)
+ M2 DEFINITIONS EXTENSIBILITY IMPLIED ::= BEGIN
+ T1 ::= ENUMERATED { a, b }
+ T2 ::= ENUMERATED { a, b, ... }
+ END
+ EOS
+
+ assert_equal true, mod2["T1"].extensible?
+ assert_equal true, mod2["T2"].extensible?
+ end
+
+ def test_real
+ # FIXME: NumericRealValue is not tested
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= REAL
+ v1 REAL ::= PLUS-INFINITY
+ v2 REAL ::= MINUS-INFINITY
+ v3 REAL ::= NOT-A-NUMBER
+ v4 T ::= v1
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::Real
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::Real, v1
+ assert_equal true, v1.plus_infinity?
+
+ v2 = mod["v2"]
+ assert_equal true, v2.minus_infinity?
+
+ v3 = mod["v3"]
+ assert_equal true, v3.not_a_number?
+
+ v4 = mod["v4"]
+ assert_instance_of t, v4
+ assert_equal true, v4.plus_infinity?
+ end
+
+ def test_bitstring
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ a INTEGER ::= 1
+ T1 ::= BIT STRING
+ T2 ::= BIT STRING { a(3), b(a) }
+ v1 BIT STRING ::= '011'B
+ v2 T2 ::= '42'H
+ v3 T2 ::= { }
+ v4 T2 ::= { a }
+ END
+ EOS
+
+ t1 = mod["T1"]
+ assert_compare t1, :<, ASN1Kit::BitString
+ assert_equal [], t1.named_bits
+
+ t2 = mod["T2"]
+ assert_compare t2, :<, ASN1Kit::BitString
+ assert_equal ["a", "b"], t2.named_bits.sort
+ assert_equal 3, t2.named_bit("a")
+ assert_equal 1, t2.named_bit("b")
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::BitString, v1
+ assert_equal "\x60".b, v1.string
+ assert_equal 3, v1.bit_length
+
+ v2 = mod["v2"]
+ assert_instance_of t2, v2
+ assert_equal "\x42".b, v2.string
+ assert_equal 8, v2.bit_length
+
+ v3 = mod["v3"]
+ assert_instance_of t2, v3
+ assert_equal "".b, v3.string
+ assert_equal 0, v3.bit_length
+
+ v4 = mod["v4"]
+ assert_instance_of t2, v4
+ assert_equal "\x10".b, v4.string
+ assert_equal 4, v4.bit_length
+ end
+
+ def test_octetstring
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= OCTET STRING
+ v1 OCTET STRING ::= '01100000 00000001'B
+ v2 T ::= '4'H
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::OctetString
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::OctetString, v1
+ assert_equal "\x60\x01".b, v1.value
+
+ v2 = mod["v2"]
+ assert_instance_of t, v2
+ assert_equal "\x40".b, v2.value
+ end
+
+ def test_null
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= NULL
+ v1 NULL ::= NULL
+ v2 T ::= NULL
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::Null
+
+ v1 = mod["v1"]
+ assert_instance_of ASN1Kit::Null, v1
+
+ v2 = mod["v2"]
+ assert_instance_of t, v2
+ end
+
+ def test_sequence
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T1 ::= SEQUENCE { a NULL, b INTEGER }
+ T2 ::= SEQUENCE { a NULL, b INTEGER OPTIONAL }
+ v1 T1 ::= { b 1, a NULL }
+ END
+ EOS
+
+ t1 = mod["T1"]
+ assert_compare t1, :<, ASN1Kit::Sequence
+ assert_equal ["a", "b"], t1.component_types.sort
+ assert_same ASN1Kit::Null, t1.component_type("a")
+ assert_same ASN1Kit::Integer, t1.component_type("b")
+
+ t2 = mod["T2"]
+ assert_compare t2, :<, ASN1Kit::Sequence
+ assert_equal ["a", "b"], t2.component_types.sort
+ assert_same ASN1Kit::Null, t2.component_type("a")
+ assert_same ASN1Kit::Integer, t2.component_type("b")
+
+ v1 = mod["v1"]
+ assert_instance_of t1, v1
+ assert_instance_of ASN1Kit::Null, v1["a"]
+ assert_instance_of ASN1Kit::Integer, v1["b"]
+ assert_equal 1, v1["b"].value
+ end
+
+ def test_object_identifier
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= OBJECT IDENTIFIER
+ v1 T ::= { 0 1 2 }
+ v2 OBJECT IDENTIFIER ::= v1
+ v3 OBJECT IDENTIFIER ::= { v1 3 }
+ v4 OBJECT IDENTIFIER ::= { iso(1) 1 }
+ v5 OBJECT IDENTIFIER ::= { iso 1 }
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::ObjectIdentifier
+
+ v1 = mod["v1"]
+ assert_instance_of t, v1
+ assert_equal "0.1.2", v1.dot_notation
+
+ v2 = mod["v2"]
+ assert_instance_of ASN1Kit::ObjectIdentifier, v2
+ assert_equal "0.1.2", v2.dot_notation
+
+ v3 = mod["v3"]
+ assert_equal "0.1.2.3", v3.dot_notation
+
+ v4 = mod["v4"]
+ assert_equal "1.1", v4.dot_notation
+
+ v5 = mod["v5"]
+ assert_equal "1.1", v5.dot_notation
+ end
+
+ def test_relative_object_identifier
+ mod = ASN1Kit.parse(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ T ::= RELATIVE-OID
+ v1 T ::= { 1 2 3 }
+ v2 RELATIVE-OID ::= v1
+ v3 RELATIVE-OID ::= { 0 v1 4 }
+ v4 RELATIVE-OID ::= { iso(1) }
+ v5 OBJECT IDENTIFIER ::= { 0 v1 4 }
+ END
+ EOS
+
+ t = mod["T"]
+ assert_compare t, :<, ASN1Kit::RelativeOID
+
+ v1 = mod["v1"]
+ assert_instance_of t, v1
+ assert_equal "1.2.3", v1.dot_notation
+
+ v2 = mod["v2"]
+ assert_instance_of ASN1Kit::RelativeOID, v2
+ assert_equal "1.2.3", v2.dot_notation
+
+ v3 = mod["v3"]
+ assert_equal "0.1.2.3.4", v3.dot_notation
+
+ v4 = mod["v4"]
+ assert_equal "1", v4.dot_notation
+
+ v5 = mod["v5"]
+ assert_equal "0.1.2.3.4", v5.dot_notation
+
+ assert_parse_error(<<~EOS)
+ M DEFINITIONS ::= BEGIN
+ v RELATIVE-OID ::= { iso }
+ END
+ EOS
+ end
+
+ private
+
+ def assert_parse_error(source, pattern = nil)
+ if pattern
+ assert_raise_with_message(ASN1Kit::ParseError, pattern) {
+ ASN1Kit.parse(source)
+ }
+ else
+ assert_raise(ASN1Kit::ParseError) {
+ ASN1Kit.parse(source)
+ }
+ end
+ end
+end