--- a/src/core/bdc_exec.c +++ b/src/core/bdc_exec.c @@ -549,9 +549,14 @@ int StartSendingReport(bdc_connection_t // Set the list of headers bc->headers = NULL; bc->headers = curl_slist_append(bc->headers, "Content-Type: application/json; charset=UTF-8"); - bc->headers = curl_slist_append(bc->headers, "BBF-Report-Format: NameValuePair"); + if (bc->flags & BDC_FLAG_HEADER_OBJ_HIER) + bc->headers = curl_slist_append(bc->headers, "BBF-Report-Format: ObjectHierarchy"); + else + bc->headers = curl_slist_append(bc->headers, "BBF-Report-Format: NameValuePair"); + if (bc->flags & BDC_FLAG_GZIP) { + curl_easy_setopt(curl_ctx, CURLOPT_ACCEPT_ENCODING, "gzip"); bc->headers = curl_slist_append(bc->headers, "Content-Encoding: gzip"); } --- a/src/core/bdc_exec.h +++ b/src/core/bdc_exec.h @@ -53,6 +53,6 @@ void BDC_EXEC_ScheduleExit(void); #define BDC_FLAG_PUT 0x00000001 // If set, HTTP PUT should be used instead of HTTP POST when sending the report to the BDC server #define BDC_FLAG_GZIP 0x00000002 // If set, the reports contants are Gzipped #define BDC_FLAG_DATE_HEADER 0x00000004 // If set, the date header should be included in the HTTP post. - +#define BDC_FLAG_HEADER_OBJ_HIER 0x00000008 // If set, report format in header would be ObjectHierarchy otherwise NameValuePair #endif --- a/src/core/device_bulkdata.c +++ b/src/core/device_bulkdata.c @@ -71,7 +71,8 @@ //------------------------------------------------------------------------------ // Definitions for formats that we support #define BULKDATA_ENCODING_TYPE "JSON" -#define BULKDATA_JSON_REPORT_FORMAT "NameValuePair" +#define BULKDATA_JSON_REPORT_FORMAT_NAME_VALUE "NameValuePair" +#define BULKDATA_JSON_REPORT_FORMAT_OBJ_HIER "ObjectHierarchy" // Definitions for Device.BulkData.Profile.{i}.JSONEncoding.ReportTimestamp @@ -162,6 +163,7 @@ typedef struct char compression[9]; char method[9]; bool use_date_header; + char report_format[20]; } profile_ctrl_params_t; //------------------------------------------------------------------------------ @@ -236,7 +238,7 @@ bulkdata_profile_t *bulkdata_find_free_p bulkdata_profile_t *bulkdata_find_profile(int profile_id); int bulkdata_calc_report_map(bulkdata_profile_t *bp, kv_vector_t *report_map); int bulkdata_reduce_to_alt_name(char *spec, char *path, char *alt_name, char *out_buf, int buf_len); -char *bulkdata_generate_json_report(bulkdata_profile_t *bp, char *report_timestamp); +char *bulkdata_generate_json_report(bulkdata_profile_t *bp, char *report_timestamp, char *report_format); unsigned char *bulkdata_compress_report(profile_ctrl_params_t *ctrl, char *input_buf, int input_len, int *p_output_len); int bulkdata_schedule_sending_http_report(profile_ctrl_params_t *ctrl, bulkdata_profile_t *bp, unsigned char *json_report, int report_len); int bulkdata_start_profile(bulkdata_profile_t *bp); @@ -310,7 +312,7 @@ int DEVICE_BULKDATA_Init(void) err |= USP_REGISTER_DBParam_ReadWrite("Device.BulkData.Profile.{i}.Parameter.{i}.Reference", "", Validate_BulkDataReference, NULL, DM_STRING); // Device.BulkData.Profile.{i}.JSONEncoding - err |= USP_REGISTER_DBParam_ReadWrite("Device.BulkData.Profile.{i}.JSONEncoding.ReportFormat", BULKDATA_JSON_REPORT_FORMAT, Validate_BulkDataReportFormat, NULL, DM_STRING); + err |= USP_REGISTER_DBParam_ReadWrite("Device.BulkData.Profile.{i}.JSONEncoding.ReportFormat", BULKDATA_JSON_REPORT_FORMAT_NAME_VALUE, Validate_BulkDataReportFormat, NULL, DM_STRING); err |= USP_REGISTER_DBParam_ReadWrite("Device.BulkData.Profile.{i}.JSONEncoding.ReportTimestamp", BULKDATA_JSON_TIMESTAMP_FORMAT_EPOCH, Validate_BulkDataReportTimestamp, NULL, DM_STRING); // Device.BulkData.Profile.{i}.HTTP @@ -698,9 +700,11 @@ int Validate_BulkDataReference(dm_req_t int Validate_BulkDataReportFormat(dm_req_t *req, char *value) { // Exit if trying to set a value outside of the range we accept - if (strcmp(value, BULKDATA_JSON_REPORT_FORMAT) != 0) + if (strcmp(value, BULKDATA_JSON_REPORT_FORMAT_NAME_VALUE) != 0 && + strcmp(value, BULKDATA_JSON_REPORT_FORMAT_OBJ_HIER) != 0) { - USP_ERR_SetMessage("%s: Only JSON Report Format supported is '%s'", __FUNCTION__, BULKDATA_JSON_REPORT_FORMAT); + USP_ERR_SetMessage("%s: Only JSON Report Format supported are '%s', '%s'", __FUNCTION__, + BULKDATA_JSON_REPORT_FORMAT_NAME_VALUE, BULKDATA_JSON_REPORT_FORMAT_OBJ_HIER); return USP_ERR_INVALID_VALUE; } @@ -2008,6 +2012,14 @@ int bulkdata_platform_get_profile_contro return err; } + // Exit if unable to get ReportFormat + USP_SNPRINTF(path, sizeof(path), "Device.BulkData.Profile.%d.JSONEncoding.ReportFormat", bp->profile_id); + err = DATA_MODEL_GetParameterValue(path, ctrl_params->report_format, sizeof(ctrl_params->report_format), 0); + if (err != USP_ERR_OK) + { + return err; + } + return USP_ERR_OK; } @@ -2283,7 +2295,7 @@ void bulkdata_process_profile_http(bulkd } // Exit if unable to generate the report - json_report = bulkdata_generate_json_report(bp, ctrl.report_timestamp); + json_report = bulkdata_generate_json_report(bp, ctrl.report_timestamp, ctrl.report_format); if (json_report == NULL) { USP_ERR_SetMessage("%s: bulkdata_generate_json_report failed", __FUNCTION__); @@ -2333,7 +2345,8 @@ void bulkdata_process_profile_usp_event( kv_pair_t kv; report_t *cur_report; char *json_report; - char report_timestamp[33]; + char report_timestamp[33] = {0}; + char report_format[20] = {0}; // Exit if the MTP has not been connected to successfully after bootup // This is to prevent BDC events being enqueued before the Boot! event is sent (the Boot! event is only sent after successfully connecting to the MTP). @@ -2350,6 +2363,14 @@ void bulkdata_process_profile_usp_event( return; } + // Exit if unable to get ReportFormat + USP_SNPRINTF(path, sizeof(path), "Device.BulkData.Profile.%d.JSONEncoding.ReportFormat", bp->profile_id); + err = DATA_MODEL_GetParameterValue(path, report_format, sizeof(report_format), 0); + if (err != USP_ERR_OK) + { + return; + } + // When sending via USP events, only one report is ever sent in each USP event // So ensure all retained reports are removed. NOTE: Clearing the reports here is only necessary when switching protocol from HTTP to USP event, and where HTTP had some unsent reports bulkdata_clear_retained_reports(bp); @@ -2367,7 +2388,7 @@ void bulkdata_process_profile_usp_event( bp->num_retained_reports = 1; // Exit if unable to generate the report - json_report = bulkdata_generate_json_report(bp, report_timestamp); + json_report = bulkdata_generate_json_report(bp, report_timestamp, report_format); if (json_report == NULL) { USP_ERR_SetMessage("%s: bulkdata_generate_json_report failed", __FUNCTION__); @@ -2579,21 +2600,7 @@ int bulkdata_reduce_to_alt_name(char *sp return USP_ERR_OK; } -/*********************************************************************//** -** -** bulkdata_generate_json_report -** -** Generates a JSON name-value pair format report -** NOTE: The report contains all retained failed reports, as well as the current report -** See TR-157 section A.4.2 (end) for an example, and section A.3.5.2 for layout of content containing failed report transmissions -** -** \param bp - pointer to bulk data profile containing all reports (current and retained) -** \param report_timestamp - value of Device.BulkData.Profile.{i}.JSONEncoding.ReportTimestamp -** -** \return pointer to NULL terminated dynamically allocated buffer containing the serialized report to send -** -**************************************************************************/ -char *bulkdata_generate_json_report(bulkdata_profile_t *bp, char *report_timestamp) +static char *create_json_name_value_pair_report(bulkdata_profile_t *bp, char *report_timestamp) { JsonNode *top; // top of report JsonNode *array; // array of reports (retained + current) @@ -2608,7 +2615,6 @@ char *bulkdata_generate_json_report(bulk long long value_as_ll; unsigned long long value_as_ull; bool value_as_bool; - char *result; int i, j; char buf[32]; kv_pair_t *kv; @@ -2631,7 +2637,7 @@ char *bulkdata_generate_json_report(bulk } else if (strcmp(report_timestamp, "ISO-8601")==0) { - result = iso8601_from_unix_time(report->collection_time, buf, sizeof(buf)); + char *result = iso8601_from_unix_time(report->collection_time, buf, sizeof(buf)); if (result != NULL) { json_append_member(element, "CollectionTime", json_mkstring(buf)); @@ -2690,11 +2696,174 @@ char *bulkdata_generate_json_report(bulk json_append_member(top, "Report", array); // Serialize the JSON tree - result = json_stringify(top, " "); + char *output = json_stringify(top, " "); // Clean up the JSON tree json_delete(top); // Other JsonNodes which are children of this top level tree will be deleted + return output; +} + +static char *create_json_obj_hier_report(bulkdata_profile_t *bp, char *report_timestamp) +{ + JsonNode *top; // top of report + JsonNode *array; // array of reports (retained + current) + JsonNode *element; // element of json array, containing an individual report + JsonNode *temp; + char *param_path; + char *param_type_value; + char param_type; + char *param_value; + kv_vector_t *report_map; + report_t *report; + double value_as_number; + long long value_as_ll; + unsigned long long value_as_ull; + bool value_as_bool; + int i, j; + char buf[32]; + kv_pair_t *kv; + int err; + + top = json_mkobject(); + array = json_mkarray(); + + // Iterate over all reports adding them to the JSON array + for (i=0; i < bp->num_retained_reports; i++) + { + report = &bp->reports[i]; + report_map = &report->report_map; + + // Add Collection time to each json report element (only if specified and not 'None') + element = json_mkobject(); + if (strcmp(report_timestamp, "Unix-Epoch")==0) + { + json_append_member(element, "CollectionTime", json_mknumber(report->collection_time)); + } + else if (strcmp(report_timestamp, "ISO-8601")==0) + { + char *result = iso8601_from_unix_time(report->collection_time, buf, sizeof(buf)); + if (result != NULL) + { + json_append_member(element, "CollectionTime", json_mkstring(buf)); + } + } + + temp = element; + // Iterate over each parameter, adding it to the json element. Take account of the parameter's type + for (j=0; j < report_map->num_entries; j++) + { + char buff[2056] = {0}; + char *pch = NULL, *pchr = NULL, *argv[128] = {0}; + int n = 0; + + kv = &report_map->vector[j]; + param_path = kv->key; + param_type_value = kv->value; + param_type = param_type_value[0]; // First character denotes the type of the parameter + param_value = ¶m_type_value[1]; // Subsequent characters contain the parameter's value + + strncpy(buff, param_path, sizeof(buff)); + for (pch = strtok_r(buff, ".", &pchr); pch != NULL; pch = strtok_r(NULL, ".", &pchr)) { + int idx; + JsonNode *obj = element; + argv[n] = pch; + + for (idx = 0; idx <= n; idx++) { + if (obj == NULL) + break; + obj = json_find_member(obj, argv[idx]); + } + + if (obj) + temp = obj; + else { + if (pchr != NULL && *pchr != '\0') { + // It is a DMOBJ + JsonNode *new = json_mkobject(); + json_append_member(temp, pch, new); + temp = new; + } else { + // It is a DMPARAM + switch (param_type) + { + case 'S': + json_append_member(temp, pch, json_mkstring(param_value) ); + break; + + case 'U': + value_as_ull = strtoull(param_value, NULL, 10); + json_append_member(temp, pch, json_mkulonglong(value_as_ull) ); + break; + + case 'L': + value_as_ll = strtoll(param_value, NULL, 10); + json_append_member(temp, pch, json_mklonglong(value_as_ll) ); + break; + + case 'N': + value_as_number = atof(param_value); + json_append_member(temp, pch, json_mknumber(value_as_number) ); + break; + + case 'B': + err = TEXT_UTILS_StringToBool(param_value, &value_as_bool); + if (err == USP_ERR_OK) + { + json_append_member(temp, pch, json_mkbool(value_as_bool) ); + } + break; + + default: + USP_ERR_SetMessage("%s: Invalid JSON parameter type ('%c') in report map for %s", __FUNCTION__, param_type_value[0], param_path); + break; + } + } + } + n++; + } + } + + // Add the json element to the json array + json_append_element(array, element); + } + + // Finally add the array to the report top level + json_append_member(top, "Report", array); + + // Serialize the JSON tree + char *output = json_stringify(top, " "); + + // Clean up the JSON tree + json_delete(top); // Other JsonNodes which are children of this top level tree will be deleted + + return output; +} + +/*********************************************************************//** +** +** bulkdata_generate_json_report +** +** Generates a JSON name-value pair or object-hierarchy format report +** NOTE: The report contains all retained failed reports, as well as the current report +** See TR-157 section A.4.2 (end) for an example, and section A.3.5.2 for layout of content containing failed report transmissions +** +** \param bp - pointer to bulk data profile containing all reports (current and retained) +** \param report_timestamp - value of Device.BulkData.Profile.{i}.JSONEncoding.ReportTimestamp +** +** \return pointer to NULL terminated dynamically allocated buffer containing the serialized report to send +** +**************************************************************************/ +char *bulkdata_generate_json_report(bulkdata_profile_t *bp, char *report_timestamp, char *report_format) +{ + char *result = NULL; + + if (strcmp(report_format, BULKDATA_JSON_REPORT_FORMAT_NAME_VALUE) == 0) { + result = create_json_name_value_pair_report(bp, report_timestamp); + } else if (strcmp(report_format, BULKDATA_JSON_REPORT_FORMAT_OBJ_HIER) == 0) { + result = create_json_obj_hier_report(bp, report_timestamp); + } + return result; } @@ -2851,6 +3020,11 @@ int bulkdata_schedule_sending_http_repor flags |= BDC_FLAG_DATE_HEADER; } + if (strcmp(ctrl->report_format, BULKDATA_JSON_REPORT_FORMAT_OBJ_HIER) == 0) + { + flags |= BDC_FLAG_HEADER_OBJ_HIER; + } + // Exit if failed to post a message to BDC thread // NOTE: Ownership of full_url, query_string, report, username and password passes to BDC_EXEC err = BDC_EXEC_PostReportToSend(bp->profile_id, full_url, query_string, username, password, report, report_len, flags);