Class: Diameter::Message

Inherits:
Object
  • Object
show all
Includes:
Internals
Defined in:
lib/diameter/message.rb

Overview

A Diameter message.

Instance Attribute Summary collapse

AVP retrieval collapse

Parsing collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Message

Creates a new Diameter message.

Parameters:

  • options (Hash) (defaults to: {})

    The options

Options Hash (options):

  • command_code (Fixnum)

    The Diameter Command-Code of this messsage.

  • app_id (Fixnum)

    The Diameter application ID of this message, or 0 for base protocol messages.

  • hbh (Fixnum)

    The hop-by-hop identifier of this message.

  • ete (Fixnum)

    The end-to-end identifier of this message.

  • request (true, false)

    Whether this message is a request. Defaults to true.

  • proxyable (true, false)

    Whether this message can be forwarded on. Defaults to true.

  • error (true, false)

    Whether this message is a Diameter protocol error. Defaults to false.

  • avps (Array<AVP>)

    The list of AVPs to include on this message.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/diameter/message.rb', line 47

def initialize(options = {})
  @version = 1
  @command_code = options[:command_code]
  @app_id = options[:app_id]
  @hbh = options[:hbh] || Message.next_hbh
  @ete = options[:ete] || Message.next_ete

  @request = options.fetch(:request, true)
  @answer = !@request
  @proxyable = options.fetch(:proxyable, false)
  @retransmitted = false
  @error = false

  @avps = options[:avps] || []
end

Instance Attribute Details

#answerObject (readonly)

Whether this message is an answer.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#app_idObject (readonly)

The Diameter application ID of this message, or 0 for base protocol messages.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#command_codeObject (readonly)

The Diameter Command-Code of this messsage.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#eteObject (readonly)

The end-to-end identifier of this message.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#hbhObject (readonly)

The hop-by-hop identifier of this message.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#requestObject (readonly)

Whether this message is a request.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

#versionObject (readonly)

The Diameter protocol version (currently always 1)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/diameter/message.rb', line 23

class Message
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request, :answer
  include Internals

  # Creates a new Diameter message.
  #
  # @param [Hash] options The options
  # @option options [Fixnum] command_code
  #   The Diameter Command-Code of this messsage.
  # @option options [Fixnum] app_id
  #   The Diameter application ID of this message, or 0 for base
  #   protocol messages.
  # @option options [Fixnum] hbh
  #   The hop-by-hop identifier of this message.
  # @option options [Fixnum] ete
  #   The end-to-end identifier of this message.
  # @option options [true, false] request
  #   Whether this message is a request. Defaults to true.
  # @option options [true, false] proxyable
  #   Whether this message can be forwarded on. Defaults to true.
  # @option options [true, false] error
  #   Whether this message is a Diameter protocol error. Defaults to false.
  # @option options [Array<AVP>] avps
  #   The list of AVPs to include on this message.
  def initialize(options = {})
    @version = 1
    @command_code = options[:command_code]
    @app_id = options[:app_id]
    @hbh = options[:hbh] || Message.next_hbh
    @ete = options[:ete] || Message.next_ete

    @request = options.fetch(:request, true)
    @answer = !@request
    @proxyable = options.fetch(:proxyable, false)
    @retransmitted = false
    @error = false

    @avps = options[:avps] || []
  end

  # Represents this message (and all its AVPs) in human-readable
  # string form.
  #
  # @see AVP::to_s for how the AVPs are represented.
  # @return [String]
  def to_s
    "#{@command_code}: #{@avps.collect(&:to_s)}"
  end

  # Serializes a Diameter message (header plus AVPs) into the series
  # of bytes representing it on the wire.
  #
  # @return [String] The byte-encoded form.
  def to_wire
    content = ''
    @avps.each { |a| content += a.to_wire }
    length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
    request_flag = @request ? '1' : '0'
    proxy_flag = @proxyable ? '1' : '0'
    flags_str = "#{request_flag}#{proxy_flag}000000"

    header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
    header + content
  end

  # @!group AVP retrieval

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # Also available as [], e.g. message['Result-Code']
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [AVP] if there is an AVP with that name
  # @return [nil] if there is not an AVP with that name
  def avp_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    avp_by_code(code, vendor)
  end

  # Returns all AVPs with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = Internals::AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  alias_method :avp, :avp_by_name
  alias_method :[], :avp_by_name
  alias_method :avps, :all_avps_by_name

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [AVP] if there is an AVP with that code/vendor
  # @return [nil] if there is not an AVP with that code/vendor
  def avp_by_code(code, vendor = 0)
    avps = all_avps_by_code(code, vendor)
    if avps.empty?
      nil
    else
      avps[0]
    end
  end

  # @private
  # Prefer AVP.define and the by-name versions to this
  #
  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @param code [Fixnum] The AVP Code
  # @param vendor [Fixnum] Optional vendor ID for a vendor-specific
  #   AVP.
  # @return [Array<AVP>]
  def all_avps_by_code(code, vendor = 0)
    @avps.select do |a|
      vendor_match =
        if a.vendor_specific?
          a.vendor_id == vendor
        else
          vendor == 0
        end
      (a.code == code) && vendor_match
    end
  end

  # Does this message contain a (top-level) AVP with this name?
  # @param name [String] The AVP name, either one predefined in
  #   {Constants::AVAILABLE_AVPS} or user-defined with {AVP.define}
  #
  # @return [true, false]  
  def has_avp?(name)
    !!avp(name)
  end

  # @private
  #
  # Not recommended for normal use - all AVPs should be given to the
  # constructor. Used to allow the stack to add appropriate
  # Origin-Host/Origin-Realm AVPs to outbound messages.
  #
  # @param host [String] The Diameter Identity for the stack.
  # @param realm [String] The Diameter realm for the stack.
  def add_origin_host_and_realm(host, realm)
    @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
    @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
  end
  
  # @!endgroup

  # @!group Parsing
  
  # Parses the first four bytes of the Diameter header to learn the
  # length. Callers should use this to work out how many more bytes
  # they need to read off a TCP connection to pass to self.from_bytes.
  #
  # @param header [String] A four-byte Diameter header
  # @return [Fixnum] The message length field from the header
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    Internals::UInt24.from_u8_and_u16(length_8, length_16)
  end

  # Parses a byte representation (a 20-byte header plus AVPs) into a
  # DiameterMessage object.
  #
  # @param bytes [String] The on-the-wire byte representation of a
  #   Diameter message.
  # @return [DiameterMessage] The parsed object form.
  def self.from_bytes(bytes)
    header = bytes[0..20]
    version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
    command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

    request = (flags_str[0] == '1')
    proxyable = (flags_str[1] == '1')

    avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
    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)
  end
  
  # @!endgroup

  # Generates an answer to this request, filling in a Result-Code or
  # Experimental-Result AVP.
  #
  # @param result_code [Fixnum] The value for the Result-Code AVP
  # @option opts [Fixnum] experimental_result_vendor
  #   If given, creates an Experimental-Result AVP with this vendor
  #   instead of the Result-Code AVP. 
  # @option opts [Array<String>] copying_avps
  #   A list of AVP names to copy from the request to the answer.
  # @option opts [Array<Diameter::AVP>] avps
  #   A list of AVP objects to add on the answer.
  # @return [Diameter::Message] The response created.
  def create_answer(result_code, opts={})
    fail "Cannot answer an answer" if answer
    
    avps = []
    avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
    avps += opts.fetch(:avps, [])
    avps << if opts[:experimental_result_vendor]
              AVP.create("Experimental-Result",
                         [AVP.create("Experimental-Result-Code", result_code),
                          AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
            else
              AVP.create("Result-Code", result_code)
            end
    
    avps += opts.fetch(:copying_avps, []).collect do |name|
      src_avp = avp_by_name(name)

      fail if src_avp.nil?
      
      src_avp.dup
    end

    Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
  end

  private
  def self.next_hbh
    @hbh ||= rand(10000)
    @hbh += 1
    @hbh
  end

  def self.next_ete
    @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000)
    @ete += 1
  end

end

Class Method Details

.from_bytes(bytes) ⇒ DiameterMessage

Parses a byte representation (a 20-byte header plus AVPs) into a DiameterMessage object.

Parameters:

  • bytes (String)

    The on-the-wire byte representation of a Diameter message.

Returns:

  • (DiameterMessage)

    The parsed object form.



207
208
209
210
211
212
213
214
215
216
217
# File 'lib/diameter/message.rb', line 207

def self.from_bytes(bytes)
  header = bytes[0..20]
  version, _length_8, _length_16, flags_str, code_8, code_16, app_id, hbh, ete = header.unpack('CCnB8CnNNN')
  command_code = Internals::UInt24.from_u8_and_u16(code_8, code_16)

  request = (flags_str[0] == '1')
  proxyable = (flags_str[1] == '1')

  avps = Internals::AVPParser.parse_avps_int(bytes[20..-1])
  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)
end

.length_from_header(header) ⇒ Fixnum

Parses the first four bytes of the Diameter header to learn the length. Callers should use this to work out how many more bytes they need to read off a TCP connection to pass to self.from_bytes.

Parameters:

  • header (String)

    A four-byte Diameter header

Returns:

  • (Fixnum)

    The message length field from the header



196
197
198
199
# File 'lib/diameter/message.rb', line 196

def self.length_from_header(header)
  _version, length_8, length_16 = header.unpack('CCn')
  Internals::UInt24.from_u8_and_u16(length_8, length_16)
end

Instance Method Details

#add_origin_host_and_realm(host, realm) ⇒ Object

Not recommended for normal use - all AVPs should be given to the constructor. Used to allow the stack to add appropriate Origin-Host/Origin-Realm AVPs to outbound messages.

Parameters:

  • host (String)

    The Diameter Identity for the stack.

  • realm (String)

    The Diameter realm for the stack.



181
182
183
184
# File 'lib/diameter/message.rb', line 181

def add_origin_host_and_realm(host, realm)
  @avps << AVP.create("Origin-Host", host) unless has_avp? 'Origin-Host'
  @avps << AVP.create("Origin-Realm", realm) unless has_avp? 'Origin-Realm'
end

#all_avps_by_code(code, vendor = 0) ⇒ Array<AVP>

Prefer AVP.define and the by-name versions to this

Returns all AVPs with the given code and vendor. Only covers “top-level” AVPs - it won’t look inside Grouped AVPs.

Parameters:

  • code (Fixnum)

    The AVP Code

  • vendor (Fixnum) (defaults to: 0)

    Optional vendor ID for a vendor-specific AVP.

Returns:



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/diameter/message.rb', line 152

def all_avps_by_code(code, vendor = 0)
  @avps.select do |a|
    vendor_match =
      if a.vendor_specific?
        a.vendor_id == vendor
      else
        vendor == 0
      end
    (a.code == code) && vendor_match
  end
end

#all_avps_by_name(name) ⇒ Array<AVP> Also known as: avps

Returns all AVPs with the given name. Only covers “top-level” AVPs - it won’t look inside Grouped AVPs.

Parameters:

Returns:



113
114
115
116
# File 'lib/diameter/message.rb', line 113

def all_avps_by_name(name)
  code, _type, vendor = Internals::AVPNames.get(name)
  all_avps_by_code(code, vendor)
end

#avp_by_code(code, vendor = 0) ⇒ AVP?

Prefer AVP.define and the by-name versions to this

Returns the first AVP with the given code and vendor. Only covers “top-level” AVPs - it won’t look inside Grouped AVPs.

Parameters:

  • code (Fixnum)

    The AVP Code

  • vendor (Fixnum) (defaults to: 0)

    Optional vendor ID for a vendor-specific AVP.

Returns:

  • (AVP)

    if there is an AVP with that code/vendor

  • (nil)

    if there is not an AVP with that code/vendor



133
134
135
136
137
138
139
140
# File 'lib/diameter/message.rb', line 133

def avp_by_code(code, vendor = 0)
  avps = all_avps_by_code(code, vendor)
  if avps.empty?
    nil
  else
    avps[0]
  end
end

#avp_by_name(name) ⇒ AVP? Also known as: avp, []

Returns the first AVP with the given name. Only covers “top-level” AVPs - it won’t look inside Grouped AVPs.

Also available as [], e.g. message

Parameters:

Returns:

  • (AVP)

    if there is an AVP with that name

  • (nil)

    if there is not an AVP with that name



101
102
103
104
# File 'lib/diameter/message.rb', line 101

def avp_by_name(name)
  code, _type, vendor = Internals::AVPNames.get(name)
  avp_by_code(code, vendor)
end

#create_answer(result_code, opts = {}) ⇒ Diameter::Message

Generates an answer to this request, filling in a Result-Code or Experimental-Result AVP.

Parameters:

  • result_code (Fixnum)

    The value for the Result-Code AVP

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • experimental_result_vendor (Fixnum)

    If given, creates an Experimental-Result AVP with this vendor instead of the Result-Code AVP.

  • copying_avps (Array<String>)

    A list of AVP names to copy from the request to the answer.

  • avps (Array<Diameter::AVP>)

    A list of AVP objects to add on the answer.

Returns:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/diameter/message.rb', line 233

def create_answer(result_code, opts={})
  fail "Cannot answer an answer" if answer
  
  avps = []
  avps << avp_by_name("Session-Id") unless avp_by_name("Session-Id").nil?
  avps += opts.fetch(:avps, [])
  avps << if opts[:experimental_result_vendor]
            AVP.create("Experimental-Result",
                       [AVP.create("Experimental-Result-Code", result_code),
                        AVP.create("Vendor-Id", opts[:experimental_result_vendor])])
          else
            AVP.create("Result-Code", result_code)
          end
  
  avps += opts.fetch(:copying_avps, []).collect do |name|
    src_avp = avp_by_name(name)

    fail if src_avp.nil?
    
    src_avp.dup
  end

  Message.new(version: version, command_code: command_code, app_id: app_id, hbh: hbh, ete: ete, request: false, proxyable: @proxyable, retransmitted: false, error: false, avps: avps)
end

#has_avp?(name) ⇒ true, false

Does this message contain a (top-level) AVP with this name?

Parameters:

Returns:

  • (true, false)


169
170
171
# File 'lib/diameter/message.rb', line 169

def has_avp?(name)
  !!avp(name)
end

#to_sString

Represents this message (and all its AVPs) in human-readable string form.

Returns:

  • (String)

See Also:

  • for how the AVPs are represented.


68
69
70
# File 'lib/diameter/message.rb', line 68

def to_s
  "#{@command_code}: #{@avps.collect(&:to_s)}"
end

#to_wireString

Serializes a Diameter message (header plus AVPs) into the series of bytes representing it on the wire.

Returns:

  • (String)

    The byte-encoded form.



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/diameter/message.rb', line 76

def to_wire
  content = ''
  @avps.each { |a| content += a.to_wire }
  length_8, length_16 = Internals::UInt24.to_u8_and_u16(content.length + 20)
  code_8, code_16 = Internals::UInt24.to_u8_and_u16(@command_code)
  request_flag = @request ? '1' : '0'
  proxy_flag = @proxyable ? '1' : '0'
  flags_str = "#{request_flag}#{proxy_flag}000000"

  header = [@version, length_8, length_16, flags_str, code_8, code_16, @app_id, @hbh, @ete].pack('CCnB8CnNNN')
  header + content
end