Module: Net::BER::BERParser

Included in:
IO, String, StringIO
Defined in:
lib/net/ber.rb

Overview

This module is for mixing into IO and IO-like objects.

Constant Summary collapse

TagClasses =

The order of these follows the class-codes in BER. Maybe this should have been a hash.

[:universal, :application, :context_specific, :private]
BuiltinSyntax =
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
    }
  }
)

Instance Method Summary collapse

Instance Method Details

#read_ber(syntax = nil) ⇒ Object

read_ber 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. – BEWARE, this violates DRY and is largely equal in functionality to read_ber_from_string. Eventually that method may subsume the functionality of this one.



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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/net/ber.rb', line 153

def read_ber syntax=nil
  # don't bother with this line, since IO#getc by definition returns nil on eof.
  #return nil if eof?

  id = BER.ord(getc) or return nil  # don't trash this value, we'll use it later
  #tag = id & 31
  #tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
  #tagclass = TagClasses[ id >> 6 ]
  #encoding = (id & 0x20 != 0) ? :constructed : :primitive

  n = BER.ord(getc)
  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
      [0, f]
  elsif f < 80
      [1, f-40]
  else
      [2, f-80] # f-80 can easily be > 80. What a weird optimization.
  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: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
    raise BerError.new( "unsupported object type: id=#{id}" )
  end

  # Add the identifier bits into the object if it's a String or an Array.
  # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
  # Replaced this mechanism with subclasses because the instance_eval profiled too hot.
  #obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
  #obj.ber_identifier = id if obj.respond_to?(:ber_identifier)
  obj

end

#read_ber_from_string(str, syntax = nil) ⇒ Object

– Violates DRY! This replicates the functionality of #read_ber. Eventually this method may replace that one. This version of #read_ber behaves properly in the face of incomplete data packets. If a full BER object is detected, we return an array containing the detected object and the number of bytes consumed from the string. If we don’t detect a complete packet, return nil.

Observe that weirdly we recursively call the original #read_ber in here. That needs to be fixed if we ever obsolete the original method in favor of this one.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/net/ber.rb', line 249

def read_ber_from_string str, syntax=nil
  id = str[0] || return
  id = id.ord if RUBY_VERSION.to_f >= 1.9

  n = str[1] || return
  n = n.ord if RUBY_VERSION.to_f >= 1.9
  
  n_consumed = 2
  lengthlength,contentlength = if n <= 127
      [1,n]
  else
    n1 = n & 127
    return nil unless str.length >= (n_consumed + n1)
    j = 0
    n1.times do
      j = (j << 8) + str[n_consumed]
      n_consumed += 1
    end
    [1 + (n1), j]
  end

  return nil unless str.length >= (n_consumed + contentlength)
  newobj = str[n_consumed...(n_consumed + contentlength)]
  n_consumed += contentlength

  objtype = (syntax && syntax[id]) || BuiltinSyntax[id]

  # == is expensive so sort this if/else so the common cases are at the top.
  obj = if objtype == :array
    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!
    # Also, we can use the standard read_ber method because
    # we know for sure we have enough data. (Although this
    # might be faster than the standard method.)
    while (e = sio.read_ber(syntax)) != nil
      seq << e
    end
    seq
  elsif objtype == :string
    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
      [0,f]
    elsif f < 80
      [1, f-40]
    else
      [2, f-80] # f-80 can easily be > 80. What a weird optimization.
    end
    oid.unshift g.last
    oid.unshift g.first
    oid
  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

  [obj, n_consumed]
end