terence zhang / Mbed OS mbed-os-example-wakaama

Dependencies:   C12832 LM75B

Revision:
3:a280069151ac
Parent:
0:f9d13e09cf11
Child:
15:d0f20339c1ad
--- a/wakaama/management.c	Fri Apr 28 09:53:26 2017 +0000
+++ b/wakaama/management.c	Fri Apr 28 18:13:27 2017 +0800
@@ -14,6 +14,8 @@
  *    David Navarro, Intel Corporation - initial API and implementation
  *    domedambrosio - Please refer to git log
  *    Toby Jaffey - Please refer to git log
+ *    Bosch Software Innovations GmbH - Please refer to git log
+ *    Pascal Rieux - Please refer to git log
  *    
  *******************************************************************************/
 /*
@@ -51,41 +53,215 @@
 
 
 #ifdef LWM2M_CLIENT_MODE
-coap_status_t handle_dm_request(lwm2m_context_t * contextP,
+static int prv_readAttributes(multi_option_t * query,
+                              lwm2m_attributes_t * attrP)
+{
+    int64_t intValue;
+    double floatValue;
+
+    memset(attrP, 0, sizeof(lwm2m_attributes_t));
+
+    while (query != NULL)
+    {
+        if (lwm2m_strncmp((char *)query->data, ATTR_MIN_PERIOD_STR, ATTR_MIN_PERIOD_LEN) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_MIN_PERIOD)) return -1;
+            if (query->len == ATTR_MIN_PERIOD_LEN) return -1;
+
+            if (1 != utils_plainTextToInt64(query->data + ATTR_MIN_PERIOD_LEN, query->len - ATTR_MIN_PERIOD_LEN, &intValue)) return -1;
+            if (intValue < 0) return -1;
+
+            attrP->toSet |= LWM2M_ATTR_FLAG_MIN_PERIOD;
+            attrP->minPeriod = intValue;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_MIN_PERIOD_STR, ATTR_MIN_PERIOD_LEN - 1) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_MIN_PERIOD)) return -1;
+            if (query->len != ATTR_MIN_PERIOD_LEN - 1) return -1;
+
+            attrP->toClear |= LWM2M_ATTR_FLAG_MIN_PERIOD;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_MAX_PERIOD_STR, ATTR_MAX_PERIOD_LEN) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_MAX_PERIOD)) return -1;
+            if (query->len == ATTR_MAX_PERIOD_LEN) return -1;
+
+            if (1 != utils_plainTextToInt64(query->data + ATTR_MAX_PERIOD_LEN, query->len - ATTR_MAX_PERIOD_LEN, &intValue)) return -1;
+            if (intValue < 0) return -1;
+
+            attrP->toSet |= LWM2M_ATTR_FLAG_MAX_PERIOD;
+            attrP->maxPeriod = intValue;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_MAX_PERIOD_STR, ATTR_MAX_PERIOD_LEN - 1) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_MAX_PERIOD)) return -1;
+            if (query->len != ATTR_MAX_PERIOD_LEN - 1) return -1;
+
+            attrP->toClear |= LWM2M_ATTR_FLAG_MAX_PERIOD;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_GREATER_THAN_STR, ATTR_GREATER_THAN_LEN) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_GREATER_THAN)) return -1;
+            if (query->len == ATTR_GREATER_THAN_LEN) return -1;
+
+            if (1 != utils_plainTextToFloat64(query->data + ATTR_GREATER_THAN_LEN, query->len - ATTR_GREATER_THAN_LEN, &floatValue)) return -1;
+
+            attrP->toSet |= LWM2M_ATTR_FLAG_GREATER_THAN;
+            attrP->greaterThan = floatValue;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_GREATER_THAN_STR, ATTR_GREATER_THAN_LEN - 1) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_GREATER_THAN)) return -1;
+            if (query->len != ATTR_GREATER_THAN_LEN - 1) return -1;
+
+            attrP->toClear |= LWM2M_ATTR_FLAG_GREATER_THAN;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_LESS_THAN_STR, ATTR_LESS_THAN_LEN) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_LESS_THAN)) return -1;
+            if (query->len == ATTR_LESS_THAN_LEN) return -1;
+
+            if (1 != utils_plainTextToFloat64(query->data + ATTR_LESS_THAN_LEN, query->len - ATTR_LESS_THAN_LEN, &floatValue)) return -1;
+
+            attrP->toSet |= LWM2M_ATTR_FLAG_LESS_THAN;
+            attrP->lessThan = floatValue;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_LESS_THAN_STR, ATTR_LESS_THAN_LEN - 1) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_LESS_THAN)) return -1;
+            if (query->len != ATTR_LESS_THAN_LEN - 1) return -1;
+
+            attrP->toClear |= LWM2M_ATTR_FLAG_LESS_THAN;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_STEP_STR, ATTR_STEP_LEN) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_STEP)) return -1;
+            if (query->len == ATTR_STEP_LEN) return -1;
+
+            if (1 != utils_plainTextToFloat64(query->data + ATTR_STEP_LEN, query->len - ATTR_STEP_LEN, &floatValue)) return -1;
+            if (floatValue < 0) return -1;
+
+            attrP->toSet |= LWM2M_ATTR_FLAG_STEP;
+            attrP->step = floatValue;
+        }
+        else if (lwm2m_strncmp((char *)query->data, ATTR_STEP_STR, ATTR_STEP_LEN - 1) == 0)
+        {
+            if (0 != ((attrP->toSet | attrP->toClear) & LWM2M_ATTR_FLAG_STEP)) return -1;
+            if (query->len != ATTR_STEP_LEN - 1) return -1;
+
+            attrP->toClear |= LWM2M_ATTR_FLAG_STEP;
+        }
+        else return -1;
+
+        query = query->next;
+    }
+
+    return 0;
+}
+
+coap_status_t dm_handleRequest(lwm2m_context_t * contextP,
                                 lwm2m_uri_t * uriP,
-                                void * fromSessionH,
+                                lwm2m_server_t * serverP,
                                 coap_packet_t * message,
                                 coap_packet_t * response)
 {
     coap_status_t result;
+    lwm2m_media_type_t format;
+
+    LOG_ARG("Code: %02X, server status: %s", message->code, STR_STATUS(serverP->status));
+    LOG_URI(uriP);
+
+    if (IS_OPTION(message, COAP_OPTION_CONTENT_TYPE))
+    {
+        format = utils_convertMediaType(message->content_type);
+    }
+    else
+    {
+        format = LWM2M_CONTENT_TLV;
+    }
+
+    if (uriP->objectId == LWM2M_SECURITY_OBJECT_ID)
+    {
+        return COAP_404_NOT_FOUND;
+    }
+
+    if (serverP->status != STATE_REGISTERED
+        && serverP->status != STATE_REG_UPDATE_NEEDED
+        && serverP->status != STATE_REG_FULL_UPDATE_NEEDED
+        && serverP->status != STATE_REG_UPDATE_PENDING)
+    {
+        return COAP_IGNORE;
+    }
+
+    // TODO: check ACL
 
     switch (message->code)
     {
     case COAP_GET:
         {
-            char * buffer = NULL;
-            int length = 0;
+            uint8_t * buffer = NULL;
+            size_t length = 0;
+            int res;
 
-            result = object_read(contextP, uriP, &buffer, &length);
-            if (result == COAP_205_CONTENT)
+            if (IS_OPTION(message, COAP_OPTION_OBSERVE))
             {
-                if (IS_OPTION(message, COAP_OPTION_OBSERVE))
+                lwm2m_data_t * dataP = NULL;
+                int size = 0;
+
+                result = object_readData(contextP, uriP, &size, &dataP);
+                if (COAP_205_CONTENT == result)
                 {
-                    result = handle_observe_request(contextP, uriP, fromSessionH, message, response);
+                    result = observe_handleRequest(contextP, uriP, serverP, size, dataP, message, response);
+                    if (COAP_205_CONTENT == result)
+                    {
+                        res = lwm2m_data_serialize(uriP, size, dataP, &format, &buffer);
+                        if (res < 0)
+                        {
+                            result = COAP_500_INTERNAL_SERVER_ERROR;
+                        }
+                        else
+                        {
+                            length = (size_t)res;
+                            LOG_ARG("Observe Request[/%d/%d/%d]: %.*s\n", uriP->objectId, uriP->instanceId, uriP->resourceId, length, buffer);
+                        }
+                    }
+                    lwm2m_data_free(size, dataP);
                 }
-                if (result == COAP_205_CONTENT)
+            }
+            else if (IS_OPTION(message, COAP_OPTION_ACCEPT)
+                  && message->accept_num == 1
+                  && message->accept[0] == APPLICATION_LINK_FORMAT)
+            {
+                format = LWM2M_CONTENT_LINK;
+                result = object_discover(contextP, uriP, serverP, &buffer, &length);
+            }
+            else
+            {
+                if (IS_OPTION(message, COAP_OPTION_ACCEPT))
                 {
-                    coap_set_payload(response, buffer, length);
-                    // lwm2m_handle_packet will free buffer
+                    format = utils_convertMediaType(message->accept[0]);
                 }
+
+                result = object_read(contextP, uriP, &format, &buffer, &length);
+            }
+            if (COAP_205_CONTENT == result)
+            {
+                coap_set_header_content_type(response, format);
+                coap_set_payload(response, buffer, length);
+                // lwm2m_handle_packet will free buffer
+            }
+            else
+            {
+                lwm2m_free(buffer);
             }
         }
         break;
+
     case COAP_POST:
         {
             if (!LWM2M_URI_IS_SET_INSTANCE(uriP))
             {
-                result = object_create(contextP, uriP, message->payload, message->payload_len);
+                result = object_create(contextP, uriP, format, message->payload, message->payload_len);
                 if (result == COAP_201_CREATED)
                 {
                     //longest uri is /65535/65535 = 12 + 1 (null) chars
@@ -103,18 +279,13 @@
                         break;
                     }
                     coap_set_header_location_path(response, location_path);
+
+                    lwm2m_update_registration(contextP, 0, true);
                 }
             }
             else if (!LWM2M_URI_IS_SET_RESOURCE(uriP))
             {
-                if (object_isInstanceNew(contextP, uriP->objectId, uriP->instanceId))
-                {
-                    result = object_create(contextP, uriP, message->payload, message->payload_len);
-                }
-                else
-                {
-                    result = object_write(contextP, uriP, message->payload, message->payload_len);
-                }
+                result = object_write(contextP, uriP, format, message->payload, message->payload_len);
             }
             else
             {
@@ -122,54 +293,75 @@
             }
         }
         break;
+
     case COAP_PUT:
         {
-            if (LWM2M_URI_IS_SET_INSTANCE(uriP))
+            if (IS_OPTION(message, COAP_OPTION_URI_QUERY))
             {
-                result = object_write(contextP, uriP, message->payload, message->payload_len);
+                lwm2m_attributes_t attr;
+
+                if (0 != prv_readAttributes(message->uri_query, &attr))
+                {
+                    result = COAP_400_BAD_REQUEST;
+                }
+                else
+                {
+                    result = observe_setParameters(contextP, uriP, serverP, &attr);
+                }
+            }
+            else if (LWM2M_URI_IS_SET_INSTANCE(uriP))
+            {
+                result = object_write(contextP, uriP, format, message->payload, message->payload_len);
             }
             else
             {
-                result = BAD_REQUEST_4_00;
+                result = COAP_400_BAD_REQUEST;
             }
         }
         break;
+
     case COAP_DELETE:
         {
-            if (LWM2M_URI_IS_SET_INSTANCE(uriP) && !LWM2M_URI_IS_SET_RESOURCE(uriP))
+            if (!LWM2M_URI_IS_SET_INSTANCE(uriP) || LWM2M_URI_IS_SET_RESOURCE(uriP))
             {
-                result = object_delete(contextP, uriP);
+                result = COAP_400_BAD_REQUEST;
             }
             else
             {
-                result = BAD_REQUEST_4_00;
+                result = object_delete(contextP, uriP);
+                if (result == COAP_202_DELETED)
+                {
+                    lwm2m_update_registration(contextP, 0, true);
+                }
             }
         }
         break;
+
     default:
-        result = BAD_REQUEST_4_00;
+        result = COAP_400_BAD_REQUEST;
         break;
     }
 
     return result;
 }
+
 #endif
 
 #ifdef LWM2M_SERVER_MODE
 
 #define ID_AS_STRING_MAX_LEN 8
 
-static void dm_result_callback(lwm2m_transaction_t * transacP,
+static void prv_resultCallback(lwm2m_transaction_t * transacP,
                                void * message)
 {
     dm_data_t * dataP = (dm_data_t *)transacP->userData;
 
     if (message == NULL)
     {
-        dataP->callback(((lwm2m_client_t*)transacP->peerP)->internalID,
+        dataP->callback(dataP->clientID,
                         &dataP->uri,
                         COAP_503_SERVICE_UNAVAILABLE,
-                        NULL, 0,
+                        LWM2M_CONTENT_TEXT, NULL, 0,
                         dataP->userData);
     }
     else
@@ -187,14 +379,14 @@
             locationString = coap_get_multi_option_as_string(packet->location_path);
             if (locationString == NULL)
             {
-                fprintf(stderr, "Error: coap_get_multi_option_as_string() failed for Location_path option in dm_result_callback()\n");
+                LOG("Error: coap_get_multi_option_as_string() failed for Location_path option in prv_resultCallback()");
                 return;
             }
 
             result = lwm2m_stringToUri(locationString, strlen(locationString), &locationUri);
             if (result == 0)
             {
-                fprintf(stderr, "Error: lwm2m_stringToUri() failed for Location_path option in dm_result_callback()\n");
+                LOG("Error: lwm2m_stringToUri() failed for Location_path option in prv_resultCallback()");
                 lwm2m_free(locationString);
                 return;
             }
@@ -205,9 +397,10 @@
             lwm2m_free(locationString);
         }
 
-        dataP->callback(((lwm2m_client_t*)transacP->peerP)->internalID,
+        dataP->callback(dataP->clientID,
                         &dataP->uri,
                         packet->code,
+                        utils_convertMediaType(packet->content_type),
                         packet->payload,
                         packet->payload_len,
                         dataP->userData);
@@ -215,14 +408,15 @@
     lwm2m_free(dataP);
 }
 
-static int prv_make_operation(lwm2m_context_t * contextP,
-                              uint16_t clientID,
-                              lwm2m_uri_t * uriP,
-                              coap_method_t method,
-                              char * buffer,
-                              int length,
-                              lwm2m_result_callback_t callback,
-                              void * userData)
+static int prv_makeOperation(lwm2m_context_t * contextP,
+                             uint16_t clientID,
+                             lwm2m_uri_t * uriP,
+                             coap_method_t method,
+                             lwm2m_media_type_t format,
+                             uint8_t * buffer,
+                             int length,
+                             lwm2m_result_callback_t callback,
+                             void * userData)
 {
     lwm2m_client_t * clientP;
     lwm2m_transaction_t * transaction;
@@ -231,11 +425,16 @@
     clientP = (lwm2m_client_t *)lwm2m_list_find((lwm2m_list_t *)contextP->clientList, clientID);
     if (clientP == NULL) return COAP_404_NOT_FOUND;
 
-    transaction = transaction_new(method, uriP, contextP->nextMID++, ENDPOINT_CLIENT, (void *)clientP);
-    if (transaction == NULL) return INTERNAL_SERVER_ERROR_5_00;
+    transaction = transaction_new(clientP->sessionH, method, clientP->altPath, uriP, contextP->nextMID++, 4, NULL);
+    if (transaction == NULL) return COAP_500_INTERNAL_SERVER_ERROR;
 
-    if (buffer != NULL)
+    if (method == COAP_GET)
     {
+        coap_set_header_accept(transaction->message, format);
+    }
+    else if (buffer != NULL)
+    {
+        coap_set_header_content_type(transaction->message, format);
         // TODO: Take care of fragmentation
         coap_set_payload(transaction->message, buffer, length);
     }
@@ -249,10 +448,11 @@
             return COAP_500_INTERNAL_SERVER_ERROR;
         }
         memcpy(&dataP->uri, uriP, sizeof(lwm2m_uri_t));
+        dataP->clientID = clientP->internalID;
         dataP->callback = callback;
         dataP->userData = userData;
 
-        transaction->callback = dm_result_callback;
+        transaction->callback = prv_resultCallback;
         transaction->userData = (void *)dataP;
     }
 
@@ -267,19 +467,42 @@
                   lwm2m_result_callback_t callback,
                   void * userData)
 {
-    return prv_make_operation(contextP, clientID, uriP,
-                              COAP_GET, NULL, 0,
-                              callback, userData);
+    lwm2m_client_t * clientP;
+    lwm2m_media_type_t format;
+
+    LOG_ARG("clientID: %d", clientID);
+    LOG_URI(uriP);
+
+    clientP = (lwm2m_client_t *)lwm2m_list_find((lwm2m_list_t *)contextP->clientList, clientID);
+    if (clientP == NULL) return COAP_404_NOT_FOUND;
+
+    if (clientP->supportJSON == true)
+    {
+        format = LWM2M_CONTENT_JSON;
+    }
+    else
+    {
+        format = LWM2M_CONTENT_TLV;
+    }
+
+    return prv_makeOperation(contextP, clientID, uriP,
+                             COAP_GET,
+                             format,
+                             NULL, 0,
+                             callback, userData);
 }
 
 int lwm2m_dm_write(lwm2m_context_t * contextP,
                    uint16_t clientID,
                    lwm2m_uri_t * uriP,
-                   char * buffer,
+                   lwm2m_media_type_t format,
+                   uint8_t * buffer,
                    int length,
                    lwm2m_result_callback_t callback,
                    void * userData)
 {
+    LOG_ARG("clientID: %d, format: %s, length: %d", clientID, STR_MEDIA_TYPE(format), length);
+    LOG_URI(uriP);
     if (!LWM2M_URI_IS_SET_INSTANCE(uriP)
      || length == 0)
     {
@@ -288,14 +511,16 @@
 
     if (LWM2M_URI_IS_SET_RESOURCE(uriP))
     {
-        return prv_make_operation(contextP, clientID, uriP,
-                                  COAP_PUT, buffer, length,
+        return prv_makeOperation(contextP, clientID, uriP,
+                                  COAP_PUT,
+                                  format, buffer, length,
                                   callback, userData);
     }
     else
     {
-        return prv_make_operation(contextP, clientID, uriP,
-                                  COAP_POST, buffer, length,
+        return prv_makeOperation(contextP, clientID, uriP,
+                                  COAP_POST,
+                                  format, buffer, length,
                                   callback, userData);
     }
 }
@@ -303,37 +528,46 @@
 int lwm2m_dm_execute(lwm2m_context_t * contextP,
                      uint16_t clientID,
                      lwm2m_uri_t * uriP,
-                     char * buffer,
+                     lwm2m_media_type_t format,
+                     uint8_t * buffer,
                      int length,
                      lwm2m_result_callback_t callback,
                      void * userData)
 {
+    LOG_ARG("clientID: %d, format: %s, length: %d", clientID, STR_MEDIA_TYPE(format), length);
+    LOG_URI(uriP);
     if (!LWM2M_URI_IS_SET_RESOURCE(uriP))
     {
         return COAP_400_BAD_REQUEST;
     }
 
-    return prv_make_operation(contextP, clientID, uriP,
-                              COAP_POST, buffer, length,
+    return prv_makeOperation(contextP, clientID, uriP,
+                              COAP_POST,
+                              format, buffer, length,
                               callback, userData);
 }
 
 int lwm2m_dm_create(lwm2m_context_t * contextP,
                     uint16_t clientID,
                     lwm2m_uri_t * uriP,
-                    char * buffer,
+                    lwm2m_media_type_t format,
+                    uint8_t * buffer,
                     int length,
                     lwm2m_result_callback_t callback,
                     void * userData)
 {
-    if (LWM2M_URI_IS_SET_RESOURCE(uriP)
+    LOG_ARG("clientID: %d, format: %s, length: %d", clientID, STR_MEDIA_TYPE(format), length);
+    LOG_URI(uriP);
+
+    if (LWM2M_URI_IS_SET_INSTANCE(uriP)
      || length == 0)
     {
         return COAP_400_BAD_REQUEST;
     }
 
-    return prv_make_operation(contextP, clientID, uriP,
-                              COAP_POST, buffer, length,
+    return prv_makeOperation(contextP, clientID, uriP,
+                              COAP_POST,
+                              format, buffer, length,
                               callback, userData);
 }
 
@@ -343,14 +577,201 @@
                     lwm2m_result_callback_t callback,
                     void * userData)
 {
+    LOG_ARG("clientID: %d", clientID);
+    LOG_URI(uriP);
     if (!LWM2M_URI_IS_SET_INSTANCE(uriP)
      || LWM2M_URI_IS_SET_RESOURCE(uriP))
     {
         return COAP_400_BAD_REQUEST;
     }
 
-    return prv_make_operation(contextP, clientID, uriP,
-                              COAP_DELETE, NULL, 0,
+    return prv_makeOperation(contextP, clientID, uriP,
+                              COAP_DELETE,
+                              LWM2M_CONTENT_TEXT, NULL, 0,
                               callback, userData);
 }
+
+int lwm2m_dm_write_attributes(lwm2m_context_t * contextP,
+                              uint16_t clientID,
+                              lwm2m_uri_t * uriP,
+                              lwm2m_attributes_t * attrP,
+                              lwm2m_result_callback_t callback,
+                              void * userData)
+{
+#define _PRV_BUFFER_SIZE 32
+    lwm2m_client_t * clientP;
+    lwm2m_transaction_t * transaction;
+    coap_packet_t * coap_pkt;
+    uint8_t buffer[_PRV_BUFFER_SIZE];
+    size_t length;
+
+    LOG_ARG("clientID: %d", clientID);
+    LOG_URI(uriP);
+    if (attrP == NULL) return COAP_400_BAD_REQUEST;
+
+    if (0 != (attrP->toSet & attrP->toClear)) return COAP_400_BAD_REQUEST;
+    if (0 != (attrP->toSet & ATTR_FLAG_NUMERIC) && !LWM2M_URI_IS_SET_RESOURCE(uriP)) return COAP_400_BAD_REQUEST;
+    if (ATTR_FLAG_NUMERIC == (attrP->toSet & ATTR_FLAG_NUMERIC)
+     && (attrP->lessThan + 2 * attrP->step >= attrP->greaterThan)) return COAP_400_BAD_REQUEST;
+
+    clientP = (lwm2m_client_t *)lwm2m_list_find((lwm2m_list_t *)contextP->clientList, clientID);
+    if (clientP == NULL) return COAP_404_NOT_FOUND;
+
+    transaction = transaction_new(clientP->sessionH, COAP_PUT, clientP->altPath, uriP, contextP->nextMID++, 4, NULL);
+    if (transaction == NULL) return COAP_500_INTERNAL_SERVER_ERROR;
+
+    if (callback != NULL)
+    {
+        dm_data_t * dataP;
+
+        dataP = (dm_data_t *)lwm2m_malloc(sizeof(dm_data_t));
+        if (dataP == NULL)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        memcpy(&dataP->uri, uriP, sizeof(lwm2m_uri_t));
+        dataP->clientID = clientP->internalID;
+        dataP->callback = callback;
+        dataP->userData = userData;
+
+        transaction->callback = prv_resultCallback;
+        transaction->userData = (void *)dataP;
+    }
+
+    coap_pkt = (coap_packet_t *)transaction->message;
+    free_multi_option(coap_pkt->uri_query);
+    if (attrP->toSet & LWM2M_ATTR_FLAG_MIN_PERIOD)
+    {
+        memcpy(buffer, ATTR_MIN_PERIOD_STR, ATTR_MIN_PERIOD_LEN);
+        length = utils_intToText(attrP->minPeriod, buffer + ATTR_MIN_PERIOD_LEN, _PRV_BUFFER_SIZE - ATTR_MIN_PERIOD_LEN);
+        if (length == 0)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        coap_add_multi_option(&(coap_pkt->uri_query), buffer, ATTR_MIN_PERIOD_LEN + length, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toSet & LWM2M_ATTR_FLAG_MAX_PERIOD)
+    {
+        memcpy(buffer, ATTR_MAX_PERIOD_STR, ATTR_MAX_PERIOD_LEN);
+        length = utils_intToText(attrP->maxPeriod, buffer + ATTR_MAX_PERIOD_LEN, _PRV_BUFFER_SIZE - ATTR_MAX_PERIOD_LEN);
+        if (length == 0)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        coap_add_multi_option(&(coap_pkt->uri_query), buffer, ATTR_MAX_PERIOD_LEN + length, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toSet & LWM2M_ATTR_FLAG_GREATER_THAN)
+    {
+        memcpy(buffer, ATTR_GREATER_THAN_STR, ATTR_GREATER_THAN_LEN);
+        length = utils_floatToText(attrP->greaterThan, buffer + ATTR_GREATER_THAN_LEN, _PRV_BUFFER_SIZE - ATTR_GREATER_THAN_LEN);
+        if (length == 0)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        coap_add_multi_option(&(coap_pkt->uri_query), buffer, ATTR_GREATER_THAN_LEN + length, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toSet & LWM2M_ATTR_FLAG_LESS_THAN)
+    {
+        memcpy(buffer, ATTR_LESS_THAN_STR, ATTR_LESS_THAN_LEN);
+        length = utils_floatToText(attrP->lessThan, buffer + ATTR_LESS_THAN_LEN, _PRV_BUFFER_SIZE - ATTR_LESS_THAN_LEN);
+        if (length == 0)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        coap_add_multi_option(&(coap_pkt->uri_query), buffer, ATTR_LESS_THAN_LEN + length, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toSet & LWM2M_ATTR_FLAG_STEP)
+    {
+        memcpy(buffer, ATTR_STEP_STR, ATTR_STEP_LEN);
+        length = utils_floatToText(attrP->step, buffer + ATTR_STEP_LEN, _PRV_BUFFER_SIZE - ATTR_STEP_LEN);
+        if (length == 0)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        coap_add_multi_option(&(coap_pkt->uri_query), buffer, ATTR_STEP_LEN + length, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toClear & LWM2M_ATTR_FLAG_MIN_PERIOD)
+    {
+        coap_add_multi_option(&(coap_pkt->uri_query), ATTR_MIN_PERIOD_STR, ATTR_MIN_PERIOD_LEN -1, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toClear & LWM2M_ATTR_FLAG_MAX_PERIOD)
+    {
+        coap_add_multi_option(&(coap_pkt->uri_query), ATTR_MAX_PERIOD_STR, ATTR_MAX_PERIOD_LEN - 1, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toClear & LWM2M_ATTR_FLAG_GREATER_THAN)
+    {
+        coap_add_multi_option(&(coap_pkt->uri_query), ATTR_GREATER_THAN_STR, ATTR_GREATER_THAN_LEN - 1, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toClear & LWM2M_ATTR_FLAG_LESS_THAN)
+    {
+        coap_add_multi_option(&(coap_pkt->uri_query), ATTR_LESS_THAN_STR, ATTR_LESS_THAN_LEN - 1, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+    if (attrP->toClear & LWM2M_ATTR_FLAG_STEP)
+    {
+        coap_add_multi_option(&(coap_pkt->uri_query), ATTR_STEP_STR, ATTR_STEP_LEN - 1, 0);
+        SET_OPTION(coap_pkt, COAP_OPTION_URI_QUERY);
+    }
+
+    contextP->transactionList = (lwm2m_transaction_t *)LWM2M_LIST_ADD(contextP->transactionList, transaction);
+
+    return transaction_send(contextP, transaction);
+}
+
+int lwm2m_dm_discover(lwm2m_context_t * contextP,
+                      uint16_t clientID,
+                      lwm2m_uri_t * uriP,
+                      lwm2m_result_callback_t callback,
+                      void * userData)
+{
+    lwm2m_client_t * clientP;
+    lwm2m_transaction_t * transaction;
+    dm_data_t * dataP;
+
+    LOG_ARG("clientID: %d", clientID);
+    LOG_URI(uriP);
+    clientP = (lwm2m_client_t *)lwm2m_list_find((lwm2m_list_t *)contextP->clientList, clientID);
+    if (clientP == NULL) return COAP_404_NOT_FOUND;
+
+    transaction = transaction_new(clientP->sessionH, COAP_GET, clientP->altPath, uriP, contextP->nextMID++, 4, NULL);
+    if (transaction == NULL) return COAP_500_INTERNAL_SERVER_ERROR;
+
+    coap_set_header_accept(transaction->message, LWM2M_CONTENT_LINK);
+
+    if (callback != NULL)
+    {
+        dataP = (dm_data_t *)lwm2m_malloc(sizeof(dm_data_t));
+        if (dataP == NULL)
+        {
+            transaction_free(transaction);
+            return COAP_500_INTERNAL_SERVER_ERROR;
+        }
+        memcpy(&dataP->uri, uriP, sizeof(lwm2m_uri_t));
+        dataP->clientID = clientP->internalID;
+        dataP->callback = callback;
+        dataP->userData = userData;
+
+        transaction->callback = prv_resultCallback;
+        transaction->userData = (void *)dataP;
+    }
+
+    contextP->transactionList = (lwm2m_transaction_t *)LWM2M_LIST_ADD(contextP->transactionList, transaction);
+
+    return transaction_send(contextP, transaction);
+}
+
 #endif