1 /*
2  * t38proto.cxx
3  *
4  * T.38 protocol handler
5  *
6  * Open Phone Abstraction Library
7  *
8  * Copyright (c) 1998-2002 Equivalence Pty. Ltd.
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Open H323 Library.
21  *
22  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23  *
24  * Contributor(s): Vyacheslav Frolov.
25  *
26  * $Revision: 28445 $
27  * $Author: rjongbloed $
28  * $Date: 2012-10-02 20:11:02 -0500 (Tue, 02 Oct 2012) $
29  */
30 
31 #include <ptlib.h>
32 
33 #ifdef __GNUC__
34 #pragma implementation "t38proto.h"
35 #endif
36 
37 #include <opal/buildopts.h>
38 
39 #include <t38/t38proto.h>
40 #include <opal/patch.h>
41 #include <codec/opalpluginmgr.h>
42 
43 
44 /////////////////////////////////////////////////////////////////////////////
45 
46 #if OPAL_FAX
47 
48 #include <asn/t38.h>
49 
50 OPAL_DEFINE_MEDIA_COMMAND(OpalFaxTerminate, PLUGINCODEC_CONTROL_TERMINATE_CODEC);
51 
52 #define new PNEW
53 
54 
55 static const char TIFF_File_FormatName[] = OPAL_FAX_TIFF_FILE;
56 
57 
58 /////////////////////////////////////////////////////////////////////////////
59 
60 class OpalFaxMediaStream : public OpalNullMediaStream
61 {
62   public:
OpalFaxMediaStream(OpalFaxConnection & conn,const OpalMediaFormat & mediaFormat,unsigned sessionID,bool isSource)63     OpalFaxMediaStream(OpalFaxConnection & conn,
64                        const OpalMediaFormat & mediaFormat,
65                        unsigned sessionID,
66                        bool isSource)
67       : OpalNullMediaStream(conn, mediaFormat, sessionID, isSource, isSource, true)
68       , m_connection(conn)
69     {
70       m_isAudio = true; // Even though we are not REALLY audio, act like we are
71     }
72 
InternalClose()73     virtual void InternalClose()
74     {
75       if (m_connection.m_state == OpalFaxConnection::e_CompletedSwitch &&
76           m_connection.m_finalStatistics.m_fax.m_result < 0) {
77         // make a referenced copy so can't be deleted out from under us
78         PatchPtr patch = m_mediaPatch;
79         if (patch != NULL)
80           patch->ExecuteCommand(OpalFaxTerminate(), false);
81         GetStatistics(m_connection.m_finalStatistics);
82         PTRACE(4, "FAX\tGot final statistics: result=" << m_connection.m_finalStatistics.m_fax.m_result);
83       }
84 
85       OpalNullMediaStream::InternalClose();
86     }
87 
88   private:
89     OpalFaxConnection & m_connection;
90 };
91 
92 
93 /////////////////////////////////////////////////////////////////////////////
94 
95 class T38PseudoRTP_Handler : public RTP_Encoding
96 {
97   public:
T38PseudoRTP_Handler()98     T38PseudoRTP_Handler()
99     {
100       PStringToString options;
101 
102       options.SetAt("T38-UDPTL-Redundancy", "32767:1");           // re-send all ifp packets 1 time
103       options.SetAt("T38-UDPTL-Redundancy-Interval", "0");        // re-send redundancy ifp packets only with next ifp
104       options.SetAt("T38-UDPTL-Keep-Alive-Interval", "0");        // no send keep-alive packets
105       options.SetAt("T38-UDPTL-Optimise-On-Retransmit", "false"); // not optimise udptl packets on retransmit
106 
107       ApplyStringOptions(options);
108     }
109 
110 
OnStart(RTP_Session & _rtpUDP)111     void OnStart(RTP_Session & _rtpUDP)
112     {
113       RTP_Encoding::OnStart(_rtpUDP);
114 
115       rtpUDP->SetJitterBufferSize(0, 0);
116       m_consecutiveBadPackets  = 0;
117       m_awaitingGoodPacket     = true;
118       m_expectedSequenceNumber = 0;
119       m_secondaryPacket        = -1;
120 
121       m_sentPacketRedundancy.clear();
122       m_sentPacket = T38_UDPTLPacket();
123       m_sentPacket.m_error_recovery.SetTag(T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets);
124       m_sentPacket.m_seq_number = (unsigned)-1;
125       rtpUDP->SetNextSentSequenceNumber(0);
126     }
127 
128 
ApplyStringOptions(const PStringToString & stringOptions)129     void ApplyStringOptions(const PStringToString & stringOptions)
130     {
131       for (PINDEX i = 0 ; i < stringOptions.GetSize() ; i++) {
132         PCaselessString key = stringOptions.GetKeyAt(i);
133 
134         if (key == "T38-UDPTL-Redundancy") {
135           PStringArray value = stringOptions.GetDataAt(i).Tokenise(",", FALSE);
136           PWaitAndSignal mutex(m_writeMutex);
137 
138           m_redundancy.clear();
139 
140           for (PINDEX i = 0 ; i < value.GetSize() ; i++) {
141             PStringArray pair = value[i].Tokenise(":", FALSE);
142 
143             if (pair.GetSize() == 2) {
144               long size = pair[0].AsInteger();
145               long redundancy = pair[1].AsInteger();
146 
147               if (size > INT_MAX)
148                 size = INT_MAX;
149 
150               if (size > 0 && redundancy >= 0) {
151                 m_redundancy[(int)size] = (int)redundancy;
152                 continue;
153               }
154             }
155 
156             PTRACE(2, "T38_UDPTL\tIgnored redundancy \"" << value[i] << '"');
157           }
158 
159 #if PTRACING
160           if (PTrace::CanTrace(3)) {
161             ostream & trace = PTrace::Begin(3, __FILE__, __LINE__);
162             trace << "T38_UDPTL\tUse redundancy \"";
163             for (std::map<int, int>::iterator it = m_redundancy.begin() ; it != m_redundancy.end() ; it++) {
164               if (it != m_redundancy.begin())
165                 trace << ",";
166               trace << it->first << ':' << it->second;
167             }
168             trace << '"' << PTrace::End;
169           }
170 #endif
171         }
172         else
173         if (key == "T38-UDPTL-Redundancy-Interval") {
174           PWaitAndSignal mutex(m_writeMutex);
175           m_redundancyInterval = stringOptions.GetDataAt(i).AsUnsigned();
176           PTRACE(3, "T38_UDPTL\tUse redundancy interval " << m_redundancyInterval);
177         }
178         else
179         if (key == "T38-UDPTL-Keep-Alive-Interval") {
180           PWaitAndSignal mutex(m_writeMutex);
181           m_keepAliveInterval = stringOptions.GetDataAt(i).AsUnsigned();
182           PTRACE(3, "T38_UDPTL\tUse keep-alive interval " << m_keepAliveInterval);
183         }
184         else
185         if (key == "T38-UDPTL-Optimise-On-Retransmit") {
186           PCaselessString value = stringOptions.GetDataAt(i);
187           PWaitAndSignal mutex(m_writeMutex);
188 
189           m_optimiseOnRetransmit =
190             (value.IsEmpty() || (value == "true") || (value == "yes") || value.AsInteger() != 0);
191 
192           PTRACE(3, "T38_UDPTL\tUse optimise on retransmit - " << (m_optimiseOnRetransmit ? "true" : "false"));
193         }
194         else {
195           PTRACE(4, "T38_UDPTL\tIgnored option " << key << " = \"" << stringOptions.GetDataAt(i) << '"');
196         }
197       }
198     }
199 
200 
WriteData(RTP_DataFrame & frame,bool oob)201     PBoolean WriteData(RTP_DataFrame & frame, bool oob)
202     {
203       if (oob)
204         return false;
205 
206       return RTP_Encoding::WriteData(frame, false);
207     }
208 
209 
OnSendData(RTP_DataFrame & frame)210     RTP_Session::SendReceiveStatus OnSendData(RTP_DataFrame & frame)
211     {
212       RTP_Session::SendReceiveStatus status = RTP_Encoding::OnSendData(frame);
213 
214       if (status == RTP_Session::e_ProcessPacket && frame.GetPayloadSize() == 0)
215         status = RTP_Session::e_IgnorePacket;
216 
217       return status;
218     }
219 
220 
WriteDataPDU(RTP_DataFrame & frame)221     bool WriteDataPDU(RTP_DataFrame & frame)
222     {
223       PINDEX plLen = frame.GetPayloadSize();
224 
225       if (plLen == 0) {
226         PTRACE(2, "T38_UDPTL\tInternal error - empty payload");
227         return false;
228       }
229 
230       PWaitAndSignal mutex(m_writeMutex);
231 
232       if (!m_sentPacketRedundancy.empty()) {
233         T38_UDPTLPacket_error_recovery &recovery = m_sentPacket.m_error_recovery;
234 
235         if (recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
236           // shift old primary ifp packet to secondary list
237 
238           T38_UDPTLPacket_error_recovery_secondary_ifp_packets &secondary = recovery;
239 
240           if (secondary.SetSize(secondary.GetSize() + 1)) {
241             for (int i = secondary.GetSize() - 2 ; i >= 0 ; i--) {
242               secondary[i + 1] = secondary[i];
243               secondary[i] = T38_UDPTLPacket_error_recovery_secondary_ifp_packets_subtype();
244             }
245 
246             secondary[0].SetValue(m_sentPacket.m_primary_ifp_packet.GetValue());
247             m_sentPacket.m_primary_ifp_packet = T38_UDPTLPacket_primary_ifp_packet();
248           }
249         } else {
250           PTRACE(3, "T38_UDPTL\tNot implemented yet " << recovery.GetTagName());
251         }
252       }
253 
254       // calculate redundancy for new ifp packet
255 
256       int redundancy = 0;
257 
258       for (std::map<int, int>::iterator it = m_redundancy.begin() ; it != m_redundancy.end() ; it++) {
259         if (plLen <= it->first) {
260           if (redundancy < it->second)
261             redundancy = it->second;
262 
263           break;
264         }
265       }
266 
267       if (redundancy > 0 || !m_sentPacketRedundancy.empty())
268         m_sentPacketRedundancy.insert(m_sentPacketRedundancy.begin(), redundancy + 1);
269 
270       // set new primary ifp packet
271 
272       m_sentPacket.m_seq_number = frame.GetSequenceNumber();
273       m_sentPacket.m_primary_ifp_packet.SetValue(frame.GetPayloadPtr(), plLen);
274 
275       bool ok = WriteUDPTL();
276 
277       DecrementSentPacketRedundancy(true);
278 
279       return ok;
280     }
281 
282 
OnWriteDataIdle()283     void OnWriteDataIdle()
284     {
285       PWaitAndSignal mutex(m_writeMutex);
286 
287       WriteUDPTL();
288 
289       DecrementSentPacketRedundancy(m_optimiseOnRetransmit);
290     }
291 
292 
SetWriteDataIdleTimer(PTimer & timer)293     void SetWriteDataIdleTimer(PTimer & timer)
294     {
295       PWaitAndSignal mutex(m_writeMutex);
296 
297       if (m_sentPacketRedundancy.empty() || m_redundancyInterval <= 0)
298         timer = m_keepAliveInterval;
299       else
300         timer = m_redundancyInterval;
301     }
302 
303 
DecrementSentPacketRedundancy(bool stripRedundancy)304     void DecrementSentPacketRedundancy(bool stripRedundancy)
305     {
306       int iMax = (int)m_sentPacketRedundancy.size() - 1;
307 
308       for (int i = iMax ; i >= 0 ; i--) {
309         m_sentPacketRedundancy[i]--;
310 
311         if (i == iMax && m_sentPacketRedundancy[i] <= 0)
312           iMax--;
313       }
314 
315       m_sentPacketRedundancy.resize(iMax + 1);
316 
317       if (stripRedundancy) {
318         T38_UDPTLPacket_error_recovery &recovery = m_sentPacket.m_error_recovery;
319 
320         if (recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
321           T38_UDPTLPacket_error_recovery_secondary_ifp_packets &secondary = recovery;
322           secondary.SetSize(iMax > 0 ? iMax : 0);
323         } else {
324           PTRACE(3, "T38_UDPTL\tNot implemented yet " << recovery.GetTagName());
325         }
326       }
327     }
328 
329 
WriteUDPTL()330     bool WriteUDPTL()
331     {
332       PTRACE(5, "T38_UDPTL\tEncoded transmitted UDPTL data :\n  " << setprecision(2) << m_sentPacket);
333 
334       PPER_Stream rawData;
335       m_sentPacket.Encode(rawData);
336       rawData.CompleteEncoding();
337 
338       PTRACE(4, "T38_UDPTL\tSending UDPTL of size " << rawData.GetSize());
339 
340       return rtpUDP->WriteDataOrControlPDU(rawData.GetPointer(), rawData.GetSize(), true);
341     }
342 
343 
OnSendControl(RTP_ControlFrame &,PINDEX &)344     RTP_Session::SendReceiveStatus OnSendControl(RTP_ControlFrame & /*frame*/, PINDEX & /*len*/)
345     {
346       return RTP_Session::e_IgnorePacket; // Non fatal error, just ignore
347     }
348 
349 
WaitForPDU(PUDPSocket & dataSocket,PUDPSocket & controlSocket,const PTimeInterval &)350     int WaitForPDU(PUDPSocket & dataSocket, PUDPSocket & controlSocket, const PTimeInterval &)
351     {
352       if (m_secondaryPacket >= 0)
353         return -1; // Force immediate call to ReadDataPDU
354 
355       // Break out once a second so closes down in orderly fashion
356       return PSocket::Select(dataSocket, controlSocket, 1000);
357     }
358 
359 
OnReadTimeout(RTP_DataFrame & frame)360     RTP_Session::SendReceiveStatus OnReadTimeout(RTP_DataFrame & frame)
361     {
362       // Override so do not do sender reports (RTP only) and push
363       // through a zero length packet so checks for orderly shut down
364       frame.SetPayloadSize(0);
365       return RTP_Session::e_ProcessPacket;
366     }
367 
368 
SetFrameFromIFP(RTP_DataFrame & frame,const PASN_OctetString & ifp,unsigned sequenceNumber)369     void SetFrameFromIFP(RTP_DataFrame & frame, const PASN_OctetString & ifp, unsigned sequenceNumber)
370     {
371       frame.SetPayloadSize(ifp.GetDataLength());
372       memcpy(frame.GetPayloadPtr(), (const BYTE *)ifp, ifp.GetDataLength());
373       frame.SetSequenceNumber((WORD)(sequenceNumber & 0xffff));
374       if (m_secondaryPacket <= 0)
375         m_expectedSequenceNumber = sequenceNumber+1;
376     }
377 
ReadDataPDU(RTP_DataFrame & frame)378     RTP_Session::SendReceiveStatus ReadDataPDU(RTP_DataFrame & frame)
379     {
380       if (m_secondaryPacket >= 0) {
381         if (m_secondaryPacket == 0)
382           SetFrameFromIFP(frame, m_receivedPacket.m_primary_ifp_packet, m_receivedPacket.m_seq_number);
383         else {
384           T38_UDPTLPacket_error_recovery_secondary_ifp_packets & secondaryPackets = m_receivedPacket.m_error_recovery;
385           SetFrameFromIFP(frame, secondaryPackets[m_secondaryPacket-1], m_receivedPacket.m_seq_number - m_secondaryPacket);
386         }
387         --m_secondaryPacket;
388         return RTP_Session::e_ProcessPacket;
389       }
390 
391       BYTE thisUDPTL[500];
392       RTP_Session::SendReceiveStatus status = rtpUDP->ReadDataOrControlPDU(thisUDPTL, sizeof(thisUDPTL), true);
393       if (status != RTP_Session::e_ProcessPacket)
394         return status;
395 
396       PINDEX pduSize = rtpUDP->GetDataSocket().GetLastReadCount();
397 
398       PTRACE(4, "T38_UDPTL\tRead UDPTL of size " << pduSize);
399 
400       PPER_Stream rawData(thisUDPTL, pduSize);
401 
402       // Decode the PDU, but not if still receiving RTP
403       bool decodeBad = !m_receivedPacket.Decode(rawData);
404       if (decodeBad || (m_awaitingGoodPacket && m_receivedPacket.m_seq_number >= 32768)) {
405         if (++m_consecutiveBadPackets > 1000) {
406           PTRACE(1, "T38_UDPTL\tRaw data decode failed 1000 times, remote probably not switched from audio, aborting!");
407           return RTP_Session::e_AbortTransport;
408         }
409 
410  #if PTRACING
411         static const unsigned Level = 2;
412         if (PTrace::CanTrace(Level)) {
413           ostream & trace = PTrace::Begin(Level, __FILE__, __LINE__);
414           trace << "T38_UDPTL\t";
415           if (m_awaitingGoodPacket)
416             trace << "Probable RTP packet: " << rawData.GetSize() << " bytes.";
417           else
418             trace << "Raw data decode failure:\n  "
419                   << setprecision(2) << rawData << "\n  UDPTL = "
420                   << setprecision(2) << m_receivedPacket;
421           trace << PTrace::End;
422         }
423   #endif
424 
425         return RTP_Session::e_IgnorePacket;
426       }
427 
428       PTRACE_IF(3, m_awaitingGoodPacket, "T38_UDPTL\tFirst decoded UDPTL packet");
429       m_awaitingGoodPacket = false;
430       m_consecutiveBadPackets = 0;
431 
432       PTRACE(5, "T38_UDPTL\tDecoded UDPTL packet:\n  " << setprecision(2) << m_receivedPacket);
433 
434       int missing = m_receivedPacket.m_seq_number - m_expectedSequenceNumber;
435       if (missing > 0 && m_receivedPacket.m_error_recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
436         // Packets are missing and we have redundency in the UDPTL packets
437         T38_UDPTLPacket_error_recovery_secondary_ifp_packets & secondaryPackets = m_receivedPacket.m_error_recovery;
438         if (secondaryPackets.GetSize() > 0) {
439           PTRACE(4, "T38_UDPTL\tUsing redundant data to reconstruct missing/out of order packet at SN=" << m_expectedSequenceNumber);
440           m_secondaryPacket = missing;
441           if (m_secondaryPacket > secondaryPackets.GetSize())
442             m_secondaryPacket = secondaryPackets.GetSize();
443           SetFrameFromIFP(frame, secondaryPackets[m_secondaryPacket-1], m_receivedPacket.m_seq_number - m_secondaryPacket);
444           --m_secondaryPacket;
445           return RTP_Session::e_ProcessPacket;
446         }
447       }
448 
449       SetFrameFromIFP(frame, m_receivedPacket.m_primary_ifp_packet, m_receivedPacket.m_seq_number);
450       m_expectedSequenceNumber = m_receivedPacket.m_seq_number+1;
451 
452       return RTP_Session::e_ProcessPacket;
453     }
454 
455 
456   protected:
457     int             m_consecutiveBadPackets;
458     bool            m_awaitingGoodPacket;
459     T38_UDPTLPacket m_receivedPacket;
460     unsigned        m_expectedSequenceNumber;
461     int             m_secondaryPacket;
462 
463     std::map<int, int>  m_redundancy;
464     PTimeInterval       m_redundancyInterval;
465     PTimeInterval       m_keepAliveInterval;
466     bool                m_optimiseOnRetransmit;
467     std::vector<int>    m_sentPacketRedundancy;
468     T38_UDPTLPacket     m_sentPacket;
469     PMutex              m_writeMutex;
470 };
471 
472 
473 PFACTORY_CREATE(PFactory<RTP_Encoding>, T38PseudoRTP_Handler, "udptl", false);
474 
475 
476 /////////////////////////////////////////////////////////////////////////////
477 
OpalFaxEndPoint(OpalManager & mgr,const char * g711Prefix,const char * t38Prefix)478 OpalFaxEndPoint::OpalFaxEndPoint(OpalManager & mgr, const char * g711Prefix, const char * t38Prefix)
479   : OpalLocalEndPoint(mgr, g711Prefix)
480   , m_t38Prefix(t38Prefix)
481   , m_defaultDirectory(".")
482 {
483   if (t38Prefix != NULL)
484     mgr.AttachEndPoint(this, m_t38Prefix);
485 
486   PTRACE(3, "Fax\tCreated Fax endpoint");
487 }
488 
489 
~OpalFaxEndPoint()490 OpalFaxEndPoint::~OpalFaxEndPoint()
491 {
492   PTRACE(3, "Fax\tDeleted Fax endpoint.");
493 }
494 
495 
MakeConnection(OpalCall & call,const PString & remoteParty,void * userData,unsigned int,OpalConnection::StringOptions * stringOptions)496 PSafePtr<OpalConnection> OpalFaxEndPoint::MakeConnection(OpalCall & call,
497                                                     const PString & remoteParty,
498                                                              void * userData,
499                                                        unsigned int /*options*/,
500                                     OpalConnection::StringOptions * stringOptions)
501 {
502   if (!OpalMediaFormat(TIFF_File_FormatName).IsValid()) {
503     PTRACE(1, "TIFF File format not valid! Missing plugin?");
504     return NULL;
505   }
506 
507   PINDEX prefixLength = remoteParty.Find(':');
508   PStringArray tokens = remoteParty.Mid(prefixLength+1).Tokenise(";", true);
509   if (tokens.IsEmpty()) {
510     PTRACE(2, "Fax\tNo filename specified!");
511     return NULL;
512   }
513 
514   bool receiving = false;
515   PString stationId = GetDefaultDisplayName();
516 
517   for (PINDEX i = 1; i < tokens.GetSize(); ++i) {
518     if (tokens[i] *= "receive")
519       receiving = true;
520     else if (tokens[i].Left(10) *= "stationid=")
521       stationId = tokens[i].Mid(10);
522   }
523 
524   PString filename = tokens[0];
525   if (!PFilePath::IsAbsolutePath(filename))
526     filename.Splice(m_defaultDirectory, 0);
527 
528   if (!receiving && !PFile::Exists(filename)) {
529     PTRACE(2, "Fax\tCannot find filename '" << filename << "'");
530     return NULL;
531   }
532 
533   OpalConnection::StringOptions localOptions;
534   if (stringOptions == NULL)
535     stringOptions = &localOptions;
536 
537   if ((*stringOptions)("stationid").IsEmpty())
538     stringOptions->SetAt("stationid", stationId);
539 
540   stringOptions->SetAt(OPAL_OPT_DISABLE_JITTER, "1");
541 
542   return AddConnection(CreateConnection(call, userData, stringOptions, filename, receiving,
543                                         remoteParty.Left(prefixLength) *= GetPrefixName()));
544 }
545 
546 
IsAvailable() const547 bool OpalFaxEndPoint::IsAvailable() const
548 {
549   return OpalMediaFormat(OPAL_FAX_TIFF_FILE).IsValid();
550 }
551 
552 
CreateConnection(OpalCall & call,void *,OpalConnection::StringOptions * stringOptions,const PString & filename,bool receiving,bool disableT38)553 OpalFaxConnection * OpalFaxEndPoint::CreateConnection(OpalCall & call,
554                                                       void * /*userData*/,
555                                                       OpalConnection::StringOptions * stringOptions,
556                                                       const PString & filename,
557                                                       bool receiving,
558                                                       bool disableT38)
559 {
560   return new OpalFaxConnection(call, *this, filename, receiving, disableT38, stringOptions);
561 }
562 
563 
GetMediaFormats() const564 OpalMediaFormatList OpalFaxEndPoint::GetMediaFormats() const
565 {
566   OpalMediaFormatList formats;
567   formats += OpalT38;
568   formats += TIFF_File_FormatName;
569 PTRACE(4, "OpalFaxEndPoint\tGetMediaFormats for " << *this << "\n    " << setfill(',') << formats << setfill(' '));
570   return formats;
571 }
572 
573 
OnFaxCompleted(OpalFaxConnection & connection,bool PTRACE_PARAM (failed))574 void OpalFaxEndPoint::OnFaxCompleted(OpalFaxConnection & connection, bool PTRACE_PARAM(failed))
575 {
576   PTRACE(3, "FAX\tFax " << (failed ? "failed" : "completed") << " on connection: " << connection);
577   connection.Release();
578 }
579 
580 
581 /////////////////////////////////////////////////////////////////////////////
582 
OpalFaxConnection(OpalCall & call,OpalFaxEndPoint & ep,const PString & filename,bool receiving,bool disableT38,OpalConnection::StringOptions * stringOptions)583 OpalFaxConnection::OpalFaxConnection(OpalCall        & call,
584                                      OpalFaxEndPoint & ep,
585                                      const PString   & filename,
586                                      bool              receiving,
587                                      bool              disableT38,
588                                      OpalConnection::StringOptions * stringOptions)
589   : OpalLocalConnection(call, ep, NULL, 0, stringOptions, 'F')
590   , m_endpoint(ep)
591   , m_filename(filename)
592   , m_receiving(receiving)
593   , m_disableT38(disableT38)
594   , m_tiffFileFormat(TIFF_File_FormatName)
595   , m_state(disableT38 ? e_CompletedSwitch : e_AwaitingSwitchToT38)
596 {
597   SetFaxMediaFormatOptions(m_tiffFileFormat);
598 
599   m_switchTimer.SetNotifier(PCREATE_NOTIFIER(OnSwitchTimeout));
600 
601   PTRACE(3, "FAX\tCreated fax connection with token \"" << callToken << "\","
602             " receiving=" << receiving << ","
603             " disabledT38=" << disableT38 << ","
604             " filename=\"" << filename << '"');
605 }
606 
607 
~OpalFaxConnection()608 OpalFaxConnection::~OpalFaxConnection()
609 {
610   PTRACE(3, "FAX\tDeleted FAX connection.");
611 }
612 
613 
GetPrefixName() const614 PString OpalFaxConnection::GetPrefixName() const
615 {
616   return m_disableT38 ? m_endpoint.GetPrefixName() : m_endpoint.GetT38Prefix();
617 }
618 
619 
GetMediaFormats() const620 OpalMediaFormatList OpalFaxConnection::GetMediaFormats() const
621 {
622   OpalMediaFormatList formats;
623 
624   if (m_filename.IsEmpty())
625     formats += OpalPCM16;
626   else
627     formats += m_tiffFileFormat;
628 
629   if (!m_disableT38) {
630     formats += OpalRFC2833;
631     formats += OpalCiscoNSE;
632   }
633 
634   return formats;
635 }
636 
637 
AdjustMediaFormats(bool local,const OpalConnection * otherConnection,OpalMediaFormatList & mediaFormats) const638 void OpalFaxConnection::AdjustMediaFormats(bool   local,
639                            const OpalConnection * otherConnection,
640                             OpalMediaFormatList & mediaFormats) const
641 {
642   // Remove everything but G.711 or fax stuff
643   OpalMediaFormatList::iterator it = mediaFormats.begin();
644   while (it != mediaFormats.end()) {
645     if ((m_state != e_SwitchingToT38 && it->GetMediaType() == OpalMediaType::Audio()) ||
646         (*it == OpalG711_ULAW_64K || *it == OpalG711_ALAW_64K || *it == OpalRFC2833 || *it == OpalCiscoNSE))
647       ++it;
648     else if (it->GetMediaType() != OpalMediaType::Fax() || (m_disableT38 && *it == OpalT38))
649       mediaFormats -= *it++;
650     else
651       SetFaxMediaFormatOptions(*it++);
652   }
653 
654   OpalConnection::AdjustMediaFormats(local, otherConnection, mediaFormats);
655 }
656 
657 
SetFaxMediaFormatOptions(OpalMediaFormat & mediaFormat) const658 void OpalFaxConnection::SetFaxMediaFormatOptions(OpalMediaFormat & mediaFormat) const
659 {
660   mediaFormat.SetOptionString("TIFF-File-Name", m_filename);
661   mediaFormat.SetOptionBoolean("Receiving", m_receiving);
662 
663   PString str = m_stringOptions(OPAL_OPT_STATION_ID);
664   if (!str.IsEmpty()) {
665     mediaFormat.SetOptionString("Station-Identifier", str);
666     PTRACE(4, "FAX\tSet Station-Identifier: \"" << str << '"');
667   }
668 
669   str = m_stringOptions(OPAL_OPT_HEADER_INFO);
670   if (!str.IsEmpty()) {
671     mediaFormat.SetOptionString("Header-Info", str);
672     PTRACE(4, "FAX\tSet Header-Info: \"" << str << '"');
673   }
674 }
675 
676 
OnEstablished()677 void OpalFaxConnection::OnEstablished()
678 {
679   OpalConnection::OnEstablished();
680 
681   // If switched and we don't need to do CNG/CED any more, or T.38 is disabled
682   // in which case the SpanDSP will deal with CNG/CED stuff.
683   if (m_state == e_AwaitingSwitchToT38) {
684     PString str = m_stringOptions(OPAL_T38_SWITCH_TIME);
685     if (!str.IsEmpty()) {
686       m_switchTimer.SetInterval(0, str.AsUnsigned());
687       PTRACE(3, "FAX\tStarting timer for auto-switch to T.38");
688     }
689   }
690 }
691 
692 
OnReleased()693 void OpalFaxConnection::OnReleased()
694 {
695   m_switchTimer.Stop(false);
696   OpalConnection::OnReleased();
697 }
698 
699 
CreateMediaStream(const OpalMediaFormat & mediaFormat,unsigned sessionID,bool isSource)700 OpalMediaStream * OpalFaxConnection::CreateMediaStream(const OpalMediaFormat & mediaFormat, unsigned sessionID, bool isSource)
701 {
702   return new OpalFaxMediaStream(*this, mediaFormat, sessionID, isSource);
703 }
704 
705 
OnStartMediaPatch(OpalMediaPatch & patch)706 void OpalFaxConnection::OnStartMediaPatch(OpalMediaPatch & patch)
707 {
708   // Have switched to T.38 mode
709   if (patch.GetSink()->GetMediaFormat() == OpalT38) {
710     m_switchTimer.Stop(false);
711     m_state = e_CompletedSwitch;
712     m_finalStatistics.m_fax.m_result = OpalMediaStatistics::FaxNotStarted;
713     PTRACE(4, "FAX\tStarted fax media stream for " << m_tiffFileFormat
714            << " state=" << m_state << " switch=" << m_faxMediaStreamsSwitchState);
715   }
716 
717   OpalConnection::OnStartMediaPatch(patch);
718 }
719 
720 
OnStopMediaPatch(OpalMediaPatch & patch)721 void OpalFaxConnection::OnStopMediaPatch(OpalMediaPatch & patch)
722 {
723   // Finished the fax transmission, look for TIFF
724   OpalMediaStream & source = patch.GetSource();
725   if (source.GetMediaFormat() == m_tiffFileFormat) {
726     m_switchTimer.Stop(false);
727 
728     PTRACE(4, "FAX\tStopped fax media stream for " << m_tiffFileFormat
729            << " state=" << m_state << " switch=" << m_faxMediaStreamsSwitchState);
730 
731     // Not an explicit switch, so fax plug in indicated end of fax
732     if (m_state == e_CompletedSwitch && m_faxMediaStreamsSwitchState == e_NotSwitchingFaxMediaStreams) {
733 #if OPAL_STATISTICS
734       InternalGetStatistics(m_finalStatistics, true);
735       PTRACE(3, "FAX\tGot final statistics: result=" << m_finalStatistics.m_fax.m_result);
736       OnFaxCompleted(m_finalStatistics.m_fax.m_result != OpalMediaStatistics::FaxSuccessful);
737 #else
738       OnFaxCompleted(true);
739 #endif
740     }
741   }
742 
743   OpalConnection::OnStopMediaPatch(patch);
744 }
745 
746 
SendUserInputTone(char tone,unsigned duration)747 PBoolean OpalFaxConnection::SendUserInputTone(char tone, unsigned duration)
748 {
749   OnUserInputTone(tone, duration);
750   return true;
751 }
752 
753 
OnUserInputTone(char tone,unsigned)754 void OpalFaxConnection::OnUserInputTone(char tone, unsigned /*duration*/)
755 {
756   // Not yet switched and got a CNG/CED from the remote system, start switch
757   if (m_state == e_AwaitingSwitchToT38 &&
758          (m_receiving ? (tone == 'X')
759                       : (tone == 'Y' && m_stringOptions.GetBoolean(OPAL_SWITCH_ON_CED)))) {
760     PTRACE(3, "FAX\tRequesting mode change in response to " << (tone == 'X' ? "CNG" : "CED"));
761     PThread::Create(PCREATE_NOTIFIER(OpenFaxStreams));
762   }
763 }
764 
765 
OnFaxCompleted(bool failed)766 void OpalFaxConnection::OnFaxCompleted(bool failed)
767 {
768   m_endpoint.OnFaxCompleted(*this, failed);
769 
770   // Prevent to reuse filename
771   m_filename.MakeEmpty();
772 }
773 
774 
775 #if OPAL_STATISTICS
776 
GetStatistics(OpalMediaStatistics & statistics) const777 void OpalFaxConnection::GetStatistics(OpalMediaStatistics & statistics) const
778 {
779   InternalGetStatistics(statistics, false);
780 }
781 
782 
InternalGetStatistics(OpalMediaStatistics & statistics,bool terminate) const783 void OpalFaxConnection::InternalGetStatistics(OpalMediaStatistics & statistics, bool terminate) const
784 {
785   if (m_finalStatistics.m_fax.m_result >= 0) {
786     statistics = m_finalStatistics;
787     return;
788   }
789 
790   OpalMediaStreamPtr stream;
791   if ((stream = GetMediaStream(OpalMediaType::Fax(), false)) == NULL &&
792       (stream = GetMediaStream(OpalMediaType::Fax(), true )) == NULL) {
793 
794     PSafePtr<OpalConnection> other = GetOtherPartyConnection();
795     if (other == NULL) {
796       PTRACE(2, "FAX\tNo connection to get statistics.");
797       return;
798     }
799 
800     if ((stream = other->GetMediaStream(OpalMediaType::Fax(), false)) == NULL &&
801         (stream = other->GetMediaStream(OpalMediaType::Fax(), true )) == NULL) {
802       PTRACE(2, "FAX\tNo stream to get statistics.");
803       return;
804     }
805   }
806 
807   if (terminate)
808     stream->ExecuteCommand(OpalFaxTerminate());
809 
810   stream->GetStatistics(statistics);
811 }
812 
813 #endif
814 
815 
OnSwitchTimeout(PTimer &,INT)816 void OpalFaxConnection::OnSwitchTimeout(PTimer &, INT)
817 {
818   if (m_state == e_AwaitingSwitchToT38) {
819     PTRACE(2, "FAX\tDid not switch to T.38 mode, forcing switch");
820     PThread::Create(PCREATE_NOTIFIER(OpenFaxStreams));
821   }
822 }
823 
824 
SwitchFaxMediaStreams(bool toT38)825 bool OpalFaxConnection::SwitchFaxMediaStreams(bool toT38)
826 {
827   PSafePtr<OpalConnection> other = GetOtherPartyConnection();
828   if (other != NULL && other->SwitchFaxMediaStreams(toT38))
829     return true;
830 
831   PTRACE(1, "FAX\tMode change request to " << (toT38 ? "T.38" : "audio") << " failed");
832   return false;
833 }
834 
835 
OnSwitchedFaxMediaStreams(bool toT38,bool success)836 void OpalFaxConnection::OnSwitchedFaxMediaStreams(bool toT38, bool success)
837 {
838   if (!toT38) {
839     PTRACE(3, "FAX\tMode change request to audio");
840     return;
841   }
842 
843   if (success) {
844     PTRACE(3, "FAX\tMode change request to T.38 succeeded");
845   }
846   else {
847     PTRACE(4, "FAX\tMode change request to T.38 failed, falling back to G.711");
848     if (m_stringOptions.GetBoolean(OPAL_NO_G111_FAX))
849       OnFaxCompleted(true);
850     else {
851       m_disableT38 = true;
852       SwitchFaxMediaStreams(false);
853     }
854   }
855 
856   m_state = e_CompletedSwitch;
857 }
858 
859 
OnSwitchingFaxMediaStreams(bool toT38)860 bool OpalFaxConnection::OnSwitchingFaxMediaStreams(bool toT38)
861 {
862   return !(toT38 && m_disableT38);
863 }
864 
865 
OpenFaxStreams(PThread &,INT)866 void OpalFaxConnection::OpenFaxStreams(PThread &, INT)
867 {
868   if (LockReadWrite()) {
869     m_state = e_SwitchingToT38;
870     if (!SwitchFaxMediaStreams(true))
871       m_state = e_CompletedSwitch; // Couldn't, don't try again.
872     UnlockReadWrite();
873   }
874 }
875 
876 
877 #endif // OPAL_FAX
878 
879