/*
 * Copyright (C) 2019 Daniele Lacamera
 *
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/* @defgroup    module sock_tls
 * @ingroup     pkg_wolfssl
 * @brief       Sock submodule for TLS/DTLS sessions
 *
 * How To Use
 * ----------
 * First you need to @ref including-modules "include" a module that implements
 * this API in your application's Makefile.
 *
 * The `sock_tls` module requires the `wolfssl` package.
 *
 * The application's Makefile should at lease contain the following:
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {Makefile}
 *
 * USEPKG += wolfssl
 * USEMODULE+=sock_tls
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *
 * ### A Simple DTLS Server
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 * #include <wolfssl/ssl.h>
 * #include <sock_tls.h>
 *
 * #include <stdio.h>
 * #include <inttypes.h>
 *
 * #include <net/sock/udp.h>
 *
 * #include <stdio.h>
 * #include <stdlib.h>
 * #include <string.h>
 *
 * #define SERVER_PORT 11111
 * #define DEBUG 1
 * extern const unsigned char server_cert[788];
 * extern const unsigned char server_key[121];
 * extern unsigned int server_cert_len;
 * extern unsigned int server_key_len;
 *
 * static sock_tls_t skv;
 * static sock_tls_t *sk = &skv;
 * static const char Test_dtls_string[] = "DTLS OK!";
 *
 * int main(void)
 * {
 *     char buf[64];
 *     int ret;
 *     sock_udp_ep_t local = SOCK_IPV6_EP_ANY;
 *     local.port = SERVER_PORT;
 *
 *     if (sock_dtls_create(sk, &local, NULL, 0, wolfDTLSv1_2_server_method()) != 0) {
 *         printf("Failed to create DTLS socket context\r\n");
 *         return -1;
 *     }
 *     if (wolfSSL_CTX_use_certificate_buffer(sk->ctx, server_cert,
 *                 server_cert_len, SSL_FILETYPE_ASN1 ) != SSL_SUCCESS)
 *     {
 *         printf("Failed to load certificate from memory.\r\n");
 *         return -1;
 *     }
 *
 *     if (wolfSSL_CTX_use_PrivateKey_buffer(sk->ctx, server_key,
 *                 server_key_len, SSL_FILETYPE_ASN1 ) != SSL_SUCCESS)
 *     {
 *         printf("Failed to load private key from memory.\r\n");
 *         return -1;
 *     }
 *     ret = sock_dtls_session_create(sk);
 *     if (ret < 0)
 *     {
 *         printf("Failed to create DTLS session (err: %s)\r\n", strerror(-ret));
 *         return -1;
 *     }
 *     printf("Listening on %d\n", SERVER_PORT);
 *     while(1) {
 *         ret = wolfSSL_accept(sk->ssl);
 *         if (ret != SSL_SUCCESS) {
 *             continue;
 *         }
 *         printf("Connection accepted\r\n");
 *         ret = wolfSSL_read(sk->ssl, buf, 64);
 *         if (ret > 0) {
 *             buf[ret] = (char)0;
 *             printf("Received '%s'\r\n", buf);
 *         }
 *         printf("Sending 'DTLS OK'...\r\n");
 *         wolfSSL_write(sk->ssl, Test_dtls_string, sizeof(Test_dtls_string));
 *         printf("Closing connection.\r\n");
 *         sock_dtls_session_destroy(sk);
 *         sock_dtls_close(sk);
 *         break;
 *     }
 *     return 0;
 * }
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Above you see a simple DTLS echo server. It is important to at least include
 * @ref including-modules "include" the IPv6 module of your networking
 * implementation (e.g. `gnrc_ipv6_default` for @ref net_gnrc GNRC) and at least
 * one network device.
 * A separate file should define the buffers used as certificate and private key,
 * in the variables `server_cert`, `private_key` respectively.
 *
 * After including all the needed header files, we use a global object to store
 * the context for incoming DTLS communication. The object contains the reference
 * to the wolfSSL context, the SSL session and the underlying transport socket.
 *
 * For simplicity, we will refer to the address of the object in the static memory,
 * through the pointer `sk`.
 *
 * A constant test string is used later as a reply to incoming connections.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 * static sock_tls_t skv;
 * static sock_tls_t *sk = &skv;
 *
 * static const char Test_dtls_string[] = "DTLS OK!";
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * In the same way as a normal @ref net_sock_udp "UDP socket", in order to be able to
 * listen for incoming packets, we bind the `sock` by setting a local endpoint with
 * a port (`11111` in this case).
 *
 * We then proceed to create the `sock`. It is bound to `local` and thus listens
 * for UDP packets with @ref udp_hdr_t::dst_port "destination port" `12345`.
 * Since we don't need any further configuration we set the flags to 0.
 * The method argument determines which kind of wolfSSL context is associated to
 * the socket.
*
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 * sock_udp_ep_t local = SOCK_IPV6_EP_ANY;
 * local.port = SERVER_PORT;
 *
 *  if (sock_dtls_create(sk, &local, NULL, 0, wolfDTLSv1_2_server_method()) != 0) {
 *      printf("ERROR: Unable to create DTLS sock\r\n");
 *      return -1;
 *  }
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * By default, all sock_tls operations in a DTLS context are blocking for a
 * limited amount of time, which depends on the DTLS session timeout. To modify
 * the timeout, use `wolfSSL_dtls_set_timeout_init(sk->ssl)`.
 *
 * Certificate and private key for the server context are loaded from a previously
 * initialized section in memory:
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 * if (wolfSSL_CTX_use_certificate_buffer(sk->ctx, server_cert,
 *             server_cert_len, SSL_FILETYPE_ASN1 ) != SSL_SUCCESS)
 * {
 *     printf("Failed to load certificate from memory.\r\n");
 *     return -1;
 * }
 *
 * if (wolfSSL_CTX_use_PrivateKey_buffer(sk->ctx, server_key,
 *             server_key_len, SSL_FILETYPE_ASN1 ) != SSL_SUCCESS)
 * {
 *     printf("Failed to load private key from memory.\r\n");
 *     return -1;
 * }
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Once the context is configured, the SSL session can be initialized.
 *
 * The listening sock automatically takes care of the DTLS handshake.
 * When the session is established, `wolfSSL_accept()` will finally return
 * `SSL_SUCCESS`.
 *
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 * ret = sock_dtls_session_create(sk);
 * if (ret < 0)
 * {
 *     printf("Failed to create DTLS session (err: %s)\r\n", strerror(-ret));
 *     return -1;
 * }
 * printf("Listening on %d\n", SERVER_PORT);
 * while(1) {
 *     ret = wolfSSL_accept(sk->ssl);
 *     if (ret != SSL_SUCCESS) {
 *         continue;
 *     }
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * At this point, the session is established, and encrypted data can be exchanged
 * using `wolfSSL_read()` and `wolfSSL_write()`:
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 *    ret = wolfSSL_read(sk->ssl, buf, 64);
 *    if (ret > 0) {
 *        buf[ret] = (char)0;
 *        printf("Received '%s'\r\n", buf);
 *    }
 *    printf("Sending 'DTLS OK'...\r\n");
 *    wolfSSL_write(sk->ssl, Test_dtls_string, sizeof(Test_dtls_string));
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * The session is terminated, and the associated socket is closed.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
 *         sock_dtls_session_destroy(sk);
 *         sock_dtls_close(sk);
 *         break;
 *     }
 *     return 0;
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 */



#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <net/sock.h>
#include <wolfssl/ssl.h>

#ifndef SOCK_TLS_H
#define SOCK_TLS_H
#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief   Creates a new DTLS sock object.
 *
 * @pre `(sock != NULL)`
 * @pre `(remote == NULL) || (remote->port != 0)`
 *
 * @param[out] sock     The resulting sock object.
 * @param[in] local     Local end point for the sock object.
 *                      May be NULL.
 *                      sock_udp_ep_t::netif must either be
 *                      @ref SOCK_ADDR_ANY_NETIF or equal to
 *                      sock_udp_ep_t::netif of @p remote if `remote != NULL`.
 *                      If NULL @ref sock_udp_send() may bind implicitly.
 *                      sock_udp_ep_t::port may also be 0 to bind the `sock` to
 *                      an ephemeral port.
 * @param[in] remote    Remote end point for the sock object.
 *                      May be `NULL` but then the `remote` parameter of
 *                      @ref sock_udp_send() may not be `NULL` or it will
 *                      always error with return value -ENOTCONN.
 *                      sock_udp_ep_t::port must not be 0 if `remote != NULL`.
 *                      sock_udp_ep_t::netif must either be
 *                      @ref SOCK_ADDR_ANY_NETIF or equal to sock_udp_ep_t::netif
 *                      of @p local if `local != NULL`.
 * @param[in] flags     Flags for the sock object. See also
 *                      [sock flags](@ref net_sock_flags).
 *                      May be 0.
 * @param[in] method    Defines the SSL or TLS protocol for the client or server to use.
 *                      There are several options for selecting the desired protocol.
 *                      wolfSSL currently supports DTLS 1.0, and DTLS 1.2. Each of these
 *                      protocols have a corresponding function that can be used as last
 *                      argument to `sock_dtls_create`. The possible client and server
 *                      protocol options are: `wolfDTLSv1_client_method()`, `wolfDTLSv1_server_method()`,
 *                      `wolfDTLSv1_2_client_method()` and `wolfDTLSv1_server_method`.
 *
 * @return  0 on success.
 * @return  -EADDRINUSE, if `local != NULL` and @p local is already used
 *          elsewhere or if `local->port == 0` but the pool of ephemeral ports
 *          is depleted
 * @return  -EAFNOSUPPORT, if `local != NULL` or `remote != NULL` and
 *          sock_udp_ep_t::family of @p local or @p remote is not supported.
 * @return  -EINVAL, if sock_udp_ep_t::addr of @p remote is an invalid address.
 * @return  -EINVAL, if sock_udp_ep_t::netif of @p local or @p remote are not a
 *          valid interfaces or contradict each other (i.e.
 *          `(local->netif != remote->netif) &&
 *          ((local->netif != SOCK_ADDR_ANY_NETIF) ||
 *          (remote->netif != SOCK_ADDR_ANY_NETIF))` if neither is `NULL`).
 * @return  -ENOMEM, if not enough resources can be provided for `sock` to be
 *          created.
 */
int sock_dtls_create(sock_tls_t *sock, const sock_udp_ep_t *local, const sock_udp_ep_t *remote, uint16_t flags, WOLFSSL_METHOD *method);

/**
 * @brief   Sets the endpoint address for the remote DTLS peer associated to this sock object
 *
 * @pre `(sk != NULL)`
 * @pre `(addr != NULL)`
 *
 * @param[in] sk        The resulting sock object.
 * @param[in] addr      Remote end point for the DTLS session.
 *
 */
void sock_dtls_set_endpoint(sock_tls_t *sk, const sock_udp_ep_t *addr);

/**
 * @brief   Creates a new DTLS session from an existing `sock_tls_t` object.
 *
 * @pre `(sk != NULL)`
 *
 * @param[in] sk      The sock object previously created using @ref sock_dtls_create
 *
 * @return  0 on success.
 * @return  -EINVAL, if @sock is null or the SSL context is not initialized yet.
 * @return  -ENOMEM, if not enough resources can be provided for the session to be
 *          created
 */
int sock_dtls_session_create(sock_tls_t *sk);

/**
 * @brief   Destroys the DTLS session associated to the sock object.
 *
 * @pre `(sk != NULL)`
 *
 * @param[in] sk      The sock object containing the session to destroy.
 *
 */
void sock_dtls_session_destroy(sock_tls_t *sk);

/**
 * @brief   Closes the socket connection associated to the DTLS session.
 *
 * @pre `(sk != NULL)`
 *
 * @param[in] sk      The sock object containing the UDP socket to close.
 *
 */
void sock_dtls_close(sock_tls_t *sk);


#ifdef MODULE_SOCK_TCP
#   error Only support for UDP/IP provided via GNRC stack.
#endif

#ifdef __cplusplus
}
#endif

#endif /* SOCK_TLS_H */
