Class: DiameterMessage

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

Overview

A Diameter message.

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (DiameterMessage) initialize(options = {})

Returns a new instance of DiameterMessage



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/diameter/message.rb', line 12

def initialize(options={})
  @version = options[:version] || 1
  @command_code = options[:command_code]
  @avps = options[:avps] || []
  @app_id = options[:app_id]
  @hbh = options[:hbh]
  @ete = options[:ete]

  @request = options[:request] || false
  @proxyable = options[:proxyable] || false
  @retransmitted = false
  @error = false
end

Instance Attribute Details

- (Object) app_id (readonly)

Returns the value of attribute app_id



9
10
11
# File 'lib/diameter/message.rb', line 9

def app_id
  @app_id
end

- (Object) avps

Returns the value of attribute avps



10
11
12
# File 'lib/diameter/message.rb', line 10

def avps
  @avps
end

- (Object) command_code (readonly)

Returns the value of attribute command_code



9
10
11
# File 'lib/diameter/message.rb', line 9

def command_code
  @command_code
end

- (Object) ete (readonly)

Returns the value of attribute ete



9
10
11
# File 'lib/diameter/message.rb', line 9

def ete
  @ete
end

- (Object) hbh (readonly)

Returns the value of attribute hbh



9
10
11
# File 'lib/diameter/message.rb', line 9

def hbh
  @hbh
end

- (Object) request (readonly)

Returns the value of attribute request



9
10
11
# File 'lib/diameter/message.rb', line 9

def request
  @request
end

- (Object) version (readonly)

The Diameter protocol version (currenmtly always 1)



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
# File 'lib/diameter/message.rb', line 8

class DiameterMessage
  attr_reader :version, :command_code, :app_id, :hbh, :ete, :request
  attr_accessor :avps
  
  def initialize(options={})
    @version = options[:version] || 1
    @command_code = options[:command_code]
    @avps = options[:avps] || []
    @app_id = options[:app_id]
    @hbh = options[:hbh]
    @ete = options[:ete]

    @request = options[:request] || false
    @proxyable = options[:proxyable] || false
    @retransmitted = false
    @error = false
  end

  # Returns true if this message represents a Diameter answer (i.e.
  # has the 'Request' bit in the header cleared).
  #
  # Always the opposite of {DiameterMessage#request}.
  #
  # @return [true, false]
  def answer
    !@request
  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{|a| a.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 = UInt24.to_u8_and_u16(content.length + 20)
    code_8, code_16 = 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

  # Returns the first AVP with the given name. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @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 = 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.
  #
  # @return [Array<AVP>]
  def all_avps_by_name(name)
    code, _type, vendor = AVPNames.get(name)
    all_avps_by_code(code, vendor)
  end

  # Returns the first AVP with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @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

  # Returns all AVPs with the given code and vendor. Only covers "top-level"
  # AVPs - it won't look inside Grouped AVPs.
  #
  # @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) and vendor_match
    end
  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
  def self.length_from_header(header)
    _version, length_8, length_16 = header.unpack('CCn')
    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 = UInt24.from_u8_and_u16(code_8, code_16)

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

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

  # Generates an answer to this request, filling in appropriate
  # fields per {http://tools.ietf.org/html/rfc6733#section-6.2}.
  #
  # @param origin_host [String] The Origin-Host to fill in on the
  #   response.
  # @return [DiameterMessage] The response created.
  def create_answer(origin_host=nil)
    # Is this a request?

    # Copy the Session-Id and Proxy-Info

    # Insert Origin-Host (should the stack do this?)

    # Don't require or insert a Result-Code - we might want
    # Experimental-Result-Code instead

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

Class Method Details

+ (DiameterMessage) from_bytes(bytes)

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:



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/diameter/message.rb', line 128

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 = UInt24.from_u8_and_u16(code_8, code_16)

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

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

+ (Fixnum) length_from_header(header)

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



117
118
119
120
# File 'lib/diameter/message.rb', line 117

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

Instance Method Details

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

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

Returns:



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/diameter/message.rb', line 99

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) and vendor_match
  end
end

- (Array<AVP>) all_avps_by_name(name)

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

Returns:



76
77
78
79
# File 'lib/diameter/message.rb', line 76

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

- (true, false) answer

Returns true if this message represents a Diameter answer (i.e. has the 'Request' bit in the header cleared).

Always the opposite of #request.

Returns:

  • (true, false)


32
33
34
# File 'lib/diameter/message.rb', line 32

def answer
  !@request
end

- (AVP?) avp_by_code(code, vendor = 0)

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

Returns:

  • (AVP)

    if there is an AVP with that code/vendor

  • (nil)

    if there is not an AVP with that code/vendor



86
87
88
89
90
91
92
93
# File 'lib/diameter/message.rb', line 86

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

- (AVP?) avp_by_name(name)

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

Returns:

  • (AVP)

    if there is an AVP with that name

  • (nil)

    if there is not an AVP with that name



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

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

- (DiameterMessage) create_answer(origin_host = nil)

Generates an answer to this request, filling in appropriate fields per http://tools.ietf.org/html/rfc6733#section-6.2.

Parameters:

  • origin_host (String) (defaults to: nil)

    The Origin-Host to fill in on the response.

Returns:



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/diameter/message.rb', line 146

def create_answer(origin_host=nil)
  # Is this a request?

  # Copy the Session-Id and Proxy-Info

  # Insert Origin-Host (should the stack do this?)

  # Don't require or insert a Result-Code - we might want
  # Experimental-Result-Code instead

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

- (String) to_s

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

Returns:

  • (String)

See Also:

  • for how the AVPs are represented.


41
42
43
# File 'lib/diameter/message.rb', line 41

def to_s
  "#{@command_code}: #{@avps.collect{|a| a.to_s }}"
end

- (String) to_wire

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

Returns:

  • (String)

    The byte-encoded form.



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

def to_wire
  content = ""
  @avps.each {|a| content += a.to_wire}
  length_8, length_16 = UInt24.to_u8_and_u16(content.length + 20)
  code_8, code_16 = 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