/* coap_io_lwip.h -- Network I/O functions for libcoap on lwIP
 *
 * Copyright (C) 2012,2014 Olaf Bergmann <bergmann@tzi.org>
 *               2014 chrysn <chrysn@fsfe.org>
 *
 * This file is part of the CoAP library libcoap. Please see
 * README for terms of use.
 */

#include "coap_config.h"
#include "mem.h"
#include "coap_io.h"
#include <lwip/udp.h>
#include <lwip/tcpip.h>   // for tcpip_callback()

#include "coap_debug.h"

/*
 * Definitions
 */
#define DEFAULT_COAP_RECVMBOX_SIZE  10   // the maximum number of buffered incoming coap messages

#define COAP_MSG_TYPE_PACKET_RECEIVED   0
#define COAP_MSG_TYPE_RESOURCE_CHANGED  1

/*
 * Typedefs, structs and enums
 */
struct coapMsg
{
    uint32_t msgId;
};

struct packetReceivedMsg
{
    uint32_t msgId;
    coap_packet_t *packet;
    coap_endpoint_t *ep;
};

struct resourceChangedMsg
{
    uint32_t msgId;
    coap_resource_t *resource;
    coap_context_t *context;
};

// Data to be used for tcp task callback
struct socket_send_pdu_data
{
    coap_socket_t *sock;
    coap_session_t *session;
    coap_pdu_t *pdu;
    size_t used_size;
} send_pdu_data;

/*
 * Globals in this file
 */
struct coapMsg *coapMsg_p;
sys_mbox_t coapMboxHandle = NULL;   // the incoming mailbox
/*
 * Local prototypes
 */
static void coap_free_packet(coap_packet_t *packet);
static void Coap_Task (void *pvParameters);
static void coap_socket_send_pdu_callback(struct socket_send_pdu_data *ctx);

/*
 * Call this from your application main() before the application task starts
 */
BaseType_t CreateCoapTask(const configSTACK_DEPTH_TYPE stackSize, UBaseType_t taskPriority)
{
    return xTaskCreate(Coap_Task, "coap_task", stackSize, NULL, taskPriority, (TaskHandle_t *) NULL);
}

/*
 * The coap task. This splits coap processing from the single thread solution implemented by the libcoap lwIP port.
 */
static void Coap_Task (void *pvParameters)
{
    err_enum_t ret;

    // Create incoming mailbox
    ret = sys_mbox_new(&coapMboxHandle, DEFAULT_COAP_RECVMBOX_SIZE);
    if (ret != ERR_OK)
    {
        coap_log(LOG_CRIT, "Failed to create COAP message mailbox\n");
        return;
    }

    while(1)
    {
        // Wait on mailbox
        sys_arch_mbox_fetch(&coapMboxHandle, (void **)(&coapMsg_p), 0);

        switch (coapMsg_p->msgId)
        {
            case COAP_MSG_TYPE_PACKET_RECEIVED:
            {
                // The libcoap port to run with lwIP ran as a single process under a Linux host. Therefore the concept of lwIP and libcoap
                // running as separate tasks did not exist.
                // This code was originally in coap_recv(), so all processing of incoming coap messages was done in the tcip_task context.
                // coap_recv() now just mallocs and initialises a coap_packet_t struct with the received packet contents and passes it
                // to the incoming mailbox of this task.

                // Sorry about the gotos. Left them in to preserve the code structure from the original version of coap_recv().

                coap_session_t *session;
                coap_pdu_t *pdu = NULL;
                struct pbuf *pbuf;
                coap_tick_t now;
                uint8_t pdu_token[9] = {0};

                // Cast coapMsg_p to the packet_received msg type
                struct packetReceivedMsg *msg = (struct packetReceivedMsg *)coapMsg_p;

                pbuf = msg->packet->pbuf;
                pdu = coap_pdu_from_pbuf(pbuf);  // malloc a coap_pdu_t struct and initialise it with data from pbuf.
                if (!pdu)
                {
                    goto error;
                }

                if (!coap_pdu_parse(msg->ep->proto, pbuf->payload, pbuf->len, pdu))
                {
                    goto error;
                }

                coap_ticks(&now);
              //  session = coap_endpoint_get_session(ep, packet, now);  // Remsdaq modification, added pdu_token param.
                memcpy(pdu_token, pdu->token, pdu->token_length);        // An ACK to a client operation needs coap_endpoint_get_session() to additionally
                session = coap_endpoint_get_session(msg->ep,             // match on the token when checking if a client session already exists.
                                                    msg->packet,         // Otherwise, it may match on an existing server session from this host.
                                                    now,                 // Original lwip libcoap does not seem to have the concept of a host being both a
                                                    pdu_token);          // server and a client.
                if (!session)
                {
                    goto error;
                }

                if (!(session->proto == COAP_PROTO_UDP || session->proto == COAP_PROTO_DTLS))
                {
                    coap_log(LOG_WARNING, "Proto %d not supported for LWIP\n", session->proto);
                    goto error;
                }

                coap_dispatch(msg->ep->context, session, pdu);  // Process the message.

                // Done. De-allocate memory
                coap_delete_pdu(pdu);    // frees the coap_pdu_t struct and the pbuf allocated by lwIP
                msg->packet->pbuf = NULL;
                coap_free_packet(msg->packet);  // free packet struct allocated in coap_recv().

                break;

                error:
                {
                    /* FIXME: send back RST? */
                    if (pdu)
                    {
                        coap_delete_pdu(pdu);
                    }
                    if (msg->packet)
                    {
                        msg->packet->pbuf = NULL;
                        coap_free_packet(msg->packet);
                    }
                }
                break;
            }

            case COAP_MSG_TYPE_RESOURCE_CHANGED:
            {
                // Cast coapMsg_p to the packet_received msg type
                struct resourceChangedMsg *msg = (struct resourceChangedMsg *)coapMsg_p;

                // Mark resource as changed.
                coap_resource_notify_observers(msg->resource, NULL);

                // Send notifies to observers of this resource.
                coap_check_notify(msg->context);

                break;
            }

            default:
                coap_log(LOG_INFO, "Unknown COAP message received, type %d\n", coapMsg_p->msgId);
                break;
        }

        // NickP: removed the ampersand that was causing libcoap to crash (sorry Jeremy!)
        coap_free_type(COAP_DATA_UNDEFINED, coapMsg_p);
    }
}


/*
 * App will call this to place a msg on the incoming message queue that indicates that one or
 * more elements of a resource have changed their value.
 *
 * I'm using void pointers as putting the prototype with coap_context_t and coap_resource_t as params
 * into coap_io.h creates multiple include issues.
 */
int CoapNotifyResourceChange(void *context_p, void *resource_p)
{
    int retVal = 0;

    coap_context_t *context = (coap_context_t *)context_p;
    coap_resource_t *resource = (coap_resource_t *)resource_p;

    if (coapMboxHandle == NULL)
    {
        coap_log(LOG_CRIT, "ResourceChange mailbox not setup fail\n");
        retVal = -1;
    }
    else
    {
		// Create packet received message
		struct resourceChangedMsg *msg = coap_malloc_type(COAP_DATA_UNDEFINED, sizeof(struct resourceChangedMsg));
		if (msg)
		{
			msg->msgId = COAP_MSG_TYPE_RESOURCE_CHANGED;
			msg->resource = resource;
			msg->context = context;

			// Put message on coap task incoming mailbox
			if (sys_mbox_trypost (&coapMboxHandle, msg) != ERR_OK)
			{
				coap_log(LOG_CRIT, "ResourceChange msg post failed\n");
				coap_free_type(COAP_DATA_UNDEFINED, msg);
				retVal = -1;
			}
		}
		else
		{
			coap_log(LOG_CRIT, "ResourceChange msg malloc fail\n");
			retVal = -1;
		}
    }

    return retVal;
}

#if NO_SYS
pthread_mutex_t lwprot_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_t lwprot_thread = (pthread_t)0xDEAD;
int lwprot_count = 0;
#endif

#if 0
void coap_packet_copy_source(coap_packet_t *packet, coap_address_t *target)
{
        target->port = packet->srcport;
        memcpy(&target->addr, ip_current_src_addr(), sizeof(ip_addr_t));
}
#endif
void coap_packet_get_memmapped(coap_packet_t *packet, unsigned char **address, size_t *length)
{
        LWIP_ASSERT("Can only deal with contiguous PBUFs to read the initial details", packet->pbuf->tot_len == packet->pbuf->len);
        *address = packet->pbuf->payload;
        *length = packet->pbuf->tot_len;
}
static void coap_free_packet(coap_packet_t *packet)
{
        if (packet->pbuf)
                pbuf_free(packet->pbuf);
        coap_free_type(COAP_PACKET, packet);
}

struct pbuf *coap_packet_extract_pbuf(coap_packet_t *packet)
{
        struct pbuf *ret = packet->pbuf;
        packet->pbuf = NULL;
        return ret;
}

/** Callback from lwIP when a package was received.
 *
 * Remsdaq modified.
 * The original lwIP port implementation dealt this to coap_dispatch() immediately, but
 * now the incoming package is passed in a message to a FreeRTOS mailbox. A FreeRTOS task has
 * been created which waits on this mailbox and when this message is received performs the
 * actions that this function used to do. This decouples the coap processing from the lwip task.
 *
 */
static void coap_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
    coap_endpoint_t *ep = (coap_endpoint_t*)arg;
    coap_packet_t *packet = coap_malloc_type(COAP_PACKET, sizeof(coap_packet_t));

//    LWIP_ASSERT("Insufficient coap_packet_t resources.", packet != NULL);
    if (!packet)
    {
        coap_log(LOG_CRIT, "Incoming COAP msg packet malloc failed\n");
        pbuf_free(p);
        return;
    }

    packet->pbuf = p;
    packet->src.port = port;
    packet->src.addr = *addr;
    packet->dst.port = upcb->local_port;
    packet->dst.addr = *ip_current_dest_addr();
    packet->ifindex = netif_get_index(ip_current_netif());

    // Create packet received message
    struct packetReceivedMsg *msg = coap_malloc_type(COAP_DATA_UNDEFINED, sizeof(struct packetReceivedMsg));
    if (msg)
    {
        msg->msgId = COAP_MSG_TYPE_PACKET_RECEIVED;
        msg->packet = packet;
        msg->ep = ep;

        // Put message on coap task incoming mailbox
        if (sys_mbox_trypost (&coapMboxHandle, msg) != ERR_OK)
        {
            coap_log(LOG_CRIT, "Incoming COAP msg mailbox post failed\n");
            coap_free_packet(packet);  // free packet struct allocated in coap_recv(). Also frees the pbuf.
            free(msg);
        }
    }
    else
    {
        coap_log(LOG_CRIT, "Cannot malloc incoming COAP msg\n");
        coap_free_packet(packet);  // free packet struct allocated in coap_recv(). Also frees the pbuf.
    }

    return;
}

coap_endpoint_t *
coap_new_endpoint(coap_context_t *context, const coap_address_t *addr, coap_proto_t proto) {
        coap_endpoint_t *result;
        err_t err;

        LWIP_ASSERT("Proto not supported for LWIP endpoints", proto == COAP_PROTO_UDP);

        result = coap_malloc_type(COAP_ENDPOINT, sizeof(coap_endpoint_t));
        if (!result) return NULL;

        result->sock.pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
        if (result->sock.pcb == NULL) goto error;

        udp_recv(result->sock.pcb, coap_recv, (void*)result);
        err = udp_bind(result->sock.pcb, &addr->addr, addr->port);
        if (err) {
                udp_remove(result->sock.pcb);
                goto error;
        }

        result->default_mtu = COAP_DEFAULT_MTU;
        result->context = context;
        result->proto = proto;
        result->sessions = NULL;    // jem - bug fix, sessions linked list needs initialising

        return result;

error:
        coap_free_type(COAP_ENDPOINT, result);
        return NULL;
}

void coap_free_endpoint(coap_endpoint_t *ep)
{
        udp_remove(ep->sock.pcb);
        coap_free_type(COAP_ENDPOINT, ep);
}

/*
 * Modified so that we send coap packets in a thread safe manner. The original code directly calls functions
 * within lwIP to transmit the packet. This was OK for the original port as lwIP was built with USE_RTOS not defined,
 * i.e. it ran coupled with libcoap in a single Linux process.
 * This code and lwIP are now in different FreeRTOS tasks, so USE_RTOS is defined and we can't directly call Tx or Rx
 * functions safely.
 *
 * So, I'm doing the easiest fix, using the tcpip_callback() API to put a callback message on the incoming lwIP task
 * queue when we want to send a message.
 * As all our messages are UDP packets we should really use the netconn API (e.g. netconn_send()) or better, use the
 * sockets API. Using the sockets method might enable us to use existing libcoap code that is targeted to build using
 * the Linux kernel stack.
 */
ssize_t coap_socket_send_pdu(coap_socket_t *sock, coap_session_t *session, coap_pdu_t *pdu)
{
    send_pdu_data.sock = sock;
    send_pdu_data.session = session;
    send_pdu_data.pdu = pdu;
    send_pdu_data.used_size = 0;

    err_t ret;

    // Put a TCPIP_MSG_CALLBACK message on the lwIP's tcp task queue. This task will then enter the blocked state until
    // the message is processed (i.e. the callback function is executed).
    // Note that the lwIP tcp task must be a higher priority than this one.
    ret = tcpip_callback((tcpip_callback_fn)coap_socket_send_pdu_callback, &send_pdu_data);
#if (configUSE_PREEMPTION == 0)
    taskYIELD()
#endif

    // If here, the callback has been done.
    if (ret != ERR_OK)
    {
        coap_log(LOG_ERR, "coap_socket_send_pdu callback failure\n");
    }

    return send_pdu_data.used_size;
}

/*
 * TCP task calls this when it sees our callback request on its input message queue.
 * We can now execute the code originally in coap_socket_send_pdu() in a thread safe manner.
 */
static void coap_socket_send_pdu_callback(struct socket_send_pdu_data *ctx)
{
    coap_socket_t *sock = send_pdu_data.sock;
    coap_session_t *session = send_pdu_data.session;
    coap_pdu_t *pdu = send_pdu_data.pdu;

// This code was originally in coap_socket_send_pdu()
    /* FIXME: we can't check this here with the existing infrastructure, but we
    * should actually check that the pdu is not held by anyone but us. the
    * respective pbuf is already exclusively owned by the pdu. */

    pbuf_realloc(pdu->pbuf, pdu->used_size + coap_pdu_parse_header_size(session->proto, pdu->pbuf->payload));

    udp_sendto(sock->pcb,
               pdu->pbuf,
               &session->remote_addr.addr,
               session->remote_addr.port);
// End of old coap_socket_send_pdu() code

    send_pdu_data.used_size = pdu->used_size;   // coap_socket_send_pdu() needs to return this.
}

ssize_t
coap_socket_send(coap_socket_t *sock, coap_session_t *session,
  const uint8_t *data, size_t data_len ) {
  /* Not implemented, use coap_socket_send_pdu instead */
  return -1;
}

int
coap_socket_bind_udp(coap_socket_t *sock,
  const coap_address_t *listen_addr,
  coap_address_t *bound_addr) {
  return 0;
}

int
coap_socket_connect_udp(coap_socket_t *sock,
  const coap_address_t *local_if,
  const coap_address_t *server,
  int default_port,
  coap_address_t *local_addr,
  coap_address_t *remote_addr) {
//    return 0;
    return 1;      // jem - prevents coap_session_create_client() from deleting the session
}

int
coap_socket_connect_tcp1(coap_socket_t *sock,
                         const coap_address_t *local_if,
                         const coap_address_t *server,
                         int default_port,
                         coap_address_t *local_addr,
                         coap_address_t *remote_addr) {
  return 0;
}

int
coap_socket_connect_tcp2(coap_socket_t *sock,
                         coap_address_t *local_addr,
                         coap_address_t *remote_addr) {
  return 0;
}

int
coap_socket_bind_tcp(coap_socket_t *sock,
                     const coap_address_t *listen_addr,
                     coap_address_t *bound_addr) {
  return 0;
}

int
coap_socket_accept_tcp(coap_socket_t *server,
                        coap_socket_t *new_client,
                        coap_address_t *local_addr,
                        coap_address_t *remote_addr) {
  return 0;
}

ssize_t
coap_socket_write(coap_socket_t *sock, const uint8_t *data, size_t data_len) {
  return -1;
}

ssize_t
coap_socket_read(coap_socket_t *sock, uint8_t *data, size_t data_len) {
  return -1;
}

void coap_socket_close(coap_socket_t *sock) {
  return;
}

