/* **************************************************************************** * * tenantCheck - */ static std::string tenantCheck(const std::string& tenant) { char* name = (char*) tenant.c_str(); if (strlen(name) > MAX_TENANT_NAME_LEN) { LM_W(("Bad Input (a tenant name can be max %d characters long. Length: %d)", MAX_TENANT_NAME_LEN, strlen(name))); return "bad length - a tenant name can be max " MAX_TENANT_NAME_LEN_STRING " characters long"; } while (*name != 0) { if ((!isalnum(*name)) && (*name != '_')) { LM_W(("Bad Input (bad character in tenant name - only underscore and alphanumeric characters are allowed. Offending character: %c)", *name)); return "bad character in tenant name - only underscore and alphanumeric characters are allowed"; } ++name; } return "OK"; }
/* **************************************************************************** * * attribute - */ static std::string attribute(std::string path, std::string value, ParseData* reqDataP) { LM_T(LmtParse, ("Got an attribute: '%s'", value.c_str())); if (value == "") { reqDataP->errorString = "Empty attribute name"; LM_W(("Empty attribute name")); } reqDataP->qcr.res.attributeList.push_back(value); return "OK"; }
/* **************************************************************************** * * UpdateContextRequest::fill - */ void UpdateContextRequest::fill ( const UpdateContextAttributeRequest* ucarP, const std::string& entityId, const std::string& entityType, const std::string& attributeName, const std::string& metaID, const std::string& _updateActionType ) { ContextElement* ceP = new ContextElement(); ContextAttribute* caP; if (ucarP->compoundValueP != NULL) { caP = new ContextAttribute(attributeName, ucarP->type, ucarP->compoundValueP); } else { caP = new ContextAttribute(attributeName, ucarP->type, ucarP->contextValue); } caP->metadataVector.fill((MetadataVector*) &ucarP->metadataVector); ceP->contextAttributeVector.push_back(caP); ceP->entityId.fill(entityId, entityType, "false"); contextElementVector.push_back(ceP); // // If there is a metaID, then the metadata named ID must exist. // If it doesn't exist already, it must be created // if (metaID != "") { Metadata* mP = caP->metadataVector.lookupByName("ID"); if (mP == NULL) { mP = new Metadata("ID", "", metaID); caP->metadataVector.push_back(mP); } else if (mP->value != metaID) { LM_W(("Bad Input (metaID differs in URI and payload")); } } updateActionType.set(_updateActionType); }
/* **************************************************************************** * * EntityIdVector::check - */ std::string EntityIdVector::check ( RequestType requestType, Format format, const std::string& indent, const std::string& predetectedError, int counter ) { // Only OK to be empty if part of a ContextRegistration if ((requestType == DiscoverContextAvailability) || (requestType == SubscribeContextAvailability) || (requestType == UpdateContextAvailabilitySubscription) || (requestType == QueryContext) || (requestType == SubscribeContext)) { if (vec.size() == 0) { LM_W(("Bad Input (mandatory entity list missing)")); return "No entities"; } } for (unsigned int ix = 0; ix < vec.size(); ++ix) { std::string res; if ((res = vec[ix]->check(requestType, format, indent, predetectedError, counter)) != "OK") { LM_W(("Bad Input (invalid vector of EntityIds)")); return res; } } return "OK"; }
/* **************************************************************************** * * entityIdList - */ static int entityIdList(xml_node<>* node, ParseData* parseDataP) { LM_T(LmtParse, ("got an entityIdList")); if (parseDataP->rcr.crP->entityIdVectorPresent == true) { parseDataP->errorString = "Got an entityIdList when one was present already"; LM_W(("Bad Input (more than one list of entityId)")); return 1; } parseDataP->rcr.crP->entityIdVectorPresent = true; return 0; }
/* **************************************************************************** * * treat - * * This is the function that actually treats a node, bu calling its treat function * provided by src/lib/xmlRequest - the entry point of XML parsing. * * It simple compares the current path with the paths in the incoming vector 'parseVector' * and if a hit is found calls the 'treat' function of that hit (the instance of the vector). * * If no hit is found it means that the path of the current XML node is unknown. * This will result in either a 'PARSE ERROR' or thatthe node is part of a Compound. */ static bool treat(ConnectionInfo* ciP, xml_node<>* node, const std::string& path, XmlNode* parseVector, ParseData* parseDataP) { for (unsigned int ix = 0; parseVector[ix].path != "LAST"; ++ix) { if (path == parseVector[ix].path) { int r; // // Before treating a node, a check is made that the value of the node has no forbidden // characters. // However, if the the node has attributes, then the values of the attributes are checked instead // if (node->first_attribute() == NULL) { if (forbiddenChars(node->value()) == true) { LM_E(("Found a forbidden value in '%s'", node->value())); ciP->httpStatusCode = SccBadRequest; ciP->answer = std::string("Illegal value for XML attribute"); return true; } } else { for (xml_attribute<> *attr = node->first_attribute(); attr; attr = attr->next_attribute()) { if (forbiddenChars(attr->value()) == true) { LM_E(("Found a forbidden value in attribute: '%s'", node->value())); ciP->httpStatusCode = SccBadRequest; ciP->answer = std::string("Illegal value for XML attribute"); return true; } } } if ((r = parseVector[ix].treat(node, parseDataP)) != 0) { LM_W(("Bad Input (xml parse error %d)", r)); } return true; // Node has been treated } } return false; // Node was not found in the parse vector }
/* **************************************************************************** * * duration - */ static std::string duration(const std::string& path, const std::string& value, ParseData* reqDataP) { std::string s; LM_T(LmtParse, ("Got a duration: '%s'", value.c_str())); reqDataP->scar.res.duration.set(value); if ((s = reqDataP->scar.res.duration.check(SubscribeContextAvailability, JSON, "", "", 0)) != "OK") { LM_W(("Bad Input (error parsing duration '%s': %s)", reqDataP->scar.res.duration.get().c_str(), s.c_str())); return s; } return "OK"; }
/* **************************************************************************** * * badVerbGetPostDeleteOnly - */ std::string badVerbGetPostDeleteOnly ( ConnectionInfo* ciP, int components, std::vector<std::string>& compV, ParseData* parseDataP ) { ciP->httpHeader.push_back("Allow"); ciP->httpHeaderValue.push_back("GET, POST, DELETE"); ciP->httpStatusCode = SccBadVerb; LM_W(("Bad Input (bad verb for url '%s', method '%s')", ciP->url.c_str(), ciP->method.c_str())); return ""; }
/* **************************************************************************** * * mongoUpdateContext - */ HttpStatusCode mongoUpdateContext ( UpdateContextRequest* requestP, UpdateContextResponse* responseP, const std::string& tenant, const std::vector<std::string>& servicePathV, std::map<std::string, std::string>& uriParams, // FIXME P7: we need this to implement "restriction-based" filters const std::string& xauthToken, const std::string& apiVersion, bool checkEntityExistance ) { bool reqSemTaken; reqSemTake(__FUNCTION__, "ngsi10 update request", SemWriteOp, &reqSemTaken); /* Check that the service path vector has only one element, returning error otherwise */ if (servicePathV.size() > 1) { LM_W(("Bad Input (service path length %d is greater than the one in update)", servicePathV.size())); responseP->errorCode.fill(SccBadRequest, "service path length greater than one in update"); } else { /* Process each ContextElement */ for (unsigned int ix = 0; ix < requestP->contextElementVector.size(); ++ix) { processContextElement(requestP->contextElementVector.get(ix), responseP, requestP->updateActionType.get(), tenant, servicePathV, uriParams, xauthToken, apiVersion, checkEntityExistance); } /* Note that although individual processContextElements() invocations return ConnectionError, this error gets "encapsulated" in the StatusCode of the corresponding ContextElementResponse and we consider the overall mongoUpdateContext() as OK. */ responseP->errorCode.fill(SccOk); } reqSemGive(__FUNCTION__, "ngsi10 update request", reqSemTaken); return SccOk; }
/* **************************************************************************** * * Notifier::sendNotifyContextAvailabilityRequest - * * FIXME: this method is very similar to sendNotifyContextRequest and probably * they could be refactored in the future to have a common part using a parent * class for both types of notifications and using it as first argument */ void Notifier::sendNotifyContextAvailabilityRequest(NotifyContextAvailabilityRequest* ncar, const std::string& url, const std::string& tenant, Format format) { /* Render NotifyContextAvailabilityRequest */ std::string payload = ncar->render(NotifyContextAvailability, format, ""); /* Parse URL */ std::string host; int port; std::string uriPath; std::string protocol; if (!parseUrl(url, host, port, uriPath, protocol)) { LM_W(("Bad Input (sending NotifyContextAvailabilityRequest: malformed URL: '%s')", url.c_str())); return; } /* Set Content-Type depending on the format */ std::string content_type = (format == XML ? "application/xml" : "application/json"); /* Send the message (no wait for response, in a separated thread to avoid blocking response)*/ #ifdef SEND_BLOCKING sendHttpSocket(host, port, protocol, "POST", tenant, "", "", uriPath, content_type, payload, true, NOTIFICATION_WAIT_MODE); #endif #ifdef SEND_IN_NEW_THREAD pthread_t tid; SenderThreadParams* params = new SenderThreadParams(); params->ip = host; params->port = port; params->verb = "POST"; params->tenant = tenant; params->resource = uriPath; params->content_type = content_type; params->content = payload; strncpy(params->transactionId, transactionId, sizeof(params->transactionId)); int ret = pthread_create(&tid, NULL, startSenderThread, params); if (ret != 0) { LM_E(("Runtime Error (error creating thread: %d)", ret)); return; } pthread_detach(tid); #endif }
/* **************************************************************************** * * xmlParse - */ void xmlParse(xml_node<>* father, xml_node<>* node, std::string indentation, std::string fatherPath, XmlNode* parseVector, ParseData* reqDataP) { if (node == NULL) return; if (node->name() == NULL) return; if (node->name()[0] == 0) return; std::string path = fatherPath + "/" + node->name(); // // Lookup node in the node vector // bool treated = false; for (unsigned int ix = 0; parseVector[ix].path != "LAST"; ++ix) { if (path == parseVector[ix].path) { int r; if ((r = parseVector[ix].treat(node, reqDataP)) != 0) { fprintf(stderr, "parse vector treat function error: %d\n", r); LM_E(("parse vector treat function error: %d", r)); } treated = true; break; } } if (treated == false) LM_W(("Warning: node '%s' not treated (%s)", path.c_str(), node->name())); xml_node<>* child = node->first_node(); while (child != NULL) { xmlParse(node, child, indentation + " ", path, parseVector, reqDataP); child = child->next_sibling(); } }
/* **************************************************************************** * * entityIdId - */ static int entityIdId(xml_node<>* node, ParseData* parseDataP) { LM_T(LmtParse, ("Got an entityId:id: '%s'", node->value())); if (parseDataP->ucas.entityIdP != NULL) { parseDataP->ucas.entityIdP->id = node->value(); } else { LM_W(("Bad Input (XML parse error)")); parseDataP->errorString = "Bad Input (XML parse error)"; return 1; } return 0; }
/* **************************************************************************** * * QueryContextResponse::check - */ std::string QueryContextResponse::check(ConnectionInfo* ciP, RequestType requestType, const std::string& indent, const std::string& predetectedError, int counter) { std::string res; if (predetectedError != "") { errorCode.fill(SccBadRequest, predetectedError); } else if ((res = contextElementResponseVector.check(QueryContext, ciP->outFormat, indent, predetectedError, 0)) != "OK") { LM_W(("Bad Input (%s)", res.c_str())); errorCode.fill(SccBadRequest, res); } else return "OK"; return render(ciP, QueryContext, indent); }
/* **************************************************************************** * * AlarmManager::badInputReset - * * Returns false if no effective alarm transition occurs, otherwise, true is returned. */ bool AlarmManager::badInputReset(const std::string& ip) { semTake(); if (badInputV.find(ip) == badInputV.end()) // Doesn't exist { semGive(); return false; } badInputV.erase(ip); ++badInputResets; semGive(); LM_W(("Releasing alarm BadInput %s", ip.c_str())); return true; }
/* **************************************************************************** * * AlarmManager::notificationErrorReset - * * Returns false if no effective alarm transition occurs, otherwise, true is returned. */ bool AlarmManager::notificationErrorReset(const std::string& url) { semTake(); if (notificationV.find(url) == notificationV.end()) // Doesn't exist { semGive(); return false; } notificationV.erase(url); ++notificationErrorResets; semGive(); LM_W(("Releasing alarm NotificationError %s", url.c_str())); return true; }
/* **************************************************************************** * * xmlRequestGet - */ static XmlRequest* xmlRequestGet(RequestType request, std::string method) { for (unsigned int ix = 0; ix < sizeof(xmlRequest) / sizeof(xmlRequest[0]); ++ix) { if ((request == xmlRequest[ix].type) && ((xmlRequest[ix].method == method) || (xmlRequest[ix].method == "*"))) { if (xmlRequest[ix].parseVector != NULL) LM_T(LmtHttpRequest, ("Found xmlRequest of type %d, method '%s' - index %d (%s)", request, method.c_str(), ix, xmlRequest[ix].parseVector[0].path.c_str())); return &xmlRequest[ix]; } } LM_W(("Bad Input (no request found for RequestType '%s', method '%s')", requestType(request), method.c_str())); return NULL; }
/* **************************************************************************** * * sourceEntityId - */ static int sourceEntityId(xml_node<>* node, ParseData* parseDataP) { LM_T(LmtParse, ("got a sourceEntityId")); LM_T(LmtParse, ("calling entityIdParse")); std::string es = entityIdParse(RegisterContext, node, &parseDataP->rcr.registrationMetadataP->association.entityAssociation.source); LM_T(LmtParse, ("back from entityIdParse")); if (es != "OK") { parseDataP->errorString = es; LM_W(("Bad Input (error parsing entity: %s)", es.c_str())); } return 0; }
/* **************************************************************************** * * ContextElementResponse::fill - */ void ContextElementResponse::fill(QueryContextResponse* qcrP, const std::string& entityId, const std::string& entityType) { if (qcrP == NULL) { statusCode.fill(SccContextElementNotFound); return; } if (qcrP->contextElementResponseVector.size() == 0) { statusCode.fill(&qcrP->errorCode); contextElement.entityId.fill(entityId, entityType, "false"); if ((statusCode.code != SccOk) && (statusCode.details == "")) { statusCode.details = "Entity id: /" + entityId + "/"; } return; } // // FIXME P7: If more than one context element is found, we simply select the first one. // A better approach would be to change this convop to return a vector of responses. // Adding a warning with 'Bad Input' - with this I mean that the user that sends the // query needs to avoid using this conv op to make any queries that can give more than // one unique context element :-). // This FIXME is related to github issue #588 and (probably) #650. // Also, optimizing this would be part of issue #768 // if (qcrP->contextElementResponseVector.size() > 1) { LM_W(("Bad Input (more than one context element found the this query - selecting the first one")); } contextElement.fill(&qcrP->contextElementResponseVector[0]->contextElement); if (qcrP->errorCode.code != SccNone) { statusCode.fill(&qcrP->errorCode); } }
/* **************************************************************************** * * AlarmManager::dbError - * * Returns false if no effective alarm transition occurs, otherwise, true is returned. */ bool AlarmManager::dbError(const std::string& details) { if (dbOk == false) { if (dbErrorLogAlways) { LM_W(("Repeated Database Error: %s", details.c_str())); } return false; } semTake(); ++dbErrors; dbOk = false; semGive(); LM_E(("Raising alarm DatabaseError: %s", details.c_str())); return true; }
/* **************************************************************************** * * xmlParse - * * This function is called once (actually it is not that simple) for each node in * the tree that the XML parser has built for us. * * * In the node '<contextValue>', isCompoundValuePath returns TRUE. * Under "<contextValue>", we have either a simple string or a Compound Value Tree. * In the case of a "simple string" the value of the current node is NON EMPTY, * and as the node is treated already in the previous call to 'treat()', no further action is needed. * * If a node is NOT TREATED and it is NOT a compound, a parse error is issued */ void xmlParse ( ConnectionInfo* ciP, xml_node<>* father, xml_node<>* node, std::string indentation, std::string fatherPath, XmlNode* parseVector, ParseData* parseDataP ) { std::string value = wsStrip(node->value()); std::string name = wsStrip(node->name()); std::string path = fatherPath + "/" + name; bool treated = treat(node, path, parseVector, parseDataP); if (isCompoundValuePath(path.c_str()) && (value == "") && (node->first_node() != NULL)) { eatCompound(ciP, NULL, node, ""); compoundValueEnd(ciP, parseDataP); return; } else if (treated == false) { ciP->httpStatusCode = SccBadRequest; if (ciP->answer == "") ciP->answer = std::string("Unknown XML field: '") + name.c_str() + "'"; LM_W(("ERROR: '%s', PATH: '%s' ", ciP->answer.c_str(), fatherPath.c_str())); return; } // Recursive calls for all children of this node xml_node<>* child = node->first_node(); while (child != NULL) { if ((child != NULL) && (child->name() != NULL) && (onlyWs(child->name()) == false)) xmlParse(ciP, node, child, indentation + " ", path, parseVector, parseDataP); child = child->next_sibling(); } }
/* **************************************************************************** * * RegisterProviderRequest::check - */ std::string RegisterProviderRequest::check(RequestType requestType, Format format, std::string indent, std::string predetectedError, int counter) { DiscoverContextAvailabilityResponse response; std::string res; if (predetectedError != "") { response.errorCode.fill(SccBadRequest, predetectedError); } else if (((res = metadataVector.check(requestType, format, indent, "", counter)) != "OK") || ((res = duration.check(requestType, format, indent, "", 0)) != "OK") || ((res = providingApplication.check(requestType, format, indent, "", 0)) != "OK") || ((res = registrationId.check(requestType, format, indent, "", 0)) != "OK")) { response.errorCode.fill(SccBadRequest, res); } else return "OK"; LM_W(("RegisterProviderRequest Error")); return response.render(DiscoverContextAvailability, format, indent); }
/* **************************************************************************** * * mapGetIndividualContextEntityAttribute - */ HttpStatusCode mapGetIndividualContextEntityAttribute ( const std::string& entityId, const std::string& entityType, const std::string& attributeName, ContextAttributeResponse* response, ConnectionInfo* ciP ) { HttpStatusCode ms; QueryContextRequest qcRequest; QueryContextResponse qcResponse; EntityId entity(entityId, entityType, "false"); qcRequest.entityIdVector.push_back(&entity); qcRequest.attributeList.push_back(attributeName); ms = mongoQueryContext(&qcRequest, &qcResponse, ciP->tenant, ciP->servicePathV, ciP->uriParam); if ((ms != SccOk) || (qcResponse.contextElementResponseVector.size() == 0)) { // Here I fill in statusCode for the response response->statusCode.fill(SccContextElementNotFound, std::string("Entity id: /") + entityId + "/"); LM_W(("Bad Input (entityId '%s' not found)", entityId.c_str())); return ms; } ContextElementResponse* cerP = qcResponse.contextElementResponseVector[0]; std::vector<ContextAttribute*> attrV = cerP->contextElement.contextAttributeVector.vec; for (unsigned int ix = 0; ix < attrV.size() ; ++ix) { ContextAttribute* ca = new ContextAttribute(attrV[ix]); response->contextAttributeVector.push_back(ca); } response->statusCode.fill(SccOk); return ms; }
/* **************************************************************************** * * StatusCode::fill - */ void StatusCode::fill(const struct UpdateContextResponse& ucrs) { if ((ucrs.errorCode.code != SccOk) && (ucrs.errorCode.code != SccNone)) { fill(ucrs.errorCode); } else if (ucrs.contextElementResponseVector.vec.size() == 1) { fill(ucrs.contextElementResponseVector.vec[0]->statusCode); } else if (ucrs.contextElementResponseVector.vec.size() > 1) { LM_W(("Filling StatusCode from UpdateContextResponse with more than one contextElementResponse, picking one of them ...")); fill(ucrs.contextElementResponseVector.vec[0]->statusCode); } else { // Empty UpdateContextResponse::contextElementResponseVector AND unfilled UpdateContextResponse::errorCode LM_E(("Internal Error (can't fill StatusCode from UpdateContextResponse)")); fill(SccReceiverInternalError, "can't fill StatusCode from UpdateContextResponse"); } }
/* **************************************************************************** * * ScopeVector::check - */ std::string ScopeVector::check ( RequestType requestType, Format format, const std::string& indent, const std::string& predetectedError, int counter ) { for (unsigned int ix = 0; ix < vec.size(); ++ix) { std::string res; if ((res = vec[ix]->check(requestType, format, indent, predetectedError, counter)) != "OK") { LM_W(("Bad Input (error in scope %d: %s)", ix, res.c_str())); return res; } } return "OK"; }
/* **************************************************************************** * * EntityTypeAttributesResponse::check - */ std::string EntityTypeAttributesResponse::check ( ConnectionInfo* ciP, const std::string& indent, const std::string& predetectedError ) { std::string res; if (predetectedError != "") { statusCode.fill(SccBadRequest, predetectedError); } else if ((res = entityType.check(ciP, indent, predetectedError)) != "OK") { LM_W(("Bad Input (%s)", res.c_str())); statusCode.fill(SccBadRequest, res); } else return "OK"; return render(ciP, ""); }
/* **************************************************************************** * * entityIdParse - help routine for the XML node entityId * * Created mainly to not copy the XML attribute lookup stuff */ std::string entityIdParse(RequestType requestType, xml_node<>* node, EntityId* entityIdP) { for (xml_attribute<> *attr = node->first_attribute(); attr; attr = attr->next_attribute()) { if (attr->name() == std::string("type")) { entityIdP->type = attr->value(); LM_T(LmtEntityId, ("Got a type for an entity: '%s'", entityIdP->type.c_str())); } else if (attr->name() == std::string("isPattern")) { entityIdP->isPattern = attr->value(); LM_T(LmtEntityId, ("Got an isPattern for an entity: '%s'", entityIdP->isPattern.c_str())); } else { LM_W(("Bad Input (unsupported attribute '%s' for EntityId)", attr->name())); return "unsupported attribute for EntityId"; } } return "OK"; }
/* **************************************************************************** * * mongoUnsubscribeContext - */ HttpStatusCode mongoUnsubscribeContext(UnsubscribeContextRequest* requestP, UnsubscribeContextResponse* responseP, const std::string& tenant) { bool reqSemTaken; BSONObj sub; DBClientBase* connection = NULL; reqSemTake(__FUNCTION__, "ngsi10 unsubscribe request", SemWriteOp, &reqSemTaken); LM_T(LmtMongo, ("Unsubscribe Context")); /* No matter if success or failure, the subscriptionId in the response is always the one * in the request */ responseP->subscriptionId = requestP->subscriptionId; if (responseP->subscriptionId.get() == "") { responseP->statusCode.fill(SccContextElementNotFound); LM_W(("Bad Input (no subscriptionId)")); return SccOk; } LM_T(LmtMongo, ("findOne() in '%s' collection _id '%s'}", getSubscribeContextCollectionName(tenant).c_str(), requestP->subscriptionId.get().c_str())); /* Look for document */ connection = getMongoConnection(); try { OID id = OID(requestP->subscriptionId.get()); sub = connection->findOne(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << id)); releaseMongoConnection(connection); LM_I(("Database Operation Successful (findOne _id: %s)", id.toString().c_str())); } catch (const AssertionException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo assertion exception)", reqSemTaken); // // This happens when OID format is wrong // FIXME: this checking should be done at parsing stage, without progressing to // mongoBackend. By the moment we can live this here, but we should remove in the future // (old issue #95) // responseP->statusCode.fill(SccContextElementNotFound); LM_W(("Bad Input (invalid OID format)")); return SccOk; } catch (const DBException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } catch (...) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo generic exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } if (sub.isEmpty()) { reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (no subscriptions found)", reqSemTaken); responseP->statusCode.fill(SccContextElementNotFound, std::string("subscriptionId: /") + requestP->subscriptionId.get() + "/"); return SccOk; } /* Remove document in MongoDB */ // FIXME: I will prefer to do the find and remove in a single operation. Is the some similar // to findAndModify for this? LM_T(LmtMongo, ("remove() in '%s' collection _id '%s'}", getSubscribeContextCollectionName(tenant).c_str(), requestP->subscriptionId.get().c_str())); connection = getMongoConnection(); try { connection->remove(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << OID(requestP->subscriptionId.get()))); releaseMongoConnection(connection); LM_I(("Database Operation Successful (remove _id: %s)", requestP->subscriptionId.get().c_str())); } catch (const DBException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - remove() _id: " + requestP->subscriptionId.get().c_str() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } catch (...) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo generic exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - remove() _id: " + requestP->subscriptionId.get().c_str() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } /* Destroy any previous ONTIMEINTERVAL thread */ getNotifier()->destroyOntimeIntervalThreads(requestP->subscriptionId.get()); responseP->statusCode.fill(SccOk); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request", reqSemTaken); // // Removing subscription from cache // subCache->remove(tenant, "", requestP->subscriptionId.get()); return SccOk; }
/* **************************************************************************** * * postUpdateContext - * * POST /v1/updateContext * POST /ngsi10/updateContext * * Payload In: UpdateContextRequest * Payload Out: UpdateContextResponse */ std::string postUpdateContext ( ConnectionInfo* ciP, int components, std::vector<std::string>& compV, ParseData* parseDataP ) { UpdateContextResponse* upcrsP = &parseDataP->upcrs.res; UpdateContextRequest* upcrP = &parseDataP->upcr.res; std::string answer; // // 01. Check service-path consistency // // If more than ONE service-path is input, an error is returned as response. // If NO service-path is issued, then the default service-path "/" is used. // After these checks, the service-path is checked to be 'correct'. // if (ciP->servicePathV.size() > 1) { upcrsP->errorCode.fill(SccBadRequest, "more than one service path in context update request"); LM_W(("Bad Input (more than one service path for an update request)")); answer = upcrsP->render(ciP, UpdateContext, ""); return answer; } else if (ciP->servicePathV.size() == 0) { ciP->servicePathV.push_back(DEFAULT_SERVICE_PATH); } std::string res = servicePathCheck(ciP->servicePathV[0].c_str()); if (res != "OK") { upcrsP->errorCode.fill(SccBadRequest, res); answer = upcrsP->render(ciP, UpdateContext, ""); return answer; } // // 02. Send the request to mongoBackend/mongoUpdateContext // upcrsP->errorCode.fill(SccOk); attributesToNotFound(upcrP); ciP->httpStatusCode = mongoUpdateContext(upcrP, upcrsP, ciP->tenant, ciP->servicePathV, ciP->uriParam, ciP->httpHeaders.xauthToken, "postUpdateContext"); foundAndNotFoundAttributeSeparation(upcrsP, upcrP, ciP); // // 03. Normal case - no forwards // // If there is nothing to forward, just return the result // bool forwarding = forwardsPending(upcrsP); if (forwarding == false) { answer = upcrsP->render(ciP, UpdateContext, ""); upcrP->release(); return answer; } // // 04. mongoBackend doesn't give us the values of the attributes. // So, here we have to do a search inside the initial UpdateContextRequest to fill in all the // attribute-values in the output from mongoUpdateContext // for (unsigned int cerIx = 0; cerIx < upcrsP->contextElementResponseVector.size(); ++cerIx) { ContextElement* ceP = &upcrsP->contextElementResponseVector[cerIx]->contextElement; for (unsigned int aIx = 0; aIx < ceP->contextAttributeVector.size(); ++aIx) { ContextAttribute* aP = upcrP->attributeLookup(&ceP->entityId, ceP->contextAttributeVector[aIx]->name); if (aP == NULL) { LM_E(("Internal Error (attribute '%s' not found)", ceP->contextAttributeVector[aIx]->name.c_str())); } else { ceP->contextAttributeVector[aIx]->value = aP->value; ceP->contextAttributeVector[aIx]->type = aP->type; } } } // // 05. Forwards necessary - sort parts in outgoing requestV // requestV is a vector of UpdateContextRequests and each Context Provider // will have a slot in the vector. // When a ContextElementResponse is found in the output from mongoUpdateContext, a // UpdateContextRequest is to be found/created and inside that UpdateContextRequest // a ContextElement for the Entity of the ContextElementResponse. // // Non-found parts go directly to 'response'. // UpdateContextRequestVector requestV; UpdateContextResponse response; response.errorCode.fill(SccOk); for (unsigned int cerIx = 0; cerIx < upcrsP->contextElementResponseVector.size(); ++cerIx) { ContextElementResponse* cerP = upcrsP->contextElementResponseVector[cerIx]; if (cerP->contextElement.contextAttributeVector.size() == 0) { // // If we find a contextElement without attributes here, then something is wrong // LM_E(("Orion Bug (empty contextAttributeVector for ContextElementResponse %d)", cerIx)); } else { for (unsigned int aIx = 0; aIx < cerP->contextElement.contextAttributeVector.size(); ++aIx) { ContextAttribute* aP = cerP->contextElement.contextAttributeVector[aIx]; // // 0. If the attribute is 'not-found' - just add the attribute to the outgoing response // if (aP->found == false) { response.notFoundPush(&cerP->contextElement.entityId, new ContextAttribute(aP), NULL); continue; } // // 1. If the attribute is found locally - just add the attribute to the outgoing response // if (aP->providingApplication.get() == "") { response.foundPush(&cerP->contextElement.entityId, new ContextAttribute(aP)); continue; } // // 2. Lookup UpdateContextRequest in requestV according to providingApplication. // If not found, add one. UpdateContextRequest* reqP = requestV.lookup(aP->providingApplication.get()); if (reqP == NULL) { reqP = new UpdateContextRequest(aP->providingApplication.get(), &cerP->contextElement.entityId); reqP->updateActionType.set("UPDATE"); requestV.push_back(reqP); } // // 3. Increase the correct format counter // if (aP->providingApplication.getFormat() == XML) { reqP->xmls++; } else { reqP->jsons++; } // // 3. Lookup ContextElement in UpdateContextRequest according to EntityId. // If not found, add one (to the ContextElementVector of the UpdateContextRequest). // ContextElement* ceP = reqP->contextElementVector.lookup(&cerP->contextElement.entityId); if (ceP == NULL) { ceP = new ContextElement(&cerP->contextElement.entityId); reqP->contextElementVector.push_back(ceP); } // // 4. Add ContextAttribute to the correct ContextElement in the correct UpdateContextRequest // ceP->contextAttributeVector.push_back(new ContextAttribute(aP)); } } } // // Now we are ready to forward the Updates // // // Calling each of the Context Providers, merging their results into the // total response 'response' // for (unsigned int ix = 0; ix < requestV.size(); ++ix) { if (requestV[ix]->contextProvider == "") { LM_E(("Internal Error (empty context provider string)")); continue; } UpdateContextResponse upcrs; Format format = requestV[ix]->format(); updateForward(ciP, requestV[ix], &upcrs, format); // // Add the result from the forwarded update to the total response in 'response' // response.merge(&upcrs); } answer = response.render(ciP, UpdateContext, ""); // // Cleanup // upcrP->release(); requestV.release(); upcrsP->release(); upcrsP->fill(&response); response.release(); return answer; }
/* **************************************************************************** * * updateForward - * * An entity/attribute has been found on some context provider. * We need to forward the update request to the context provider, indicated in upcrsP->contextProvider * * 1. Parse the providing application to extract IP, port and URI-path * 2. Render the string of the request we want to forward * 3. Send the request to the providing application (and await the response) * 4. Parse the response and fill in a binary UpdateContextResponse * 5. Fill in the response from the redirection into the response of this function * 6. 'Fix' StatusCode * 7. Freeing memory * * * FIXME P5: The function 'updateForward' is implemented to pick the format (XML or JSON) based on the * count of the Format for all the participating attributes. If we have more attributes 'preferring' * XML than JSON, the forward is done in XML, etc. This is all OK. * What is not OK is that the Accept HTTP header is set to the same format as the Content-Type HTTP Header. * While this is acceptable, it is not great. As the broker understands both XML and JSON, we could send * the forward message with an Acceot header of XML/JSON and then at reading the response, instead of * throwing away the HTTP headers, we could read the "Content-Type" and do the parse according the Content-Type. */ static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, UpdateContextResponse* upcrsP, Format format) { std::string ip; std::string protocol; int port; std::string prefix; std::string answer; // // 1. Parse the providing application to extract IP, port and URI-path // if (parseUrl(upcrP->contextProvider, ip, port, prefix, protocol) == false) { LM_W(("Bad Input (invalid providing application '%s')", upcrP->contextProvider.c_str())); // Somehow, if we accepted this providing application, it is the brokers fault ... // SccBadRequest should have been returned before, when it was registered! upcrsP->errorCode.fill(SccContextElementNotFound, ""); return; } // // 2. Render the string of the request we want to forward // Format outFormat = ciP->outFormat; std::string payload; char* cleanPayload; ciP->outFormat = format; payload = upcrP->render(ciP, UpdateContext, ""); ciP->outFormat = outFormat; cleanPayload = (char*) payload.c_str(); if (format == XML) { if ((cleanPayload = xmlPayloadClean(payload.c_str(), "<updateContextRequest>")) == NULL) { LM_E(("Runtime Error (error rendering forward-request)")); upcrsP->errorCode.fill(SccContextElementNotFound, ""); return; } } // // 3. Send the request to the Context Provider (and await the reply) // FIXME P7: Should Rush be used? // std::string out; std::string verb = "POST"; std::string resource = prefix + "/updateContext"; std::string tenant = ciP->tenant; std::string servicePath = (ciP->httpHeaders.servicePathReceived == true)? ciP->httpHeaders.servicePath : ""; std::string mimeType = (format == XML)? "application/xml" : "application/json"; out = httpRequestSend(ip, port, protocol, verb, tenant, servicePath, ciP->httpHeaders.xauthToken, resource, mimeType, cleanPayload, false, true, mimeType); if ((out == "error") || (out == "")) { upcrsP->errorCode.fill(SccContextElementNotFound, ""); LM_E(("Runtime Error (error forwarding 'Update' to providing application)")); return; } // // 4. Parse the response and fill in a binary UpdateContextResponse // std::string s; std::string errorMsg; if (format == XML) { cleanPayload = xmlPayloadClean(out.c_str(), "<updateContextResponse>"); } else { cleanPayload = jsonPayloadClean(out.c_str()); } if ((cleanPayload == NULL) || (cleanPayload[0] == 0)) { // // This is really an internal error in the Context Provider // It is not in the orion broker though, so 404 is returned // LM_W(("Other Error (context provider response to UpdateContext is empty)")); upcrsP->errorCode.fill(SccContextElementNotFound, "invalid context provider response"); return; } // // NOTE // When coming from a convenience operation, such as GET /v1/contextEntities/EID/attributes/attrName, // the verb/method in ciP is GET. However, the parsing function expects a POST, as if it came from a // POST /v1/updateContext. // So, here we change the verb/method for POST. // ParseData parseData; ciP->verb = POST; ciP->method = "POST"; parseData.upcrs.res.errorCode.fill(SccOk); if (format == XML) { s = xmlTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, "updateContextResponse", NULL, &errorMsg); } else { s = jsonTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, "updateContextResponse", NULL); } if (s != "OK") { LM_W(("Internal Error (error parsing reply from prov app: %s)", errorMsg.c_str())); upcrsP->errorCode.fill(SccContextElementNotFound, ""); parseData.upcr.res.release(); parseData.upcrs.res.release(); return; } // // 5. Fill in the response from the redirection into the response of this function // upcrsP->fill(&parseData.upcrs.res); // // 6. 'Fix' StatusCode // if (upcrsP->errorCode.code == SccNone) { upcrsP->errorCode.fill(SccOk); } if ((upcrsP->contextElementResponseVector.size() == 1) && (upcrsP->contextElementResponseVector[0]->statusCode.code == SccContextElementNotFound)) { upcrsP->errorCode.fill(SccContextElementNotFound); } // // 7. Freeing memory // parseData.upcr.res.release(); parseData.upcrs.res.release(); }
/* **************************************************************************** * * connectionTreat - * * This is the MHD_AccessHandlerCallback function for MHD_start_daemon * This function returns: * o MHD_YES if the connection was handled successfully * o MHD_NO if the socket must be closed due to a serious error * * - This function is called once when the headers are read and the ciP is created. * - Then it is called for data payload and once all the payload an acknowledgement * must be done, setting *upload_data_size to ZERO. * - The last call is made with *upload_data_size == 0 and now is when the connection * is open to send responses. * * Call 1: *con_cls == NULL * Call 2: *con_cls != NULL AND *upload_data_size != 0 * Call 3: *con_cls != NULL AND *upload_data_size == 0 */ static int connectionTreat ( void* cls, MHD_Connection* connection, const char* url, const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls ) { ConnectionInfo* ciP = (ConnectionInfo*) *con_cls; size_t dataLen = *upload_data_size; // 1. First call - setup ConnectionInfo and get/check HTTP headers if (ciP == NULL) { // // IP Address and port of caller // char ip[32]; unsigned short port = 0; const union MHD_ConnectionInfo* mciP = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); if (mciP != NULL) { port = (mciP->client_addr->sa_data[0] << 8) + mciP->client_addr->sa_data[1]; snprintf(ip, sizeof(ip), "%d.%d.%d.%d", mciP->client_addr->sa_data[2] & 0xFF, mciP->client_addr->sa_data[3] & 0xFF, mciP->client_addr->sa_data[4] & 0xFF, mciP->client_addr->sa_data[5] & 0xFF); } else { port = 0; snprintf(ip, sizeof(ip), "IP unknown"); } // // ConnectionInfo // if ((ciP = new ConnectionInfo(url, method, version, connection)) == NULL) { LM_E(("Runtime Error (error allocating ConnectionInfo)")); return MHD_NO; } *con_cls = (void*) ciP; // Pointer to ConnectionInfo for subsequent calls ciP->port = port; ciP->ip = ip; // // Transaction starts here // LM_TRANSACTION_START("from", ip, port, url); // Incoming REST request starts // // URI parameters // ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = DEFAULT_PARAM_NOTIFY_FORMAT; ciP->uriParam[URI_PARAM_PAGINATION_OFFSET] = DEFAULT_PAGINATION_OFFSET; ciP->uriParam[URI_PARAM_PAGINATION_LIMIT] = DEFAULT_PAGINATION_LIMIT; ciP->uriParam[URI_PARAM_PAGINATION_DETAILS] = DEFAULT_PAGINATION_DETAILS; MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, uriArgumentGet, ciP); if (ciP->httpStatusCode != SccOk) { LM_W(("Bad Input (error in URI parameters)")); restReply(ciP, ciP->answer); return MHD_YES; } LM_T(LmtUriParams, ("notifyFormat: '%s'", ciP->uriParam[URI_PARAM_NOTIFY_FORMAT].c_str())); MHD_get_connection_values(connection, MHD_HEADER_KIND, httpHeaderGet, &ciP->httpHeaders); char tenant[128]; ciP->tenantFromHttpHeader = strToLower(tenant, ciP->httpHeaders.tenant.c_str(), sizeof(tenant)); LM_T(LmtTenant, ("HTTP tenant: '%s'", ciP->httpHeaders.tenant.c_str())); ciP->outFormat = wantedOutputSupported(ciP->httpHeaders.accept, &ciP->charset); if (ciP->outFormat == NOFORMAT) ciP->outFormat = XML; // XML is default output format ciP->servicePath = ciP->httpHeaders.servicePath; if (servicePathSplit(ciP) != 0) { LM_W(("Bad Input (error in ServicePath http-header)")); restReply(ciP, ciP->answer); } if (contentTypeCheck(ciP) != 0) { LM_W(("Bad Input (invalid mime-type in Content-Type http-header)")); restReply(ciP, ciP->answer); } else if (outFormatCheck(ciP) != 0) { LM_W(("Bad Input (invalid mime-type in Accept http-header)")); restReply(ciP, ciP->answer); } else ciP->inFormat = formatParse(ciP->httpHeaders.contentType, NULL); // Set default mime-type for notifications if (ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] == "") { if (ciP->outFormat == XML) ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "XML"; else if (ciP->outFormat == JSON) ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "JSON"; else ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "XML"; LM_T(LmtUriParams, ("'default' value for notifyFormat (ciP->outFormat == %d)): '%s'", ciP->outFormat, ciP->uriParam[URI_PARAM_NOTIFY_FORMAT].c_str())); } return MHD_YES; } // // 2. Data gathering calls // // 2-1. Data gathering calls, just wait // 2-2. Last data gathering call, acknowledge the receipt of data // if (dataLen != 0) { if (dataLen == ciP->httpHeaders.contentLength) { if (ciP->httpHeaders.contentLength <= PAYLOAD_MAX_SIZE) { if (ciP->httpHeaders.contentLength > STATIC_BUFFER_SIZE) ciP->payload = (char*) malloc(ciP->httpHeaders.contentLength + 1); else ciP->payload = static_buffer; ciP->payloadSize = dataLen; memcpy(ciP->payload, upload_data, dataLen); ciP->payload[dataLen] = 0; } else { char details[256]; snprintf(details, sizeof(details), "payload size: %d, max size supported: %d", ciP->httpHeaders.contentLength, PAYLOAD_MAX_SIZE); ciP->answer = restErrorReplyGet(ciP, ciP->outFormat, "", ciP->url, SccRequestEntityTooLarge, details); ciP->httpStatusCode = SccRequestEntityTooLarge; } // All payload received, acknowledge! *upload_data_size = 0; } else LM_T(LmtPartialPayload, ("Got %d of payload of %d bytes", dataLen, ciP->httpHeaders.contentLength)); return MHD_YES; } // 3. Finally, serve the request (unless an error has occurred) if (((ciP->verb == POST) || (ciP->verb == PUT)) && (ciP->httpHeaders.contentLength == 0) && (strncasecmp(ciP->url.c_str(), "/log/", 5) != 0)) { std::string errorMsg = restErrorReplyGet(ciP, ciP->outFormat, "", url, SccLengthRequired, "Zero/No Content-Length in PUT/POST request"); ciP->httpStatusCode = SccLengthRequired; restReply(ciP, errorMsg); } else if (ciP->answer != "") restReply(ciP, ciP->answer); else serveFunction(ciP); return MHD_YES; }