diff options
Diffstat (limited to '')
-rw-r--r-- | src/output/dnssim/https2.c | 592 |
1 files changed, 0 insertions, 592 deletions
diff --git a/src/output/dnssim/https2.c b/src/output/dnssim/https2.c deleted file mode 100644 index 72fcdaf..0000000 --- a/src/output/dnssim/https2.c +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (c) 2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" -#include "lib/base64url.h" - -#include <gnutls/gnutls.h> -#include <string.h> - -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - -#define OUTPUT_DNSSIM_MAKE_NV(NAME, VALUE, VALUELEN) \ - { \ - (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \ - NGHTTP2_NV_FLAG_NONE \ - } - -#define OUTPUT_DNSSIM_MAKE_NV2(NAME, VALUE) \ - { \ - (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ - NGHTTP2_NV_FLAG_NONE \ - } - -#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE "?dns=" -#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN (sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1) -#define OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS 100 -#define OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static ssize_t _http2_send(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - - mldebug("http2 (%p): sending data, len=%ld", session, length); - - ssize_t len = 0; - if ((len = gnutls_record_send(conn->tls->session, data, length)) < 0) { - mlwarning("gnutls_record_send failed: %s", gnutls_strerror(len)); - len = NGHTTP2_ERR_CALLBACK_FAILURE; - } - - return len; -} - -static ssize_t _http2_on_data_provider_read(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -{ - _output_dnssim_https2_data_provider_t* buffer = source->ptr; - mlassert(buffer, "no data provider"); - mlassert(buffer->len <= MAX_DNSMSG_SIZE, "invalid dnsmsg size: %zu B", buffer->len); - - ssize_t sent = (length < buffer->len) ? length : buffer->len; - mlassert(sent >= 0, "negative length of bytes to send"); - - memcpy(buf, buffer->buf, sent); - buffer->buf += sent; - buffer->len -= sent; - if (buffer->len == 0) - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - - return sent; -} - -static _output_dnssim_query_tcp_t* _http2_get_stream_qry(_output_dnssim_connection_t* conn, int32_t stream_id) -{ - mlassert(conn, "conn is nil"); - mlassert(stream_id >= 0, "invalid stream_id"); - - _output_dnssim_query_tcp_t* qry = (_output_dnssim_query_tcp_t*)conn->sent; - while (qry != NULL && qry->stream_id != stream_id) { - qry = (_output_dnssim_query_tcp_t*)qry->qry.next; - } - - return qry; -} - -static int _http2_on_header(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data) -{ - if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { - if (namelen == 7 && strncmp((char*)name, ":status", 7) == 0) { - if (valuelen != 3 || (value[0] != '1' && value[0] != '2')) { - /* When reponse code isn't 1xx or 2xx, close the query. - * This will result in request timeout, which currently seems - * slightly better than mocking SERVFAIL for statistics. */ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn is nil"); - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id); - - if (qry != NULL) { - _output_dnssim_close_query_https2(qry); - mlinfo("http response %s, closing query", value); - } - } - } - } - return 0; -} - -static int _http2_on_data_recv(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn is nil"); - - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, stream_id); - - mldebug("http2: data chunk recv, session=%p, len=%d", session, len); - - if (qry) { - if (qry->recv_buf_len == 0) { - if (len > MAX_DNSMSG_SIZE) { - mlwarning("http response exceeded maximum size of dns message"); - return -1; - } - mlfatal_oom(qry->recv_buf = malloc(len)); - memcpy(qry->recv_buf, data, len); - qry->recv_buf_len = len; - } else { - size_t total_len = qry->recv_buf_len + len; - if (total_len > MAX_DNSMSG_SIZE) { - mlwarning("http response exceeded maximum size of dns message"); - return -1; - } - mlfatal_oom(qry->recv_buf = realloc(qry->recv_buf, total_len)); - memcpy(qry->recv_buf + qry->recv_buf_len, data, len); - qry->recv_buf_len = total_len; - } - } else { - mldebug("no query associated with this stream id, ignoring"); - } - - return 0; -} - -static void _http2_check_max_streams(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be null"); - mlassert(conn->http2, "conn must have http2 ctx"); - - switch (conn->state) { - case _OUTPUT_DNSSIM_CONN_ACTIVE: - if (conn->http2->open_streams >= conn->http2->max_concurrent_streams) { - mlinfo("http2 (%p): reached maximum number of concurrent streams (%ld)", - conn->http2->session, conn->http2->max_concurrent_streams); - conn->state = _OUTPUT_DNSSIM_CONN_CONGESTED; - } - break; - case _OUTPUT_DNSSIM_CONN_CONGESTED: - if (conn->http2->open_streams < conn->http2->max_concurrent_streams) - conn->state = _OUTPUT_DNSSIM_CONN_ACTIVE; - break; - default: - break; - } -} - -static int _http2_on_stream_close(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->open_streams > 0, "conn has no open streams"); - - conn->http2->open_streams--; - _http2_check_max_streams(conn); - return 0; -} - -static int _http2_on_frame_recv(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - mlassert(conn->http2, "conn must have http2 ctx"); - - switch (frame->hd.type) { - case NGHTTP2_DATA: - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - mldebug("http2 (%p): final DATA frame recv", session); - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id); - - if (qry != NULL) { - conn->http2->current_qry = qry; - _output_dnssim_read_dnsmsg(conn, qry->recv_buf_len, (char*)qry->recv_buf); - } - } - break; - case NGHTTP2_SETTINGS: - if (!conn->http2->remote_settings_received) { - /* On the first SETTINGS frame, set concurrent streams to unlimited, same as nghttp2. */ - conn->http2->remote_settings_received = true; - conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; - _http2_check_max_streams(conn); - } - nghttp2_settings* settings = (nghttp2_settings*)frame; - int i; - for (i = 0; i < settings->niv; i++) { - switch (settings->iv[i].settings_id) { - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - conn->http2->max_concurrent_streams = settings->iv[i].value; - _http2_check_max_streams(conn); - break; - default: - break; - } - } - break; - default: - break; - } - return 0; -} - -int _output_dnssim_https2_init(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls == NULL, "conn already has tls context"); - mlassert(conn->http2 == NULL, "conn already has http2 context"); - mlassert(conn->client, "conn must be associated with a client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - int ret = -1; - nghttp2_session_callbacks* callbacks; - nghttp2_option* option; - output_dnssim_t* self = conn->client->dnssim; - - /* Initialize TLS session. */ - ret = _output_dnssim_tls_init(conn); - if (ret < 0) - return ret; - - /* Configure ALPN to negotiate HTTP/2. */ - const gnutls_datum_t protos[] = { - { (unsigned char*)"h2", 2 } - }; - ret = gnutls_alpn_set_protocols(conn->tls->session, protos, 1, 0); - if (ret < 0) { - lwarning("failed to set ALPN protocol: %s", gnutls_strerror(ret)); - return ret; - } - - lfatal_oom(conn->http2 = calloc(1, sizeof(_output_dnssim_http2_ctx_t))); - conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS; - - /* Set up HTTP/2 callbacks and client. */ - lassert(nghttp2_session_callbacks_new(&callbacks) == 0, "out of memory"); - nghttp2_session_callbacks_set_send_callback(callbacks, _http2_send); - nghttp2_session_callbacks_set_on_header_callback(callbacks, _http2_on_header); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, _http2_on_data_recv); - nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, _http2_on_frame_recv); - nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, _http2_on_stream_close); - - lassert(nghttp2_option_new(&option) == 0, "out of memory"); - nghttp2_option_set_peer_max_concurrent_streams(option, conn->http2->max_concurrent_streams); - - ret = nghttp2_session_client_new2(&conn->http2->session, callbacks, conn, option); - - nghttp2_session_callbacks_del(callbacks); - nghttp2_option_del(option); - - if (ret < 0) { - free(conn->http2); - conn->http2 = NULL; - } - - return ret; -} - -int _output_dnssim_https2_setup(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - - int ret = -1; - - /* Check "h2" protocol was negotiated with ALPN. */ - gnutls_datum_t proto; - ret = gnutls_alpn_get_selected_protocol(conn->tls->session, &proto); - if (ret < 0) { - mlwarning("http2: failed to get negotiated protocol: %s", gnutls_strerror(ret)); - return ret; - } - if (proto.size != 2 || memcmp("h2", proto.data, 2) != 0) { - mlwarning("http2: protocol is not negotiated"); - return ret; - } - - /* Submit SETTIGNS frame. */ - static const nghttp2_settings_entry iv[] = { - { NGHTTP2_SETTINGS_MAX_FRAME_SIZE, MAX_DNSMSG_SIZE }, - { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 }, /* Only we can initiate streams. */ - }; - ret = nghttp2_submit_settings(conn->http2->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv) / sizeof(*iv)); - if (ret < 0) { - mlwarning("http2: failed to submit SETTINGS: %s", nghttp2_strerror(ret)); - return ret; - } - - ret = 0; - return ret; -} - -void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - - /* Process incoming frames. */ - ssize_t ret = 0; - conn->prevent_close = true; - ret = nghttp2_session_mem_recv(conn->http2->session, (uint8_t*)data, len); - conn->prevent_close = false; - if (ret < 0) { - mlwarning("failed nghttp2_session_mem_recv: %s", nghttp2_strerror(ret)); - _output_dnssim_conn_close(conn); - return; - } else if (conn->state == _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED) { - _output_dnssim_conn_close(conn); - return; - } - mlassert(ret == len, "nghttp2_session_mem_recv didn't process all data"); - - /* Send any frames the read might have triggered. */ - ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mlwarning("failed nghttp2_session_send: %s", nghttp2_strerror(ret)); - _output_dnssim_conn_close(conn); - return; - } -} - -int _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req) -{ - mlassert_self(); - lassert(req, "req is nil"); - lassert(req->client, "request must have a client associated with it"); - - _output_dnssim_query_tcp_t* qry; - - lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t))); - - qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_HTTPS2; - qry->qry.req = req; - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE; - qry->stream_id = -1; - req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req - _ll_append(req->client->pending, &qry->qry); - - return _output_dnssim_handle_pending_queries(req->client); -} - -void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "query must be part of a request"); - _output_dnssim_request_t* req = qry->qry.req; - mlassert(req->client, "request must belong to a client"); - - _ll_try_remove(req->client->pending, &qry->qry); - if (qry->conn) { - _output_dnssim_connection_t* conn = qry->conn; - _ll_try_remove(conn->sent, &qry->qry); - qry->conn = NULL; - _output_dnssim_conn_idle(conn); - } - - if (qry->recv_buf != NULL) - free(qry->recv_buf); - - _ll_remove(req->qry, &qry->qry); - free(qry); -} - -void _output_dnssim_https2_close(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->http2, "conn must have http2 ctx"); - - nghttp2_session_del(conn->http2->session); - _output_dnssim_tls_close(conn); -} - -static int _http2_send_query_get(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(conn, "conn can't be null"); - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->payload, "payload can't be null"); - mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - core_object_payload_t* content = qry->qry.req->payload; - - const size_t uri_path_len = strlen(_self->h2_uri_path); - const size_t path_len = uri_path_len - + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN - + (content->len * 4) / 3 + 3 /* upper limit of base64 encoding */ - + 1; /* terminating null byte */ - if (path_len >= _MAX_URI_LEN) { - self->discarded++; - linfo("http2: uri path with query too long, query discarded"); - return 0; - } - char path[path_len]; - memcpy(path, _self->h2_uri_path, uri_path_len); - memcpy(&path[uri_path_len], OUTPUT_DNSSIM_HTTP_GET_TEMPLATE, OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN); - - int32_t ret = base64url_encode(content->payload, content->len, - (uint8_t*)&path[uri_path_len + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN], - sizeof(path) - uri_path_len - OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN - 1); - if (ret < 0) { - self->discarded++; - linfo("http2: base64url encode of query failed, query discarded"); - return 0; - } - - nghttp2_nv hdrs[] = { - OUTPUT_DNSSIM_MAKE_NV2(":method", "GET"), - OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"), - OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)), - OUTPUT_DNSSIM_MAKE_NV(":path", path, uri_path_len + sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1 + ret), - OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"), - }; - - qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), NULL, NULL); - - if (qry->stream_id < 0) { - mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id)); - return -1; - } - mldebug("http2 (%p): GET %s", conn->http2->session, path); - conn->http2->open_streams++; - _http2_check_max_streams(conn); - - ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret)); - return -1; - } - - return 0; -} - -static int _http2_send_query_post(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(conn, "conn can't be null"); - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->payload, "payload can't be null"); - mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - - core_object_payload_t* content = qry->qry.req->payload; - - int window_size = nghttp2_session_get_remote_window_size(conn->http2->session); - if (content->len > window_size) { - mldebug("http2 (%p): insufficient remote window size, deferring", conn->http2->session); - return 0; - } - - char content_length[6]; /* max dnslen "65535" */ - int content_length_len = snprintf(content_length, 6, "%zd", content->len); - - nghttp2_nv hdrs[] = { - OUTPUT_DNSSIM_MAKE_NV2(":method", "POST"), - OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"), - OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)), - OUTPUT_DNSSIM_MAKE_NV(":path", _self->h2_uri_path, strlen(_self->h2_uri_path)), - OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"), - OUTPUT_DNSSIM_MAKE_NV2("content-type", "application/dns-message"), - OUTPUT_DNSSIM_MAKE_NV("content-length", content_length, content_length_len) - }; - - _output_dnssim_https2_data_provider_t data = { - .buf = content->payload, - .len = content->len - }; - - nghttp2_data_provider data_provider = { - .source.ptr = &data, - .read_callback = _http2_on_data_provider_read - }; - - qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), &data_provider, NULL); - - if (qry->stream_id < 0) { - mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id)); - return -1; - } - mldebug("http2 (%p): POST payload len=%ld", conn->http2->session, content->len); - conn->http2->open_streams++; - _http2_check_max_streams(conn); - - window_size = nghttp2_session_get_stream_remote_window_size(conn->http2->session, qry->stream_id); - mlassert(content->len <= window_size, - "unsupported: http2 stream window size (%ld B) is smaller than dns payload (%ld B)", - window_size, content->len); - - int ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret)); - return -1; - } - - return 0; -} - -void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write"); - mlassert(conn, "conn can't be null"); - mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->pending, "conn has no pending queries"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - int ret = 0; - output_dnssim_t* self = conn->client->dnssim; - - if (!nghttp2_session_check_request_allowed(conn->http2->session)) { - mldebug("http2 (%p): request not allowed", conn->http2->session); - _output_dnssim_conn_close(conn); - return; - } - - switch (_self->h2_method) { - case OUTPUT_DNSSIM_H2_POST: - ret = _http2_send_query_post(conn, qry); - break; - case OUTPUT_DNSSIM_H2_GET: - ret = _http2_send_query_get(conn, qry); - break; - default: - lfatal("http2: unsupported method"); - } - - if (ret < 0) { - _output_dnssim_conn_close(conn); - return; - } - - qry->conn = conn; - _ll_remove(conn->client->pending, &qry->qry); - _ll_append(conn->sent, &qry->qry); - - /* Stop idle timer, since there are queries to answer now. */ - if (conn->idle_timer != NULL) { - conn->is_idle = false; - uv_timer_stop(conn->idle_timer); - } - - qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT; -} - -#endif |