Reformat and documentation update of Net::BER and Net::BER::BERParser.
This commit is contained in:
parent
28d58cd581
commit
1d3817e007
3 changed files with 472 additions and 175 deletions
|
@ -1,112 +1,168 @@
|
|||
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, "unsupported object type: id=0x#{id.to_s(16)}"
|
||||
end
|
||||
|
||||
obj
|
||||
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).
|
||||
#
|
||||
# <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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue