Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/rtt/rtt_red/configs/ast1/extensions.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[general]

[globals]

[rtt-test]

exten => 101,1,NoOp(Calling 101)
same => n,Dial(PJSIP/101)
same => n,Hangup()

exten => 102,1,NoOp(Calling 102)
same => n,Dial(PJSIP/102)
same => n,Hangup()
9 changes: 9 additions & 0 deletions tests/rtt/rtt_red/configs/ast1/manager.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[user]
secret = mysecret
read = all
write = all
91 changes: 91 additions & 0 deletions tests/rtt/rtt_red/configs/ast1/pjsip.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

[global]
type=global
endpoint_identifier_order=ip,username

[system]
type=system

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

;[101]
;type=auth
;auth_type=password
;username=phone101
;password=phone101pw


[101]
type=aor
contact=sip:101@127.0.0.1:5065
max_contacts=4
remove_existing=yes
support_path=yes
qualify_frequency=0

[101]
type=endpoint
context=rtt-test
transport=transport-udp
allow=!all,red,ulaw,alaw,g722,h264
max_text_streams=1
direct_media=no
force_rport=yes
disable_direct_media_on_nat=yes
ice_support=no
allow_transfer=yes
trust_id_inbound=yes
send_diversion=yes
rtp_symmetric=yes
rewrite_contact=yes
tos_text=0
cos_text=0
callerid=phone101
;auth=101
;outbound_auth=101
aors=101
identify_by=ip,username

[101-identify]
type=identify
endpoint=101
match=127.0.0.1:5065

[102]
type=aor
contact=sip:102@127.0.0.1:5066
max_contacts=4
remove_existing=yes
support_path=yes
qualify_frequency=0

[102]
type=endpoint
context=rtt-test
transport=transport-udp
allow=!all,red,ulaw,alaw,g722,h264
max_text_streams=1
direct_media=no
force_rport=yes
disable_direct_media_on_nat=yes
ice_support=no
allow_transfer=yes
trust_id_inbound=yes
send_diversion=yes
rtp_symmetric=yes
rewrite_contact=yes
tos_text=0
cos_text=0
callerid=phone102
;auth=102
;outbound_auth=102
aors=102
identify_by=ip,username

[102-identify]
type=identify
endpoint=102
match=127.0.0.1:5066
249 changes: 249 additions & 0 deletions tests/rtt/rtt_red/run-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#!/usr/bin/env python3
import sys
import os
import logging
import time

from asterisk.test_case import TestCase
LOGGER = logging.getLogger(__name__)

import signal
import subprocess
from twisted.internet import reactor

# pjsua don't support long texts currently
en_txt = "Real-time text (RTT) transmitted"
gr_txt = "πραγματικό κείμενο σε χρόνο"
de_txt = "Echtzeittext übertragen"
cn_txt = "已发送实时文本"
am_txt = "Իրական ժամանակի տեքստ"
hi_txt = "वास्तविक समय का पाठ"

class RTTTest(TestCase):

def __init__(self):
super(RTTTest, self).__init__()
self.reactor_timeout = 90
self.create_asterisk(count=1)
self.ast[0].all_out = True
self.pjsua_rx = None
self.pjsua_tx = None

def run(self):
super(RTTTest, self).run()
LOGGER.info("Starting Asterisk...")
self.ast[0].cli_exec("rtp set debug on")
self.ast[0].cli_exec("pjsip set logger on")
reactor.callLater(3, self.start_pjsua)
def start_pjsua(self):
print("--- Starting ---")
LOGGER.info("Starting Receiver (102) and Caller (101)...")

common_params = [
'--no-tcp', '--text', '--text-red=2','--no-vad',
'--log-level=3', '--app-log-level=3',
'--dis-codec=speex', '--dis-codec=gsm', '--dis-codec=opus',
'--dis-codec=pcma', '--dis-codec=pcmu', '--dis-codec=ilbc',
'--dis-codec=g722', '--dis-codec=t140', '--null-audio'
]

# Receiver (102)
self.pjsua_rx = subprocess.Popen([
'pjsua',
'--id=sip:102@127.0.0.1',
'--ip-addr=127.0.0.1',
'--local-port=5066',
'--rtp-port=40000',
'--outbound=sip:127.0.0.1:5060',
'--auto-answer=200'
] + common_params, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)

# Caller (101)
self.pjsua_tx = subprocess.Popen([
'pjsua',
'--id=sip:101@127.0.0.1',
'--ip-addr=127.0.0.1',
'--local-port=5065',
'--rtp-port=30000',
'--outbound=sip:127.0.0.1:5060'
] + common_params, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)

reactor.callLater(3, self.make_call)

def make_call(self):
LOGGER.info("pjsua_tx: Dialing 102...")
self.pjsua_tx.stdin.write(b"\n")
self.pjsua_tx.stdin.write(b"m\n")
self.pjsua_tx.stdin.write(b"sip:102@127.0.0.1:5060\n")
self.pjsua_tx.stdin.flush()
print("--- 102 calling ---")
reactor.callLater(2, self.send_en)

def send_en(self):
print("Starting Ping-Pong RTT test...")
LOGGER.info("Starting Ping-Pong RTT test...")
self.pjsua_tx.stdin.write(b"rt\n")
self.pjsua_tx.stdin.flush()
self.pjsua_tx.stdin.write(en_txt.encode('utf-8') + b"\n")
self.pjsua_tx.stdin.flush()
print("101 wrote 'English'...")
reactor.callLater(2, self.step_gr)

def step_gr(self):
self.pjsua_rx.stdin.write(b"rt\n")
self.pjsua_rx.stdin.flush()
self.pjsua_rx.stdin.write(gr_txt.encode('utf-8') + b"\n")
self.pjsua_rx.stdin.flush()
print("102 wrote 'Greek'...")
reactor.callLater(2, self.step_de)

def step_de(self):
# send Esc-cr-rt, as we probably have some console output
self.pjsua_tx.stdin.write(b"\x1b\nrt\n")
self.pjsua_tx.stdin.flush()
self.pjsua_tx.stdin.write(de_txt.encode('utf-8') + b"\n")
self.pjsua_tx.stdin.flush()
print(f"101 wrote 'German'...")
reactor.callLater(2, self.step_cn)

def step_cn(self):
self.pjsua_rx.stdin.write(b"\x1b\nrt\n")
self.pjsua_rx.stdin.flush()
self.pjsua_rx.stdin.write(cn_txt.encode('utf-8') + b"\n")
self.pjsua_rx.stdin.flush()
print(f"102 wrote 'Chinese'...")
reactor.callLater(2, self.step_am)

def step_am(self):
self.pjsua_tx.stdin.write(b"\x1b\nrt\n")
self.pjsua_tx.stdin.flush()
self.pjsua_tx.stdin.write(am_txt.encode('utf-8') + b"\n")
self.pjsua_tx.stdin.flush()
print(f"101 wrote 'Armenian'...")
reactor.callLater(2, self.step_hi)

def step_hi(self):
self.pjsua_rx.stdin.write(b"\x1b\nrt\n")
self.pjsua_rx.stdin.flush()
self.pjsua_rx.stdin.write(hi_txt.encode('utf-8') + b"\n")
self.pjsua_rx.stdin.flush()
print(f"102 wrote 'Hindi'...")
reactor.callLater(2, self.finish)

def finish(self):
LOGGER.info("Verifying message exchange...")
print("Verifying message exchange...")
try:
if self.pjsua_tx and self.pjsua_tx.stdin:
self.pjsua_tx.stdin.write(b"q\n")
self.pjsua_tx.stdin.flush()
if self.pjsua_rx and self.pjsua_rx.stdin:
self.pjsua_rx.stdin.write(b"q\n")
self.pjsua_rx.stdin.flush()
except (BrokenPipeError, OSError):
LOGGER.warning("Could not send 'q' to pjsua; process likely already dead.")

out_tx, err_tx = b"", b""
out_rx, err_rx = b"", b""

try:
if self.pjsua_tx:
out_tx, err_tx = self.pjsua_tx.communicate(timeout=3)

if self.pjsua_rx:
out_rx, err_rx = self.pjsua_rx.communicate(timeout=3)
except (subprocess.TimeoutExpired, ValueError, OSError):
self.pjsua_tx.kill()
self.pjsua_rx.kill()
out_tx, err_tx = self.pjsua_tx.communicate()
out_rx, err_rx = self.pjsua_rx.communicate()

# 1. Handle TX (101)
if out_tx is None:
print("out_tx is none...")
out_tx = ""
elif isinstance(out_tx, bytes):
out_tx = out_tx.decode('utf-8', errors='replace')
print(f"out_tx is==== {out_tx}===")

# 2. Handle RX (102)
if out_rx is None:
print("out_rx is none...")
out_rx = ""
elif isinstance(out_rx, bytes):
out_rx = out_rx.decode('utf-8', errors='replace')
print(f"out_rx is==== {out_rx}====")

text_tx = self.print_pjsua_logs("TX (101)", out_tx, err_tx)
text_rx = self.print_pjsua_logs("RX (102)", out_rx, err_rx)
self.stop_reactor()

rx_saw_english = "Incoming text" in text_rx and en_txt in text_rx
tx_saw_greek = "Incoming text" in text_tx and gr_txt in text_tx
rx_saw_german = "Incoming text" in text_rx and de_txt in text_rx
tx_saw_chinese = "Incoming text" in text_tx and cn_txt in text_tx
rx_saw_armenian = "Incoming text" in text_rx and am_txt in text_rx
tx_saw_hindi = "Incoming text" in text_tx and hi_txt in text_tx

if not rx_saw_english and not tx_saw_greek and not rx_saw_german and not tx_saw_chinese and not rx_saw_armenian and not tx_saw_hindi:
LOGGER.info("SUCCESS: No RTT exchange T140 disabled, as expected!")
print("SUCCESS.")
self.passed = True
else:
if rx_saw_english:
LOGGER.error("FAILURE: 102 (Receiver) saw 'English'")
if x_saw_greek:
LOGGER.error("FAILURE: 101 (Caller) saw 'Greek'")
if rx_saw_german:
LOGGER.error("FAILURE: 102 (Receiver) saw 'German'")
if tx_saw_chinese:
LOGGER.error("FAILURE: 101 (Caller) saw 'Chinese'")
if rx_saw_armenian:
LOGGER.error("FAILURE: 102 (Receiver) saw 'Armenian'")
if _saw_hindi:
LOGGER.error("FAILURE: 101 (Caller) saw 'Hindi'")
if "401" in text_rx or "401" in text_tx:
LOGGER.error("DEBUG: Found '401 Unauthorized' in logs")
self.passed = False

def print_pjsua_logs(self, label, output, error):
content = ""

if output is not None:
if isinstance(output, bytes):

content = output.decode('utf-8', 'ignore')
else:
content = str(output)

print(f"--- STARTING {label} LOG DUMP ---")
print(content)
print(f"--- END {label} LOG DUMP ---")

if error:
err_msg = error.decode('utf-8', 'ignore') if isinstance(error, bytes) else str(error)
print(f"{label} reported an error: {err_msg}")

return content

def stop_reactor(self):
for p in [self.pjsua_rx, self.pjsua_tx]:
if p:
try:
os.kill(p.pid, signal.SIGKILL)
except OSError:
pass
super(RTTTest, self).stop_reactor()


def main():
test = RTTTest()
reactor.run()
return 0 if test.passed else 1

if __name__ == "__main__":
sys.exit(main() or 0)
17 changes: 17 additions & 0 deletions tests/rtt/rtt_red/test-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
testinfo:
summary: 'Bidirectional RTT Ping-Pong Test'
description: 'Verifies T.140 RTT media relay between 101 and 102.'
test-modules:
test-object:
config-section: rtt-test
typename: run-test.RTTTest
rtt-test:
asterisk-instances: 1
timeout: 70
properties:
dependencies:
- python : 'twisted'
- app : 'pjsua'
#- asterisk: 'res_pjsip'
tags:
- pjsip
Loading