/*
 * main.cxx
 *
 * OpenH323 call generator
 *
 * Copyright (c) 2001 Benny L. Prijono <seventhson@theseventhson.freeserve.co.uk>
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is CallGen323.
 *
 * The Initial Developer of the Original Code is Benny L. Prijono
 *
 * Contributor(s): Equivalence Pty. Ltd.
 *
 * $Log: main.cxx,v $
 * Revision 1.22  2002/12/20 03:41:47  robertj
 * Added usage text for port setting command line options.
 *
 * Revision 1.21  2002/12/10 23:04:49  robertj
 * Fixed repeated error message when have no outgoing message file.
 *
 * Revision 1.20  2002/12/10 06:01:34  robertj
 * Fixed conditional around the wrong way
 *
 * Revision 1.19  2002/12/10 05:54:41  robertj
 * Removed delay if call vanishes quickly and are waiting fro establishment.
 *
 * Revision 1.18  2002/11/25 01:27:00  robertj
 * Changed range for initial startup delay.
 *
 * Revision 1.17  2002/11/16 00:32:03  robertj
 * Added wait for establishment timeout.
 * Removed delay after last call in run.
 *
 * Revision 1.16  2002/11/12 22:22:22  robertj
 * Fixed incorrect test for outgoing message file, thanks Héctor Leonardo Bolanos Muñoz
 *
 * Revision 1.15  2002/11/08 03:11:09  robertj
 * Changed start up of threads so small inter-call delay does not swamp
 *   target with large number of calls first up.
 *
 * Revision 1.14  2002/08/27 05:28:27  robertj
 * Changed message name for established to be "established".
 *
 * Revision 1.13  2002/08/27 03:49:31  robertj
 * Added no media transmission if no WAV file supplied, still opens the
 *   logical channels though. Uses silence suppression.
 * Added detection of completion of run and exit of program.
 *
 * Revision 1.12  2002/07/29 12:03:46  robertj
 * Fixed write of G.723.1. file.
 * Added port setting.
 * Displayed capabilities.
 *
 * Revision 1.11  2002/07/26 02:14:12  robertj
 * Added ability to prefer and delete codecs.
 * Added fake G.723.1 codec.
 *
 * Revision 1.10  2002/07/23 09:33:58  robertj
 * Fixed correct calculation of delay times on buffer size.
 *
 * Revision 1.9  2002/07/23 06:30:58  robertj
 * Another set of enhancements, highlights are continually sending ogm WAV
 *   file and putting received audio into WAV files. As well as more
 *   statistics including a CDR file drop for every call.
 *
 * Revision 1.8  2002/05/31 04:32:33  robertj
 * Fixed bad conditional, thanks Federico Pinna
 *
 * Revision 1.7  2002/05/27 00:30:03  robertj
 * Added adjustable time ranges for random calls, thanks Federico Pinna
 *
 * Revision 1.6  2002/05/10 00:25:30  robertj
 * Changed to trace out data and time as this is more useful for matching up
 *   with other endpoints or ethereal traces (provided clocks a synchronised).
 *
 * Revision 1.5  2002/03/14 05:00:36  robertj
 * Added some tracing of call starts.
 * Added console output of end of run in each thread.
 *
 * Revision 1.4  2002/02/19 06:01:06  rogerh
 * Added --listenport option. Submitted by andreas.sikkema@philips.com
 * Added history log.
 *
 *
 * 25 Jan 2002 Substantial improvement [Equivalence Pty. Ltd.]
 * 25 Jan 2000 Update to incorporate openh323 v.01 alpha2 and fix gatekeeper
 *             related codes [bennylp]
 */

#include "precompile.h"
#include "main.h"
#include "version.h"

#include <ptclib/random.h>
#include <opalvxml.h>


PCREATE_PROCESS(CallGen323);

PMutex coutMutex;


///////////////////////////////////////////////////////////////////////////////

CallGen323::CallGen323()
  : PProcess("Equivalence", "CallGen323", MAJOR_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER),
    console(PConsoleChannel::StandardInput)
{
}


void CallGen323::Main()
{
  PArgList & args = GetArguments();
  args.Parse("a-access-token-oid:"
             "c-cdr:"
             "D-disable:"
             "g-gatekeeper:"
             "I-in-dir:"
             "i-interface:"
             "l-listen."
             "m-max:"
             "n-no-gatekeeper."
             "O-out-msg:"
             "o-output:"
             "P-prefer:"
             "p-password:"
             "r-repeat:"
             "-require-gatekeeper."
             "t-trace."
             "-tmaxest:"
             "-tmincall:"
             "-tmaxcall:"
             "-tminwait:"
             "-tmaxwait:"
             "-tcp-base:"
             "-tcp-max:"
             "-udp-base:"
             "-udp-max:"
             "-rtp-base:"
             "-rtp-max:"
             "u-user:"
             , FALSE);
  
  if (args.GetCount() == 0 && !args.HasOption('l')) {
    cout << "Usage:\n"
            "  callgen323 [options] -l\n"
            "  callgen323 [options] dest-host\n"
            "where options:\n"
            "  -l                   Passive/listening mode.\n"
            "  -m --max num         Maximum number of simultaneous calls\n"
            "  -r --repeat num      Repeat calls n times\n"
            "  -t --trace           Trace enable (use multiple times for more detail)\n"
            "  -o --output file     Specify filename for trace output [stdout]\n"
            "  -i --interface addr  Specify IP address and port listen on [*:1720]\n"
            "  -g --gatekeeper host Specify gatekeeper host [auto-discover]\n"
            "  -n --no-gatekeeper   Disable gatekeeper discovery [false]\n"
            "  --require-gatekeeper Exit if gatekeeper discovery fails [false]\n"
            "  -u --user username   Specify local username [login name]\n"
            "  -p --password pwd    Specify gatekeeper H.235 password [none]\n"
            "  -P --prefer codec    Set codec preference (use multiple times) [none]\n"
            "  -D --disable codec   Disable codec (use multiple times) [none]\n"
            "  -O --out-msg file    Specify PCM16 WAV file for outgoing message [ogm.wav]\n"
            "  -I --in-dir dir      Specify directory for incoming WAV files [disabled]\n"
            "  -c --cdr file        Specify Call Detail Record file [none]\n"
            "  --tcp-base port      Specific the base TCP port to use.\n"
            "  --tcp-max port       Specific the maximum TCP port to use.\n"
            "  --udp-base port      Specific the base UDP port to use.\n"
            "  --udp-max port       Specific the maximum UDP port to use.\n"
            "  --rtp-base port      Specific the base RTP/RTCP pair of UDP port to use.\n"
            "  --rtp-max port       Specific the maximum RTP/RTCP pair of UDP port to use.\n"
            "  --tmaxest  secs      Maximum time to wait for \"Established\" [0]\n"
            "  --tmincall secs      Minimum call duration in seconds [10]\n"
            "  --tmaxcall secs      Maximum call duration in seconds [30]\n"
            "  --tminwait secs      Minimum interval between calls in seconds [10]\n"
            "  --tmaxwait secs      Maximum interval between calls in seconds [30]\n"
            "\n"
            "Notes:\n"
            "  If --tmaxest is set a non-zero value then --tmincall is the time to leave\n"
            "  the call running once established. If zero (the default) then --tmincall\n"
            "  is the length of the call from initiation. The call may or may not be\n"
            "  \"answered\" within that time.\n"
            "\n";
    return;
  }
  
#if PTRACING
  PTrace::Initialise(args.GetOptionCount('t'),
                     args.HasOption('o') ? (const char *)args.GetOptionString('o') : NULL,
		     PTrace::Blocks | PTrace::DateAndTime | PTrace::Thread | PTrace::FileAndLine);
#endif
  
  MyH323EndPoint endpoint;
  if (!endpoint.Initialise(args))
    return;
  
  if (args.HasOption('l')) {
    cout << "Endpoint is listening for incoming calls, press ENTER to exit.\n";
    console.ReadChar();
    endpoint.ClearAllCalls();
    return;
  }

  PSyncPoint threadEnded;
  CallParams params(endpoint, threadEnded);

  params.tmax_est .SetInterval(0, args.GetOptionString("tmaxest",  "0" ).AsUnsigned());
  params.tmin_call.SetInterval(0, args.GetOptionString("tmincall", "10").AsUnsigned());
  params.tmax_call.SetInterval(0, args.GetOptionString("tmaxcall", "60").AsUnsigned());
  params.tmin_wait.SetInterval(0, args.GetOptionString("tminwait", "10").AsUnsigned());
  params.tmax_wait.SetInterval(0, args.GetOptionString("tmaxwait", "30").AsUnsigned());

  if (params.tmin_call == 0 ||
      params.tmin_wait == 0 ||
      params.tmin_call > params.tmax_call ||
      params.tmin_wait > params.tmax_wait) {
    cerr << "Invalid times entered!\n";
    return;
  }

  unsigned number = args.GetOptionString('m').AsUnsigned();
  if (number == 0)
    number = 1;
  cout << "Endpoint starting " << number << " simultaneous call";
  if (number > 1)
    cout << 's';
  cout << ' ';

  params.repeat = args.GetOptionString('r', "10").AsUnsigned();
  if (params.repeat != 0)
    cout << params.repeat;
  else
    cout << "infinite";
  cout << " time";
  if (params.repeat != 1)
    cout << 's';
  if (params.repeat != 0)
    cout << ", grand total of " << number*params.repeat << " calls";
  cout << '.' << endl;

  // create some threads to do calls, but start them randomly
  for (unsigned idx = 0; idx < number; idx++)
    threadList.Append(new CallThread(idx+1, args[idx % args.GetCount()], params));

  PThread::Create(PCREATE_NOTIFIER(Cancel), (INT)&endpoint);

  for (;;) {
    threadEnded.Wait();
    PThread::Sleep(100);

    BOOL finished = TRUE;
    for (PINDEX i = 0; i < threadList.GetSize(); i++) {
      if (!threadList[i].IsTerminated()) {
        finished = FALSE;
        break;
      }
    }

    if (finished) {
      cout << "\nAll call sets completed." << endl;
      console.Close();
      return;
    }
  }
}


void CallGen323::Cancel(PThread &, INT ep)
{
  PTRACE(3, "CallGen\tCancel thread started.");

  coutMutex.Wait();
  cout << "Press ENTER at any time to quit.\n" << endl;
  coutMutex.Signal();

  // wait for a keypress
  while (console.ReadChar() != '\n') {
    if (!console.IsOpen()) {
      PTRACE(3, "CallGen\tCancel thread ended.");
      return;
    }
  }

  PTRACE(2, "CallGen\tCancelling calls.");

  coutMutex.Wait();
  cout << "\nAborting all calls ..." << endl;
  coutMutex.Signal();
  
  // stop threads
  for (PINDEX i = 0; i < threadList.GetSize(); i++)
    threadList[i].Stop();

  // stop all calls
  ((H323EndPoint *)ep)->ClearAllCalls();

  PTRACE(1, "CallGen\tCancelled calls.");
}


///////////////////////////////////////////////////////////////////////////////

CallThread::CallThread(unsigned _index,
                       const PString & _destination,
                       const CallParams & _params)
  : PThread(1000, NoAutoDeleteThread, NormalPriority, psprintf("CallGen %u", _index)),
    destination(_destination),          
    index(_index),
    params(_params)
{
  Resume();
}


static unsigned RandomRange(PRandom & rand,
                            const PTimeInterval & tmin,
                            const PTimeInterval & tmax)
{
  unsigned umax = tmax.GetInterval();
  unsigned umin = tmin.GetInterval();
  return rand.Generate() % (umax - umin + 1) + umin;
}


void CallThread::Main()
{
  PTRACE(2, "CallGen\tStarted thread " << index);

  PRandom rand(PRandom::Number());

  PTimeInterval delay = RandomRange(rand, (index-1)*500, (index+1)*500);
  coutMutex.Wait();
  cout << setw(3) << index << ": Initial delay of " << delay << " seconds" << endl;
  coutMutex.Signal();

  if (exit.Wait(delay)) {
    PTRACE(2, "CallGen\tAborted thread " << index);
    params.threadEnded.Signal();
    return;
  }

  // Loop "repeat" times for (repeat > 0), or loop forever for (repeat == 0)
  unsigned count = 1;
  do {
    // trigger a call
    PString token;
    PTRACE(1, "CallGen\tMaking call to " << destination);
    unsigned totalAttempts = ++params.endpoint.totalAttempts;
    if (!params.endpoint.MakeCall(destination, token))
      PError << setw(3) << index << ": MakeCall to " << destination << " failed" << endl;
    else {
      BOOL stopping = FALSE;

      delay = RandomRange(rand, params.tmin_call, params.tmax_call);
      coutMutex.Wait();
      cout << setw(3) << index << ": Making call " << count;
      if (params.repeat)
        cout << " of " << params.repeat;
      cout << " (total=" << totalAttempts
           << ") for " << delay << " seconds to "
           << destination << " - " << token << endl;
      coutMutex.Signal();

      if (params.tmax_est > 0) {
        coutMutex.Wait();
        cout << setw(3) << index << ": Waiting " << params.tmax_est
             << " seconds for establishment" << endl;
        coutMutex.Signal();

        PTimer timeout = params.tmax_est;
        while (!params.endpoint.IsConnectionEstablished(token)) {
          stopping = exit.Wait(100);
          if (stopping || !timeout.IsRunning() || !params.endpoint.HasConnection(token)) {
            delay = 0;
            break;
          }
        }
      }

      if (delay > 0) {
        // wait for a random time
        PTRACE(1, "CallGen\tWaiting for " << delay);
        stopping = exit.Wait(delay);
      }

      // end the call
      coutMutex.Wait();
      cout << setw(3) << index << ": Clearing call " << token << endl;
      coutMutex.Signal();

      params.endpoint.ClearCallSynchronous(token);

      if (stopping)
        break;
    }

    count++;
    if (params.repeat > 0 && count > params.repeat)
      break;

    // wait for a random delay
    delay = RandomRange(rand, params.tmin_wait, params.tmax_wait);
    coutMutex.Wait();
    cout << setw(3) << index << ": Delaying for " << delay << " seconds" << endl;
    coutMutex.Signal();

    PTRACE(1, "CallGen\tDelaying for " << delay);
    // wait for a random time
  } while (!exit.Wait(delay));

  coutMutex.Wait();
  cout << setw(3) << index << ": Completed call set." << endl;
  coutMutex.Signal();
  PTRACE(2, "CallGen\tFinished thread " << index);

  params.threadEnded.Signal();
}


void CallThread::Stop()
{
  if (!IsTerminated()) {
    coutMutex.Wait();
    cout << setw(3) << index << ": Stopping." << endl;
    coutMutex.Signal();
  }

  exit.Signal();
}


///////////////////////////////////////////////////////////////////////////////

MyH323EndPoint::MyH323EndPoint()
{
  totalAttempts = 0;
  totalEstablished = 0;

  // Set capability
  AddAllCapabilities(0, 0, "*{sw}");
  SetCapability(0, 0, new G7231_File_Capability());
  AddAllUserInputCapabilities(0, P_MAX_INDEX);
}


MyH323EndPoint::~MyH323EndPoint()
{
  if (totalAttempts > 0)
    cout << "Total calls: " << totalAttempts
         << " attempted, " << totalEstablished << " established\n";
}


BOOL MyH323EndPoint::Initialise(const PArgList& args)
{
  H323TransportAddressArray interfaces = args.GetOptionString('i').Lines();
  if (interfaces.IsEmpty()) {
    if (!StartListener("*")) {
      cout << "Couldn't start standard listener!" << endl;
      return FALSE;
    }
  }
  else {
    if (!StartListeners(interfaces)) {
      cout << "Couldn't start any listeners on interfaces/ports:\n"
           << setfill('\n') << interfaces << setfill(' ') << endl;
      return FALSE;
    }
  }
  cout << "Listening on: " << setfill(',') << GetListeners() << setfill(' ') << endl;

  if (args.HasOption("tcp-base"))
    SetTCPPorts(args.GetOptionString("tcp-base").AsUnsigned(),
                args.GetOptionString("tcp-max").AsUnsigned());
  if (args.HasOption("udp-base"))
    SetUDPPorts(args.GetOptionString("udp-base").AsUnsigned(),
                args.GetOptionString("udp-max").AsUnsigned());
  if (args.HasOption("rtp-base"))
    SetRtpIpPorts(args.GetOptionString("rtp-base").AsUnsigned(),
                  args.GetOptionString("rtp-max").AsUnsigned());

  outgoingMessageFile = args.GetOptionString('O', "ogm.wav");
  if (outgoingMessageFile.IsEmpty())
    cout << "Not using outgoing message file." << endl;
  else if (PFile::Exists(outgoingMessageFile))
    cout << "Using outgoing message file: " << outgoingMessageFile << endl;
  else {
    cout << "Outgoing message file  \"" << outgoingMessageFile << "\" does not exist!" << endl;
    PTRACE(1, "CallGen\tOutgoing message file \"" << outgoingMessageFile << "\" does not exist");
    outgoingMessageFile = PString::Empty();
  }

  incomingAudioDirectory = args.GetOptionString('I');
  if (incomingAudioDirectory.IsEmpty())
    cout << "Not saving incoming audio data." << endl;
  else if (PDirectory::Exists(incomingAudioDirectory) ||
           PDirectory::Create(incomingAudioDirectory)) {
    incomingAudioDirectory = PDirectory(incomingAudioDirectory);
    cout << "Using incoming audio directory: " << incomingAudioDirectory << endl;
  }
  else {
    cout << "Could not create incoming audio directory \"" << incomingAudioDirectory << "\"!" << endl;
    PTRACE(1, "CallGen\tCould not create incoming audio directory \"" << incomingAudioDirectory << '"');
    incomingAudioDirectory = PString::Empty();
  }

  RemoveCapabilities(args.GetOptionString('D').Lines());
  ReorderCapabilities(args.GetOptionString('P').Lines());
  cout << "Local capabilities:\n" << capabilities << endl;

  if (args.HasOption('c')) {
    if (cdrFile.Open(args.GetOptionString('c'), PFile::WriteOnly, PFile::Create)) {
      cdrFile.SetPosition(0, PFile::End);
      PTRACE(1, "CallGen\tSetting CDR to \"" << cdrFile.GetFilePath() << '"');
      cout << "Sending Call Detail Records to \"" << cdrFile.GetFilePath() << '"' << endl;
    }
    else {
      cout << "Could not open \"" << cdrFile.GetFilePath() << "\"!" << endl;
    }
  }

  // set local username, is necessary
  if (args.HasOption('u')) {
    PStringArray aliases = args.GetOptionString('u').Lines();
    SetLocalUserName(aliases[0]);
    for (PINDEX i = 1; i < aliases.GetSize(); ++i)
      AddAliasName(aliases[i]);
  }
  cout << "Local username: \"" << GetLocalUserName() << '"' << endl;
  
  if (args.HasOption('p')) {
    SetGatekeeperPassword(args.GetOptionString('p'));
    cout << "Using H.235 security." << endl;
  }

  if (args.HasOption('a')) {
    gkAccessTokenOID = args.GetOptionString('a');
    cout << "Set Access Token OID to \"" << gkAccessTokenOID << '"' << endl;
  }
  
  // process gatekeeper registration options
  if (args.HasOption('g')) {
    PString gkName = args.GetOptionString('g');
    cout << "Registering with gatekeeper \"" << gkName << "\" ..." << flush;
    H323TransportUDP * rasChannel;
    if (interfaces.IsEmpty())
      rasChannel  = new H323TransportUDP(*this);
    else {
      PIPSocket::Address interfaceAddress;
      interfaces[0].GetIpAddress(interfaceAddress);
      rasChannel  = new H323TransportUDP(*this, interfaceAddress);
    }

    if (SetGatekeeper("ip$"+gkName, rasChannel))
      cout << "\nGatekeeper set to \"" << *gatekeeper << '"' << endl;
    else {
      cout << "\nError registering with gatekeeper at \"" << gkName << '"' << endl;
      return FALSE;
    }
  }
  else if (!args.HasOption('n')) {
    cout << "Searching for gatekeeper ..." << flush;
    if (DiscoverGatekeeper(new H323TransportUDP(*this)))
      cout << "\nGatekeeper found: " << *gatekeeper << endl;
    else {
      cout << "\nNo gatekeeper found." << endl;
      if (args.HasOption("require-gatekeeper")) 
        return FALSE;
    }
  }
  
  return TRUE;
}


H323Connection * MyH323EndPoint::CreateConnection(unsigned callReference)
{
  return new MyH323Connection(*this, callReference);
}


void MyH323EndPoint::OnConnectionEstablished(H323Connection & connection,
                                             const PString & token)
{
  coutMutex.Wait();
  totalEstablished++;
  cout << "   : Established \"" << connection.GetRemotePartyName()
       << "\" " << token
       << " active=" << connectionsActive.GetSize()
       << " total=" << totalEstablished
       << endl;
  coutMutex.Signal();
}


///////////////////////////////////////////////////////////////////////////////

MyH323Connection::MyH323Connection(MyH323EndPoint & ep, unsigned callRef)
  : H323Connection(ep, callRef),
    endpoint(ep),
    openedMedia(0),
    receivedMedia(0)
{
}


void MyH323Connection::OnCleared()
{
  coutMutex.Wait();
  cout << "   : Cleared \"" << GetRemotePartyName()
       << "\" " << GetCallToken()
       << " reason=" << GetCallEndReason()
       << endl;
  coutMutex.Signal();

  PTextFile & cdrFile = endpoint.GetCdrFile();

  if (!cdrFile.IsOpen())
    return;

  static PMutex cdrMutex;
  cdrMutex.Wait();

  if (cdrFile.GetLength() == 0)
    cdrFile << "Call Start Time,"
               "Total duration,"
               "Media open time,"
               "Media received time,"
               "ALERTING time,"
               "CONNECT time,"
               "Call End Reason,"
               "Remote party,"
               "Signalling gateway,"
               "Media gateway,"
               "Call Id,"
               "Call Token\n";

  cdrFile << GetSetupUpTime().AsString("yyyy/M/d hh:mm:ss") << ','
          << setprecision(1) << (GetConnectionEndTime() - GetSetupUpTime()) << ',';

  if (openedMedia.IsValid())
    cdrFile << (openedMedia - GetSetupUpTime());
  cdrFile << ',';

  if (receivedMedia.IsValid())
    cdrFile << (receivedMedia - GetSetupUpTime());
  cdrFile << ',';

  if (GetAlertingTime().IsValid())
    cdrFile << (GetAlertingTime() - GetSetupUpTime());
  cdrFile << ',';

  if (GetConnectionStartTime().IsValid())
    cdrFile << (GetConnectionStartTime() - GetSetupUpTime());
  cdrFile << ',';

  cdrFile << GetCallEndReason() << ','
          << GetRemotePartyName() << ','
          << GetControlChannel().GetRemoteAddress() << ','
          << mediaGateway << ','
          << GetCallIdentifier() << ','
          << GetCallToken() << endl;

  cdrMutex.Signal();
}


BOOL MyH323Connection::OpenAudioChannel(BOOL isEncoding,
                                        unsigned bufferSize,
                                        H323AudioCodec & codec)
{
  unsigned frameDelay;
  if (codec.IsRawDataChannelNative()) {
    frameDelay = 30; // Must be G7231_File_Codec, so always have 20ms frames
    bufferSize = 0;
  }
  else
    frameDelay = bufferSize/16; // Assume 16 bit PCM

  PIndirectChannel * channel;
  if (isEncoding)
    channel = new PlayMessage(endpoint.GetOutgoingMessageFile(), frameDelay, bufferSize);
  else {
    PString wavFileName;
    if (!endpoint.GetIncomingAudioDirectory()) {
      PString token = GetCallToken();
      token.Replace("/", "_", TRUE);
      wavFileName = endpoint.GetIncomingAudioDirectory() + token;
    }
    channel = new RecordMessage(wavFileName, frameDelay, bufferSize);
  }

  codec.AttachChannel(channel);

  openedMedia = PTime();

  coutMutex.Wait();
  cout << "   : Opened " << (isEncoding ? "transmitter" : "receiver")
       << " for " << codec.GetMediaFormat() << " on " << GetCallToken()
       << endl;
  coutMutex.Signal();

  return TRUE;
}


void MyH323Connection::OnRTPStatistics(const RTP_Session & session) const
{
  if (receivedMedia.GetTimeInSeconds() == 0 && session.GetPacketsReceived() > 0)
    ((MyH323Connection *)this)->receivedMedia = PTime();
}


///////////////////////////////////////////////////////////////////////////////

PlayMessage::PlayMessage(const PString & filename,
                         unsigned frameDelay,
                         unsigned frameSize)
  : PDelayChannel(PDelayChannel::DelayReadsOnly, frameDelay, frameSize)
{
  if (filename.IsEmpty())
      PTRACE(2, "CallGen\tPlaying silence, no outgoing message file");
  else {
    if (wavFile.Open(filename, PFile::ReadOnly)) {
      Open(wavFile);
      PTRACE(2, "CallGen\tPlaying outgoing message file \"" << wavFile.GetFilePath() << '"');
    }
    else {
      PTRACE(2, "CallGen\tCould not open outgoing message file \"" << wavFile.GetFilePath() << '"');
    }
  }

  reallyClose = FALSE;
}


BOOL PlayMessage::Read(void * buf, PINDEX len)
{
  if (PDelayChannel::Read(buf, len))
    return TRUE;

  if (reallyClose)
    return FALSE;

  // By opening the file as soon as we get a read error, we continually play
  // out the outgoing message as the usual error is end of file.
  if (wavFile.Open(PFile::ReadOnly)) {
    if (PDelayChannel::Read(buf, len))
      return TRUE;
  }

  // Just play out silence
  memset(buf, len, 0);
  lastReadCount = len;
  return TRUE;
}


BOOL PlayMessage::Close()
{
  reallyClose = TRUE;
  return PDelayChannel::Close();
}


///////////////////////////////////////////////////////////////////////////////

RecordMessage::RecordMessage(const PString & wavFileName,
                             unsigned frameDelay,
                             unsigned frameSize)
  : PDelayChannel(PDelayChannel::DelayWritesOnly, frameDelay, frameSize)
{
  reallyClose = FALSE;

  if (wavFileName.IsEmpty())
    return;

  PWAVFile * wavFile = new PWAVFile(wavFileName, PFile::WriteOnly);
  if (wavFile->IsOpen()) {
    Open(wavFile, TRUE);
    PTRACE(2, "CallGen\tRecording to file \"" << wavFileName << '"');
  }
  else
    delete wavFile;
}


BOOL RecordMessage::Write(const void * buf, PINDEX len)
{
  if (PDelayChannel::Write(buf, len))
    return TRUE;

  lastWriteCount = len;
  return !reallyClose;
}


BOOL RecordMessage::Close()
{
  reallyClose = TRUE;
  return PDelayChannel::Close();
}


// End of file ////////////////////////////////////////////////////////////////
