
#include "resip/stack/Contents.hxx"
#include "resip/dum/BaseCreator.hxx"
#include "resip/dum/ClientInviteSession.hxx"
#include "resip/dum/Dialog.hxx"
#include "resip/dum/DialogEventStateManager.hxx"
#include "resip/dum/DialogUsageManager.hxx"
#include "resip/dum/InviteSessionHandler.hxx"
#include "resip/dum/DumTimeout.hxx"
#include "resip/dum/MasterProfile.hxx"
#include "resip/dum/ServerInviteSession.hxx"
#include "resip/dum/ServerSubscription.hxx"
#include "resip/dum/UsageUseException.hxx"
#include "resip/dum/DumHelper.hxx"
#include "resip/stack/SipFrag.hxx"
#include "rutil/Logger.hxx"
#include "rutil/Random.hxx"
#include "rutil/compat.hxx"
#include "rutil/WinLeakCheck.hxx"

#include <utility>

#define RESIPROCATE_SUBSYSTEM Subsystem::DUM

using namespace resip;
using namespace std;

ClientInviteSession::ClientInviteSession(DialogUsageManager& dum,
                                         Dialog& dialog,
                                         std::shared_ptr<SipMessage> request,
                                         const Contents* initialOffer,
                                         DialogUsageManager::EncryptionLevel level,
                                         ServerSubscriptionHandle serverSub) :
   InviteSession(dum, dialog),
   mStaleCallTimerSeq(1),
   mCancelledTimerSeq(1),
   mServerSub(serverSub),
   mAllowOfferInPrack(false)
{
   resip_assert(request->isRequest());
   if(initialOffer)  
   {
      mProposedLocalOfferAnswer = unique_ptr<Contents>(initialOffer->clone());
      mProposedEncryptionLevel = level;
   }
   *mLastLocalSessionModification = *request;  // Copy message, so that modifications to mLastLocalSessionModification don't effect creator->getLastRequest

   mState=UAC_Start;
}

ClientInviteSessionHandle
ClientInviteSession::getHandle()
{
   return ClientInviteSessionHandle(mDum, getBaseHandle().getId());
}

const Contents&
ClientInviteSession::getEarlyMedia() const
{
   return *mEarlyMedia;
}

void
ClientInviteSession::provideOffer(const Contents& offer, DialogUsageManager::EncryptionLevel level, const Contents* alternative)
{
   InfoLog (<< toData(mState) << ": provideOffer");

   switch(mState)
   {
      case UAC_EarlyWithAnswer:
         if(mAllowOfferInPrack)
         {
            // This flag is enabled when we are about to send our first PRACK.  We are
            // allowed to send an offer in our first PRACK request, so don't use UPDATE in 
            // this case.
            //  Remember proposed local offerAnswer.
            mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer, alternative);
            mProposedEncryptionLevel = level;
         }
         else
         {
            transition(UAC_SentUpdateEarly);

            //  Creates an UPDATE request with application supplied offer.
            mDialog.makeRequest(*mLastLocalSessionModification, UPDATE);
            InviteSession::setOfferAnswer(*mLastLocalSessionModification, offer);

            //  Remember proposed local offerAnswer.
            mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer, alternative);
            mProposedEncryptionLevel = level;

            //  Send the req and do state transition.
            DumHelper::setOutgoingEncryptionLevel(*mLastLocalSessionModification, mProposedEncryptionLevel);
            send(mLastLocalSessionModification);
         }
         break;

      case UAC_SentAnswer:
         // just queue it for later
         transition(UAC_QueuedUpdate);
         mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer, alternative);
         mProposedEncryptionLevel = level;
         break;

      case UAC_Start:
      case UAC_Early:
      case UAC_EarlyWithOffer:
      case UAC_Answered:
      case UAC_SentUpdateEarly:
      case UAC_ReceivedUpdateEarly:
      case UAC_Cancelled:
      case UAC_QueuedUpdate:
      case Terminated:
         WarningLog (<< "Incorrect state to provideOffer: " << toData(mState));
         throw DialogUsage::Exception("Can't provide an offer", __FILE__,__LINE__);
         break;

      default:
         InviteSession::provideOffer(offer, level, alternative);
         break;
   }
}

void
ClientInviteSession::provideOffer (const Contents& offer)
{
   this->provideOffer(offer, mCurrentEncryptionLevel, 0);
}

void
ClientInviteSession::provideAnswer (const Contents& answer)
{
   InfoLog (<< toData(mState) << ": provideAnswer");

   switch(mState)
   {
      case UAC_EarlyWithOffer:
      {
         transition(UAC_SentAnswer);

         //  Remember proposed local offerAnswer.
         mCurrentRemoteOfferAnswer = std::move(mProposedRemoteOfferAnswer);
         mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);

         //  Creates a PRACK request with application supplied answer
         sendPrack(answer, mCurrentEncryptionLevel);
         break;
      }

      case UAC_Answered:
      {
         transition(Connected);
         std::shared_ptr<SipMessage> ack = sendAck(&answer);

         mCurrentRemoteOfferAnswer = std::move(mProposedRemoteOfferAnswer);
         mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);
         break;
      }
      case UAC_ReceivedUpdateEarly:
      {
         transition(UAC_EarlyWithAnswer); //.dcm. earlyWithAnswer is a strange
                                          //name...maybe earlyEstablished?
         //this sequence is repeated in many places...due for refactoring.
         //see ReceivedUpdate handling in InviteSession.
         //?dcm? are session timers allowed in the early dialog?

         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, *mLastRemoteSessionModification, 200);
         InviteSession::setOfferAnswer(*response, answer, 0);
         mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);
         mCurrentRemoteOfferAnswer = std::move(mProposedRemoteOfferAnswer);
         InfoLog (<< "Sending " << response->brief());
         DumHelper::setOutgoingEncryptionLevel(*response, mCurrentEncryptionLevel);
         send(response);
         break;
      }
        
      case UAC_Start:
      case UAC_Early:
      case UAC_EarlyWithAnswer:
      case UAC_SentUpdateEarly:
      case UAC_SentAnswer:
      case UAC_Cancelled:
      case UAC_QueuedUpdate:
      case Terminated:
         WarningLog (<< "Incorrect state to provideAnswer: " << toData(mState));
         throw DialogUsage::Exception("Can't provide an answer", __FILE__,__LINE__);
         break;

      default:
         InviteSession::provideAnswer(answer);
         break;
   }
}

void
ClientInviteSession::end()
{
   end(NotSpecified);
}

void
ClientInviteSession::end(const Data& userReason)
{
   mUserEndReason = userReason;
   end(InviteSession::UserSpecified);
}

void
ClientInviteSession::end(const ParserContainer<Token>& endReasons)
{
   mUserEndReasons = endReasons;
   end(InviteSession::UserSpecified);
}

void
ClientInviteSession::end(EndReason reason)
{
   InfoLog (<< toData(mState) << ": end");
   if (mEndReason == NotSpecified)
   {
      mEndReason = reason;   
   }

   switch(mState)
   {
      case UAC_Early:
      case UAC_EarlyWithOffer:
      case UAC_EarlyWithAnswer:
      case UAC_Answered:
      case UAC_SentUpdateEarly:
      case UAC_ReceivedUpdateEarly:
      case UAC_SentAnswer:
      case UAC_QueuedUpdate:
      case UAC_Cancelled: // !jf! possibly incorrect to always BYE in UAC_Cancelled
      {
         const auto msg = sendBye();
         transition(Terminated);
         mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get()); 
         break;
      }

      case UAC_Start:
         WarningLog (<< "Try to end when in state=" << toData(mState));
         resip_assert(0);
         break;

      case Terminated:
         // no-op
         break;

      default:
         InviteSession::end(reason);
         break;
   }
}

void
ClientInviteSession::reject (int statusCode, WarningCategory *warning)
{
   InfoLog (<< toData(mState) << ": reject(" << statusCode << ")");

   switch(mState)
   {
      case UAC_ReceivedUpdateEarly:
      {
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, *mLastRemoteSessionModification, statusCode);
         if(warning)
         {
            response->header(h_Warnings).push_back(*warning);
         }

         //  Send the req and do state transition.
         send(response);
         transition(UAC_EarlyWithAnswer);
         break;
      }

      case UAC_Answered:
      {
         // We received an offer in a 2xx response, and we want to reject it
         // ACK with no body, then send bye
         sendAck();
         const auto msg = sendBye();
         transition(Terminated);
         mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get()); 
         break;
      }

      case UAC_Start:
      case UAC_Early:
      case UAC_EarlyWithOffer:
      case UAC_EarlyWithAnswer:
      case UAC_SentUpdateEarly:
      case UAC_SentAnswer:
      case UAC_Cancelled:
         WarningLog (<< "Try to reject when in state=" << toData(mState));
         resip_assert(0);
         break;

      default:
         InviteSession::reject(statusCode, warning);
         break;
   }
}

void
ClientInviteSession::cancel()
{
   switch(mState)
   {
      case UAC_Early:
      case UAC_EarlyWithOffer:
      case UAC_EarlyWithAnswer:
      case UAC_SentUpdateEarly:
      case UAC_ReceivedUpdateEarly:
      case UAC_SentAnswer:
         InfoLog (<< toData(mState) << ": cancel");
         startCancelTimer();
         transition(UAC_Cancelled);
         break;

      case UAC_Cancelled:
      case Terminated:
         // no-op already cancelled or ended
         break;

      default:
         resip_assert(0);
         break;
   }
}

void
ClientInviteSession::onForkAccepted()
{
   switch(mState)
   {
      case UAC_Early:
      case UAC_EarlyWithOffer:
      case UAC_EarlyWithAnswer:
      case UAC_SentUpdateEarly:
      case UAC_ReceivedUpdateEarly:
         InfoLog (<< toData(mState) << ": onForkAccepted");
         // !jf! should we avoid creating another timer here? I don't think it
         // matters. Default timer is for 32secs. This is here to handle the
         // cleanup on forked INVITEs that have sent a provisional response but
         // don't ever receive a final response. 
         mDum.addTimerMs(DumTimeout::WaitingForForked2xx, Timer::TH, getBaseHandle(), 1); 
         break;
      default:
         // If the dialog is already set up (or cancelled) we disregard. 
         break;
   }
}

void
ClientInviteSession::startCancelTimer()
{
   InfoLog (<< toData(mState) << ": startCancelTimer");
   mDum.addTimerMs(DumTimeout::Cancelled, Timer::TH, getBaseHandle(), ++mCancelledTimerSeq);
}

void
ClientInviteSession::startStaleCallTimer()
{
   InfoLog (<< toData(mState) << ": startStaleCallTimer");
   unsigned long when = mDialog.mDialogSet.getUserProfile()->getDefaultStaleCallTime();
   
   mDum.addTimer(DumTimeout::StaleCall, 
                 when, 
                 getBaseHandle(), 
                 ++mStaleCallTimerSeq);
}

void
ClientInviteSession::sendSipFrag(const SipMessage& msg)
{
   if (mServerSub.isValid())
   {
      if (msg.isResponse() && mState >= UAC_Start && mState <= UAC_Cancelled)
      {
         int code = msg.header(h_StatusLine).statusCode();
         if (code > 100)
         {
            SipFrag contents;
            contents.message().header(h_StatusLine) = msg.header(h_StatusLine);
            if(mDialog.mDialogSet.getUserProfile()->getExtraHeadersInReferNotifySipFragEnabled())
            {
               contents.message().header(h_Vias) = msg.header(h_Vias);
               contents.message().header(h_From) = msg.header(h_From);
               contents.message().header(h_To) = msg.header(h_To);
               contents.message().header(h_CallId) = msg.header(h_CallId);
               contents.message().header(h_CSeq) = msg.header(h_CSeq);
               contents.message().header(h_Contacts) = msg.header(h_Contacts);
            }
            if (code < 200)
            {
               mServerSub->send(mServerSub->update(&contents));
            }
            else
            {
               mServerSub->end(NoResource, &contents);
            }
         }
      }
   }
}

void
ClientInviteSession::dispatch(const SipMessage& msg)
{
  try
  {
     if (msg.isRequest())
     {
        if (msg.header(h_RequestLine).method() == INFO)
        {
           InviteSession::dispatchInfo(msg);
           return;
        }
        if (msg.header(h_RequestLine).method() == MESSAGE)
        {
           InviteSession::dispatchMessage(msg);
           return;
        }
     }

     if (isBadRseq(msg))
     {
        return;
     }

     sendSipFrag(msg);
     switch(mState)
     {
        case UAC_Start:
           dispatchStart(msg);
           break;
        case UAC_Early:
           dispatchEarly(msg);
           break;
        case UAC_EarlyWithOffer:
           dispatchEarlyWithOffer(msg);
           break;
        case UAC_EarlyWithAnswer:
           dispatchEarlyWithAnswer(msg);
           break;
        case UAC_Answered:
           dispatchAnswered(msg);
           break;
        case UAC_SentUpdateEarly:
           dispatchSentUpdateEarly(msg);
           break;
        case UAC_SentUpdateEarlyGlare:
           dispatchSentUpdateEarlyGlare(msg);
           break;
        case UAC_ReceivedUpdateEarly:
           dispatchReceivedUpdateEarly(msg);
           break;
        case UAC_SentAnswer:
           dispatchSentAnswer(msg);
           break;
        case UAC_QueuedUpdate:
           dispatchQueuedUpdate(msg);
           break;
        case UAC_Cancelled:
           dispatchCancelled(msg);
           break;
        default:
           InviteSession::dispatch(msg);
           break;
     }
  }
  catch (BaseException& e)
  {
     WarningLog (<< "Caught: " << e);
     onFailureAspect(getHandle(), msg);
     end(NotSpecified); 
  }
}

void
ClientInviteSession::dispatch(const DumTimeout& timer)
{
   if (timer.type() == DumTimeout::Cancelled)
   {
      if(timer.seq() == mCancelledTimerSeq)
      {
         if (mServerSub.isValid())
         {
            SipMessage response;
            mDialog.makeResponse(response, *mLastLocalSessionModification, 487);
            sendSipFrag(response);
         }
         transition(Terminated);
         mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalCancel);
         mDum.destroy(this);
      }
   }
   else if (timer.type() == DumTimeout::StaleCall)
   {
      if(timer.seq() == mStaleCallTimerSeq)
      {
         mDum.mInviteSessionHandler->onStaleCallTimeout(getHandle());
         mDum.mInviteSessionHandler->terminate(getHandle());
      }
   }
   else if (timer.type() == DumTimeout::WaitingForForked2xx)
   {
      transition(Terminated);
      mDum.mInviteSessionHandler->onForkDestroyed(getHandle());
      mDum.destroy(this);
   }
   else if (timer.type() == DumTimeout::Glare)
   {
      if (mState == UAC_SentUpdateEarlyGlare)
      {
         transition(UAC_SentUpdateEarly);
         InfoLog (<< "Retransmitting the UPDATE (glare condition timer)");
         mDialog.makeRequest(*mLastLocalSessionModification, UPDATE);  // increments CSeq
         send(mLastLocalSessionModification);
      }
      else
      {
         InviteSession::dispatch(timer);
      }
   }
   else
   {
      InviteSession::dispatch(timer);
   }
}

void
ClientInviteSession::handleRedirect (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   transition(Terminated);

   if (mDum.mDialogEventStateManager)
   {
      mDum.mDialogEventStateManager->onTerminated(mDialog, msg, InviteSessionHandler::Rejected);
   }

   handler->onRedirected(getHandle(), msg);
   mDum.destroy(this);
}

void
ClientInviteSession::handleProvisional(const SipMessage& msg)
{
   resip_assert(msg.isResponse());
   resip_assert(msg.header(h_StatusLine).statusCode() < 200);
   resip_assert(msg.header(h_StatusLine).statusCode() > 100);

   //.dcm. Kept the following checks here rather than discardMessage as the
   // state machine can be affected(termination).

   // !dcm! should we really end the InviteSession or should be discard the 1xx instead?
   // Check CSeq in 1xx against original INVITE request
   if (msg.header(h_CSeq).sequence() != mDialog.mDialogSet.getCreator()->getLastRequest()->header(h_CSeq).sequence())
   {
      InfoLog (<< "Failure:  CSeq doesn't match invite: " << msg.brief());
      onFailureAspect(getHandle(), msg);
      end(NotSpecified);
      return;
   }
   else if (isReliable(msg))
   {
      if (!msg.exists(h_RSeq))
      {
         InfoLog (<< "Failure:  No RSeq in 1xx: " << msg.brief());
         onFailureAspect(getHandle(), msg);
         end(NotSpecified);
         return;
      }
   }

   startStaleCallTimer();
   onProvisionalAspect(getHandle(), msg);
}

void
ClientInviteSession::handleFinalResponse(const SipMessage& msg)
{
   resip_assert(msg.isResponse());
   resip_assert(msg.header(h_StatusLine).statusCode() >= 200);
   resip_assert(msg.header(h_StatusLine).statusCode() < 300);

   handleSessionTimerResponse(msg);
   storePeerCapabilities(msg);
   ++mStaleCallTimerSeq;  // disable stale call timer
}

void
ClientInviteSession::handle1xxOffer(const SipMessage& msg, const Contents& offer)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;

   handleProvisional(msg);
   mProposedRemoteOfferAnswer = InviteSession::makeOfferAnswer(offer);
   mCurrentEncryptionLevel = getEncryptionLevel(msg);
   handler->onOffer(getSessionHandle(), msg, offer);
}

void
ClientInviteSession::handle1xxAnswer(const SipMessage& msg, const Contents& answer)
{
   setCurrentLocalOfferAnswer(msg);
   mCurrentEncryptionLevel = getEncryptionLevel(msg);
   mCurrentRemoteOfferAnswer = InviteSession::makeOfferAnswer(answer);

   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   handleProvisional(msg);

   // flag to let handle1xxAnswer know that it is OK to send an offer in the 
   // first PRACK (for a provisional with SDP answer) and to let providerOffer know 
   // that the offer will be going in an PRACK and not in an update.  Flag is not 
   // needed after handle1xxAnswer is called so it is reset.
   mAllowOfferInPrack = true;  

   handler->onAnswer(getSessionHandle(), msg, answer);

   // Reset flag - no longer needed
   mAllowOfferInPrack = false;

   // If offer is provided in onAnswer callback then send offer in PRACK
   if (mProposedLocalOfferAnswer)
   {
      sendPrack(*mProposedLocalOfferAnswer, mProposedEncryptionLevel);
   }
   else
   {
      sendPrackIfNeeded(msg);
   }
}

// will not include SDP (this is a subsequent 1xx)
void
ClientInviteSession::sendPrackIfNeeded(const SipMessage& msg)
{
   resip_assert(msg.isResponse());
   resip_assert(msg.header(h_StatusLine).statusCode() < 200);
   resip_assert(msg.header(h_StatusLine).statusCode() > 100);
   
   if (isReliable(msg))
   {
      auto prack = std::make_shared<SipMessage>();
      mDialog.makeRequest(*prack, PRACK);
      prack->header(h_RAck) = mRelRespInfo;
      send(prack);
   }
}

// This version is used to send an answer to the UAS in PRACK from EarlyWithOffer 
// state. Assumes that it is the first PRACK. Subsequent PRACK will not have SDP
// Also used to send an offer in the first PRACK if the 18x included an
// answer.
void
ClientInviteSession::sendPrack(const Contents& offerAnswer, DialogUsageManager::EncryptionLevel encryptionLevel)
{
   auto prack = std::make_shared<SipMessage>();
   mDialog.makeRequest(*prack, PRACK);
   prack->header(h_RAck)= mRelRespInfo;
   
   InviteSession::setOfferAnswer(*prack, offerAnswer);

   DumHelper::setOutgoingEncryptionLevel(*prack, encryptionLevel);
   send(prack);
}

void
ClientInviteSession::dispatchStart (const SipMessage& msg)
{
   resip_assert(msg.isResponse());
   resip_assert(msg.header(h_StatusLine).statusCode() > 100);
   resip_assert(msg.header(h_CSeq).method() == INVITE);

   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   InviteSession::Event event = toEvent(msg, offerAnswer.get());

   switch (event)
   {
      case On1xx:
         transition(UAC_Early);
         handler->onNewSession(getHandle(), InviteSession::None, msg);
         if(!isTerminated())  
         {
            handleProvisional(msg);
            sendPrackIfNeeded(msg);  // may wish to move empty PRACK handling
                                     // outside the state machine            
         }
         break;

      case On1xxEarly:  // only unreliable
         //!dcm! according to draft-ietf-sipping-offeranswer there can be a non
         // reliable 1xx followed by a reliable 1xx.  Also, the initial 1xx
         // doesn't have to have an offer. However, DUM will only generate
         // reliable 1xx responses when 100rel is available.

         transition(UAC_Early);
         mEarlyMedia = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onNewSession(getHandle(), InviteSession::None, msg);
         if(!isTerminated())  
         {
            handleProvisional(msg);
            if(!isTerminated())  
            {
               handler->onEarlyMedia(getHandle(), msg, *offerAnswer);
            }
         }
         break;

      case On1xxOffer:
         transition(UAC_EarlyWithOffer);
         handler->onNewSession(getHandle(), InviteSession::Offer, msg);
         if(!isTerminated())  
         {
            handle1xxOffer(msg, *offerAnswer);
         }
         break;

      case On1xxAnswer:
         transition(UAC_EarlyWithAnswer);
         handler->onNewSession(getHandle(), InviteSession::Answer, msg);
         if(!isTerminated())  
         {
            handle1xxAnswer(msg, *offerAnswer);
         }
         break;

      case On2xxOffer:
         transition(UAC_Answered);
         handleFinalResponse(msg);
         mProposedRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onNewSession(getHandle(), InviteSession::Offer, msg);
         resip_assert(mProposedLocalOfferAnswer.get() == 0);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         if(!isTerminated())  
         {
            handler->onOffer(getSessionHandle(), msg, *offerAnswer);
            if(!isTerminated())   // application can call end() in onOffer callback
            {
               onConnectedAspect(getHandle(), msg);
            }
         }
         break;

      case On2xxAnswer:
         transition(Connected);
         sendAck();
         handleFinalResponse(msg);
         setCurrentLocalOfferAnswer(msg);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         mCurrentRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onNewSession(getHandle(), InviteSession::Answer, msg);
         if(!isTerminated())  // onNewSession callback may call end() or reject()
         {
            handler->onAnswer(getSessionHandle(), msg, *offerAnswer);
            if(!isTerminated())  // onAnswer callback may call end() or reject()
            {
               onConnectedAspect(getHandle(), msg);
            }
         }
         break;

      case On2xx:
      {
         sendAck();
         sendBye();
         InfoLog (<< "Failure:  2xx with no answer: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;
      }

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionally not handled and should be treated as an INVITE failure      
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
      case On491Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchEarly (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On1xx:
         transition(UAC_Early);
         handleProvisional(msg);
         sendPrackIfNeeded(msg);
         break;

      case On1xxEarly: // only unreliable
         transition(UAC_Early);
         handleProvisional(msg);
         if(!isTerminated())  
         {
            mEarlyMedia = InviteSession::makeOfferAnswer(*offerAnswer);
            handler->onEarlyMedia(getHandle(), msg, *offerAnswer);
         }
         break;

      case On1xxOffer:
         transition(UAC_EarlyWithOffer);
         handle1xxOffer(msg, *offerAnswer);
         break;

      case On1xxAnswer:
         transition(UAC_EarlyWithAnswer);
         handle1xxAnswer(msg, *offerAnswer);
         break;

      case On2xxOffer:
         transition(UAC_Answered);
         handleFinalResponse(msg);

         resip_assert(mProposedLocalOfferAnswer.get() == 0);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         mProposedRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);

         handler->onOffer(getSessionHandle(), msg, *offerAnswer);
         if(!isTerminated())  
         {
            onConnectedAspect(getHandle(), msg);   
         }
         break;

      case On2xxAnswer:
         transition(Connected);
         sendAck();
         handleFinalResponse(msg);
         setCurrentLocalOfferAnswer(msg);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         mCurrentRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onAnswer(getSessionHandle(), msg, *offerAnswer);
         if(!isTerminated())  // onNewSession callback may call end() or reject()
         {
            onConnectedAspect(getHandle(), msg);
         }
         break;

      case On2xx:
      {
         sendAck();
         sendBye();
         InfoLog (<< "Failure:  2xx with no answer: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;
      }

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionally not handled and should be treated as an INVITE failure      
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      case OnUpdateOffer:
         if (mProposedLocalOfferAnswer)
         {
            WarningLog (<< "Invalid UPDATE with offer received in early state with pending offer: " << msg.brief());
            auto response = std::make_shared<SipMessage>();
            mDialog.makeResponse(*response, msg, 500);  // RFC3311 - section 5.2
            InfoLog (<< "Sending " << response->brief());
            send(response);
         }
         else
         {
            *mLastRemoteSessionModification = msg;
            transition(UAC_ReceivedUpdateEarly);
            mCurrentEncryptionLevel = getEncryptionLevel(msg);
            mProposedRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
            handler->onOffer(getSessionHandle(), msg, *offerAnswer);
         }
         break;

      case OnUpdate:
      {
         // ?slg? no offerAnswer in update - just respond immediately - do we need a callback?
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, msg, 200);
         send(response);
         break;
      }
     
      case On200Prack:
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchAnswered (const SipMessage& msg)
{
   //InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   const auto offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On1xx:
      case On1xxEarly:
      case On1xxOffer:
         // late, so ignore
         break;

      case On2xxOffer:
      case On2xx:
      case On2xxAnswer:
         // retransmission
         break;

      case OnRedirect:
         // too late
         break;

      // .slg. This doesn't really make sense (after a 2xx)
      case OnGeneralFailure:
      case On422Invite:
         break;
#if 0 // !jf! don't think this is right
      {
         sendBye();
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;
      }
#endif
      case OnBye:
         dispatchBye(msg);
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);         
         break;
   }

}

void
ClientInviteSession::dispatchEarlyWithOffer (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On1xx: // must be reliable
         handleProvisional(msg);
         sendPrackIfNeeded(msg);
         break;

      case On2xx:
      case On2xxAnswer:
         sendAck();
         sendBye();
         InfoLog (<< "Failure:  no answer sent: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionally not handled and should be treated as an INVITE failure      
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
      case On491Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      case OnUpdateOffer:
      {
         WarningLog (<< "Invalid UPDATE with offer received in early state with pending offer: " << msg.brief());
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, msg, 500);  // RFC3311 - section 5.2
         InfoLog (<< "Sending " << response->brief());
         send(response);
         break;
      }

      case OnUpdate:
      {
         // ?slg? no offerAnswer in update - just respond immediately - do we need a callback?
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, msg, 200);
         send(response);
         break;
      }

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchSentAnswer (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On200Prack:
         transition(UAC_EarlyWithAnswer);
         break;

      case On2xx:
         transition(Connected);
         sendAck();
         handleFinalResponse(msg);
         onConnectedAspect(getHandle(), msg);
         break;

      case On2xxOffer:
          // RFC6337 section 3.1.2 recommends we ignore any illegal SDP here to be more interoperable
          WarningLog(<< "Ignoring illegal SDP offer in 2xx: " << msg.brief());
          transition(Connected);
          sendAck();
          handleFinalResponse(msg);
          onConnectedAspect(getHandle(), msg);
          break;

      case On2xxAnswer:
      case On1xxAnswer:
         sendAck();
         sendBye();
         WarningLog(<< "Failure:  illegal offer/answer: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;

      case On1xxOffer:
         // RFC6337 section 3.1.2 recommends we ignore any illegal SDP here to be more interoperable
         WarningLog(<< "Ignoring illegal SDP offer in 1xx: " << msg.brief());
         // No break is intentional
      case On1xx:
         handleProvisional(msg);
         sendPrackIfNeeded(msg);
         break;

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionally not handled and should be treated as an INVITE failure
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
      case On491Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchQueuedUpdate (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On200Prack:
         transition(UAC_SentUpdateEarly);
         {
            mDialog.makeRequest(*mLastLocalSessionModification, UPDATE);
            InviteSession::setOfferAnswer(*mLastLocalSessionModification, mProposedLocalOfferAnswer.get());

            DumHelper::setOutgoingEncryptionLevel(*mLastLocalSessionModification, mProposedEncryptionLevel);
            send(mLastLocalSessionModification);
         }
         break;

      case On2xx:
         transition(SentUpdate);
         {
            sendAck();

            auto update = std::make_shared<SipMessage>();
            mDialog.makeRequest(*update, UPDATE);
            InviteSession::setOfferAnswer(*update, mProposedLocalOfferAnswer.get());
            DumHelper::setOutgoingEncryptionLevel(*update, mProposedEncryptionLevel);
            send(update);
         }
         handleFinalResponse(msg);
         onConnectedAspect(getHandle(), msg);
         break;

      case On2xxOffer:
          // RFC6337 section 3.1.2 recommends we ignore any illegal SDP here to be more interoperable
          WarningLog(<< "Ignoring illegal SDP offer in 2xx: " << msg.brief());
          transition(Connected);
          sendAck();
          handleFinalResponse(msg);
          onConnectedAspect(getHandle(), msg);
          break;

      case On2xxAnswer:
      case On1xxAnswer:
         sendAck();
         sendBye();
         WarningLog(<< "Failure:  illegal offer/answer: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;

      case On1xxOffer:
         // RFC6337 section 3.1.2 recommends we ignore any illegal SDP here to be more interoperable
         WarningLog(<< "Ignoring illegal SDP offer in 1xx: " << msg.brief());
         // No break is intentional
      case On1xx:
         handleProvisional(msg);
         sendPrackIfNeeded(msg);
         break;

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionally not handled and should be treated as an INVITE failure
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
      case On491Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}



void
ClientInviteSession::dispatchEarlyWithAnswer (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   std::unique_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On1xxOffer:
         // RFC6337 section 3.1.1 recommends we ignore any illegal SDP here to be more interoperable
         WarningLog(<< "Ignoring illegal SDP offer in 1xx: " << msg.brief());
         // No break is intentional
      case On1xx:
         handleProvisional(msg);
         sendPrackIfNeeded(msg);
         break;

      case On2xx:
         transition(Connected);
         sendAck();
         handleFinalResponse(msg);
         onConnectedAspect(getHandle(), msg);
         break;

      case On2xxOffer:
          // RFC6337 section 3.1.1 recommends we ignore any illegal SDP here to be more interoperable
          WarningLog(<< "Ignoring illegal SDP offer in 2xx: " << msg.brief());
          transition(Connected);
          sendAck();
          handleFinalResponse(msg);
          onConnectedAspect(getHandle(), msg);
          break;

      case On2xxAnswer:
         sendAck();
         sendBye();
         WarningLog(<< "Failure:  illegal offer/answer: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         break;

      case OnUpdateOffer:
         *mLastRemoteSessionModification = msg;
         transition(UAC_ReceivedUpdateEarly);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         mProposedRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onOffer(getSessionHandle(), msg, *offerAnswer);
         break;

      case OnUpdate:
      {
         // ?slg? no offerAnswer in update - just respond immediately - do we need a callback?
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, msg, 200);
         send(response);
         break;
      }

      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets here then it's because the redirect was intentionaly not handled and should be treated as an INVITE failure
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
      case On491Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case OnBye:
         dispatchBye(msg);
         break;

      case On200Prack:
         // We may have sent a PRACK with an offer (if provideOffer was called from onAnswer 
         // from the first reliable provisional) - if so this will have SDP we need to call onAnswer
         if (offerAnswer && mProposedLocalOfferAnswer)
         {
            setCurrentLocalOfferAnswer(msg);
            mCurrentEncryptionLevel = getEncryptionLevel(msg);
            mCurrentRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
            handler->onAnswer(getSessionHandle(), msg, *offerAnswer);
         }
         break;

      default:
         // !kh!
         // should not assert here for peer sent us garbage.
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchSentUpdateEarly (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   const auto offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case On200Update:
         transition(UAC_EarlyWithAnswer);
         setCurrentLocalOfferAnswer(msg);
         mCurrentEncryptionLevel = getEncryptionLevel(msg);
         mCurrentRemoteOfferAnswer = InviteSession::makeOfferAnswer(*offerAnswer);
         handler->onAnswer(getSessionHandle(), msg, *offerAnswer);
         break;
         
      case OnUpdateOffer:
         {
            auto response = std::make_shared<SipMessage>();
            mDialog.makeResponse(*response, msg, 491);
            send(response);
         }
         break;
         
      case OnUpdate:
      {
         // ?slg? no offerAnswer in update - just respond immediately - do we need a callback?
         auto response = std::make_shared<SipMessage>();
         mDialog.makeResponse(*response, msg, 200);
         send(response);
         break;
      }

      case On491Update:
         transition(UAC_SentUpdateEarlyGlare);
         start491Timer();
         break;

      case On2xx:
         transition(SentUpdate);         
         sendAck();
         break;
         
      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets
                       // here then it's because the redirect was intentionally
                       // not handled and should be treated as an INVITE failure
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      case On200Prack:
         break;

      default:
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchSentUpdateEarlyGlare (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   const auto offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case OnUpdateOffer:
         handler->onOfferRejected(getSessionHandle(), &msg);
         //will cause transition to UAC_ReceivedUpdateEarly
         dispatchEarlyWithAnswer(msg);
         break;
         
      case On2xx:
         //transition to connected state machine
         transition(SentUpdateGlare);         
         sendAck();
         break;
         //!dcm! TODO This block below is repeated many, many times...refactor
         //!into a method. Prob. represents the effective ClientInvite superstate.
      case OnRedirect: // Redirects are handled by the DialogSet - if a 3xx gets
                       // here then it's because the redirect was intentionally
                       // not handled and should be treated as an INVITE failure
      case OnInviteFailure:
      case OnGeneralFailure:
      case On422Invite:
      case On487Invite:
         InfoLog (<< "Failure:  error response: " << msg.brief());
         transition(Terminated);
         onFailureAspect(getHandle(), msg);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
         mDum.destroy(this);
         break;

      default:
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
}

void
ClientInviteSession::dispatchReceivedUpdateEarly(const SipMessage& msg)
{
   const auto offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case OnUpdate:
      case OnUpdateOffer:
         // If we receive an UPDATE before we have generated a final response to a previous UPDATE on the 
         // same dialog, then we MUST return a 500 response with a Retry-After header (random duration 0-10 seconds)
         {
            auto u500 = std::make_shared<SipMessage>();
            mDialog.makeResponse(*u500, msg, 500);
            u500->header(h_RetryAfter).value() = Random::getRandom() % 10;
            send(u500);
         }
         break;

      default:
         WarningLog (<< "Don't know what this is : " << msg);
         break;
   }
   WarningLog (<< "Ignoring message received in ReceivedUpdateEarly: " << msg);
}

void
ClientInviteSession::dispatchCancelled (const SipMessage& msg)
{
   InviteSessionHandler* handler = mDum.mInviteSessionHandler;
   const auto offerAnswer = InviteSession::getOfferAnswer(msg);

   switch (toEvent(msg, offerAnswer.get()))
   {
      case OnGeneralFailure:
      case OnCancelFailure:
      case On487Invite:
      case OnRedirect:
      case On422Invite:
      case On491Invite:
      case OnInviteFailure:
         transition(Terminated);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalCancel, &msg);
         mDum.destroy(this);
         break;

      case On2xx:
      case On2xxOffer:
      case On2xxAnswer:
      {
         // this is the 2xx crossing the CANCEL case
         sendAck();
         sendBye();
         transition(Terminated);
         handler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalCancel, &msg);
         mCancelledTimerSeq++;
         break;
      }

      case OnBye:
         dispatchBye(msg);
         break;

      default:
        if(msg.isRequest())
        {
           // We have canceled - reject any new requests
           std::shared_ptr<SipMessage> rsp(new SipMessage);
           mDialog.makeResponse(*rsp, msg, 481);
           send(rsp);
        }
        break;
   }
}

//true if 180rel should be ignored. Saves rseq as a side effect.
bool 
ClientInviteSession::isBadRseq(const SipMessage& msg)
{
   int code = msg.isResponse() ? msg.header(h_StatusLine).statusCode() : 0;
   if (msg.method() == INVITE && code > 100 && code < 200)
   {
      if (msg.exists(h_RSeq))
      {
         // store state about the provisional if reliable, so we can detect retransmissions
         unsigned int rseq = (unsigned int) msg.header(h_RSeq).value();
         unsigned int lastRseq = (unsigned int) mRelRespInfo.rSequence();
         
         if (rseq == lastRseq)
         {
            DebugLog(<< "Discarding reliable 1xx retranmission with rseq " << rseq);
            return true;
         }
         else if (lastRseq != 0 && rseq > lastRseq + 1)
         {
            DebugLog(<< "Discarding out of order reliable 1xx with rseq " << rseq);
            return true;
         }
         mRelRespInfo.rSequence() = rseq;
         mRelRespInfo.cSequence() = msg.header(h_CSeq).sequence();
         mRelRespInfo.method() = msg.header(h_CSeq).method();
      }
   }
   return false;
}

void 
ClientInviteSession::onConnectedAspect(ClientInviteSessionHandle c, const SipMessage& msg)
{
   if (mDum.mDialogEventStateManager)
   {
      mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
   }
   mDum.mInviteSessionHandler->onConnected(c, msg);
}

void 
ClientInviteSession::onProvisionalAspect(ClientInviteSessionHandle c, const SipMessage& msg)
{
   if (mDum.mDialogEventStateManager)
   {
      mDum.mDialogEventStateManager->onEarly(mDialog, getSessionHandle());
   }
   mDum.mInviteSessionHandler->onProvisional(c, msg);
}

void 
ClientInviteSession::onFailureAspect(ClientInviteSessionHandle c, const SipMessage& msg)
{
   if (mDum.mDialogEventStateManager)
   {
      InviteSessionHandler::TerminatedReason reason = InviteSessionHandler::Rejected;
      if (msg.isResponse())
      {
         if (msg.header(h_StatusLine).responseCode() == 408)
         {
            reason = InviteSessionHandler::Timeout;
         }
         else if (msg.header(h_StatusLine).responseCode() / 100 == 5)
         {
            reason = InviteSessionHandler::Error;
         }
      }
      mDum.mDialogEventStateManager->onTerminated(mDialog, msg, reason);
   }
   mDum.mInviteSessionHandler->onFailure(c, msg);
}
   
/* ====================================================================
 * The Vovida Software License, Version 1.0
 *
 * Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the

 *    distribution.
 *
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
 *    and "Vovida Open Communication Application Library (VOCAL)" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact vocal@vovida.org.
 *
 * 4. Products derived from this software may not be called "VOCAL", nor
 *    may "VOCAL" appear in their name, without prior written
 *    permission of Vovida Networks, Inc.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by Vovida
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
 * Inc.  For more information on Vovida Networks, Inc., please see
 * <http://www.vovida.org/>.
 *
 */
