
/*
 * Copyright (C) Roman Arutyunyan
 * Copyright (C) Dmitry Volyntsev
 * Copyright (C) NGINX, Inc.
 */


#ifndef _NGX_JS_H_INCLUDED_
#define _NGX_JS_H_INCLUDED_


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_connect.h>
#include <njs.h>
#include <njs_rbtree.h>
#include <njs_arr.h>

#if (NJS_HAVE_QUICKJS)
#include <qjs.h>
#endif

#define NGX_ENGINE_NJS      1
#define NGX_ENGINE_QJS      2

#define NGX_JS_UNSET        0
#define NGX_JS_DEPRECATED   1
#define NGX_JS_STRING       2
#define NGX_JS_BUFFER       4
#define NGX_JS_BOOLEAN      8
#define NGX_JS_NUMBER       16

#define NGX_JS_BOOL_FALSE   0
#define NGX_JS_BOOL_TRUE    1
#define NGX_JS_BOOL_UNSET   2

#define NGX_NJS_VAR_NOCACHE 1

#define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED)

#if (NJS_HAVE_QUICKJS)
/*
 * This static table solves the problem of a native QuickJS approach
 * which uses a static variables of type JSClassID and JS_NewClassID() to
 * allocate class ids for custom classes. The static variables approach
 * causes a problem when two modules linked with -Wl,-Bsymbolic-functions flag
 * are loaded dynamically.
 */

enum {
    NGX_QJS_CLASS_ID_CONSOLE = QJS_CORE_CLASS_ID_LAST,
    NGX_QJS_CLASS_ID_HTTP_REQUEST,
    NGX_QJS_CLASS_ID_HTTP_PERIODIC,
    NGX_QJS_CLASS_ID_HTTP_VARS,
    NGX_QJS_CLASS_ID_HTTP_HEADERS_IN,
    NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT,
    NGX_QJS_CLASS_ID_STREAM_SESSION,
    NGX_QJS_CLASS_ID_STREAM_PERIODIC,
    NGX_QJS_CLASS_ID_STREAM_FLAGS,
    NGX_QJS_CLASS_ID_STREAM_VARS,
    NGX_QJS_CLASS_ID_SHARED,
    NGX_QJS_CLASS_ID_SHARED_DICT,
    NGX_QJS_CLASS_ID_SHARED_DICT_ERROR,
    NGX_QJS_CLASS_ID_FETCH_HEADERS,
    NGX_QJS_CLASS_ID_FETCH_REQUEST,
    NGX_QJS_CLASS_ID_FETCH_RESPONSE,
};
#endif


typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t;
typedef struct ngx_js_event_s ngx_js_event_t;
typedef struct ngx_js_dict_s  ngx_js_dict_t;
typedef struct ngx_js_ctx_s  ngx_js_ctx_t;
typedef struct ngx_engine_s  ngx_engine_t;


typedef ngx_pool_t *(*ngx_external_pool_pt)(njs_external_ptr_t e);
typedef void (*ngx_js_event_finalize_pt)(njs_external_ptr_t e, ngx_int_t rc);
typedef ngx_resolver_t *(*ngx_external_resolver_pt)(njs_external_ptr_t e);
typedef ngx_msec_t (*ngx_external_timeout_pt)(njs_external_ptr_t e);
typedef ngx_js_loc_conf_t *(*ngx_js_external_loc_conf_pt)(njs_external_ptr_t e);
typedef ngx_js_ctx_t *(*ngx_js_external_ctx_pt)(njs_external_ptr_t e);


typedef struct {
    ngx_array_t          *native_modules;
} ngx_js_core_conf_t;


typedef struct {
    ngx_str_t              name;
    ngx_str_t              path;
    u_char                *file;
    ngx_uint_t             line;
} ngx_js_named_path_t;


struct ngx_js_event_s {
    void                *ctx;
    njs_opaque_value_t   function;
    njs_opaque_value_t  *args;
    ngx_socket_t         fd;
    NJS_RBTREE_NODE     (node);
    njs_uint_t           nargs;
    void               (*destructor)(ngx_js_event_t *event);
    ngx_event_t          ev;
    void                *data;
};


typedef struct {
    void               **data;
    ngx_uint_t           head;
    ngx_uint_t           tail;
    ngx_uint_t           size;
    ngx_uint_t           capacity;
} ngx_js_queue_t;


#define NGX_JS_COMMON_MAIN_CONF                                               \
    ngx_js_dict_t         *dicts;                                             \
    ngx_array_t           *periodics                                          \


#define _NGX_JS_COMMON_LOC_CONF                                               \
    ngx_uint_t             type;                                              \
    ngx_engine_t          *engine;                                            \
    ngx_uint_t             reuse;                                             \
    size_t                 reuse_max_size;                                    \
    ngx_js_queue_t        *reuse_queue;                                       \
    ngx_str_t              cwd;                                               \
    ngx_array_t           *imports;                                           \
    ngx_array_t           *paths;                                             \
                                                                              \
    ngx_array_t           *preload_objects;                                   \
                                                                              \
    size_t                 buffer_size;                                       \
    size_t                 max_response_body_size;                            \
    ngx_msec_t             timeout;                                           \
                                                                              \
    ngx_uint_t             fetch_keepalive;                                   \
    ngx_uint_t             fetch_keepalive_requests;                          \
    ngx_msec_t             fetch_keepalive_time;                              \
    ngx_msec_t             fetch_keepalive_timeout;                           \
    ngx_queue_t            fetch_keepalive_cache;                             \
    ngx_queue_t            fetch_keepalive_free;                              \
                                                                              \
    ngx_url_t              *fetch_proxy_url;                                  \
    ngx_str_t               fetch_proxy_auth_header;                          \
                                                                              \
    ngx_int_t             (*eval_proxy_url)(ngx_pool_t *pool,                 \
                                            void *request,                    \
                                            void *module_conf,                \
                                            ngx_url_t **url_out,              \
                                            ngx_str_t *auth_out)

#define ngx_js_conf_dynamic_proxy(conf)                                       \
     ((conf)->eval_proxy_url != NULL)

#define ngx_js_conf_proxy(conf)                                               \
    (((conf)->fetch_proxy_url != NULL                                         \
      && (conf)->fetch_proxy_url->host.len > 0)                               \
     || ngx_js_conf_dynamic_proxy(conf))


#if (NGX_SSL)
#define NGX_JS_COMMON_LOC_CONF                                                \
    _NGX_JS_COMMON_LOC_CONF;                                                  \
                                                                              \
    ngx_ssl_t             *ssl;                                               \
    ngx_str_t              ssl_ciphers;                                       \
    ngx_uint_t             ssl_protocols;                                     \
    ngx_flag_t             ssl_verify;                                        \
    ngx_int_t              ssl_verify_depth;                                  \
    ngx_str_t              ssl_trusted_certificate

#else
#define NGX_JS_COMMON_LOC_CONF _NGX_JS_COMMON_LOC_CONF
#endif


#define NGX_JS_COMMON_CTX                                                     \
    ngx_engine_t          *engine;                                            \
    ngx_log_t             *log;                                               \
    njs_opaque_value_t     args[3];                                           \
    njs_opaque_value_t     retval;                                            \
    njs_arr_t             *rejected_promises;                                 \
    njs_rbtree_t           waiting_events;                                    \
    ngx_socket_t           event_id


#define ngx_js_add_event(ctx, event)                                          \
    njs_rbtree_insert(&(ctx)->waiting_events, &(event)->node)


#define ngx_js_del_event(ctx, event)                                          \
    do {                                                                      \
        if ((event)->destructor) {                                            \
            (event)->destructor(event);                                       \
        }                                                                     \
                                                                              \
        njs_rbtree_delete(&(ctx)->waiting_events, &(event)->node);            \
    } while (0)


typedef struct {
    NGX_JS_COMMON_MAIN_CONF;
} ngx_js_main_conf_t;


struct ngx_js_loc_conf_s {
    NGX_JS_COMMON_LOC_CONF;
};


typedef struct {
    ngx_str_t    fname;
    unsigned     flags;
    u_char      *file_name;
    ngx_uint_t   line;
} ngx_js_set_t;


struct ngx_js_ctx_s {
    NGX_JS_COMMON_CTX;
};


typedef struct {
    void                        *external;
} ngx_js_opaque_t;


typedef struct ngx_engine_opts_s {
    unsigned                    engine;
    union {
        struct {
            njs_vm_meta_t      *metas;
            njs_module_t      **addons;
        } njs;
#if (NJS_HAVE_QUICKJS)
        struct {
            uintptr_t          *metas;
            qjs_module_t      **addons;
        } qjs;
#endif
    } u;

    njs_str_t                   file;
    ngx_js_core_conf_t         *core_conf;
    ngx_js_loc_conf_t          *conf;
    ngx_engine_t             *(*clone)(ngx_js_ctx_t *ctx,
                                        ngx_js_loc_conf_t *cf, njs_int_t pr_id,
                                        void *external);
    void                      (*destroy)(ngx_engine_t *e, ngx_js_ctx_t *ctx,
                                        ngx_js_loc_conf_t *conf);
} ngx_engine_opts_t;


typedef struct {
    u_char                     *code;
    size_t                      code_size;
} ngx_js_code_entry_t;


struct ngx_engine_s {
    union {
        struct {
            njs_vm_t           *vm;
        } njs;
#if (NJS_HAVE_QUICKJS)
        struct {
            JSContext          *ctx;
        } qjs;
#endif
    } u;

    ngx_int_t                 (*compile)(ngx_js_loc_conf_t *conf, ngx_log_t *lg,
                                         u_char *start, size_t size);
    ngx_int_t                 (*call)(ngx_js_ctx_t *ctx, ngx_str_t *fname,
                                      njs_opaque_value_t *args,
                                      njs_uint_t nargs);
    ngx_engine_t             *(*clone)(ngx_js_ctx_t *ctx,
                                       ngx_js_loc_conf_t *cf, njs_int_t pr_id,
                                       void *external);
    void                     *(*external)(ngx_engine_t *e);
    ngx_int_t                 (*pending)(ngx_engine_t *e);
    ngx_int_t                 (*string)(ngx_engine_t *e,
                                        njs_opaque_value_t *value,
                                        ngx_str_t *str);
    void                      (*destroy)(ngx_engine_t *e, ngx_js_ctx_t *ctx,
                                         ngx_js_loc_conf_t *conf);

#if (NGX_DEBUG)
    ngx_uint_t                  id;
#endif
    unsigned                    type;
    const char                 *name;
    njs_mp_t                   *pool;
    njs_arr_t                  *precompiled;
    njs_arr_t                  *native_modules;
    ngx_js_core_conf_t         *core_conf;
};


enum {
    NGX_JS_EXTERNAL_CONNECTION = 0,
    NGX_JS_EXTERNAL_POOL,
    NGX_JS_EXTERNAL_RESOLVER,
    NGX_JS_EXTERNAL_RESOLVER_TIMEOUT,
    NGX_JS_EXTERNAL_EVENT_FINALIZE,
    NGX_JS_EXTERNAL_LOC_CONF,
    NGX_JS_EXTERNAL_CTX,
    NGX_JS_EXTERNAL_MAIN_CONF,
};

#define ngx_external_connection(vm, e)                                        \
    (*((ngx_connection_t **)                                                  \
       ((u_char *) (e) + njs_vm_meta(vm, NGX_JS_EXTERNAL_CONNECTION))))
#define ngx_external_pool(vm, e)                                              \
    ((ngx_external_pool_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_POOL))(e)
#define ngx_external_resolver(vm, e)                                          \
    ((ngx_external_resolver_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER))(e)
#define ngx_external_resolver_timeout(vm, e)                                  \
    ((ngx_external_timeout_pt)                                                \
      njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e)
#define ngx_external_event_finalize(vm) \
    ((ngx_js_event_finalize_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_EVENT_FINALIZE))
#define ngx_external_loc_conf(vm, e)                                          \
    ((ngx_js_external_loc_conf_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_LOC_CONF))(e)
#define ngx_external_ctx(vm, e) \
    ((ngx_js_external_ctx_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_CTX))(e)
#define ngx_main_conf(vm)                                                     \
	((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_EXTERNAL_MAIN_CONF))


#define ngx_js_prop(vm, type, value, start, len)                              \
    ((type == NGX_JS_STRING) ? njs_vm_value_string_create(vm, value, start, len) \
                             : njs_vm_value_buffer_set(vm, value, start, len))


void ngx_js_ctx_init(ngx_js_ctx_t *ctx, ngx_log_t *log);

#define ngx_js_ctx_pending(ctx)                                               \
    ((ctx)->engine->pending(ctx->engine)                                      \
     || !njs_rbtree_is_empty(&(ctx)->waiting_events))

#define ngx_js_ctx_external(ctx)                                              \
    ((ctx)->engine->external(ctx->engine))


void ngx_js_ctx_destroy(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf);
ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func,
    njs_opaque_value_t *args, njs_uint_t nargs);
ngx_int_t ngx_js_log_exception(njs_vm_t *vm, ngx_log_t *log, const char *txt);
ngx_engine_t *ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
    void *external);

#define NGX_CHB_CTX_INIT(chain, pool)                                        \
    njs_chb_init(chain, pool, (njs_chb_alloc_t) ngx_palloc, NULL)

#if (NJS_HAVE_QUICKJS)

typedef struct ngx_qjs_event_s ngx_qjs_event_t;

typedef union {
    njs_opaque_value_t opaque;
    JSValue            value;
} ngx_qjs_value_t;

struct ngx_qjs_event_s {
    void                *ctx;
    JSValue              function;
    JSValue             *args;
    ngx_socket_t         fd;
    NJS_RBTREE_NODE     (node);
    njs_uint_t           nargs;
    void               (*destructor)(ngx_qjs_event_t *event);
    ngx_event_t          ev;
    void                *data;
};

#define ngx_qjs_arg(val) (((ngx_qjs_value_t *) &(val))->value)
ngx_engine_t *ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
    void *external);
void ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
    ngx_js_loc_conf_t *conf);
ngx_int_t ngx_qjs_call(JSContext *cx, JSValue function, JSValue *argv,
    int argc);
ngx_int_t ngx_qjs_log_exception(ngx_engine_t *e, ngx_log_t *log,
    const char *txt);
ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n);
ngx_int_t ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val,
    ngx_str_t *dst);

JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc,
     JSValueConst *argv);

#define ngx_qjs_prop(cx, type, start, len)                                   \
    ((type == NGX_JS_STRING) ? qjs_string_create(cx, start, len)             \
                             : qjs_buffer_create(cx, (u_char *) start, len))

#define ngx_qjs_meta(cx, i)                                                  \
    ((uintptr_t *) JS_GetRuntimeOpaque(JS_GetRuntime(cx)))[i]
#define ngx_qjs_external_connection(cx, e)                                   \
    (*((ngx_connection_t **)                                                 \
       ((u_char *) (e) + ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CONNECTION))))
#define ngx_qjs_external_pool(cx, e)                                         \
    ((ngx_external_pool_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_POOL))(e)
#define ngx_qjs_external_resolver(cx, e)                                     \
    ((ngx_external_resolver_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER))(e)
#define ngx_qjs_external_resolver_timeout(cx, e)                             \
    ((ngx_external_timeout_pt)                                               \
     ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e)
#define ngx_qjs_external_event_finalize(cx)                                  \
    ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_EVENT_FINALIZE))
#define ngx_qjs_external_loc_conf(cx, e)                                     \
    ((ngx_js_external_loc_conf_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_LOC_CONF))(e)
#define ngx_qjs_external_ssl(cx, e)                                          \
    (ngx_qjs_external_loc_conf(cx, e)->ssl)
#define ngx_qjs_external_ssl_verify(cx, e)                                   \
    (ngx_qjs_external_loc_conf(cx, e)->ssl_verify)
#define ngx_qjs_external_fetch_timeout(cx, e)                                \
    (ngx_qjs_external_loc_conf(cx, e)->timeout)
#define ngx_qjs_external_buffer_size(cx, e)                                  \
    (ngx_qjs_external_loc_conf(cx, e)->buffer_size)
#define ngx_qjs_external_max_response_buffer_size(cx, e)                     \
    (ngx_qjs_external_loc_conf(cx, e)->max_response_body_size)
#define ngx_qjs_external_ctx(cx, e)                                          \
    ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CTX))(e)
#define ngx_qjs_main_conf(cx)                                                \
    ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_MAIN_CONF))

extern qjs_module_t  ngx_qjs_ngx_module;
extern qjs_module_t  ngx_qjs_ngx_shared_dict_module;
extern qjs_module_t  ngx_qjs_ngx_fetch_module;

#endif

njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t level, njs_value_t *retval);
void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external,
    ngx_uint_t level, const char *fmt, ...);
void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level,
    const u_char *start, size_t length);
char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char * ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char * ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_int_t ngx_js_parse_proxy_url(ngx_pool_t *pool, ngx_log_t *log,
    ngx_str_t *url_str, ngx_url_t **url_out, ngx_str_t *auth_header_out);
ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf,
    ngx_js_loc_conf_t *prev,
    ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));
ngx_int_t ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf,
    ngx_engine_opts_t *opt);
ngx_js_loc_conf_t *ngx_js_create_conf(ngx_conf_t *cf, size_t size);
char * ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
   ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));
char *ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
    void *tag);

void *ngx_js_core_create_conf(ngx_cycle_t *cycle);
char *ngx_js_core_load_native_module(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
void ngx_js_native_module_cleanup(void *data);

njs_int_t ngx_js_ext_string(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
njs_int_t ngx_js_ext_uint(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
njs_int_t ngx_js_ext_constant(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
njs_int_t ngx_js_ext_flags(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);

ngx_int_t ngx_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str);
ngx_int_t ngx_js_ngx_string(njs_vm_t *vm, njs_value_t *value, ngx_str_t *str);
ngx_int_t ngx_js_integer(njs_vm_t *vm, njs_value_t *value, ngx_int_t *n);
const char *ngx_js_errno_string(int errnum);

ngx_js_queue_t *ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity);
ngx_int_t ngx_js_queue_push(ngx_js_queue_t *queue, void *item);
void *ngx_js_queue_pop(ngx_js_queue_t *queue);


extern njs_module_t  ngx_js_ngx_module;


#include "ngx_js_fetch.h"
#include "ngx_js_shared_dict.h"

#endif /* _NGX_JS_H_INCLUDED_ */
