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.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/diameter/message.rb', line 47 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 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( = {}) @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 = [] 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_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 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( = {}) @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 = [] 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_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 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( = {}) @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 = [] 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 |
#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 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( = {}) @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 = [] 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 |
#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 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( = {}) @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 = [] 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 |
#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 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( = {}) @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 = [] 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 |
#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 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( = {}) @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 = [] 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.
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.
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.
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.
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.
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.
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
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.
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?
169 170 171 |
# File 'lib/diameter/message.rb', line 169 def has_avp?(name) !!avp(name) end |
#to_s ⇒ String
Represents this message (and all its AVPs) in human-readable string form.
68 69 70 |
# File 'lib/diameter/message.rb', line 68 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.
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 |