/*
 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <math.h>
#include <limits.h>
#include <assert.h>
#include "device_model_private.h"
#include "iotcs_device.h"
#include "util/util.h"
#include "json/json_helper.h"
#include "protocol/http/http_wrapper.h"
#include "iotcs/iotcs_private.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "advanced/iotcs_messaging.h"
#include "messaging/msg_private.h"
#include "iotcs_port_system.h"
#include "util/util_memory.h"
#include "util/util_thread_private.h"
#include "util/util_buffer.h"
#include "util/util.h"
#include "iotcs_port_thread.h"
#include "policy/device_policy.h"
#ifdef IOTCS_STORAGE_SUPPORT
#include "scs/storage_dispatcher_private.h"
#include "advanced/iotcs_storage_object.h"
#endif

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_DM
#include "log/log_template.h"

extern iotcs_window_handle_t window_array[WINDOWS_COUNT];

#ifdef IOTCS_MESSAGING_THREAD_SAFETY
iotcs_port_mutex g_device_model_lock; /* protects all enpoints, models, devices */
#endif
static dm_endpoint_node_t* g_endpoint_list = NULL; /* each node contains device handles for particular endpoint id */
static iotcs_device_model_handle_t* g_model_list = NULL;

static void device_model_message_release(const iotcs_data_item_desc *desc, iotcs_value *value) {
    const iotcs_data_item_desc *desc_copy = desc;
    iotcs_value *value_copy = value;
    while (desc->key) {
        if (desc->type == IOTCS_VALUE_TYPE_STRING) {
            util_free((char*) value->string_value);
        }
        ++value;
        ++desc;
    }
    util_free(value_copy);
    util_free((void*) desc_copy);
}

void data_message_release(iotcs_message *data_msg) {
    device_model_message_release(data_msg->u.data.items_desc, data_msg->u.data.items_value);
}

void alert_message_release(iotcs_message *alert_msg) {
    device_model_message_release(alert_msg->u.alert.items_desc, alert_msg->u.alert.items_value);
}

void dm_update_value(iotcs_value* attribute_value, iotcs_typed_value *ptyped_value) {

    switch (ptyped_value->type) {
        case IOTCS_VALUE_TYPE_INT:
            attribute_value->int_value = ptyped_value->value.int_value;
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            attribute_value->number_value = ptyped_value->value.number_value;
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
            attribute_value->bool_value = ptyped_value->value.bool_value;
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
            attribute_value->date_time_value = ptyped_value->value.date_time_value;
            break;
        case IOTCS_VALUE_TYPE_URI:
#if defined IOTCS_STORAGE_SUPPORT
            if (g_storage_initialized) {
                dm_free_uri_object(attribute_value->uri_object);
                attribute_value->uri_object = ptyped_value->value.uri_object;
                attribute_value->uri_object->ref_cnt++;
                break;
            }
#endif
        case IOTCS_VALUE_TYPE_STRING:
            util_free((char*) attribute_value->string_value);
            attribute_value->string_value = util_safe_strcpy(ptyped_value->value.string_value);
            break;
        case IOTCS_VALUE_TYPE_NONE:
            break;
    }
}

iotcs_result dm_validate_value(dm_attribute* attribute_desc, iotcs_typed_value *ptyped_value) {
    if (attribute_desc->type != ptyped_value->type) {
        LOG_ERRS("Unexpected type of the attribute value to set");
        return IOTCS_RESULT_FAIL;
    }

    switch (ptyped_value->type) {
        case IOTCS_VALUE_TYPE_INT:
            if ((attribute_desc->range[0] != NULL)
                    && (ptyped_value->value.int_value < attribute_desc->range[0]->int_value)) {

                LOG_ERR("Integer attribute value is less than minimum range threshold: %d < %d",
                        ptyped_value->value.int_value, attribute_desc->range[0]->int_value);
                return IOTCS_RESULT_FAIL;
            }
            if ((attribute_desc->range[1] != NULL)
                    && ptyped_value->value.int_value > attribute_desc->range[1]->int_value) {

                LOG_ERR("Integer attribute value is greater than maximum range threshold: %d > %d",
                        ptyped_value->value.int_value, attribute_desc->range[1]->int_value);
                return IOTCS_RESULT_FAIL;
            }
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            if ((attribute_desc->range[0] != NULL)
                    && (ptyped_value->value.number_value < attribute_desc->range[0]->number_value)) {

                LOG_ERR("Number attribute value is less than minimum range threshold: %f < %f",
                        ptyped_value->value.number_value, attribute_desc->range[0]->number_value);
                return IOTCS_RESULT_FAIL;
            }
            if ((attribute_desc->range[1] != NULL)
                    && ptyped_value->value.number_value > attribute_desc->range[1]->number_value) {

                LOG_ERR("Number attribute value is greater than maximum range threshold: %f > %f",
                        ptyped_value->value.number_value, attribute_desc->range[1]->number_value);
                return IOTCS_RESULT_FAIL;
            }
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
            break;
        case IOTCS_VALUE_TYPE_URI:
            break;
        case IOTCS_VALUE_TYPE_STRING:
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
            break;
        case IOTCS_VALUE_TYPE_NONE:
            break;
        default:
            LOG_ERRS("Unknown type of the attribute value to set");
            return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result device_model_init(void) {
    if (NULL == (g_device_model_lock = iotcs_port_mutex_create())) {
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

void device_model_finalize(void) {
    UTIL_FINIT_DM_LOCK();
    /* if everything is released correctly global model list and endpoint
     * list must be empty. If they aren't it could be user's fault or ours */
    assert(g_endpoint_list == NULL);
    assert(g_model_list == NULL);
}

static dm_endpoint_node_t *find_endpoint(const char *endpoint_id) {
    dm_endpoint_node_t *e = g_endpoint_list;

    while (e) {
        if (!strcmp(e->endpoint_id, endpoint_id)) {
            return e;
        }
        e = e->next;
    }
    return NULL;
}

static iotcs_virtual_device_handle_t* find_device(dm_endpoint_node_t *endpoint_node, const char *urn) {
    iotcs_virtual_device_handle_t *d = endpoint_node->device_list;

    while (d) {
        if (!strcmp(d->model_handle->urn, urn)) {
            return d;
        }
        d = d->next;
    }
    return NULL;
}

static iotcs_result get_value_from_body(json_tokens_t* tok, const char* name, iotcs_typed_value* typed_value, iotcs_virtual_device_handle_t* device) {
    iotcs_result rv;

    if (!tok || !name || !typed_value) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    switch (typed_value->type) {
        case IOTCS_VALUE_TYPE_INT:
        case IOTCS_VALUE_TYPE_DATE_TIME:
        {
            int64_t val;
            rv = json_get_int_value_by_key(tok, name, &val);
            if (val > INT_MAX) {
                LOG_ERRS("Loss of data found.");
                return IOTCS_RESULT_FAIL;
            }
            typed_value->value.int_value = (int) val;
            return rv;
        }
        case IOTCS_VALUE_TYPE_NUMBER:
            return json_get_float_value_by_key(tok, name, &typed_value->value.number_value);
        case IOTCS_VALUE_TYPE_BOOLEAN:
            return json_get_bool_value_by_key(tok, name, &typed_value->value.bool_value);
        case IOTCS_VALUE_TYPE_URI:
#ifdef IOTCS_STORAGE_SUPPORT
        {
            const char *url;
            char* tmp_str = NULL;
            int tmp_str_len = 0;
            GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(tok, name, &tmp_str, &tmp_str_len));

            CHECK_OOM(url = util_safe_strncpy(tmp_str, tmp_str_len));

            rv = dm_create_uri_object_by_uri(url, &typed_value->value.uri_object);
            if (rv == IOTCS_RESULT_OK && IS_STORAGE_OBJECT(typed_value->value.uri_object)) {
                sd_storage_object *storage_object = (sd_storage_object *)typed_value->value.uri_object->object;
                storage_object->device_handle = (struct iotcs_virtual_device_handle_t*)device;
                return IOTCS_RESULT_OK;
            }
        }
#endif
        case IOTCS_VALUE_TYPE_STRING:
        {
            char* tmp_str = NULL;
            int tmp_str_len = 0;
            GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(tok, name, &tmp_str, &tmp_str_len));
            CHECK_OOM(typed_value->value.string_value = util_safe_strncpy(tmp_str, tmp_str_len));
            return IOTCS_RESULT_OK;
        }
        case IOTCS_VALUE_TYPE_NONE:
            return IOTCS_RESULT_INVALID_ARGUMENT;
        default:
            return IOTCS_RESULT_INVALID_ARGUMENT;
    }
error:
    return rv;
}

static int get_format_idx(dm_format_t* format, const char *name) {
    int i = -1;
    int count = format->fields_len;
    dm_format_field_t* fields = format->fields;
    for (i = 0; i < count; i++) {
        if (strcmp(name, fields[i].name) == 0) {
            return i;
        }
    }
    return -1;
}

int get_attribute_idx(iotcs_device_model_handle_t* hmodel, const char *name) {
    int i = -1;
    int count = hmodel->attributes.count;
    dm_attribute* attributes = hmodel->attributes.desc;
    for (i = 0; i < count; i++) {
        if (strcmp(name, attributes[i].name) == 0) {
            return i;
        }
    }
    return -1;
}

int get_action_idx(iotcs_device_model_handle_t* hmodel, const char *name) {
    int i = -1;
    int count = hmodel->actions.count;
    dm_action* actions = hmodel->actions.desc;
    for (i = 0; i < count; i++) {
        if (strcmp(name, actions[i].name) == 0) {
            return i;
        }
    }
    return -1;
}

static iotcs_result update_attribute(json_tokens_t* tok, iotcs_virtual_device_handle_t* device,
        int idx, const char* name, iotcs_named_value* named_values) {

    if (!tok || !name || idx < 0) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    named_values->typed_value.type = device->model_handle->attributes.desc[idx].type;
    named_values->name = device->model_handle->attributes.desc[idx].name;

    if (get_value_from_body(tok, name, &named_values->typed_value, device) != IOTCS_RESULT_OK) {
        return IOTCS_RESULT_FAIL;
    }

    if (named_values->typed_value.type == IOTCS_VALUE_TYPE_STRING) {
        /* release old string value */
        util_free((char*) device->attributes[idx].value.string_value);
        /* Safe copy of new string value because user could release string in callback */
        named_values->typed_value.value.string_value = util_safe_strcpy(device->attributes[idx].value.string_value);
    } else {
        device->attributes[idx].value = named_values->typed_value.value;
    }

    device->attributes[idx].is_filled = IOTCS_TRUE;

    return IOTCS_RESULT_OK;
}

static void free_named_values(iotcs_named_value* named_values) {
    while (named_values) {
        if (named_values->typed_value.type == IOTCS_VALUE_TYPE_STRING) {
            util_free((char*) named_values->typed_value.value.string_value);
        }
        named_values = named_values->next;
    };
}

static iotcs_result batch_attributes_control(json_tokens_t* tok, iotcs_virtual_device_handle_t* device) {
    iotcs_result rv = IOTCS_RESULT_FAIL;
    iotcs_virtual_device_change_event event;
    iotcs_virtual_device_changed_callback *local = NULL; /* [device->model_handle->attributes.count] */
    iotcs_named_value *named_values = NULL; /* [device->model_handle->attributes.count] */
    int idx;
    int event_count = 0;

    //TODO: use here correct macros
    if (NULL == (local = (iotcs_virtual_device_changed_callback*) util_malloc(
            device->model_handle->attributes.count * sizeof (iotcs_virtual_device_changed_callback)))) {
        rv = IOTCS_RESULT_OUT_OF_MEMORY;
        goto error;
    }

    if (NULL == (named_values = (iotcs_named_value*) util_malloc(
            device->model_handle->attributes.count * sizeof (iotcs_named_value)))) {
        rv = IOTCS_RESULT_OUT_OF_MEMORY;
        goto error;
    }

    for (idx = 0; idx < device->model_handle->attributes.count; ++idx) {
        if (update_attribute(tok, device, idx, device->model_handle->attributes.desc[idx].name,
                &named_values[event_count]) == IOTCS_RESULT_OK) {

            local[event_count] = device->attributes[idx].changed_cb;
            named_values[event_count].next = &named_values[event_count + 1];

            event_count++;
        }
    }
    if (event_count == 0) {
        goto error;
    }

    named_values[event_count - 1].next = NULL;
    event.named_value = named_values[0]; /* copy first named_value */

    device->ref_cnt++;

    DM_UNLOCK();
    UTIL_UNLOCK_LL();

    event.virtual_device_handle = (iotcs_virtual_device_handle) device;

    if (device->attribute_cb) {
        (*device->attribute_cb)(&event);
    }

    for (idx = 0; idx < event_count; idx++) {
        if (local[idx]) {
            named_values[idx].next = NULL;
            event.named_value = named_values[idx];
            (*local[idx])(&event);
        }
    }

    UTIL_LOCK_LL();
    DM_LOCK();
    free_named_values(named_values);
    dm_free_virtual_device(device);
    rv = IOTCS_RESULT_OK;

error:
    util_free(local);
    util_free(named_values);
    return rv;
}

iotcs_result device_model_handle_request(iotcs_request_message *request) {
    iotcs_virtual_device_handle_t* device = NULL;
    iotcs_device_model_handle_t* model = NULL;
    dm_endpoint_node_t *endpoint_node;
    iotcs_result rv = IOTCS_RESULT_FAIL;
    const char *urn;
    char* next_ptr = NULL;
    char *type;
    char* tmp;
    char* url;
    char* name;
    int idx;
    json_tokens_t tokens;
    int locked = 0; //don't forget to set this variable to 1/0 if you want to use GOTO_ERR after lock/unlock

    /* Possible requests examples:
     * 1)Device Attribute Control (batch) - deviceModels/urn:com:oracle:iot:device:thermometer/attributes
     * 2)Device Attribute Control (single) - deviceModels/urn:com:oracle:iot:device:thermometer/attributes/temperature
     * 3)Device Action Control - deviceModels/urn:com:oracle:iot:device:thermometer/actions/calibrate
     */
    if (NULL == (url = util_safe_strcpy(request->url))) {
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    UTIL_LOCK_LL(); /* needed to json parser */
    next_ptr = url;
    tmp = util_get_next_token(&next_ptr, '/');
    GOTO_ERR(!tmp);
    GOTO_ERR_MSG(strcmp(tmp, "deviceModels") != 0, "Unexpected request");
    urn = util_get_next_token(&next_ptr, '/');
    GOTO_ERR(!urn);
    type = util_get_next_token(&next_ptr, '/');
    GOTO_ERR(!type);
    name = util_get_next_token(&next_ptr, '/'); //could be NULL

    GOTO_ERR_MSG(IOTCS_RESULT_OK != (rv = json_parse(request->body, strlen(request->body), &tokens)),
            "Handle Request: json parse failed");

    DM_LOCK();
    locked = 1;

    GOTO_ERR_MSG(NULL == (endpoint_node = find_endpoint(request->destination)),
            "Handle Request: unknown endpoint id.");
    GOTO_ERR_MSG(NULL == (device = find_device(endpoint_node, urn)), "Handle Request: unknown model");

    model = device->model_handle;

    if (strcmp(type, "attributes") == 0) {
        if (name) {
            iotcs_virtual_device_change_event event;
            iotcs_virtual_device_changed_callback local;
            event.named_value.next = NULL;
            idx = get_attribute_idx(model, name);
            GOTO_ERR(update_attribute(&tokens, device, idx, "value", &event.named_value) != IOTCS_RESULT_OK);
            local = device->attributes[idx].changed_cb;
            device->ref_cnt++;

            DM_UNLOCK();
            UTIL_UNLOCK_LL();

            event.virtual_device_handle = (iotcs_virtual_device_handle) device;

            if (device->attribute_cb) {
                (*device->attribute_cb)(&event);
            }

            if (local) {
                (*local)(&event);
            }

            UTIL_LOCK_LL();
            DM_LOCK();
            free_named_values(&event.named_value);
            dm_free_virtual_device(device);
            rv = IOTCS_RESULT_OK;
        } else {
            rv = batch_attributes_control(&tokens, device);
        }
    } else if (strcmp(type, "actions") == 0) {
        iotcs_action_callback action_cb;
        iotcs_action_callback_cpp action_cb_cpp;
        iotcs_typed_value arg;
        dm_action *action = NULL;
        idx = get_action_idx(model, name);
        GOTO_ERR(idx < 0);

        action_cb = device->actions[idx].action_cb;
        action_cb_cpp = device->actions[idx].action_cb_cpp;
        if (action_cb || action_cb_cpp) {
            action = &device->model_handle->actions.desc[idx];
            arg.type = action->arg_type;

            if (arg.type != IOTCS_VALUE_TYPE_NONE) {
                get_value_from_body(&tokens, "value", &arg, device);
            }
            device->ref_cnt++;
            DM_UNLOCK();
            UTIL_UNLOCK_LL();

            //callback
            if (action && action_cb) {
                (*action_cb)((iotcs_virtual_device_handle) device, arg);
            }
            //cpp callback
            if (action && action_cb_cpp) {
                (*action_cb_cpp)((iotcs_virtual_device_handle) device, name, arg);
            }

            UTIL_LOCK_LL();
            DM_LOCK();
            if (arg.type == IOTCS_VALUE_TYPE_STRING) {
                util_free((char*) arg.value.string_value);
            }
            dm_free_virtual_device(device);
        }
        rv = IOTCS_RESULT_OK;
    } else {
        LOG_ERR("Handle Request: unknown attribute/action = %s", request->url);
        goto error;
    }

error:
    UTIL_UNLOCK_LL();
    util_free(url);
    if (locked) {
        DM_UNLOCK();
    }
    return rv;
}

static void notify_send_error(iotcs_virtual_device_handle_t *device, iotcs_message *request, const char *fail_reason) {
    int idx;
    int event_count = 0;
    iotcs_virtual_device_error_event event;
    iotcs_device_model_handle_t* model = device->model_handle;
    iotcs_virtual_device_error_callback global = device->attribute_err_cb;
    iotcs_virtual_device_error_callback *local = NULL; /*[device->model_handle->attributes.count]*/
    iotcs_named_value *named_values = NULL; /*[device->model_handle->attributes.count]*/
    const iotcs_data_item_desc *items_desc;
    const iotcs_value *items_value;

    if (NULL == (named_values = util_malloc(device->model_handle->attributes.count * sizeof (iotcs_named_value))) ||
            NULL == (local = util_malloc(device->model_handle->attributes.count * sizeof (iotcs_virtual_device_error_callback)))) {
        LOG_CRITS("Handle Error:out of memory");
        goto error;
    }

    items_desc = request->u.data.items_desc;
    items_value = request->u.data.items_value;

    /* message items_desc are always in the same order as model items_desc,
       but some could be missing. So one loop is enough */
    for (idx = 0; idx < model->attributes.count && items_desc[event_count].key; ++idx) {

        if (strcmp(model->attributes.desc[idx].name, items_desc[event_count].key) == 0) {
            named_values[event_count].name = model->attributes.desc[idx].name;
            named_values[event_count].typed_value.type = items_desc[event_count].type;
            named_values[event_count].typed_value.value = items_value[event_count];
            named_values[event_count].next = &named_values[event_count + 1];

            local[event_count] = device->attributes[idx].error_cb;
            ++event_count;
        }
    }

    if (event_count) {
        named_values[event_count - 1].next = NULL;
    }
    device->ref_cnt++;
    DM_UNLOCK();

    //global error cb
    event.virtual_device_handle = (iotcs_virtual_device_handle) device;
    event.message = fail_reason;
    event.named_value = named_values[0];
    if (global) {
        (*global)(&event);
    }

    //local error cb
    for (idx = 0; idx < event_count; idx++) {
        if (local[idx]) {
            named_values[idx].next = NULL;
            event.named_value = named_values[idx];
            (*local[idx])(&event);
        }
    }

    DM_LOCK();
    dm_free_virtual_device(device);

error:
    util_free(named_values);
    util_free(local);
}

static char* find_last_char(char* str, char ch) {
    char *last = NULL;
    while (*str) {
        if (*str == ch) {
            last = str;
        }
        ++str;
    }
    return last;
}

static void device_model_handle_send_error(iotcs_message *request, const char *fail_reason) {
    iotcs_virtual_device_handle_t* device = NULL;
    dm_endpoint_node_t *endpoint_node = NULL;
    const char *endpoint_id = NULL;
    char *urn = NULL;
    char *urn_last_colon;

    /* onError callbacks could be called for
     * iotcs_alert_raise and virtual_device_set_XXX methods and responses.
     * We have callbacks for attributes only.
     */
    if (request->base->type != IOTCS_MESSAGE_DATA) {
        LOG_INFO("Handle Error: skip failed send, type=%d", request->base->type);
        return;
    }

    endpoint_id = request->base->source;

    /* this string is urn:attributes - put \0 instead of last ':' */
    if (NULL == (urn = util_safe_strcpy(request->u.data.base->format))) {
        LOG_CRITS("Handle Error: out of memory");
        return;
    }

    if (NULL == (urn_last_colon = find_last_char(urn, ':'))) {
        LOG_ERR("Handle Error: unexpected format=%s, expected urn:attributes", request->u.data.base->format);
        return;
    }

    *urn_last_colon = '\0';

    DM_LOCK();
    if (NULL == (endpoint_node = find_endpoint(endpoint_id))) {
        LOG_ERR("Handle Error: unknown endpoint id = %s", endpoint_id);
    } else {
        if (NULL != (device = find_device(endpoint_node, urn))) {
            notify_send_error(device, request, fail_reason);
        } else {
            LOG_ERR("Handle Error: can't find device with urn %s", urn);
        }
    }
    DM_UNLOCK();
    util_free((void*) urn);
}

void device_model_handle_send(iotcs_message *request, iotcs_result result, const char *fail_reason) {
    if (IOTCS_RESULT_OK != result) {
        device_model_handle_send_error(request, fail_reason);
    }
    if (request->base->type == IOTCS_MESSAGE_DATA) {
        iotcs_virtual_device_handle_t* device = (iotcs_virtual_device_handle_t*) request->user_data;
        data_message_release(request);
        DM_LOCK();
        dm_free_virtual_device(device);
        DM_UNLOCK();
    } else if (request->base->type == IOTCS_MESSAGE_ALERT) {
        iotcs_virtual_device_handle_t* device = (iotcs_virtual_device_handle_t*) request->user_data;
        alert_message_release(request);
        DM_LOCK();
        dm_free_virtual_device(device);
        DM_UNLOCK();
    } else if (request->base->type == IOTCS_MESSAGE_RESOURCE) {
        /* TODO: remove dirty use of offset */
        int i;
        int resource_len = request->u.resource.resource_len;
        const iotcs_resource_message_base *resources = request->u.resource.base;
        iotcs_virtual_device_handle_t* device = (iotcs_virtual_device_handle_t*) request->user_data;
        for (i = 0; i < resource_len; ++i) {
            util_free((void*) resources[i].path);
        }
        util_free((iotcs_resource_message_base*) resources);
        DM_LOCK();
        dm_free_virtual_device(device);
        DM_UNLOCK();
    } else if (request->base->type == IOTCS_MESSAGE_RESPONSE) {
        int i;
        for (i = 0; i < request->u.response.headers_len; i++) {
            util_free((char*) request->u.response.headers[i].key);
            util_free((char*) request->u.response.headers[i].value);
        }
        util_free((char*) request->u.response.body);
    } else {
        LOG_ERR("UNEXPECTED MESSAGE TYPE!!!: %d", request->base->type);
    }
}

//public

void iotcs_virtual_device_set_on_change(iotcs_virtual_device_handle virtual_device_handle, iotcs_virtual_device_changed_callback callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    if (!virtual_device_handleice) {
        return;
    }
    DM_LOCK();
    virtual_device_handleice->attribute_cb = callback;
    DM_UNLOCK();
    return;
}

//public

iotcs_result iotcs_virtual_device_attribute_set_on_change(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_virtual_device_changed_callback callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handleice || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(virtual_device_handleice->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    virtual_device_handleice->attributes[idx].changed_cb = callback;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public

void iotcs_virtual_device_set_on_error(iotcs_virtual_device_handle virtual_device_handle, iotcs_virtual_device_error_callback callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    if (!virtual_device_handleice) {
        return;
    }
    DM_LOCK();
    virtual_device_handleice->attribute_err_cb = callback;
    DM_UNLOCK();
    return;
}

//public

iotcs_result iotcs_virtual_device_attribute_set_on_error(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_virtual_device_error_callback callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handleice || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(virtual_device_handleice->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    virtual_device_handleice->attributes[idx].error_cb = callback;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public

iotcs_result iotcs_virtual_device_set_callback(iotcs_virtual_device_handle virtual_device_handle, const char *action_name, iotcs_action_callback callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handleice || !action_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_action_idx(virtual_device_handleice->model_handle, action_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    virtual_device_handleice->actions[idx].action_cb = callback;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//private

iotcs_result iotcs_virtual_device_set_callback_cpp(iotcs_virtual_device_handle virtual_device_handle, const char *action_name, iotcs_action_callback_cpp callback) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handleice || !action_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_action_idx(virtual_device_handleice->model_handle, action_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    virtual_device_handleice->actions[idx].action_cb_cpp = callback;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public

void iotcs_virtual_device_start_update(iotcs_virtual_device_handle virtual_device_handle) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    if (!virtual_device_handleice) {
        return;
    }

    if (virtual_device_handleice->update_state != DM_DEVICE_UPDATE_ON) {
        return;
    }

    virtual_device_handleice->update_state = DM_DEVICE_UPDATE_OFF;
    return;
}

//public

void iotcs_virtual_device_finish_update(iotcs_virtual_device_handle virtual_device_handle) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    iotcs_result rv;
    iotcs_message msg, offer_msg, *p_msg;

    if (!virtual_device_handleice) {
        return;
    }
    DM_LOCK();

    if (virtual_device_handleice->update_state != DM_DEVICE_IS_UPDATED) {
        virtual_device_handleice->update_state = DM_DEVICE_UPDATE_ON;
        DM_UNLOCK();
        return;
    }

    virtual_device_handleice->update_state = DM_DEVICE_UPDATE_ON;
    rv = dm_make_update_message(virtual_device_handleice, &msg);
    if (IOTCS_RESULT_OK == rv) {
        if (IOTCS_RESULT_OK != iotcs_message_dispatcher_queue(&msg)) {
            LOG_ERRS("Can not send update message");
        }
        data_message_release(&msg);
    }
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    rv = dm_make_offer_message(virtual_device_handleice, &offer_msg);
    DM_UNLOCK();
    if (IOTCS_RESULT_OK == rv) {
        copy_msg(&offer_msg, &p_msg);
        data_message_release(&offer_msg);
        if (!apply_policy(virtual_device_handleice, p_msg)) {
            if (IOTCS_RESULT_OK != iotcs_message_dispatcher_queue(p_msg)) {
                release_msg_copy(p_msg);
            }
        } else {
            release_msg_copy(p_msg);
        }
        util_free(p_msg);
    }
#else
    DM_UNLOCK();
#endif

    return;
}

//public
const char * iotcs_get_device_model_name(iotcs_device_model_handle device_model_handle) {
    iotcs_device_model_handle_t *model_handle = (iotcs_device_model_handle_t *) device_model_handle;
    return model_handle->name;
}

const char * iotcs_get_device_model_description(iotcs_device_model_handle device_model_handle) {
    iotcs_device_model_handle_t *model_handle = (iotcs_device_model_handle_t *) device_model_handle;
    return model_handle->description;
}

const char * iotcs_get_device_model_urn(iotcs_device_model_handle device_model_handle) {
    iotcs_device_model_handle_t *model_handle = (iotcs_device_model_handle_t *) device_model_handle;
    return model_handle->urn;
}

iotcs_result iotcs_get_device_model(const char *device_model_urn, iotcs_device_model_handle *virtual_device_handleice_model) {
    iotcs_device_model_handle_t* device_model = NULL;
    char* model_string = NULL;
    json_tokens_t tokens;

    if (!device_model_urn) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    {
        iotcs_device_model_handle_t *cur = g_model_list;
        while (cur) {
            if (!strcmp(cur->urn, device_model_urn)) {
                cur->ref_cnt++;
                *virtual_device_handleice_model = (iotcs_device_model_handle) cur;
                DM_UNLOCK();
                return IOTCS_RESULT_OK;
            }
            cur = cur->next;
        }
    }

    UTIL_LOCK_LL();
    GOTO_ERR(dm_get_device_model(device_model_urn, &model_string) != IOTCS_RESULT_OK);
    GOTO_ERR(!model_string);
    GOTO_ERR(json_parse(model_string, strlen(model_string), &tokens) != IOTCS_RESULT_OK);

    device_model = device_model_fromJSON(&tokens);
    GOTO_ERR(!device_model);
    UTIL_UNLOCK_LL(); /* model string points to shared buffer */

    device_model->next = g_model_list;
    g_model_list = device_model;
    device_model->ref_cnt = 1;

    *virtual_device_handleice_model = (iotcs_device_model_handle) device_model;
    DM_UNLOCK();

    return IOTCS_RESULT_OK;

error:
    UTIL_UNLOCK_LL();
    dm_release_model(device_model);
    DM_UNLOCK();

    return IOTCS_RESULT_FAIL;
}

//public

void iotcs_free_device_model(iotcs_device_model_handle virtual_device_handleice_model) {
    iotcs_device_model_handle_t *model_handle = (iotcs_device_model_handle_t *) virtual_device_handleice_model;

    DM_LOCK();
    dm_release_model(model_handle);
    DM_UNLOCK();
}

static iotcs_message_base g_resource_msg_base = {
    .type = IOTCS_MESSAGE_RESOURCE,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGHEST,
    .reliability = IOTCS_MESSAGE_RELIABILITY_GUARANTED_DELIVERY
};

static iotcs_result register_device_resources(iotcs_virtual_device_handle_t* device) {
    char buf[128];
    char *pattr;
    int i;
    iotcs_device_model_handle_t* model = device->model_handle;
    int resource_len = model->attributes.count + 1;
    iotcs_resource_message_base *resources = NULL;
    iotcs_result rv;

    if (NULL == (resources = (iotcs_resource_message_base *) util_calloc(resource_len, sizeof (iotcs_resource_message_base)))) {
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }

    strcpy(buf, model->urn);
    pattr = buf + strlen(model->urn);
    *pattr++ = '/';

    for (i = 0; i < resource_len - 1; ++i) {
        strcpy(pattr, model->attributes.desc[i].name);
        resources[i].path = util_safe_strcpy(buf);
        resources[i].name = model->attributes.desc[i].name;
        resources[i].methods = IOTCS_REQUEST_METHOD_GET | (model->attributes.desc[i].is_writable ? IOTCS_REQUEST_METHOD_PUT : 0);
        resources[i].description = model->attributes.desc[i].description;
    }
    strcpy(pattr, "attributes");
    resources[i].path = util_safe_strcpy(buf);
    resources[i].name = "all attributes";
    resources[i].methods = IOTCS_REQUEST_METHOD_GET | IOTCS_REQUEST_METHOD_PUT;
    resources[i].description = "Resource for batch update";

    iotcs_message request = {
        .base = &g_resource_msg_base,
        .u.resource =
        {
            .report_type = IOTCS_RESOURCE_MESSAGE_UPDATE,
            .endpointName = device->endpoint_node->endpoint_id,
            .base = resources,
            .resource_len = resource_len
        }
    };
    request.user_data = (void*) device;

    if (IOTCS_RESULT_OK != (rv = iotcs_message_dispatcher_queue(&request))) {
        for (i = 0; i < resource_len; ++i) {
            util_free((void*) resources[i].path);
        }
        util_free(resources);
    } else {
        device->ref_cnt++;
    }

    return rv;
}

//public

iotcs_result iotcs_create_virtual_device_handle(const char *endpoint_id, iotcs_device_model_handle virtual_device_handleice_model, iotcs_virtual_device_handle *virtual_device_handle) {
    iotcs_virtual_device_handle_t* device_handle;
    iotcs_device_model_handle_t* device_model = (iotcs_device_model_handle_t*) virtual_device_handleice_model;
    dm_endpoint_node_t *endpoint_node;
    iotcs_result rv = IOTCS_RESULT_FAIL;

    if (!endpoint_id || !device_model) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_LOCK();
    if (NULL != (endpoint_node = find_endpoint(endpoint_id))) {
        /* if we found endpoint - check if we already have device handle with given URN */
        if (NULL != (device_handle = find_device(endpoint_node, device_model->urn))) {
            device_handle->ref_cnt++;
            *virtual_device_handle = (iotcs_virtual_device_handle) device_handle;
            DM_UNLOCK();
            return IOTCS_RESULT_OK;
        }
    }

    device_handle = (iotcs_virtual_device_handle_t*) util_calloc(1, sizeof (iotcs_virtual_device_handle_t));
    if (!device_handle) {
        DM_UNLOCK();
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    device_handle->ref_cnt = 1; /* in case we've got OOM - so free_device_handle works properly */

    if (!endpoint_node) {
        /* Create new endpoint node, init and add to global list */
        if (NULL == (endpoint_node = (dm_endpoint_node_t*) util_malloc(sizeof (dm_endpoint_node_t)))) {
            rv = IOTCS_RESULT_OUT_OF_MEMORY;
            goto error;
        }
        endpoint_node->device_list = NULL;
        endpoint_node->endpoint_id = util_safe_strcpy(endpoint_id);
        if (NULL == endpoint_node->endpoint_id) {
            util_free(endpoint_node);
            rv = IOTCS_RESULT_OUT_OF_MEMORY;
            goto error;
        }

        /* add to global list */
        endpoint_node->next = g_endpoint_list;
        g_endpoint_list = endpoint_node;

        endpoint_node->data_message_base.type = IOTCS_MESSAGE_DATA;
        endpoint_node->data_message_base.reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT;
        endpoint_node->data_message_base.priority = IOTCS_MESSAGE_PRIORITY_HIGH;
        endpoint_node->data_message_base.destination = cl_get_server_host();
        endpoint_node->data_message_base.sender = iotcs_get_endpoint_id();
        endpoint_node->data_message_base.source = endpoint_node->endpoint_id;

        endpoint_node->alert_message_base.type = IOTCS_MESSAGE_ALERT;
        endpoint_node->alert_message_base.reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT;
        endpoint_node->alert_message_base.priority = IOTCS_MESSAGE_PRIORITY_HIGH;
        endpoint_node->alert_message_base.destination = cl_get_server_host();
        endpoint_node->alert_message_base.sender = iotcs_get_endpoint_id();
        endpoint_node->alert_message_base.source = endpoint_node->endpoint_id;
    }
    /* Add device handle to device_list */
    device_handle->next = endpoint_node->device_list;
    endpoint_node->device_list = device_handle;

    /* safe: endpoint node lives until at least one device exist */
    device_handle->endpoint_node = endpoint_node;
    device_handle->model_handle = device_model;
    device_handle->update_state = DM_DEVICE_UPDATE_ON;
    device_model->ref_cnt++;

    if (NULL == (device_handle->attributes = util_calloc(device_model->attributes.count, sizeof (dm_attribute_value_t)))) {
        rv = IOTCS_RESULT_OUT_OF_MEMORY;
        goto error;
    }

    if (NULL == (device_handle->actions = util_calloc(device_model->actions.count, sizeof (dm_action_value_t)))) {
        rv = IOTCS_RESULT_OUT_OF_MEMORY;
        goto error;
    }

    if (IOTCS_RESULT_OK != (rv = register_device_resources(device_handle))) {
        goto error;
    }

    *virtual_device_handle = (iotcs_virtual_device_handle) device_handle;

    /*setting of the default values*/
    int idx;
    for (idx = 0; idx < device_handle->model_handle->attributes.count; idx++) {
        if (device_handle->model_handle->attributes.desc[idx].has_default_value) {
            device_handle->attributes[idx].value = device_handle->model_handle->attributes.desc[idx].default_value;
            if (device_handle->model_handle->attributes.desc[idx].type == IOTCS_VALUE_TYPE_STRING 
#if defined IOTCS_STORAGE_SUPPORT
               ) {
#else
                || device_handle->model_handle->attributes.desc[idx].type == IOTCS_VALUE_TYPE_URI) {
#endif
                device_handle->attributes[idx].value.string_value = strdup(device_handle->model_handle->attributes.desc[idx].default_value.string_value);
            }
#if defined IOTCS_STORAGE_SUPPORT
            if (device_handle->model_handle->attributes.desc[idx].type == IOTCS_VALUE_TYPE_URI) {
                struct iotcs_uri_object_t* default_uri_object = device_handle->model_handle->attributes.desc[idx].default_value.uri_object;
                switch (default_uri_object->type) {
                    case IOTCS_EXTERNAL_OBJECT:
                        iotcs_create_external_object((char*)default_uri_object->object,
                                            &device_handle->attributes[idx].value.uri_object);
                        break;
                    case IOTCS_STORAGE_OBJECT:
                        dm_create_uri_object_by_uri(iotcs_storage_object_get_uri(default_uri_object),
                                            &device_handle->attributes[idx].value.uri_object);
                        break;
                    default:
                        device_handle->attributes[idx].value.uri_object = NULL;
                        break;
                }
            }
#endif
            device_handle->attributes[idx].is_filled = IOTCS_TRUE;
        }
    }

    DM_UNLOCK();

    return IOTCS_RESULT_OK;

error:

    dm_free_virtual_device(device_handle);
    DM_UNLOCK();
    return rv;
}

static int get_dirty_len(dm_format_handle_t *hformat) {
    return hformat->format->fields_len / 32 + 1;
}

static iotcs_result iotcs_virtual_device_get_format_handle(iotcs_virtual_device_handle virtual_device_handle,
        const char *format_name, dm_format_handle_t **hformat, dm_format_type format_type) {
    iotcs_virtual_device_handle_t* device_handle = NULL;
    iotcs_device_model_handle_t* model_handle;
    int i;

    if (!virtual_device_handle || !format_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    DM_LOCK();
    model_handle = device_handle->model_handle;
    for (i = 0; i < model_handle->formats.formats_len; i++) {
        if (strcmp(format_name, model_handle->formats.formats[i].urn) == 0) {
            if (model_handle->formats.formats[i].type == format_type) {
                dm_format_handle_t *format = (dm_format_handle_t*) util_calloc(1, sizeof (dm_format_handle_t) +
                        (model_handle->formats.formats[i].fields_len / 32 + 1) * sizeof (int32_t));

                if (format == NULL) {
                    DM_UNLOCK();
                    return IOTCS_RESULT_OUT_OF_MEMORY;
                }

                format->values = (iotcs_value*) util_calloc(model_handle->formats.formats[i].fields_len, sizeof (iotcs_value));

                if (format->values == NULL) {
                    DM_UNLOCK();
                    util_free(format);
                    return IOTCS_RESULT_OUT_OF_MEMORY;
                }

                format->format = &model_handle->formats.formats[i];
                format->device = device_handle;
                format->type = format_type;

                *hformat = format;
                device_handle->ref_cnt++; // TODO: fix device structure
                DM_UNLOCK();
                return IOTCS_RESULT_OK;
            }
        }
    }
    DM_UNLOCK();
    return IOTCS_RESULT_INVALID_ARGUMENT;
}

//public

iotcs_result iotcs_virtual_device_create_alert_handle(iotcs_virtual_device_handle virtual_device_handle, const char *alert_name, iotcs_alert_handle *alert_handle) {
    return iotcs_virtual_device_get_format_handle(virtual_device_handle, alert_name, (dm_format_handle_t**) alert_handle, DM_ALERT_FORMAT_TYPE);
}

//public

iotcs_result iotcs_virtual_device_create_data_handle(iotcs_virtual_device_handle virtual_device_handle, const char *data_name, iotcs_data_handle *data_handle) {
    return iotcs_virtual_device_get_format_handle(virtual_device_handle, data_name, (dm_format_handle_t**) data_handle, DM_DATA_FORMAT_TYPE);
}


#ifdef IOTCS_STORAGE_SUPPORT
iotcs_result iotcs_virtual_device_get_uri_object(iotcs_virtual_device_handle virtual_device_handle,
        const char *attribute_name, struct iotcs_uri_object_t **value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;
    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
       DM_UNLOCK();
       return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
       DM_UNLOCK();
       return IOTCS_RESULT_FAIL;
    }
    *value = device_handle->attributes[idx].value.uri_object;
    (device_handle->attributes[idx].value.uri_object)->ref_cnt++;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_virtual_device_get_storage_object(iotcs_virtual_device_handle virtual_device_handle,
        const char *attribute_name, iotcs_storage_object_handle *value) {
    iotcs_storage_object_handle *value_tmp = NULL;
    iotcs_result rv = iotcs_virtual_device_get_uri_object(virtual_device_handle, attribute_name, value_tmp);
    if (value_tmp == NULL || (rv == IOTCS_RESULT_OK && !IS_STORAGE_OBJECT(*value_tmp))) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    } else {
        *value = *value_tmp;
        return rv;
    }
}

iotcs_result iotcs_virtual_device_get_external_object(iotcs_virtual_device_handle virtual_device_handle,
        const char *attribute_name, iotcs_external_object_handle *value) {
    iotcs_external_object_handle *value_tmp = NULL;
    iotcs_result rv = iotcs_virtual_device_get_uri_object(virtual_device_handle, attribute_name, value_tmp);
    if (value_tmp == NULL || (rv == IOTCS_RESULT_OK && !IS_EXTERNAL_OBJECT(*value_tmp))) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    } else {
        *value = *value_tmp;
        return rv;
    }
}
#endif

static iotcs_result set_attribute_by_idx(iotcs_virtual_device_handle_t *virtual_device_handleice, int idx, iotcs_typed_value *ptyped_value) {

    if (dm_validate_value(&virtual_device_handleice->model_handle->attributes.desc[idx], ptyped_value) != IOTCS_RESULT_OK) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    dm_update_value(&virtual_device_handleice->attributes[idx].value, ptyped_value);
    virtual_device_handleice->attributes[idx].is_filled = IOTCS_TRUE;

    if (virtual_device_handleice->update_state == DM_DEVICE_UPDATE_ON) {
        iotcs_message msg;
        if (IOTCS_RESULT_OK == dm_make_update_message(virtual_device_handleice, &msg)) {
            DM_UNLOCK();
            iotcs_message_dispatcher_queue(&msg);
            DM_LOCK();
            data_message_release(&msg);
        }
    } else {
        virtual_device_handleice->update_state = DM_DEVICE_IS_UPDATED;
    }
    return IOTCS_RESULT_OK;
}

// Must be called without lock
static iotcs_result set_offer_attribute_safe(iotcs_virtual_device_handle_t *virtual_device_handleice, const char *name, iotcs_typed_value *ptyped_value, iotcs_bool is_offered) {
    int idx = -1;
    dm_attribute* attribute_desc;
    iotcs_device_model_handle_t* model_handle;
    iotcs_message *p_msg, offer_msg, msg;
    iotcs_result rv;

    if (!virtual_device_handleice || !name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    model_handle = virtual_device_handleice->model_handle;

    if ((idx = get_attribute_idx(model_handle, name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    attribute_desc = &model_handle->attributes.desc[idx];

    if (dm_validate_value(attribute_desc, ptyped_value) != IOTCS_RESULT_OK) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    dm_update_value(&virtual_device_handleice->attributes[idx].value, ptyped_value);
    virtual_device_handleice->attributes[idx].is_dirty = IOTCS_TRUE;
    virtual_device_handleice->attributes[idx].is_offered = is_offered;
    virtual_device_handleice->attributes[idx].is_filled = IOTCS_TRUE;

    if (virtual_device_handleice->update_state != DM_DEVICE_UPDATE_ON) {
        virtual_device_handleice->update_state = DM_DEVICE_IS_UPDATED;
        DM_UNLOCK();
        return IOTCS_RESULT_OK;
    }

    rv = dm_make_update_message(virtual_device_handleice, &msg);
    if (IOTCS_RESULT_OK == rv) {
        rv = iotcs_message_dispatcher_queue(&msg);
        if (rv != IOTCS_RESULT_OK) {
            LOG_ERRS("Can not send update message");
        }
        data_message_release(&msg);
    }
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    if (is_offered && IOTCS_RESULT_OK == rv) {
        rv = dm_make_offer_message(virtual_device_handleice, &offer_msg);
        DM_UNLOCK();
        if (IOTCS_RESULT_OK == rv) {
            copy_msg(&offer_msg, &p_msg);
            data_message_release(&offer_msg);
            if (!apply_policy(virtual_device_handleice, p_msg)) {
                if (IOTCS_RESULT_OK != iotcs_message_dispatcher_queue(p_msg)) {
                    release_msg_copy(p_msg);
                }
            } else {
                release_msg_copy(p_msg);
            }
            util_free(p_msg);
        }
    } else {
        DM_UNLOCK();
    }
#else
    DM_UNLOCK();
#endif

    return rv;
}

static iotcs_result set_attribute_safe(iotcs_virtual_device_handle_t *virtual_device_handleice, const char *name, iotcs_typed_value *ptyped_value) {
    return set_offer_attribute_safe(virtual_device_handleice, name, ptyped_value, IOTCS_FALSE);
}

static iotcs_result offer_attribute_safe(iotcs_virtual_device_handle_t *virtual_device_handleice, const char *name, iotcs_typed_value *ptyped_value) {
    return set_offer_attribute_safe(virtual_device_handleice, name, ptyped_value, IOTCS_TRUE);
}

const size_t g_dm_type_to_size[] = {
    sizeof (int), // IOTCS_VALUE_TYPE_INT = 0
    sizeof (float), // IOTCS_VALUE_TYPE_NUMBER = 1
    sizeof (iotcs_bool), // IOTCS_VALUE_TYPE_BOOLEAN = 2
    sizeof (char), // IOTCS_VALUE_TYPE_STRING = 3
    sizeof (iotcs_date_time), // IOTCS_VALUE_TYPE_DATE_TIME = 4
    0, // IOTCS_VALUE_TYPE_NONE = 5
#if defined IOTCS_STORAGE_SUPPORT
    sizeof (struct iotcs_uri_object_t*) // IOTCS_VALUE_TYPE_URI = 6
#else
    sizeof (char), // IOTCS_VALUE_TYPE_URI = 6
#endif

};

iotcs_result iotcs_virtual_device_set(iotcs_virtual_device_handle virtual_device_handle, const char* attribute_name, const void* value, size_t length) {
    iotcs_virtual_device_handle_t* virtual_device_handleice = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    const char* char_value = value;
    dm_attribute* attribute_desc;
    iotcs_device_model_handle_t* model_handle;
    size_t arg_size;
    int idx;
    iotcs_result result = IOTCS_RESULT_FAIL;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();

    model_handle = virtual_device_handleice->model_handle;

    if ((idx = get_attribute_idx(virtual_device_handleice->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    attribute_desc = &model_handle->attributes.desc[idx];

    arg_size = g_dm_type_to_size[attribute_desc->type];

    switch (attribute_desc->type) {
        case IOTCS_VALUE_TYPE_INT:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.int_value = *((int*) char_value);
                char_value += arg_size;
                if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                    DM_UNLOCK();
                    return result;
                }
            }
        }
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.number_value = *((float*) char_value);
                char_value += arg_size;
                if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                    DM_UNLOCK();
                    return result;
                }
            }
        }
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.bool_value = *((iotcs_bool*) char_value);
                char_value += arg_size;
                if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                    DM_UNLOCK();
                    return result;
                }
            }
        }
            break;
        case IOTCS_VALUE_TYPE_URI:
#ifdef IOTCS_STORAGE_SUPPORT
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_URI};
            typed_value.value.uri_object = *((struct iotcs_uri_object_t **) char_value);
            if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                DM_UNLOCK();
                return result;
            }
        }
            break;
#endif
        case IOTCS_VALUE_TYPE_STRING:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_STRING};
            typed_value.value.string_value = char_value;
            if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                DM_UNLOCK();
                return result;
            }
        }
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.date_time_value = *((iotcs_date_time*) char_value);
                char_value += arg_size;
                if ((result = set_attribute_by_idx(virtual_device_handleice, idx, &typed_value)) != IOTCS_RESULT_OK) {
                    DM_UNLOCK();
                    return result;
                }
            }
        }
            break;
        default:
            LOG_ERRS("Unknown type of the attribute");
            DM_UNLOCK();
            return IOTCS_RESULT_FAIL;
    }

    DM_UNLOCK();

    return IOTCS_RESULT_OK;
}

//public

iotcs_result iotcs_virtual_device_set_string(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, const char *value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_STRING,
        {.string_value = value}};

    return set_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_get_string(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, const char **value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
        DM_UNLOCK();
        return IOTCS_RESULT_FAIL;
    }

    *value = strdup(device_handle->attributes[idx].value.string_value);
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public
iotcs_result iotcs_virtual_device_offer_float(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, float value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    if (!util_is_valid_number(value)) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER,
        {.number_value = value}};

    return offer_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_offer_integer(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, int value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT,
        {.int_value = value}};

    return offer_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_offer_boolean(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_bool value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN,
        {.bool_value = value}};

    return offer_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_offer_date_time(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_date_time value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME,
        {.date_time_value = value}};

    return offer_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_set_integer(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, int value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT,
        {.int_value = value}};
    return set_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_get_integer(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, int *value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
        DM_UNLOCK();
        return IOTCS_RESULT_FAIL;
    }

    *value = device_handle->attributes[idx].value.int_value;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_virtual_device_set_float(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, float value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    if (!util_is_valid_number(value)) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER,
        {.number_value = value}};

    return set_attribute_safe(device_handle, attribute_name, &typed_value);
}

//public

iotcs_result iotcs_virtual_device_get_float(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, float *value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
        DM_UNLOCK();
        return IOTCS_RESULT_FAIL;
    }

    *value = device_handle->attributes[idx].value.number_value;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public

iotcs_result iotcs_virtual_device_set_boolean(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_bool value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN,
        {.bool_value = value}};

    return set_attribute_safe(device_handle, attribute_name, &typed_value);
}

iotcs_result iotcs_virtual_device_get_boolean(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_bool *value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
        DM_UNLOCK();
        return IOTCS_RESULT_FAIL;
    }

    *value = device_handle->attributes[idx].value.bool_value;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

//public

iotcs_result iotcs_virtual_device_set_date_time(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_date_time value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME,
        {.date_time_value = value}};

    return set_attribute_safe(device_handle, attribute_name, &typed_value);
}

//public

iotcs_result iotcs_virtual_device_get_date_time(iotcs_virtual_device_handle virtual_device_handle, const char *attribute_name, iotcs_date_time *value) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    int idx = -1;

    if (!virtual_device_handle || !attribute_name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (device_handle->attributes[idx].is_filled == IOTCS_FALSE) {
        DM_UNLOCK();
        return IOTCS_RESULT_FAIL;
    }

    *value = device_handle->attributes[idx].value.date_time_value;
    DM_UNLOCK();

    return IOTCS_RESULT_OK;
}

static void iotcs_virtual_device_free_format_handle(dm_format_handle_t *hformat) {
    if (!hformat)
        return;

    DM_LOCK();
    int i;
    for (i = 0; i < hformat->format->fields_len; i++) {
        if (hformat->format->fields[i].type == IOTCS_VALUE_TYPE_STRING
#if defined IOTCS_STORAGE_SUPPORT
            ) {
#else
            || hformat->format->fields[i].type == IOTCS_VALUE_TYPE_URI) {
#endif
            util_free((char*) hformat->values[i].string_value);
        }

#if defined IOTCS_STORAGE_SUPPORT
        if (hformat->format->fields[i].type == IOTCS_VALUE_TYPE_URI) {
            dm_free_uri_object(hformat->values[i].uri_object);
        }
#endif
    }
    dm_free_virtual_device(hformat->device);
    util_free(hformat->values);
    util_free(hformat);
    DM_UNLOCK();
}

//public

void iotcs_virtual_device_free_alert_handle(iotcs_alert_handle alert_handle) {
    iotcs_virtual_device_free_format_handle((dm_format_handle_t *) alert_handle);
}

//public

void iotcs_virtual_device_free_data_handle(iotcs_data_handle data_handle) {
    iotcs_virtual_device_free_format_handle((dm_format_handle_t *) data_handle);
}

//internal: used in the iotcs_free_device_handle method
//TODO: optimize this

void dm_free_attributes(dm_attributes_t* attributes_desc) {

    util_free((void*) attributes_desc->message_props.format);

    if (attributes_desc->desc) {
        int i;
        for (i = 0; i < attributes_desc->count; i++) {
            util_free((char*) attributes_desc->desc[i].alias);
            util_free((char*) attributes_desc->desc[i].description);
            util_free((char*) attributes_desc->desc[i].name);
            util_free(attributes_desc->desc[i].range[0]);
            util_free(attributes_desc->desc[i].range[1]);
        }
        util_free(attributes_desc->desc);
    }
}

void dm_free_actions(dm_actions_t* actions_desc) {
    int i;

    if (actions_desc->desc) {
        for (i = 0; i < actions_desc->count; i++) {
            util_free((char*) actions_desc->desc[i].alias);
            util_free((char*) actions_desc->desc[i].description);
            util_free((char*) actions_desc->desc[i].name);
        }
        util_free(actions_desc->desc);
    }
}

//internal: used in the iotcs_free_device_handle method
//TODO: optimize this

void dm_free_formats(dm_formats_handle_t* formats) {

    if (formats->formats) {
        int i;
        for (i = 0; i < formats->formats_len; i++) {
            if (formats->formats[i].fields) {
                int j;
                for (j = 0; j < formats->formats[i].fields_len; j++) {
                    util_free((char*) formats->formats[i].fields[j].name);
                }
                util_free(formats->formats[i].fields);
            }

            util_free((char*) formats->formats[i].name);
            util_free((char*) formats->formats[i].urn);
            util_free((char*) formats->formats[i].description);
        }
        util_free(formats->formats);
    }
}

//public - frees all items of the device model

void iotcs_free_virtual_device_handle(iotcs_virtual_device_handle virtual_device_handle) {
    iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
    DM_LOCK();
    dm_free_virtual_device(device_handle);
    DM_UNLOCK();
}

void dm_release_model(iotcs_device_model_handle_t* model_handle) {

    if (!model_handle) {
        return;
    }

    model_handle->ref_cnt--;

    if (0 == model_handle->ref_cnt) {
        iotcs_device_model_handle_t *prev, *cur;

        /* find the previous item in the list */
        prev = NULL;
        cur = g_model_list;
        while (cur) {
            /* Check if it's a item to remove */
            if (cur == model_handle) {
                /* Check if we are about to remove first item from the list */
                if (NULL == prev) {
                    g_model_list = model_handle->next;
                } else {
                    prev->next = model_handle->next;
                }

                dm_free_model(model_handle);
                return;
            }
            prev = cur;
            cur = cur->next;
        }

        LOG_CRIT("Release model: inconsistency in model list %s", model_handle->urn);
    }
}

void dm_free_model(iotcs_device_model_handle_t* model_handle) {
    util_free((void*) model_handle->urn);
    util_free((void*) model_handle->name);
    util_free((void*) model_handle->description);
    dm_free_formats(&model_handle->formats);
    dm_free_attributes(&model_handle->attributes);
    dm_free_actions(&model_handle->actions);
    util_free(model_handle);
}

void dm_free_virtual_device(iotcs_virtual_device_handle_t* device_handle) {
    iotcs_device_model_handle_t* model_handle;

    if (!device_handle) {
        return;
    }

    model_handle = device_handle->model_handle;

    device_handle->ref_cnt--;

    if (0 == device_handle->ref_cnt) {

        if (device_handle->actions) {
            int i = 0;
            dm_action* action_desc = model_handle->actions.desc;
            dm_action_value_t *action_value = device_handle->actions;
            int count = model_handle->actions.count;
            for (i = 0; i < count; ++i) {
                if (action_desc[i].arg_type == IOTCS_VALUE_TYPE_STRING)
                    util_free((char*) action_value[i].value.string_value);
            }
            util_free(device_handle->actions);
        }

        if (device_handle->attributes) {
            int i = 0;
            dm_attribute* attribute_desc = model_handle->attributes.desc;
            dm_attribute_value_t *attribute_value = device_handle->attributes;
            int count = model_handle->attributes.count;
            for (i = 0; i < count; ++i) {
                if (attribute_desc[i].type == IOTCS_VALUE_TYPE_STRING
#if defined IOTCS_STORAGE_SUPPORT
                    ) {
#else
                    || attribute_desc[i].type == IOTCS_VALUE_TYPE_URI) {
#endif
                        util_free((char*) attribute_value[i].value.string_value);
                        if (attribute_desc[i].has_default_value) {
                            util_free((char*) attribute_desc[i].default_value.string_value);
                        }
                }
#if defined IOTCS_STORAGE_SUPPORT
                if (attribute_desc[i].type == IOTCS_VALUE_TYPE_URI) {
                    dm_free_uri_object(attribute_value[i].value.uri_object);
                }
#endif
            }
            util_free(device_handle->attributes);
        }

        dm_release_model(device_handle->model_handle);

        dm_remove_device_from_endpoint(device_handle);

        util_free(device_handle);
    }
    return;
}

void dm_remove_device_from_endpoint(iotcs_virtual_device_handle_t* device_handle) {
    iotcs_virtual_device_handle_t *prev, *cur;
    dm_endpoint_node_t *endpoint_node;

    if (!device_handle->endpoint_node)
        return;

    endpoint_node = device_handle->endpoint_node;

    /* find the previous item in the list */
    prev = NULL;
    cur = endpoint_node->device_list;
    while (cur) {
        /* Check if it's a item to remove */
        if (cur == device_handle) {
            /* Check if we are about to remove first item from the list */
            if (NULL == prev) {
                endpoint_node->device_list = device_handle->next;
            } else {
                prev->next = device_handle->next;
            }

            if (endpoint_node->device_list == NULL) {
                dm_release_endpoint(endpoint_node);
            }
            return;
        }
        prev = cur;
        cur = cur->next;
    }

    LOG_CRIT("Remove device: inconsistency in device list %s", endpoint_node->endpoint_id);
}

void dm_release_endpoint(dm_endpoint_node_t* endpoint_node) {
    dm_endpoint_node_t *prev, *cur;

    /* find the previous item in the list */
    prev = NULL;
    cur = g_endpoint_list;
    while (cur) {
        /* Check if it's a item to remove */
        if (cur == endpoint_node) {
            /* Check if we are about to remove first item from the list */
            if (NULL == prev) {
                g_endpoint_list = endpoint_node->next;
            } else {
                prev->next = endpoint_node->next;
            }
            util_free((char*) endpoint_node->endpoint_id);
            util_free(endpoint_node);
            return;
        }
        prev = cur;
        cur = cur->next;
    }

    LOG_CRIT("Endpoint release: inconsistency in endpoint list %s", endpoint_node->endpoint_id);
}

//internal - used instead of HL messaging

iotcs_result dm_make_update_offer_message(iotcs_virtual_device_handle_t* device, iotcs_message *msg, iotcs_bool is_offered) {
    int attr_num = device->model_handle->attributes.count;
    iotcs_data_item_desc* items_desc;
    int i, j;

    memset(msg, 0, sizeof (iotcs_message));

    msg->u.data.items_value = (iotcs_value*) util_malloc(sizeof (iotcs_value) * attr_num);
    if (NULL == msg->u.data.items_value) {
        return IOTCS_RESULT_FAIL;
    }

    items_desc = (iotcs_data_item_desc*) util_malloc(sizeof (iotcs_data_item_desc) * (attr_num + 1));
    if (NULL == items_desc) {
        util_free(msg->u.data.items_value);
        return IOTCS_RESULT_FAIL;
    }

    msg->base = &device->endpoint_node->data_message_base;
    msg->u.data.base = &device->model_handle->attributes.message_props;

    for (i = 0, j = 0; i < attr_num; i++) {
        if (device->attributes[i].is_dirty && device->attributes[i].is_offered == is_offered) {

            device->attributes[i].is_dirty = IOTCS_FALSE;
            device->attributes[i].is_offered = IOTCS_FALSE;

            /* set value */
            msg->u.data.items_value[j] = device->attributes[i].value;
            if (device->model_handle->attributes.desc[i].type == IOTCS_VALUE_TYPE_STRING) {
                if (device->attributes[i].value.string_value) {
                    msg->u.data.items_value[j].string_value = util_safe_strcpy(device->attributes[i].value.string_value);
                } else {
                    msg->u.data.items_value[j].string_value = util_safe_strcpy("");
                }
            }
            if (device->model_handle->attributes.desc[i].type == IOTCS_VALUE_TYPE_URI) {
                if (IS_STORAGE_OBJECT(device->attributes[i].value.uri_object) &&
                    iotcs_storage_object_sync(device->attributes[i].value.uri_object) != IOTCS_RESULT_OK) {
                    LOG_CRIT("Can not sync object %s", iotcs_storage_object_get_name(device->attributes[i].value.uri_object));
                    continue;
                }
            }

            /* set description */
            items_desc[j].key = device->model_handle->attributes.desc[i].name;
            items_desc[j].type = device->model_handle->attributes.desc[i].type;
            ++j;
        }
    }

    if (j == 0) {
        util_free(msg->u.data.items_value);
        util_free(items_desc);
        return IOTCS_RESULT_FAIL;
    }

    items_desc[j].key = NULL;
    items_desc[j].type = IOTCS_VALUE_TYPE_NONE;

    msg->u.data.items_desc = items_desc;

    msg->user_data = (void*) device;

    return IOTCS_RESULT_OK;
}

iotcs_result dm_make_update_message(iotcs_virtual_device_handle_t* device, iotcs_message *msg) {
    return dm_make_update_offer_message(device, msg, IOTCS_FALSE);
}

iotcs_result dm_make_offer_message(iotcs_virtual_device_handle_t* device, iotcs_message *msg) {
    return dm_make_update_offer_message(device, msg, IOTCS_TRUE);
}

static void set_dirty_idx(dm_format_handle_t *format, int field_idx) {
    int bit = field_idx % 32;
    int idx = field_idx / 32;
    int32_t mask = 1 << bit;
    format->is_dirty[idx] |= mask;
}

static int is_dirty_idx(dm_format_handle_t *format, int field_idx) {
    int bit = field_idx % 32;
    int idx = field_idx / 32;
    return (format->is_dirty[idx] >> bit) & 1;
}

static void clean_dirty_idx(dm_format_handle_t *format) {
    int dirty_len = get_dirty_len(format);
    int i;
    for (i = 0; i < dirty_len; i++) {
        format->is_dirty[i] = 0;
    }
}

static int get_next_dirty_idx(dm_format_handle_t *format, int field_idx) {
    while (!is_dirty_idx(format, ++field_idx)) {
    }

    return field_idx;
}

static int get_dirty_bit_num(dm_format_handle_t *format) {
    int dirty_len = get_dirty_len(format);
    int dirty_num = 0;
    int i;
    for (i = 0; i < dirty_len; i++) {
        int x = format->is_dirty[i];
        while (x) {
            dirty_num++;
            x &= (x - 1);
        }
    }

    return dirty_num;
}

iotcs_result set_value(dm_format_handle_t *hformat, const char *field_name, iotcs_typed_value *ptyped_value) {
    int idx = -1;
    dm_format_field_t* field;

    DM_LOCK();

    if (!hformat || !field_name) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if ((idx = get_format_idx(hformat->format, field_name)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    field = &hformat->format->fields[idx];

    if (field->type != ptyped_value->type) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    dm_update_value(&hformat->values[idx], ptyped_value);

    set_dirty_idx(hformat, idx);

    DM_UNLOCK();

    return IOTCS_RESULT_OK;
}

static iotcs_result check_format(dm_format_handle_t* hformat, dm_format_type type) {
    int idx;

    if (!hformat || hformat->type != type) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    /* Check that all non-optional data fields has been set (dirty) */
    for (idx = 0; idx < hformat->format->fields_len; idx++) {
        /* if non-optional and not-dirty */
        if (hformat->format->fields[idx].optional == IOTCS_FALSE &&
                is_dirty_idx(hformat, idx) == IOTCS_FALSE) {
            return IOTCS_RESULT_FAIL;
        }
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_data_submit(iotcs_data_handle data_handle) {
    dm_format_t* data_format = NULL;
    dm_format_handle_t *data;
    iotcs_message data_message;
    iotcs_data_item_desc* data_item_desc = NULL;
    iotcs_result rv = IOTCS_RESULT_FAIL;
    int dirty_bits;
    int count = 0;
    int idx;

    data = (dm_format_handle_t*) data_handle;
    DM_LOCK();

    if (IOTCS_RESULT_OK != (rv = check_format(data, DM_DATA_FORMAT_TYPE))) {
        DM_UNLOCK();
        return rv;
    }

    data_format = data->format;
    memset(&data_message, 0, sizeof (iotcs_message));

    dirty_bits = get_dirty_bit_num(data);
    rv = IOTCS_RESULT_FAIL;
    GOTO_ERR(dirty_bits == 0);

    //calloc used here because error block required for structures that initializes all their bits to zero.
    rv = IOTCS_RESULT_OUT_OF_MEMORY;
    GOTO_ERR_CRIT(NULL == (data_item_desc = (iotcs_data_item_desc*) util_calloc(dirty_bits + 1, sizeof (iotcs_data_item_desc))));
    GOTO_ERR_CRIT(NULL == (data_message.u.data.items_value = (iotcs_value*) util_calloc(dirty_bits, sizeof (iotcs_value))));

    count = 0;
    idx = -1;
    while (dirty_bits--) {
        dm_format_field_t* field;
        idx = get_next_dirty_idx(data, idx);
        field = data_format->fields + idx;
#if defined IOTCS_STORAGE_SUPPORT
        if (field->type == IOTCS_VALUE_TYPE_URI) {
            if (IS_STORAGE_OBJECT(data->values[idx].uri_object)) {
                GOTO_ERR_NO_OK(rv, iotcs_storage_object_sync(data->values[idx].uri_object));
            }
        }
#endif

        data_item_desc[count].key = field->name;
        data_item_desc[count].type = field->type;
        data_message.u.data.items_value[count] = data->values[idx];
        data->values[idx].string_value = NULL;
        count++;
    }

    data_item_desc[count].key = NULL;
    data_item_desc[count].type = IOTCS_VALUE_TYPE_NONE;

    data_message.event_time = iotcs_port_get_current_time_millis();
    data_message.base = &data->device->endpoint_node->data_message_base;
    data_message.u.data.base = &data_format->u.data_message_base;
    data_message.u.data.items_desc = data_item_desc;

    data_message.user_data = (void*) (data->device);
    data->device->ref_cnt++;
    clean_dirty_idx(data);
    DM_UNLOCK();
    if (IOTCS_RESULT_OK != (rv = iotcs_message_dispatcher_queue(&data_message))) {
        LOG_CRITS("Failed to enqueue data message");
    }
    data_message_release(&data_message);
    DM_LOCK();
    dm_free_virtual_device(data->device);
    DM_UNLOCK();

    return rv;

error:
    util_free(data_item_desc);
    util_free(data_message.u.data.items_value);

    DM_UNLOCK();
    return rv;
}

iotcs_result iotcs_alert_raise(iotcs_alert_handle alert_handle) {
    dm_format_t* alert_format = NULL;
    dm_format_handle_t *alert;
    iotcs_message alert_msg;
    iotcs_data_item_desc* data_item_desc = NULL;
    iotcs_result rv = IOTCS_RESULT_FAIL;
#if defined IOTCS_STORAGE_SUPPORT
    sd_storage_object *storage_object = NULL;
#endif
    int dirty_bits;
    int count = 0;
    int idx;

    alert = (dm_format_handle_t*) alert_handle;
    DM_LOCK();

    if (IOTCS_RESULT_OK != (rv = check_format(alert, DM_ALERT_FORMAT_TYPE))) {
        DM_UNLOCK();
        return rv;
    }

    alert_format = alert->format;
    memset(&alert_msg, 0, sizeof (iotcs_message));

    rv = IOTCS_RESULT_FAIL;

    GOTO_ERR(0 == (dirty_bits = get_dirty_bit_num(alert)) && alert->format->fields_len != 0);

    //calloc used here because error block required for structures that initializes all their bits to zero.
    rv = IOTCS_RESULT_OUT_OF_MEMORY;
    GOTO_ERR_OOM(rv, data_item_desc = (iotcs_data_item_desc*) util_calloc(dirty_bits + 1, sizeof (iotcs_data_item_desc)));
    GOTO_ERR_OOM(rv, alert_msg.u.alert.items_value = (iotcs_value*) util_calloc(dirty_bits, sizeof (iotcs_value)));

    count = 0;
    idx = -1;
    while (dirty_bits--) {
        dm_format_field_t* field;
        idx = get_next_dirty_idx(alert, idx);
        field = alert_format->fields + idx;
#if defined IOTCS_STORAGE_SUPPORT
        if (field->type == IOTCS_VALUE_TYPE_URI) {
            if (IS_STORAGE_OBJECT(alert->values[idx].uri_object)) {
                storage_object = (sd_storage_object *)alert->values[idx].uri_object->object;
                GOTO_ERR_NO_OK(rv, iotcs_storage_object_sync(alert->values[idx].uri_object));
            }
        }
#endif

        data_item_desc[count].key = field->name;
        data_item_desc[count].type = field->type;
        alert_msg.u.alert.items_value[count] = alert->values[idx];
        alert->values[idx].string_value = NULL;
        count++;
    }

    data_item_desc[count].key = NULL;
    data_item_desc[count].type = IOTCS_VALUE_TYPE_NONE;

    alert_msg.event_time = iotcs_port_get_current_time_millis();
    alert_msg.base = &alert->device->endpoint_node->alert_message_base;
    alert_msg.u.alert.base = &alert_format->u.alert_message_base;
    alert_msg.u.alert.items_desc = data_item_desc;

    alert_msg.user_data = (void*) (alert->device);
    alert->device->ref_cnt++;
    clean_dirty_idx(alert);

    DM_UNLOCK();
    if (IOTCS_RESULT_OK != (rv = iotcs_message_dispatcher_queue(&alert_msg))) {
        LOG_CRITS("Failed to enqueue alert message");
    }
    alert_message_release(&alert_msg);
    DM_LOCK();
    dm_free_virtual_device(alert->device);
    DM_UNLOCK();

    return rv;

error:
    util_free(data_item_desc);
    util_free(alert_msg.u.alert.items_value);

    DM_UNLOCK();
    return rv;
}

static iotcs_result iotcs_format_field_set(dm_format_handle_t* hformat, const char* field, const void* value, size_t length) {
    const char* char_value = (const char*) value;
    dm_format_field_t* field_t;
    size_t arg_size;
    int idx = -1;

    if (!hformat || !field || !char_value || !length) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();

    if ((idx = get_format_idx(hformat->format, field)) < 0) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    field_t = &hformat->format->fields[idx];

    arg_size = g_dm_type_to_size[field_t->type];

    switch (field_t->type) {
        case IOTCS_VALUE_TYPE_INT:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.int_value = *((int*) char_value);
                char_value += arg_size;
                dm_update_value(&hformat->values[idx], &typed_value);
                set_dirty_idx(hformat, idx);
            }
        }
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.number_value = *((float*) char_value);
                char_value += arg_size;
                dm_update_value(&hformat->values[idx], &typed_value);
                set_dirty_idx(hformat, idx);
            }
        }
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.bool_value = *((iotcs_bool*) char_value);
                char_value += arg_size;
                dm_update_value(&hformat->values[idx], &typed_value);
                set_dirty_idx(hformat, idx);
            }
        }
            break;
        case IOTCS_VALUE_TYPE_STRING:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_STRING};
            typed_value.value.string_value = char_value;
            dm_update_value(&hformat->values[idx], &typed_value);
            set_dirty_idx(hformat, idx);
        }
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
        {
            iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME};
            while (length >= arg_size) {
                length -= arg_size;
                typed_value.value.date_time_value = *((iotcs_date_time*) char_value);
                char_value += arg_size;
                dm_update_value(&hformat->values[idx], &typed_value);
                set_dirty_idx(hformat, idx);
            }
        }
            break;
        default:
            DM_UNLOCK();
            return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_UNLOCK();

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_alert_set(iotcs_alert_handle alert_handle, const char* field, const void* value, size_t length) {
    if (!alert_handle || ((dm_format_handle_t*) alert_handle)->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    return iotcs_format_field_set((dm_format_handle_t*) alert_handle, field, value, length);
}

#ifdef IOTCS_STORAGE_SUPPORT
iotcs_result iotcs_alert_set_storage_object(iotcs_alert_handle alert_handle, const char *field,
        iotcs_storage_object_handle value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;
    iotcs_typed_value typed_value;
    iotcs_result rv;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    typed_value.type = IOTCS_VALUE_TYPE_URI;
    typed_value.value.uri_object = value;

    if (IOTCS_RESULT_OK != (rv = set_value(alert, field, &typed_value))) {
        return rv;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_alert_set_external_object(iotcs_alert_handle alert_handle, const char *field,
            struct iotcs_uri_object_t * value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;
    iotcs_typed_value typed_value;
    iotcs_result rv;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    typed_value.type = IOTCS_VALUE_TYPE_URI;
    typed_value.value.uri_object = value;

    if (IOTCS_RESULT_OK != (rv = set_value(alert, field, &typed_value))) {
        return rv;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_data_set_storage_object(iotcs_data_handle data_handle, const char *field,
            struct iotcs_uri_object_t * value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;
    iotcs_typed_value typed_value;
    iotcs_result rv;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    typed_value.type = IOTCS_VALUE_TYPE_URI;
    typed_value.value.uri_object = value;

    if (IOTCS_RESULT_OK != (rv = set_value(data, field, &typed_value))) {
        return rv;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_data_set_external_object(iotcs_data_handle data_handle, const char *field,
            iotcs_external_object_handle value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;
    iotcs_typed_value typed_value;
    iotcs_result rv;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    typed_value.type = IOTCS_VALUE_TYPE_URI;
    typed_value.value.uri_object = value;

    if (IOTCS_RESULT_OK != (rv = set_value(data, field, &typed_value))) {
        return rv;
    }

    return IOTCS_RESULT_OK;
}
#endif

iotcs_result iotcs_data_set(iotcs_data_handle data_handle, const char* field, const void* value, size_t length) {
    if (!data_handle || ((dm_format_handle_t*) data_handle)->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    return iotcs_format_field_set((dm_format_handle_t*) data_handle, field, value, length);
}

iotcs_result iotcs_alert_set_string(iotcs_alert_handle alert_handle, const char *field, const char *value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_STRING,
        {.string_value = value}};

    return set_value(alert, field, &typed_value);
}

iotcs_result iotcs_data_set_string(iotcs_data_handle data_handle, const char *field, const char *value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_STRING,
        {.string_value = value}};

    return set_value(data, field, &typed_value);
}

iotcs_result iotcs_alert_set_integer(iotcs_alert_handle alert_handle, const char *field, int value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT,
        {.int_value = value}};

    return set_value(alert, field, &typed_value);
}

iotcs_result iotcs_data_set_integer(iotcs_data_handle data_handle, const char *field, int value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_INT,
        {.int_value = value}};

    return set_value(data, field, &typed_value);
}

iotcs_result iotcs_alert_set_float(iotcs_alert_handle alert_handle, const char *field, float value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER,
        {.number_value = value}};

    return set_value(alert, field, &typed_value);
}

iotcs_result iotcs_data_set_float(iotcs_data_handle data_handle, const char *field, float value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_NUMBER,
        {.number_value = value}};

    return set_value(data, field, &typed_value);
}

iotcs_result iotcs_alert_set_boolean(iotcs_alert_handle alert_handle, const char *field, iotcs_bool value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN,
        {.bool_value = value}};

    return set_value(alert, field, &typed_value);
}

iotcs_result iotcs_data_set_boolean(iotcs_data_handle data_handle, const char *field, iotcs_bool value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_BOOLEAN,
        {.bool_value = value}};

    return set_value(data, field, &typed_value);
}

iotcs_result iotcs_alert_set_date_time(iotcs_alert_handle alert_handle, const char *field, iotcs_date_time value) {
    dm_format_handle_t* alert = (dm_format_handle_t*) alert_handle;

    if (!alert || alert->format->type != DM_ALERT_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME,
        {.date_time_value = value}};

    return set_value(alert, field, &typed_value);
}

iotcs_result iotcs_data_set_date_time(iotcs_data_handle data_handle, const char *field, iotcs_date_time value) {
    dm_format_handle_t* data = (dm_format_handle_t*) data_handle;

    if (!data || data->format->type != DM_DATA_FORMAT_TYPE) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    iotcs_typed_value typed_value = {IOTCS_VALUE_TYPE_DATE_TIME,
        {.date_time_value = value}};

    return set_value(data, field, &typed_value);
}

static iotcs_result iotcs_get_device_model_from_server(const char* device_model_urn, char** device_model_json) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    char* url = NULL;
    protocol_response response;

    /*
     * We will use the payload buffer because message body here is "".
     * By default it has length more than 1025.
     */
    url = util_get_payload_buffer();
    if (0 > util_safe_snprintf(url, IOTCS_DEVICE_MODEL_URL_SIZE, "/iot/api/v2/deviceModels/%s", device_model_urn)) {
        LOG_ERRS("Error was appeared during writing to url buffer.");
        return IOTCS_RESULT_FAIL;
    }

    protocol_request request = {
        .body = "",
        .url = url,
        .method = PROTOCOL_REQUEST_METHOD_GET,
        .headers =
        {
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    result = cl_proceed_request(request, &response);

    if (result == IOTCS_RESULT_OK) {
        if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED || response.status_code == PROTOCOL_RESPONSE_CODE_OK || response.status_code == PROTOCOL_RESPONSE_CODE_CREATED) {
            *device_model_json = response.body;
            result = IOTCS_RESULT_OK;
        }
    }

    return result;
}

#ifdef  IOTCS_DEVICE_MODEL_DIR
#ifndef IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE
#define IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE 128
#endif

static void write_string_to_file(const char *filename, const char *str) {
    size_t str_ln = strlen(str);
    FILE *fp = fopen(filename, "wb");
    if (!fp) {
        LOG_ERR("write_string_to_file: can't open file %s", filename);
        return;
    }
    fwrite(str, sizeof (char), str_ln + 1, fp);
    fclose(fp);
}

static char * read_file_to_string(const char *filename) {
    char *rv;
    long file_sz;
    FILE *fp = fopen(filename, "rb");
    if (!fp)
        return NULL;

    /*
     * We use here response buffer because we are under LL lock
     */
    rv = util_get_response_buffer();

    file_sz = fread(rv, sizeof (char), UTIL_RESPONSE_BUFFER_LENGTH - 1, fp);
    if (!feof(fp) || ferror(fp)) {
        rv = NULL;
    } else {
        rv[file_sz] = 0; /* if file has no '\0' at the end */
    }
    fclose(fp);

    return rv;
}

#endif // #ifdef  IOTCS_DEVICE_MODEL_DIR

#define DEVICE_MODEL_FILENAME_EXT ".json"

iotcs_result dm_get_device_model(const char* device_model_urn, char** device_model_json) {
    iotcs_result rv;
#ifdef  IOTCS_DEVICE_MODEL_DIR

    char buf[IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE];

    /* try to get device model json from local file system
     * full filename: IOTCS_DEVICE_MODEL_DIR/<encodedURN>DEVICE_MODEL_FILENAME_EXT
     */
    do {

        size_t dir_ln = strlen(UTIL_STRINGIFY(IOTCS_DEVICE_MODEL_DIR));
        size_t urn_ln = strlen(device_model_urn);
        size_t ext_ln = strlen(DEVICE_MODEL_FILENAME_EXT);
        size_t written = 0;
        if (dir_ln + 1 > IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE) {
            LOG_ERR("Device Model dir is too long: %d (acceptable len %d)", dir_ln, IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE - 1);
            break;
        }

        strcpy(buf, UTIL_STRINGIFY(IOTCS_DEVICE_MODEL_DIR));
        buf[dir_ln] = '/';

        written += dir_ln + 1;

        if ((written += util_urlencode(device_model_urn, buf + dir_ln + 1, IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE - (dir_ln + 1))) <= dir_ln + 1) {
            LOG_ERR("Device Model urn is too long: %d (acceptable len %d)", urn_ln, IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE - (dir_ln + 1));
            break;
        }

        if (IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE - written < ext_ln + 1) {
            LOG_ERR("Device Model urn is too long: %d (acceptable len %d)", strlen(buf) + ext_ln + 1, IOTCS_DEVICE_MODEL_FILENAME_BUFFER_SIZE);
            break;
        }

        strcpy(buf + written, DEVICE_MODEL_FILENAME_EXT);

        *device_model_json = read_file_to_string(buf);
        if (!*device_model_json) {
            LOG_ERR("Can't find or read file %s", buf);
            break;
        }

        return IOTCS_RESULT_OK;
    } while (0);
#endif // #ifdef  IOTCS_DEVICE_MODEL_DIR

    rv = iotcs_get_device_model_from_server(device_model_urn, device_model_json);
#ifdef  IOTCS_DEVICE_MODEL_DIR
    if (rv == IOTCS_RESULT_OK)
        write_string_to_file(buf, *device_model_json);
#endif
    return rv;
}

#ifdef IOTCS_STORAGE_SUPPORT
    iotcs_result iotcs_virtual_device_set_uri_object(iotcs_virtual_device_handle virtual_device_handle,
                const char *attribute_name, struct iotcs_uri_object_t *value) {
        iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
        dm_attribute_value_t* attr;
        int idx = -1;

        if (!virtual_device_handle || !attribute_name) {
            return IOTCS_RESULT_INVALID_ARGUMENT;
        }

        if ((idx = get_attribute_idx(device_handle->model_handle, attribute_name)) < 0) {
            return IOTCS_RESULT_INVALID_ARGUMENT;
        }
        
        attr = &(device_handle->attributes[idx]);
        dm_free_uri_object(attr->value.uri_object);
        attr->value.uri_object = value;
        value->ref_cnt++;

        attr->is_dirty = IOTCS_TRUE;
        attr->is_filled = IOTCS_TRUE;
        return IOTCS_RESULT_OK;
    }

    iotcs_result iotcs_virtual_device_set_storage_object(iotcs_virtual_device_handle virtual_device_handle,
                const char *attribute_name, iotcs_storage_object_handle value) {
        iotcs_virtual_device_handle_t* device_handle = (iotcs_virtual_device_handle_t*) virtual_device_handle;
        if (!IS_STORAGE_OBJECT(value)) {
            return IOTCS_RESULT_INVALID_ARGUMENT;
        }
        DM_LOCK();
        iotcs_result rv = iotcs_virtual_device_set_uri_object(virtual_device_handle, attribute_name, value);
        if (rv == IOTCS_RESULT_OK) {
            ((sd_storage_object *)value->object)
                        ->device_handle = (struct iotcs_virtual_device_handle_t*)device_handle;
            rv = cl_storage_object_sync(value);
        }

        DM_UNLOCK();
        return rv;
    }

    iotcs_result iotcs_virtual_device_set_external_object(iotcs_virtual_device_handle virtual_device_handle,
            const char *attribute_name, iotcs_external_object_handle value) {
        if (!IS_EXTERNAL_OBJECT(value)) {
            return IOTCS_RESULT_INVALID_ARGUMENT;
        }
        DM_LOCK();
        iotcs_result rv = iotcs_virtual_device_set_uri_object(virtual_device_handle, attribute_name, value);
        DM_UNLOCK();
        return rv;
    }
#endif
