Class: SippyCup::Scenario

Inherits:
Object
  • Object
show all
Defined in:
lib/sippy_cup/scenario.rb

Overview

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.

Constant Summary collapse

USER_AGENT =
"SIPp/sippy_cup"
VALID_DTMF =
%w{0 1 2 3 4 5 6 7 8 9 0 * # A B C D}.freeze
MSEC =
1_000
DEFAULT_RETRANS =
500

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, args = {}) {|scenario| ... } ⇒ Scenario

Create a scenario instance

Parameters:

  • name (String)

    The scenario’s name

  • args (Hash) (defaults to: {})

    options to customise the scenario

  • options (Hash)

    a customizable set of options

Yields:

  • (scenario)

    Builder block to construct scenario

Yield Parameters:

  • scenario (Scenario)

    the initialized scenario instance



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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

Instance Attribute Details

#errorsArray<Hash> (readonly)

Returns a collection of errors encountered while building the scenario.

Returns:

  • (Array<Hash>)

    a collection of errors encountered while building the scenario.



72
73
74
# File 'lib/sippy_cup/scenario.rb', line 72

def errors
  @errors
end

#scenario_optionsHash (readonly)

Returns The options the scenario was created with, either from a manifest or passed as overrides.

Returns:

  • (Hash)

    The options the scenario was created with, either from a manifest or passed as overrides



69
70
71
# File 'lib/sippy_cup/scenario.rb', line 69

def scenario_options
  @scenario_options
end

Class Method Details

.from_manifest(manifest, options = {}) ⇒ SippyCup::Scenario

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.

Examples:

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)

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"))

Override keys from the manifest

Scenario.from_manifest(manifest, source: '192.168.12.1')

Parameters:

  • manifest (String, File)

    The YAML manifest

  • options (Hash) (defaults to: {})

    Options to override (see #initialize)

Options Hash (options):

  • :input_filename (String)

    The name of the input file if there is one. Used as a preferable fallback if no name is included in the manifest.

Returns:



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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

Instance Method Details

#ack_answer(opts = {}) ⇒ Object

Acknowledge a received answer message and start media playback

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the message parameters



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# 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

#answer(opts = {}) ⇒ Object

Helper method to answer an INVITE and expect the ACK

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp element attributes - will be passed to both the <send> and <recv> elements



344
345
346
347
# File 'lib/sippy_cup/scenario.rb', line 344

def answer(opts = {})
  send_answer opts
  receive_ack opts
end

#build(steps) ⇒ Object

Build the scenario steps provided

Parameters:

  • steps (Array<String>)

    A collection of steps to build the scenario

Raises:

  • (ArgumentError)


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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) ⇒ Object

Create partition table for Call Length

Parameters:

  • min (Integer)

    An value specifying the minimum time in milliseconds for the table

  • max (Integer)

    An value specifying the maximum time in milliseconds for the table

  • interval (Integer)

    An value specifying the interval in milliseconds for the table



636
637
638
# 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!String

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.

Examples:

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

Export a scenario to a calculated filename

scenario = Scenario.new 'Test Scenario'
scenario.compile! # Leaves files at test_scenario.xml and test_scenario.pcap

Returns:

  • (String)

    the path to the resulting scenario file



696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# 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 = {}) ⇒ Object

Shortcut to send a BYE and wait for the acknowledgement

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp <recv> element attributes - will be passed to both the <send> and <recv> elements



626
627
628
629
# File 'lib/sippy_cup/scenario.rb', line 626

def hangup(opts = {})
  send_bye opts
  receive_ok opts
end

#invite(opts = {}) ⇒ Object

Send an invite message

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the message

Options Hash (opts):

  • :retrans (Integer)
  • :headers (String)

    Extra headers to place into the INVITE



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
# 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 = {}) ⇒ Object Also known as: ack_bye

Acknowledge the last request

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the message parameters



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# 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

#receive_ack(opts = {}) ⇒ Object



349
350
351
# File 'lib/sippy_cup/scenario.rb', line 349

def receive_ack(opts = {})
  recv opts.merge request: 'ACK'
end

#receive_answer(opts = {}) ⇒ Object

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation

Options Hash (opts):

  • :optional (true, false)

    Whether or not receipt of the message is optional. Defaults to false.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# 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 = {}) ⇒ Object

Expect to receive a BYE message

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation



583
584
585
# File 'lib/sippy_cup/scenario.rb', line 583

def receive_bye(opts = {})
  recv opts.merge request: 'BYE'
end

#receive_invite(opts = {}) ⇒ Object Also known as: wait_for_call

Expect to receive a SIP INVITE

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp <recv> element attributes



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
# 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

#receive_message(regexp = nil) ⇒ Object

Expect to receive a MESSAGE message

Parameters:

  • regexp (String) (defaults to: nil)

    A regular expression (as a String) to match the message body against



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# 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) ⇒ Object Also known as: receive_200

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation

Options Hash (opts):

  • :optional (true, false)

    Whether or not receipt of the message is optional. Defaults to false.



419
420
421
# File 'lib/sippy_cup/scenario.rb', line 419

def receive_ok(opts = {}, &block)
  recv({ response: 200 }.merge(opts), &block)
end

#receive_progress(opts = {}) ⇒ Object Also known as: receive_183

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation

Options Hash (opts):

  • :optional (true, false)

    Whether or not receipt of the message is optional. Defaults to true.



381
382
383
# File 'lib/sippy_cup/scenario.rb', line 381

def receive_progress(opts = {})
  handle_response 183, opts
end

#receive_ringing(opts = {}) ⇒ Object Also known as: receive_180

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation

Options Hash (opts):

  • :optional (true, false)

    Whether or not receipt of the message is optional. Defaults to true.



370
371
372
# File 'lib/sippy_cup/scenario.rb', line 370

def receive_ringing(opts = {})
  handle_response 180, opts
end

#receive_trying(opts = {}) ⇒ Object Also known as: receive_100

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation

Options Hash (opts):

  • :optional (true, false)

    Whether or not receipt of the message is optional. Defaults to true.



359
360
361
# File 'lib/sippy_cup/scenario.rb', line 359

def receive_trying(opts = {})
  handle_response 100, opts
end

#register(user, password = nil, opts = {}) ⇒ Object

Send a REGISTER message with the specified credentials

Examples:

Register with authentication

s.register '[email protected]', 'abc123'

Register without authentication or a domain

s.register 'frank'

Parameters:

  • user (String)

    the user to register as. May be given as a full SIP URI (sip:[email protected]), in email-address format ([email protected]) or as a simple username (‘user’). If no domain is supplied, the source IP from SIPp will be used.

  • password (optional, String, nil) (defaults to: nil)

    the password to authenticate with.

  • opts (Hash) (defaults to: {})

    A set of options to modify the message



216
217
218
219
220
221
222
223
224
225
226
227
228
# 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) ⇒ Object

Create partition table for Response Time

Parameters:

  • min (Integer)

    An value specifying the minimum time in milliseconds for the table

  • max (Integer)

    An value specifying the maximum time in milliseconds for the table

  • interval (Integer)

    An value specifying the interval in milliseconds for the table



645
646
647
# 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_answer(opts = {}) ⇒ Object

Answer an incoming call

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp <send> element attributes



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# 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 = {}) ⇒ Object

Send a BYE message

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the message parameters



560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
# 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) ⇒ Object

Send DTMF digits

Examples:

Send a single DTMF digit

send_digits '1'

Enter a pin number

send_digits '1234'

Parameters:

  • DTMF (String)

    digits to send. Must be 0-9, *, # or A-D



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# 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 = {}) ⇒ Object Also known as: send_180

Send a “180 Ringing” response

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp <recv> element attributes



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# 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

#send_trying(opts = {}) ⇒ Object Also known as: send_100

Send a “100 Trying” response

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options containing SIPp <recv> element attributes



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# 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

#sleep(seconds) ⇒ Object

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

Parameters:

  • seconds (Numeric)

    The duration of the pause in seconds



469
470
471
472
473
# 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_tmpfilesHash<Symbol => Tempfile>

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.

Returns:

  • (Hash<Symbol => Tempfile>)

    handles to created Tempfiles at :scenario and :media

See Also:



722
723
724
725
726
727
728
729
730
731
732
733
734
735
# 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 = {}) ⇒ String

Dump the scenario to a SIPp XML string

Returns:

  • (String)

    the SIPp XML scenario



653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# 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?true, false

Returns the validity of the scenario. Will be false if errors were encountered while building the scenario from a manifest.

Returns:

  • (true, false)

    the validity of the scenario. Will be false if errors were encountered while building the scenario from a manifest



121
122
123
# File 'lib/sippy_cup/scenario.rb', line 121

def valid?
  @errors.size.zero?
end

#wait_for_answer(opts = {}) ⇒ Object

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.

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectations



432
433
434
435
436
437
438
# 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_hangup(opts = {}) ⇒ Object

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

Parameters:

  • opts (Hash) (defaults to: {})

    A set of options to modify the expectation



616
617
618
619
# File 'lib/sippy_cup/scenario.rb', line 616

def wait_for_hangup(opts = {})
  receive_bye(opts)
  ack_bye(opts)
end