class SippyCup::Scenario

A representation of a SippyCup scenario from a manifest or created in code. Allows building a scenario from a set of basic primitives, and then exporting to SIPp scenario files, including the XML scenario and PCAP audio.

Constants

DEFAULT_RETRANS
MSEC
USER_AGENT
VALID_DTMF

Attributes

errors[R]

@return [Array<Hash>] a collection of errors encountered while building the scenario.

scenario_options[R]

@return [Hash] The options the scenario was created with, either from a manifest or passed as overrides

Public Class Methods

from_manifest(manifest, options = {}) click to toggle source

Build a scenario based on either a manifest string or a file handle. Manifests are supplied in YAML format. All manifest keys can be overridden by passing in a Hash of corresponding values.

@param [String, File] manifest The YAML manifest @param [Hash] options Options to override (see initialize) @option options [String] :input_filename The name of the input file if there is one. Used as a preferable fallback if no name is included in the manifest.

@return [SippyCup::Scenario]

@example Parse a manifest string

manifest = <<-MANIFEST
  source: 192.168.1.1
  destination: 192.168.1.2
  steps:
    - invite
    - wait_for_answer
    - ack_answer
    - sleep 3
    - wait_for_hangup
  MANIFEST
Scenario.from_manifest(manifest)

@example Parse a manifest file by path

File.open("/my/manifest.yml") { |f| Scenario.from_manifest(f) }
# or
Scenario.from_manifest(File.read("/my/manifest.yml"))

@example Override keys from the manifest

Scenario.from_manifest(manifest, source: '192.168.12.1')
# File lib/sippy_cup/scenario.rb, line 49
def self.from_manifest(manifest, options = {})
  args = ActiveSupport::HashWithIndifferentAccess.new(Psych.safe_load(manifest)).merge options

  input_name = options.has_key?(:input_filename) ? File.basename(options[:input_filename]).gsub(/\.ya?ml/, '') : nil
  name = args.delete(:name) || input_name || 'My Scenario'

  scenario = if args[:scenario]
    media = args.has_key?(:media) ? File.read(args[:media], mode: 'rb') : nil
    SippyCup::XMLScenario.new name, File.read(args[:scenario]), media, args
  else
    steps = args.delete :steps
    scenario = Scenario.new name, args
    scenario.build steps
    scenario
  end

  scenario
end
new(name, args = {}, &block) click to toggle source

Create a scenario instance

@param [String] name The scenario's name @param [Hash] args options to customise the scenario @option options [String] :name The name of the scenario, used for the XML scenario and for determining the compiled filenames. Defaults to 'My Scenario'. @option options [String] :filename The name of the files to be saved to disk. @option options [String] :source The source IP/hostname with which to invoke SIPp. @option options [String, Numeric] :source_port The source port to bind SIPp to (defaults to 8836). @option options [String] :destination The target system at which to direct traffic. @option options [String] :advertise_address The IP address to advertise in SIP and SDP if different from the bind IP (defaults to the bind IP). @option options [String] :from_user The SIP user from which traffic should appear. @option options [String] :to_user The SIP user to send requests to. Alias for `:to` and deprecated in favour of the same. @option options [String] :to The SIP user / address to send requests to. @option options [Integer] :media_port The RTCP (media) port to bind to locally. @option options [String, Numeric] :max_concurrent The maximum number of concurrent calls to execute. @option options [String, Numeric] :number_of_calls The maximum number of calls to execute in the test run. @option options [String, Numeric] :calls_per_second The rate at which to initiate calls. @option options [String] :stats_file The path at which to dump statistics. @option options [String, Numeric] :stats_interval The interval (in seconds) at which to dump statistics (defaults to 1s). @option options [String] :transport_mode The transport mode over which to direct SIP traffic. @option options [String] :dtmf_mode The output DTMF mode, either rfc2833 (default) or info. @option options [String] :scenario_variables A path to a CSV file of variables to be interpolated with the scenario at runtime. @option options [Hash] :options A collection of options to pass through to SIPp, as key-value pairs. In cases of value-less options (eg -trace_err), specify a nil value. @option options [Array<String>] :steps A collection of steps

@yield [scenario] Builder block to construct scenario @yieldparam [Scenario] scenario the initialized scenario instance

# File lib/sippy_cup/scenario.rb, line 103
def initialize(name, args = {}, &block)
  parse_args args

  @scenario_options = args.merge name: name
  @filename = args[:filename] || name.downcase.gsub(/\W+/, '_')
  @filename = File.expand_path @filename, Dir.pwd
  @media = nil
  @message_variables = 0
  # Reference variables don't generate warnings/errors if unused in the scenario
  @reference_variables = Set.new
  @media_nodes = []
  @errors = []
  @adv_ip = args[:advertise_address] || "[local_ip]"

  instance_eval &block if block_given?
end

Public Instance Methods

ack_answer(opts = {}) click to toggle source

Acknowledge a received answer message and start media playback

@param [Hash] opts A set of options to modify the message parameters

# File lib/sippy_cup/scenario.rb, line 445
    def ack_answer(opts = {})
      msg = <<-BODY

ACK [next_url] SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: "#{@from_user}" <sip:#{@from_user}@#{@adv_ip}:[local_port]>;tag=[call_number]
To: <sip:#{to_addr}>[peer_tag_param]
Call-ID: [call_id]
CSeq: [cseq] ACK
Contact: <sip:[$local_addr];transport=[transport]>
Max-Forwards: 100
User-Agent: #{USER_AGENT}
Content-Length: 0
[routes]
      BODY
      send msg, opts
      start_media
    end
ack_bye(opts = {})
Alias for: okay
answer(opts = {}) click to toggle source

Helper method to answer an INVITE and expect the ACK

@param [Hash] opts A set of options containing SIPp element attributes - will be passed to both the <send> and <recv> elements

# File lib/sippy_cup/scenario.rb, line 344
def answer(opts = {})
  send_answer opts
  receive_ack opts
end
build(steps) click to toggle source

Build the scenario steps provided

@param [Array<String>] steps A collection of steps to build the scenario

# File lib/sippy_cup/scenario.rb, line 130
def build(steps)
  raise ArgumentError, "Must provide scenario steps" unless steps
  steps.each_with_index do |step, index|
    begin
      instruction, args = step.split ' ', 2
      args = split_quoted_string args
      if args && !args.empty?
        self.__send__ instruction, *args
      else
        self.__send__ instruction
      end
    rescue => e
      @errors << {step: index + 1, message: "#{step}: #{e.message}"}
    end
  end
end
call_length_repartition(min, max, interval) click to toggle source

Create partition table for Call Length

@param [Integer] min An value specifying the minimum time in milliseconds for the table @param [Integer] max An value specifying the maximum time in milliseconds for the table @param [Integer] interval An value specifying the interval in milliseconds for the table

# File lib/sippy_cup/scenario.rb, line 636
def call_length_repartition(min, max, interval)
  partition_table 'CallLengthRepartition', min.to_i, max.to_i, interval.to_i
end
compile!() click to toggle source

Compile the scenario and its media to disk

Writes the SIPp scenario file to disk at {filename}.xml, and the PCAP media to {filename}.pcap if applicable. {filename} is taken from the :filename option when creating the scenario, or falls back to a down-snake-cased version of the scenario name.

@return [String] the path to the resulting scenario file

@example Export a scenario to a specified filename

scenario = Scenario.new 'Test Scenario', filename: 'my_scenario'
scenario.compile! # Leaves files at my_scenario.xml and my_scenario.pcap

@example Export a scenario to a calculated filename

scenario = Scenario.new 'Test Scenario'
scenario.compile! # Leaves files at test_scenario.xml and test_scenario.pcap
# File lib/sippy_cup/scenario.rb, line 696
def compile!
  unless @media.nil?
    print "Compiling media to #{@filename}.pcap..."
    compile_media.to_file filename: "#{@filename}.pcap"
    puts "done."
  end

  scenario_filename = "#{@filename}.xml"
  print "Compiling scenario to #{scenario_filename}..."
  File.open scenario_filename, 'w' do |file|
    file.write to_xml(:pcap_path => "#{@filename}.pcap")
  end
  puts "done."

  scenario_filename
end
hangup(opts = {}) click to toggle source

Shortcut to send a BYE and wait for the acknowledgement

@param [Hash] opts A set of options containing SIPp <recv> element attributes - will be passed to both the <send> and <recv> elements

# File lib/sippy_cup/scenario.rb, line 626
def hangup(opts = {})
  send_bye opts
  receive_ok opts
end
invite(opts = {}) click to toggle source

Send an invite message

@param [Hash] opts A set of options to modify the message @option opts [Integer] :retrans @option opts [String] :headers Extra headers to place into the INVITE

# File lib/sippy_cup/scenario.rb, line 154
    def invite(opts = {})
      opts[:retrans] ||= 500
      # FIXME: The DTMF mapping (101) is hard-coded. It would be better if we could
      # get this from the DTMF payload generator
      from_addr = "#{@from_user}@#{@adv_ip}:[local_port]"
      msg = <<-MSG

INVITE sip:#{to_addr} SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: "#{@from_user}" <sip:#{from_addr}>;tag=[call_number]
To: <sip:#{to_addr}>
Call-ID: [call_id]
CSeq: [cseq] INVITE
Contact: <sip:#{from_addr};transport=[transport]>
Max-Forwards: 100
User-Agent: #{USER_AGENT}
Content-Type: application/sdp
Content-Length: [len]
#{opts.has_key?(:headers) ? opts.delete(:headers).sub(/\n*\Z/, "\n") : ''}
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] #{@adv_ip}
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
      MSG
      send msg, opts do |send|
        send << doc.create_element('action') do |action|
          action << doc.create_element('assignstr') do |assignstr|
            assignstr['assign_to'] = "remote_addr"
            assignstr['value']     = to_addr
          end
          action << doc.create_element('assignstr') do |assignstr|
            assignstr['assign_to'] = "local_addr"
            assignstr['value']     = from_addr
          end
          action << doc.create_element('assignstr') do |assignstr|
            assignstr['assign_to'] = "call_addr"
            assignstr['value']     = to_addr
          end
        end
      end
      # These variables will only be used if we initiate a hangup
      @reference_variables += %w(remote_addr local_addr call_addr)
    end
okay(opts = {}) click to toggle source

Acknowledge the last request

@param [Hash] opts A set of options to modify the message parameters

# File lib/sippy_cup/scenario.rb, line 592
    def okay(opts = {})
      msg = <<-ACK

SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[$local_addr];transport=[transport]>
Max-Forwards: 100
User-Agent: #{USER_AGENT}
Content-Length: 0
[routes]
      ACK
      send msg, opts
    end
Also aliased as: ack_bye
receive_100(opts = {})
Alias for: receive_trying
receive_180(opts = {})
Alias for: receive_ringing
receive_183(opts = {})
Alias for: receive_progress
receive_200(opts = {}, &block)
Alias for: receive_ok
receive_ack(opts = {}) click to toggle source
# File lib/sippy_cup/scenario.rb, line 349
def receive_ack(opts = {})
  recv opts.merge request: 'ACK'
end
receive_answer(opts = {}) click to toggle source

Sets an expectation for a SIP 200 message from the remote party as well as storing the record set and the response time duration

@param [Hash] opts A set of options to modify the expectation @option opts [true, false] :optional Whether or not receipt of the message is optional. Defaults to false.

# File lib/sippy_cup/scenario.rb, line 393
def receive_answer(opts = {})
  options = {
    rrs: true, # Record Record Set: Make the Route headers available via [routes] later
    rtd: true # Response Time Duration: Record the response time
  }

  receive_200(options.merge(opts)) do |recv|
    recv << doc.create_element('action') do |action|
      action << doc.create_element('ereg') do |ereg|
        ereg['regexp'] = '<sip:(.*)>.*;tag=([^;]*)'
        ereg['search_in'] = 'hdr'
        ereg['header'] = 'To:'
        ereg['assign_to'] = 'dummy,remote_addr,remote_tag'
      end
    end
  end
  # These variables will only be used if we initiate a hangup
  @reference_variables += %w(dummy remote_addr remote_tag)
end
receive_bye(opts = {}) click to toggle source

Expect to receive a BYE message

@param [Hash] opts A set of options to modify the expectation

# File lib/sippy_cup/scenario.rb, line 583
def receive_bye(opts = {})
  recv opts.merge request: 'BYE'
end
receive_invite(opts = {}) click to toggle source

Expect to receive a SIP INVITE

@param [Hash] opts A set of options containing SIPp <recv> element attributes

# File lib/sippy_cup/scenario.rb, line 235
def receive_invite(opts = {})
  recv(opts.merge(request: 'INVITE', rrs: true)) do |recv|
    action = doc.create_element('action') do |action|
      action << doc.create_element('ereg') do |ereg|
        ereg['regexp'] = '<sip:(.*)>.*;tag=([^;]*)'
        ereg['search_in'] = 'hdr'
        ereg['header'] = 'From:'
        ereg['assign_to'] = 'dummy,remote_addr,remote_tag'
      end
      action << doc.create_element('ereg') do |ereg|
        ereg['regexp'] = '<sip:(.*)>'
        ereg['search_in'] = 'hdr'
        ereg['header'] = 'To:'
        ereg['assign_to'] = 'dummy,local_addr'
      end
      action << doc.create_element('assignstr') do |assignstr|
        assignstr['assign_to'] = "call_addr"
        assignstr['value']     = "[$local_addr]"
      end
    end
    recv << action
  end
  # These variables (except dummy) will only be used if we initiate a hangup
  @reference_variables += %w(dummy remote_addr remote_tag local_addr call_addr)
end
Also aliased as: wait_for_call
receive_message(regexp = nil) click to toggle source

Expect to receive a MESSAGE message

@param [String] regexp A regular expression (as a String) to match the message body against

# File lib/sippy_cup/scenario.rb, line 531
def receive_message(regexp = nil)
  recv = Nokogiri::XML::Node.new 'recv', doc
  recv['request'] = 'MESSAGE'
  scenario_node << recv

  if regexp
    action = Nokogiri::XML::Node.new 'action', doc
    ereg = Nokogiri::XML::Node.new 'ereg', doc

    ereg['regexp'] = regexp
    ereg['search_in'] = 'body'
    ereg['check_it'] = true

    var = "message_#{@message_variables += 1}"
    ereg['assign_to'] = var
    @reference_variables << var

    action << ereg
    recv << action
  end

  okay
end
receive_ok(opts = {}, &block) click to toggle source

Sets an expectation for a SIP 200 message from the remote party

@param [Hash] opts A set of options to modify the expectation @option opts [true, false] :optional Whether or not receipt of the message is optional. Defaults to false.

# File lib/sippy_cup/scenario.rb, line 419
def receive_ok(opts = {}, &block)
  recv({ response: 200 }.merge(opts), &block)
end
Also aliased as: receive_200
receive_progress(opts = {}) click to toggle source

Sets an expectation for a SIP 183 message from the remote party

@param [Hash] opts A set of options to modify the expectation @option opts [true, false] :optional Whether or not receipt of the message is optional. Defaults to true.

# File lib/sippy_cup/scenario.rb, line 381
def receive_progress(opts = {})
  handle_response 183, opts
end
Also aliased as: receive_183
receive_ringing(opts = {}) click to toggle source

Sets an expectation for a SIP 180 message from the remote party

@param [Hash] opts A set of options to modify the expectation @option opts [true, false] :optional Whether or not receipt of the message is optional. Defaults to true.

# File lib/sippy_cup/scenario.rb, line 370
def receive_ringing(opts = {})
  handle_response 180, opts
end
Also aliased as: receive_180
receive_trying(opts = {}) click to toggle source

Sets an expectation for a SIP 100 message from the remote party

@param [Hash] opts A set of options to modify the expectation @option opts [true, false] :optional Whether or not receipt of the message is optional. Defaults to true.

# File lib/sippy_cup/scenario.rb, line 359
def receive_trying(opts = {})
  handle_response 100, opts
end
Also aliased as: receive_100
register(user, password = nil, opts = {}) click to toggle source

Send a REGISTER message with the specified credentials

@param [String] user the user to register as. May be given as a full SIP URI (sip:user@domain.com), in email-address format (user@domain.com) or as a simple username ('user'). If no domain is supplied, the source IP from SIPp will be used. @param [optional, String, nil] password the password to authenticate with. @param [Hash] opts A set of options to modify the message

@example Register with authentication

s.register 'frank@there.com', 'abc123'

@example Register without authentication or a domain

s.register 'frank'
# File lib/sippy_cup/scenario.rb, line 216
def register(user, password = nil, opts = {})
  send_opts = opts.dup
  send_opts[:retrans] ||= DEFAULT_RETRANS
  user, domain = parse_user user
  if password
    send register_message(domain, user), send_opts
    recv opts.merge(response: 401, auth: true, optional: false)
    send register_auth(domain, user, password), send_opts
    receive_ok opts.merge(optional: false)
  else
    send register_message(domain, user), send_opts
  end
end
response_time_repartition(min, max, interval) click to toggle source

Create partition table for Response Time

@param [Integer] min An value specifying the minimum time in milliseconds for the table @param [Integer] max An value specifying the maximum time in milliseconds for the table @param [Integer] interval An value specifying the interval in milliseconds for the table

# File lib/sippy_cup/scenario.rb, line 645
def response_time_repartition(min, max, interval)
  partition_table 'ResponseTimeRepartition', min.to_i, max.to_i, interval.to_i
end
send_100(opts = {})
Alias for: send_trying
send_180(opts = {})
Alias for: send_ringing
send_answer(opts = {}) click to toggle source

Answer an incoming call

@param [Hash] opts A set of options containing SIPp <send> element attributes

# File lib/sippy_cup/scenario.rb, line 311
    def send_answer(opts = {})
      opts[:retrans] ||= DEFAULT_RETRANS
      msg = <<-MSG

SIP/2.0 200 Ok
[last_Via:]
From: <sip:[$remote_addr]>;tag=[$remote_tag]
To: <sip:[$local_addr]>;tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Server: #{USER_AGENT}
Contact: <sip:[$local_addr];transport=[transport]>
Content-Type: application/sdp
[routes]
Content-Length: [len]

v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] #{@adv_ip}
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
      MSG
      start_media
      send msg, opts
    end
send_bye(opts = {}) click to toggle source

Send a BYE message

@param [Hash] opts A set of options to modify the message parameters

# File lib/sippy_cup/scenario.rb, line 560
    def send_bye(opts = {})
      msg = <<-MSG

BYE sip:[$call_addr] SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: <sip:[$local_addr]>;tag=[call_number]
To: <sip:[$remote_addr]>;tag=[$remote_tag]
Contact: <sip:[$local_addr];transport=[transport]>
Call-ID: [call_id]
CSeq: [cseq] BYE
Max-Forwards: 100
User-Agent: #{USER_AGENT}
Content-Length: 0
[routes]
      MSG
      send msg, opts
    end
send_digits(digits) click to toggle source

Send DTMF digits

@param [String] DTMF digits to send. Must be 0-9, *, # or A-D

@example Send a single DTMF digit

send_digits '1'

@example Enter a pin number

send_digits '1234'
# File lib/sippy_cup/scenario.rb, line 486
    def send_digits(digits)
      raise "Media not started" unless @media
      delay = (0.250 * MSEC).to_i # FIXME: Need to pass this down to the media layer
      digits.split('').each do |digit|
        raise ArgumentError, "Invalid DTMF digit requested: #{digit}" unless VALID_DTMF.include? digit

        case @dtmf_mode
        when :rfc2833
          @media << "dtmf:#{digit}"
          @media << "silence:#{delay}"
        when :info
          info = <<-INFO

INFO [next_url] SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: "#{@from_user}" <sip:#{@from_user}@#{@adv_ip}:[local_port]>;tag=[call_number]
To: <sip:#{to_addr}>[peer_tag_param]
Call-ID: [call_id]
CSeq: [cseq] INFO
Contact: <sip:[$local_addr];transport=[transport]>
Max-Forwards: 100
User-Agent: #{USER_AGENT}
[routes]
Content-Length: [len]
Content-Type: application/dtmf-relay

Signal=#{digit}
Duration=#{delay}
          INFO
          send info
          recv response: 200
          pause delay
        end
      end

      if @dtmf_mode == :rfc2833
        pause delay * 2 * digits.size
      end
    end
send_ringing(opts = {}) click to toggle source

Send a “180 Ringing” response

@param [Hash] opts A set of options containing SIPp <recv> element attributes

# File lib/sippy_cup/scenario.rb, line 289
    def send_ringing(opts = {})
      msg = <<-MSG

SIP/2.0 180 Ringing
[last_Via:]
From: <sip:[$remote_addr]>;tag=[$remote_tag]
To: <sip:[$local_addr]>;tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Server: #{USER_AGENT}
Contact: <sip:[$local_addr];transport=[transport]>
Content-Length: 0
      MSG
      send msg, opts
    end
Also aliased as: send_180
send_trying(opts = {}) click to toggle source

Send a “100 Trying” response

@param [Hash] opts A set of options containing SIPp <recv> element attributes

# File lib/sippy_cup/scenario.rb, line 267
    def send_trying(opts = {})
      msg = <<-MSG

SIP/2.0 100 Trying
[last_Via:]
From: <sip:[$remote_addr]>;tag=[$remote_tag]
To: <sip:[$local_addr]>;tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Server: #{USER_AGENT}
Contact: <sip:[$local_addr];transport=[transport]>
Content-Length: 0
      MSG
      send msg, opts
    end
Also aliased as: send_100
sleep(seconds) click to toggle source

Insert a pause into the scenario and its media of the specified duration

@param [Numeric] seconds The duration of the pause in seconds

# File lib/sippy_cup/scenario.rb, line 469
def sleep(seconds)
  milliseconds = (seconds.to_f * MSEC).to_i
  pause milliseconds
  @media << "silence:#{milliseconds}" if @media
end
to_tmpfiles() click to toggle source

Write compiled Scenario XML and PCAP media (if applicable) to tempfiles.

These will automatically be closed and deleted once they have gone out of scope, and can be used to execute the scenario without leaving stuff behind.

@return [Hash<Symbol => Tempfile>] handles to created Tempfiles at :scenario and :media

@see www.ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html

# File lib/sippy_cup/scenario.rb, line 722
def to_tmpfiles
  unless @media.nil? || @media.empty?
    media_file = Tempfile.new 'media'
    media_file.binmode
    media_file.write compile_media.to_s
    media_file.rewind
  end

  scenario_file = Tempfile.new 'scenario'
  scenario_file.write to_xml(:pcap_path => media_file.try(:path))
  scenario_file.rewind

  {scenario: scenario_file, media: media_file}
end
to_xml(options = {}) click to toggle source

Dump the scenario to a SIPp XML string

@return [String] the SIPp XML scenario

# File lib/sippy_cup/scenario.rb, line 653
def to_xml(options = {})
  pcap_path = options[:pcap_path]
  docdup = doc.dup

  # Not removing in reverse would most likely remove the wrong
  # nodes because of changing indices.
  @media_nodes.reverse.each do |nop|
    nopdup = docdup.xpath(nop.path)

    if pcap_path.nil? or @media.empty?
      nopdup.remove
    else
      exec = nopdup.xpath("./action/exec").first
      exec['play_pcap_audio'] = pcap_path
    end
  end

  unless @reference_variables.empty?
    scenario_node = docdup.xpath('scenario').first
    scenario_node << docdup.create_element('Reference') do |ref|
      ref[:variables] = @reference_variables.to_a.join ','
    end
  end

  docdup.to_xml
end
valid?() click to toggle source

@return [true, false] the validity of the scenario. Will be false if errors were encountered while building the scenario from a manifest

# File lib/sippy_cup/scenario.rb, line 121
def valid?
  @errors.size.zero?
end
wait_for_answer(opts = {}) click to toggle source

Convenience method to wait for an answer from the called party

This sets expectations for optional SIP 100, 180 and 183, followed by a required 200 and sending the acknowledgement.

@param [Hash] opts A set of options to modify the expectations

# File lib/sippy_cup/scenario.rb, line 432
def wait_for_answer(opts = {})
  receive_trying opts
  receive_ringing opts
  receive_progress opts
  receive_answer opts
  ack_answer opts
end
wait_for_call(opts = {})
Alias for: receive_invite
wait_for_hangup(opts = {}) click to toggle source

Shortcut to set an expectation for a BYE and acknowledge it when received

@param [Hash] opts A set of options to modify the expectation

# File lib/sippy_cup/scenario.rb, line 616
def wait_for_hangup(opts = {})
  receive_bye(opts)
  ack_bye(opts)
end

Private Instance Methods

compile_media() click to toggle source
# File lib/sippy_cup/scenario.rb, line 790
def compile_media
  raise "Media not started" unless @media
  @media.compile!
end
doc() click to toggle source
# File lib/sippy_cup/scenario.rb, line 758
def doc
  @doc ||= begin
    Nokogiri::XML::Builder.new do |xml|
      xml.scenario name: @scenario_options[:name] do
        @scenario_node = xml.parent
      end
    end.doc
  end
end
handle_response(code, opts) click to toggle source
# File lib/sippy_cup/scenario.rb, line 875
def handle_response(code, opts)
  optional_recv opts.merge(response: code)
end
optional_recv(opts) click to toggle source
# File lib/sippy_cup/scenario.rb, line 870
def optional_recv(opts)
  opts[:optional] = true if opts[:optional].nil?
  recv opts
end
parse_args(args) click to toggle source
# File lib/sippy_cup/scenario.rb, line 773
def parse_args(args)
  if args[:dtmf_mode]
    @dtmf_mode = args[:dtmf_mode].to_sym
    raise ArgumentError, "dtmf_mode must be rfc2833 or info" unless [:rfc2833, :info].include?(@dtmf_mode)
  else
    @dtmf_mode = :rfc2833
  end

  @from_user = args[:from_user] || "sipp"

  args[:to] ||= args[:to_user] if args.has_key?(:to_user)
  if args[:to]
    @to_user, @to_domain = args[:to].to_s.split('@')
  end
  @to_domain ||= "[remote_ip]"
end
parse_user(user) click to toggle source

TODO: SIPS support?

# File lib/sippy_cup/scenario.rb, line 744
def parse_user(user)
  user.slice! 0, 4 if user =~ /sip:/
  user = user.split(":")[0]
  user, domain = user.split("@")
  domain ||= "[remote_ip]"
  [user, domain]
end
partition_table(name, min, max, interval) click to toggle source
# File lib/sippy_cup/scenario.rb, line 879
def partition_table(name, min, max, interval)
  range = Range.new(min, max).step interval
  partition_table = Nokogiri::XML::Node.new name, doc
  partition_table[:value] = range.inject{ |n,m| "#{n},#{m}"}
  scenario_node << partition_table
end
pause(msec) click to toggle source
# File lib/sippy_cup/scenario.rb, line 842
def pause(msec)
  pause = Nokogiri::XML::Node.new 'pause', doc
  pause['milliseconds'] = msec.to_i
  scenario_node << pause
end
recv(opts = {}) { |recv| ... } click to toggle source
# File lib/sippy_cup/scenario.rb, line 860
def recv(opts = {}, &block)
  raise ArgumentError, "Receive must include either a response or a request" unless opts.keys.include?(:response) || opts.keys.include?(:request)
  recv = Nokogiri::XML::Node.new 'recv', doc
  opts.each do |k,v|
    recv[k.to_s] = v
  end
  yield recv if block_given?
  scenario_node << recv
end
register_auth(domain, user, password) click to toggle source
# File lib/sippy_cup/scenario.rb, line 812
    def register_auth(domain, user, password)
      <<-AUTH

REGISTER sip:#{domain} SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: <sip:#{user}@#{domain}>;tag=[call_number]
To: <sip:#{user}@#{domain}>
Call-ID: [call_id]
CSeq: [cseq] REGISTER
Contact: <sip:#{@from_user}@#{@adv_ip}:[local_port];transport=[transport]>
Max-Forwards: 20
Expires: 3600
[authentication username=#{user} password=#{password}]
User-Agent: #{USER_AGENT}
Content-Length: 0
      AUTH
    end
register_message(domain, user) click to toggle source
# File lib/sippy_cup/scenario.rb, line 795
    def register_message(domain, user)
      <<-BODY

REGISTER sip:#{domain} SIP/2.0
Via: SIP/2.0/[transport] #{@adv_ip}:[local_port];branch=[branch]
From: <sip:#{user}@#{domain}>;tag=[call_number]
To: <sip:#{user}@#{domain}>
Call-ID: [call_id]
CSeq: [cseq] REGISTER
Contact: <sip:#{@from_user}@#{@adv_ip}:[local_port];transport=[transport]>
Max-Forwards: 10
Expires: 120
User-Agent: #{USER_AGENT}
Content-Length: 0
      BODY
    end
scenario_node() click to toggle source
# File lib/sippy_cup/scenario.rb, line 768
def scenario_node
  doc
  @scenario_node
end
send(msg, opts = {}) { |send| ... } click to toggle source
# File lib/sippy_cup/scenario.rb, line 848
def send(msg, opts = {})
  send = Nokogiri::XML::Node.new 'send', doc
  opts.each do |k,v|
    send[k.to_s] = v
  end
  send << "\n"
  send << Nokogiri::XML::CDATA.new(doc, msg)
  send << "\n" #Newlines are required before and after CDATA so SIPp will parse properly
  yield send if block_given?
  scenario_node << send
end
split_quoted_string(args) click to toggle source

Split a string into space-delimited components, optionally allowing quoted groups Example: cars “cats and dogs” fish 'hammers' => [“cars”, “cats and dogs”, “fish”, “hammers”]

# File lib/sippy_cup/scenario.rb, line 754
def split_quoted_string(args)
  args.to_s.scan(/'.+?'|".+?"|[^ ]+/).map { |s| s.gsub /^['"]|['"]$/, '' }
end
start_media() click to toggle source
# File lib/sippy_cup/scenario.rb, line 830
def start_media
  @media = Media.new '127.0.0.255', 55555, '127.255.255.255', 44444
  nop = doc.create_element('nop') { |nop|
    nop << doc.create_element('action') { |action|
      action << doc.create_element('exec')
    }
  }

  @media_nodes << nop
  scenario_node << nop
end
to_addr() click to toggle source
# File lib/sippy_cup/scenario.rb, line 739
def to_addr
  @to_addr ||= "[service]@#{@to_domain}:[remote_port]"
end