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 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: