diameter 0.1.0.pre4 → 0.1.0
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 +4 -4
- data/lib/diameter/avp.rb +24 -17
- data/lib/diameter/message.rb +30 -22
- data/lib/diameter/stack.rb +82 -12
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 121a1f974bb560f963d47972a12d9e8ab1702b58
|
4
|
+
data.tar.gz: e0b7395beece98bf489951db82e5760c57827488
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de100f0fbb64fcf7f65caf44a0f4ba42915d69e05a5c6d574df3aa1eb398df123eb7788caf8487dcb5637d1f9ae8e42a8683149e9179584f39f7c3dade7db2a5
|
7
|
+
data.tar.gz: bcca6e6ece1548202843fbe343729ca652048ad98efb2a8e38834af85d75744d3a09ed18f9d3dc029bdce46cbc1c95614f9a41de49dbb78f01e639361fa60d3b
|
data/lib/diameter/avp.rb
CHANGED
@@ -51,12 +51,14 @@ module Diameter
|
|
51
51
|
|
52
52
|
include AVPParser
|
53
53
|
|
54
|
+
# @api private
|
55
|
+
#
|
56
|
+
# Prefer {AVP.create} where possible.
|
54
57
|
def initialize(code, options = {})
|
55
58
|
@code = code
|
56
59
|
@vendor_id = options[:vendor_id] || 0
|
57
60
|
@content = options[:content] || ''
|
58
|
-
@mandatory = options
|
59
|
-
@mandatory = true if @mandatory.nil?
|
61
|
+
@mandatory = options.fetch(:mandatory, true)
|
60
62
|
end
|
61
63
|
|
62
64
|
# Creates an AVP by name, and assigns it a value.
|
@@ -66,6 +68,10 @@ module Diameter
|
|
66
68
|
# that AVP - e.g. a Fixnum for an AVP defined as Unsigned32, a
|
67
69
|
# String for an AVP defined as OctetString, or an IPAddr for an AVP
|
68
70
|
# defined as IPAddress.
|
71
|
+
# @option opts [true, false] mandatory
|
72
|
+
# Whether understanding this AVP is mandatory within this
|
73
|
+
# application.
|
74
|
+
#
|
69
75
|
# @return [AVP] The AVP that was created.
|
70
76
|
def self.create(name, val, options = {})
|
71
77
|
code, type, vendor = AVPNames.get(name)
|
@@ -103,22 +109,7 @@ module Diameter
|
|
103
109
|
to_wire_novendor
|
104
110
|
end
|
105
111
|
end
|
106
|
-
|
107
|
-
def to_wire_novendor
|
108
|
-
length_8, length_16 = UInt24.to_u8_and_u16(@content.length + 8)
|
109
|
-
avp_flags = @mandatory ? '01000000' : '00000000'
|
110
|
-
header = [@code, avp_flags, length_8, length_16].pack('NB8Cn')
|
111
|
-
header + padded_content
|
112
|
-
end
|
113
|
-
|
114
|
-
def to_wire_vendor
|
115
|
-
length_8, length_16 = UInt24.to_u8_and_u16(@content.length + 12)
|
116
|
-
avp_flags = @mandatory ? '11000000' : '10000000'
|
117
|
-
header = [@code, avp_flags, length_8, length_16, @vendor_id].pack('NB8CnN')
|
118
|
-
header + padded_content
|
119
|
-
end
|
120
112
|
|
121
|
-
|
122
113
|
# Guessing the type of an AVP and displaying it sensibly is complex,
|
123
114
|
# so this is a complex method (but one that has a unity of purpose,
|
124
115
|
# so can't easily be broken down). Disable several Rubocop
|
@@ -216,6 +207,8 @@ module Diameter
|
|
216
207
|
grouped_value.select { |a| a.code == code }
|
217
208
|
end
|
218
209
|
|
210
|
+
alias_method :[], :inner_avp
|
211
|
+
|
219
212
|
# Even though it is just "the raw bytes in the content",
|
220
213
|
# octet_string is only one way of interpreting the AVP content and
|
221
214
|
# shouldn't be treated differently to the others, so disable the
|
@@ -371,6 +364,20 @@ module Diameter
|
|
371
364
|
end
|
372
365
|
end
|
373
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
|
+
|
374
381
|
protected
|
375
382
|
|
376
383
|
def padded_content
|
data/lib/diameter/message.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'diameter/avp_parser'
|
2
2
|
require 'diameter/u24'
|
3
3
|
|
4
|
+
# The Diameter module
|
4
5
|
module Diameter
|
5
6
|
# A Diameter message.
|
6
7
|
#
|
@@ -17,16 +18,14 @@ module Diameter
|
|
17
18
|
# The end-to-end identifier of this message.
|
18
19
|
# @!attribute [r] request
|
19
20
|
# Whether this message is a request.
|
21
|
+
# @!attribute [r] answer
|
22
|
+
# Whether this message is an answer.
|
20
23
|
class Message
|
21
|
-
attr_reader :version, :command_code, :app_id, :hbh, :ete, :request
|
24
|
+
attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
|
22
25
|
include Internals
|
23
|
-
|
24
|
-
# @!attribute [r] answer
|
25
|
-
# Whether this message is an answer.
|
26
|
-
def answer
|
27
|
-
!@request
|
28
|
-
end
|
29
26
|
|
27
|
+
# Creates a new Diameter message.
|
28
|
+
#
|
30
29
|
# @option opts [Fixnum] command_code
|
31
30
|
# The Diameter Command-Code of this messsage.
|
32
31
|
# @option opts [Fixnum] app_id
|
@@ -52,6 +51,7 @@ module Diameter
|
|
52
51
|
@ete = options[:ete] || Message.next_ete
|
53
52
|
|
54
53
|
@request = options.fetch(:request, true)
|
54
|
+
@answer = !@request
|
55
55
|
@proxyable = options.fetch(:proxyable, false)
|
56
56
|
@retransmitted = false
|
57
57
|
@error = false
|
@@ -90,8 +90,10 @@ module Diameter
|
|
90
90
|
# Returns the first AVP with the given name. Only covers "top-level"
|
91
91
|
# AVPs - it won't look inside Grouped AVPs.
|
92
92
|
#
|
93
|
+
# Also available as [], e.g. message['Result-Code']
|
94
|
+
#
|
93
95
|
# @param name [String] The AVP name, either one predefined in
|
94
|
-
# {
|
96
|
+
# {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
|
95
97
|
#
|
96
98
|
# @return [AVP] if there is an AVP with that name
|
97
99
|
# @return [nil] if there is not an AVP with that name
|
@@ -104,7 +106,7 @@ module Diameter
|
|
104
106
|
# AVPs - it won't look inside Grouped AVPs.
|
105
107
|
#
|
106
108
|
# @param name [String] The AVP name, either one predefined in
|
107
|
-
# {
|
109
|
+
# {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
|
108
110
|
#
|
109
111
|
# @return [Array<AVP>]
|
110
112
|
def all_avps_by_name(name)
|
@@ -115,7 +117,10 @@ module Diameter
|
|
115
117
|
alias_method :avp, :avp_by_name
|
116
118
|
alias_method :[], :avp_by_name
|
117
119
|
alias_method :avps, :all_avps_by_name
|
118
|
-
|
120
|
+
|
121
|
+
# @private
|
122
|
+
# Prefer AVP.define and the by-name versions to this
|
123
|
+
#
|
119
124
|
# Returns the first AVP with the given code and vendor. Only covers "top-level"
|
120
125
|
# AVPs - it won't look inside Grouped AVPs.
|
121
126
|
#
|
@@ -133,6 +138,9 @@ module Diameter
|
|
133
138
|
end
|
134
139
|
end
|
135
140
|
|
141
|
+
# @private
|
142
|
+
# Prefer AVP.define and the by-name versions to this
|
143
|
+
#
|
136
144
|
# Returns all AVPs with the given code and vendor. Only covers "top-level"
|
137
145
|
# AVPs - it won't look inside Grouped AVPs.
|
138
146
|
#
|
@@ -154,25 +162,24 @@ module Diameter
|
|
154
162
|
|
155
163
|
# Does this message contain a (top-level) AVP with this name?
|
156
164
|
# @param name [String] The AVP name, either one predefined in
|
157
|
-
# {
|
165
|
+
# {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
|
158
166
|
#
|
159
167
|
# @return [true, false]
|
160
168
|
def has_avp?(name)
|
161
169
|
!!avp(name)
|
162
170
|
end
|
163
171
|
|
164
|
-
# @
|
172
|
+
# @private
|
165
173
|
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
# @param
|
171
|
-
#
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
@avps << AVP.create(name, value)
|
174
|
+
# Not recommended for normal use - all AVPs should be given to the
|
175
|
+
# constructor. Used to allow the stack to add appropriate
|
176
|
+
# Origin-Host/Origin-Realm AVPs to outbound messages.
|
177
|
+
#
|
178
|
+
# @param host [String] The Diameter Identity for the stack.
|
179
|
+
# @param realm [String] The Diameter realm for the stack.
|
180
|
+
def add_origin_host_and_realm(host, realm)
|
181
|
+
@avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
|
182
|
+
@avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
|
176
183
|
end
|
177
184
|
|
178
185
|
# @!endgroup
|
@@ -207,6 +214,7 @@ module Diameter
|
|
207
214
|
avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
|
208
215
|
Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: request, proxyable: proxyable, retransmitted: false, error: false, avps: avps)
|
209
216
|
end
|
217
|
+
|
210
218
|
# @!endgroup
|
211
219
|
|
212
220
|
# Generates an answer to this request, filling in a Result-Code or
|
data/lib/diameter/stack.rb
CHANGED
@@ -9,10 +9,21 @@ require 'concurrent'
|
|
9
9
|
module Diameter
|
10
10
|
class Stack
|
11
11
|
include Internals
|
12
|
-
|
12
|
+
|
13
|
+
# @!group Setup methods
|
14
|
+
|
15
|
+
# Stack constructor.
|
16
|
+
#
|
17
|
+
# @note The stack does not advertise any applications to peers by
|
18
|
+
# default - {#add_handler} must be called early on.
|
19
|
+
#
|
20
|
+
# @param host [String] The Diameter Identity of this stack (for
|
21
|
+
# the Origin-Host AVP).
|
22
|
+
# @param realm [String] The Diameter realm of this stack (for
|
23
|
+
# the Origin-Realm AVP).
|
24
|
+
def initialize(host, realm)
|
13
25
|
@local_host = host
|
14
26
|
@local_realm = realm
|
15
|
-
@local_port = opts[:port] || 3868
|
16
27
|
|
17
28
|
@auth_apps = []
|
18
29
|
@acct_apps = []
|
@@ -34,15 +45,43 @@ module Diameter
|
|
34
45
|
Diameter.logger.log(Logger::INFO, 'Stack initialized')
|
35
46
|
end
|
36
47
|
|
37
|
-
#
|
48
|
+
# Complete the stack initialization and begin reading from the TCP connections.
|
38
49
|
def start
|
39
50
|
@tcp_helper.start_main_loop
|
40
51
|
end
|
41
52
|
|
42
|
-
|
43
|
-
|
53
|
+
# Begins listening for inbound Diameter connections (making this a
|
54
|
+
# Diameter server instead of just a client).
|
55
|
+
#
|
56
|
+
# @param port [Fixnum] The TCP port to listen on (default 3868)
|
57
|
+
def listen_for_tcp(port=3868)
|
58
|
+
@tcp_helper.setup_new_listen_connection("0.0.0.0", port)
|
44
59
|
end
|
45
60
|
|
61
|
+
# Adds a handler for a specific Diameter application.
|
62
|
+
#
|
63
|
+
# @note If you expect to only send requests for this application,
|
64
|
+
# not receive them, the block can be a no-op (e.g. `{ nil }`)
|
65
|
+
#
|
66
|
+
# @param app_id [Fixnum] The Diameter application ID.
|
67
|
+
# @option opts [true, false] auth
|
68
|
+
# Whether we should advertise support for this application in
|
69
|
+
# the Auth-Application-ID AVP. Note that at least one of auth or
|
70
|
+
# acct must be specified.
|
71
|
+
# @option opts [true, false] acct
|
72
|
+
# Whether we should advertise support for this application in
|
73
|
+
# the Acct-Application-ID AVP. Note that at least one of auth or
|
74
|
+
# acct must be specified.
|
75
|
+
# @option opts [Fixnum] vendor
|
76
|
+
# If we should advertise support for this application in a
|
77
|
+
# Vendor-Specific-Application-Id AVP, this specifies the
|
78
|
+
# associated Vendor-Id.
|
79
|
+
#
|
80
|
+
# @yield [req, cxn] Passes a Diameter message (and its originating
|
81
|
+
# connection) for application-specific handling.
|
82
|
+
# @yieldparam [Message] req The parsed Diameter message from the peer.
|
83
|
+
# @yieldparam [Socket] cxn The TCP connection to the peer, to be
|
84
|
+
# passed to {Stack#send_answer}.
|
46
85
|
def add_handler(app_id, opts={}, &blk)
|
47
86
|
vendor = opts.fetch(:vendor, 0)
|
48
87
|
auth = opts.fetch(:auth, false)
|
@@ -58,6 +97,8 @@ module Diameter
|
|
58
97
|
|
59
98
|
# @!endgroup
|
60
99
|
|
100
|
+
# This shuts the stack down, closing all TCP connections and
|
101
|
+
# terminating any background threads still waiting for an answer.
|
61
102
|
def shutdown
|
62
103
|
@tcp_helper.shutdown
|
63
104
|
@pending_ete.each do |ete, q|
|
@@ -68,6 +109,12 @@ module Diameter
|
|
68
109
|
@threadpool.wait_for_termination(5)
|
69
110
|
end
|
70
111
|
|
112
|
+
# Closes the given connection, blanking out any internal data
|
113
|
+
# structures associated with it.
|
114
|
+
#
|
115
|
+
# Likely to be moved to the Peer object in a future release/
|
116
|
+
#
|
117
|
+
# @param connection [Socket] The connection to close.
|
71
118
|
def close(connection)
|
72
119
|
@tcp_helper.close(connection)
|
73
120
|
end
|
@@ -78,12 +125,11 @@ module Diameter
|
|
78
125
|
# network location indicated by peer_uri.
|
79
126
|
#
|
80
127
|
# @param peer_uri [URI] The aaa:// URI identifying the peer. Should
|
81
|
-
# contain a hostname/IP; may contain a port (default 3868)
|
82
|
-
# transport param indicating TCP or SCTP (default TCP).
|
128
|
+
# contain a hostname/IP; may contain a port (default 3868).
|
83
129
|
# @param peer_host [String] The DiameterIdentity of this peer, which
|
84
130
|
# will uniquely identify it in the peer table.
|
85
131
|
# @param realm [String] The Diameter realm of this peer.
|
86
|
-
def connect_to_peer(peer_uri, peer_host,
|
132
|
+
def connect_to_peer(peer_uri, peer_host, realm)
|
87
133
|
uri = URI(peer_uri)
|
88
134
|
cxn = @tcp_helper.setup_new_connection(uri.host, uri.port)
|
89
135
|
avps = [AVP.create('Origin-Host', @local_host),
|
@@ -102,10 +148,16 @@ module Diameter
|
|
102
148
|
# Will move to :UP when the CEA is received
|
103
149
|
end
|
104
150
|
|
151
|
+
# Sends a Diameter request. This is routed to an appropriate peer
|
152
|
+
# based on the Destination-Host AVP.
|
153
|
+
#
|
154
|
+
# This adds this stack's Origin-Host and Origin-Realm AVPs, if
|
155
|
+
# those AVPs don't already exist.
|
156
|
+
#
|
157
|
+
# @param req [Message] The request to send.
|
105
158
|
def send_request(req)
|
106
159
|
fail "Must pass a request" unless req.request
|
107
|
-
req.
|
108
|
-
req.add_avp('Origin-Realm', @local_realm) unless req.has_avp? 'Origin-Realm'
|
160
|
+
req.add_origin_host_and_realm(@local_host, @local_realm)
|
109
161
|
peer_name = req.avp_by_name('Destination-Host').octet_string
|
110
162
|
state = peer_state(peer_name)
|
111
163
|
if state == :UP
|
@@ -125,13 +177,28 @@ module Diameter
|
|
125
177
|
end
|
126
178
|
end
|
127
179
|
|
180
|
+
# Sends a Diameter answer. This is sent over the same connection
|
181
|
+
# the request was received on (which needs to be passed into to
|
182
|
+
# this method).
|
183
|
+
#
|
184
|
+
# This adds this stack's Origin-Host and Origin-Realm AVPs, if
|
185
|
+
# those AVPs don't already exist.
|
186
|
+
#
|
187
|
+
# @param ans [Message] The Diameter answer
|
188
|
+
# @param original_cxn [Socket] The connection which the request
|
189
|
+
# came in on. This will have been passed to the block registered
|
190
|
+
# with {Stack#add_handler}.
|
128
191
|
def send_answer(ans, original_cxn)
|
129
192
|
fail "Must pass an answer" unless ans.answer
|
130
|
-
ans.
|
131
|
-
ans.add_avp('Origin-Realm', @local_realm) unless ans.has_avp? 'Origin-Realm'
|
193
|
+
ans.add_origin_host_and_realm(@local_host, @local_realm)
|
132
194
|
@tcp_helper.send(ans.to_wire, original_cxn)
|
133
195
|
end
|
134
196
|
|
197
|
+
# Retrieves the current state of a peer, defaulting to :CLOSED if
|
198
|
+
# the peer does not exist.
|
199
|
+
#
|
200
|
+
# @param id [String] The Diameter identity of the peer.
|
201
|
+
# @return [Keyword] The state of the peer (:UP, :WAITING or :CLOSED).
|
135
202
|
def peer_state(id)
|
136
203
|
if !@peer_table.key? id
|
137
204
|
:CLOSED
|
@@ -143,6 +210,9 @@ module Diameter
|
|
143
210
|
# @!endgroup
|
144
211
|
|
145
212
|
# @private
|
213
|
+
# Handles a Diameter request straight from a network connection.
|
214
|
+
# Intended to be called by TCPStackHelper after it retrieves a
|
215
|
+
# message, not directly by users.
|
146
216
|
def handle_message(msg_bytes, cxn)
|
147
217
|
# Common processing - ensure that this message has come in on this
|
148
218
|
# peer's expected connection, and update the last time we saw
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diameter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Day
|
@@ -126,9 +126,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
126
|
version: '0'
|
127
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: '0'
|
132
132
|
requirements: []
|
133
133
|
rubyforge_project:
|
134
134
|
rubygems_version: 2.4.3
|
@@ -136,3 +136,4 @@ signing_key:
|
|
136
136
|
specification_version: 4
|
137
137
|
summary: Pure-Ruby Diameter stack
|
138
138
|
test_files: []
|
139
|
+
has_rdoc:
|