lemontree55/rasn1

Types: drop name argument

Closed this issue · 5 comments

First of all, cool that you're doing this gem! I made this gem called netsnmp which is dependent on the openssl gem ASN1 parser, and due to the many inconsistencies of jruby's equivalent parser, can't be really considered platform-independent. Having a pure-ruby parser can definitely help in the jruby space.

I started creating a wrapper around this gem, and I've found a few pitfalls regarding compatibility. The first one would be:

All types have a name as first argument. According to specs, the way to create an Integer is by doing Integer.new(:int). My question is, when is this argument not :int?. I think that this could naturally be switched to Integer.new. The same for all the other primitive types (at least).

The other thing is whether it is reasonable to expect API parity with openssl's ASN1 lib. I see that all Types respond to #to_der, which is nice.

Question is whether the types name match, the parsing happens in the same way, and the types have the same base API. For instance, for instantiation base types:

OpenSSL::ASN1::OctetString.new(@value)
OpenSSL::ASN1::Integer.new(@value)
OpenSSL::ASN1::Boolean.new(@value)
OpenSSL::ASN1::Null.new(nil)

for application types:

# equivalent to the base type?
OpenSSL::ASN1::ASN1Data.new(@value, 0, :APPLICATION)
# this is how you'd build an integer of value 2
OpenSSL::ASN1::ASN1Data.new(2, 2, :UNIVERSAL)

for decoding:

OpenSSL::ASN1.decode(der)

for attributes:

i = OpenSSL::ASN1::Integer.new(2)
i.value #=> 2
i.tag #=> 2
i.tagging #=> nil
i.tag_class #=> :UNIVERSAL
i.to_der #=> "\x02\x01\x02"

That's at least for me use-cases. Some of the things I can get around to with monkey-patching, except for the first one maybe.

@HoneyryderChuck Thank you for your interest.

Actually, first argument for type creation is the value name. It is used in #inspect, in model to access values, and also in error messages when decoding ASN.1 to better understand where is the error.

Example:

seq = RASN1::Types::Sequence.new(:record)
seq.value = [
             RASN1::Types::Integer.new(:id),
             RASN1::Types::Integer.new(:room, explicit: 0, optional: true),
             RASN1::Types::Integer.new(:house, implicit: 1, default: 0)
]

seq.parse!("\x30\x00") # => RASN1::ASN1Error: Expected UNIVERSAL PRIMITIVE INTEGER but get no tag for id

About your second question: parsing does not happen the same way: OpenSSL decodes ASN.1 on the fly, whereas RASN1 decodes from a "grammary" defined by RASN1 base objects or models. By example, RASN1 will complain if first element of a sequence is an integer but a boolean was expected.

About application types:

# Create an implicit application integer
int = RASN1::Types::Integer.new(:name, implicit: 0, class: :application, value: 2)
int.tag             # => 64 (0x40) ie APPLICATION [0]

And attributes are:

i = RASN1::Types::Integer.new(:name, value: 2)
i.value      #=> 2
i.tag        #=> 2
i.explicit?  #=> false
i.implicit?  #=> false
i.asn1_class #=> :universal
i.to_der     #=> "\x02\x01\x02"

But what is your question? Sorry, that's not clear to me 😢

@sdaubert thank you for your interest. Indeed, what I was suggesting was more parity (as sanely possible) with the "openssl" API. The "why?" is clear: proper ASN1 library for jruby and (hopefully) less work in multi-environment support.

Actually, first argument for type creation is the value name.

I didn't understand this part. What's the use of it, beyond ease of debugging? The way I see it, one could make this "name" argument optional, and fallback to the #object_id if not set. Something more like:

i = RASN1::Types::Integer.new(2, name: :id)
i.name #=> :id
i = RASN1::Types::Integer.new(2)
i.name #=> "0x129a123932..."

As I don't see another point of making it a mandatory argument.

About your second question: parsing does not happen the same way:

Any specific reason not to support on-the-fly parsing like openssl? I'm not saying that defining the grammar upfront doesn't have its use, but providing a simple on-the-fly whenever not defined could be a nice to have (if there's no burden in it, of course).

Again, my main motivation for this is to have a sane ASN.1 library to enable jruby support for my library (because jruby-openssl's ASN.1 layer is buggy and might remain so for the long run), and that I can decently wrap both libraries with the same top-level API, if possible. And adoption of this library could be made easier if the top-level API could be (as much as possible) compatible with openssl's. But I will respect if this is not in your interest.

As I don't see another point of making it a mandatory argument.

Yes, I think it may be an optional argument. I did this because OpenSSL error messages are too cryptic for me.
I will have a look on this topic.

Any specific reason not to support on-the-fly parsing like openssl?

Yes, my primary goal was decoding ASN.1 and ensuring grammar is correct. And having a grammar is also very convenient when encoding.
I don't know if this may be easily added. I will have to look at.

Thank you for taking this into consideration.

I don't know if this may be easily added.

I think that parsing is as of now done relative to one Asn1 uninitialised node. I'd suggest to have a top-level parsing method, like Rasn.parse(der), which parses on the fly, and have an optional parameter to deactivate this, like Rasn.parse(der, type: Rasn::Sequence), and this could be then used in the parse method from types.

Closed: mandatory name argument is removed.

For second topic, please open a new issue.