From bc98bd8adb81c9b98ddb81fc2b7e96ceb83343ad Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 28 Feb 2017 02:16:33 +0900 Subject: wip --- .gitignore | 3 + .travis.yml | 9 + README.md | 33 ++ Rakefile | 24 + asn1kit.gemspec | 16 + lib/asn1kit.rb | 25 + lib/asn1kit/ber.rb | 6 + lib/asn1kit/berstring.rb | 70 +++ lib/asn1kit/compile.rb | 707 +++++++++++++++++++++++ lib/asn1kit/module.rb | 25 + lib/asn1kit/parse.ry | 861 ++++++++++++++++++++++++++++ lib/asn1kit/types.rb | 183 ++++++ lib/asn1kit/types/bit_string.rb | 112 ++++ lib/asn1kit/types/boolean.rb | 41 ++ lib/asn1kit/types/character_string_types.rb | 61 ++ lib/asn1kit/types/choice.rb | 18 + lib/asn1kit/types/enumerated.rb | 160 ++++++ lib/asn1kit/types/integer.rb | 87 +++ lib/asn1kit/types/null.rb | 18 + lib/asn1kit/types/object_identifier.rb | 76 +++ lib/asn1kit/types/octet_string.rb | 21 + lib/asn1kit/types/real.rb | 46 ++ lib/asn1kit/types/relative_oid.rb | 67 +++ lib/asn1kit/types/sequence.rb | 121 ++++ lib/asn1kit/types/sequence_of.rb | 37 ++ lib/asn1kit/types/set.rb | 28 + lib/asn1kit/types/set_of.rb | 37 ++ lib/asn1kit/types/useful_types.rb | 36 ++ test/helper.rb | 25 + test/test_builtin_types.rb | 132 +++++ test/test_helper.rb | 50 ++ test/test_parser.rb | 413 +++++++++++++ 32 files changed, 3548 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 Rakefile create mode 100644 asn1kit.gemspec create mode 100644 lib/asn1kit.rb create mode 100644 lib/asn1kit/ber.rb create mode 100644 lib/asn1kit/berstring.rb create mode 100644 lib/asn1kit/compile.rb create mode 100644 lib/asn1kit/module.rb create mode 100644 lib/asn1kit/parse.ry create mode 100644 lib/asn1kit/types.rb create mode 100644 lib/asn1kit/types/bit_string.rb create mode 100644 lib/asn1kit/types/boolean.rb create mode 100644 lib/asn1kit/types/character_string_types.rb create mode 100644 lib/asn1kit/types/choice.rb create mode 100644 lib/asn1kit/types/enumerated.rb create mode 100644 lib/asn1kit/types/integer.rb create mode 100644 lib/asn1kit/types/null.rb create mode 100644 lib/asn1kit/types/object_identifier.rb create mode 100644 lib/asn1kit/types/octet_string.rb create mode 100644 lib/asn1kit/types/real.rb create mode 100644 lib/asn1kit/types/relative_oid.rb create mode 100644 lib/asn1kit/types/sequence.rb create mode 100644 lib/asn1kit/types/sequence_of.rb create mode 100644 lib/asn1kit/types/set.rb create mode 100644 lib/asn1kit/types/set_of.rb create mode 100644 lib/asn1kit/types/useful_types.rb create mode 100644 test/helper.rb create mode 100644 test/test_builtin_types.rb create mode 100644 test/test_helper.rb create mode 100644 test/test_parser.rb 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 -- cgit v1.2.3