diameter 0.1.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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