Class: Diameter::Message
- Inherits:
-
Object
- Object
- Diameter::Message
- Includes:
- Internals
- Defined in:
- lib/diameter/message.rb
Overview
A Diameter message.
Instance Attribute Summary collapse
-
#answer ⇒ Object
readonly
Whether this message is an answer.
-
#app_id ⇒ Object
readonly
The Diameter application ID of this message, or 0 for base protocol messages.
-
#command_code ⇒ Object
readonly
The Diameter Command-Code of this messsage.
-
#ete ⇒ Object
readonly
The end-to-end identifier of this message.
-
#hbh ⇒ Object
readonly
The hop-by-hop identifier of this message.
-
#request ⇒ Object
readonly
Whether this message is a request.
-
#version ⇒ Object
readonly
The Diameter protocol version (currently always 1).
AVP retrieval collapse
-
#add_origin_host_and_realm(host, realm) ⇒ Object
Not recommended for normal use - all AVPs should be given to the constructor.
-
#all_avps_by_code(code, vendor = 0) ⇒ Array<AVP>
Prefer AVP.define and the by-name versions to this.
-
#all_avps_by_name(name) ⇒ Array<AVP>
(also: #avps)
Returns all AVPs with the given name.
-
#avp_by_code(code, vendor = 0) ⇒ AVP?
Prefer AVP.define and the by-name versions to this.
-
#avp_by_name(name) ⇒ AVP?
(also: #avp, #[])
Returns the first AVP with the given name.
-
#has_avp?(name) ⇒ true, false
Does this message contain a (top-level) AVP with this name?.
Parsing collapse
-
.from_bytes(bytes) ⇒ DiameterMessage
Parses a byte representation (a 20-byte header plus AVPs) into a DiameterMessage object.
-
.length_from_header(header) ⇒ Fixnum
Parses the first four bytes of the Diameter header to learn the length.
Instance Method Summary collapse
-
#create_answer(result_code, opts = {}) ⇒ Diameter::Message
Generates an answer to this request, filling in a Result-Code or Experimental-Result AVP.
-
#initialize(options = {}) ⇒ Message
constructor
Creates a new Diameter message.
-
#to_s ⇒ String
Represents this message (and all its AVPs) in human-readable string form.
-
#to_wire ⇒ String
Serializes a Diameter message (header plus AVPs) into the series of bytes representing it on the wire.
Constructor Details
#initialize(options = {}) ⇒ Message
Creates a new Diameter message.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/diameter/message.rb', line 46 def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @retransmitted = false @error = false @avps = [:avps] || [] end |
Instance Attribute Details
#answer ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#app_id ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#command_code ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#ete ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#hbh ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#request ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
#version ⇒ Object (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 |
# 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. # # @option opts [Fixnum] command_code # The Diameter Command-Code of this messsage. # @option opts [Fixnum] app_id # The Diameter application ID of this message, or 0 for base # protocol messages. # @option opts [Fixnum] hbh # The hop-by-hop identifier of this message. # @option opts [Fixnum] ete # The end-to-end identifier of this message. # @option opts [true, false] request # Whether this message is a request. Defaults to true. # @option opts [true, false] proxyable # Whether this message can be forwarded on. Defaults to true. # @option opts [true, false] error # Whether this message is a Diameter protocol error. Defaults to false. # @option opts [Array<AVP>] avps # The list of AVPs to include on this message. def initialize( = {}) @version = 1 @command_code = [:command_code] @app_id = [:app_id] @hbh = [:hbh] || Message.next_hbh @ete = [:ete] || Message.next_ete @request = .fetch(:request, true) @answer = !@request @proxyable = .fetch(:proxyable, false) @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] 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 = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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 @ete end end |
Class Method Details
.from_bytes(bytes) ⇒ DiameterMessage
Parses a byte representation (a 20-byte header plus AVPs) into a DiameterMessage object.
206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/diameter/message.rb', line 206 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.
195 196 197 198 |
# File 'lib/diameter/message.rb', line 195 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.
180 181 182 183 |
# File 'lib/diameter/message.rb', line 180 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.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/diameter/message.rb', line 151 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.
112 113 114 115 |
# File 'lib/diameter/message.rb', line 112 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.
132 133 134 135 136 137 138 139 |
# File 'lib/diameter/message.rb', line 132 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
100 101 102 103 |
# File 'lib/diameter/message.rb', line 100 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.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/diameter/message.rb', line 232 def create_answer(result_code, opts={}) fail "Cannot answer an answer" if answer avps = opts.fetch(:avps, []) avps << if opts[:experimental_result_vendor] fail 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?
168 169 170 |
# File 'lib/diameter/message.rb', line 168 def has_avp?(name) !!avp(name) end |
#to_s ⇒ String
Represents this message (and all its AVPs) in human-readable string form.
67 68 69 |
# File 'lib/diameter/message.rb', line 67 def to_s "#{@command_code}: #{@avps.collect(&:to_s)}" end |
#to_wire ⇒ String
Serializes a Diameter message (header plus AVPs) into the series of bytes representing it on the wire.
75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/diameter/message.rb', line 75 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 |