text: /*
   * Copyright (c) 2015 ARM Limited. All rights reserved.
   * SPDX-License-Identifier: Apache-2.0
   * Licensed under the Apache License, Version 2.0 (the License); you may
   * not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an AS IS BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  // Needed for PRIu64 on FreeRTOS
  #include <stdio.h>
  // Note: this macro is needed on armcc to get the the limit macros like UINT16_MAX
  #ifndef __STDC_LIMIT_MACROS
  #define __STDC_LIMIT_MACROS
  #endif
  
  // Note: this macro is needed on armcc to get the the PRI*32 macros
  // from inttypes.h in a C++ code.
  #ifndef __STDC_FORMAT_MACROS
  #define __STDC_FORMAT_MACROS
  #endif
  
  #include "include/m2mtlvdeserializer.h"
  #include "mbed-client/m2mconstants.h"
  #include "mbed-trace/mbed_trace.h"
  #include "common_functions.h"
  
  #define TRACE_GROUP "mClt"
  #define BUFFER_SIZE 10
  
  bool M2MTLVDeserializer::is_object_instance(const uint8_t *tlv)
  {
      return is_object_instance(tlv, 0);
  }
  
  bool M2MTLVDeserializer::is_resource(const uint8_t *tlv)
  {
      return is_resource(tlv, 0);
  }
  
  bool M2MTLVDeserializer::is_multiple_resource(const uint8_t *tlv)
  {
      return is_multiple_resource(tlv, 0);
  }
  
  bool M2MTLVDeserializer::is_resource_instance(const uint8_t *tlv)
  {
      return is_resource_instance(tlv, 0);
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialise_object_instances(const uint8_t* tlv,
                                                                             uint32_t tlv_size,
                                                                             M2MObject &object,
                                                                             M2MTLVDeserializer::Operation operation)
  {
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      if (is_object_instance(tlv) ) {
          tr_debug("M2MTLVDeserializer::deserialise_object_instances");
          error = deserialize_object_instances(tlv, tlv_size, 0, object,operation,false);
          if(M2MTLVDeserializer::None == error) {
              error = deserialize_object_instances(tlv, tlv_size, 0, object,operation,true);
          }
      } else {
          tr_debug("M2MTLVDeserializer::deserialise_object_instances ::NotValid");
          error = M2MTLVDeserializer::NotValid;
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_resources(const uint8_t *tlv,
                                                                      uint32_t tlv_size,
                                                                      M2MObjectInstance &object_instance,
                                                                      M2MTLVDeserializer::Operation operation)
  {
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      if (!is_resource(tlv) && !is_multiple_resource(tlv)) {
          error = M2MTLVDeserializer::NotValid;
      } else {
          error = deserialize_resources(tlv, tlv_size, 0, object_instance, operation,false);
          if(M2MTLVDeserializer::None == error) {
              if (M2MTLVDeserializer::Put == operation) {
                  remove_resources(tlv, tlv_size, object_instance, 0);
              }
              error = deserialize_resources(tlv, tlv_size, 0, object_instance, operation,true);
          }
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_resource_instances(const uint8_t *tlv,
                                                                               uint32_t tlv_size,
                                                                               M2MResource &resource,
                                                                               M2MTLVDeserializer::Operation operation)
  {
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      if (!is_multiple_resource(tlv)) {
          error = M2MTLVDeserializer::NotValid;
      } else {
          tr_debug("M2MTLVDeserializer::deserialize_resource_instances()");
          uint8_t offset = 2;
  
          ((tlv[0] & 0x20) == 0) ? offset : offset++;
  
          uint8_t length = tlv[0] & 0x18;
          if(length == 0x08) {
              offset += 1;
          } else if(length == 0x10) {
              offset += 2;
          } else if(length == 0x18) {
              offset += 3;
          }
  
          tr_debug("M2MTLVDeserializer::deserialize_resource_instances() Offset %d", offset);
          error = deserialize_resource_instances(tlv, tlv_size, offset, resource, operation,false);
          if(M2MTLVDeserializer::None == error) {
              if (M2MTLVDeserializer::Put == operation) {
                  remove_resource_instances(tlv, tlv_size, resource, offset);
              }
              error = deserialize_resource_instances(tlv, tlv_size, offset, resource, operation,true);
          }
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_object_instances(const uint8_t *tlv,
                                                                             uint32_t tlv_size,
                                                                             uint32_t offset,
                                                                             M2MObject &object,
                                                                             M2MTLVDeserializer::Operation operation,
                                                                             bool update_value)
  {
      tr_debug("M2MTLVDeserializer::deserialize_object_instances()");
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      TypeIdLength til(tlv, offset);
      til.deserialize();
      offset = til._offset;
  
      const M2MObjectInstanceList &list = object.instances();
      M2MObjectInstanceList::const_iterator it;
      it = list.begin();
  
      if (TYPE_OBJECT_INSTANCE == til._type) {
          for (; it!=list.end(); it++) {
              if((*it)->instance_id() == til._id) {
                  error = deserialize_resources(tlv, tlv_size, offset, (**it),operation, update_value);
              }
          }
          offset += til._length;
  
          if(offset < tlv_size) {
              error = deserialize_object_instances(tlv, tlv_size, offset, object, operation, update_value);
          }
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_resources(const uint8_t *tlv,
                                                                      uint32_t tlv_size,
                                                                      uint32_t offset,
                                                                      M2MObjectInstance &object_instance,
                                                                      M2MTLVDeserializer::Operation operation,
                                                                      bool update_value)
  {
      tr_debug("M2MTLVDeserializer::deserialize_resources()");
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      TypeIdLength til(tlv, offset);
      til.deserialize();
      offset = til._offset;
  
      const M2MResourceList &list = object_instance.resources();
      M2MResourceList::const_iterator it;
      it = list.begin();
  
      bool found = false;
      bool multi = false;
      if (TYPE_RESOURCE == til._type || TYPE_RESOURCE_INSTANCE == til._type) {
          multi = false;
          for (; it!=list.end(); it++) {
              if((*it)->name_id() == til._id){
                  tr_debug("M2MTLVDeserializer::deserialize_resources() - Resource ID %d ", til._id);
                  found = true;
                  if(update_value) {
                      if(til._length > 0) {
                          tr_debug("M2MTLVDeserializer::deserialize_resources() - Update value");
                          if (!set_resource_instance_value((*it), tlv+offset, til._length)) {
                              error = M2MTLVDeserializer::OutOfMemory;
                              break;
                          }
                      } else {
                          tr_debug("M2MTLVDeserializer::deserialize_resources() - Clear Value");
                          (*it)->clear_value();
                      }
                      break;
                  } else if(0 == ((*it)->operation() & M2MBase::PUT_ALLOWED)) {
                      tr_debug("M2MTLVDeserializer::deserialize_resources() - NOT_ALLOWED");
                      error = M2MTLVDeserializer::NotAllowed;
                      break;
                  }
              }
          }
      } else if (TYPE_MULTIPLE_RESOURCE == til._type) {
          multi = true;
          for (; it!=list.end(); it++) {
              if((*it)->supports_multiple_instances() &&
                      (*it)->name_id() == til._id) {
                  found = true;
                  error = deserialize_resource_instances(tlv, tlv_size, offset, (**it), object_instance, operation, update_value);
              }
          }
      } else {
          error = M2MTLVDeserializer::NotValid;
          return error;
      }
  
      if(!found) {
          if(M2MTLVDeserializer::Post == operation) {
              //Create a new Resource
              String id;
              id.append_int(til._id);
              M2MResource *resource = object_instance.create_dynamic_resource(id,"",M2MResourceInstance::OPAQUE,true,multi);
              if(resource) {
                  resource->set_operation(M2MBase::GET_PUT_POST_DELETE_ALLOWED);
              }
              if( TYPE_MULTIPLE_RESOURCE == til._type ) {
                  error = deserialize_resource_instances(tlv, tlv_size, offset, (*resource), object_instance, operation, update_value);
              }
          } else if(M2MTLVDeserializer::Put == operation) {
              error = M2MTLVDeserializer::NotFound;
          }
      }
  
  
      offset += til._length;
  
      if(offset < tlv_size) {
          error = deserialize_resources(tlv, tlv_size, offset, object_instance, operation, update_value);
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_resource_instances(const uint8_t *tlv,
                                                                               uint32_t tlv_size,
                                                                               uint32_t offset,
                                                                               M2MResource &resource,
                                                                               M2MObjectInstance &object_instance,
                                                                               M2MTLVDeserializer::Operation operation,
                                                                               bool update_value)
  {
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      TypeIdLength til(tlv, offset);
      til.deserialize();
      offset = til._offset;
  
      if (TYPE_MULTIPLE_RESOURCE == til._type || TYPE_RESOURCE_INSTANCE == til._type) {
          const M2MResourceInstanceList &list = resource.resource_instances();
          M2MResourceInstanceList::const_iterator it;
          it = list.begin();
          bool found = false;
          for (; it!=list.end(); it++) {
              if((*it)->instance_id() == til._id && TYPE_RESOURCE_INSTANCE == til._type) {
                  found = true;
                  if(update_value) {
                      if(til._length > 0) {
                          if (!set_resource_instance_value((*it), tlv+offset, til._length)) {
                              error = M2MTLVDeserializer::OutOfMemory;
                              break;
                          }
                      } else {
                          (*it)->clear_value();
                      }
                      break;
                  } else if(0 == ((*it)->operation() & M2MBase::PUT_ALLOWED)) {
                      error = M2MTLVDeserializer::NotAllowed;
                      break;
                  }
              }
          }
  
          if(!found) {
              if(M2MTLVDeserializer::Post == operation) {
                  // Create a new Resource Instance
                  M2MResourceInstance *res_instance = object_instance.create_dynamic_resource_instance(resource.name(),"",
                                                                                                   resource.resource_instance_type(),
                                                                                                   true,
                                                                                                   til._id);
                  if(res_instance) {
                      res_instance->set_operation(M2MBase::GET_PUT_POST_DELETE_ALLOWED);
                  }
              } else if(M2MTLVDeserializer::Put == operation) {
                  error = M2MTLVDeserializer::NotFound;
              }
          }
      } else {
          error = M2MTLVDeserializer::NotValid;
          return error;
      }
  
      offset += til._length;
  
      if(offset < tlv_size) {
        error = deserialize_resource_instances(tlv, tlv_size, offset, resource, object_instance, operation, update_value);
      }
      return error;
  }
  
  M2MTLVDeserializer::Error M2MTLVDeserializer::deserialize_resource_instances(const uint8_t *tlv,
                                                                               uint32_t tlv_size,
                                                                               uint32_t offset,
                                                                               M2MResource &resource,
                                                                               M2MTLVDeserializer::Operation operation,
                                                                               bool update_value)
  {
      M2MTLVDeserializer::Error error = M2MTLVDeserializer::None;
      TypeIdLength til(tlv, offset);
      til.deserialize();
      offset = til._offset;
  
      if (TYPE_RESOURCE_INSTANCE == til._type) {
          const M2MResourceInstanceList &list = resource.resource_instances();
          M2MResourceInstanceList::const_iterator it;
          it = list.begin();
          bool found = false;
          for (; it!=list.end(); it++) {
              if((*it)->instance_id() == til._id) {
                  found = true;
                  if(update_value) {
                      if(til._length > 0) {
                          if (!set_resource_instance_value((*it),tlv+offset, til._length)) {
                              error = M2MTLVDeserializer::OutOfMemory;
                              break;
                          }
                      } else {
                          (*it)->clear_value();
                      }
                      break;
                  } else if(0 == ((*it)->operation() & M2MBase::PUT_ALLOWED)) {
                      error = M2MTLVDeserializer::NotAllowed;
                      break;
                  }
              }
          }
          if(!found) {
              if(M2MTLVDeserializer::Post == operation) {
                  error = M2MTLVDeserializer::NotAllowed;
              } else if(M2MTLVDeserializer::Put == operation) {
                  error = M2MTLVDeserializer::NotFound;
              }
          }
      } else {
          error = M2MTLVDeserializer::NotValid;
          return error;
      }
  
      offset += til._length;
  
      if(offset < tlv_size) {
           error = deserialize_resource_instances(tlv, tlv_size, offset, resource, operation, update_value);
      }
      return error;
  }
  
  bool M2MTLVDeserializer::is_object_instance(const uint8_t *tlv, uint32_t offset)
  {
      bool ret = false;
      if (tlv) {
          uint8_t value = tlv[offset];
          ret = (TYPE_OBJECT_INSTANCE == (value & TYPE_RESOURCE));
      }
      return ret;
  }
  
  uint16_t M2MTLVDeserializer::instance_id(const uint8_t *tlv)
  {
      TypeIdLength til(tlv, 0);
      til.deserialize();
      uint16_t id = til._id;
      return id;
  }
  
  bool M2MTLVDeserializer::is_resource(const uint8_t *tlv, uint32_t offset)
  {
      bool ret = false;
      if (tlv) {
          ret = (TYPE_RESOURCE == (tlv[offset] & TYPE_RESOURCE));
      }
      return ret;
  }
  
  bool M2MTLVDeserializer::is_multiple_resource(const uint8_t *tlv, uint32_t offset)
  {
      bool ret = false;
      if (tlv) {
          ret = (TYPE_MULTIPLE_RESOURCE == (tlv[offset] & TYPE_RESOURCE));
      }
      return ret;
  }
  
  bool M2MTLVDeserializer::is_resource_instance(const uint8_t *tlv, uint32_t offset)
  {
      bool ret = false;
      if (tlv) {
          ret = (TYPE_RESOURCE_INSTANCE == (tlv[offset] & TYPE_RESOURCE));
      }
      return ret;
  }
  
  bool M2MTLVDeserializer::set_resource_instance_value(M2MResourceBase *res, const uint8_t *tlv, const uint32_t size)
  {
      bool success = true;
      switch (res->resource_instance_type()) {
          case M2MResourceBase::INTEGER:
          case M2MResourceBase::BOOLEAN:
          case M2MResourceBase::TIME:
          {
              int64_t value = String::convert_array_to_integer(tlv, size);
              if (!res->set_value(value)) {
                  success = false;
              }
              break;
          // Todo! implement conversion for other types as well
          }
          case M2MResourceBase::STRING:
          case M2MResourceBase::OPAQUE:
          case M2MResourceBase::OBJLINK:
              if (!res->set_value(tlv, size)) {
                  success = false;
              }
              break;
          case M2MResourceBase::FLOAT:
          {
              uint32_t value = common_read_32_bit(tlv);
              if (!res->set_value_float(*(float*)&value)) {
                  success = false;
              }
              break;
          }
          default:
              success = false;
              break;
      }
  
      return success;
  }
  
  void M2MTLVDeserializer::remove_resources(const uint8_t *tlv,
                                            uint32_t tlv_size,
                                            M2MObjectInstance &object_instance,
                                            uint32_t offset_size)
  {
      tr_debug("M2MTLVDeserializer::remove_resources");
      uint32_t offset = offset_size;
      const M2MResourceList &list = object_instance.resources();
      M2MResourceList::const_iterator it;
  
      it = list.begin();
      for (; it!=list.end();) {
          bool found = false;
          while(offset < tlv_size) {
              TypeIdLength til(tlv, offset);
              til.deserialize();
              offset = til._offset;
              offset += til._length;
              if((*it)->name_id() == til._id){
                  offset = offset_size;
                  found = true;
                  break;
              }
          }
          offset = offset_size;
  
          // Remove resource if not part of the TLV message
          if (!found) {
              tr_debug("M2MTLVDeserializer::remove_resources - remove resource %" PRId32, (*it)->name_id());
              object_instance.remove_resource((*it)->name());
          } else {
              ++it;
          }
      }
  }
  
  void M2MTLVDeserializer::remove_resource_instances(const uint8_t *tlv,
                                            uint32_t tlv_size,
                                            M2MResource &resource,
                                            uint32_t offset_size)
  {
      tr_debug("M2MTLVDeserializer::remove_resource_instances");
      uint32_t offset = offset_size;
      const M2MResourceInstanceList &list = resource.resource_instances();
      M2MResourceInstanceList::const_iterator it;
      it = list.begin();
  
      for (; it!=list.end();) {
          bool found = false;
          while (offset < tlv_size) {
              TypeIdLength til(tlv, offset);
              til.deserialize();
              offset = til._offset;
              offset += til._length;
              if ((*it)->instance_id() == til._id){
                  offset = offset_size;
                  found = true;
                  break;
              }
          }
          offset = offset_size;
  
          // Remove resource instance if not part of the TLV message
          if (!found) {
              tr_debug("M2MTLVDeserializer::remove_resource_instances - remove resource instance %d", (*it)->instance_id());
              resource.remove_resource_instance((*it)->instance_id());
          } else {
              ++it;
          }
      }
  }
  
  TypeIdLength::TypeIdLength(const uint8_t *tlv, uint32_t offset)
  : _tlv(tlv), _offset(offset), _type(tlv[offset] & 0xC0), _id(0), _length(0)
  {
  }
  
  void TypeIdLength::deserialize()
  {
      uint32_t idLength = _tlv[_offset] & ID16;
      uint32_t lengthType = _tlv[_offset] & LENGTH24;
      if (0 == lengthType) {
          _length = _tlv[_offset] & 0x07;
      }
      _offset++;
  
      deserialiseID(idLength);
      deserialiseLength(lengthType);
  }
  
  void TypeIdLength::deserialiseID(uint32_t idLength)
  {
      _id = _tlv[_offset++] & 0xFF;
      if (ID16 == idLength) {
          _id = (_id << 8) + (_tlv[_offset++] & 0xFF);
      }
  }
  
  void TypeIdLength::deserialiseLength(uint32_t lengthType)
  {
      if (lengthType > 0) {
          _length = _tlv[_offset++] & 0xFF;
      }
      if (lengthType > LENGTH8) {
          _length = (_length << 8) + (_tlv[_offset++] & 0xFF);
      }
      if (lengthType > LENGTH16) {
          _length = (_length << 8) + (_tlv[_offset++] & 0xFF);
      }
  }
  