File: ber_parser.rb

package info (click to toggle)
ruby-net-ldap 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 640 kB
  • sloc: ruby: 4,583; sh: 53; makefile: 4
file content (182 lines) | stat: -rw-r--r-- 5,443 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# -*- 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
      neg = !(data.unpack("C").first & 0x80).zero?
      int = 0

      data.each_byte do |b|
        int = (int << 8) + (neg ? 255 - b : b)
      end

      if neg
        (int + 1) * -1
      else
        int
      end
    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).
  #
  # <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.
  #
  # Yields the object type `id` and the data `content_length` if a block is
  # given. This is namely to support instrumentation.
  #
  # 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

    yield id, content_length if block_given?

    if -1 == content_length
      raise Net::BER::BerError,
            "Indeterminite BER content length not implemented."
    end
    data = read(content_length)

    parse_ber_object(syntax, id, data)
  end
end