class Diameter::Message
A Diameter
message.
@!attribute [r] version
The Diameter protocol version (currently always 1)
@!attribute [r] command_code
The Diameter Command-Code of this messsage.
@!attribute [r] app_id
The Diameter application ID of this message, or 0 for base protocol messages.
@!attribute [r] hbh
The hop-by-hop identifier of this message.
@!attribute [r] ete
The end-to-end identifier of this message.
@!attribute [r] request
Whether this message is a request.
@!attribute [r] answer
Whether this message is an answer.
Attributes
Public Class Methods
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.
# 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
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
# 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
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.
# 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
Private Class Methods
# File lib/diameter/message.rb, line 265 def self.next_ete @ete ||= (Time.now.to_i & 0x00000fff) + (rand(2**32) & 0xfffff000) @ete += 1 end
# File lib/diameter/message.rb, line 259 def self.next_hbh @hbh ||= rand(10000) @hbh += 1 @hbh end
Public Instance Methods
@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.
# 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
@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>]
# 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
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>]
# 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
@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
# 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
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
@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
# 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
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.
# 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
Represents this message (and all its AVPs) in human-readable string form.
@see AVP::to_s for how the AVPs are represented. @return [String]
# File lib/diameter/message.rb, line 68 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.
# 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