# -*- ruby encoding: utf-8 -*- require 'stringio' # Implements Basic Encoding Rules parsing to be mixed into types as needed. module Net::BER::BERParser primitive = { 1 => :boolean, 2 => :integer, 4 => :string, 5 => :null, 6 => :oid, 10 => :integer, 13 => :string # (relative OID) } constructed = { 16 => :array, 17 => :array } universal = { :primitive => primitive, :constructed => constructed } primitive = { 10 => :integer } context = { :primitive => primitive } # The universal, built-in ASN.1 BER syntax. BuiltinSyntax = Net::BER.compile_syntax(:universal => universal, :context_specific => context) ## # This is an extract of our BER object parsing to simplify our # understanding of how we parse basic BER object types. def parse_ber_object(syntax, id, data) # Find the object type from either the provided syntax lookup table or # the built-in syntax lookup table. # # This exceptionally clever bit of code is verrrry slow. object_type = (syntax && syntax[id]) || BuiltinSyntax[id] # == is expensive so sort this so the common cases are at the top. if object_type == :string s = Net::BER::BerIdentifiedString.new(data || "") s.ber_identifier = id s elsif object_type == :integer j = 0 data.each_byte { |b| j = (j << 8) + b } j elsif object_type == :oid # See X.690 pgh 8.19 for an explanation of this algorithm. # This is potentially not good enough. We may need a # BerIdentifiedOid as a subclass of BerIdentifiedArray, to # get the ber identifier and also a to_s method that produces # the familiar dotted notation. oid = data.unpack("w*") f = oid.shift g = if f < 40 [0, f] elsif f < 80 [1, f - 40] else # f - 80 can easily be > 80. What a weird optimization. [2, f - 80] end oid.unshift g.last oid.unshift g.first # Net::BER::BerIdentifiedOid.new(oid) oid elsif object_type == :array seq = Net::BER::BerIdentifiedArray.new seq.ber_identifier = id sio = StringIO.new(data || "") # Interpret the subobject, but note how the loop is built: # nil ends the loop, but false (a valid BER value) does not! while (e = sio.read_ber(syntax)) != nil seq << e end seq elsif object_type == :boolean data != "\000" elsif object_type == :null n = Net::BER::BerIdentifiedNull.new n.ber_identifier = id n else raise Net::BER::BerError, "Unsupported object type: id=#{id}" end end private :parse_ber_object ## # This is an extract of how our BER object length parsing is done to # simplify the primary call. This is defined in X.690 section 8.1.3. # # The BER length will either be a single byte or up to 126 bytes in # length. There is a special case of a BER length indicating that the # content-length is undefined and will be identified by the presence of # two null values (0x00 0x00). # # # # # # # # # # # # # # # # # # # # # # #
RangeLength
0x00 -- 0x7f
0b00000000 -- 0b01111111
0 - 127 bytes
0x80
0b10000000
Indeterminate (end-of-content marker required)
0x81 -- 0xfe
0b10000001 -- 0b11111110
1 - 126 bytes of length as an integer value
0xff
0b11111111
Illegal (reserved for future expansion)
# #-- # This has been modified from the version that was previously inside # #read_ber to handle both the indeterminate terminator case and the # invalid BER length case. Because the "lengthlength" value was not used # inside of #read_ber, we no longer return it. def read_ber_length n = getbyte if n <= 0x7f n elsif n == 0x80 -1 elsif n == 0xff raise Net::BER::BerError, "Invalid BER length 0xFF detected." else v = 0 read(n & 0x7f).each_byte do |b| v = (v << 8) + b end v end end private :read_ber_length ## # Reads a BER object from the including object. Requires that #getbyte is # implemented on the including object and that it returns a Fixnum value. # Also requires #read(bytes) to work. # # This does not work with non-blocking I/O. def read_ber(syntax = nil) # TODO: clean this up so it works properly with partial packets coming # from streams that don't block when we ask for more data (like # StringIOs). At it is, this can throw TypeErrors and other nasties. id = getbyte or return nil # don't trash this value, we'll use it later content_length = read_ber_length if -1 == content_length raise Net::BER::BerError, "Indeterminite BER content length not implemented." else data = read(content_length) end parse_ber_object(syntax, id, data) end end