Example
Dependencies: FXAS21002 FXOS8700Q
Diff: simple-mbed-cloud-client/mbed-cloud-client/mbed-client/mbed-client-classic/source/m2mconnectionhandlerpimpl.cpp
- Revision:
- 0:11cc2b7889af
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/simple-mbed-cloud-client/mbed-cloud-client/mbed-client/mbed-client-classic/source/m2mconnectionhandlerpimpl.cpp Tue Nov 19 09:49:38 2019 +0000 @@ -0,0 +1,974 @@ +/* + * Copyright (c) 2015 - 2017 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. + */ + +// fixup the compilation on ARMCC for PRIu32 +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#include "mbed-client-classic/m2mconnectionhandlerpimpl.h" +#include "mbed-client/m2mconnectionobserver.h" +#include "mbed-client/m2mconstants.h" +#include "mbed-client/m2msecurity.h" +#include "mbed-client/m2mconnectionhandler.h" + +#include "pal.h" + +#include "eventOS_scheduler.h" + +#include "eventOS_event_timer.h" + +#include "mbed-trace/mbed_trace.h" + +#include <stdlib.h> // free() and malloc() + +#define TRACE_GROUP "mClt" + +#ifndef MBED_CONF_MBED_CLIENT_TLS_MAX_RETRY +#define MBED_CONF_MBED_CLIENT_TLS_MAX_RETRY 60 +#endif + +#if (PAL_DNS_API_VERSION == 1) && defined(TARGET_LIKE_MBED) +#error "For async PAL DNS only API v2 or greater is supported on Mbed." +#endif + +int8_t M2MConnectionHandlerPimpl::_tasklet_id = -1; + +// This is called from event loop, but as it is static C function, this is just a wrapper +// which calls C++ on the instance. +extern "C" void eventloop_event_handler(arm_event_s *event) +{ + tr_debug("M2MConnectionHandlerPimpl::eventloop_event_handler %d", event->event_type); + if (event->event_type != M2MConnectionHandlerPimpl::ESocketIdle) { + if(!event->data_ptr) { + tr_error("M2MConnectionHandlerPimpl::eventloop_event_handler event->data_ptr=NULL !!!!"); + assert(event->data_ptr); + } + M2MConnectionHandlerPimpl* instance = (M2MConnectionHandlerPimpl*)event->data_ptr; + instance->event_handler(event); + } +} + +// event handler that forwards the event according to its type and/or connection state +void M2MConnectionHandlerPimpl::event_handler(arm_event_s *event) +{ + switch (event->event_type) { + + // Event from socket callback method + case M2MConnectionHandlerPimpl::ESocketCallback: + case M2MConnectionHandlerPimpl::ESocketTimerCallback: + + // this will enable sending more events during this event processing, but that is less evil than missing one + _suppressable_event_in_flight = false; + + if (_socket_state == M2MConnectionHandlerPimpl::ESocketStateHandshaking) { + receive_handshake_handler(); + } else if ((_socket_state == M2MConnectionHandlerPimpl::ESocketStateUnsecureConnection) || + (_socket_state == M2MConnectionHandlerPimpl::ESocketStateSecureConnection)) { + // the connection is established + receive_handler(); + } else { + socket_connect_handler(); + } + + // Receive processing could have changed state, so recheck + if ((_socket_state == M2MConnectionHandlerPimpl::ESocketStateUnsecureConnection) || + (_socket_state == M2MConnectionHandlerPimpl::ESocketStateSecureConnection)) { + // the connection is established + send_socket_data(); + } + break; + + // Data send request from client side + case M2MConnectionHandlerPimpl::ESocketSend: + send_socket_data(); + break; + + // DNS resolved successfully + case M2MConnectionHandlerPimpl::ESocketDnsResolved: + handle_dns_result(true); + break; + + // DNS resolving failed + case M2MConnectionHandlerPimpl::ESocketDnsError: + handle_dns_result(false); + break; + + // Establish the connection by connecting the socket + case M2MConnectionHandlerPimpl::ESocketConnect: + socket_connect_handler(); + break; + + case M2MConnectionHandlerPimpl::ESocketClose: + close_socket(); + break; + + default: + tr_debug("M2MConnectionHandlerPimpl::connection_event_handler: default type: %d", (int)event->event_type); + break; + } +} + +// This callback is used from PAL sockets, it is called with object instance as argument. +// This is received from "some" socket event from "some" socket and the C++ side is responsible +// of forwarding it or ignoring the event completely. +extern "C" void socket_event_handler(void* arg) +{ + M2MConnectionHandlerPimpl* instance = (M2MConnectionHandlerPimpl*)arg; + + if (!instance) { + tr_error("Invalid callback argument"); + return; + } + + instance->send_socket_event(M2MConnectionHandlerPimpl::ESocketCallback); +} + +void M2MConnectionHandlerPimpl::send_socket_event(SocketEvent event_type) +{ + // the socket callback events can safely be suppressed, the receiving end must tolerate that + if (event_type == ESocketCallback) { + // only the socket connected state supports retries somehow + if (_suppressable_event_in_flight == false) { + _suppressable_event_in_flight = true; + } else { + // XXX: DO NOT ADD FOLLOWING LINE TO OFFICIAL GIT, THIS WILL KILL SOME NETWORK STACKS + // IF EVENT IS SENT FROM A INTERRUPT CONTEXT + // tr_debug("** SKIPPING event"); + return; + } + } + + if (!send_event(event_type)) { + // TODO: give a proper error based on state instead of this + _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true); + } +} + +M2MConnectionHandlerPimpl::M2MConnectionHandlerPimpl(M2MConnectionHandler* base, M2MConnectionObserver &observer, + M2MConnectionSecurity* sec, + M2MInterface::BindingMode mode, + M2MInterface::NetworkStack stack) +:_base(base), + _observer(observer), + _security_impl(sec), + _security(NULL), + _binding_mode(mode), + _socket(0), + _server_type(M2MConnectionObserver::LWM2MServer), + _server_port(0), + _listen_port(0), + _net_iface(0), +#if (PAL_DNS_API_VERSION == 0) || (PAL_DNS_API_VERSION == 1) + _socket_address_len(0), +#elif (PAL_DNS_API_VERSION == 2) + _handler_async_DNS(0), +#endif + _socket_state(ESocketStateDisconnected), + _handshake_retry(0), + _suppressable_event_in_flight(false), + _secure_connection(false) +{ +#ifndef PAL_NET_TCP_AND_TLS_SUPPORT + if (is_tcp_connection()) { + tr_error("ConnectionHandler: TCP support not available."); + return; + } +#endif + + if (PAL_SUCCESS != pal_init()) { + tr_error("PAL init failed."); + } + + memset(&_address, 0, sizeof _address); + memset((void*)&_socket_address, 0, sizeof _socket_address); + memset(&_ipV4Addr, 0, sizeof(palIpV4Addr_t)); + memset(&_ipV6Addr, 0, sizeof(palIpV6Addr_t)); + ns_list_init(&_linked_list_send_data); + + eventOS_scheduler_mutex_wait(); + if (M2MConnectionHandlerPimpl::_tasklet_id == -1) { + M2MConnectionHandlerPimpl::_tasklet_id = eventOS_event_handler_create(&eventloop_event_handler, ESocketIdle); + } + eventOS_scheduler_mutex_release(); +} + +M2MConnectionHandlerPimpl::~M2MConnectionHandlerPimpl() +{ + tr_debug("~M2MConnectionHandlerPimpl()"); +#if (PAL_DNS_API_VERSION == 2) + if ( _handler_async_DNS > 0) { + pal_cancelAddressInfoAsync(_handler_async_DNS); + } +#endif + + close_socket(); + delete _security_impl; + _security_impl = NULL; + pal_destroy(); + tr_debug("~M2MConnectionHandlerPimpl() - OUT"); +} + +bool M2MConnectionHandlerPimpl::bind_connection(const uint16_t listen_port) +{ + _listen_port = listen_port; + return true; +} + +bool M2MConnectionHandlerPimpl::send_event(SocketEvent event_type) +{ + arm_event_s event = {0}; + + event.receiver = M2MConnectionHandlerPimpl::_tasklet_id; + event.sender = 0; + event.event_type = event_type; + event.data_ptr = this; + event.event_data = 0; + event.priority = ARM_LIB_HIGH_PRIORITY_EVENT; + return !eventOS_event_send(&event); +} + + +// This callback is used from PAL pal_getAddressInfoAsync, +#if (PAL_DNS_API_VERSION == 2) +extern "C" void address_resolver_cb(const char* url, palSocketAddress_t* address, palStatus_t status, void* callbackArgument) +{ + tr_debug("M2MConnectionHandlerPimpl::address_resolver callback"); + M2MConnectionHandlerPimpl* instance = (M2MConnectionHandlerPimpl*)callbackArgument; + + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::address_resolver callback failed with %" PRIx32, status); + if (!(instance->send_event(M2MConnectionHandlerPimpl::ESocketDnsError))) { + tr_error("M2MConnectionHandlerPimpl::address_resolver callback, error event alloc fail."); + } + } else { + if (!(instance->send_event(M2MConnectionHandlerPimpl::ESocketDnsResolved))) { + tr_error("M2MConnectionHandlerPimpl::address_resolver callback, resolved event alloc fail."); + } + } +} +#endif + +bool M2MConnectionHandlerPimpl::address_resolver(void) +{ + palStatus_t status; + bool ret = false; + +#if (PAL_DNS_API_VERSION == 2) + tr_debug("M2MConnectionHandlerPimpl::address_resolver:asynchronous DNS"); + _handler_async_DNS = 0; + status = pal_getAddressInfoAsync(_server_address.c_str(), (palSocketAddress_t*)&_socket_address, &address_resolver_cb, this, &_handler_async_DNS); + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::address_resolver, pal_getAddressInfoAsync fail. %" PRIx32, status); + _observer.socket_error(M2MConnectionHandler::DNS_RESOLVING_ERROR); + } + else { + ret = true; + } +#else // #if (PAL_DNS_API_VERSION == 0) + tr_debug("M2MConnectionHandlerPimpl::address_resolver:synchronous DNS"); + status = pal_getAddressInfo(_server_address.c_str(), (palSocketAddress_t*)&_socket_address, &_socket_address_len); + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::getAddressInfo failed with %" PRIx32, status); + if (!send_event(ESocketDnsError)) { + tr_error("M2MConnectionHandlerPimpl::address_resolver, error event alloc fail."); + } + } else { + if (!send_event(ESocketDnsResolved)) { + tr_error("M2MConnectionHandlerPimpl::address_resolver, resolved event alloc fail."); + } + else { + ret = true; + } + } +#endif + return ret; +} + +void M2MConnectionHandlerPimpl::handle_dns_result(bool success) +{ +#if (PAL_DNS_API_VERSION == 2) + _handler_async_DNS = 0; +#endif + if (_socket_state != ESocketStateDNSResolving) { + tr_warn("M2MConnectionHandlerPimpl::handle_dns_result() called, not in ESocketStateDNSResolving state!"); + return; + } + + if (success) { + _socket_state = EsocketStateInitializeConnection; + socket_connect_handler(); + + } else { + _observer.socket_error(M2MConnectionHandler::DNS_RESOLVING_ERROR); + } +} + +bool M2MConnectionHandlerPimpl::resolve_server_address(const String& server_address, + const uint16_t server_port, + M2MConnectionObserver::ServerType server_type, + const M2MSecurity* security) +{ +#if (PAL_DNS_API_VERSION == 2) + if ( _handler_async_DNS > 0) { + if (pal_cancelAddressInfoAsync(_handler_async_DNS) != PAL_SUCCESS) { + return false; + } + } +#endif + _socket_state = ESocketStateDNSResolving; + _security = security; + + int32_t security_instance_id = _security->get_security_instance_id(M2MSecurity::M2MServer); + if (server_type == M2MConnectionObserver::Bootstrap) { + security_instance_id = _security->get_security_instance_id(M2MSecurity::Bootstrap); + } + + if (_security && + security_instance_id >= 0 && + (_security->resource_value_int(M2MSecurity::SecurityMode, security_instance_id) == M2MSecurity::Certificate || + _security->resource_value_int(M2MSecurity::SecurityMode, security_instance_id) == M2MSecurity::Psk)) { + _secure_connection = true; + } + + _server_port = server_port; + _server_type = server_type; + _server_address = server_address; + + + return address_resolver(); +} + +void M2MConnectionHandlerPimpl::socket_connect_handler() +{ + palStatus_t status; + int32_t security_instance_id = _security->get_security_instance_id(M2MSecurity::M2MServer); + if (_server_type == M2MConnectionObserver::Bootstrap) { + security_instance_id = _security->get_security_instance_id(M2MSecurity::Bootstrap); + } + + tr_debug("M2MConnectionHandlerPimpl::socket_connect_handler - _socket_state = %d", _socket_state); + + switch (_socket_state) { + case ESocketStateCloseBeingCalled: + case ESocketStateDNSResolving: + case ESocketStateDisconnected: + case ESocketStateHandshaking: + case ESocketStateUnsecureConnection: + case ESocketStateSecureConnection: + // Ignore these events + break; + + case EsocketStateInitializeConnection: + + // Initialize the socket to stable state + close_socket(); + + status = pal_setSockAddrPort((palSocketAddress_t*)&_socket_address, _server_port); + + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - setSockAddrPort err: %" PRIx32, status); + } else { + tr_debug("address family: %d", (int)_socket_address.addressType); + } + + if (_socket_address.addressType == PAL_AF_INET) { + status = pal_getSockAddrIPV4Addr((palSocketAddress_t*)&_socket_address,_ipV4Addr); + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - sockAddr4, err: %" PRIx32, status); + _observer.socket_error(M2MConnectionHandler::DNS_RESOLVING_ERROR); + return; + } + + tr_info("M2MConnectionHandlerPimpl::socket_connect_handler - IPv4 Address %d.%d.%d.%d", + _ipV4Addr[0], _ipV4Addr[1], _ipV4Addr[2], _ipV4Addr[3]); + + _address._address = (void*)_ipV4Addr; + _address._length = PAL_IPV4_ADDRESS_SIZE; + _address._port = _server_port; + } else if (_socket_address.addressType == PAL_AF_INET6) { + status = pal_getSockAddrIPV6Addr((palSocketAddress_t*)&_socket_address,_ipV6Addr); + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - sockAddr6, err: %" PRIx32, status); + _observer.socket_error(M2MConnectionHandler::DNS_RESOLVING_ERROR); + return; + } + + tr_info("M2MConnectionHandlerPimpl::socket_connect_handler - IPv6 Address: %s", mbed_trace_ipv6(_ipV6Addr)); + + _address._address = (void*)_ipV6Addr; + _address._length = PAL_IPV6_ADDRESS_SIZE; + _address._port = _server_port; + } else { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - socket config error, stack: %d", (int)_socket_address.addressType); + _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT); + return; + } + + if (!init_socket()) { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - socket init error"); + // The init_socket() calls the socket_error() -callback directly, so it must not be + // done here too. + return; + } + + // This state was used to ignore the spurious events _during_ the call of non-blocking pal_connect(). + // Now that we just retry connect when it is not yet succeeded anyway this state might be removed completely. + _socket_state = ESocketStateConnectBeingCalled; + + // fall through is intentional + case ESocketStateConnectBeingCalled: + case ESocketStateConnecting: + if (is_tcp_connection()) { +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + tr_info("M2MConnectionHandlerPimpl::socket_connect_handler - Using TCP"); + + status = pal_connect(_socket, (palSocketAddress_t*)&_socket_address, sizeof(_socket_address)); + + if ((status == PAL_ERR_SOCKET_IN_PROGRES) || (status == PAL_ERR_SOCKET_WOULD_BLOCK)) { + // In this case the connect is done asynchronously, and the pal_socketMiniSelect() + // will be used to detect the end of connect. + tr_debug("M2MConnectionHandlerPimpl::socket_connect_handler - pal_connect(): %" PRIx32 ", async connect started", status); + // we need to wait for the event + _socket_state = ESocketStateConnecting; + break; + + } else if (status == PAL_SUCCESS || status == PAL_ERR_SOCKET_ALREADY_CONNECTED) { + + tr_debug("M2MConnectionHandlerPimpl::socket_connect_handler - pal_connect(): success"); + _socket_state = ESocketStateConnected; + + } else { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - pal_connect(): failed: %" PRIx32, status); + close_socket(); + _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT); + return; + } +#else + tr_error("socket_connect_handler() - TCP not configured" +#endif //PAL_NET_TCP_AND_TLS_SUPPORT + } else { + tr_info("M2MConnectionHandlerPimpl::socket_connect_handler - Using UDP"); + _socket_state = ESocketStateConnected; + } + + // fall through is a normal flow in case the UDP was used or pal_connect() happened to return immediately with PAL_SUCCESS + case ESocketStateConnected: + if (_security && security_instance_id >= 0) { + if (_secure_connection) { + if ( _security_impl != NULL ) { + _security_impl->reset(); + int ret_code = _security_impl->init(_security, security_instance_id); + if (ret_code == M2MConnectionHandler::ERROR_NONE) { + // Initiate handshake. Perhaps there could be a separate event type for this? + _socket_state = ESocketStateHandshaking; + send_socket_event(ESocketCallback); + } else { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - init failed"); + close_socket(); + + if (ret_code == M2MConnectionHandler::FAILED_TO_READ_CREDENTIALS) { + _observer.socket_error(M2MConnectionHandler::FAILED_TO_READ_CREDENTIALS, false); + } else { + _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, true); + } + + return; + } + } else { + tr_error("M2MConnectionHandlerPimpl::socket_connect_handler - sec is null"); + close_socket(); + _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, true); + return; + } + } + } + if (_socket_state != ESocketStateHandshaking) { + _socket_state = ESocketStateUnsecureConnection; + _observer.address_ready(_address, + _server_type, + _address._port); + } + break; + + } +} + +bool M2MConnectionHandlerPimpl::send_data(uint8_t *data, + uint16_t data_len, + sn_nsdl_addr_s *address) +{ + if (address == NULL || data == NULL || !data_len || _socket_state < ESocketStateUnsecureConnection) { + tr_warn("M2MConnectionHandlerPimpl::send_data() - too early"); + return false; + } + + send_data_queue_s* out_data = (send_data_queue_s*)malloc(sizeof(send_data_queue_s)); + if (!out_data) { + return false; + } + + memset(out_data, 0, sizeof(send_data_queue_s)); + + uint8_t offset = 0; +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + if (is_tcp_connection() && !_secure_connection ) { + offset = 4; + } +#endif + + out_data->data = (uint8_t*)malloc(data_len + offset); + if (!out_data->data) { + free(out_data); + return false; + } + + // TCP non-secure + // We need to "shim" the length in front +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + if (is_tcp_connection() && !_secure_connection ) { + out_data->data[0] = 0; + out_data->data[1] = 0; + out_data->data[2] = (data_len >> 8 ) & 0xff; + out_data->data[3] = data_len & 0xff; + } +#endif //PAL_NET_TCP_AND_TLS_SUPPORT + + memcpy(out_data->data + offset, data, data_len); + out_data->data_len = data_len + offset; + + claim_mutex(); + ns_list_add_to_end(&_linked_list_send_data, out_data); + release_mutex(); + + if (!send_event(ESocketSend)) { + // Event push failed, free the buffer + claim_mutex(); + ns_list_remove(&_linked_list_send_data, out_data); + release_mutex(); + free(out_data->data); + free(out_data); + return false; + } + + return true; +} + +void M2MConnectionHandlerPimpl::send_socket_data() +{ + tr_debug("M2MConnectionHandlerPimpl::send_socket_data()"); + int bytes_sent = 0; + bool success = true; + + send_data_queue_s* out_data = get_item_from_list(); + if (!out_data) { + return; + } + + if (!out_data->data || !out_data->data_len || _socket_state < ESocketStateUnsecureConnection) { + tr_warn("M2MConnectionHandlerPimpl::send_socket_data() - too early"); + add_item_to_list(out_data); + return; + } + + // Loop until all the data is sent + for (; out_data->offset < out_data->data_len; out_data->offset += bytes_sent) { + // Secure send + if (_socket_state == ESocketStateSecureConnection) { + // TODO! Change the send_message API to take bytes_sent as a out param like the pal send API's. + while ((bytes_sent = _security_impl->send_message(out_data->data + out_data->offset, + out_data->data_len - out_data->offset)) <= 0) { + if (bytes_sent == M2MConnectionHandler::CONNECTION_ERROR_WANTS_WRITE) { + // Return and wait the next event + add_item_to_list(out_data); + return; + } + + if (bytes_sent != M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ) { + tr_error("M2MConnectionHandlerPimpl::send_socket_data() - secure, failed %d", bytes_sent); + success = false; + break; + } + } + if (!success) { + break; + } + } + // Unsecure send + else { + bytes_sent = 0; + palStatus_t ret; + if (is_tcp_connection()) { +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + ret = pal_send(_socket, + out_data->data + out_data->offset, + out_data->data_len - out_data->offset, + (size_t*)&bytes_sent); +#endif + } else { + ret = pal_sendTo(_socket, + out_data->data + out_data->offset, + out_data->data_len - out_data->offset, + (palSocketAddress_t*)&_socket_address, + sizeof(_socket_address), + (size_t*)&bytes_sent); + } + if (ret == PAL_ERR_SOCKET_WOULD_BLOCK) { + // Return and wait next event + add_item_to_list(out_data); + return; + } + if (ret < 0) { + tr_error("M2MConnectionHandlerPimpl::send_socket_data() - unsecure failed %" PRIx32, ret); + success = false; + break; + } + } + } + + free(out_data->data); + free(out_data); + + if (!success) { + if (bytes_sent == M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY) { + _observer.socket_error(M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY, true); + } else if (bytes_sent == M2MConnectionHandler::MEMORY_ALLOCATION_FAILED) { + tr_error("M2MConnectionHandlerPimpl::send_socket_data() - memory allocation failed!"); + _handshake_retry = 0; + _observer.socket_error(M2MConnectionHandler::MEMORY_ALLOCATION_FAILED, false); + } else { + tr_error("M2MConnectionHandlerPimpl::send_socket_data() - SOCKET_SEND_ERROR"); + _observer.socket_error(M2MConnectionHandler::SOCKET_SEND_ERROR, true); + } + close_socket(); + } else { + _observer.data_sent(); + } +} + +bool M2MConnectionHandlerPimpl::start_listening_for_data() +{ + return true; +} + +void M2MConnectionHandlerPimpl::stop_listening() +{ + // Do not call close_socket() directly here. + // This can be called from multiple locations. + send_socket_event(ESocketClose); +} + +void M2MConnectionHandlerPimpl::handle_connection_error(int error) +{ + tr_error("M2MConnectionHandlerPimpl::handle_connection_error - error %d", error); + _observer.socket_error(error); +} + +void M2MConnectionHandlerPimpl::set_platform_network_handler(void *handler) +{ + tr_debug("M2MConnectionHandlerPimpl::set_platform_network_handler"); + + if (PAL_SUCCESS != pal_registerNetworkInterface(handler, &_net_iface)) { + tr_error("M2MConnectionHandlerPimpl::set_platform_network_handler - Interface registration failed."); + } + tr_debug("M2MConnectionHandlerPimpl::set_platform_network_handler - index = %d", _net_iface); +} + +void M2MConnectionHandlerPimpl::receive_handshake_handler() +{ + int return_value; + tr_debug("M2MConnectionHandlerPimpl::receive_handshake_handler()"); + + // assert(_socket_state == ESocketStateHandshaking); + + return_value = _security_impl->connect(_base); + + if (return_value == M2MConnectionHandler::ERROR_NONE) { + + _handshake_retry = 0; + _socket_state = ESocketStateSecureConnection; + _observer.address_ready(_address, + _server_type, + _server_port); + + } else if (return_value == M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY) { + _handshake_retry = 0; + _observer.socket_error(M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY, true); + close_socket(); + + } else if (return_value == M2MConnectionHandler::MEMORY_ALLOCATION_FAILED) { + + tr_error("M2MConnectionHandlerPimpl::receive_handshake_handler() - memory allocation failed"); + _handshake_retry = 0; + _observer.socket_error(M2MConnectionHandler::MEMORY_ALLOCATION_FAILED, false); + close_socket(); + + } else if (return_value != M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ) { + + tr_error("M2MConnectionHandlerPimpl::receive_handshake_handler() - SSL_HANDSHAKE_ERROR"); + _handshake_retry = 0; + _observer.socket_error(M2MConnectionHandler::SSL_HANDSHAKE_ERROR, true); + close_socket(); + + } else { + // Comes here only if error is M2MConnectionHandler::ERROR_GENERIC + if (_handshake_retry++ > MBED_CONF_MBED_CLIENT_TLS_MAX_RETRY) { + + tr_error("M2MConnectionHandlerPimpl::receive_handshake_handler() - Max TLS retry fail"); + _handshake_retry = 0; + _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT, true); + close_socket(); + + } + eventOS_event_timer_cancel(ESocketTimerCallback, M2MConnectionHandlerPimpl::_tasklet_id); + + // There is required to set event.data_ptr for eventloop_event_handler. + arm_event_s event = {0}; + event.receiver = M2MConnectionHandlerPimpl::_tasklet_id; + event.sender = 0; + event.event_id = ESocketTimerCallback; + event.event_type = ESocketTimerCallback; + event.data_ptr = this; + event.event_data = 0; + event.priority = ARM_LIB_HIGH_PRIORITY_EVENT; + eventOS_event_timer_request_in(&event, eventOS_event_timer_ms_to_ticks(1000)); + } +} + +bool M2MConnectionHandlerPimpl::is_handshake_ongoing() const +{ + return (_socket_state == ESocketStateHandshaking); +} + +void M2MConnectionHandlerPimpl::receive_handler() +{ + // assert(_socket_state > ESocketStateHandshaking); + + if (_socket_state == ESocketStateSecureConnection) { + + int rcv_size; + unsigned char recv_buffer[BUFFER_LENGTH]; + + // we need to read as much as there is data available as the events may or may not be suppressed + do { + tr_debug("M2MConnectionHandlerPimpl::receive_handler().."); + rcv_size = _security_impl->read(recv_buffer, sizeof(recv_buffer)); + tr_debug("M2MConnectionHandlerPimpl::receive_handler() res: %d", rcv_size); + if (rcv_size > 0) { + _observer.data_available((uint8_t*)recv_buffer, + rcv_size, _address); + + } else if (M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY == rcv_size) { + tr_error("M2MConnectionHandlerPimpl::receive_handler() - peer close notify!"); + _observer.socket_error(M2MConnectionHandler::SSL_PEER_CLOSE_NOTIFY, true); + return; + + } else if (M2MConnectionHandler::MEMORY_ALLOCATION_FAILED == rcv_size) { + tr_error("M2MConnectionHandlerPimpl::receive_handler() - memory allocation failed!"); + _observer.socket_error(M2MConnectionHandler::MEMORY_ALLOCATION_FAILED, false); + close_socket(); + return; + + } else if (M2MConnectionHandler::ERROR_GENERIC == rcv_size) { + tr_error("M2MConnectionHandlerPimpl::receive_handler() - secure ERROR_GENERIC"); + _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true); + close_socket(); + return; + } + } while (rcv_size > 0 && _socket_state == ESocketStateSecureConnection); + + } else { + size_t recv; + palStatus_t status; + unsigned char recv_buffer[BUFFER_LENGTH]; + do { + if (is_tcp_connection()) { +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + status = pal_recv(_socket, recv_buffer, sizeof(recv_buffer), &recv); +#endif //PAL_NET_TCP_AND_TLS_SUPPORT + } else { + status = pal_receiveFrom(_socket, recv_buffer, sizeof(recv_buffer), NULL, NULL, &recv); + } + + if (status == PAL_ERR_SOCKET_WOULD_BLOCK) { + return; + } else if (status != PAL_SUCCESS) { + tr_error("M2MConnectionHandlerPimpl::receive_handler() - SOCKET_READ_ERROR %" PRIx32, status); + _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true); + close_socket(); + return; + } + + tr_debug("M2MConnectionHandlerPimpl::receive_handler() - data received, len: %zu", recv); + + if (!is_tcp_connection()) { // Observer for UDP plain mode + _observer.data_available((uint8_t*)recv_buffer, recv, _address); + } else { +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + if ( recv < 4 ) { + tr_error("M2MConnectionHandlerPimpl::receive_handler() - TCP SOCKET_READ_ERROR"); + _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true); + close_socket(); + return; + } + + //We need to "shim" out the length from the front + uint32_t len = (recv_buffer[0] << 24 & 0xFF000000) + (recv_buffer[1] << 16 & 0xFF0000); + len += (recv_buffer[2] << 8 & 0xFF00) + (recv_buffer[3] & 0xFF); + if (len > 0 && len <= recv - 4) { + // Observer for TCP plain mode + _observer.data_available(recv_buffer + 4, len, _address); + } +#endif //PAL_NET_TCP_AND_TLS_SUPPORT + } + } while (recv > 0 && _socket_state == ESocketStateUnsecureConnection); + } +} + +void M2MConnectionHandlerPimpl::claim_mutex() +{ + eventOS_scheduler_mutex_wait(); +} + +void M2MConnectionHandlerPimpl::release_mutex() +{ + eventOS_scheduler_mutex_release(); +} + +bool M2MConnectionHandlerPimpl::init_socket() +{ + palSocketType_t socket_type = PAL_SOCK_DGRAM; + palStatus_t status; + palSocketAddress_t bind_address; + palIpV4Addr_t interface_address4; + palIpV6Addr_t interface_address6; + + memset(&bind_address, 0, sizeof(palSocketAddress_t)); + memset(&interface_address4, 0, sizeof(interface_address4)); + memset(&interface_address6, 0, sizeof(interface_address6)); + + if (is_tcp_connection()) { +#ifdef PAL_NET_TCP_AND_TLS_SUPPORT + socket_type = PAL_SOCK_STREAM; +#else + // Somebody has built code without TCP support but tries to use it. + // Perhaps a "assert(false)" would be sufficient. + tr_error("M2MConnectionHandlerPimpl::init_socket() - TCP config error"); + _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT); + return; +#endif //PAL_NET_TCP_AND_TLS_SUPPORT + } + status = pal_asynchronousSocketWithArgument((palSocketDomain_t)_socket_address.addressType, + socket_type, true, _net_iface, &socket_event_handler, + this, &_socket); + + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::init_socket() - socket create error : %" PRIx32, status); + _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT); + return false; + } + + if (_socket_address.addressType == PAL_AF_INET) { + status = pal_setSockAddrIPV4Addr(&bind_address, interface_address4); + } else if (_socket_address.addressType == PAL_AF_INET6) { + status = pal_setSockAddrIPV6Addr(&bind_address, interface_address6); + } else { + tr_warn("M2MConnectionHandlerPimpl::init_socket() - stack type: %d", (int)_socket_address.addressType); + } + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::init_socket - setSockAddrIPV err: %" PRIx32, status); + return false; + } + status = pal_setSockAddrPort(&bind_address, _listen_port); + if (PAL_SUCCESS != status) { + tr_error("M2MConnectionHandlerPimpl::init_socket - setSockAddrPort err: %" PRIx32, status); + return false; + } + pal_bind(_socket, &bind_address, sizeof(bind_address)); + + _security_impl->set_socket(_socket, (palSocketAddress_t*)&_socket_address); + + return true; +} + +bool M2MConnectionHandlerPimpl::is_tcp_connection() const +{ + return ( _binding_mode == M2MInterface::TCP || + _binding_mode == M2MInterface::TCP_QUEUE ); +} + +void M2MConnectionHandlerPimpl::close_socket() +{ + _suppressable_event_in_flight = false; + + if (_socket) { + // At least on mbed-os the pal_close() will perform callbacks even during it + // is called, which we will ignore when this state is set. + _socket_state = ESocketStateCloseBeingCalled; + pal_close(&_socket); + _socket = 0; + } + + // make sure the socket connection statemachine is reset too. + _socket_state = ESocketStateDisconnected; + + if (_security_impl) { + _security_impl->reset(); + } + + claim_mutex(); + /*ns_list_foreach_safe(M2MConnectionHandlerPimpl::send_data_queue_s, tmp, &_linked_list_send_data) { + ns_list_remove(&_linked_list_send_data, tmp); + free(tmp->data); + free(tmp); + }*/ + // Workaround for IAR compilation issue. ns_list_foreach does not compile with IAR. + // Error[Pe144]: a value of type "void *" cannot be used to initialize an entity of type "M2MConnectionHandlerPimpl::send_data_queue *" + while (!ns_list_is_empty(&_linked_list_send_data)) { + send_data_queue_s* data = (send_data_queue_s*)ns_list_get_first(&_linked_list_send_data); + ns_list_remove(&_linked_list_send_data, data); + free(data->data); + free(data); + } + release_mutex(); +} + +M2MConnectionHandlerPimpl::send_data_queue_s* M2MConnectionHandlerPimpl::get_item_from_list() +{ + claim_mutex(); + send_data_queue_s* out_data = (send_data_queue_s*)ns_list_get_first(&_linked_list_send_data); + if (out_data) { + ns_list_remove(&_linked_list_send_data, out_data); + } + release_mutex(); + return out_data; +} + +void M2MConnectionHandlerPimpl::add_item_to_list(M2MConnectionHandlerPimpl::send_data_queue_s *data) +{ + claim_mutex(); + ns_list_add_to_start(&_linked_list_send_data, data); + release_mutex(); +} + +void M2MConnectionHandlerPimpl::force_close() +{ + close_socket(); +} + +void M2MConnectionHandlerPimpl::unregister_network_handler() +{ + pal_unregisterNetworkInterface(_net_iface); +}