Exemple #1
0
// Queue a message to be sent to the specified address and port.
UtlBoolean SipClient::sendTo(SipMessage& message,
                             const char* address,
                             int port)
{
   UtlBoolean sendOk;

   if (mClientSocket)
   {
      // If port == PORT_NONE, get the correct default port for this
      // transport method.
      int portToSendTo = ( port == PORT_NONE ? defaultPort() : port );

      // We are about to post a message that will cause the
      // SIP message to be sent.  Notify the user agent so
      // that it can offer the message to all its registered
      // output processors.

      ssize_t msgLength = 0;
      UtlString msgText;
      message.getBytes(&msgText, &msgLength, true);
      if (msgLength)
      {
        system_tap_sip_tx(
             mLocalHostAddress.data(), portIsValid(mLocalHostPort) ? mLocalHostPort : defaultPort(),
             address, portToSendTo,
             msgText.data(), msgLength);

        mpSipUserAgent->executeAllSipOutputProcessors( message, address, portToSendTo );
      }

      // Create message to queue.
      SipClientSendMsg sendMsg(OsMsg::OS_EVENT,
                               SipClientSendMsg::SIP_CLIENT_SEND,
                               message, address,
                               portToSendTo );

      // Post the message to the task's queue.
      OsStatus status = postMessage(sendMsg, OsTime::NO_WAIT);
      sendOk = status == OS_SUCCESS;
      if (!sendOk)
      {
         Os::Logger::instance().log(FAC_SIP, PRI_ERR,
                       "SipClient[%s]::sendTo attempt to post message failed",
                       mName.data());
      }
   }
   else
   {
      Os::Logger::instance().log(FAC_SIP, PRI_CRIT,
                    "SipClient[%s]::sendTo called for client without socket",
                    mName.data()
         );
      sendOk = FALSE;
   }

   return sendOk;
}
UtlBoolean SipSubscribeServerEventHandler::getNotifyContent(const UtlString& resourceId,
                                                            const UtlString& eventTypeKey,
                                                            const UtlString& eventType,
                                                            SipPublishContentMgr& contentMgr,
                                                            const char* acceptHeaderValue,
                                                            SipMessage& notifyRequest,
                                                            int& version)
{
    UtlBoolean gotBody = FALSE;
    // Default behavior is to just go get the content from
    // the content manager and attach it to the notify
    HttpBody* messageBody = NULL;
    UtlBoolean isDefaultEventContent;
    gotBody = contentMgr.getContent(resourceId,
                                    eventTypeKey,
                                    eventType,
                                    acceptHeaderValue,
                                    messageBody,
                                    version,
                                    isDefaultEventContent);

    // The body will be freed with the NOTIFY message.
    if(messageBody)
    {
        const char* contentTypePtr = messageBody->getContentType();
        UtlString contentType;
        if(contentTypePtr)
        {
            contentType = contentTypePtr;
        }
        else
        {
            OsSysLog::add(FAC_SIP, PRI_ERR,
                "SipSubscribeServerEventHandler::getNotifyContent body published for resourceId: '%s' eventTypeKey: '%s' with no content type",
                resourceId.data() ? resourceId.data() : "<null>", 
                eventTypeKey.data() ? eventTypeKey.data() : "<null>");

            contentType = "text/unknown";
        }
          
        notifyRequest.setContentType(contentType);
        notifyRequest.setBody(messageBody);
        
        UtlString request;
        ssize_t requestLength;
        notifyRequest.getBytes(&request, &requestLength);   
        OsSysLog::add(FAC_SIP, PRI_DEBUG,
                      "SipSubscribeServerEventHandler::getNotifyContent resourceId '%s', eventTypeKey '%s' contentType '%s' NOTIFY message length = %zu, message = '%s'",
                      resourceId.data(), eventTypeKey.data(),
                      contentType.data(), requestLength, request.data());
    }

    return(gotBody);
}
Exemple #3
0
UtlBoolean SipUserAgentStateless::sendTo(SipMessage& message,
        const char* sendAddress,
        const char* sendProtocol,
        int sendPort)
{
    UtlBoolean sendOk = FALSE;

    if(sendAddress && *sendAddress && mpUdpServer)
    {
        if (!portIsValid(sendPort))
        {
            sendPort = SIP_PORT;
        }

        sendOk = mpUdpServer->sendTo(message, sendAddress, sendPort);
    }

    // Log the message
    if (isMessageLoggingEnabled())
    {
        UtlString messageStatus;
        char messageChars[200];
        if(sendOk)
            sprintf(messageChars, "Sent message %s port:%d:\n",
                    sendAddress, sendPort);
        else
            sprintf(messageChars, "Failed to send message %s port:%d:\n",
                    sendAddress, sendPort);
        messageStatus = messageChars;
        UtlString msgBytes;
        int msgLen;
        message.getBytes(&msgBytes, &msgLen);
        msgBytes.insert(0, messageStatus);
        msgBytes.append("--------------------END--------------------\n");
        logMessage(msgBytes.data(), msgBytes.length());
    }

    return(sendOk);
}
//functions
UtlBoolean
SubscribeServerThread::handleMessage(OsMsg& eventMessage)
{

    // Only handle SIP messages
    if (eventMessage.getMsgType() != OsMsg::PHONE_APP ||
        eventMessage.getMsgSubType() != SipMessage::NET_SIP_MESSAGE)
    {
       return FALSE ;
    }

    const SipMessage* message =
        ((SipMessageEvent&)eventMessage).getMessage();

    UtlString userKey;
    UtlString uri;
    SipMessage finalResponse;

    // Test for request/response processing code path
    if (!message->isResponse())
    {
        // this is a request, so authenticate and authorize the request
        if ( isValidDomain( message, &finalResponse ) )
        {
            UtlString eventPackage;
            UtlString id;
            UtlHashMap otherParams;

            message->getEventField(&eventPackage, &id, &otherParams);

            StatusPluginReference* pluginContainer =
                mPluginTable->getPlugin( eventPackage );

            if( pluginContainer )
            {
               //check in credential database if authentication needed
               UtlString authenticatedUser, authenticatedRealm;
               if( isAuthenticated ( message, &finalResponse, authenticatedUser, authenticatedRealm ) )
               {
                  if ( isAuthorized ( message, &finalResponse, pluginContainer ) )
                  {
                     // fetch the plugin
                     SubscribeServerPluginBase* plugin =
                        pluginContainer->getPlugin();

                     if (plugin)
                     {
                        int timeNow = (int)OsDateTime::getSecsSinceEpoch();
                        int grantedExpiration;
                        UtlString newToTag;

                        // add the subscription to the IMDB
                        SubscribeStatus isSubscriptionAdded
                           = addSubscription(timeNow,
                                             message,
                                             mDefaultDomain,
                                             eventPackage,
                                             id,
                                             otherParams,
                                             newToTag,
                                             grantedExpiration);

                        otherParams.destroyAll();

                        switch ( isSubscriptionAdded )
                        {
                        case STATUS_SUCCESS:
                           // create response - 202 Accepted Response
                           finalResponse.setResponseData( message,
                                                          SIP_ACCEPTED_CODE,
                                                          SIP_ACCEPTED_TEXT);
                           // Set the granted subscription time.
                           finalResponse.setExpiresField(grantedExpiration);

                           plugin->handleSubscribeRequest( *message,
                                                           finalResponse,
                                                           authenticatedUser.data(),
                                                           authenticatedRealm.data(),
                                                           mDefaultDomain.data());

                           // ensure that the contact returned will route back to here
                           // (the default supplied by SipUserAgent will not).
                           {
                              UtlString requestUri;
                              message->getRequestUri(&requestUri);
                              finalResponse.setContactField(requestUri);
                           }
                           break;

                        case STATUS_TO_BE_REMOVED:
                           // create response - 202 Accepted Response
                           finalResponse.setResponseData( message,
                                                          SIP_ACCEPTED_CODE,
                                                          SIP_ACCEPTED_TEXT);
                           // Set the granted subscription time.
                           finalResponse.setExpiresField(grantedExpiration);

                           plugin->handleSubscribeRequest( *message,
                                                           finalResponse,
                                                           authenticatedUser.data(),
                                                           authenticatedRealm.data(),
                                                           mDefaultDomain.data());

                           // ensure that the contact returned will route back to here
                           // (the default supplied by SipUserAgent will not).
                           {
                              UtlString requestUri;
                              message->getRequestUri(&requestUri);
                              finalResponse.setContactField(requestUri);
                           }
                           // Now that final NOTIFY has been sent, remove row
                           removeSubscription(message);

                           break;

                        case STATUS_LESS_THAN_MINEXPIRES:
                           // (already logged in addSubscription)

                           // send 423 Subscription Too Brief response
                           finalResponse.setResponseData(
                              message,
                              SIP_TOO_BRIEF_CODE,
                              SIP_TOO_BRIEF_TEXT );

                           finalResponse.setHeaderValue(
                              SIP_MIN_EXPIRES_FIELD,
                              mMinExpiresTimeStr,
                              0 );
                           break;

                        case STATUS_INVALID_REQUEST:
                           OsSysLog::add(FAC_SIP, PRI_ERR,
                                         "SubscribeServerThread::handleMessage()"
                                         "Subscription Could Not Be Added "
                                         SIP_BAD_REQUEST_TEXT
                              );

                           finalResponse.setResponseData(
                              message,
                              SIP_BAD_REQUEST_CODE,
                              SIP_BAD_REQUEST_TEXT );
                           break;

                        case STATUS_FORBIDDEN:
                           OsSysLog::add(FAC_SIP, PRI_ERR,
                                         "SubscribeServerThread::handleMessage()"
                                         "Subscription Could Not Be Added "
                                         SIP_FORBIDDEN_TEXT
                              );

                           finalResponse.setResponseData(
                              message,
                              SIP_FORBIDDEN_CODE,
                              SIP_FORBIDDEN_TEXT);
                           break;

                        case STATUS_NOT_FOUND:
                           OsSysLog::add(FAC_SIP, PRI_ERR,
                                         "SubscribeServerThread::handleMessage()"
                                         "Subscription Could Not Be Added "
                                         SIP_NOT_FOUND_TEXT
                              );
                           finalResponse.setResponseData(
                              message,
                              SIP_NOT_FOUND_CODE,
                              SIP_NOT_FOUND_TEXT );
                           break;

                        case STATUS_BAD_SUBSCRIPTION:
                           // send 481 Subscription Does Not Exist response
                           OsSysLog::add(FAC_SIP, PRI_DEBUG,
                                         "SubscribeServerThread::handleMessage()"
                                         "Subscription to be renewed does not exist "
                                         SIP_BAD_SUBSCRIPTION_TEXT
                              );
                           finalResponse.setResponseData(
                              message,
                              SIP_BAD_SUBSCRIPTION_CODE,
                              SIP_BAD_SUBSCRIPTION_TEXT );
                           break;

                        case STATUS_INTERNAL_ERROR:
                        default:
                           OsSysLog::add(FAC_SIP, PRI_ERR,
                                         "SubscribeServerThread::handleMessage()"
                                         "Subscription Could Not Be Added "
                                         "Status %d from addSubscription",
                                         isSubscriptionAdded
                              );
                           finalResponse.setResponseData(
                              message,
                              SIP_SERVER_INTERNAL_ERROR_CODE,
                              "Subscription database error" );
                        }

                        // Apply the new to-tag, if any, to the response.
                        if (!newToTag.isNull())
                        {
                           finalResponse.setToFieldTag(newToTag);
                        }
                     }
                     else
                     {
                        OsSysLog::add(FAC_SIP, PRI_CRIT,
                                      "SubscribeServerThread::handleMessage()"
                                      " container->getPlugin failed for '%s'",
                                      eventPackage.data()
                           );
                        finalResponse.setResponseData(
                           message,
                           SIP_SERVER_INTERNAL_ERROR_CODE,
                           SIP_SERVER_INTERNAL_ERROR_TEXT );
                     }
                  }
                  else
                  {
                     // not authorized - the response was created in isAuthorized
                  }
               }
               else
               {
                  // not authenticated - the response was created in isAuthenticated
               }
            }
            else // no plugin found for this event type
            {
               OsSysLog::add(FAC_SIP, PRI_WARNING,
                             "SubscribeServerThread::handleMessage()"
                             " Request denied - "
                             SIP_BAD_EVENT_TEXT
                  );
               finalResponse.setResponseData( message,
                                              SIP_BAD_EVENT_CODE,
                                              "Event type not supported" );
            }

            // send final response
            UtlString finalMessageStr;
            ssize_t finalMessageLen;
            finalResponse.getBytes(&finalMessageStr, &finalMessageLen);
            OsSysLog::add(FAC_SIP, PRI_DEBUG, "\n----------------------------------\n"
                "Sending final response\n%s",finalMessageStr.data());
            mpSipUserAgent->setUserAgentHeader( finalResponse );
            mpSipUserAgent->send( finalResponse );
        }
        else // Invalid domain
        {
           const char* notFoundMsg = SIP_NOT_FOUND_TEXT " Invalid Domain";
           finalResponse.setResponseData(message,
                                         SIP_NOT_FOUND_CODE,
                                         notFoundMsg
                                         );
           mpSipUserAgent->setUserAgentHeader( finalResponse );
           mpSipUserAgent->send( finalResponse );
        }
    }
    else // response
    {
       // The server may send us back a "481" response, if it does we need
       // to remove the subscription from the SubscriptionDB as the callid
       // that it corresponds to is stale (probably the phone was rebooted)
       // In the above case, RFC 3265 says we MUST remove the subscription.
       // It also says (essentially) that any error that does not imply a retry
       // SHOULD remove the subscription.  We will interpret this to be any
       // 4xx code _except_ 408 timeout (because that may be a transient error).
       int responseCode = message->getResponseStatusCode();
       if (   responseCode >= SIP_4XX_CLASS_CODE
           && responseCode != SIP_REQUEST_TIMEOUT_CODE )
       {
          // remove the subscription
          removeErrorSubscription ( *message );
       }
    }
    return TRUE;
}
UtlBoolean CommandMsgProcessor::handleMessage(OsMsg& eventMessage)
{
    int msgType = eventMessage.getMsgType();
    // int msgSubType = eventMessage.getMsgSubType();

    if(msgType == OsMsg::PHONE_APP)
        // && msgSubType == CP_SIP_MESSAGE)
    {
        osPrintf("CommandMsgProcessor::handleMessage Got a message\n");
        int messageType = ((SipMessageEvent&)eventMessage).getMessageStatus();

        const SipMessage* sipMsg = ((SipMessageEvent&)eventMessage).getMessage();
        UtlString callId;
        if(sipMsg)
        {
            osPrintf("numRespondToMessages: %d isResponse: %d messageType: %d TransErro: %d\n",
                     numRespondToMessages, sipMsg->isResponse(), messageType,
                     SipMessageEvent::TRANSPORT_ERROR);
            if((numRespondToMessages == -1 || numRespondToMessages > 0) &&
                    !sipMsg->isResponse() && messageType != SipMessageEvent::TRANSPORT_ERROR)
            {
                osPrintf("valid message\n");
                if(numRespondToMessages > 0)
                {
                    numRespondToMessages--;
                }

                SipMessage response;
                if(mpResponseMessage)
                {
                    response = *mpResponseMessage;
                }
                response.setResponseData(sipMsg, responseStatusCode, responseStatusText.data());

                UtlString address;
                int port;
                UtlString protocol;
                UtlString tag;

                sipMsg->getToAddress(&address,
                                     &port,
                                     &protocol,
                                     NULL,
                                     NULL,
                                     &tag) ;

                if( tag.isNull())
                {
                    int tagNum = rand();
                    char tag[100];
                    sprintf(tag, "%d", tagNum);
                    UtlString tagWithDot(tag);
                    tagWithDot.append(".34756498567498567");
                    response.setToFieldTag(tagWithDot);
                }

                UtlString msgBytes;
                int msgLen;
                response.getBytes(&msgBytes, &msgLen);
                osPrintf("%s",msgBytes.data());

                if(mpLastResponseMessage)
                {
                    delete mpLastResponseMessage;
                    mpLastResponseMessage = NULL;
                }
                // Keep a copy of the last response sent
                mpLastResponseMessage = new SipMessage(response);

                if(userAgent->send(response))
                {
                    osPrintf("Sent response\n");
                }
                else
                {
                    osPrintf("Send failed\n");
                }
            }
        }
    }
    return(TRUE);
}
   void AppearanceTest()
   {
      instantiateAllTestFixtures( "appearance-groups1.xml",
                                  "subscription1",
                                  "credential1",
                                  "177.0.0.1:54140");

      UtlString sharedUri = "sip:[email protected]:54140";
      UtlString app1uri = "sip:127.0.0.1:45141";
      UtlString dialogHandle;

      // receive the reg-info subscribe
      SipMessage request;
      UtlString b;
      ssize_t l;
      while(getNextMessageFromAppearanceAgentUnderTest( request, 5 ))
      {
         request.getBytes(&b, &l);
         OsSysLog::add(FAC_RLS, PRI_DEBUG, "got message %s", b.data());
         UtlString method;
         request.getRequestMethod(&method);
         if(!request.isResponse() &&
            0 == method.compareTo(SIP_SUBSCRIBE_METHOD) )
         {
            // Accept the Subscription, regardless of whether it for a 'dialog' or 'reg' event
            // in order to stop retransmissions
            SipMessage regResponse;
            regResponse.setResponseData(&request, 202, "Accepted", app1uri);
            SipMessage * dispatchedMessage = new SipMessage(regResponse);
            dispatchedMessage->getBytes(&b, &l);
            OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent message %s", b.data());
            pAppearanceAgentUnderTest->mServerUserAgent.dispatch(dispatchedMessage);

            // Deal with the two events separately
            UtlString eventField;
            request.getEventField(eventField);
            if(0 == eventField.compareTo("reg"))
            {
               UtlString contactInfo;
               request.getContactUri(0, &contactInfo);
               UtlString callid;
               request.getCallIdField(&callid);
               int cseq;
               request.getCSeqField(&cseq, NULL);

               Url toField;
               regResponse.getToUrl(toField);

               SipMessage regNotify;
               regNotify.setNotifyData(&request, 1, "", "", "reg");
               UtlString regInfo ("<?xml version=\"1.0\"?>\r\n"
                                 "<reginfo xmlns=\"urn:ietf:params:xml:ns:reginfo\" "
                                 "xmlns:gr=\"urn:ietf:params:xml:ns:gruuinfo\" version=\"911\" state=\"full\">\r\n"
                                 "   <registration aor=\"sip:[email protected]:54140\" id=\"sip:[email protected]:54140\" state=\"active\">\r\n "
                                 "      <contact id=\"sip:[email protected]:54140@@&lt;");
               regInfo.append(contactInfo);
               regInfo.append("&gt;\" state=\"active\" event=\"registered\" q=\"1\" callid=\"");
               regInfo.append(callid);
               regInfo.append("\" cseq=\"");
               regInfo.appendNumber(cseq);
               regInfo.append("\">\r\n");
               regInfo.append("<uri>");
               regInfo.append(app1uri);
               regInfo.append("</uri>");
               regInfo.append("      </contact>\r\n"
                              "   </registration>\r\n"
                              "</reginfo>");
               HttpBody * newBody = new HttpBody (regInfo, strlen(regInfo), "application/reginfo+xml");
               regNotify.setContentType("application/reginfo+xml");
               regNotify.setBody(newBody);

               // Set the From field the same as the to field from the 202 response, as it
               // contains the dialog identifying to tags
               regNotify.setRawFromField(toField.toString().data());
               sendToAppearanceAgentUnderTest( regNotify );
               regNotify.getBytes(&b, &l);
               OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent reg NOTIFY to AppAgent");
               OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent message %s", b.data());
            }
            else if (0 == eventField.compareTo(SLA_EVENT_TYPE))
             {
                // should send empty NOTIFY, but no one will care
                // save dialogHandle for this subscription/Appearance (ignore retransmissions)
                if (dialogHandle.isNull())
                {
                   SipMessage fake(b);
                   fake.getDialogHandle(dialogHandle);
                   OsSysLog::add(FAC_RLS, PRI_DEBUG, "got SUBSCRIBE(sla) request: dialogHandle %s", dialogHandle.data());
                }
             }
         }
      }

      CPPUNIT_ASSERT( !dialogHandle.isNull() );
      OsSysLog::add(FAC_RLS, PRI_DEBUG, "we now have an Appearance - test it");
      AppearanceGroup* pAppGroup = pAppearanceAgentUnderTest->getAppearanceGroupSet().
         findAppearanceGroup(sharedUri);
      CPPUNIT_ASSERT( pAppGroup );

      Appearance* pApp = pAppGroup->findAppearance(dialogHandle);
      CPPUNIT_ASSERT( pApp );
      ASSERT_STR_EQUAL( app1uri.data(), pApp->getUri()->data() );

      // test adding a new dialog
      const char* dialogEventString =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"partial\" entity=\"sip:[email protected]:54140\">\n"
            "<dialog id=\"1\" call-id=\"[email protected]\" local-tag=\"264460498\" remote-tag=\"1c10982\" direction=\"recipient\">\n"
            "<state>confirmed</state>\n"
            "<local>\n"
            "<identity>[email protected]:5120</identity>\n"
            "<target uri=\"sip:[email protected]:5120\">\n"
            "<param pname=\"x-line-id\" pval=\"0\"/>\n"
            "<param pname=\"+sip.rendering\" pval=\"yes\"/>\n"
            "</target>\n"
            "</local>\n"
            "<remote>\n"
            "<identity>[email protected]</identity>\n"
            "</remote>\n"
            "</dialog>\n"
            "</dialog-info>\n"
            ;
      SipDialogEvent dialogEvent(dialogEventString);
      bool bFullContentChanged = false;
      bool bPartialContentChanged = pApp->updateState(&dialogEvent, bFullContentChanged);
      CPPUNIT_ASSERT(bPartialContentChanged);
      CPPUNIT_ASSERT(bFullContentChanged);
      pApp->dumpState();
      CPPUNIT_ASSERT(pApp->appearanceIsBusy());
      CPPUNIT_ASSERT(pApp->appearanceIdIsSeized("0"));
      CPPUNIT_ASSERT(!pApp->appearanceIdIsSeized("1"));

      // simulate user putting the call on hold
      const char* dialogEventString2 =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"partial\" entity=\"sip:[email protected]:54140\">\n"
            "<dialog id=\"1\" call-id=\"[email protected]\" local-tag=\"264460498\" remote-tag=\"1c10982\" direction=\"recipient\">\n"
            "<state>confirmed</state>\n"
            "<local>\n"
            "<identity>[email protected]:5120</identity>\n"
            "<target uri=\"sip:[email protected]:5120\">\n"
            "<param pname=\"x-line-id\" pval=\"0\"/>\n"
            "<param pname=\"+sip.rendering\" pval=\"no\"/>\n"
            "</target>\n"
            "</local>\n"
            "<remote>\n"
            "<identity>[email protected]</identity>\n"
            "</remote>\n"
            "</dialog>\n"
            "</dialog-info>\n"
            ;
      SipDialogEvent dialogEvent2(dialogEventString2);
      bPartialContentChanged = pApp->updateState(&dialogEvent2, bFullContentChanged);
      CPPUNIT_ASSERT(bPartialContentChanged);
      CPPUNIT_ASSERT(bFullContentChanged);
      pApp->dumpState();
      CPPUNIT_ASSERT(!pApp->appearanceIsBusy());
      CPPUNIT_ASSERT(pApp->appearanceIdIsSeized("0"));
      CPPUNIT_ASSERT(!pApp->appearanceIdIsSeized("1"));

      // test MESSAGE debug handling
      const char* message =
         "MESSAGE sip:[email protected]:54140 SIP/2.0\r\n"
         "From: <sip:[email protected]>;tag=17211757-9E4FBD78\r\n"
         "To: <sip:[email protected]:54140>\r\n"
         "CSeq: 1 MESSAGE\r\n"
         "Call-ID: 51405734-b9be4835-dcd9d196\r\n"
         "Contact: <sip:[email protected]>\r\n"
         "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, INFO, MESSAGE, SUBSCRIBE, NOTIFY, PRACK, UPDATE, REFER\r\n"
         "Event: dialog\r\n"
         "User-Agent: UnitTest\r\n"
         "Accept-Language: en\r\n"
         "Accept: application/dialog-info+xml\r\n"
         "Max-Forwards: 70\r\n"
         "Expires: 3600\r\n"
         "Content-Length: 0\r\n"
         "\r\n";

      // send the MESSAGE
      SipMessage messageRequest( message, strlen( message ) );
      CPPUNIT_ASSERT( sendToAppearanceAgentUnderTest( messageRequest ) );

      // receive the 200 OK response
      SipMessage response;
      CPPUNIT_ASSERT( getNextMessageFromAppearanceAgentUnderTest( response, 5 ) );
      CPPUNIT_ASSERT( response.isResponse() );
      CPPUNIT_ASSERT( response.getResponseStatusCode() == SIP_OK_CODE );
   }
// Constructor
SipPersistentSubscriptionMgr::SipPersistentSubscriptionMgr(
   const UtlString& component,
   const UtlString& domain,
   const UtlString& fileName) :
   mComponent(component),
   mDomain(domain),
   mSubscriptionDBInstance(SubscriptionDB::getInstance(fileName)),
   mPersistenceTimer(mPersistTask.getMessageQueue(), 0),
   mPersistTask(mSubscriptionDBInstance)
{
   OsSysLog::add(FAC_SIP, PRI_DEBUG,
                 "SipPersistentSubscriptionMgr:: "
                 "mComponent = '%s', mDomain = '%s', fileName = '%s'",
                 mComponent.data(), mDomain.data(), fileName.data());

   // Start the persist task.
   mPersistTask.start();

   // Read the subscription table and initialize the SipSubscriptionMgr.

   unsigned long now = OsDateTime::getSecsSinceEpoch();
   ResultSet rs;
   mSubscriptionDBInstance->getAllRows(rs);
   UtlSListIterator itor(rs);
   UtlHashMap* rowp;
   while ((rowp = dynamic_cast <UtlHashMap*> (itor())))
   {
      if (OsSysLog::willLog(FAC_SIP, PRI_DEBUG))
      {
         UtlString out;
         UtlHashMapIterator itor(*rowp);
         UtlString* key;
         UtlContainable* value;
         while ((key = dynamic_cast <UtlString*> (itor())))
         {
            value = itor.value();
            if (!out.isNull())
            {
               out.append(", ");
            }
            out.append(*key);
            out.append(" = ");
            if (value->getContainableType() == UtlString::TYPE)
            {
               out.append("'");
               out.append(*(dynamic_cast <UtlString*> (value)));
               out.append("'");
            }
            else if (value->getContainableType() == UtlInt::TYPE)
            {
               out.appendNumber((int) (*(dynamic_cast <UtlInt*> (value))));
            }
            else
            {
               out.append(value->getContainableType());
            }
         }
         OsSysLog::add(FAC_SIP, PRI_DEBUG,
                       "SipPersistentSubscriptionMgr:: "
                       "table row: %s",
                       out.data());
      }

      // First, filter for rows that have the right component and have
      // not yet expired.
      UtlString* componentp =
         dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gComponentKey));
      assert(componentp);
      int expires =
         *(dynamic_cast <UtlInt*> (rowp->findValue(&SubscriptionDB::gExpiresKey)));
      if (componentp->compareTo(mComponent) == 0 &&
          expires - now >= 0)
      {
         OsSysLog::add(FAC_SIP, PRI_DEBUG,
                       "SipPersistentSubscriptionMgr:: "
                       "loading row");

         // Extract the values from the row.
         const UtlString* top =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gToKey));
         const UtlString* fromp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gFromKey));
         const UtlString* callidp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gCallidKey));
         const UtlString* eventtypep =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gEventtypeKey));
         const UtlString* eventidp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gIdKey));
         // Correct the null string if it is returned as
         // SPECIAL_IMDB_NULL_VALUE.
         if (eventidp->compareTo(special_imdb_null_value) == 0)
         {
            eventidp = &null_string;
         }
         int subcseq =
            *(dynamic_cast <UtlInt*> (rowp->findValue(&SubscriptionDB::gSubscribecseqKey)));
         const UtlString* urip =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gUriKey));
         const UtlString* contactp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gContactKey));
         const UtlString* routep =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gRecordrouteKey));
         // Correct the null string if it is returned as
         // SPECIAL_IMDB_NULL_VALUE.
         if (routep->compareTo(special_imdb_null_value) == 0)
         {
            routep = &null_string;
         }
         int notifycseq =
            *(dynamic_cast <UtlInt*> (rowp->findValue(&SubscriptionDB::gNotifycseqKey)));
         const UtlString* acceptp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gAcceptKey));
         int version =
            *(dynamic_cast <UtlInt*> (rowp->findValue(&SubscriptionDB::gVersionKey)));
         const UtlString* keyp =
            dynamic_cast <UtlString*> (rowp->findValue(&SubscriptionDB::gKeyKey));

         // Use SipSubscriptionMgr to update the in-memory data.

         // Construct a fake SUBSCRIBE request to carry most of the data
         // that updateDialogInfo needs.
         SipMessage subscribeRequest;
         OsSysLog::add(FAC_SIP, PRI_DEBUG,
                       "SipPersistentSubscriptionMgr:: expires = %d, now = %d",
                       (int) expires, (int) now);
         subscribeRequest.setSubscribeData(urip->data(),
                                           fromp->data(),
                                           top->data(),
                                           callidp->data(),
                                           subcseq,
                                           eventtypep->data(),
                                           acceptp->data(),
                                           eventidp->data(),
                                           contactp->data(),
                                           NULL,
                                           expires - now);
         // Install the saved Route as a set of Record-Route headers in the
         // SUBSCRIBE, so that insertDialogInfo will find and record the route.
         Url route_url;
         UtlString route_url_string;
         UtlString route_string(*routep);
         UtlString remainder_string;
         int route_index;
         for (route_index = 0;
              !route_string.isNull() &&
                 route_url.fromString(route_string, Url::NameAddr, &remainder_string);
              route_string = remainder_string, route_index++)
         {
            route_url.toString(route_url_string);
            subscribeRequest.setRecordRouteField(route_url_string.data(), route_index);
         }
         if (OsSysLog::willLog(FAC_SIP, PRI_DEBUG))
         {
            UtlString m, d;
            ssize_t l;
            subscribeRequest.getBytes(&m, &l, FALSE);
            OsSysLog::add(FAC_SIP, PRI_DEBUG,
                          "SipPersistentSubscriptionMgr:: subscribeRequest = '%s'",
                          m.data());
         }

         // Variables to hold the output of insertDialogInfo.
         UtlString subscribeDialogHandle;
         UtlBoolean isNew;
         UtlBoolean ret =
            SipSubscriptionMgr::insertDialogInfo(subscribeRequest,
                                                 // *keyp is the resource that
                                                 // is subscribed to.
                                                 *keyp,
                                                 *eventtypep,
                                                 expires,
                                                 notifycseq,
                                                 version,
                                                 subscribeDialogHandle,
                                                 isNew);
         if (!ret)
         {
            OsSysLog::add(FAC_SIP, PRI_ERR,
                          "SipPersistentSubscriptionMgr:: "
                          "SipSubscriptionMgr::insertDialogInfo failed keyp = '%s', eventtypep = '%s', subscribeDialogHandle = '%s'",
                          keyp->data(),
                          eventtypep->data(),
                          subscribeDialogHandle.data());
         }
         else
         {
            // Set the next NOTIFY CSeq value.
            // (The data in IMDB has already been set.)
            SipSubscriptionMgr::setNextNotifyCSeq(subscribeDialogHandle,
                                                  notifycseq,
                                                  version);
         }
      }
   }
}
/// Write as much of the buffered messages as can be written.
// Executed by the thread.
void SipClientWriteBuffer::writeMore()
{
   // 'exit_loop' will be set to TRUE if an attempt to write does
   // not write any bytes, and we will then return.
   UtlBoolean exit_loop = FALSE;

   while (mWriteQueued && !exit_loop)
   {
      if (mWritePointer >= mWriteString.length())
      {
         // We have written all of the first message.
         // Pop it and set up to write the next message.
         delete mWriteBuffer.get();
         mWriteString.remove(0);
         mWritePointer = 0;
         mWriteQueued = ! mWriteBuffer.isEmpty();
         if (mWriteQueued)
         {
            // get the message on the head of the queue, and figure out which kind it is
            UtlContainable* nextMsg = mWriteBuffer.first();
            SipMessage* sipMsg;
            UtlString* keepAliveMsg;
            if ((sipMsg = dynamic_cast<SipMessage*>(nextMsg))) // a SIP message
            {
               ssize_t length;
               sipMsg->getBytes(&mWriteString, &length);
            }
            else if ((keepAliveMsg = dynamic_cast<UtlString*>(nextMsg))) // a keepalive CRLF
            {
               mWriteString.append(*keepAliveMsg);
            }
            else
            {
               Os::Logger::instance().log(FAC_SIP, PRI_CRIT,
                             "SipClientWriteBuffer[%s]::writeMore "
                             "unrecognized message type in queue",
                             mName.data());
               assert(false);
               delete mWriteBuffer.get();
               mWriteQueued = mWriteBuffer.isEmpty();
            }
         }
      }
      else
      {
         // Some portion of the first message remains to be written.

         // If the socket has failed, attempt to reconnect it.
         // :NOTE: OsConnectionSocket::reconnect isn't implemented.
         if (!mClientSocket->isOk())
         {
            mClientSocket->reconnect();
         }

         // Calculate the length to write.
         int length = mWriteString.length() - mWritePointer;

         // ret is the value returned from write attempt.
         // -1 means an error was seen.
         int ret;
         if (mClientSocket->isOk())
         {
            // Write what we can.
            ret = mClientSocket->write(mWriteString.data() + mWritePointer, length);
            // Theoretically, ret > 0, since the socket is ready for writing,
            // but it appears that that ret can be 0.
         }
         else
         {
            // Record the error.
            ret = -1;
            // Set a special errno value, which hopefully is not a real value.
            errno = 1000;
         }

         if (ret > 0)
         {
            // We successfully sent some data, perhaps all of the
            // remainder of the first message.
            // Update the last-activity time.
            touch();
            // Update the state variables.
            mWritePointer += ret;
         }
         else if (ret == 0)
         {
            // No data sent, even though (in our caller) poll()
            // reported the socket was ready to write.
            exit_loop = TRUE;
         }
         else
         {
            // Error while writing.
            Os::Logger::instance().log(FAC_SIP, PRI_ERR,
                          "SipClientWriteBuffer[%s]::writeMore "
                          "OsSocket::write() returned %d, errno = %d",
                          getName().data(), ret, errno);
            // Return all buffered messages with a transport error indication.
            emptyBuffer(TRUE);
            // Because TCP is a connection protocol, we know that we cannot
            // send successfully any more and so should shut down this client.
            clientStopSelf();
            // Exit the loop so handleMessage() can process the stop request.
            exit_loop = TRUE;
         }
      }
   }
}