rkday / quaff

A Ruby library for writing SIP test scenarios, in a more concise style than SIPp.
GNU General Public License v3.0
7 stars 8 forks source link

handling of in-dialog INVITEs is incorrect #18

Open jaroszan opened 10 years ago

jaroszan commented 10 years ago

UPDATE 15-10-2014

The erroneous behavior is caused by improper use of API, it looks that decision whether to discard the message or add it to @call_ids Queue is taken in queue_msg function (endpoint.rb) which is called right after reception of the message and before incoming_call function is called. Therefore incoming_call is not a proper place to receive further in-dialog INVITEs.

It is still not clear (for me) how to handle such messages and whether it is possible without modifications in the library itself.

The most obvious solution would be to introduce additional instance variable for endpoint which would contain all calls in progress (e.g. @calls_in_progress = {}) and add cid of the call inside the incoming_call function with true value.

When the subsequent in-dialog is received inside recv_msg function, queue_msg would verify if @calls_in_progress contains cid, if true cid would be pushed to @call_ids which would be picked up by incoming_call function and further processing would be possible.

cid would be deleted in the mark_call_dead function when end_call is called by user script.

I am still thinking it through however I wanted to highlight that issue in its original description is outdated and some progress has been made. Still would appreciate any hints on how to simulate this scenario.

ORIGINAL ISSUE In a transfer related case where multiple reINVITEs are involved it seems that quaff does not work correctly if single entry point for INVITEs is used. If I use a test case like:

... call.send_response(100, "Trying") call.send_response(200, "OK") call.recv_request("ACK") call.recv_request("INVITE") call.send_response(200, "OK") call.recv_request("ACK") call.recv_request("INVITE") ...

And all the action happens in a single thread test case works as expected.

However if I receive all INVITEs in single point and further logic is determined based on contents of INVITE message all further INVITEs with the Call-ID already received are discarded. The structure of the test case is as follows (a real example is a bit more complex, but problematic section is below):

call_ids = Hash.new(0) loop do Thread.start(phone.incoming_call) do |call| data = call.recv_request("INVITE") cid = data["message"].header("Call-ID") if call_ids.has_key? cid

call already in progress

  call_ids[cid] += 1
  call.send_response(200, "OK")
  call.recv_request("ACK") 
  if call_ids[cid] == 3
    call_ids.delete(cid)
    call.end_call
  end
elsif !call_ids.has_key? cid
  #call is started
  call_ids[cid] += 1
  call.send_response(100, "Trying")
  call.send_response(200, "OK")
  call.recv_request("ACK")
elsif ...

While tracing in wireshark I can see second INVITE with the same Call-ID is sent to script but it looks that it gets discarded.

The reason behind writing test case in this way is that script should receive several INVITEs per second and putting all the logic in one leg puts test case in complete disarray when several threads are started at the caller side.

If I am approaching it in the incorrect way could you please point me in the right direction?

rkday commented 10 years ago

The way I'd originally envisioned this happening is that you'd handle a SIP dialog on a single thread, instead of reacting to individual requests separately. I'm not quite sure what the benefits of that approach are - I can see that a reactive model is likely to perform better, but Quaff's key use-case is for functional testing rather than high-performance testing. For ordinary tests, I think it's clearer to describe a "call" all in one place, with the sequence of messages you expect to be sent and received.

You say that "putting all the logic in one leg puts test case in complete disarray when several threads are started at the caller side" - can you give a bit more detail on what you're trying to do here, and why this causes complete disarray?

jaroszan commented 10 years ago

My test case looks as follows (simplified version):

  1. A party calls contact number
  2. Upon reception of call at the contact number, service establishes SIP session with media server
  3. Media server manages playing announcement for particular party via http session with service (in includes playing initial announcement and turning on music which is played to the caller until connection is established with the target party)
  4. Service dials in target party and plays initial announcement to target party
  5. Service connects two parties and disconnects media dialogs

A-party is played by another tool which I used initially, but it turned out that it was impossible to prepare such scenario due to several limitations.

B-party and media server are supposed to be played by the simulator as well and I tried to prepare script which would do that in quaff (and ruby). Functional tests worked correctly, however my intention was to be able to run performance tests. Script looked as follows:

loop do Thread.start(phone.incoming_call) do |call| data = call.recv_request("INVITE") if data["message"].header("To") =~ /DE/ ... else ... call.recv_request("INVITE") ... call.recv_request("INVITE") ...

For single test execution it worked correctly, calls to media sever were marked with 'DE' in To header. So session was initiated three times 2 for matching 'DE' and one for target number. Media sessions contained only one INVITE and have been handled correctly. However call to target number contained additional INVITEs and it is what caused trouble. When several sessions have been initiated simultaneously not a single test finished successfully.

I managed to make modifications in queue_msg (most likely poor - key one being matching on method 'INVITE' to be added to call_ids queue) and test was running successfully when handling each incoming INVITE individually. However it proved to be unstable. Condition for test execution are that all receiving logic should happen on one endpoint and original tool didn't support that (tremendously poor matching mechanisms). With quaff I was able to prepare functional test, however failed miserably in performance tests. Currently I am using ruby script as udp proxy which routes and modifies messages depending on their contents to the instances of the old tool located on different ports.

If in your opinion I have misused quaff and it is possible to simulate B-party and media server in the above way I would be happy to come back and follow your hints and check if it works for my case.