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@@<"); regInfo.append(contactInfo); regInfo.append(">\" 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; } }