diameter 0.1.0.pre4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e787a00dc9e59758d16cceaa42c530a14e474721
4
- data.tar.gz: 0b99ff5787724bbbda608a90e0f8d249601ca63f
3
+ metadata.gz: 121a1f974bb560f963d47972a12d9e8ab1702b58
4
+ data.tar.gz: e0b7395beece98bf489951db82e5760c57827488
5
5
  SHA512:
6
- metadata.gz: 1d284fd28e4511c7bf8565f987298d9be213b53f3108c39188abeaa91dc5943dbbcd6e2b00fcb1084bfac6d201725343c650a6ad908bfaaef1a9764982412de4
7
- data.tar.gz: 4eed27cfbc0a60588d1c696437a3be93dd5a811ba609d4c5b48a8ae085f621076a5ccf6f973ff75cf1611d37b8994d53c233d1858fec6d680d13b7f25e77b32d
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[:mandatory]
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
@@ -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
- # {AVPNames::AVAILABLE_AVPS} or user-defined with {AVP.define}
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
- # {AVPNames::AVAILABLE_AVPS} or user-defined with {AVP.define}
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
- # {AVPNames::AVAILABLE_AVPS} or user-defined with {AVP.define}
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
- # @api private
172
+ # @private
165
173
  #
166
- # Adds an AVP to this message. Not recommended for normal use -
167
- # all AVPs should be given to the constructor. Used to allow the
168
- # stack to add appropriate Origin-Host/Origin-Realm AVPs to outbound
169
- # messages.
170
- # @param name [String] The AVP name, either one predefined in
171
- # {AVPNames::AVAILABLE_AVPS} or user-defined with {AVP.define}
172
- # @param value [Object] The AVP value, with type dependent on the
173
- # AVP itself.
174
- def add_avp(name, value)
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
@@ -9,10 +9,21 @@ require 'concurrent'
9
9
  module Diameter
10
10
  class Stack
11
11
  include Internals
12
- def initialize(host, realm, opts={})
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
- # @!group Setup methods
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
- def listen_for_tcp
43
- @tcp_helper.setup_new_listen_connection("0.0.0.0", @local_port)
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) and a
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, _realm)
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.add_avp('Origin-Host', @local_host) unless req.has_avp? 'Origin-Host'
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.add_avp('Origin-Host', @local_host) unless ans.has_avp? 'Origin-Host'
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.pre4
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: 1.3.1
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: