diameter 0.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 155d66671781834056b72ee9fca500a317dd2580
4
+ data.tar.gz: d2233fc23a94125190656b1f13b73d193e178082
5
+ SHA512:
6
+ metadata.gz: 0193a6fee32495c1566fe839cc3cdfb44f65ce7de5730cc4753b344c8809ea7afe2ce72b385d30f098544586e79c7543005c0198eb453c67cbf19cd3c7199428
7
+ data.tar.gz: cbed37f246b89602d463fc4cd5ce04d84a464c79e0ea3a054e47b78e37a2b54b3b3516c8c650194ebafd4cf78683b6f61b4b34f70e4db1ef8a68d924aab5c9a3
@@ -0,0 +1,391 @@
1
+ require 'diameter/avp_parser'
2
+ require 'diameter/u24'
3
+ require 'diameter/constants'
4
+ require 'ipaddr'
5
+
6
+ module Diameter
7
+ module Internals
8
+ # Maps AVP names to their on-the-wire values and data definitions.
9
+ class AVPNames
10
+ include Constants
11
+
12
+ @custom_avps = {}
13
+
14
+ # Converts an AVP name into its code number, data type, and (if
15
+ # applicable) vendor ID.
16
+ #
17
+ # @param [String] name The AVP name
18
+ # @return [Array(Fixnum, AVPType)] if this is not vendor-specific
19
+ # @return [Array(Fixnum, AVPType, Vendor)] if this is vendor-specific
20
+ def self.get(name)
21
+ code, type, vendor = @custom_avps.merge(AVAILABLE_AVPS)[name]
22
+ vendor ||= 0
23
+ fail "AVP name #{name} not recognised" unless code
24
+ [code, type, vendor]
25
+ end
26
+
27
+ # @see {AVP.define}
28
+ def self.add(name, code, type, vendor=nil)
29
+ @custom_avps[name] = vendor.nil? ? [code, type] : [code, type, vendor]
30
+ end
31
+ end
32
+ end
33
+
34
+ # The AVP class is a sensible, coherent whole - it's just big,
35
+ # particularly because of all the various ways to interpret the
36
+ # content. Ignore the class length guidelines.
37
+
38
+ # rubocop:disable Metrics/ClassLength
39
+
40
+ # Represents a Diameter AVP. Use this for non-vendor-specific AVPs,
41
+ # and its subclass VendorSpecificAVP for ones defined for a particular
42
+ # vendor.
43
+ # @!attribute [r] code
44
+ # @return [Fixnum] The AVP Code
45
+ # @!attribute [r] mandatory
46
+ # @return [true, false] Whether this AVP is mandatory (i.e. its M flag is set)
47
+ class AVP
48
+ include Internals
49
+ include Constants::AVPType
50
+ attr_reader :code, :mandatory, :vendor_id
51
+
52
+ include AVPParser
53
+
54
+ # @api private
55
+ #
56
+ # Prefer {AVP.create} where possible.
57
+ def initialize(code, options = {})
58
+ @code = code
59
+ @vendor_id = options[:vendor_id] || 0
60
+ @content = options[:content] || ''
61
+ @mandatory = options.fetch(:mandatory, true)
62
+ end
63
+
64
+ # Creates an AVP by name, and assigns it a value.
65
+ #
66
+ # @param name The name of the AVP, e.g. "Origin-Host"
67
+ # @param val The value of the AVP. Must be of the type defined for
68
+ # that AVP - e.g. a Fixnum for an AVP defined as Unsigned32, a
69
+ # String for an AVP defined as OctetString, or an IPAddr for an AVP
70
+ # defined as IPAddress.
71
+ # @option opts [true, false] mandatory
72
+ # Whether understanding this AVP is mandatory within this
73
+ # application.
74
+ #
75
+ # @return [AVP] The AVP that was created.
76
+ def self.create(name, val, options = {})
77
+ code, type, vendor = AVPNames.get(name)
78
+ options[:vendor_id] = vendor
79
+ avp = AVP.new(code, options)
80
+
81
+ set_content(avp, type, val)
82
+
83
+ avp
84
+ end
85
+
86
+ # Defines a new AVP that can subsequently be created/retrieved by
87
+ # name.
88
+ #
89
+ # @param name [String] The AVP name
90
+ # @param code [Fixnum] The AVP Code
91
+ # @param type [AVPType] The type of this AVP's value
92
+ # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
93
+ # AVP.
94
+ # @return [void]
95
+ def self.define(name, code, type, vendor=nil)
96
+ AVPNames.add(name, code, type, vendor)
97
+ end
98
+
99
+ # Returns this AVP encoded properly as bytes in network byte order,
100
+ # suitable for sending over a TCP or SCTP connection. See
101
+ # {http://tools.ietf.org/html/rfc6733#section-4.1} for the
102
+ # format.
103
+ #
104
+ # @return [String] The bytes representing this AVP
105
+ def to_wire
106
+ if vendor_specific?
107
+ to_wire_vendor
108
+ else
109
+ to_wire_novendor
110
+ end
111
+ end
112
+
113
+ # Guessing the type of an AVP and displaying it sensibly is complex,
114
+ # so this is a complex method (but one that has a unity of purpose,
115
+ # so can't easily be broken down). Disable several Rubocop
116
+ # complexity metrics to reflect this.
117
+
118
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
119
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
120
+
121
+ # Returns a string representation of this AVP. Makes a best-effort
122
+ # attempt to guess the type of the content (even for unknown AVPs)
123
+ # and display it sensibly.
124
+ #
125
+ # @example
126
+ # avp.to_s => "AVP 267, mandatory: true, content as int32: 1"
127
+ def to_s
128
+ has_all_ascii_values =
129
+ @content.bytes.reject { |c| (32 < c && c < 126) }.empty?
130
+
131
+ could_be_32bit_num = (@content.length == 4)
132
+ could_be_64bit_num = (@content.length == 8)
133
+
134
+ could_be_ip = ((@content.length == 6 && @content[0..1] == "\x00\x01") ||
135
+ (@content.length == 18 && @content[0..1] == "\x00\x02"))
136
+
137
+ maybe_grouped = !(has_all_ascii_values ||
138
+ could_be_64bit_num ||
139
+ could_be_32bit_num ||
140
+ could_be_ip)
141
+
142
+ s = vendor_specific? ? "AVP #{@code}, Vendor-ID #{@vendor_id}, mandatory: #{@mandatory}" :
143
+ "AVP #{@code}, mandatory: #{@mandatory}"
144
+ s += ", content as string: #{@content}" if has_all_ascii_values
145
+ s += ", content as int32: #{uint32}" if could_be_32bit_num
146
+ s += ", content as int64: #{uint64}" if could_be_64bit_num
147
+ s += ", content as ip: #{ip_address}" if could_be_ip
148
+ s += ", grouped AVP, #{grouped_value.collect(&:to_s)}" if maybe_grouped
149
+
150
+ s
151
+ end
152
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
153
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
154
+
155
+ # @!attribute [r] vendor_specific?
156
+ # @return [true, false] Whether this AVP is mandatory
157
+ # (i.e. its M flag is set)
158
+ def vendor_specific?
159
+ @vendor_id != 0
160
+ end
161
+
162
+ # @!group Data getters/setters for different AVP types
163
+
164
+ # Returns this AVP's byte data, interpreted as a
165
+ # {http://tools.ietf.org/html/rfc6733#section-4.4 Grouped AVP}.
166
+ #
167
+ # @return [Array<AVP>] The contained AVPs.
168
+ def grouped_value
169
+ AVPParser.parse_avps_int(@content)
170
+ end
171
+
172
+ # Sets this AVP's byte data to a
173
+ # {http://tools.ietf.org/html/rfc6733#section-4.4 Grouped AVP}.
174
+ #
175
+ # @param [Array<AVP>] avps The AVPs that should be contained within
176
+ # this AVP.
177
+ # @return [void]
178
+ def grouped_value=(avps)
179
+ new_content = ''
180
+ avps.each { |a| new_content += a.to_wire }
181
+ @content = new_content
182
+ end
183
+
184
+ # For a grouped AVP, returns the first AVP with this name it
185
+ # contains.
186
+ #
187
+ # @param [String] name The AVP name
188
+ # @return [AVP] if this AVP is found inside the Grouped AVP
189
+ # @return [nil] if this AVP is not found inside the Grouped AVP
190
+ def inner_avp(name)
191
+ avps = inner_avps(name)
192
+
193
+ if avps.empty?
194
+ nil
195
+ else
196
+ avps[0]
197
+ end
198
+ end
199
+
200
+ # For a grouped AVP, returns all AVPs it contains with this name.
201
+ #
202
+ # @param [String] name The AVP name
203
+ # @return [Array<AVP>]
204
+ def inner_avps(name)
205
+ code, _type, _vendor = AVPNames.get(name)
206
+
207
+ grouped_value.select { |a| a.code == code }
208
+ end
209
+
210
+ alias_method :[], :inner_avp
211
+
212
+ # Even though it is just "the raw bytes in the content",
213
+ # octet_string is only one way of interpreting the AVP content and
214
+ # shouldn't be treated differently to the others, so disable the
215
+ # TrivialAccessors warning.
216
+
217
+ # rubocop:disable Style/TrivialAccessors
218
+
219
+ # Returns this AVP's byte data, interpreted as an OctetString.
220
+ #
221
+ # @return [String] The contained OctetString.
222
+ def octet_string
223
+ @content
224
+ end
225
+
226
+ # Sets this AVP's byte data to an OctetString.
227
+ #
228
+ # @param [String] value The octets to use as the value.
229
+ # @return [void]
230
+ def octet_string=(value)
231
+ @content = value
232
+ end
233
+
234
+ # rubocop:enable Style/TrivialAccessors
235
+
236
+ # Returns this AVP's byte data, interpreted as an Integer32.
237
+ #
238
+ # @return [Fixnum] The contained Integer32.
239
+ def int32
240
+ @content.unpack('l>')[0]
241
+ end
242
+
243
+ # Sets this AVP's byte data to an Integer32.
244
+ #
245
+ # @param [Fixnum] value
246
+ # @return [void]
247
+ def int32=(value)
248
+ @content = [value].pack('l>')
249
+ end
250
+
251
+ # Returns this AVP's byte data, interpreted as an Integer64.
252
+ #
253
+ # @return [Fixnum] The contained Integer64.
254
+ def int64
255
+ @content.unpack('q>')[0]
256
+ end
257
+
258
+ # Sets this AVP's byte data to an Integer64.
259
+ #
260
+ # @param [Fixnum] value
261
+ # @return [void]
262
+ def int64=(value)
263
+ @content = [value].pack('q>')
264
+ end
265
+
266
+ # Returns this AVP's byte data, interpreted as an Unsigned32.
267
+ #
268
+ # @return [Fixnum] The contained Unsigned32.
269
+ def uint32
270
+ @content.unpack('N')[0]
271
+ end
272
+
273
+ # Sets this AVP's byte data to an Unsigned32.
274
+ #
275
+ # @param [Fixnum] value
276
+ # @return [void]
277
+ def uint32=(value)
278
+ @content = [value].pack('N')
279
+ end
280
+
281
+ # Returns this AVP's byte data, interpreted as an Unsigned64.
282
+ #
283
+ # @return [Fixnum] The contained Unsigned64.
284
+ def uint64
285
+ @content.unpack('Q>')[0]
286
+ end
287
+
288
+ # Sets this AVP's byte data to an Unsigned64.
289
+ #
290
+ # @param [Fixnum] value
291
+ # @return [void]
292
+ def uint64=(value)
293
+ @content = [value].pack('Q>')
294
+ end
295
+
296
+ # Returns this AVP's byte data, interpreted as a Float32.
297
+ #
298
+ # @return [Float] The contained Float32.
299
+ def float32
300
+ @content.unpack('g')[0]
301
+ end
302
+
303
+ # Sets this AVP's byte data to a Float32.
304
+ #
305
+ # @param [Float] value
306
+ # @return [void]
307
+ def float32=(value)
308
+ @content = [value].pack('g')
309
+ end
310
+
311
+ # Returns this AVP's byte data, interpreted as a Float64.
312
+ #
313
+ # @return [Float] The contained Float64.
314
+ def float64
315
+ @content.unpack('G')[0]
316
+ end
317
+
318
+ # Sets this AVP's byte data to a Float64.
319
+ #
320
+ # @param [Float] value
321
+ # @return [void]
322
+ def float64=(value)
323
+ @content = [value].pack('G')
324
+ end
325
+
326
+ # Returns this AVP's byte data, interpreted as an
327
+ # {http://tools.ietf.org/html/rfc6733#section-4.3.1 Address}.
328
+ #
329
+ # @return [IPAddr] The contained
330
+ # {http://tools.ietf.org/html/rfc6733#section-4.3.1 Address}.
331
+ def ip_address
332
+ IPAddr.new_ntoh(@content[2..-1])
333
+ end
334
+
335
+ # Sets this AVP's byte data to an Address.
336
+ #
337
+ # @param [IPAddr] value
338
+ # @return [void]
339
+ def ip_address=(value)
340
+ bytes = if value.ipv4?
341
+ [1].pack('n')
342
+ else
343
+ [2].pack('n')
344
+ end
345
+
346
+ bytes += value.hton
347
+ @content = bytes
348
+ end
349
+
350
+ # @!endgroup
351
+
352
+ private
353
+
354
+ def self.set_content(avp, type, val)
355
+ case type
356
+ when GROUPED
357
+ avp.grouped_value = val
358
+ when U32
359
+ avp.uint32 = val
360
+ when OCTETSTRING
361
+ avp.octet_string = val
362
+ when IPADDR
363
+ avp.ip_address = val
364
+ end
365
+ end
366
+
367
+ def to_wire_novendor
368
+ length_8, length_16 = UInt24.to_u8_and_u16(@content.length + 8)
369
+ avp_flags = @mandatory ? '01000000' : '00000000'
370
+ header = [@code, avp_flags, length_8, length_16].pack('NB8Cn')
371
+ header + padded_content
372
+ end
373
+
374
+ def to_wire_vendor
375
+ length_8, length_16 = UInt24.to_u8_and_u16(@content.length + 12)
376
+ avp_flags = @mandatory ? '11000000' : '10000000'
377
+ header = [@code, avp_flags, length_8, length_16, @vendor_id].pack('NB8CnN')
378
+ header + padded_content
379
+ end
380
+
381
+ protected
382
+
383
+ def padded_content
384
+ wire_content = @content
385
+ wire_content += "\x00" while ((wire_content.length % 4) != 0)
386
+ wire_content
387
+ end
388
+ end
389
+
390
+ # rubocop:enable Metrics/ClassLength
391
+ end
@@ -0,0 +1,81 @@
1
+ require 'diameter/u24'
2
+
3
+ module Diameter
4
+ module Internals
5
+ # @private
6
+ # Parser mixin, sharing functionality common to:
7
+ # * parsing all the AVPs in a message
8
+ # * parsing the AVPs inside a Grouped AVP
9
+ module AVPParser
10
+ # Is the vendor-specific bit (the top bit) set?
11
+ #
12
+ # @param flags [String] A string of eight bits, e.g. "00000000"
13
+ # @return [true, false]
14
+ def self.vendor_id_bit(flags)
15
+ flags[0] == '1'
16
+ end
17
+
18
+ # Is the mandatory bit (the second bit) set?
19
+ #
20
+ # @param flags [String] A string of eight bits, e.g. "00000000"
21
+ # @return [true, false]
22
+ def self.mandatory_bit(flags)
23
+ flags[1] == '1'
24
+ end
25
+
26
+ # @return [Array(Fixnum, Fixnum, Bool, Fixnum, Fixnum)] The bytes consumed
27
+ # (8 or 12), AVP code,
28
+ # mandatory bit, length and vendor-ID (or 0 in the case of a
29
+ # non-vendor-specific AVP).
30
+ def self.parse_avp_header(bytes)
31
+ first_avp_header = bytes[0..8]
32
+ # Parse them
33
+ code, avp_flags, alength_8, alength_16 =
34
+ first_avp_header.unpack('NB8Cn')
35
+
36
+ mandatory = mandatory_bit(avp_flags)
37
+ length = UInt24.from_u8_and_u16(alength_8, alength_16)
38
+
39
+ if vendor_id_bit(avp_flags)
40
+ avp_vendor_header = bytes[8..12]
41
+ avp_vendor, = avp_vendor_header.unpack('N')
42
+ [12, code, mandatory, length, avp_vendor]
43
+ else
44
+ [8, code, mandatory, length, 0]
45
+ end
46
+ end
47
+
48
+ # @api private
49
+ #
50
+ # @param bytes [String] A sequence of bytes representing a set of AVPs.
51
+ # @return [Array<AVP>] The AVPs parsed out of the bytes.
52
+ def self.parse_avps_int(bytes)
53
+ avps = []
54
+ position = 0
55
+ while position < bytes.length
56
+ # Consume the first 8 octets
57
+ avp_consumed, code, mandatory, length, avp_vendor = parse_avp_header(bytes[position..-1])
58
+ position += avp_consumed
59
+
60
+ # Read the content, ensuring it aligns to a 32-byte boundary
61
+ avp_content_length = length - avp_consumed
62
+ avp_content = bytes[position..(position + avp_content_length) - 1]
63
+
64
+ padding = 0
65
+ padding += 1 until ((avp_content_length + padding) % 4) == 0
66
+
67
+ position += avp_content_length + padding
68
+
69
+ # Construct an AVP object from the parsed data
70
+ parsed_avp = AVP.new(code,
71
+ vendor_id: avp_vendor,
72
+ mandatory: mandatory,
73
+ content: avp_content)
74
+
75
+ avps.push parsed_avp
76
+ end
77
+ avps
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,54 @@
1
+ module Diameter
2
+ module Constants
3
+ # Contains Vendor-ID constants
4
+ module Vendors
5
+ # The 3GPP/IMS Vendor-ID
6
+ TGPP = 10_415
7
+ end
8
+
9
+ # Represents the type of data a particular AVP should be interpreted
10
+ # as.
11
+ module AVPType
12
+ # Represents an AVP of Grouped type
13
+ GROUPED = :Grouped
14
+
15
+ # Represents an AVP of Unsigned32 type
16
+ U32 = :Unsigned32
17
+
18
+ # Represents an AVP of OctetString type
19
+ OCTETSTRING = :OctetString
20
+
21
+ # Represents an AVP of IPAddress type
22
+ IPADDR = :Address
23
+ end
24
+
25
+ include AVPType
26
+ include Vendors
27
+
28
+ # The AVPs that can be looked up by name.
29
+ AVAILABLE_AVPS = {
30
+ 'Vendor-Specific-Application-Id' => [260, GROUPED],
31
+ 'Vendor-Id' => [266, U32],
32
+ 'Auth-Application-Id' => [258, U32],
33
+ 'Acct-Application-Id' => [259, U32],
34
+ 'Session-Id' => [263, OCTETSTRING],
35
+ 'Product-Name' => [269, OCTETSTRING],
36
+ 'Auth-Session-State' => [277, U32],
37
+ 'Inband-Security-Id' => [299, U32],
38
+ 'Origin-Host' => [264, OCTETSTRING],
39
+ 'Firmware-Revision' => [267, U32],
40
+ 'Result-Code' => [268, U32],
41
+ 'Origin-Realm' => [296, OCTETSTRING],
42
+ 'Destination-Host' => [293, OCTETSTRING],
43
+ 'Destination-Realm' => [283, OCTETSTRING],
44
+ 'User-Name' => [1, OCTETSTRING],
45
+ 'Host-IP-Address' => [257, IPADDR],
46
+ 'Public-Identity' => [601, OCTETSTRING, TGPP],
47
+ 'Server-Name' => [602, OCTETSTRING, TGPP],
48
+ 'SIP-Number-Auth-Items' => [607, U32, TGPP],
49
+ 'SIP-Auth-Data-Item' => [612, GROUPED, TGPP],
50
+ 'SIP-Item-Number' => [613, U32, TGPP],
51
+ 'SIP-Authentication-Scheme' => [608, OCTETSTRING, TGPP] }
52
+
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+
3
+ # The Diameter namespace
4
+ module Diameter
5
+ @int_DiameterLogger = nil
6
+
7
+ # Returns the logger to be used by the Diameter stack and associated
8
+ # objects. If no logger has been set with {Diameter.set_logger},
9
+ # defaults to writing to ./diameterstack.log
10
+ #
11
+ # @return [Logger]
12
+ def self.logger
13
+ @int_DiameterLogger ||= Logger.new('./diameterstack.log', 10, (1024^3))
14
+ @int_DiameterLogger
15
+ end
16
+
17
+ # Sets the logger to be used by the Diameter stack and associated
18
+ # objects.
19
+ #
20
+ # @param value [Logger]
21
+ def self.set_logger(value)
22
+ @int_DiameterLogger = value
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ require 'micromachine'
2
+
3
+ machine = MicroMachine.new(:closed) # Initial state.
4
+
5
+ # Define the possible transitions for each event.
6
+ machine.when(:closed, :start => :wait_conn_ack)
7
+ machine.when(:closed, :r_conn_cer => :r_open)
8
+
9
+ machine.when(:wait_conn_ack, :i_rcv_conn_ack => :wait_i_cea)
10
+ machine.when(:wait_conn_ack, :i_rcv_conn_nack => :closed)
11
+ machine.when(:wait_conn_ack, :r_conn_cer => :wait_conn_ack_elect)
12
+ machine.when(:wait_conn_ack, :timeout => :closed)
13
+
14
+
15
+ machine.when(:reset, :confirmed => :new, :ignored => :new)
16
+
17
+ machine.trigger(:confirm) #=> true
18
+ machine.state #=> :confirmed
19
+
20
+ machine.trigger(:ignore) #=> false
21
+ machine.state #=> :confirmed
22
+
23
+ machine.trigger(:reset) #=> true
24
+ machine.state #=> :new
25
+
26
+ machine.trigger(:ignore) #=> true
27
+ machine.state #=> :ignored