File: ber.rb

package info (click to toggle)
ruby-snmp 1.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,860 kB
  • sloc: ruby: 1,791; makefile: 9
file content (363 lines) | stat: -rw-r--r-- 11,094 bytes parent folder | download | duplicates (2)
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
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
238
239
240
241
242
243
244
245
246
247
248
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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# encoding: ascii-8bit
# frozen_string_literal: true
#
# Copyright (c) 2004-2014 David R. Halliday
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

#
# This module implements methods for encoding and decoding SNMP packets
# using the ASN.1 BER (Basic Encoding Rules).
#
module SNMP
  module BER #:nodoc:all

    # SNMP version codes
    SNMP_V1  = 0
    SNMP_V2C = 1
    SNMP_V3  = 3  # not supported

    # SNMP context-specific data types
    # See RFC 1157 for SNMPv1
    # See RFC 1905 for SNMPv2c
    GetRequest_PDU_TAG = 0xa0
    GetNextRequest_PDU_TAG = 0xa1
    Response_PDU_TAG = 0xa2
    SetRequest_PDU_TAG = 0xa3
    SNMPv1_Trap_PDU_TAG = 0xa4    # Note: valid for SNMPv1 only
    GetBulkRequest_PDU_TAG = 0xa5
    InformRequest_PDU_TAG = 0xa6
    SNMPv2_Trap_PDU_TAG = 0xa7
    Report_PDU_TAG = 0xa8  # Note: Usage not defined - not supported

    # Primitive ASN.1 data types
    INTEGER_TAG = 0x02
    OCTET_STRING_TAG = 0x04
    NULL_TAG = 0x05
    OBJECT_IDENTIFIER_TAG = 0x06

    # Constructed ASN.1 data type
    SEQUENCE_TAG = 0x30

    # SNMP application data types
    # See RFC 1155 for SNMPv1
    # See RFC 1902 for SNMPv2c
    IpAddress_TAG = 0x40
    Counter32_TAG = 0x41   # Counter in SNMPv1
    Gauge32_TAG = 0x42     # Gauge in SNMPv1
    Unsigned32_TAG = 0x42  # Note: same as Gauge32
    TimeTicks_TAG = 0x43
    Opaque_TAG = 0x44
    Counter64_TAG = 0x46

    # VarBind response exceptions
    NoSuchObject_TAG = 0x80
    NoSuchInstance_TAG = 0x81
    EndOfMibView_TAG = 0x82

    # Exceptions thrown in this module
    class OutOfData < RuntimeError; end
    class InvalidLength < RuntimeError; end
    class InvalidTag < RuntimeError; end
    class InvalidObjectId < RuntimeError; end
    class InvalidLength < RuntimeError; end

    module Decode

      def assert_no_remainder(remainder)
        raise ParseError, remainder.inspect if remainder != ""
      end

      #
      # Decode tag-length-value data.  The data is assumed to be a string of
      # bytes in network byte order.  This format is returned by Socket#recv.
      #
      # Returns a tuple containing the tag, the value, and any remaining
      # unprocessed data.
      #
      # The data is not interpretted by this method.  Use one of the other
      # decoding methods to interpret the data.
      #
      # Note that ASN.1 supports an indefinite length format where the end of
      # content is marked by a pair of 0 octets.  SNMP does not support this
      # format, so only the two definite forms are implemented (single byte and
      # multi-byte).
      #
      def decode_tlv(data)
        raise OutOfData if (data.length == 2 && data[1].ord != 0) || data.length < 2
        tag = data[0].ord
        length = data[1].ord
        if length < 0x80
          value = data[2, length]
          remainder = data[length+2..-1]
        else
          # ASN.1 says this octet can't be 0xff
          raise InvalidLength, length.to_s if length == 0xff
          num_octets = length & 0x7f
          length = build_integer(data, 2, num_octets)
          value = data[num_octets+2, length]
          remainder = data[num_octets+2+length..-1]
        end
        return tag, value, remainder
      end

      #
      # Decode TLV data for an ASN.1 integer.
      #
      # Throws an InvalidTag exception if the tag is incorrect.
      #
      # Returns a tuple containing an integer and any remaining unprocessed data.
      #
      def decode_integer(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != INTEGER_TAG
        return decode_integer_value(value), remainder
      end

      def decode_timeticks(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != TimeTicks_TAG
        return decode_uinteger_value(value), remainder
      end

      def decode_integer_value(value)
        result = build_integer(value, 0, value.length)
        if value[0].ord[7] == 1
          result -= (1 << (8 * value.length))
        end
        result
      end

      ##
      # Decode an integer, ignoring the sign bit.  Some agents insist on
      # encoding 32 bit unsigned integers with four bytes even though it
      # should be 5 bytes (at least the way I read it).
      #
      def decode_uinteger_value(value)
        build_integer(value, 0, value.length)
      end

      def build_integer(data, start, num_octets)
        number = 0
        num_octets.times { |i| number = number<<8 | data[start+i].ord }
        return number
      end

      #
      # Decode TLV data for an ASN.1 octet string.
      #
      # Throws an InvalidTag exception if the tag is incorrect.
      #
      # Returns a tuple containing a string and any remaining unprocessed data.
      #
      def decode_octet_string(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != OCTET_STRING_TAG
        return value, remainder
      end

      def decode_ip_address(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != IpAddress_TAG
        raise InvalidLength, tag.to_s if value.length != 4
        return value, remainder
      end

      #
      # Decode TLV data for an ASN.1 sequence.
      #
      # Throws an InvalidTag exception if the tag is incorrect.
      #
      # Returns a tuple containing the sequence data and any remaining
      # unprocessed data that follows the sequence.
      #
      def decode_sequence(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != SEQUENCE_TAG
        return value, remainder
      end

      #
      # Unwrap TLV data for an ASN.1 object identifier.  This method extracts
      # the OID value as a character string but does not decode it further.
      #
      # Throws an InvalidTag exception if the tag is incorrect.
      #
      # Returns a tuple containing the object identifier (OID) and any
      # remaining unprocessed data.  The OID is represented as an array
      # of integers.
      #
      def decode_object_id(data)
        tag, value, remainder = decode_tlv(data)
        raise InvalidTag, tag.to_s if tag != OBJECT_IDENTIFIER_TAG
        return decode_object_id_value(value), remainder
      end

      def decode_object_id_value(value)
        if value.length == 0
          object_id = []
        else
          value0 = value[0].ord
          if value0 == 0x2b
            object_id = [1,3]
          else
            second = value0 % 40
            first = (value0 - second) / 40
            raise InvalidObjectId, value.to_s if first > 2
            object_id = [first, second]
          end
          n = 0
          for i in 1...value.length
            n = (n<<7) + (value[i].ord & 0x7f)
            if value[i].ord < 0x80
              object_id << n
              n = 0
            end
          end
        end
        return object_id
      end

    end

    module Encode

      #
      # Encode the length field for TLV data.  Returns the length octets
      # as a string.
      #
      def encode_length(length)
        raise InvalidLength, length.to_s if length < 0
        if length < 0x80
          length.chr
        else
          data = integer_to_octets(length)
          (data.size | 0x80).chr << data
        end
      end

      #
      # Encode integer
      #
      def encode_integer(value)
        encode_tagged_integer(INTEGER_TAG, value)
      end

      def encode_tagged_integer(tag, value)
        if value > 0 && value < 0x80
          data = value.chr
        else
          data = integer_to_octets(value)
          if value > 0 && data[0].ord > 0x7f
            data = "\000".dup << data
          elsif value < 0 && data[0].ord < 0x80
            data = "\377".dup << data
          end
        end
        encode_tlv(tag, data)
      end

      #
      # Helper method for encoding integer-like things.
      #
      def integer_to_octets(i)
        if i >= 0
          done = 0
        else
          done = -1
        end
        octets = ""
        begin
          octets = (i & 0xff).chr << octets
          i = i >> 8
        end until i == done
        octets
      end

      def encode_null
        NULL_TAG.chr << "\000"
      end

      #
      # Encode an exception.  The encoding is simply the exception tag with
      # no data, similar to NULL.
      #
      def encode_exception(tag)
        tag.chr << "\000"
      end

      #
      # Wraps value in a tag and length.  This method expects an
      # integer tag and a string value.
      #
      def encode_tlv(tag, value)
        data = tag.chr << encode_length(value.length)
        data = data << value if value.length > 0
        data
      end

      #
      # Wrap string in a octet string tag and length.
      #
      def encode_octet_string(value)
        encode_tlv(OCTET_STRING_TAG, value)
      end

      #
      # Wrap value in a sequence tag and length.
      #
      def encode_sequence(value)
        encode_tlv(SEQUENCE_TAG, value)
      end

      #
      # Encode an object id.  The input is assumed to be an array of integers
      # representing the object id.
      #
      def encode_object_id(value)
        raise InvalidObjectId, value.to_s if value.length < 1
        raise InvalidObjectId, value.to_s if value[0] > 2
        data = "".dup
        if (value.length > 1)
          raise InvalidObjectId if value[0] < 2 && value[1] > 40
          data << (40 * value[0] + value[1]).chr
          for i in 2...value.length
            if value[i] < 0x80
              data << value[i].chr
            else
              octets = ""
              n = value[i]
              begin
                octets = (n & 0x7f | 0x80).chr << octets
                n = n >> 7
              end until n == 0
              octets[-1] = (octets[-1].ord & 0x7f).chr
              data << octets
            end
          end
        elsif (value.length == 1)
          data << (40 * value[0]).chr
        end
        encode_tlv(OBJECT_IDENTIFIER_TAG, data)
      end

    end
  end
end