sippy_cup 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0bf8b63922a80ba9eb886d9ad0713f953376f6f6
4
- data.tar.gz: df89e36db406772721fb83164372f1e7c7787261
3
+ metadata.gz: a6c679bd0c325ab8fb92408015dfacb1c5a88373
4
+ data.tar.gz: 557e222b71aa5286356bf63f37f63ba9233a46e3
5
5
  SHA512:
6
- metadata.gz: 436f979aeee94dfbe201ec011ba339cf12977dc9929741bb07c9beee9fe4ccfda3d0f113263b0ca5ad1f003d475e9e2dc2e37c247cabe527cb549f4d26c164cc
7
- data.tar.gz: 7c613e7768152d9e6a84d3145abfeca4396363fb2aaeaabec2927d45329ecf04f3a5ebc61f06b5a01dd71631aba37618ec05a5c6f2e1909586924986a18d36a6
6
+ metadata.gz: 2a3f0ef5f1ffcbe843918382fa318e34476c2e7ecdb0dff8fe762f27efd26339d34c7ceeb69ed1d409b45fb329ba687f2e6552c051def0cff88a222c5d972ca4
7
+ data.tar.gz: d8cbeeb69fde07e94936b5e20835a56c4df265f4ded018a6a3d051709d40ab5006df6f58e005ab165f10559dfab4e03d926a3370f76605aedba737c84dd202eb
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  .*.sw*
2
2
  .DS_Store
3
+ *.gem
data/Gemfile CHANGED
@@ -1,5 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- gem 'pcap', github: 'ahobson/ruby-pcap'
data/README.markdown CHANGED
@@ -3,3 +3,52 @@ Sippy Cup
3
3
 
4
4
  Sippy Cup is a tool to generate [SIPp](http://sipp.sourceforge.net/) load test profiles. The goal is to take an input document that describes a load test in a very simple way (call this number, wait this many seconds, send this digit, wait a few more seconds, etc). The ideas are taken from [LoadBot](https://github.com/mojolingo/ahn-loadbot), but the goal is for a more performant load generating tool with no dependency on Asterisk.
5
5
 
6
+
7
+ Example
8
+ =======
9
+
10
+ ```Ruby
11
+ require 'sippy_cup'
12
+
13
+ scenario = SippyCup::Scenario.new 'Sippy Cup', source: '192.168.5.5:10001', destination: '10.10.0.3:19995' do |s|
14
+ s.invite
15
+ s.receive_trying
16
+ s.receive_ringing
17
+ s.receive_progress
18
+
19
+ s.receive_answer
20
+ s.ack_answer
21
+
22
+ s.sleep 3
23
+ s.send_digits '3125551234'
24
+ s.sleep 5
25
+ s.send_digits '#'
26
+
27
+ s.receive_bye
28
+ s.ack_bye
29
+ end
30
+
31
+ # Create the scenario XML and PCAP media. File will be named after the scenario name, in our case:
32
+ # * sippy_cup.xml
33
+ # * sippy_cup.pcap
34
+ scenario.compile!
35
+ ```
36
+
37
+ Customize Your Scenarios
38
+ ========================
39
+
40
+ With Sippy Cup, you can add additional attributes to each step of the scenario:
41
+ ```Ruby
42
+
43
+ #This limits the amount of time the server has to reply to an invite (3 seconds)
44
+ s.receive_answer timeout: 3000
45
+
46
+ #You can override the default 'optional' parameters
47
+ s.receive_ringing optional: false
48
+ s.receive_answer optional: true
49
+
50
+ #Let's combine multiple attributes...
51
+ s.receive_answer timeout: 3000, crlf: true
52
+ ```
53
+
54
+ For more information on possible attributes, visit the [SIPp Documentation](http://sipp.sourceforge.net/doc/reference.html)
data/lib/sippy_cup.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'sippy_cup/scenario'
1
+ %w{
2
+ scenario
3
+ media
4
+ }.each {|r| require "sippy_cup/#{r}"}
2
5
 
3
6
  module SippyCup
4
7
 
@@ -0,0 +1,120 @@
1
+ require 'ipaddr'
2
+ require 'sippy_cup/media/pcmu_payload'
3
+ require 'sippy_cup/media/dtmf_payload'
4
+
5
+ module SippyCup
6
+ class Media
7
+ VALID_STEPS = %w{silence dtmf}.freeze
8
+ USEC = 1_000_000
9
+ MSEC = 1_000
10
+ attr_accessor :sequence
11
+ attr_reader :packets
12
+
13
+ def initialize(from_addr, from_port, to_addr, to_port, generator = PCMUPayload)
14
+ @from_addr, @to_addr = IPAddr.new(from_addr), IPAddr.new(to_addr)
15
+ @from_port, @to_port, @generator = from_port, to_port, generator
16
+ reset!
17
+ end
18
+
19
+ def reset!
20
+ @sequence = []
21
+ @packets = []
22
+ end
23
+
24
+ def <<(input)
25
+ get_step input # validation
26
+ @sequence << input
27
+ end
28
+
29
+ def compile!
30
+ sequence_number = 0
31
+ start_time = Time.now
32
+ @pcap_file = PacketFu::PcapFile.new
33
+ timestamp = 0
34
+ elapsed = 0
35
+ ssrc_id = rand 2147483648
36
+ first_audio = true
37
+
38
+ @sequence.each do |input|
39
+ action, value = get_step input
40
+
41
+ case action
42
+ when 'silence'
43
+ # value is the duration in milliseconds
44
+ # append that many milliseconds of silent RTP audio
45
+ (value.to_i / @generator::PTIME).times do
46
+ packet = new_packet
47
+ rtp_frame = @generator.new
48
+
49
+ # The first RTP audio packet should have the marker bit set
50
+ if first_audio
51
+ rtp_frame.rtp_marker = 1
52
+ first_audio = false
53
+ end
54
+
55
+ rtp_frame.rtp_timestamp = timestamp += rtp_frame.timestamp_interval
56
+ elapsed += rtp_frame.ptime
57
+ rtp_frame.rtp_sequence_num = sequence_number += 1
58
+ rtp_frame.rtp_ssrc_id = ssrc_id
59
+ packet.headers.last.body = rtp_frame.to_bytes
60
+ packet.recalc
61
+ @pcap_file.body << get_pcap_packet(packet, next_ts(start_time, elapsed))
62
+ end
63
+ when 'dtmf'
64
+ # value is the DTMF digit to send
65
+ # append that RFC2833 digit
66
+ # Assume 0.25 second duration for now
67
+ count = 250 / DTMFPayload::PTIME
68
+ count.times do |i|
69
+ packet = new_packet
70
+ dtmf_frame = DTMFPayload.new value
71
+ dtmf_frame.rtp_marker = 1 if i == 0
72
+ dtmf_frame.rtp_timestamp = timestamp # Is this correct? This is what Blink does...
73
+ #dtmf_frame.rtp_timestamp = timestamp += dtmf_frame.timestamp_interval
74
+ dtmf_frame.rtp_sequence_num = sequence_number += 1
75
+ dtmf_frame.rtp_ssrc_id = ssrc_id
76
+ dtmf_frame.end_of_event = (count == i) # Last packet?
77
+ packet.headers.last.body = dtmf_frame.to_bytes
78
+ packet.recalc
79
+ @pcap_file.body << get_pcap_packet(packet, next_ts(start_time, elapsed))
80
+ end
81
+ # Now bump up the timestamp to cover the gap
82
+ timestamp += count * DTMFPayload::TIMESTAMP_INTERVAL
83
+ else
84
+ end
85
+ end
86
+ @pcap_file
87
+ end
88
+ private
89
+ def get_step(input)
90
+ action, value = input.split ':'
91
+ raise "Invalid Sequence: #{input}" unless VALID_STEPS.include? action
92
+
93
+ [action, value]
94
+ end
95
+
96
+
97
+ def get_pcap_packet(packet, timestamp)
98
+ PacketFu::PcapPacket.new :timestamp => timestamp,
99
+ :incl_len => packet.to_s.size,
100
+ :orig_len => packet.to_s.size,
101
+ :data => packet.to_s
102
+ end
103
+
104
+ def next_ts(start_time, offset)
105
+ distance = offset * MSEC
106
+ sec = start_time.to_i + (distance / USEC)
107
+ usec = distance % USEC
108
+ PacketFu::Timestamp.new(sec: sec, usec: usec).to_s
109
+ end
110
+
111
+ def new_packet
112
+ packet = PacketFu::UDPPacket.new
113
+ packet.ip_src = @from_addr.to_i
114
+ packet.ip_dst = @to_addr.to_i
115
+ packet.udp_src = @from_port
116
+ packet.udp_dst = @to_port
117
+ packet
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,54 @@
1
+ require 'sippy_cup/media/rtp_payload'
2
+
3
+ module SippyCup
4
+ class Media
5
+ class DTMFPayload < RTPPayload
6
+ RTP_PAYLOAD_ID = 101
7
+ PTIME = 20 # in milliseconds
8
+ TIMESTAMP_INTERVAL = 160
9
+ END_OF_EVENT = 1 << 7
10
+ DTMF = %w{0 1 2 3 4 5 6 7 8 9 * # A B C D}.freeze
11
+ attr_accessor :ptime
12
+
13
+ def initialize(digit, opts = {})
14
+ super RTP_PAYLOAD_ID
15
+ @flags = 0
16
+ @digit = atoi digit
17
+ @ptime = opts[:ptime] || PTIME
18
+
19
+ volume opts[:volume] || 10
20
+ end
21
+
22
+ def end_of_event=(bool)
23
+ if bool
24
+ @flags |= END_OF_EVENT
25
+ else
26
+ @flags &= (0xf - END_OF_EVENT)
27
+ end
28
+ end
29
+
30
+ def atoi(digit)
31
+ DTMF.index digit.to_s
32
+ end
33
+
34
+ def volume(value)
35
+ value = [value, 0x3f].min # Cap to 6 bits
36
+ @flags &= 0xc0 # zero out old volume
37
+ @flags += value
38
+ end
39
+
40
+ def end_of_event
41
+ @flags & END_OF_EVENT
42
+ end
43
+
44
+ def media
45
+ [@digit, @flags, timestamp_interval].pack 'CCn'
46
+ end
47
+
48
+ def timestamp_interval
49
+ TIMESTAMP_INTERVAL
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,27 @@
1
+ require 'sippy_cup/media/rtp_payload'
2
+
3
+ module SippyCup
4
+ class Media
5
+ class PCMUPayload < RTPPayload
6
+ RTP_PAYLOAD_ID = 0x0
7
+ SILENT_BYTE = 0xff.chr
8
+ PTIME = 20 # in milliseconds
9
+ RATE = 8 # in KHz
10
+ attr_accessor :ptime
11
+
12
+ def initialize(opts = {})
13
+ super RTP_PAYLOAD_ID
14
+ @ptime = opts[:ptime] || PTIME
15
+ @rate = opts[:rate] || RATE
16
+ end
17
+
18
+ def media
19
+ SILENT_BYTE * timestamp_interval
20
+ end
21
+
22
+ def timestamp_interval
23
+ @rate * @ptime
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ require 'packetfu'
3
+
4
+ module SippyCup
5
+ class Media
6
+ class RTPHeader < Struct.new(:version, :padding, :extension, :marker, :payload_id, :sequence_num, :timestamp, :ssrc_id, :csrc_ids)
7
+ VERSION = 2
8
+
9
+ include StructFu
10
+
11
+ def initialize(args = {})
12
+ # TODO: Support Extension Header
13
+ super(
14
+ (args[:version] ? args[:version] : VERSION),
15
+ (args[:padding] ? args[:padding] : 0),
16
+ (args[:extension] ? args[:extension] : 0),
17
+ (args[:marker] ? args[:marker] : 0),
18
+ (args[:payload_id] ? args[:payload_id] : 0),
19
+ Int16.new(args[:sequence_num] ? args[:sequence_num] : 0),
20
+ Int32.new(args[:timestamp] ? args[:timestamp] : 0),
21
+ Int32.new(args[:ssrc_id] ? args[:ssrc_id] : 0),
22
+ (args[:csrc_ids] ? Array(args[:csrc_ids]) : []),
23
+ )
24
+ end
25
+
26
+ def read(str)
27
+ self[:version] = str[0].ord >> 6
28
+ self[:padding] = (str[0].ord >> 5) & 1
29
+ self[:extension] = (str[0].ord >> 4) & 1
30
+ num_csrcs = str[0].ord & 0xf
31
+ self[:marker] = str[1] >> 7
32
+ self[:payload_id] = str[1] & 0x7f
33
+ self[:sequence_num].read str[2,2]
34
+ self[:timestamp].read str[4,4]
35
+ self[:ssrc_id].read str[8,4]
36
+ i = 8
37
+ num_csrcs.times do
38
+ self[:csrc_ids] << Int32.new(str[i += 4, 4])
39
+ end
40
+ self[:body] = str[i, str.length - i]
41
+ end
42
+
43
+ def csrc_count
44
+ csrc_ids.count
45
+ end
46
+
47
+ def csrc_ids_readable
48
+ csrc_ids.to_s
49
+ end
50
+
51
+ def to_s
52
+ bytes = [
53
+ (version << 6) + (padding << 5) + (extension << 4) + (csrc_count),
54
+ (marker << 7) + (payload_id),
55
+ sequence_num,
56
+ timestamp,
57
+ ssrc_id
58
+ ].pack 'CCnNN'
59
+
60
+ csrc_ids.each do |csrc_id|
61
+ bytes << [csrc_id].pack('N')
62
+ end
63
+
64
+ bytes
65
+ end
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ require 'packetfu'
3
+ require 'sippy_cup/media/rtp_header'
4
+
5
+ module SippyCup
6
+ class Media
7
+ class RTPPayload
8
+ attr_reader :header
9
+
10
+ def initialize(payload_id = 0)
11
+ @header = RTPHeader.new payload_id: payload_id
12
+ end
13
+
14
+ def to_bytes
15
+ @header.to_s + media
16
+ end
17
+
18
+ def method_missing(method, *args)
19
+ if method.to_s =~ /^rtp_/
20
+ method = method.to_s.sub(/^rtp_/, '').to_sym
21
+ @header.send method, *args
22
+ else
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ require 'pcap'
2
+
3
+ module SippyCup
4
+ class RTPGenerator
5
+ DEFAULT_DATALINK = 1 # Corresponds to DLT_EN10MB, Ethernet (10Mb) from pcap/bpf.h
6
+
7
+ def initialize
8
+ @output = Pcap::Capture.open_dead DEFAULT_DATALINK, 65535
9
+
10
+
11
+
12
+ def save!(file)
13
+ pcap_file = Pcap::Dumper.open @output, file
14
+ @output.loop(-1) do |packet|
15
+ pcap_file.dump packet
16
+ end
17
+ end
18
+ end
19
+ end
@@ -2,33 +2,56 @@ require 'nokogiri'
2
2
 
3
3
  module SippyCup
4
4
  class Scenario
5
- def initialize(name, &block)
5
+ VALID_DTMF = %w{0 1 2 3 4 5 6 7 8 9 0 * # A B C D}.freeze
6
+ MSEC = 1_000
7
+
8
+ def initialize(name, args = {}, &block)
6
9
  builder = Nokogiri::XML::Builder.new do |xml|
7
10
  xml.scenario name: name
8
11
  end
9
12
 
13
+ parse_args args
14
+
15
+ @filename = name.downcase.gsub(/\W+/, '_')
10
16
  @doc = builder.doc
17
+ @media = Media.new @from_addr, @from_port, @to_addr, @to_port
11
18
  @scenario = @doc.xpath('//scenario').first
12
19
 
13
20
  instance_eval &block
14
21
  end
15
22
 
23
+ def parse_args(args)
24
+ raise ArgumentError, "Must include source IP:PORT" unless args.keys.include? :source
25
+ raise ArgumentError, "Must include destination IP:PORT" unless args.keys.include? :destination
26
+
27
+ @from_addr, @from_port = args[:source].split ':'
28
+ @to_addr, @to_port = args[:destination].split ':'
29
+ @from_user = args[:from_user] || "sipp"
30
+ end
31
+
32
+ def compile_media
33
+ @media.compile!
34
+ end
35
+
16
36
  def sleep(seconds)
17
37
  # TODO play silent audio files to the server to fill the gap
18
- pause = Nokogiri::XML::Node.new 'pause', @doc
19
- pause['milliseconds'] = seconds
20
- @scenario.add_child pause
38
+ pause seconds * MSEC
39
+ @media << "silence:#{seconds * MSEC}"
21
40
  end
22
41
 
23
- def invite
42
+ def invite(opts = {})
43
+ opts[:retrans] ||= 500
44
+ # FIXME: The DTMF mapping (101) is hard-coded. It would be better if we could
45
+ # get this from the DTMF payload generator
24
46
  msg = <<-INVITE
47
+
25
48
  INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
26
49
  Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
27
- From: sipp <sip:[field0]@[local_ip]>;tag=[call_number]
50
+ From: sipp <sip:#{@from_user}@[local_ip]>;tag=[call_number]
28
51
  To: <sip:[service]@[remote_ip]:[remote_port]>
29
52
  Call-ID: [call_id]
30
53
  CSeq: [cseq] INVITE
31
- Contact: sip:[field0]@[local_ip]:[local_port]
54
+ Contact: sip:#{@from_user}@[local_ip]:[local_port]
32
55
  Max-Forwards: 100
33
56
  Content-Type: application/sdp
34
57
  Content-Length: [len]
@@ -38,61 +61,111 @@ module SippyCup
38
61
  s=-
39
62
  c=IN IP[media_ip_type] [media_ip]
40
63
  t=0 0
41
- m=audio [media_port] RTP/AVP 0
64
+ m=audio [media_port] RTP/AVP 0 101
42
65
  a=rtpmap:0 PCMU/8000
66
+ a=rtpmap:101 telephone-event/8000
67
+ a=fmtp:101 0-15
43
68
  INVITE
44
- send = new_send msg
45
- # FIXME: Does this need to be configurable?
46
- send['retrans'] = 500
47
-
69
+ send = new_send msg, opts
48
70
  @scenario << send
49
71
  end
50
72
 
51
- def receive_trying(optional = true)
52
- @scenario.add_child new_recv response: 100, optional: optional
73
+ def receive_trying(opts = {})
74
+ opts[:optional] = true if opts[:optional].nil?
75
+ opts.merge! response: 100
76
+ @scenario << new_recv(opts)
53
77
  end
54
78
  alias :receive_100 :receive_trying
55
79
 
56
- def receive_ringing(optional = true)
57
- @scenario.add_child new_recv response: 180, optional: optional
80
+ def receive_ringing(opts = {})
81
+ opts[:optional] = true if opts[:optional].nil?
82
+ opts.merge! response: 180
83
+ @scenario << new_recv(opts)
58
84
  end
59
85
  alias :receive_180 :receive_ringing
60
86
 
61
- def receive_progress(optional = true)
62
- @scenario.add_child new_recv response: 183, optional: optional
87
+ def receive_progress(opts = {})
88
+ opts[:optional] = true if opts[:optional].nil?
89
+ opts.merge! response: 183
90
+ @scenario << new_recv(opts)
63
91
  end
64
92
  alias :receive_183 :receive_progress
65
93
 
66
- def receive_answer
67
- recv = new_recv response: 200, optional: false
94
+ def receive_answer(opts = {})
95
+ opts.merge! response: 200
96
+ recv = new_recv opts
68
97
  # Record Record Set: Make the Route headers available via [route] later
69
98
  recv['rrs'] = true
70
- @scenario.add_child recv
99
+ @scenario << recv
71
100
  end
72
101
  alias :receive_200 :receive_answer
73
102
 
74
- def ack_answer
103
+ def ack_answer(opts = {})
75
104
  msg = <<-ACK
105
+
76
106
  ACK [next_url] SIP/2.0
77
107
  Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
78
- From: <sip:[field0]@[local_ip]>;tag=[call_number]
108
+ From: <sip:#{@from_user}@[local_ip]>;tag=[call_number]
79
109
  [last_To:]
80
110
  [routes]
81
111
  Call-ID: [call_id]
82
112
  CSeq: [cseq] ACK
83
- Contact: sip:[field0]@[local_ip]:[local_port]
113
+ Contact: sip:#{@from_user}@[local_ip]:[local_port]
84
114
  Max-Forwards: 100
85
115
  Content-Length: 0
86
116
  ACK
87
- @scenario << new_send(msg)
117
+ @scenario << new_send(msg, opts)
118
+ start_media
119
+ end
120
+
121
+ def start_media
122
+ nop = Nokogiri::XML::Node.new 'nop', @doc
123
+ action = Nokogiri::XML::Node.new 'action', @doc
124
+ nop << action
125
+ exec = Nokogiri::XML::Node.new 'exec', @doc
126
+ exec['play_pcap_audio'] = "#{@filename}.pcap"
127
+ action << exec
128
+ @scenario << nop
88
129
  end
89
130
 
90
- def receive_bye
91
- @scenario.add_child new_recv response: 'BYE'
131
+ ##
132
+ # Send DTMF digits
133
+ # @param[String] DTMF digits to send. Must be 0-9, *, # or A-D
134
+ def send_digits(digits, delay = 0.250)
135
+ delay = 0.250 * MSEC # FIXME: Need to pass this down to the media layer
136
+ digits.split('').each do |digit|
137
+ raise ArgumentError, "Invalid DTMF digit requested: #{digit}" unless VALID_DTMF.include? digit
138
+
139
+ @media << "dtmf:#{digit}"
140
+ @media << "silence:#{delay}"
141
+ pause delay * 2
142
+ end
92
143
  end
93
144
 
94
- def ack_bye
145
+ def send_bye(opts = {})
146
+ msg = <<-MSG
147
+
148
+ BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
149
+ [last_Via:]
150
+ [last_From:]
151
+ [last_To:]
152
+ [last_Call-ID]
153
+ CSeq: [cseq] BYE
154
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
155
+ Max-Forwards: 100
156
+ Content-Length: 0
157
+ MSG
158
+ @scenario << new_send(msg, opts)
159
+ end
160
+
161
+ def receive_bye(opts = {})
162
+ opts.merge! request: 'BYE'
163
+ @scenario << new_recv(opts)
164
+ end
165
+
166
+ def ack_bye(opts = {})
95
167
  msg = <<-ACK
168
+
96
169
  SIP/2.0 200 OK
97
170
  [last_Via:]
98
171
  [last_From:]
@@ -104,27 +177,47 @@ module SippyCup
104
177
  Max-Forwards: 100
105
178
  Content-Length: 0
106
179
  ACK
107
- @scenario << new_send(msg)
180
+ @scenario << new_send(msg, opts)
108
181
  end
109
182
 
110
183
  def to_xml
111
184
  @doc.to_xml
112
185
  end
113
186
 
187
+ def compile!
188
+ xml_file = File.open "#{@filename}.xml", 'w' do |file|
189
+ file.write @doc.to_xml
190
+ end
191
+ compile_media.to_file filename: "#{@filename}.pcap"
192
+ end
193
+
114
194
  private
195
+ def pause(msec)
196
+ pause = Nokogiri::XML::Node.new 'pause', @doc
197
+ pause['milliseconds'] = msec.to_i
198
+ @scenario << pause
199
+ end
115
200
 
116
- def new_send(msg)
201
+ def new_send(msg, opts = {})
117
202
  send = Nokogiri::XML::Node.new 'send', @doc
118
- send << Nokogiri::XML::Text.new(msg, @doc)
203
+ opts.each do |k,v|
204
+ send[k.to_s] = v
205
+ end
206
+ send << "\n"
207
+ send << Nokogiri::XML::CDATA.new(@doc, msg)
208
+ send << "\n" #Newlines are required before and after CDATA so SIPp will parse properly
119
209
  send
120
210
  end
121
211
 
122
212
  def new_recv(opts = {})
123
213
  raise ArgumentError, "Receive must include either a response or a request" unless opts.keys.include?(:response) || opts.keys.include?(:request)
124
214
  recv = Nokogiri::XML::Node.new 'recv', @doc
125
- recv['request'] = opts[:request] if opts.keys.include? :request
126
- recv['response'] = opts[:response] if opts.keys.include? :response
127
- recv['optional'] = !!opts[:optional]
215
+ recv['request'] = opts.delete :request if opts.keys.include? :request
216
+ recv['response'] = opts.delete :response if opts.keys.include? :response
217
+ recv['optional'] = !!opts.delete(:optional)
218
+ opts.each do |k,v|
219
+ recv[k.to_s] = v
220
+ end
128
221
  recv
129
222
  end
130
223
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module SippyCup
4
- VERSION = '0.0.1'
4
+ VERSION = '0.1.0'
5
5
  end
data/sippy_cup.gemspec CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.name = "sippy_cup"
7
7
  s.version = SippyCup::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Ben Klang"]
10
- s.email = "bklang&mojolingo.com"
9
+ s.authors = ["Ben Klang", "Will Drexler"]
10
+ s.email = ["bklang&mojolingo.com", "wdrexler&mojolingo.com"]
11
11
  s.homepage = "https://github.com/bklang/sippy_cup"
12
12
  s.summary = "SIPp profile and RTP stream generator"
13
13
  s.description = "This tool makes it easier to generate SIPp load tests with DTMF interactions."
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_runtime_dependency 'pcap'
20
+ s.add_runtime_dependency 'packetfu'
21
21
  s.add_runtime_dependency 'nokogiri', ["~> 1.5.0"]
22
22
 
23
23
  s.add_development_dependency 'guard-rspec'
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sippy_cup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Klang
8
+ - Will Drexler
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-06-13 00:00:00.000000000 Z
12
+ date: 2013-07-03 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: pcap
15
+ name: packetfu
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
18
  - - '>='
@@ -95,7 +96,9 @@ dependencies:
95
96
  - !ruby/object:Gem::Version
96
97
  version: '0'
97
98
  description: This tool makes it easier to generate SIPp load tests with DTMF interactions.
98
- email: bklang&mojolingo.com
99
+ email:
100
+ - bklang&mojolingo.com
101
+ - wdrexler&mojolingo.com
99
102
  executables:
100
103
  - sippy_cup
101
104
  extensions: []
@@ -108,6 +111,12 @@ files:
108
111
  - README.markdown
109
112
  - bin/sippy_cup
110
113
  - lib/sippy_cup.rb
114
+ - lib/sippy_cup/media.rb
115
+ - lib/sippy_cup/media/dtmf_payload.rb
116
+ - lib/sippy_cup/media/pcmu_payload.rb
117
+ - lib/sippy_cup/media/rtp_header.rb
118
+ - lib/sippy_cup/media/rtp_payload.rb
119
+ - lib/sippy_cup/rtp_generator.rb
111
120
  - lib/sippy_cup/scenario.rb
112
121
  - lib/sippy_cup/version.rb
113
122
  - sippy_cup.gemspec