lib/net/ber/ber_parser.rb in net-ldap-0.1.1 vs lib/net/ber/ber_parser.rb in net-ldap-0.2
- old
+ new
@@ -1,112 +1,168 @@
+# -*- ruby encoding: utf-8 -*-
require 'stringio'
-module Net
- module BER
- module BERParser
- VERSION = '0.1.0'
+# 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 }
- # The order of these follows the class-codes in BER.
- # Maybe this should have been a hash.
- TagClasses = [:universal, :application, :context_specific, :private]
+ primitive = { 10 => :integer }
+ context = { :primitive => primitive }
- BuiltinSyntax = Net::BER.compile_syntax( {
- :universal => {
- :primitive => {
- 1 => :boolean,
- 2 => :integer,
- 4 => :string,
- 5 => :null,
- 6 => :oid,
- 10 => :integer,
- 13 => :string # (relative OID)
- },
- :constructed => {
- 16 => :array,
- 17 => :array
- }
- },
- :context_specific => {
- :primitive => {
- 10 => :integer
- }
- }
- })
+ # The universal, built-in ASN.1 BER syntax.
+ BuiltinSyntax = Net::BER.compile_syntax(:universal => universal,
+ :context_specific => context)
- 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
+ ##
+ # 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]
- n = getbyte
- lengthlength,contentlength = if n <= 127
- [1,n]
- else
- # Replaced the inject because it profiles hot.
- # j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
- j = 0
- read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
- [1 + (n & 127), j]
- end
-
- newobj = read contentlength
-
- # This exceptionally clever and clear bit of code is verrrry slow.
- objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
-
- # == is expensive so sort this if/else so the common cases are at the top.
- obj = if objtype == :string
- #(newobj || "").dup
- s = BerIdentifiedString.new( newobj || "" )
- s.ber_identifier = id
- s
- elsif objtype == :integer
- j = 0
- newobj.each_byte {|b| j = (j << 8) + b}
- j
- elsif objtype == :oid
- # cf X.690 pgh 8.19 for an explanation of this algorithm.
- # 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 = newobj.unpack("w*")
- f = oid.shift
- g = if f < 40
+ # == 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]
+ [1, f - 40]
else
- [2, f-80] # f-80 can easily be > 80. What a weird optimization.
+ # f - 80 can easily be > 80. What a weird optimization.
+ [2, f - 80]
end
- oid.unshift g.last
- oid.unshift g.first
- oid
- elsif objtype == :array
- #seq = []
- seq = BerIdentifiedArray.new
- seq.ber_identifier = id
- sio = StringIO.new( newobj || "" )
- # 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 objtype == :boolean
- newobj != "\000"
- elsif objtype == :null
- n = BerIdentifiedNull.new
- n.ber_identifier = id
- n
- else
- raise BerError.new( "unsupported object type: id=#{id}" )
- 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
- obj
+ ##
+ # 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).
+ #
+ # <table>
+ # <tr>
+ # <th>Range</th>
+ # <th>Length</th>
+ # </tr>
+ # <tr>
+ # <th>0x00 -- 0x7f<br />0b00000000 -- 0b01111111</th>
+ # <td>0 - 127 bytes</td>
+ # </tr>
+ # <tr>
+ # <th>0x80<br />0b10000000</th>
+ # <td>Indeterminate (end-of-content marker required)</td>
+ # </tr>
+ # <tr>
+ # <th>0x81 -- 0xfe<br />0b10000001 -- 0b11111110</th>
+ # <td>1 - 126 bytes of length as an integer value</td>
+ # </tr>
+ # <tr>
+ # <th>0xff<br />0b11111111</th>
+ # <td>Illegal (reserved for future expansion)</td>
+ # </tr>
+ # </table>
+ #
+ #--
+ # 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