From 103bd5b65c5000cd19b75da8be9ba82437e1aa5c Mon Sep 17 00:00:00 2001
From: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
Date: Sun, 15 Apr 2012 10:27:51 -0400
Subject: [PATCH] #9621: Implement conference functional tests

---
 tools/pysflphone/sflphonectrl.py              |  50 ++-
 tools/pysflphone/sippwrap.py                  |  13 +-
 .../test_sflphone_dbus_interface.py           | 307 ++++++++++++++----
 ...no_cvs.xml => uac_register_no_cvs_300.xml} |   0
 tools/sippxml/uac_register_no_cvs_400.xml     | 110 +++++++
 5 files changed, 397 insertions(+), 83 deletions(-)
 rename tools/sippxml/{uac_register_no_cvs.xml => uac_register_no_cvs_300.xml} (100%)
 create mode 100644 tools/sippxml/uac_register_no_cvs_400.xml

diff --git a/tools/pysflphone/sflphonectrl.py b/tools/pysflphone/sflphonectrl.py
index 88a4fe9038..b6a17226a2 100755
--- a/tools/pysflphone/sflphonectrl.py
+++ b/tools/pysflphone/sflphonectrl.py
@@ -59,11 +59,15 @@ class SflPhoneCtrl(Thread):
         onCallCurrent_cb
         onCallBusy_cb
         onCallFailure_cb
+        onConferenceCreated_cb
     """
 
     # list of active calls (known by the client)
     activeCalls = {}
 
+    # list of active conferences
+    activeConferences = {}
+
     def __init__(self, name=sys.argv[0]):
         Thread.__init__(self)
 
@@ -74,6 +78,7 @@ class SflPhoneCtrl(Thread):
         self.name = name
 
 	self.currentCallId = ""
+        self.currentConfId = ""
 
 	self.isStop = False
 
@@ -145,6 +150,7 @@ class SflPhoneCtrl(Thread):
             print "Adding Incoming call method"
             proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall)
             proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged)
+            proxy_callmgr.connect_to_signal('conferenceCreated', self.onConferenceCreated)
         except dbus.DBusException, e:
             print e
 
@@ -171,7 +177,7 @@ class SflPhoneCtrl(Thread):
     def onIncomingCall_cb(self):
         pass
 
-    def onCallHangup_cb(self):
+    def onCallHangup_cb(self, callId):
         pass
 
     def onCallRinging_cb(self):
@@ -202,7 +208,7 @@ class SflPhoneCtrl(Thread):
     def onCallHangUp(self, callid):
         """ Remove callid from call list """
 
-        self.onCallHangup_cb()
+        self.onCallHangup_cb(callid)
 	self.currentCallId = ""
         del self.activeCalls[callid]
 
@@ -274,6 +280,12 @@ class SflPhoneCtrl(Thread):
         else:
             print "unknown state"
 
+    def onConferenceCreated_cb(self):
+        pass
+
+    def onConferenceCreated(self, confId):
+        self.currentConfId = confId
+        self.onConferenceCreated_cb()
 
     #
     # Account management
@@ -545,9 +557,6 @@ class SflPhoneCtrl(Thread):
         for call in self.activeCalls:
             print "\t" + call
 
-    #
-    # Action
-    #
     def Call(self, dest):
         """Start a call and return a CallID
 
@@ -636,12 +645,6 @@ class SflPhoneCtrl(Thread):
     def Hold(self, callid):
         """Hold a call identified by a CallID"""
 
-        # if not self.account:
-        #    self.setFirstRegisteredAccount()
-
-        # if not self.isAccountRegistered():
-        #    raise SflPhoneError("Can't hold a call without a registered account")
-
         if callid is None or callid == "":
             raise SflPhoneError("Invalid callID")
 
@@ -651,12 +654,6 @@ class SflPhoneCtrl(Thread):
     def UnHold(self, callid):
         """Unhold an incoming call identified by a CallID"""
 
-        # if not self.account:
-        #    self.setFirstRegisteredAccount()
-
-        # if not self.isAccountRegistered():
-        #    raise SflPhoneError("Can't unhold a call without a registered account")
-
         if callid is None or callid == "":
             raise SflPhoneError("Invalid callID")
 
@@ -679,13 +676,30 @@ class SflPhoneCtrl(Thread):
         callid = m.hexdigest()
 	return callid
 
+
+    def createConference(self, call1Id, call2Id):
+        """ Create a conference given the two call ids """
+
+        self.callmanager.joinParticipant(call1Id, call2Id)
+
+
+    def hangupConference(self, confId):
+        """ Hang up each call for this conference """
+
+        self.callmanager.hangUpConference(confId)
+
+
     def run(self):
         """Processing method for this thread"""
 
-	context = self.loop.get_context()
+        context = self.loop.get_context()
 
 	while True:
             context.iteration(True)
 
 	    if self.isStop:
+                print "++++++++++++++++++++++++++++++++++++++++"
+                print "++++++++++++++++++++++++++++++++++++++++"
+                print "++++++++++++++++++++++++++++++++++++++++"
+                print "++++++++++++++++++++++++++++++++++++++++"
                 return
diff --git a/tools/pysflphone/sippwrap.py b/tools/pysflphone/sippwrap.py
index b959478241..0bdc073558 100644
--- a/tools/pysflphone/sippwrap.py
+++ b/tools/pysflphone/sippwrap.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2009 by the Free Software Foundation, Inc.
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -22,7 +24,7 @@ class SippWrapper:
     """ Wrapper taht allow for managing sipp command line easily """
 
     def __init__(self):
-        self.commandLine = "sipp"
+        self.commandLine = "./sipp"
         self.remoteServer = ""
         self.remotePort = ""
         self.localInterface = ""
@@ -41,9 +43,11 @@ class SippWrapper:
         self.enableTraceRtt = False
         self.enableTraceLogs = False
 
-    def buildCommandLine(self):
+    def buildCommandLine(self, port):
 	""" Fill the command line arguments based on specified parameters """
 
+        self.localPort = str(port)
+
         if not self.remotePort and not self.remoteServer:
             self.isUserAgentClient = False
         elif self.remotePort and not self.remoteServer:
@@ -106,9 +110,8 @@ class SippWrapper:
     def launch(self):
         """ Launch the sipp instance using the specified arguments """
 
-        self.buildCommandLine()
         print self.commandLine
-        return os.system(self.commandLine)
+        return os.system(self.commandLine + " 2>&1 > /dev/null")
 
 
 class SippScreenStatParser:
diff --git a/tools/pysflphone/test_sflphone_dbus_interface.py b/tools/pysflphone/test_sflphone_dbus_interface.py
index 2af61f5c55..7b796bb527 100644
--- a/tools/pysflphone/test_sflphone_dbus_interface.py
+++ b/tools/pysflphone/test_sflphone_dbus_interface.py
@@ -1,6 +1,8 @@
-#!/usr/bin/env python
+#!/USR/BIN/ENV PYTHON
 #
-# Copyright (C) 2009 by the Free Software Foundation, Inc.
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -18,7 +20,9 @@
 
 import os
 import time
+import yaml
 import logging
+import multiprocessing
 from sippwrap import SippWrapper
 from sippwrap import SippScreenStatParser
 from sflphonectrl import SflPhoneCtrl
@@ -29,10 +33,62 @@ from nose.tools import nottest
 ### function starting with 'test' are executed.
 ###
 
-accountList = ["IP2IP", "Account:1332798167"]
-
 SCENARIO_PATH = "../sippxml/"
 
+class SippCtrl:
+
+    def __init__(self):
+        self.remoteServer = "127.0.0.1"
+        self.remotePort = str(5062)
+        self.localInterface = "127.0.0.1"
+        self.localPort = str(5060)
+
+    def initialize_sipp_registration_instance(self, instance, xmlScenario):
+        instance.remoteServer = self.remoteServer
+        instance.remotePort = self.remotePort
+        instance.localInterface = self.localInterface
+        instance.localPort = self.localPort
+        instance.customScenarioFile = SCENARIO_PATH + xmlScenario
+        instance.numberOfCall = 1
+        instance.numberOfSimultaneousCall = 1
+
+    def initialize_sipp_call_instance(self, instance):
+        instance.localInterface = self.localInterface
+        instance.localPort = self.localPort
+        instance.numberOfCall = 1
+        instance.numberOfSimultaneousCall = 1
+        instance.enableTraceScreen = True
+
+    def launchSippProcess(self, sippInstance, localPort):
+        sippInstance.buildCommandLine(localPort)
+        sippInstance.launch()
+
+    def find_sipp_pid(self):
+        # Retreive the PID of the last
+        # The /proc/PID/cmdline contain the command line from
+        pids = [int(x) for x in os.listdir("/proc") if x.isdigit()]
+        sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()]
+
+        return sippPid[0]
+
+    def clean_log_directory(self):
+        dirlist = os.listdir("./")
+        files = [x for x in dirlist if "screen.log" in x]
+        for f in files:
+	    os.remove(f)
+
+    def parse_results(self):
+        dirlist = os.listdir("./")
+        logfile = [x for x in dirlist if "screen.log" in x]
+
+        fullpath = os.path.dirname(os.path.realpath(__file__)) + "/"
+
+        # there should be only one screen.log file (see clean_log_directory)
+        resultParser = SippScreenStatParser(fullpath + logfile[0])
+
+        assert(not resultParser.isAnyFailedCall())
+        assert(resultParser.isAnySuccessfulCall())
+
 class TestSFLPhoneAccountConfig(SflPhoneCtrl):
     """ The test suite for account configuration """
 
@@ -46,14 +102,40 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
         self.logger.addHandler(filehdlr)
         self.logger.setLevel(logging.INFO)
 
-    @nottest
+
+    def get_config(self):
+        """ Parsse configuration file and return a dictionary """
+        config = {}
+        with open("sflphoned.functest.yml","r") as stream:
+            config = yaml.load(stream)
+
+        return config
+
+
+    def get_account_list_from_config(self):
+        """ Get the accout list from config and add IP2IP """
+
+        config = self.get_config()
+
+        accounts = config["preferences"]["order"]
+        accountList = accounts.split('/')
+        del accountList[len(accountList)-1]
+        accountList.append("IP2IP")
+
+        return accountList
+
+
     def test_get_account_list(self):
         self.logger.info("Test get account list")
+
+        accountList = self.get_account_list_from_config()
+
+        # make sure that the intersection between the list is of same size
         accList = self.getAllAccounts()
         listIntersection = set(accList) & set(accountList)
         assert len(listIntersection) == len(accountList)
 
-    @nottest
+
     def test_account_registration(self):
         self.logger.info("Test account registration")
         accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
@@ -72,6 +154,24 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
 
             assert self.isAccountRegistered(acc)
 
+
+    @nottest
+    def test_get_account_details(self):
+        self.logger.info("Test account details")
+
+        accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
+
+        config = self.get_config()
+
+        accountDetails = {}
+        for acc in accList:
+            accountDetails[acc] = self.getAccountDetails(acc)
+
+        accountConfDetails = {}
+        for accConf in config["accounts"]:
+            accountConfDetails[accConf["id"]] = accConf
+
+
     @nottest
     def test_add_remove_account(self):
         self.logger.info("Test add/remove account")
@@ -99,11 +199,12 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
 
 
 
-class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
+class TestSFLPhoneRegisteredCalls(SflPhoneCtrl, SippCtrl):
     """ The test suite for call interaction """
 
     def __init__(self):
         SflPhoneCtrl.__init__(self)
+        SippCtrl.__init__(self)
 
         self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls")
         filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log")
@@ -113,8 +214,6 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
         self.logger.setLevel(logging.INFO)
         self.sippRegistrationInstance = SippWrapper()
         self.sippCallInstance = SippWrapper()
-        self.localInterface = "127.0.0.1"
-        self.localPort = str(5064)
 
         # Make sure the test directory is populated with most recent log files
         self.clean_log_directory()
@@ -143,68 +242,144 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
         self.stopThread()
 
 
-    def clean_log_directory(self):
-        dirlist = os.listdir("./")
-        files = [x for x in dirlist if "screen.log" in x]
-        for f in files:
-	    os.remove(f)
+    def test_registered_call(self):
+        self.logger.info("Test Registered Call")
 
+        # Launch a sipp instance for account registration on asterisk
+        # this account will then be used to receive call from sflphone
+        self.initialize_sipp_registration_instance(self.sippRegistrationInstance, "uac_register_no_cvs_300.xml")
+        regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess,
+                                                          args=(self.sippRegistrationInstance, 5064,))
+        regd.start()
 
-    def find_sipp_pid(self):
-        # Retreive the PID of the last
-        # The /proc/PID/cmdline contain the command line from
-        pids = [int(x) for x in os.listdir("/proc") if x.isdigit()]
-        sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()]
+        # wait for the registration to complete
+        regd.join()
 
-        return sippPid[0]
+        # Launch a sipp instance waiting for a call from previously registered account
+        self.initialize_sipp_call_instance(self.sippCallInstance)
+        calld = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess,
+                                                      args=(self.sippCallInstance, 5064,))
+        calld.start()
 
+        # Make sure every account are enabled
+        accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
+        for acc in accList:
+            if not self.isAccountRegistered(acc):
+                self.setAccountEnable(acc, True)
 
-    def parse_results(self):
-        dirlist = os.listdir("./")
-        logfile = [x for x in dirlist if "screen.log" in x]
-        print logfile
+        # Make a call to the SIPP instance
+        self.Call("300")
 
-        fullpath = os.path.dirname(os.path.realpath(__file__)) + "/"
+        # Start the threaded loop to handle GLIB cllbacks
+        self.start()
 
-        # there should be only one screen.log file (see clean_log_directory)
-        resultParser = SippScreenStatParser(fullpath + logfile[0])
+        # Wait for the sipp instance to dump log files
+        calld.join()
 
-        assert(not resultParser.isAnyFailedCall())
-        assert(resultParser.isAnySuccessfulCall())
+        self.stopThread()
+        self.parse_results()
 
 
-    def test_registered_call(self):
-        self.logger.info("Test Registered Call")
+class TestSFLPhoneConferenceCalls(SflPhoneCtrl, SippCtrl):
+    """ Test Conference calls """
 
-        # launch the sipp instance in background
-        # sipp 127.0.0.1:5060 -sf uac_register_no_cvs.xml -i 127.0.0.1 -p 5062
-        self.sippRegistrationInstance.remoteServer = "127.0.0.1"
-        self.sippRegistrationInstance.remotePort = str(5062)
-        self.sippRegistrationInstance.localInterface = self.localInterface
-        self.sippRegistrationInstance.localPort = self.localPort
-        self.sippRegistrationInstance.customScenarioFile = SCENARIO_PATH + "uac_register_no_cvs.xml"
-        self.sippRegistrationInstance.launchInBackground = True
-        self.sippRegistrationInstance.numberOfCall = 1
-        self.sippRegistrationInstance.numberOfSimultaneousCall = 1
+    def __init__(self):
+        SflPhoneCtrl.__init__(self)
+        SippCtrl.__init__(self)
 
-        self.sippRegistrationInstance.launch()
+        self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls")
+        filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log")
+        formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
+        filehdlr.setFormatter(formatter)
+        self.logger.addHandler(filehdlr)
+        self.logger.setLevel(logging.INFO)
+        self.sippRegistrationInstanceA = SippWrapper()
+        self.sippRegistrationInstanceB = SippWrapper()
+        self.sippCallInstanceA = SippWrapper()
+        self.sippCallInstanceB = SippWrapper()
+        self.localPortCallA = str(5064)
+        self.localPortCallB = str(5066)
+        self.callCount = 0
+        self.accountCalls = []
 
-        # wait for this instance of sipp to complete registration
-        sippPid = self.find_sipp_pid()
-        while os.path.exists("/proc/" + str(sippPid)):
-            time.sleep(1)
+        # Make sure the test directory is populated with most recent log files
+        # self.clean_log_directory()
 
-        # sipp -sn uas -p 5062 -i 127.0.0.1
-        self.sippCallInstance.localInterface = self.localInterface
-        self.sippCallInstance.localPort = self.localPort
-        self.sippCallInstance.launchInBackground = True
-        self.sippCallInstance.numberOfCall = 1
-        self.sippCallInstance.numberOfSimultaneousCall = 1
-        self.sippCallInstance.enableTraceScreen = True
 
-        self.sippCallInstance.launch()
+    def onCallCurrent_cb(self):
+        """ On incoming call, answer the call, then hangup """
 
-        sippPid = self.find_sipp_pid()
+        self.callCount += 1
+
+        self.accountCalls.append(self.currentCallId)
+        print "Account List: ", str(self.accountCalls)
+
+        if self.callCount == 2:
+            self.createConference(self.accountCalls[0], self.accountCalls[1])
+
+
+    def onCallRinging_cb(self):
+        """ Display messages when call is ringing """
+
+        print "The call is ringing"
+
+
+    def onCallHangup_cb(self, callId):
+        """ Exit thread when all call are finished """
+
+        if callId in self.accountCalls:
+            self.accountCalls.remove(callId)
+
+            self.callCount -= 1
+            if self.callCount == 0:
+                self.stopThread()
+
+
+    def onCallFailure_cb(self):
+        """ If a failure occurs duing the call, just leave the running thread """
+
+        print "Stopping Thread"
+        self.stopThread()
+
+
+    def onConferenceCreated_cb(self):
+        """ Called once the conference is created """
+
+        print "Conference Created ", self.currentConfId
+        print "Conference Hangup ", self.currentConfId
+
+        self.hangupConference(self.currentConfId)
+
+
+    def test_conference_call(self):
+        self.logger.info("Test Registered Call")
+
+        # launch the sipp instance to register the first participant to astersik
+        self.initialize_sipp_registration_instance(self.sippRegistrationInstanceA, "uac_register_no_cvs_300.xml")
+        regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess,
+                                                                 args=(self.sippRegistrationInstanceA, 5064,))
+        regd.start()
+        regd.join()
+
+        # launch the sipp instance to register the second participant to asterisk
+        self.initialize_sipp_registration_instance(self.sippRegistrationInstanceB, "uac_register_no_cvs_400.xml")
+        regd = multiprocessing.Process(name='sipp2register', target=self.launchSippProcess,
+                                                                 args=(self.sippRegistrationInstanceB, 5066,))
+        regd.start()
+        regd.join()
+
+        # launch the sipp instance waining for call as the first participant
+        self.initialize_sipp_call_instance(self.sippCallInstanceA)
+        calldA = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess,
+                                                              args=(self.sippCallInstanceA, 5064,))
+        calldA.start()
+
+
+        # launch the sipp instance waiting for call as the second particpant
+        self.initialize_sipp_call_instance(self.sippCallInstanceB)
+        calldB = multiprocessing.Process(name='sipp2call', target=self.launchSippProcess,
+                                                               args=(self.sippCallInstanceB, 5066,))
+        calldB.start()
 
         # make sure every account are enabled
         accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
@@ -212,14 +387,26 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
             if not self.isAccountRegistered(acc):
                 self.setAccountEnable(acc, True)
 
-        # Make a call to the SIPP instance
+        # make a call to the SIPP instance
         self.Call("300")
+        self.Call("400")
 
-        # Start Glib mainloop to process callbacks
+        # start the main loop for processing glib callbacks
         self.start()
 
-        # Wait the sipp instance to dump log files
-        while os.path.exists("/proc/" + str(sippPid)):
-            time.sleep(1)
+        print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
+        calldA.join()
+        print "+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+"
+        calldB.join()
+
+        print "====================================================="
 
+        self.stopThread()
         self.parse_results()
+
+
+# callInstance = TestSFLPhoneRegisteredCalls()
+# callInstance.test_registered_call()
+
+confInstance = TestSFLPhoneConferenceCalls()
+confInstance.test_conference_call()
diff --git a/tools/sippxml/uac_register_no_cvs.xml b/tools/sippxml/uac_register_no_cvs_300.xml
similarity index 100%
rename from tools/sippxml/uac_register_no_cvs.xml
rename to tools/sippxml/uac_register_no_cvs_300.xml
diff --git a/tools/sippxml/uac_register_no_cvs_400.xml b/tools/sippxml/uac_register_no_cvs_400.xml
new file mode 100644
index 0000000000..ccceacdf77
--- /dev/null
+++ b/tools/sippxml/uac_register_no_cvs_400.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="ISO-8859-2" ?>
+
+<!--  Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000];
+      (user part of uri, server address, auth tag in each line)
+-->
+
+<scenario name="register_client">
+  <send retrans="500">
+    <![CDATA[
+
+      REGISTER sip:127.0.0.1 SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:300@127.0.0.1>;tag=[call_number]
+      To: <sip:400@127.0.0.1>
+      Call-ID: [call_id]
+      CSeq: [cseq] REGISTER
+      Contact: sip:300@[local_ip]:[local_port]
+      Max-Forwards: 10
+      Expires: 120
+      User-Agent: SIPp/Win32
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <!-- asterisk -->
+  <recv response="200">
+  <!--
+    <action>
+      <ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" />
+    </action>
+  -->
+  </recv>
+
+  <!--
+  <recv request="INVITE" crlf="true">
+  </recv>
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=user1 53655765 2353687637 IN IP[local_ip_type] [local_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
+
+    ]]>
+  </send>
+
+  <recv request="ACK"
+        optional="true"
+        rtd="true"
+        crlf="true">
+  </recv>
+
+  <recv request="BYE">
+  </recv>
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <timewait milliseconds="4000"/>
+
+
+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+  -->
+
+</scenario>
-- 
GitLab