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 );
   }
/// Process an incoming NOTIFY from an Appearance.  Always sends a response.
void AppearanceGroup::handleNotifyRequest(const UtlString* dialogHandle,
                                          const SipMessage* msg)
{
   SipMessage response;
   Appearance* pThisAppearance = findAppearance(*dialogHandle);
   if ( !pThisAppearance )
   {
      UtlString swappedDialogHandle;
      mAppearanceGroupSet->swapTags(*dialogHandle, swappedDialogHandle);
      pThisAppearance = findAppearance(swappedDialogHandle);
      if ( !pThisAppearance )
      {
         // should never happen, since the NOTIFY was sent straight to the Appearance
         Os::Logger::instance().log(FAC_SAA, PRI_WARNING,
               "AppearanceGroup::handleNotifyRequest: ignoring NOTIFY from unknown subscription, dialogHandle %s",
               dialogHandle->data());
         response.setInterfaceIpPort(msg->getInterfaceIp(), msg->getInterfacePort());
         response.setResponseData(msg, 481, "Subscription does not exist");
         getAppearanceAgent()->getServerUserAgent().send(response);
         return;
      }
   }
   UtlString contactUri = pThisAppearance->getUri()->data();

   // check that event type is supported
   UtlString eventType;
   msg->getEventField(eventType);
   if (eventType != SLA_EVENT_TYPE)
   {
      Os::Logger::instance().log(FAC_SAA, PRI_INFO,
                    "AppearanceGroup::handleNotifyRequest: ignoring NOTIFY(%s): not an SLA event", eventType.data());
      response.setOkResponseData(msg, NULL);
      getAppearanceAgent()->getServerUserAgent().send(response);
      return;
   }

   // Get the NOTIFY content.
   const char* content;
   ssize_t l;
   const HttpBody* body = msg->getBody();
   if (body)
   {
      body->getBytes(&content, &l);
   }
   else
   {
      Os::Logger::instance().log(FAC_SAA, PRI_WARNING,
            "AppearanceGroup::handleNotifyRequest: could not get NOTIFY content, dialogHandle %s",
            dialogHandle->data());
      response.setInterfaceIpPort(msg->getInterfaceIp(), msg->getInterfacePort());
      response.setResponseData(msg, 493, "Undecipherable");
      getAppearanceAgent()->getServerUserAgent().send(response);
      return;
   }
   SipDialogEvent* lContent = new SipDialogEvent(content);

   UtlString state;
   UtlString entity;
   lContent->getState(state);
   lContent->getEntity(entity);

   UtlString dialogState = STATE_TERMINATED;
   UtlString event;
   UtlString code;
   UtlString appearanceId;

   if (state == STATE_TERMINATED)
   {
      // probably not needed now that SipSubscribeClient checks for NOTIFY with terminated state
      // our subscription to this Appearance has terminated.
      Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                    "AppearanceGroup::handleNotifyRequest: subscription to %s has been terminated",
                    contactUri.data());
      // terminate any non-held dialogs? or all dialogs?
   }

   Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                 "AppearanceGroup::handleNotifyRequest: %s update from %s",
                 state.data(), contactUri.data());

   // Assign each dialog a globally-unique ID, if not done yet
   UtlSListIterator* itor = lContent->getDialogIterator();
   Dialog* pDialog;
   while ( (pDialog = dynamic_cast <Dialog*> ((*itor)())) )
   {
      UtlString dialogId;
      UtlString uniqueDialogId;
      UtlString rendering;
      pDialog->getDialogId(dialogId);
      pDialog->getLocalParameter("x-line-id", appearanceId);
      pDialog->getLocalParameter("+sip.rendering", rendering);
      pDialog->getState(dialogState, event, code);

      // Ignore calls with no appearanceId -  these are considered "exclusive".
      // (e.g. MoH calls are private to the set involved)
      // These dialogs are not forwarded on to other sets in either partial or full updates.
      if ( appearanceId == "" )
      {
         Os::Logger::instance().log(FAC_SAA, PRI_DEBUG, "AppearanceGroup::handleNotifyRequest skipping call with no appearance info");
         delete lContent->removeDialog(pDialog);
         continue;
      }

      if ( dialogId.contains("@@") )
      {
         // this is one of our identifiers already
         uniqueDialogId = dialogId;
      }
      else
      {
         // make a guaranteed unique dialog id, by
         // prepending to each id value the call-id of the subscription
         // from which the dialog event was obtained, with "@@" as a separator
         msg->getCallIdField(&uniqueDialogId);
         uniqueDialogId.append("@@");
         uniqueDialogId.append(dialogId);
         pDialog->setDialogId(uniqueDialogId);
      }
      Os::Logger::instance().log(FAC_SAA, PRI_INFO,
                    "AppearanceGroup::handleNotifyRequest: "
                    "%s update from %s: dialogId %s, x-line-id %s, state %s(%s)",
                    state.data(), contactUri.data(), uniqueDialogId.data(),
                    appearanceId.data(), dialogState.data(), rendering.data());
   }
   delete itor;

   // Check to see if this appearance (of the Appearance) is available
   bool okToProceed = true;
   if ( state == "partial" && dialogState == "trying" )
   {
      UtlHashMapIterator appitor(mAppearances);
      UtlString* handle;
      while ( okToProceed && (handle = dynamic_cast <UtlString*> (appitor())))
      {
         Appearance* inst = dynamic_cast <Appearance*> (appitor.value());
         okToProceed = !inst->appearanceIdIsSeized(appearanceId);
      }
   }

   bool bSendPartialContent = false;
   bool bSendFullContent = false;

   // Send the response.
   if (okToProceed)
   {
      response.setOkResponseData(msg, NULL);
   }
   else
   {
      Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                    "AppearanceGroup::handleNotifyRequest '%s' appearanceId %s is busy",
                    entity.data(), appearanceId.data());
      response.setInterfaceIpPort(msg->getInterfaceIp(), msg->getInterfacePort());
      response.setResponseData(msg, 409, "Conflict");
   }
   getAppearanceAgent()->getServerUserAgent().send(response);

   if (okToProceed)
   {
      if (lContent)
      {
         // Send the content to the proper Appearance instance, so it can
         // save these dialogs, and set flag to indicate whether they should be sent in partial update
         bSendPartialContent = pThisAppearance->updateState(lContent, bSendFullContent);
      }
      // Publish full and partial updates to the SipPublishContentMgr
      publish(bSendFullContent, bSendPartialContent, lContent);
   }

   // Adjust the expiration of the subscription if the set has the line "seized"
   // and return it to the longer default if it does not.
   UtlHashMapIterator appitor(mAppearances);
   UtlString* handle;
   while ( (handle = dynamic_cast <UtlString*> (appitor())))
   {
      Appearance* inst = dynamic_cast <Appearance*> (appitor.value());

      // if set has line seized, use short subscription timer
      if ( inst->appearanceIsBusy() )
      {
         inst->setResubscribeInterval(true);
      }
      else
      {
         inst->setResubscribeInterval(false);
      }
   }

   if (lContent)
   {
      delete lContent;
   }
}