2015-09-25 03:16:02 +02:00
|
|
|
|
2015-09-22 15:58:19 +02:00
|
|
|
/** Node type: OMA Next Generation Services Interface 10 (NGSI) (FIWARE context broker)
|
2015-09-19 18:54:27 +02:00
|
|
|
*
|
2015-09-22 15:58:19 +02:00
|
|
|
* This file implements the NGSI context interface. NGSI is RESTful HTTP is specified by
|
|
|
|
* the Open Mobile Alliance (OMA).
|
|
|
|
* It uses the standard operations of the NGSI 10 context information standard.
|
2015-09-19 18:54:27 +02:00
|
|
|
*
|
|
|
|
* @see https://forge.fiware.org/plugins/mediawiki/wiki/fiware/index.php/FI-WARE_NGSI-10_Open_RESTful_API_Specification
|
2015-09-22 15:58:19 +02:00
|
|
|
* @see http://technical.openmobilealliance.org/Technical/Release_Program/docs/NGSI/V1_0-20120529-A/OMA-TS-NGSI_Context_Management-V1_0-20120529-A.pdf
|
2015-09-19 18:54:27 +02:00
|
|
|
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
|
|
|
* @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC
|
|
|
|
* This file is part of S2SS. All Rights Reserved. Proprietary and confidential.
|
|
|
|
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
|
|
|
**********************************************************************************/
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2015-09-19 18:54:27 +02:00
|
|
|
#include <curl/curl.h>
|
2015-09-21 17:13:50 +02:00
|
|
|
#include <uuid/uuid.h>
|
2015-09-19 18:54:27 +02:00
|
|
|
#include <jansson.h>
|
|
|
|
|
|
|
|
#include "ngsi.h"
|
|
|
|
#include "utils.h"
|
|
|
|
|
2015-10-11 14:45:54 +02:00
|
|
|
extern struct settings settings;
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
static json_t * json_uuid()
|
2015-09-19 18:54:27 +02:00
|
|
|
{
|
2015-09-21 17:13:50 +02:00
|
|
|
char eid[37];
|
|
|
|
uuid_t uuid;
|
|
|
|
|
|
|
|
uuid_generate_time(uuid);
|
2015-09-22 13:19:44 +02:00
|
|
|
uuid_unparse_lower(uuid, eid);
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
return json_string(eid);
|
|
|
|
}
|
|
|
|
|
2015-09-22 13:19:44 +02:00
|
|
|
static json_t * json_date(struct timespec *ts)
|
|
|
|
{
|
|
|
|
// Example: 2015-09-21T11:42:25+02:00
|
|
|
|
char date[64];
|
2015-10-12 13:45:05 +02:00
|
|
|
strftimespec(date, sizeof(date), "%FT%T.%u%z", ts);
|
2015-09-22 13:19:44 +02:00
|
|
|
|
|
|
|
return json_string(date);
|
|
|
|
}
|
|
|
|
|
|
|
|
static json_t * json_lookup(json_t *array, char *key, char *needle)
|
2015-09-21 17:13:50 +02:00
|
|
|
{
|
|
|
|
size_t ind;
|
|
|
|
json_t *obj;
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
json_array_foreach(array, ind, obj) {
|
|
|
|
json_t *value = json_object_get(obj, key);
|
|
|
|
if (value && json_is_string(value)) {
|
|
|
|
if (!strcmp(json_string_value(value), needle))
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
struct ngsi_response {
|
|
|
|
char *data;
|
|
|
|
size_t len;
|
|
|
|
};
|
|
|
|
|
|
|
|
static size_t ngsi_request_writer(void *contents, size_t size, size_t nmemb, void *userp)
|
2015-09-19 18:54:27 +02:00
|
|
|
{
|
2015-09-21 17:13:50 +02:00
|
|
|
size_t realsize = size * nmemb;
|
|
|
|
struct ngsi_response *mem = (struct ngsi_response *) userp;
|
|
|
|
|
|
|
|
mem->data = realloc(mem->data, mem->len + realsize + 1);
|
|
|
|
if(mem->data == NULL) /* out of memory! */
|
|
|
|
error("Not enough memory (realloc returned NULL)");
|
|
|
|
|
|
|
|
memcpy(&(mem->data[mem->len]), contents, realsize);
|
|
|
|
mem->len += realsize;
|
|
|
|
mem->data[mem->len] = 0;
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
return realsize;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
2015-10-09 17:20:58 +02:00
|
|
|
static int ngsi_request(CURL *handle, const char *endpoint, const char *operation, json_t *content, json_t **response)
|
2015-09-19 18:54:27 +02:00
|
|
|
{
|
2015-09-21 17:13:50 +02:00
|
|
|
struct ngsi_response chunk = { 0 };
|
|
|
|
char *post = json_dumps(content, JSON_INDENT(4));
|
|
|
|
|
2015-10-09 17:20:58 +02:00
|
|
|
char url[128];
|
|
|
|
snprintf(url, sizeof(url), "%s/v1/%s", endpoint, operation);
|
|
|
|
|
|
|
|
curl_easy_setopt(handle, CURLOPT_URL, url);
|
2015-09-21 17:13:50 +02:00
|
|
|
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ngsi_request_writer);
|
|
|
|
curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *) &chunk);
|
|
|
|
curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, strlen(post));
|
|
|
|
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post);
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-09-22 13:19:44 +02:00
|
|
|
debug(20, "Request to context broker:\n%s", post);
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
CURLcode ret = curl_easy_perform(handle);
|
|
|
|
if (ret)
|
|
|
|
error("HTTP request failed: %s", curl_easy_strerror(ret));
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-09-28 21:32:04 +02:00
|
|
|
long code;
|
|
|
|
double time;
|
|
|
|
curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time);
|
2015-09-21 17:13:50 +02:00
|
|
|
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code);
|
|
|
|
|
2015-10-12 15:29:26 +02:00
|
|
|
debug(10, "Request to context broker completed in %.4f seconds", time);
|
2015-09-22 13:19:44 +02:00
|
|
|
debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data);
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
json_error_t err;
|
|
|
|
json_t *resp = json_loads(chunk.data, 0, &err);
|
|
|
|
if (!resp)
|
|
|
|
error("Received invalid JSON: %s in %s:%u:%u\n%s", err.text, err.source, err.line, err.column, chunk.data);
|
|
|
|
|
|
|
|
if (response)
|
|
|
|
*response = resp;
|
2015-09-27 20:27:47 +02:00
|
|
|
else
|
|
|
|
json_decref(resp);
|
2015-09-21 17:13:50 +02:00
|
|
|
|
|
|
|
free(post);
|
|
|
|
free(chunk.data);
|
|
|
|
|
|
|
|
return code;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
2015-09-22 15:58:19 +02:00
|
|
|
void ngsi_prepare_context(struct node *n, config_setting_t *mapping)
|
2015-09-21 17:13:50 +02:00
|
|
|
{
|
|
|
|
struct ngsi *i = n->ngsi;
|
2015-10-12 15:29:26 +02:00
|
|
|
|
2015-10-12 13:49:31 +02:00
|
|
|
list_init(&i->mapping, NULL);
|
2015-09-27 20:27:47 +02:00
|
|
|
|
2015-10-12 15:29:26 +02:00
|
|
|
i->context = json_object();
|
|
|
|
|
2015-09-27 20:27:47 +02:00
|
|
|
json_t *elements = json_array();
|
2015-10-12 15:29:26 +02:00
|
|
|
json_object_set_new(i->context, "contextElements", elements);
|
|
|
|
|
|
|
|
json_t *entity = json_pack("{ s: s, s: s, s: b }",
|
|
|
|
"id", i->entity_id,
|
|
|
|
"type", i->entity_type,
|
|
|
|
"isPattern", 0
|
|
|
|
);
|
|
|
|
json_array_append_new(elements, entity);
|
|
|
|
|
|
|
|
json_t *attributes = json_array();
|
|
|
|
json_object_set_new(entity, "attributes", attributes);
|
2015-09-21 17:13:50 +02:00
|
|
|
|
2015-10-12 13:49:31 +02:00
|
|
|
for (int j = 0; j < config_setting_length(mapping); j++) {
|
2015-10-12 15:29:26 +02:00
|
|
|
const char *token = config_setting_get_string_elem(mapping, j);
|
|
|
|
if (!token)
|
|
|
|
cerror(mapping, "Invalid mapping token");
|
2015-09-21 17:13:50 +02:00
|
|
|
|
2015-09-27 20:27:47 +02:00
|
|
|
/* Parse token */
|
2015-10-12 15:29:26 +02:00
|
|
|
char aname[64], atype[64];
|
|
|
|
if (sscanf(token, "%[^(](%[^)])", aname, atype) != 2)
|
|
|
|
cerror(mapping, "Invalid mapping token: '%s'", token);
|
2015-09-21 17:13:50 +02:00
|
|
|
|
2015-10-12 15:29:26 +02:00
|
|
|
json_t *attribute = json_pack("{ s: s, s: s, s: [ ] }",
|
|
|
|
"name", aname,
|
|
|
|
"type", atype,
|
|
|
|
"value"
|
|
|
|
);
|
|
|
|
json_array_append_new(attributes, attribute);
|
|
|
|
|
2015-09-27 20:27:47 +02:00
|
|
|
/* Create Metadata for attribute */
|
|
|
|
json_t *metadatas = json_array();
|
2015-10-12 15:29:26 +02:00
|
|
|
json_object_set_new(attribute, "metadatas", metadatas);
|
2015-09-21 17:13:50 +02:00
|
|
|
|
2015-10-11 14:45:54 +02:00
|
|
|
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }",
|
|
|
|
"name", "source",
|
|
|
|
"type", "string",
|
|
|
|
"value", "s2ss:", settings.name
|
2015-09-21 17:13:50 +02:00
|
|
|
));
|
2015-09-27 20:27:47 +02:00
|
|
|
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }",
|
2015-09-21 17:13:50 +02:00
|
|
|
"name", "index",
|
|
|
|
"type", "integer",
|
|
|
|
"value", j
|
|
|
|
));
|
2015-10-12 13:45:05 +02:00
|
|
|
json_array_append(metadatas, json_pack("{ s: s, s: s, s: s }",
|
2015-10-07 16:52:23 +02:00
|
|
|
"name", "timestamp",
|
|
|
|
"type", "date",
|
2015-10-12 13:45:05 +02:00
|
|
|
"value", ""
|
2015-10-12 15:29:26 +02:00
|
|
|
));
|
2015-10-12 13:49:31 +02:00
|
|
|
|
|
|
|
list_push(&i->mapping, attribute);
|
2015-09-21 17:13:50 +02:00
|
|
|
}
|
|
|
|
}
|
2015-09-19 18:54:27 +02:00
|
|
|
|
|
|
|
int ngsi_init(int argc, char *argv[], struct settings *set)
|
|
|
|
{
|
2015-09-21 17:13:50 +02:00
|
|
|
return curl_global_init(CURL_GLOBAL_ALL);
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_deinit()
|
|
|
|
{
|
|
|
|
curl_global_cleanup();
|
2015-09-21 17:13:50 +02:00
|
|
|
|
|
|
|
return 0;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_parse(config_setting_t *cfg, struct node *n)
|
|
|
|
{
|
2015-09-21 17:13:50 +02:00
|
|
|
struct ngsi *i = alloc(sizeof(struct ngsi));
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-10-12 15:29:26 +02:00
|
|
|
if (!config_setting_lookup_string(cfg, "access_token", &i->access_token))
|
|
|
|
i->access_token = NULL; /* disabled by default */
|
2015-09-22 15:58:19 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
if (!config_setting_lookup_string(cfg, "endpoint", &i->endpoint))
|
|
|
|
cerror(cfg, "Missing NGSI endpoint for node '%s'", n->name);
|
2015-10-12 15:29:26 +02:00
|
|
|
|
|
|
|
if (!config_setting_lookup_string(cfg, "entity_id", &i->entity_id))
|
|
|
|
cerror(cfg, "Missing NGSI entity ID for node '%s'", n->name);
|
|
|
|
|
|
|
|
if (!config_setting_lookup_string(cfg, "entity_type", &i->entity_type))
|
|
|
|
cerror(cfg, "Missing NGSI entity type for node '%s'", n->name);
|
2015-09-22 15:58:19 +02:00
|
|
|
|
2015-09-22 14:49:42 +02:00
|
|
|
if (!config_setting_lookup_bool(cfg, "ssl_verify", &i->ssl_verify))
|
|
|
|
i->ssl_verify = 1; /* verify by default */
|
2015-09-22 15:58:19 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
if (!config_setting_lookup_float(cfg, "timeout", &i->timeout))
|
|
|
|
i->timeout = 1; /* default value */
|
2015-09-22 15:58:19 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
n->ngsi = i;
|
|
|
|
|
|
|
|
config_setting_t *mapping = config_setting_get_member(cfg, "mapping");
|
|
|
|
if (!mapping || !config_setting_is_array(mapping))
|
|
|
|
cerror(cfg, "Missing mapping for node '%s", n->name);
|
2015-09-22 15:58:19 +02:00
|
|
|
|
|
|
|
ngsi_prepare_context(n, mapping);
|
2015-09-21 17:13:50 +02:00
|
|
|
|
|
|
|
return 0;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
2015-09-22 12:58:37 +02:00
|
|
|
char * ngsi_print(struct node *n)
|
2015-09-19 18:54:27 +02:00
|
|
|
{
|
|
|
|
struct ngsi *i = n->ngsi;
|
2015-09-22 12:58:37 +02:00
|
|
|
char *buf = NULL;
|
|
|
|
|
|
|
|
return strcatf(&buf, "endpoint=%s, timeout=%.3f secs",
|
2015-09-21 17:13:50 +02:00
|
|
|
i->endpoint, i->timeout);
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_open(struct node *n)
|
|
|
|
{
|
|
|
|
char buf[128];
|
|
|
|
struct ngsi *i = n->ngsi;
|
|
|
|
|
|
|
|
i->curl = curl_easy_init();
|
2015-09-21 17:13:50 +02:00
|
|
|
i->headers = NULL;
|
2015-09-19 18:54:27 +02:00
|
|
|
|
2015-10-12 15:29:26 +02:00
|
|
|
if (i->access_token) {
|
|
|
|
snprintf(buf, sizeof(buf), "Auth-Token: %s", i->access_token);
|
2015-09-19 18:54:27 +02:00
|
|
|
i->headers = curl_slist_append(i->headers, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
i->headers = curl_slist_append(i->headers, "User-Agent: S2SS " VERSION);
|
2015-09-22 15:58:19 +02:00
|
|
|
i->headers = curl_slist_append(i->headers, "Accept: application/json");
|
2015-09-19 18:54:27 +02:00
|
|
|
i->headers = curl_slist_append(i->headers, "Content-Type: application/json");
|
|
|
|
|
2015-09-22 14:49:42 +02:00
|
|
|
curl_easy_setopt(i->curl, CURLOPT_SSL_VERIFYPEER, i->ssl_verify);
|
2015-09-21 17:13:50 +02:00
|
|
|
curl_easy_setopt(i->curl, CURLOPT_TIMEOUT_MS, i->timeout * 1e3);
|
2015-09-19 18:54:27 +02:00
|
|
|
curl_easy_setopt(i->curl, CURLOPT_HTTPHEADER, i->headers);
|
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
/* Create entity and atributes */
|
2015-09-27 20:27:47 +02:00
|
|
|
json_object_set_new(i->context, "updateAction", json_string("APPEND"));
|
2015-10-09 17:20:58 +02:00
|
|
|
|
|
|
|
return ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_close(struct node *n)
|
|
|
|
{
|
|
|
|
struct ngsi *i = n->ngsi;
|
|
|
|
|
2015-09-22 15:58:19 +02:00
|
|
|
/* Delete attributes */
|
2015-09-27 20:27:47 +02:00
|
|
|
json_object_set_new(i->context, "updateAction", json_string("DELETE"));
|
2015-10-09 17:20:58 +02:00
|
|
|
int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1;
|
2015-09-22 15:58:19 +02:00
|
|
|
|
2015-09-19 18:54:27 +02:00
|
|
|
curl_easy_cleanup(i->curl);
|
|
|
|
curl_slist_free_all(i->headers);
|
|
|
|
|
2015-09-22 15:58:19 +02:00
|
|
|
return code == 200 ? 0 : -1;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
|
2015-10-12 15:29:26 +02:00
|
|
|
{
|
|
|
|
/* struct ngsi *i = n->ngsi;
|
|
|
|
struct msg *m = &pool[first % poolsize];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
json_t *response;
|
|
|
|
json_t *entities = json_array();
|
|
|
|
json_t *query = json_pack("{ s: o }", "entities", entities);
|
|
|
|
|
|
|
|
ret = ngsi_request(i->curl, i->endpoint, "queryContext", NULL, NULL);
|
|
|
|
if (ret < 0) {
|
|
|
|
warn("Failed to query data from context broker");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
*/
|
2015-09-19 18:54:27 +02:00
|
|
|
return -1; /** @todo not yet implemented */
|
|
|
|
}
|
|
|
|
|
|
|
|
int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
|
|
|
|
{
|
|
|
|
struct ngsi *i = n->ngsi;
|
2015-10-12 15:29:26 +02:00
|
|
|
|
|
|
|
/* First message */
|
|
|
|
struct msg *fm = &pool[first % poolsize];
|
2015-09-25 03:16:02 +02:00
|
|
|
|
2015-09-21 17:13:50 +02:00
|
|
|
/* Update context */
|
2015-10-12 15:29:26 +02:00
|
|
|
for (int j = 0; j < MIN(i->mapping.length, fm->length); j++) {
|
2015-10-12 13:49:31 +02:00
|
|
|
json_t *attribute = list_at(&i->mapping, j);
|
|
|
|
json_t *values = json_object_get(attribute, "value");
|
2015-09-22 13:19:44 +02:00
|
|
|
json_t *metadatas = json_object_get(attribute, "metadatas");
|
2015-10-07 16:52:23 +02:00
|
|
|
|
|
|
|
/* Update timestamp */
|
|
|
|
json_t *metadata_ts = json_lookup(metadatas, "name", "timestamp");
|
2015-10-12 15:29:26 +02:00
|
|
|
json_object_set(metadata_ts, "value", json_date(&MSG_TS(fm)));
|
2015-09-22 13:19:44 +02:00
|
|
|
|
2015-10-07 16:52:23 +02:00
|
|
|
/* Update value */
|
2015-10-12 13:49:31 +02:00
|
|
|
json_array_clear(values);
|
2015-10-12 15:29:26 +02:00
|
|
|
for (int k = 0; k < cnt; k++) {
|
|
|
|
struct msg *m = &pool[(first + k) % poolsize];
|
|
|
|
|
|
|
|
double tsms = (double) m->ts.sec * 1e3 + m->ts.nsec / 1e6;
|
|
|
|
|
|
|
|
json_array_append_new(values, json_pack("[ o, o ]",
|
|
|
|
json_real(tsms),
|
|
|
|
json_real(m->data[j].f)
|
|
|
|
));
|
|
|
|
}
|
2015-09-21 17:13:50 +02:00
|
|
|
}
|
2015-10-12 15:29:26 +02:00
|
|
|
|
|
|
|
json_object_set_new(i->context, "updateAction", json_string("UPDATE")); // @todo REPLACE?
|
2015-09-21 17:13:50 +02:00
|
|
|
|
|
|
|
json_t *response;
|
2015-10-09 17:20:58 +02:00
|
|
|
int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, &response);
|
2015-09-21 17:13:50 +02:00
|
|
|
if (code != 200)
|
|
|
|
error("Failed to NGSI update Context request:\n%s", json_dumps(response, JSON_INDENT(4)));
|
|
|
|
|
2015-09-22 15:58:19 +02:00
|
|
|
return 1;
|
2015-09-19 18:54:27 +02:00
|
|
|
}
|
|
|
|
|
2015-09-25 03:16:02 +02:00
|
|
|
REGISTER_NODE_TYPE(NGSI, "ngsi", ngsi)
|