// // Copyright 2018 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this // file was obtained (LICENSE.txt). A copy of the license may also be // found online at https://opensource.org/licenses/MIT. // #include #include #include #include #include "core/nng_impl.h" #include "supplemental/http/http_api.h" #include "supplemental/tls/tls.h" #include "supplemental/tls/tls_api.h" #include "supplemental/websocket/websocket.h" #include "websocket.h" typedef struct ws_dialer ws_dialer; typedef struct ws_listener ws_listener; typedef struct ws_pipe ws_pipe; typedef struct ws_hdr { nni_list_node node; char * name; char * value; } ws_hdr; struct ws_dialer { uint16_t lproto; // local protocol uint16_t rproto; // remote protocol size_t rcvmax; char * prname; nni_list aios; nni_mtx mtx; nni_aio * connaio; nni_ws_dialer *dialer; nni_list headers; // req headers bool started; }; struct ws_listener { uint16_t lproto; // local protocol uint16_t rproto; // remote protocol size_t rcvmax; char * prname; nni_list aios; nni_mtx mtx; nni_aio * accaio; nni_ws_listener *listener; nni_list headers; // res headers bool started; }; struct ws_pipe { nni_mtx mtx; size_t rcvmax; bool closed; uint16_t rproto; uint16_t lproto; nni_aio *user_txaio; nni_aio *user_rxaio; nni_aio *txaio; nni_aio *rxaio; nni_ws * ws; }; static void ws_pipe_send_cb(void *arg) { ws_pipe *p = arg; nni_aio *taio; nni_aio *uaio; nni_mtx_lock(&p->mtx); taio = p->txaio; uaio = p->user_txaio; p->user_txaio = NULL; if (uaio != NULL) { int rv; if ((rv = nni_aio_result(taio)) != 0) { nni_aio_finish_error(uaio, rv); } else { nni_aio_finish(uaio, 0, 0); } } nni_mtx_unlock(&p->mtx); } static void ws_pipe_recv_cb(void *arg) { ws_pipe *p = arg; nni_aio *raio = p->rxaio; nni_aio *uaio; int rv; nni_mtx_lock(&p->mtx); uaio = p->user_rxaio; p->user_rxaio = NULL; if ((rv = nni_aio_result(raio)) != 0) { if (uaio != NULL) { nni_aio_finish_error(uaio, rv); } } else { nni_msg *msg = nni_aio_get_msg(raio); if (uaio != NULL) { nni_aio_finish_msg(uaio, msg); } else { nni_msg_free(msg); } } nni_mtx_unlock(&p->mtx); } static void ws_pipe_recv_cancel(nni_aio *aio, int rv) { ws_pipe *p = nni_aio_get_prov_data(aio); nni_mtx_lock(&p->mtx); if (p->user_rxaio != aio) { nni_mtx_unlock(&p->mtx); return; } nni_aio_abort(p->rxaio, rv); p->user_rxaio = NULL; nni_aio_finish_error(aio, rv); nni_mtx_unlock(&p->mtx); } static void ws_pipe_recv(void *arg, nni_aio *aio) { ws_pipe *p = arg; int rv; if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&p->mtx); if ((rv = nni_aio_schedule(aio, ws_pipe_recv_cancel, p)) != 0) { nni_mtx_unlock(&p->mtx); nni_aio_finish_error(aio, rv); return; } p->user_rxaio = aio; nni_ws_recv_msg(p->ws, p->rxaio); nni_mtx_unlock(&p->mtx); } static void ws_pipe_send_cancel(nni_aio *aio, int rv) { ws_pipe *p = nni_aio_get_prov_data(aio); nni_mtx_lock(&p->mtx); if (p->user_txaio != aio) { nni_mtx_unlock(&p->mtx); return; } p->user_txaio = NULL; nni_aio_abort(p->txaio, rv); nni_aio_finish_error(aio, rv); nni_mtx_unlock(&p->mtx); } static void ws_pipe_send(void *arg, nni_aio *aio) { ws_pipe *p = arg; int rv; if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&p->mtx); if ((rv = nni_aio_schedule(aio, ws_pipe_send_cancel, p)) != 0) { nni_mtx_unlock(&p->mtx); nni_aio_finish_error(aio, rv); return; } p->user_txaio = aio; nni_aio_set_msg(p->txaio, nni_aio_get_msg(aio)); nni_aio_set_msg(aio, NULL); nni_ws_send_msg(p->ws, p->txaio); nni_mtx_unlock(&p->mtx); } static void ws_pipe_stop(void *arg) { ws_pipe *p = arg; nni_aio_stop(p->rxaio); nni_aio_stop(p->txaio); } static void ws_pipe_fini(void *arg) { ws_pipe *p = arg; nni_aio_fini(p->rxaio); nni_aio_fini(p->txaio); if (p->ws) { nni_ws_fini(p->ws); } nni_mtx_fini(&p->mtx); NNI_FREE_STRUCT(p); } static void ws_pipe_close(void *arg) { ws_pipe *p = arg; nni_aio_close(p->rxaio); nni_aio_close(p->txaio); nni_mtx_lock(&p->mtx); nni_ws_close(p->ws); nni_mtx_unlock(&p->mtx); } static int ws_pipe_init(ws_pipe **pipep, void *ws) { ws_pipe *p; int rv; if ((p = NNI_ALLOC_STRUCT(p)) == NULL) { return (NNG_ENOMEM); } nni_mtx_init(&p->mtx); // Initialize AIOs. if (((rv = nni_aio_init(&p->txaio, ws_pipe_send_cb, p)) != 0) || ((rv = nni_aio_init(&p->rxaio, ws_pipe_recv_cb, p)) != 0)) { ws_pipe_fini(p); return (rv); } p->ws = ws; *pipep = p; return (0); } static uint16_t ws_pipe_peer(void *arg) { ws_pipe *p = arg; return (p->rproto); } // We have very different approaches for server and client. // Servers use the HTTP server framework, and a request methodology. static int ws_hook(void *arg, nni_http_req *req, nni_http_res *res) { ws_listener *l = arg; ws_hdr * h; NNI_ARG_UNUSED(req); // Eventually we'll want user customizable hooks. // For now we just set the headers we want. NNI_LIST_FOREACH (&l->headers, h) { int rv; rv = nng_http_res_set_header(res, h->name, h->value); if (rv != 0) { return (rv); } } return (0); } static int ws_listener_bind(void *arg) { ws_listener *l = arg; int rv; nni_ws_listener_set_maxframe(l->listener, l->rcvmax); nni_ws_listener_hook(l->listener, ws_hook, l); if ((rv = nni_ws_listener_listen(l->listener)) == 0) { l->started = true; } return (rv); } static void ws_listener_cancel(nni_aio *aio, int rv) { ws_listener *l = nni_aio_get_prov_data(aio); nni_mtx_lock(&l->mtx); if (nni_aio_list_active(aio)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, rv); } nni_mtx_unlock(&l->mtx); } static void ws_listener_accept(void *arg, nni_aio *aio) { ws_listener *l = arg; int rv; // We already bound, so we just need to look for an available // pipe (created by the handler), and match it. // Otherwise we stick the AIO in the accept list. if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&l->mtx); if ((rv = nni_aio_schedule(aio, ws_listener_cancel, l)) != 0) { nni_mtx_unlock(&l->mtx); nni_aio_finish_error(aio, rv); return; } nni_list_append(&l->aios, aio); if (aio == nni_list_first(&l->aios)) { nni_ws_listener_accept(l->listener, l->accaio); } nni_mtx_unlock(&l->mtx); } static void ws_dialer_cancel(nni_aio *aio, int rv) { ws_dialer *d = nni_aio_get_prov_data(aio); nni_mtx_lock(&d->mtx); if (nni_aio_list_active(aio)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, rv); } nni_mtx_unlock(&d->mtx); } static void ws_dialer_connect(void *arg, nni_aio *aio) { ws_dialer *d = arg; int rv; if (nni_aio_begin(aio) != 0) { return; } if (!d->started) { ws_hdr *h; NNI_LIST_FOREACH (&d->headers, h) { int rv = nni_ws_dialer_header(d->dialer, h->name, h->value); if (rv != 0) { nni_aio_finish_error(aio, rv); return; } } } nni_mtx_lock(&d->mtx); if ((rv = nni_aio_schedule(aio, ws_dialer_cancel, d)) != 0) { nni_mtx_unlock(&d->mtx); nni_aio_finish_error(aio, rv); return; } NNI_ASSERT(nni_list_empty(&d->aios)); d->started = true; nni_list_append(&d->aios, aio); nni_ws_dialer_set_maxframe(d->dialer, d->rcvmax); nni_ws_dialer_dial(d->dialer, d->connaio); nni_mtx_unlock(&d->mtx); } static int ws_check_string(const void *v, size_t sz, nni_opt_type t) { if ((t != NNI_TYPE_OPAQUE) && (t != NNI_TYPE_STRING)) { return (NNG_EBADTYPE); } if (nni_strnlen(v, sz) >= sz) { return (NNG_EINVAL); } return (0); } static int ws_dialer_set_recvmaxsz(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer *d = arg; size_t val; int rv; if ((rv = nni_copyin_size(&val, v, sz, 0, NNI_MAXSZ, t)) == 0) { nni_mtx_lock(&d->mtx); d->rcvmax = val; nni_mtx_unlock(&d->mtx); nni_ws_dialer_set_maxframe(d->dialer, val); } return (rv); } static int ws_dialer_get_recvmaxsz(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_dialer *d = arg; int rv; nni_mtx_lock(&d->mtx); rv = nni_copyout_size(d->rcvmax, v, szp, t); nni_mtx_unlock(&d->mtx); return (rv); } static int ws_listener_set_recvmaxsz(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener *l = arg; size_t val; int rv; if ((rv = nni_copyin_size(&val, v, sz, 0, NNI_MAXSZ, t)) == 0) { nni_mtx_lock(&l->mtx); l->rcvmax = val; nni_mtx_unlock(&l->mtx); nni_ws_listener_set_maxframe(l->listener, val); } return (rv); } static int ws_listener_get_recvmaxsz(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_listener *l = arg; int rv; nni_mtx_lock(&l->mtx); rv = nni_copyout_size(l->rcvmax, v, szp, t); nni_mtx_unlock(&l->mtx); return (rv); } static int ws_check_recvmaxsz(const void *v, size_t sz, nni_opt_type t) { return (nni_copyin_size(NULL, v, sz, 0, NNI_MAXSZ, t)); } static int ws_set_headers(nni_list *headers, const char *v) { char * dupstr; size_t duplen; char * name; char * value; char * nl; nni_list l; ws_hdr * h; int rv; NNI_LIST_INIT(&l, ws_hdr, node); if ((dupstr = nni_strdup(v)) == NULL) { return (NNG_ENOMEM); } duplen = strlen(dupstr) + 1; // so we can free it later name = dupstr; for (;;) { if ((value = strchr(name, ':')) == NULL) { // Note that this also means that if // a bare word is present, we ignore it. break; } *value = '\0'; value++; while (*value == ' ') { // Skip leading whitespace. Not strictly // necessary, but still a good idea. value++; } nl = value; // Find the end of the line -- should be CRLF, but can // also be unterminated or just LF if user while ((*nl != '\0') && (*nl != '\r') && (*nl != '\n')) { nl++; } while ((*nl == '\r') || (*nl == '\n')) { *nl = '\0'; nl++; } if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { rv = NNG_ENOMEM; goto done; } nni_list_append(&l, h); if (((h->name = nni_strdup(name)) == NULL) || ((h->value = nni_strdup(value)) == NULL)) { rv = NNG_ENOMEM; goto done; } name = nl; } while ((h = nni_list_first(headers)) != NULL) { nni_list_remove(headers, h); nni_strfree(h->name); nni_strfree(h->value); NNI_FREE_STRUCT(h); } while ((h = nni_list_first(&l)) != NULL) { nni_list_remove(&l, h); nni_list_append(headers, h); } rv = 0; done: while ((h = nni_list_first(&l)) != NULL) { nni_list_remove(&l, h); nni_strfree(h->name); nni_strfree(h->value); NNI_FREE_STRUCT(h); } nni_free(dupstr, duplen); return (rv); } static int ws_dialer_set_reqhdrs(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer *d = arg; int rv; if (d->started) { return (NNG_EBUSY); } if ((rv = ws_check_string(v, sz, t)) == 0) { nni_mtx_lock(&d->mtx); rv = ws_set_headers(&d->headers, v); nni_mtx_unlock(&d->mtx); } return (rv); } static int ws_listener_set_reshdrs(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener *l = arg; int rv; if (l->started) { return (NNG_EBUSY); } if ((rv = ws_check_string(v, sz, t)) == 0) { nni_mtx_lock(&l->mtx); rv = ws_set_headers(&l->headers, v); nni_mtx_unlock(&l->mtx); } return (rv); } static int ws_pipe_get_locaddr(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_pipe * p = arg; int rv; nni_sockaddr sa; memset(&sa, 0, sizeof(sa)); if ((rv = nni_ws_sock_addr(p->ws, &sa)) == 0) { rv = nni_copyout_sockaddr(&sa, v, szp, t); } return (rv); } static int ws_pipe_get_remaddr(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_pipe * p = arg; int rv; nni_sockaddr sa; memset(&sa, 0, sizeof(sa)); if ((rv = nni_ws_peer_addr(p->ws, &sa)) == 0) { rv = nni_copyout_sockaddr(&sa, v, szp, t); } return (rv); } static int ws_pipe_get_reshdrs(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_pipe * p = arg; const char *s; if ((s = nni_ws_response_headers(p->ws)) == NULL) { return (NNG_ENOMEM); } return (nni_copyout_str(s, v, szp, t)); } static int ws_pipe_get_reqhdrs(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_pipe * p = arg; const char *s; if ((s = nni_ws_request_headers(p->ws)) == NULL) { return (NNG_ENOMEM); } return (nni_copyout_str(s, v, szp, t)); } static int ws_pipe_get_tls_verified(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_pipe *p = arg; return (nni_copyout_bool(nni_ws_tls_verified(p->ws), v, szp, t)); } static nni_tran_option ws_pipe_options[] = { { .o_name = NNG_OPT_LOCADDR, .o_type = NNI_TYPE_SOCKADDR, .o_get = ws_pipe_get_locaddr, }, { .o_name = NNG_OPT_REMADDR, .o_type = NNI_TYPE_SOCKADDR, .o_get = ws_pipe_get_remaddr, }, { .o_name = NNG_OPT_WS_REQUEST_HEADERS, .o_type = NNI_TYPE_STRING, .o_get = ws_pipe_get_reqhdrs, }, { .o_name = NNG_OPT_WS_RESPONSE_HEADERS, .o_type = NNI_TYPE_STRING, .o_get = ws_pipe_get_reshdrs, }, { .o_name = NNG_OPT_TLS_VERIFIED, .o_type = NNI_TYPE_BOOL, .o_get = ws_pipe_get_tls_verified, }, // terminate list { .o_name = NULL, } }; static nni_tran_pipe_ops ws_pipe_ops = { .p_fini = ws_pipe_fini, .p_stop = ws_pipe_stop, .p_send = ws_pipe_send, .p_recv = ws_pipe_recv, .p_close = ws_pipe_close, .p_peer = ws_pipe_peer, .p_options = ws_pipe_options, }; static nni_tran_option ws_dialer_options[] = { { .o_name = NNG_OPT_RECVMAXSZ, .o_type = NNI_TYPE_SIZE, .o_get = ws_dialer_get_recvmaxsz, .o_set = ws_dialer_set_recvmaxsz, .o_chk = ws_check_recvmaxsz, }, { .o_name = NNG_OPT_WS_REQUEST_HEADERS, .o_type = NNI_TYPE_STRING, .o_set = ws_dialer_set_reqhdrs, .o_chk = ws_check_string, }, // terminate list { .o_name = NULL, }, }; static nni_tran_option ws_listener_options[] = { { .o_name = NNG_OPT_RECVMAXSZ, .o_type = NNI_TYPE_SIZE, .o_get = ws_listener_get_recvmaxsz, .o_set = ws_listener_set_recvmaxsz, .o_chk = ws_check_recvmaxsz, }, { .o_name = NNG_OPT_WS_RESPONSE_HEADERS, .o_type = NNI_TYPE_STRING, .o_set = ws_listener_set_reshdrs, .o_chk = ws_check_string, }, // terminate list { .o_name = NULL, }, }; static void ws_dialer_fini(void *arg) { ws_dialer *d = arg; ws_hdr * hdr; nni_aio_stop(d->connaio); if (d->dialer != NULL) { nni_ws_dialer_fini(d->dialer); } nni_aio_fini(d->connaio); while ((hdr = nni_list_first(&d->headers)) != NULL) { nni_list_remove(&d->headers, hdr); nni_strfree(hdr->name); nni_strfree(hdr->value); NNI_FREE_STRUCT(hdr); } nni_strfree(d->prname); nni_mtx_fini(&d->mtx); NNI_FREE_STRUCT(d); } static void ws_listener_fini(void *arg) { ws_listener *l = arg; ws_hdr * hdr; nni_aio_stop(l->accaio); if (l->listener != NULL) { nni_ws_listener_fini(l->listener); } nni_aio_fini(l->accaio); while ((hdr = nni_list_first(&l->headers)) != NULL) { nni_list_remove(&l->headers, hdr); nni_strfree(hdr->name); nni_strfree(hdr->value); NNI_FREE_STRUCT(hdr); } nni_strfree(l->prname); nni_mtx_fini(&l->mtx); NNI_FREE_STRUCT(l); } static void ws_connect_cb(void *arg) { ws_dialer *d = arg; ws_pipe * p; nni_aio * caio = d->connaio; nni_aio * uaio; int rv; nni_ws * ws = NULL; nni_mtx_lock(&d->mtx); if (nni_aio_result(caio) == 0) { ws = nni_aio_get_output(caio, 0); } if ((uaio = nni_list_first(&d->aios)) == NULL) { // The client stopped caring about this! if (ws != NULL) { nni_ws_fini(ws); } nni_mtx_unlock(&d->mtx); return; } nni_aio_list_remove(uaio); NNI_ASSERT(nni_list_empty(&d->aios)); if ((rv = nni_aio_result(caio)) != 0) { nni_aio_finish_error(uaio, rv); } else if ((rv = ws_pipe_init(&p, ws)) != 0) { nni_ws_fini(ws); nni_aio_finish_error(uaio, rv); } else { p->rcvmax = d->rcvmax; p->rproto = d->rproto; p->lproto = d->lproto; nni_aio_set_output(uaio, 0, p); nni_aio_finish(uaio, 0, 0); } nni_mtx_unlock(&d->mtx); } static void ws_dialer_close(void *arg) { ws_dialer *d = arg; nni_aio_close(d->connaio); nni_ws_dialer_close(d->dialer); } static void ws_listener_close(void *arg) { ws_listener *l = arg; nni_aio_close(l->accaio); nni_ws_listener_close(l->listener); } static void ws_accept_cb(void *arg) { ws_listener *l = arg; nni_aio * aaio = l->accaio; nni_aio * uaio; int rv; nni_mtx_lock(&l->mtx); uaio = nni_list_first(&l->aios); if ((rv = nni_aio_result(aaio)) != 0) { if (uaio != NULL) { nni_aio_list_remove(uaio); nni_aio_finish_error(uaio, rv); } } else { nni_ws *ws = nni_aio_get_output(aaio, 0); if (uaio != NULL) { ws_pipe *p; // Make a pipe nni_aio_list_remove(uaio); if ((rv = ws_pipe_init(&p, ws)) != 0) { nni_ws_close(ws); nni_aio_finish_error(uaio, rv); } else { p->rcvmax = l->rcvmax; p->rproto = l->rproto; p->lproto = l->lproto; nni_aio_set_output(uaio, 0, p); nni_aio_finish(uaio, 0, 0); } } } if (!nni_list_empty(&l->aios)) { nni_ws_listener_accept(l->listener, aaio); } nni_mtx_unlock(&l->mtx); } static int ws_dialer_init(void **dp, nni_url *url, nni_sock *s) { ws_dialer * d; const char *n; int rv; if ((d = NNI_ALLOC_STRUCT(d)) == NULL) { return (NNG_ENOMEM); } nni_mtx_init(&d->mtx); NNI_LIST_INIT(&d->headers, ws_hdr, node); nni_aio_list_init(&d->aios); d->lproto = nni_sock_proto_id(s); d->rproto = nni_sock_peer_id(s); n = nni_sock_peer_name(s); if (((rv = nni_ws_dialer_init(&d->dialer, url)) != 0) || ((rv = nni_aio_init(&d->connaio, ws_connect_cb, d)) != 0) || ((rv = nni_asprintf(&d->prname, "%s.sp.nanomsg.org", n)) != 0) || ((rv = nni_ws_dialer_proto(d->dialer, d->prname)) != 0)) { ws_dialer_fini(d); return (rv); } *dp = d; return (0); } static int ws_listener_init(void **lp, nni_url *url, nni_sock *sock) { ws_listener *l; const char * n; int rv; if ((l = NNI_ALLOC_STRUCT(l)) == NULL) { return (NNG_ENOMEM); } nni_mtx_init(&l->mtx); NNI_LIST_INIT(&l->headers, ws_hdr, node); nni_aio_list_init(&l->aios); l->lproto = nni_sock_proto_id(sock); l->rproto = nni_sock_peer_id(sock); n = nni_sock_proto_name(sock); if (((rv = nni_ws_listener_init(&l->listener, url)) != 0) || ((rv = nni_aio_init(&l->accaio, ws_accept_cb, l)) != 0) || ((rv = nni_asprintf(&l->prname, "%s.sp.nanomsg.org", n)) != 0) || ((rv = nni_ws_listener_proto(l->listener, l->prname)) != 0)) { ws_listener_fini(l); return (rv); } *lp = l; return (0); } static int ws_tran_init(void) { return (0); } static void ws_tran_fini(void) { } static nni_tran_dialer_ops ws_dialer_ops = { .d_init = ws_dialer_init, .d_fini = ws_dialer_fini, .d_connect = ws_dialer_connect, .d_close = ws_dialer_close, .d_options = ws_dialer_options, }; static nni_tran_listener_ops ws_listener_ops = { .l_init = ws_listener_init, .l_fini = ws_listener_fini, .l_bind = ws_listener_bind, .l_accept = ws_listener_accept, .l_close = ws_listener_close, .l_options = ws_listener_options, }; static nni_tran ws_tran = { .tran_version = NNI_TRANSPORT_VERSION, .tran_scheme = "ws", .tran_dialer = &ws_dialer_ops, .tran_listener = &ws_listener_ops, .tran_pipe = &ws_pipe_ops, .tran_init = ws_tran_init, .tran_fini = ws_tran_fini, }; int nng_ws_register(void) { return (nni_tran_register(&ws_tran)); } #ifdef NNG_TRANSPORT_WSS static int wss_dialer_get_tlsconfig(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_dialer * d = arg; nng_tls_config *tls; int rv; if (((rv = nni_ws_dialer_get_tls(d->dialer, &tls)) != 0) || ((rv = nni_copyout_ptr(tls, v, szp, t)) != 0)) { return (rv); } return (0); } static int wss_listener_get_tlsconfig(void *arg, void *v, size_t *szp, nni_opt_type t) { ws_listener * l = arg; nng_tls_config *tls; int rv; if (((rv = nni_ws_listener_get_tls(l->listener, &tls)) != 0) || ((rv = nni_copyout_ptr(tls, v, szp, t)) != 0)) { return (rv); } return (0); } static int wss_check_tlsconfig(const void *v, size_t sz, nni_opt_type t) { void *p; int rv; if (((rv = nni_copyin_ptr(&p, v, sz, t)) == 0) && (p == NULL)) { rv = NNG_EINVAL; } return (rv); } static int wss_dialer_set_tlsconfig(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer * d = arg; nng_tls_config *cfg; int rv; if ((rv = nni_copyin_ptr((void **) &cfg, v, sz, t)) != 0) { return (rv); } if (cfg == NULL) { return (NNG_EINVAL); } return (nni_ws_dialer_set_tls(d->dialer, cfg)); } static int wss_listener_set_tlsconfig(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener * l = arg; nng_tls_config *cfg; int rv; if ((rv = nni_copyin_ptr((void **) &cfg, v, sz, t)) != 0) { return (rv); } if (cfg == NULL) { return (NNG_EINVAL); } return (nni_ws_listener_set_tls(l->listener, cfg)); } static int wss_dialer_set_cert_key_file( void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer * d = arg; int rv; nng_tls_config *tls; if (((rv = ws_check_string(v, sz, t)) != 0) || ((rv = nni_ws_dialer_get_tls(d->dialer, &tls)) != 0)) { return (rv); } return (nng_tls_config_cert_key_file(tls, v, NULL)); } static int wss_listener_set_cert_key_file( void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener * l = arg; int rv; nng_tls_config *tls; if (((rv = ws_check_string(v, sz, t)) != 0) || ((rv = nni_ws_listener_get_tls(l->listener, &tls)) != 0)) { return (rv); } return (nng_tls_config_cert_key_file(tls, v, NULL)); } static int wss_dialer_set_ca_file(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer * d = arg; int rv; nng_tls_config *tls; if (((rv = ws_check_string(v, sz, t)) != 0) || ((rv = nni_ws_dialer_get_tls(d->dialer, &tls)) != 0)) { return (rv); } return (nng_tls_config_ca_file(tls, v)); } static int wss_listener_set_ca_file(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener * l = arg; int rv; nng_tls_config *tls; if (((rv = ws_check_string(v, sz, t)) != 0) || ((rv = nni_ws_listener_get_tls(l->listener, &tls)) != 0)) { return (rv); } return (nng_tls_config_ca_file(tls, v)); } static int wss_check_auth_mode(const void *v, size_t sz, nni_opt_type t) { return (nni_copyin_int(NULL, v, sz, NNG_TLS_AUTH_MODE_NONE, NNG_TLS_AUTH_MODE_REQUIRED, t)); } static int wss_dialer_set_auth_mode(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer * d = arg; int rv; nng_tls_config *tls; int mode; rv = nni_copyin_int(&mode, v, sz, NNG_TLS_AUTH_MODE_NONE, NNG_TLS_AUTH_MODE_REQUIRED, t); if ((rv != 0) || ((rv = nni_ws_dialer_get_tls(d->dialer, &tls)) != 0)) { return (rv); } return (nng_tls_config_auth_mode(tls, mode)); } static int wss_listener_set_auth_mode(void *arg, const void *v, size_t sz, nni_opt_type t) { ws_listener * l = arg; int rv; nng_tls_config *tls; int mode; rv = nni_copyin_int(&mode, v, sz, NNG_TLS_AUTH_MODE_NONE, NNG_TLS_AUTH_MODE_REQUIRED, t); if ((rv != 0) || ((rv = nni_ws_listener_get_tls(l->listener, &tls)) != 0)) { return (rv); } return (nng_tls_config_auth_mode(tls, mode)); } static int wss_dialer_set_tls_server_name( void *arg, const void *v, size_t sz, nni_opt_type t) { ws_dialer * d = arg; int rv; nng_tls_config *tls; if (((rv = ws_check_string(v, sz, t)) != 0) || ((rv = nni_ws_dialer_get_tls(d->dialer, &tls)) != 0)) { return (rv); } return (nng_tls_config_server_name(tls, v)); } static nni_tran_option wss_dialer_options[] = { { .o_name = NNG_OPT_RECVMAXSZ, .o_type = NNI_TYPE_SIZE, .o_get = ws_dialer_get_recvmaxsz, .o_set = ws_dialer_set_recvmaxsz, .o_chk = ws_check_recvmaxsz, }, { .o_name = NNG_OPT_WS_REQUEST_HEADERS, .o_type = NNI_TYPE_STRING, .o_set = ws_dialer_set_reqhdrs, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_CONFIG, .o_type = NNI_TYPE_POINTER, .o_get = wss_dialer_get_tlsconfig, .o_set = wss_dialer_set_tlsconfig, .o_chk = wss_check_tlsconfig, }, { .o_name = NNG_OPT_TLS_CERT_KEY_FILE, .o_type = NNI_TYPE_STRING, .o_set = wss_dialer_set_cert_key_file, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_CA_FILE, .o_type = NNI_TYPE_STRING, .o_set = wss_dialer_set_ca_file, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_AUTH_MODE, .o_type = NNI_TYPE_INT32, .o_set = wss_dialer_set_auth_mode, .o_chk = wss_check_auth_mode, }, { .o_name = NNG_OPT_TLS_SERVER_NAME, .o_type = NNI_TYPE_STRING, .o_set = wss_dialer_set_tls_server_name, .o_chk = ws_check_string, }, // terminate list { .o_name = NULL, }, }; static nni_tran_option wss_listener_options[] = { { .o_name = NNG_OPT_RECVMAXSZ, .o_type = NNI_TYPE_SIZE, .o_get = ws_listener_get_recvmaxsz, .o_set = ws_listener_set_recvmaxsz, .o_chk = ws_check_recvmaxsz, }, { .o_name = NNG_OPT_WS_RESPONSE_HEADERS, .o_type = NNI_TYPE_STRING, .o_set = ws_listener_set_reshdrs, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_CONFIG, .o_type = NNI_TYPE_POINTER, .o_get = wss_listener_get_tlsconfig, .o_set = wss_listener_set_tlsconfig, .o_chk = wss_check_tlsconfig, }, { .o_name = NNG_OPT_TLS_CERT_KEY_FILE, .o_type = NNI_TYPE_STRING, .o_set = wss_listener_set_cert_key_file, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_CA_FILE, .o_type = NNI_TYPE_STRING, .o_set = wss_listener_set_ca_file, .o_chk = ws_check_string, }, { .o_name = NNG_OPT_TLS_AUTH_MODE, .o_type = NNI_TYPE_INT32, .o_set = wss_listener_set_auth_mode, .o_chk = wss_check_auth_mode, }, // terminate list { .o_name = NULL, }, }; static nni_tran_dialer_ops wss_dialer_ops = { .d_init = ws_dialer_init, .d_fini = ws_dialer_fini, .d_connect = ws_dialer_connect, .d_close = ws_dialer_close, .d_options = wss_dialer_options, }; static nni_tran_listener_ops wss_listener_ops = { .l_init = ws_listener_init, .l_fini = ws_listener_fini, .l_bind = ws_listener_bind, .l_accept = ws_listener_accept, .l_close = ws_listener_close, .l_options = wss_listener_options, }; static nni_tran wss_tran = { .tran_version = NNI_TRANSPORT_VERSION, .tran_scheme = "wss", .tran_dialer = &wss_dialer_ops, .tran_listener = &wss_listener_ops, .tran_pipe = &ws_pipe_ops, .tran_init = ws_tran_init, .tran_fini = ws_tran_fini, }; int nng_wss_register(void) { return (nni_tran_register(&wss_tran)); } #else int nng_wss_register(void) { return (0); } #endif // NNG_TRANSPORT_WSS