From b6f34096af829f0e44d06d6767535b47b5c75dce Mon Sep 17 00:00:00 2001
From: Richard Fuchs <rfuchs@sipwise.com>
Date: Fri, 14 Mar 2025 11:56:36 -0400
Subject: [PATCH] MT#55283 allow overriding T.38 version

Change-Id: I2e8040774ceb7faaa65a2ec6e69bc67be0260caa
---
 daemon/call_interfaces.c    |  12 ++++
 daemon/codec.c              |   5 ++
 docs/ng_control_protocol.md |   8 +++
 include/call_interfaces.h   |   1 +
 t/auto-daemon-tests-t38.pl  | 108 ++++++++++++++++++++++++++++++++++++
 5 files changed, 134 insertions(+)

diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c
index 8e8522922..90a5b398f 100644
--- a/daemon/call_interfaces.c
+++ b/daemon/call_interfaces.c
@@ -1449,6 +1449,7 @@ void call_ng_flags_init(sdp_ng_flags *out, enum ng_opmode opmode) {
 	out->frequencies = g_array_new(false, false, sizeof(int));
 	for (int i = 0; i < __MT_MAX; ++i)
 		out->sdp_media_remove[i] = false;
+	out->t38_version = -1;
 }
 
 static void call_ng_direction_flag_iter(str *s, unsigned int i, helper_arg arg) {
@@ -2115,6 +2116,17 @@ void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, h
 		case CSH_LOOKUP("t.38"):
 			call_ng_flags_str_list(parser, value, ng_t38_option, out);
 			break;
+
+		case CSH_LOOKUP("T38-version"):
+		case CSH_LOOKUP("T.38-version"):
+		case CSH_LOOKUP("t38-version"):
+		case CSH_LOOKUP("t.38-version"):
+		case CSH_LOOKUP("T38 version"):
+		case CSH_LOOKUP("T.38 version"):
+		case CSH_LOOKUP("t38 version"):
+		case CSH_LOOKUP("t.38 version"):
+			out->t38_version = parser->get_int_str(value, out->t38_version);
+			break;
 #endif
 		case CSH_LOOKUP("template"):;
 			str *tplate = t_hash_table_lookup(rtpe_signalling_templates, &s);
diff --git a/daemon/codec.c b/daemon/codec.c
index 629235dfc..6c7b82efa 100644
--- a/daemon/codec.c
+++ b/daemon/codec.c
@@ -899,6 +899,10 @@ static void __t38_options_from_flags(struct t38_options *t_opts, const sdp_ng_fl
 	t38_opt(no_v29);
 	t38_opt(no_v34);
 	t38_opt(no_iaf);
+#undef t38_opt
+
+	if (flags && flags->t38_version >= 0)
+		t_opts->version = flags->t38_version;
 }
 
 static void __check_t38_gateway(struct call_media *pcm_media, struct call_media *t38_media,
@@ -914,6 +918,7 @@ static void __check_t38_gateway(struct call_media *pcm_media, struct call_media
 			t_opts.fec_span = 3;
 		t_opts.max_ec_entries = 3;
 	}
+
 	__t38_options_from_flags(&t_opts, flags);
 
 	MEDIA_SET(pcm_media, GENERATOR);
diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md
index 5a466ad5d..d2d2769c8 100644
--- a/docs/ng_control_protocol.md
+++ b/docs/ng_control_protocol.md
@@ -860,6 +860,14 @@ Optionally included keys are:
 		Use UDPTL FEC instead of redundancy. Only useful with `T.38=force` as
 		it's a negotiated parameter.
 
+* `T.38 version`
+
+    Sets the T.38 version number to use for the T.38 gateway. The default is
+    version zero, or to go along with what has been advertised in the SDP if
+    responding to a received T.38 offer. Overriding the version to zero
+    regardless of what has been advertised in the SDP can solve T.38 gateway
+    problems against certain endpoints.
+
 * `volume`
 
 	Sets the tone volume for `DTMF-security` modes `tone`, `zero, `DTMF`,
diff --git a/include/call_interfaces.h b/include/call_interfaces.h
index e462ba1a7..4860ed648 100644
--- a/include/call_interfaces.h
+++ b/include/call_interfaces.h
@@ -155,6 +155,7 @@ struct sdp_ng_flags {
 	str vsc_pause_rec;
 	str vsc_pause_resume_rec;
 	str vsc_start_pause_resume_rec;
+	int t38_version;
 
 #define X(x) str_q x;
 RTPE_NG_FLAGS_STR_Q_PARAMS
diff --git a/t/auto-daemon-tests-t38.pl b/t/auto-daemon-tests-t38.pl
index b2e0ca16f..01c66e7d6 100755
--- a/t/auto-daemon-tests-t38.pl
+++ b/t/auto-daemon-tests-t38.pl
@@ -681,6 +681,114 @@ t38_gw_test('FEC span 5',
 
 
 
+new_call;
+
+offer('override T.38 version control', { 'T.38' => [ 'decode' ] }, <<SDP);
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.1
+s=tester
+t=0 0
+m=image 4000 udptl t38
+c=IN IP4 198.51.100.1
+a=sendrecv
+a=T38FaxVersion:1
+a=T38MaxBitRate:14400
+a=T38FaxRateManagement:transferredTCF
+a=T38FaxMaxBuffer:262
+a=T38FaxMaxDatagram:300
+a=T38FaxUdpEC:t38UDPRedundancy
+----------------------------------
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.1
+s=tester
+t=0 0
+m=audio PORT RTP/AVP 0 8
+c=IN IP4 203.0.113.1
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=sendrecv
+a=rtcp:PORT
+SDP
+
+answer('override T.38 version control', { }, <<SDP);
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.3
+s=tester
+t=0 0
+m=audio 4002 RTP/AVP 8
+c=IN IP4 198.51.100.3
+a=sendrecv
+--------------------------------------
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.3
+s=tester
+t=0 0
+m=image PORT udptl t38
+c=IN IP4 203.0.113.1
+a=T38FaxVersion:1
+a=T38MaxBitRate:14400
+a=T38FaxRateManagement:transferredTCF
+a=T38FaxMaxBuffer:1800
+a=T38FaxMaxDatagram:512
+a=T38FaxUdpEC:t38UDPRedundancy
+a=sendrecv
+SDP
+
+
+new_call;
+
+offer('override T.38 version', { 'T.38' => [ 'decode' ], 'T.38 version' => 0 }, <<SDP);
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.1
+s=tester
+t=0 0
+m=image 4000 udptl t38
+c=IN IP4 198.51.100.1
+a=sendrecv
+a=T38FaxVersion:1
+a=T38MaxBitRate:14400
+a=T38FaxRateManagement:transferredTCF
+a=T38FaxMaxBuffer:262
+a=T38FaxMaxDatagram:300
+a=T38FaxUdpEC:t38UDPRedundancy
+----------------------------------
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.1
+s=tester
+t=0 0
+m=audio PORT RTP/AVP 0 8
+c=IN IP4 203.0.113.1
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=sendrecv
+a=rtcp:PORT
+SDP
+
+answer('override T.38 version', { }, <<SDP);
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.3
+s=tester
+t=0 0
+m=audio 4002 RTP/AVP 8
+c=IN IP4 198.51.100.3
+a=sendrecv
+--------------------------------------
+v=0
+o=- 1545997027 1 IN IP4 198.51.100.3
+s=tester
+t=0 0
+m=image PORT udptl t38
+c=IN IP4 203.0.113.1
+a=T38FaxVersion:0
+a=T38MaxBitRate:14400
+a=T38FaxRateManagement:transferredTCF
+a=T38FaxMaxBuffer:1800
+a=T38FaxMaxDatagram:512
+a=T38FaxUdpEC:t38UDPRedundancy
+a=sendrecv
+SDP
+
+
 
 # XXX packet loss tests
 # XXX tests of different SDP options