// Delete all resource lists. void ResourceListSet::deleteAllResourceLists(bool abortOnShutdown) { Os::Logger::instance().log(FAC_RLS, PRI_DEBUG, "ResourceListSet::deleteAllResourceLists " "this = %p, abortOnShutdown = %d", this, abortOnShutdown); // Gradually remove elements from the ResourceLists and delete them. ResourceList* rl; int changeDelay = getResourceListServer()->getChangeDelay(); do { // Set to true if a ResourceCached was deleted and so we need to delay. bool resource_deleted = false; { // Serialize access to the ResourceListSet. OsLock lock(mSemaphore); // Get pointer to the first ResourceList. rl = dynamic_cast <ResourceList*> (mResourceLists.first()); // If one exists, shrink it. if (rl) { bool list_empty; rl->shrink(list_empty, resource_deleted); if (list_empty) { // The ResourceList is empty, and so can be removed and deleted. mResourceLists.removeReference(rl); delete rl; } } } if (resource_deleted) { // Delay to allow the consequent processing to catch up. OsTask::delay(changeDelay); } } while (rl && !gShutdownFlag); }
//! Start subscriptions for this resource. void ResourceCached::startSubscriptions() { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceCached::startSubscriptions URI = '%s'", data()); // Create the ContactSet. mContactSetP = new ContactSet(this, // The URI, which is the UtlString-nature // of a RecourceCached. *(static_cast <UtlString*> (this)) ); // Start the refresh timer. // Choose a random time between 1/2 and 1 times // ResourceListServer::getRefreshInterval(). int refresh_time = (int) ((1.0 + ((float) sRandom.rand()) / RAND_MAX) / 2.0 * getResourceListServer()->getRefreshInterval()); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceCached::startSubscriptions refresh_time = %d", refresh_time); OsTime rt(refresh_time, 0); mRefreshTimer.oneshotAfter(rt); }
// Insert a subscription into the set. void SubscriptionSet::addInstance(const char* instanceName, const char* subscriptionState) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "SubscriptionSet::addInstance instanceName = '%s', subscriptionState = '%s'", instanceName, subscriptionState); // Check that we don't have too many instances. if (mSubscriptions.entries() < getResourceListServer()->getMaxResInstInCont()) { // Add the instance to the set. ResourceInstance* inst = new ResourceInstance(this, instanceName, subscriptionState); mSubscriptions.append(inst); } else { OsSysLog::add(FAC_RLS, PRI_ERR, "SubscriptionSet::addInstance cannot add ResourceInstance with name '%s', already %zu in SubscriptionSet '%s'", instanceName, mSubscriptions.entries(), mUri.data()); } }
// Generate and publish the full and partial RLMI for the specified URI // (full/consolidated) of a resource list. // Both the Full and the Partial RLMI are sent to the SIP Subscribe Server. // The Partial RLMI will then be sent out right away and the Full RLMI // will be stored in the Subscribe Server to be sent on any initial // SUBSCRIBEs and re-SUBSCRIBEs. void ResourceList::genAndPublish(UtlBoolean consolidated, UtlString resourceListUri) { const UtlBoolean RLMI_FULL = TRUE; const UtlBoolean RLMI_PARTIAL = FALSE; HttpBody* body; UtlSList partialList; // Generate and publish the fullState=TRUE notice body. // Note that the full-state publish() must be done before the partial-state // publish() to avoid race conditions with regard to starting a new // subscription. body = generateRlmiBody(consolidated, RLMI_FULL, mResourcesList); getResourceListServer()->getEventPublisher(). publish(resourceListUri.data(), getResourceListServer()->getEventType(), getResourceListServer()->getEventType(), 1, &body, RLMI_FULL, // Suppress generating notifications for this call of // SipPublishContentMgr::publish, because the call below // will generate notifications for the same subscribed-to // URIs. TRUE); // Generate and publish the fullState=FALSE notice body. // If there are no URIs in mChangesList, then there have been no changes // and publish() does not need to be called to trigger notifications. if (genPartialList(partialList)) { body = generateRlmiBody(consolidated, RLMI_PARTIAL, partialList); getResourceListServer()->getEventPublisher(). publish(resourceListUri.data(), getResourceListServer()->getEventType(), getResourceListServer()->getEventType(), 1, &body, RLMI_PARTIAL, // This call to SipPublishContentMgr::publish triggers // notification. FALSE); } }
// Constructor SubscriptionSet::SubscriptionSet(ResourceCached* resource, UtlString& uri) : ResourceSubscriptionReceiver(), mResource(resource), mUri(uri) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "SubscriptionSet:: this = %p, resource = %p, mUri = '%s'", this, mResource, mUri.data()); // Start the subscription for dialog events. UtlBoolean ret; UtlString mUriNameAddr = "<" + mUri + ">"; ret = getResourceListServer()->getSubscribeClient(). addSubscription(mUri.data(), getResourceListServer()->getEventType(), getResourceListServer()->getContentType(), getResourceListServer()->getClientFromURI(), mUriNameAddr.data(), getResourceListServer()->getClientContactURI(), getResourceListServer()->getResubscribeInterval(), getResourceListSet(), ResourceListSet::subscriptionEventCallbackAsync, ResourceListSet::notifyEventCallbackAsync, mSubscriptionEarlyDialogHandle); if (ret) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "SubscriptionSet:: addSubscription for '%s' succeeded", mUri.data()); // Add this SubscriptionSet to mSubscribeMap. getResourceListSet()->addSubscribeMapping(&mSubscriptionEarlyDialogHandle, this); } else { OsSysLog::add(FAC_RLS, PRI_WARNING, "SubscriptionSet:: addSubscription for '%s' failed", mUri.data()); } }
//! Generate the HttpBody for the current state of the resource list. HttpBody* ResourceList::generateRlmiBody(UtlBoolean consolidated, UtlBoolean fullRlmi, UtlSList& listToSend) { if (Os::Logger::instance().willLog(FAC_RLS, PRI_DEBUG)) { UtlString l; UtlSListIterator resourcesItor(listToSend); ResourceReference* resource; while ((resource = dynamic_cast <ResourceReference*> (resourcesItor()))) { l.append(*resource->getUri()); l.append(","); } if (!l.isNull()) { l.remove(l.length() - 1); } Os::Logger::instance().log(FAC_RLS, PRI_DEBUG, "ResourceList::generateRlmiBody cons=%d URI='%s' full=%d listToSend='%s'", consolidated, (consolidated ? mResourceListNameCons.data() : mResourceListName.data()), fullRlmi, l.data()); } // Construct the multipart body. // We add the <...> here, as they are used in all the contexts where // rlmiBodyPartCid appears. UtlString rlmiBodyPartCid; rlmiBodyPartCid += "<rlmi@"; rlmiBodyPartCid += getResourceListServer()->getDomainName(); rlmiBodyPartCid += ">"; UtlString content_type(CONTENT_TYPE_MULTIPART_RELATED ";type=\"" RLMI_CONTENT_TYPE "\"" ";start=\""); content_type += rlmiBodyPartCid; content_type += "\""; HttpBody* body = new HttpBodyMultipart(content_type); // This is the Resource List Meta-Information, XML describing the resources // and their instances. It is the main part of the NOTIFY body. UtlString rlmi; // Generate the initial part of the RLMI. rlmi += "<?xml version=\"1.0\"?>\r\n"; rlmi += "<list xmlns=\"" RLMI_XMLNS "\" uri=\""; XmlEscape(rlmi, consolidated ? mResourceListNameCons : mResourceListName); // Placeholder for version from SIP stack. rlmi += "\" version=\"" VERSION_PLACEHOLDER "\" "; // Generate either the full or the partial RLMI. if (fullRlmi) { rlmi += "fullState=\"true\">\r\n"; } else { rlmi += "fullState=\"false\">\r\n"; } // If we implemented names for resource lists, <name> elements would be added here. // Iterate through the resources. UtlSListIterator resourcesItor(listToSend); ResourceReference* resource; while ((resource = dynamic_cast <ResourceReference*> (resourcesItor()))) { // Add the content for the resource. resource->generateBody(rlmi, *body, consolidated); } // Generate the postamble for the resource list. rlmi += "</list>\r\n"; // Construct the RLMI body part. HttpBody rlmi_body(rlmi.data(), rlmi.length(), RLMI_CONTENT_TYPE); UtlDList rlmi_body_parameters; rlmi_body_parameters.append(new NameValuePair(HTTP_CONTENT_ID_FIELD, rlmiBodyPartCid)); // Attach the RLMI. body->appendBodyPart(rlmi_body, rlmi_body_parameters); // Clean up the parameter list. rlmi_body_parameters.destroyAll(); return body; }
// Add to the HttpBody the current state of the resource. void ResourceCached::generateBody(UtlString& rlmi, HttpBody& body, UtlBoolean consolidated, const UtlString& nameXml, const UtlString& displayName) const { // Generate the preamble for the resource. rlmi += " <resource uri=\""; XmlEscape(rlmi, *(static_cast <const UtlString*> (this))); rlmi += "\">\r\n"; if (!nameXml.isNull()) { rlmi += " "; rlmi += nameXml; } if (consolidated) { // If consolidating resource instances, generate the XML for the // unified resource instance. rlmi += " <instance id=\"consolidated\" state=\"active\""; UtlString contentBodyPartCid; // Use the count of parts in 'body' to generate a unique identifier for // each part. contentBodyPartCid.appendNumber(body.getMultipartCount()); contentBodyPartCid += "@"; contentBodyPartCid += getResourceListServer()->getDomainName(); rlmi += " cid=\""; rlmi += contentBodyPartCid; rlmi += "\""; // Now add the <...> and use it in the header. contentBodyPartCid.prepend("<"); contentBodyPartCid.append(">"); // Create a single HttpBody to contain the unified dialog event. UtlString dialog_event; // XML declaration is optional, but Broadworks uses it. dialog_event += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; dialog_event += BEGIN_DIALOG_INFO; dialog_event += VERSION_EQUAL; dialog_event += "\""; // The consolidated dialog events need to have persistent // version numbers, as they all have the same instance id. // So we use a global version number in the ResourceListSet. dialog_event.appendNumber(getResourceListSet()->getVersion()); dialog_event += "\""; dialog_event += STATE_EQUAL; dialog_event += "\"full\""; dialog_event += ENTITY_EQUAL; dialog_event += "\""; dialog_event += *(static_cast <const UtlString*> (this)); dialog_event += "\">\r\n"; // Save the length of dialog_event, so we can tell later if // any <dialog>s have been added to it. unsigned int preamble_length = dialog_event.length(); // Call the ContactSet to generate the consolidated dialog event body. if (mContactSetP) { mContactSetP->generateBody(dialog_event, body, consolidated, displayName); } // If no <dialog>s have been added, we have to add a dummy // <dialog> to carry the display name. if (dialog_event.length() == preamble_length) { dialog_event += "<dialog id=\";\"><state>terminated</state><local><identity display=\""; XmlEscape(dialog_event, displayName); dialog_event += "\">"; XmlEscape(dialog_event, *(static_cast <const UtlString*> (this))); dialog_event += "</identity></local></dialog>\r\n"; } dialog_event += END_DIALOG_INFO; // Insert the consolidated dialog event body into the multiplart body. HttpBody content_body(dialog_event.data(), dialog_event.length(), DIALOG_EVENT_CONTENT_TYPE); UtlDList content_body_parameters; content_body_parameters.append( new NameValuePair(HTTP_CONTENT_ID_FIELD, contentBodyPartCid)); body.appendBodyPart(content_body, content_body_parameters); content_body_parameters.destroyAll(); // Finish the <instance> element. rlmi += "/>\r\n"; } else { // Call the ContactSet to do the work. if (mContactSetP) { mContactSetP->generateBody(rlmi, body, consolidated, displayName); } } // Generate the postamble for the resource. rlmi += " </resource>\r\n"; }
// Add to the HttpBody the current state of the resource. void ResourceInstance::generateBody(UtlString& rlmi, HttpBody& body, UtlBoolean consolidated, const UtlString& displayName) const { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::generateBody mInstanceName = '%s', consolidated = %d, displayName = '%s', mContentPresent = %d", mInstanceName.data(), consolidated, displayName.data(), mContentPresent); if (consolidated) { if (mContentPresent) { // If this is a consolidated dialog event list, edit the // stored XML into the right form and append each dialog // to the resource list event notice. TiXmlUtlStringWriter writer(&rlmi); // Iterate through all the <dialog> elements. UtlHashMapIterator itor(mXmlDialogs); UtlContainable* id; while ((id = itor())) { UtlVoidPtr* p = dynamic_cast <UtlVoidPtr*> (itor.value()); TiXmlElement* dialog_element = static_cast <TiXmlElement*> (p->getValue()); // Now that we've got a <dialog> element, edit it to fit // into a consolidated event notice. // Get the display name right. // Find the <local> element, which we know exists due to // earlier processing. TiXmlNode* local = dialog_element->FirstChild("local"); // Find the <local><identity> element, which we know // exists due to earlier processing. TiXmlNode* identity = local->FirstChild("identity"); // Update the display attribute, as that is what will show // on the phone. identity->ToElement()-> SetAttribute("display", displayName); // Un-parse the dialog into the string for storage. writer << *dialog_element; writer << "\r\n"; } } } else { // Generate the XML for the instance. rlmi += " <instance id=\""; XmlEscape(rlmi, mInstanceName); rlmi += "\" state=\""; // Subscription states don't require escaping. rlmi += mSubscriptionState; rlmi += "\""; // Generate the body part for the instance, if necessary. if (mContentPresent) { UtlString contentBodyPartCid; // Use the count of parts in 'body' to generate a unique identifier for // each part. contentBodyPartCid.appendNumber(body.getMultipartCount()); contentBodyPartCid += "@"; contentBodyPartCid += getResourceListServer()->getDomainName(); rlmi += " cid=\""; rlmi += contentBodyPartCid; rlmi += "\""; // Now add the <...> and use it in the header. contentBodyPartCid.prepend("<"); contentBodyPartCid.append(">"); HttpBody content_body(mContent.data(), mContent.length(), getResourceListServer()->getContentType()); UtlDList content_body_parameters; content_body_parameters.append( new NameValuePair(HTTP_CONTENT_ID_FIELD, contentBodyPartCid)); body.appendBodyPart(content_body, content_body_parameters); content_body_parameters.destroyAll(); } rlmi += "/>\r\n"; } }
// Process a notify event callback. void ResourceInstance::notifyEventCallback(const UtlString* dialogHandle, const UtlString* content) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::notifyEventCallback mInstanceName = '%s', content = '%s'", mInstanceName.data(), content->data()); // Set to true if we find publishable data. bool publish = false; // Set the subscription state to "active". mSubscriptionState = "active"; // Save the content as text for the RFC 4662 resource list events. mContent.remove(0); mContent.append(*content); mContentPresent = TRUE; // Dissect the XML for each dialog event and store it in a map // so we can construct BroadWorks-style resource list events // (which have to have full state). // Initialize Tiny XML document object. TiXmlDocument xmlDialogEvent; TiXmlNode* dialog_info_node; if ( // Load the XML into it. xmlDialogEvent.Parse(mContent.data()) && // Find the top element, which should be a <dialog-info>. (dialog_info_node = xmlDialogEvent.FirstChild("dialog-info")) != NULL && dialog_info_node->Type() == TiXmlNode::ELEMENT) { // Check the state attribute. const char* p = dialog_info_node->ToElement()->Attribute("state"); if (p && strcmp(p, "full") == 0) { // If the state is "full", terminate all non-terminated dialogs. (XECS-1668) terminateXmlDialogs(); publish = true; OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::notifyEventCallback all non-terminated dialogs"); } // Find all the <dialog> elements. for (TiXmlNode* dialog_node = 0; (dialog_node = dialog_info_node->IterateChildren("dialog", dialog_node)); ) { if (dialog_node->Type() == TiXmlNode::ELEMENT) { TiXmlElement* dialog_element = dialog_node->ToElement(); // Determine if the <dialog> is a bogus report of a NAT Keepalive // OPTIONS message, as reported by Polycom SPIP firmware 3.1.2. // (XTRN-425) If so, ignore it. #ifdef NAT_KEEPALIVE_DETECT const char* call_id_attr = dialog_element->Attribute("call-id"); // Reject <dialog>s on the narrowest grounds, that is, only if the // call-id attribute is present and contains NAT_KEEPALIVE_SIGNATURE. const bool ok = !(call_id_attr && strstr(call_id_attr, NAT_KEEPALIVE_SIGNATURE) != NULL); #else const bool ok = true; #endif if (ok) { // Now that we've got a <dialog> element, edit it to fit // into a consolidated event notice. publish = true; // Prepend the resource instance name to the 'id' // attribute, so it is unique within the <resource>. UtlString id(mInstanceName); // mInstanceName is guaranteed to not contain ';', because // it is a dialog handle that we generate by concatenating // the Call-Id and tags using ',' as a separator. And ';' // may not appear in Call-Ids or tags. id.append(";"); id.append(dialog_element->Attribute("id")); dialog_element->SetAttribute("id", id.data()); // Prepare the display name, so we can insert it easily // when we generate consolidated events. // Find or add the <local> element. TiXmlNode* local = dialog_element->FirstChild("local"); if (!local) { local = dialog_element->LinkEndChild(new TiXmlElement("local")); } // Find or add the <local><identity> element. TiXmlNode* identity = local->FirstChild("identity"); if (!identity) { identity = local->LinkEndChild(new TiXmlElement("identity")); } // Clear the display attribute. identity->ToElement()->SetAttribute("display", ""); // Put the resource URI as the content of the // <local><identity> element. // First, remove all text children. TiXmlNode* child; for (TiXmlNode* prev_child = 0; (child = identity->IterateChildren(prev_child)); ) { if (child->Type() == TiXmlNode::TEXT) { identity->RemoveChild(child); // Leave prev_child unchanged. } else { prev_child = child; } } // Insert a text child containing the URI. identity->LinkEndChild(new TiXmlText(getResourceCached()-> getUri()->data())); // Now that we have the XML all nice and pretty, store a copy of // it in mXmlDialogs. // Clone the XML and create a UtlVoidPtr to wrap it. TiXmlElement* alloc_xml = dialog_element->Clone()->ToElement(); // Look for an earlier version of this dialog in the hash map. UtlVoidPtr* p = dynamic_cast <UtlVoidPtr*> (mXmlDialogs.findValue(&id)); if (p) { // Replace the old XML with new XML. delete static_cast <TiXmlElement*> (p->getValue()); p->setValue(alloc_xml); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::notifyEventCallback replaced dialog with id '%s'", id.data()); } else { // Check that we don't have too many dialogs. if (mXmlDialogs.entries() < getResourceListServer()->getMaxDialogsInResInst()) { mXmlDialogs.insertKeyAndValue(new UtlString(id), new UtlVoidPtr(alloc_xml)); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::notifyEventCallback added dialog with id '%s'", id.data()); } else { // Free alloc_xml, because we aren't saving a pointer to it. delete alloc_xml; OsSysLog::add(FAC_RLS, PRI_ERR, "ResourceInstance::notifyEventCallback cannot add dialog with id '%s', already %zu in ResourceInstance '%s'", id.data(), mXmlDialogs.entries(), mInstanceName.data()); } } } else { // The <dialog> was rejected because it appears to report // a NAT Maintainer OPTIONS message. // We log this at DEBUG level because if these appear, // there is likely to be one every 20 seconds. OsSysLog::add(FAC_RLS, PRI_DEBUG, "ResourceInstance::notifyEventCallback " "ignored <dialog> reporting a NAT Keepalive message " "in subscription dialog handle '%s' - " "see XTRN-426", mInstanceName.data()); } } } } else { // Report error parsing XML. OsSysLog::add(FAC_RLS, PRI_ERR, "ResourceInstance::notifyEventCallback " "Dialog event from '%s' not parsable.", getResourceCached()->getUri()->data()); OsSysLog::add(FAC_RLS, PRI_INFO, "ResourceInstance::notifyEventCallback " "Dialog event content is '%s'", content->data()); // Throw away the content, since we cannot generate matching // consolidated content. mContentPresent = FALSE; mContent.remove(0); destroyXmlDialogs(); } // Get the change published, if we found <dialog> that was not incorrect. if (publish) { getResourceCached()->setToBePublished(FALSE, getResourceCached()->getUri()); } }
// Update the subscriptions we maintain to agree with the current contact state void ContactSet::updateSubscriptions() { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions mUri = '%s'", mUri.data()); // First, scan mSubscriptions to construct a list of all the // call-id/contact combinations. // (If a phone reboots and starts registering with a different Call-Id, // the call-id/contact combination will be different even if the contact // URI is unchanged. So the new registration will appear to be different // to this machinery, and it will establish a new SubscriptionSet to // the contact URI. This compensates for the fact that the previous // SubscriptionSet to the contact URI appears to the RLS to be // working but the phone no longer knows of the subscription. The // reg events will eventually terminate the old combination and we // will delete its SubscriptionSet.) UtlHashBag callid_contacts; UtlHashMapIterator subs_itor(mSubscriptions); while (subs_itor()) { if (OsSysLog::willLog(FAC_RLS, PRI_DEBUG)) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions subscription '%s'", (dynamic_cast <UtlString*> (subs_itor.key()))->data()); } UtlHashMap* contact_state = dynamic_cast <UtlHashMap*> (subs_itor.value()); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions contact_state = %p", contact_state); UtlHashMapIterator contact_itor(*contact_state); while (contact_itor()) { UtlString* contact = dynamic_cast <UtlString*> (contact_itor.value()); if (OsSysLog::willLog(FAC_RLS, PRI_DEBUG)) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions contact id '%s', Call-Id/URI '%s'", (dynamic_cast <UtlString*> (contact_itor.key()))->data(), contact->data()); } // Check if the contact is already in callid_contacts. if (!callid_contacts.find(contact)) { // If not, add it. UtlString* c = new UtlString(*contact); callid_contacts.insert(c); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions contact added"); } } } // If the list of callid_contacts is empty, add mUri as the default contact // (with an empty registration Call-Id). if (callid_contacts.isEmpty()) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions adding default contact mUri = '%s'", mUri.data()); UtlString* c = new UtlString(";"); c->append(mUri); callid_contacts.insert(c); } // Now that we have a clean list of callid_contacts, update // SubscriptionSets to match it. // If we both terminate subscriptions and create subscriptions, // wait a short while to allow the terminations to complete. This // should not be necessary, but it makes life easier on Polycom // phones which (at this time) cannot support two subscriptions at // a time, and if the termination of the old subscription arrives // after the initiation of the new subscription, the new // subscription will be lost. // This variable tracks whether such a wait is needed before a // subscription is started. bool subscription_ended_but_no_wait_done_yet = false; // Iterate through the list of SubscriptionSets and remove any that aren't // in callid_contacts. { UtlHashMapIterator itor(mSubscriptionSets); UtlString* ss; while ((ss = dynamic_cast <UtlString*> (itor()))) { if (!callid_contacts.find(ss)) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions deleting subscription for '%s' in mUri = '%s'", ss->data(), mUri.data()); mSubscriptionSets.destroy(ss); subscription_ended_but_no_wait_done_yet = true; } } } // Iterate through callid_contacts and add a SubscriptionSet for // any that aren't in mSubscriptionSets. // We don't limit the number of additions here, as the size of // callid_contacts is guarded by the tests in notifyEventCallback. { UtlHashBagIterator itor(callid_contacts); UtlString* callid_contact; while ((callid_contact = dynamic_cast <UtlString*> (itor()))) { if (!mSubscriptionSets.find(callid_contact)) { // If we both terminate subscriptions and create subscriptions, // wait a short while to allow the terminations to complete. // Note that this wait must be no more than the bulk add/delete // change delay, as that is how fast ResourceListFileReader // generates requests to the ResourceListServer task. int wait = getResourceListServer()->getChangeDelay(); if (wait > 0) { if (subscription_ended_but_no_wait_done_yet) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions waiting for %d msec", wait); OsTask::delay(wait); subscription_ended_but_no_wait_done_yet = false; } } OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::updateSubscriptions adding subscription for '%s' in mUri = '%s'", callid_contact->data(), mUri.data()); // Get the contact URI into a UtlString. UtlString uri(callid_contact->data() + callid_contact->index(';') + 1); mSubscriptionSets.insertKeyAndValue(new UtlString(*callid_contact), new SubscriptionSet(mResource, uri)); } } } // Free callid_contacts. callid_contacts.destroyAll(); }
// Process a notify event callback. // This involves parsing the content of the callback and revising our record // of the state for that subscription. Then, we must regenerate the list // of contacts and update the set of subscriptions to match the current // contacts. void ContactSet::notifyEventCallback(const UtlString* dialogHandle, const UtlString* content) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback mUri = '%s', dialogHandle = '%s', content = '%s'", mUri.data(), dialogHandle->data(), content->data()); // Parse the XML and update the contact status. // Find the UtlHashMap for this subscription. UtlHashMap* state_from_this_subscr = dynamic_cast <UtlHashMap*> (mSubscriptions.findValue(dialogHandle)); if (!state_from_this_subscr) { // No state for this dialogHandle, so we need to add one. OsSysLog::add(FAC_RLS, PRI_WARNING, "ContactSet::notifyEventCallback mSubscriptions element does not exist for this dialog handle mUri = '%s', dialogHandle = '%s'", mUri.data(), dialogHandle->data()); // Check that we don't have too many subscriptions. if (mSubscriptions.entries() < getResourceListServer()->getMaxRegSubscInResource()) { state_from_this_subscr = new UtlHashMap; mSubscriptions.insertKeyAndValue(new UtlString(*dialogHandle), state_from_this_subscr); } else { OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::notifyEventCallback cannot add reg subscription with dialog handle '%s', already %zu in ContactSet '%s'", dialogHandle->data(), mSubscriptions.entries(), mUri.data()); } } // Perform the remainder of the processing if we obtained a hash map // from the above processing. if (state_from_this_subscr) { // Initialize Tiny XML document object. TiXmlDocument document; TiXmlNode* reginfo_node; if ( // Load the XML into it. document.Parse(content->data()) && // Find the top element, which should be a <reginfo>. (reginfo_node = document.FirstChild("reginfo")) != NULL && reginfo_node->Type() == TiXmlNode::ELEMENT) { // Check the state attribute. const char* p = reginfo_node->ToElement()->Attribute("state"); if (p && strcmp(p, "full") == 0) { // If the state is "full", delete the current state. state_from_this_subscr->destroyAll(); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback clearing state"); } // Find all the <registration> elements for this URI. for (TiXmlNode* registration_node = 0; (registration_node = reginfo_node->IterateChildren("registration", registration_node)); ) { // Do not test the aor attribute of <registration> elements // because the reg event server may be operating with the real // AOR for this URI, whereas we may have been told of an alias. // Find all the <contact> elements. for (TiXmlNode* contact_node = 0; (contact_node = registration_node->IterateChildren("contact", contact_node)); ) { TiXmlElement* contact_element = contact_node->ToElement(); // Get the state attribute. const char* state = contact_element->Attribute("state"); // Get the id attribute const char* id = contact_element->Attribute("id"); // Get the Contact URI for the phone. If a GRUU address is present we should // use that as the Contact URI. Otherwise, use the contact URI present in the // "uri" element, and append any path headers that are present to the ROUTE // header parameter in the URI. This will ensure proper routing in HA systems. // Please refer to XECS-1694 for more details. UtlString* uri_allocated = new UtlString; UtlBoolean check_uri = TRUE; TiXmlNode* pub_gruu_node = contact_element->FirstChild("gr:pub-gruu"); if (pub_gruu_node) { TiXmlElement* pub_gruu_element = pub_gruu_node->ToElement(); UtlString pub_gruu_uri(pub_gruu_element->Attribute("uri")); if (!pub_gruu_uri.isNull()) { // Check the URI Scheme. Only accept the GRUU address if it is of either // a sip or sips scheme Url tmp(pub_gruu_uri, TRUE); Url::Scheme uriScheme = tmp.getScheme(); if(Url::SipUrlScheme == uriScheme || Url::SipsUrlScheme == uriScheme) { tmp.removeAngleBrackets(); tmp.getUri(*uri_allocated); check_uri = FALSE; } } } // If we did not find a GRUU address, then use the address in the "uri" element as the // contact URI, and check for path headers. if (check_uri) { TiXmlNode* u = contact_element->FirstChild("uri"); if (u) { textContentShallow(*uri_allocated, u); // Iterate through all the path header elements. Path headers are stored in the // "unknown-param" elements that have a "name" attribute value of "path". for (TiXmlNode* unknown_param_node = 0; (unknown_param_node = contact_node->IterateChildren("unknown-param", unknown_param_node)); ) { TiXmlElement* unknown_param_element = unknown_param_node->ToElement(); UtlString path(unknown_param_element->Attribute("name")); if(0 == path.compareTo("path")) { UtlString pathVector; textContentShallow(pathVector, unknown_param_node); if(!pathVector.isNull()) { Url contact_uri(*uri_allocated, TRUE); // there is already a Route header parameter in the contact; append it to the // Route derived from the Path vector. UtlString existingRouteValue; if ( contact_uri.getHeaderParameter(SIP_ROUTE_FIELD, existingRouteValue)) { pathVector.append(SIP_MULTIFIELD_SEPARATOR); pathVector.append(existingRouteValue); } contact_uri.setHeaderParameter(SIP_ROUTE_FIELD, pathVector); contact_uri.removeAngleBrackets(); contact_uri.getUri(*uri_allocated); } } } } } // Only process <contact> elements that have the needed values. if (state && state[0] != '\0' && id && id[0] != '\0' && !uri_allocated->isNull()) { UtlString* id_allocated = new UtlString(id); if (strcmp(state, "active") == 0) { // Add the contact if it is not already present. if (!state_from_this_subscr->find(id_allocated)) { // Prepend the registration Call-Id and ';' to *uri_allocated.. uri_allocated->insert(0, ';'); const char* call_id = contact_element->Attribute("callid"); if (call_id) { uri_allocated->insert(0, call_id); } // Check that we don't have too many contacts. if (state_from_this_subscr->entries() < getResourceListServer()->getMaxContInRegSubsc()) { // Insert the registration record. if (state_from_this_subscr->insertKeyAndValue(id_allocated, uri_allocated)) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback adding id = '%s' Call-Id;URI = '%s'", id, uri_allocated->data()); id_allocated = NULL; uri_allocated = NULL; } else { OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::notifyEventCallback adding id = '%s' Call-Id;URI = '%s' failed", id_allocated->data(), uri_allocated->data()); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback *state_from_this_subscr is:"); UtlHashMapIterator itor(*state_from_this_subscr); UtlContainable* k; while ((k = itor())) { UtlContainable* v = itor.value(); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback (*state_from_this_subscr)['%s'] = '%s'", (dynamic_cast <UtlString*> (k))->data(), (dynamic_cast <UtlString*> (v))->data()); } } } else { OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::notifyEventCallback cannot add Call-Id;RUI '%s', already %zu in ContactSet '%s' subscription '%s'", uri_allocated->data(), state_from_this_subscr->entries(), mUri.data(), dialogHandle->data()); } } } else if (strcmp(state, "terminated") == 0) { // Delete it from the contact state. state_from_this_subscr->destroy(id_allocated); OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::notifyEventCallback deleting id = '%s'", id); } // Free id_allocated, if it is not pointed to by a data // structure, which is indicated by setting it to NULL. if (id_allocated) { delete id_allocated; } } else { OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::notifyEventCallback <contact> element with id = '%s' is missing id, state, and/or URI", id ? id : "(missing)"); } // Free uri_allocated, if it is not pointed to by a data // structure, which is indicated by setting it to NULL. if (uri_allocated) { delete uri_allocated; } } } } else { // Error parsing the contents. OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::notifyEventCallback malformed reg event content for mUri = '%s'", mUri.data()); } // Update the subscriptions we maintain to agree with the new state. updateSubscriptions(); } }
void ContactSet::subscriptionEventCallback( const UtlString* earlyDialogHandle, const UtlString* dialogHandle, SipSubscribeClient::SubscriptionState newState, const UtlString* subscriptionState) { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::subscriptionEventCallback mUri = '%s', newState = %d, earlyDialogHandle = '%s', dialogHandle = '%s', subscriptionState = '%s'", mUri.data(), newState, mSubscriptionEarlyDialogHandle.data(), dialogHandle->data(), subscriptionState->data()); switch (newState) { case SipSubscribeClient::SUBSCRIPTION_INITIATED: break; case SipSubscribeClient::SUBSCRIPTION_SETUP: { // Remember it in mSubscriptions, if there isn't already an entry. if (!mSubscriptions.find(dialogHandle)) { // Check that we don't have too many subscriptions. if (mSubscriptions.entries() < getResourceListServer()->getMaxRegSubscInResource()) { // Add this ContactSet to mNotifyMap for the subscription. // (::addNotifyMapping() copies *dialogHandle.) getResourceListSet()->addNotifyMapping(*dialogHandle, this); // Remember to make a copy of *dialogHandle. mSubscriptions.insertKeyAndValue(new UtlString(*dialogHandle), new UtlHashMap); } else { OsSysLog::add(FAC_RLS, PRI_ERR, "ContactSet::subscriptionEventCallback cannot add reg subscription with dialog handle '%s', already %zu in ContactSet '%s'", dialogHandle->data(), mSubscriptions.entries(), mUri.data()); } } else { OsSysLog::add(FAC_RLS, PRI_DEBUG, "ContactSet::subscriptionEventCallback mSubscriptions element already exists for this dialog handle mUri = '%s', dialogHandle = '%s'", mUri.data(), dialogHandle->data()); } } break; case SipSubscribeClient::SUBSCRIPTION_TERMINATED: { // Remove this ContactSet from mNotifyMap for the subscription. getResourceListSet()->deleteNotifyMapping(dialogHandle); // Delete this subscription from mSubscriptions. mSubscriptions.destroy(dialogHandle); // Update the subscriptions. updateSubscriptions(); } break; } }