/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2012, Digium, Inc.
 *
 * Joshua Colp <jcolp@digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

#ifndef _ASTERISK_HTTP_WEBSOCKET_H
#define _ASTERISK_HTTP_WEBSOCKET_H

#include "asterisk/http.h"
#include "asterisk/optional_api.h"

#include <errno.h>

/*! \brief Default websocket write timeout, in ms */
#define AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT 100

/*! \brief Default websocket write timeout, in ms (as a string) */
#define AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR "100"

/*!
 * \file
 *
 * \brief Support for WebSocket connections within the Asterisk HTTP server and client
 *        WebSocket connections to a server.
 *
 * Supported WebSocket versions in server implementation:
 *     Version 7 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
 *     Version 8 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
 *     Version 13 defined in specification http://tools.ietf.org/html/rfc6455
 * Supported WebSocket versions in client implementation:
 *     Version 13 defined in specification http://tools.ietf.org/html/rfc6455
 *
 * \author Joshua Colp <jcolp@digium.com>
 *
 */

/*! \brief WebSocket connection/configuration types.
 *
 * These may look like they overlap or are redundant, but
 * they're shared by other modules like ari and chan_websocket
 * and it didn't make sense to force them to define their
 * own types.
 */
enum ast_websocket_type {
	AST_WS_TYPE_CLIENT_PERSISTENT = (1 << 0),
	AST_WS_TYPE_CLIENT_PER_CALL_CONFIG = (1 << 1),
	AST_WS_TYPE_CLIENT_PER_CALL = (1 << 2),
	AST_WS_TYPE_CLIENT = (1 << 3),
	AST_WS_TYPE_INBOUND = (1 << 4),
	AST_WS_TYPE_SERVER = (1 << 5),
	AST_WS_TYPE_ANY = (0xFFFFFFFF),
};

const char *ast_websocket_type_to_str(enum ast_websocket_type type);


/*! \brief WebSocket operation codes */
enum ast_websocket_opcode {
	AST_WEBSOCKET_OPCODE_TEXT = 0x1,         /*!< Text frame */
	AST_WEBSOCKET_OPCODE_BINARY = 0x2,       /*!< Binary frame */
	AST_WEBSOCKET_OPCODE_PING = 0x9,         /*!< Request that the other side respond with a pong */
	AST_WEBSOCKET_OPCODE_PONG = 0xA,         /*!< Response to a ping */
	AST_WEBSOCKET_OPCODE_CLOSE = 0x8,        /*!< Connection is being closed */
	AST_WEBSOCKET_OPCODE_CONTINUATION = 0x0, /*!< Continuation of a previous frame */
};

/*! \brief Websocket Status Codes from RFC-6455 */
enum ast_websocket_status_code {
	AST_WEBSOCKET_STATUS_NORMAL = 1000,
	AST_WEBSOCKET_STATUS_GOING_AWAY = 1001,
	AST_WEBSOCKET_STATUS_PROTOCOL_ERROR = 1002,
	AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA = 1003,
	AST_WEBSOCKET_STATUS_RESERVED_1004 = 1004,
	AST_WEBSOCKET_STATUS_RESERVED_1005 = 1005,
	AST_WEBSOCKET_STATUS_RESERVED_1006 = 1006,
	AST_WEBSOCKET_STATUS_INVALID_FRAME = 1007,
	AST_WEBSOCKET_STATUS_POLICY_VIOLATION = 1008,
	AST_WEBSOCKET_STATUS_TOO_BIG = 1009,
	AST_WEBSOCKET_STATUS_MANDATORY_EXT = 1010,
	AST_WEBSOCKET_STATUS_INTERNAL_ERROR = 1011,
	AST_WEBSOCKET_STATUS_RESERVED_1012 = 1012,
	AST_WEBSOCKET_STATUS_RESERVED_1013 = 1013,
	AST_WEBSOCKET_STATUS_BAD_GATEWAY = 1014,
	AST_WEBSOCKET_STATUS_RESERVED_1015 = 1015,
};

#ifdef LOW_MEMORY
/*! \brief Size of the pre-determined buffer for WebSocket frames */
#define AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE 8192
#else
/*! \brief Size of the pre-determined buffer for WebSocket frames */
#define AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE 65535
#endif

/*!
 * \brief Opaque structure for WebSocket server.
 * \since 12
 */
struct ast_websocket_server;

/*!
 * \brief Opaque structure for WebSocket sessions.
 */
struct ast_websocket;

/*!
 * \brief Callback from the HTTP request attempting to establish a websocket connection
 *
 * This callback occurs when an HTTP request is made to establish a websocket connection.
 * Implementers of \ref ast_websocket_protocol can use this to deny a request, or to
 * set up application specific data before invocation of \ref ast_websocket_callback.
 *
 * \param ser The TCP/TLS session
 * \param parameters Parameters extracted from the request URI
 * \param headers Headers included in the request
 * \param session_id The id of the current session.
 *
 * \retval 0 The session should be accepted
 * \retval -1 The session should be rejected. Note that the caller must send an error
 * response using \ref ast_http_error.
 * \since 13.5.0
 */
typedef int (*ast_websocket_pre_callback)(struct ast_tcptls_session_instance *ser, struct ast_variable *parameters, struct ast_variable *headers, const char *session_id);

/*!
 * \brief Callback for when a new connection for a sub-protocol is established
 *
 * \param session A WebSocket session structure
 * \param parameters Parameters extracted from the request URI
 * \param headers Headers included in the request
 *
 * \note Once called the ownership of the session is transferred to the sub-protocol handler. It
 *       is responsible for closing and cleaning up.
 *
 */
typedef void (*ast_websocket_callback)(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers);

/*!
 * \brief A websocket protocol implementation
 *
 * Users of the Websocket API can register themselves as a websocket
 * protocol. See \ref ast_websocket_add_protocol2 and \ref ast_websocket_server_add_protocol2.
 * Simpler implementations may use only \ref ast_websocket_add_protocol and
 * \ref ast_websocket_server_add_protocol.
 *
 * \since 13.5.0
 */
struct ast_websocket_protocol {
	/*! \brief Name of the protocol */
	char *name;
/*!
 * \brief Protocol version. This prevents dynamically loadable modules from registering
 * if this struct is changed.
 */
#define AST_WEBSOCKET_PROTOCOL_VERSION 1
	/*! \brief Protocol version. Should be set to /ref AST_WEBSOCKET_PROTOCOL_VERSION */
	unsigned int version;
	/*! \brief Callback called when a new session is attempted. Optional. */
	ast_websocket_pre_callback session_attempted;
	/*! \brief Callback called when a new session is established. Mandatory. */
	ast_websocket_callback session_established;
};

/*!
 * \brief Creates a \ref ast_websocket_server
 *
 * \return New \ref ast_websocket_server instance
 * \retval NULL on error
 * \since 12
 */
AST_OPTIONAL_API(struct ast_websocket_server *, ast_websocket_server_create, (void), { return NULL; });

/*!
 * \brief Callback suitable for use with a \ref ast_http_uri.
 *
 * Set the data field of the ast_http_uri to \ref ast_websocket_server.
 * \since 12
 */
AST_OPTIONAL_API(int, ast_websocket_uri_cb, (struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers), { return -1; });

/*!
 * \brief Allocate a websocket sub-protocol instance
 *
 * \return An instance of \ref ast_websocket_protocol on success
 * \retval NULL on error
 * \since 13.5.0
 */
AST_OPTIONAL_API(struct ast_websocket_protocol *, ast_websocket_sub_protocol_alloc, (const char *name), {return NULL;});

/*!
 * \brief Add a sub-protocol handler to the default /ws server
 *
 * \param name Name of the sub-protocol to register
 * \param callback Callback called when a new connection requesting the sub-protocol is established
 *
 * \retval 0 success
 * \retval -1 if sub-protocol handler could not be registered
 */
AST_OPTIONAL_API(int, ast_websocket_add_protocol, (const char *name, ast_websocket_callback callback), {return -1;});

/*!
 * \brief Add a sub-protocol handler to the default /ws server
 *
 * \param protocol The sub-protocol to register. Note that this must
 * be allocated using /ref ast_websocket_sub_protocol_alloc.
 *
 * \note This method is reference stealing. It will steal the reference to \p protocol
 * on success.
 *
 * \retval 0 success
 * \retval -1 if sub-protocol handler could not be registered
 * \since 13.5.0
 */
AST_OPTIONAL_API(int, ast_websocket_add_protocol2, (struct ast_websocket_protocol *protocol), {return -1;});

/*!
 * \brief Remove a sub-protocol handler from the default /ws server.
 *
 * \param name Name of the sub-protocol to unregister
 * \param callback Session Established callback that was previously registered with the sub-protocol
 *
 * \retval 0 success
 * \retval -1 if sub-protocol was not found or if callback did not match
 */
AST_OPTIONAL_API(int, ast_websocket_remove_protocol, (const char *name, ast_websocket_callback callback), {return -1;});

/*!
 * \brief Add a sub-protocol handler to the given server.
 *
 * \param server The server to add the sub-protocol to
 * \param name Name of the sub-protocol to register
 * \param callback Callback called when a new connection requesting the sub-protocol is established
 *
 * \retval 0 success
 * \retval -1 if sub-protocol handler could not be registered
 * \since 12
 */
AST_OPTIONAL_API(int, ast_websocket_server_add_protocol, (struct ast_websocket_server *server, const char *name, ast_websocket_callback callback), {return -1;});

/*!
 * \brief Add a sub-protocol handler to the given server.
 *
 * \param server The server to add the sub-protocol to.
 * \param protocol The sub-protocol to register. Note that this must
 * be allocated using /ref ast_websocket_sub_protocol_alloc.
 *
 * \note This method is reference stealing. It will steal the reference to \p protocol
 * on success.
 *
 * \retval 0 success
 * \retval -1 if sub-protocol handler could not be registered
 * \since 13.5.0
 */
AST_OPTIONAL_API(int, ast_websocket_server_add_protocol2, (struct ast_websocket_server *server, struct ast_websocket_protocol *protocol), {return -1;});

/*!
 * \brief Remove a sub-protocol handler from the given server.
 *
 * \param server The server to unregister the sub-protocol from
 * \param name Name of the sub-protocol to unregister
 * \param callback Callback that was previously registered with the sub-protocol
 *
 * \retval 0 success
 * \retval -1 if sub-protocol was not found or if callback did not match
 * \since 12
 */
AST_OPTIONAL_API(int, ast_websocket_server_remove_protocol, (struct ast_websocket_server *server, const char *name, ast_websocket_callback callback), {return -1;});

/*!
 * \brief Read a WebSocket frame and handle it
 *
 * \param session Pointer to the WebSocket session
 * \param payload Pointer to a char* which will be populated with a pointer to the payload if present
 * \param payload_len Pointer to a uint64_t which will be populated with the length of the payload if present
 * \param opcode Pointer to an enum which will be populated with the opcode of the frame
 * \param fragmented Pointer to an int which is set to 1 if payload is fragmented and 0 if not
 *
 * \retval -1 on error
 * \retval 0 on success
 *
 * \note Once an AST_WEBSOCKET_OPCODE_CLOSE opcode is received the socket will be closed
 */
AST_OPTIONAL_API(int, ast_websocket_read, (struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented), { errno = ENOSYS; return -1;});

/*!
 * \brief Read a WebSocket frame containing string data.
 *
 * \note The caller is responsible for freeing the output "buf".
 *
 * \param ws pointer to the websocket
 * \param buf string buffer to populate with data read from socket
 * \retval -1 on error
 * \return number of bytes read on success
 *
 * \note Once an AST_WEBSOCKET_OPCODE_CLOSE opcode is received the socket will be closed
 */
AST_OPTIONAL_API(int, ast_websocket_read_string,
		 (struct ast_websocket *ws, char **buf),
		 { errno = ENOSYS; return -1;});

/*!
 * \brief Construct and transmit a WebSocket frame
 *
 * \param session Pointer to the WebSocket session
 * \param opcode WebSocket operation code to place in the frame
 * \param payload Optional pointer to a payload to add to the frame
 * \param payload_size Length of the payload (0 if no payload)
 *
 * \retval 0 if successfully written
 * \retval -1 if error occurred
 */
AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size), { errno = ENOSYS; return -1;});

/*!
 * \brief Construct and transmit a WebSocket frame containing string data.
 *
 * \param ws pointer to the websocket
 * \param buf string data to write to socket
 * \retval 0 if successfully written
 * \retval -1 if error occurred
 */
AST_OPTIONAL_API(int, ast_websocket_write_string,
		 (struct ast_websocket *ws, const char *buf),
		 { errno = ENOSYS; return -1;});
/*!
 * \brief Close a WebSocket session by sending a message with the CLOSE opcode and an optional code
 *
 * \param session Pointer to the WebSocket session
 * \param reason Reason code for closing the session as defined in the RFC
 *
 * \retval 0 if successfully written
 * \retval -1 if error occurred
 */
AST_OPTIONAL_API(int, ast_websocket_close, (struct ast_websocket *session, uint16_t reason), { errno = ENOSYS; return -1;});

/*!
 * \brief Enable multi-frame reconstruction up to a certain number of bytes
 *
 * \param session Pointer to the WebSocket session
 * \param bytes If a reconstructed payload exceeds the specified number of bytes the payload will be returned
 *              and upon reception of the next multi-frame a new reconstructed payload will begin.
 */
AST_OPTIONAL_API(void, ast_websocket_reconstruct_enable, (struct ast_websocket *session, size_t bytes), {return;});

/*!
 * \brief Disable multi-frame reconstruction
 *
 * \param session Pointer to the WebSocket session
 *
 * \note If reconstruction is disabled each message that is part of a multi-frame message will be sent up to
 *       the user when ast_websocket_read is called.
 */
AST_OPTIONAL_API(void, ast_websocket_reconstruct_disable, (struct ast_websocket *session), {return;});

/*!
 * \brief Increase the reference count for a WebSocket session
 *
 * \param session Pointer to the WebSocket session
 */
AST_OPTIONAL_API(void, ast_websocket_ref, (struct ast_websocket *session), {return;});

/*!
 * \brief Decrease the reference count for a WebSocket session
 *
 * \param session Pointer to the WebSocket session
 */
AST_OPTIONAL_API(void, ast_websocket_unref, (struct ast_websocket *session), {return;});

/*!
 * \brief Get the file descriptor for a WebSocket session.
 *
 * \return file descriptor
 *
 * \note You must *not* directly read from or write to this file descriptor. It should only be used for polling.
 */
AST_OPTIONAL_API(int, ast_websocket_fd, (struct ast_websocket *session), { errno = ENOSYS; return -1;});

/*!
 * \brief Wait for the WebSocket session to be ready to be read.
 * \since 16.8.0
 * \since 17.2.0
 *
 * \param session Pointer to the WebSocket session
 * \param timeout the number of milliseconds to wait
 *
 * \retval -1 if error occurred
 * \retval 0 if the timeout expired
 * \retval 1 if the WebSocket session is ready for reading
 */
AST_OPTIONAL_API(int, ast_websocket_wait_for_input, (struct ast_websocket *session, int timeout), { errno = ENOSYS; return -1; });

/*!
 * \brief Get the remote address for a WebSocket connected session.
 *
 * \return Remote address
 */
AST_OPTIONAL_API(struct ast_sockaddr *, ast_websocket_remote_address, (struct ast_websocket *session), {return NULL;});

/*!
 * \brief Get the local address for a WebSocket connection session.
 *
 * \return Local address
 *
 * \since 13.19.0
 */
AST_OPTIONAL_API(struct ast_sockaddr *, ast_websocket_local_address, (struct ast_websocket *session), {return NULL;});

/*!
 * \brief Get whether the WebSocket session is using a secure transport or not.
 *
 * \retval 0 if unsecure
 * \retval 1 if secure
 */
AST_OPTIONAL_API(int, ast_websocket_is_secure, (struct ast_websocket *session), { errno = ENOSYS; return -1;});

/*!
 * \brief Set the socket of a WebSocket session to be non-blocking.
 *
 * \retval 0 on success
 * \retval -1 on failure
 */
AST_OPTIONAL_API(int, ast_websocket_set_nonblock, (struct ast_websocket *session), { errno = ENOSYS; return -1;});

/*!
 * \brief Get the session ID for a WebSocket session.
 *
 * \return session id
 */
AST_OPTIONAL_API(const char *, ast_websocket_session_id, (struct ast_websocket *session), { errno = ENOSYS; return NULL;});

/*!
 * \brief Result code for a websocket client.
 */
enum ast_websocket_result {
	WS_OK = 0,
	WS_ALLOCATE_ERROR,
	WS_KEY_ERROR,
	WS_URI_PARSE_ERROR,
	WS_URI_RESOLVE_ERROR,
	WS_BAD_STATUS,
	WS_INVALID_RESPONSE,
	WS_BAD_REQUEST,
	WS_URL_NOT_FOUND,
	WS_HEADER_MISMATCH,
	WS_HEADER_MISSING,
	WS_NOT_SUPPORTED,
	WS_WRITE_ERROR,
	WS_CLIENT_START_ERROR,
	WS_UNAUTHORIZED,
};

/*!
 * \brief Create, and connect, a websocket client.
 *
 * If the client websocket successfully connects, then the accepted protocol can be
 * checked via a call to ast_websocket_client_accept_protocol.
 *
 * \note While connecting this *will* block until a response is
 *       received from the remote host.
 * \note Expected uri form:
 *       \verbatim ws[s]://<address>[:port][/<path>] \endverbatim
 *       The address (can be a host name) and port are parsed out and used to connect
 *       to the remote server.  If multiple IPs are returned during address
 *       resolution then the first one is chosen.
 *
 * \param uri uri to connect to
 * \param protocols a comma separated string of supported protocols
 * \param tls_cfg secure websocket credentials
 * \param result result code set on client failure
 * \return a client websocket.
 * \retval NULL if object could not be created or connected
 * \since 13
 */
AST_OPTIONAL_API(struct ast_websocket *, ast_websocket_client_create,
		 (const char *uri, const char *protocols,
		  struct ast_tls_config *tls_cfg,
		  enum ast_websocket_result *result), { return NULL;});

/*!
 * \brief Options used for a websocket client
 */
struct ast_websocket_client_options {
	/*!
	 * The URI to connect to
	 *
	 * Expected uri form:
	 *     \verbatim ws[s]://<address>[:port][/<path>] \endverbatim
	 *     The address (can be a host name) and port are parsed out and used to connect
	 *     to the remote server.  If multiple IPs are returned during address
	 *     resolution then the first one is chosen.
	 */
	const char *uri;
	/*!
	 * A comma separated string of supported protocols
	 */
	const char *protocols;
	/*!
	 * Optional connection timeout
	 *
	 * How long (in milliseconds) to attempt to connect (-1 equals infinite)
	 */
	int timeout;
	/*!
	 * Secure websocket credentials
	 */
	struct ast_tls_config *tls_cfg;
	const char *username;          /*!< Auth username */
	const char *password;          /*!< Auth password */
	int suppress_connection_msgs;  /*!< Suppress connection log messages */
};

/*!
 * \brief Create, and connect, a websocket client using given options.
 *
 * If the client websocket successfully connects, then the accepted protocol can be
 * checked via a call to ast_websocket_client_accept_protocol.
 *
 * \note While connecting this *will* block until a response is received
 *       from the remote host, or the connection timeout is reached
 *
 * \param options Websocket client options
 * \param result result code set on client failure
 *
 * \return a client websocket.
 * \retval NULL if object could not be created or connected
 */
AST_OPTIONAL_API(struct ast_websocket *, ast_websocket_client_create_with_options,
	(struct ast_websocket_client_options *options,
	enum ast_websocket_result *result), { return NULL;});

/*!
 * \brief Retrieve the server accepted sub-protocol on the client.
 *
 * \param ws the websocket client
 * \return the accepted client sub-protocol.
 * \since 13
 */
AST_OPTIONAL_API(const char *, ast_websocket_client_accept_protocol,
		 (struct ast_websocket *ws), { return NULL;});

/*!
 * \brief Set the timeout on a non-blocking WebSocket session.
 *
 * \since 11.11.0
 * \since 12.4.0
 *
 * \retval 0 on success
 * \retval -1 on failure
 */
AST_OPTIONAL_API(int, ast_websocket_set_timeout, (struct ast_websocket *session, int timeout), {return -1;});

/*!
 * \brief Convert a websocket result code to a string.
 *
 * \param result The result code to convert
 *
 * \return A string representation of the result code
 */
AST_OPTIONAL_API(const char *, ast_websocket_result_to_str, (enum ast_websocket_result result), {return "";});

/*!
 * \brief Convert a websocket status code to a string.
 *
 * \param code The code to convert
 *
 * \return A string representation of the code
 */
AST_OPTIONAL_API(const char *, ast_websocket_status_to_str, (enum ast_websocket_status_code code), {return "";});

#endif
