/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * Copyright (C) 2020 Miquel Ortega
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "prom_server.h"
#include "mainrelay.h"
#include "ns_turn_atomic.h" // for TURN_THREAD_LOCAL (portable thread-local on MSVC)
#include "ns_turn_ratelimit.h"
#include "ns_turn_utils.h"
#include <stdio.h>
#if !defined(WINDOWS)
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

#if !defined(TURN_NO_PROMETHEUS)

prom_counter_t *packet_processed;
prom_counter_t *packet_dropped;

prom_counter_t *stun_binding_request;
prom_counter_t *stun_binding_response;
prom_counter_t *stun_binding_error;

prom_counter_t *turn_unauthenticated_401_requests;
prom_counter_t *turn_unauthenticated_401_responses;
prom_counter_t *turn_unauthenticated_401_dropped_responses;

prom_counter_t *turn_ratelimit_hash_collisions;
prom_gauge_t *turn_ratelimit_occupied_buckets;
prom_gauge_t *turn_ratelimit_total_buckets;

prom_counter_t *turn_traffic_rcvp;
prom_counter_t *turn_traffic_rcvb;
prom_counter_t *turn_traffic_sentp;
prom_counter_t *turn_traffic_sentb;

prom_counter_t *turn_traffic_peer_rcvp;
prom_counter_t *turn_traffic_peer_rcvb;
prom_counter_t *turn_traffic_peer_sentp;
prom_counter_t *turn_traffic_peer_sentb;

prom_counter_t *turn_total_traffic_rcvp;
prom_counter_t *turn_total_traffic_rcvb;
prom_counter_t *turn_total_traffic_sentp;
prom_counter_t *turn_total_traffic_sentb;

prom_counter_t *turn_total_traffic_peer_rcvp;
prom_counter_t *turn_total_traffic_peer_rcvb;
prom_counter_t *turn_total_traffic_peer_sentp;
prom_counter_t *turn_total_traffic_peer_sentb;

prom_gauge_t *turn_total_allocations;

prom_counter_t *turn_udp_recvmmsg_calls;
prom_counter_t *turn_udp_recvmmsg_packets;
prom_counter_t *turn_udp_sendmmsg_flushes;
prom_counter_t *turn_udp_sendmmsg_datagrams;
prom_counter_t *turn_udp_sendmmsg_gso_datagrams;

#if MHD_VERSION >= 0x00097002
#define MHD_RESULT enum MHD_Result
#else
#define MHD_RESULT int
#endif

// Pull the 401 rate-limit table's self-instrumented stats into Prometheus
// metrics. Called only from the single MHD scrape thread, so the static delta
// tracker below needs no locking. The whole-table scan in
// ratelimit_count_occupied() touches the buckets once per scrape and never runs
// on the data path, so this stays off the hot path entirely.
static void prom_refresh_ratelimit_stats(void) {
  static uint32_t last_collisions = 0;
  uint32_t cur = ratelimit_get_collisions();
  uint32_t delta = cur - last_collisions; // unsigned subtraction is correct across the 2^32 wrap
  if (delta) {
    prom_counter_add(turn_ratelimit_hash_collisions, (double)delta, NULL);
    last_collisions = cur;
  }
  prom_gauge_set(turn_ratelimit_occupied_buckets, (double)ratelimit_count_occupied(), NULL);
}

static MHD_RESULT promhttp_handler(void *cls, struct MHD_Connection *connection, const char *url, const char *method,
                                   const char *version, const char *upload_data, size_t *upload_data_size,
                                   void **con_cls) {
  MHD_RESULT ret;
  (void)cls;
  (void)version;
  (void)upload_data;
  (void)upload_data_size;
  (void)con_cls;

  char *body = "not found";
  enum MHD_ResponseMemoryMode mode = MHD_RESPMEM_PERSISTENT;
  unsigned int status = MHD_HTTP_NOT_FOUND;

  if (strcmp(method, "GET") != 0) {
    status = MHD_HTTP_METHOD_NOT_ALLOWED;
    body = "method not allowed";
  } else if (strcmp(url, turn_params.prometheus_path) == 0) {
    prom_refresh_ratelimit_stats();
    body = (char *)prom_collector_registry_bridge(PROM_COLLECTOR_REGISTRY_DEFAULT);
    if (body == NULL) {
      return MHD_NO;
    }
    mode = MHD_RESPMEM_MUST_FREE;
    status = MHD_HTTP_OK;
  } else if (strcmp(url, "/") == 0) {
    // Return 200 OK for root path as a health check
    body = "ok";
    mode = MHD_RESPMEM_PERSISTENT;
    status = MHD_HTTP_OK;
  }

  struct MHD_Response *response = MHD_create_response_from_buffer(strlen(body), body, mode);
  if (response == NULL) {
    if (mode == MHD_RESPMEM_MUST_FREE) {
      free(body);
    }
    ret = MHD_NO;
  } else {
    MHD_add_response_header(response, "Content-Type", "text/plain; version=0.0.4; charset=utf-8");
    ret = MHD_queue_response(connection, status, response);
    MHD_destroy_response(response);
  }
  return ret;
}

/* Read an entire file into a freshly malloc'd NUL-terminated buffer, or return
 * NULL on error. Used to load the PEM cert/key for the HTTPS metrics endpoint. */
static char *prom_read_file(const char *path) {
  FILE *f = fopen(path, "rb");
  if (f == NULL) {
    return NULL;
  }
  char *buf = NULL;
  if (fseek(f, 0, SEEK_END) == 0) {
    long size = ftell(f);
    if (size >= 0 && fseek(f, 0, SEEK_SET) == 0) {
      buf = turn_malloc((size_t)size + 1);
      if (buf != NULL) {
        if (fread(buf, 1, (size_t)size, f) != (size_t)size) {
          free(buf);
          buf = NULL;
        } else {
          buf[size] = '\0';
        }
      }
    }
  }
  fclose(f);
  return buf;
}

void start_prometheus_server(void) {
  if (!turn_params.prometheus) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "prometheus collector disabled, not started\n");
    return;
  }
  prom_collector_registry_default_init();

  const char *label[] = {"realm", NULL};
  size_t nlabels = 1;

  if (turn_params.prometheus_username_labels) {
    label[1] = "user";
    nlabels++;
  }

  // Create STUN counters
  stun_binding_request = prom_collector_registry_must_register_metric(
      prom_counter_new("stun_binding_request", "Incoming STUN Binding requests", 0, NULL));
  stun_binding_response = prom_collector_registry_must_register_metric(
      prom_counter_new("stun_binding_response", "Outgoing STUN Binding responses", 0, NULL));
  stun_binding_error = prom_collector_registry_must_register_metric(
      prom_counter_new("stun_binding_error", "STUN Binding errors", 0, NULL));

  turn_unauthenticated_401_requests = prom_collector_registry_must_register_metric(prom_counter_new(
      "turn_unauthenticated_401_requests", "UDP requests requiring a 401 Unauthorized response", 0, NULL));
  turn_unauthenticated_401_responses = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_unauthenticated_401_responses", "UDP 401 Unauthorized responses emitted", 0, NULL));
  turn_unauthenticated_401_dropped_responses = prom_collector_registry_must_register_metric(prom_counter_new(
      "turn_unauthenticated_401_dropped_responses", "UDP 401 responses suppressed by DDoS mitigation", 0, NULL));

  // 401 rate-limit hash-table health. Refreshed lazily at scrape time from the
  // lock-free table's self-instrumented counters (see prom_refresh_ratelimit_stats).
  turn_ratelimit_hash_collisions = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_ratelimit_hash_collisions",
                       "401 rate-limit hash-bucket collisions (a source sharing another's live bucket)", 0, NULL));
  turn_ratelimit_occupied_buckets = prom_collector_registry_must_register_metric(prom_gauge_new(
      "turn_ratelimit_occupied_buckets", "401 rate-limit buckets currently tracking a live window", 0, NULL));
  turn_ratelimit_total_buckets = prom_collector_registry_must_register_metric(
      prom_gauge_new("turn_ratelimit_total_buckets", "401 rate-limit hash table capacity in buckets", 0, NULL));
  // Capacity is a compile-time constant; publish it once so occupancy can be
  // read as a utilization ratio in PromQL.
  prom_gauge_set(turn_ratelimit_total_buckets, (double)ratelimit_get_capacity(), NULL);

  // Create TURN traffic counter metrics
  turn_traffic_rcvp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_rcvp", "Represents finished sessions received packets", nlabels, label));
  turn_traffic_rcvb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_rcvb", "Represents finished sessions received bytes", nlabels, label));
  turn_traffic_sentp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_sentp", "Represents finished sessions sent packets", nlabels, label));
  turn_traffic_sentb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_sentb", "Represents finished sessions sent bytes", nlabels, label));

  // Create finished sessions traffic for peers counter metrics
  turn_traffic_peer_rcvp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_peer_rcvp", "Represents finished sessions peer received packets", nlabels, label));
  turn_traffic_peer_rcvb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_peer_rcvb", "Represents finished sessions peer received bytes", nlabels, label));
  turn_traffic_peer_sentp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_peer_sentp", "Represents finished sessions peer sent packets", nlabels, label));
  turn_traffic_peer_sentb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_traffic_peer_sentb", "Represents finished sessions peer sent bytes", nlabels, label));

  // Create total finished traffic counter metrics
  turn_total_traffic_rcvp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_total_traffic_rcvp", "Represents total finished sessions received packets", 0, NULL));
  turn_total_traffic_rcvb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_total_traffic_rcvb", "Represents total finished sessions received bytes", 0, NULL));
  turn_total_traffic_sentp = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_total_traffic_sentp", "Represents total finished sessions sent packets", 0, NULL));
  turn_total_traffic_sentb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_total_traffic_sentb", "Represents total finished sessions sent bytes", 0, NULL));

  // Create total finished sessions traffic for peers counter metrics
  turn_total_traffic_peer_rcvp = prom_collector_registry_must_register_metric(prom_counter_new(
      "turn_total_traffic_peer_rcvp", "Represents total finished sessions peer received packets", 0, NULL));
  turn_total_traffic_peer_rcvb = prom_collector_registry_must_register_metric(prom_counter_new(
      "turn_total_traffic_peer_rcvb", "Represents total finished sessions peer received bytes", 0, NULL));
  turn_total_traffic_peer_sentp = prom_collector_registry_must_register_metric(prom_counter_new(
      "turn_total_traffic_peer_sentp", "Represents total finished sessions peer sent packets", 0, NULL));
  turn_total_traffic_peer_sentb = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_total_traffic_peer_sentb", "Represents total finished sessions peer sent bytes", 0, NULL));

  // Create total allocations number gauge metric
  const char *typeLabel[] = {"type"};
  turn_total_allocations = prom_collector_registry_must_register_metric(
      prom_gauge_new("turn_total_allocations", "Represents current allocations number", 1, typeLabel));
  // Packet counters
  packet_processed = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_packet_processed", "Incoming packet processed", 0, NULL));
  packet_dropped = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_packet_dropped", "Incoming packet dropped", 0, NULL));

  // UDP recvmmsg/sendmmsg batching counters (Linux). Bumped once per syscall;
  // average batch size = rate(packets)/rate(calls) and rate(datagrams)/rate(flushes).
  turn_udp_recvmmsg_calls = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_udp_recvmmsg_calls", "recvmmsg() syscalls that returned at least one datagram", 0, NULL));
  turn_udp_recvmmsg_packets = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_udp_recvmmsg_packets", "Datagrams received via recvmmsg()", 0, NULL));
  turn_udp_sendmmsg_flushes = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_udp_sendmmsg_flushes", "Egress batch flushes (sendmmsg/UDP-GSO)", 0, NULL));
  turn_udp_sendmmsg_datagrams = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_udp_sendmmsg_datagrams", "Datagrams sent via egress batches", 0, NULL));
  turn_udp_sendmmsg_gso_datagrams = prom_collector_registry_must_register_metric(
      prom_counter_new("turn_udp_sendmmsg_gso_datagrams", "Datagrams coalesced via a single UDP-GSO sendmsg", 0, NULL));

  // some flags appeared first in microhttpd v0.9.53
  unsigned int flags = 0;
#if MHD_VERSION >= 0x00095300
  flags |= MHD_USE_ERROR_LOG;
#endif
  if (MHD_is_feature_supported(MHD_FEATURE_EPOLL)) {
#if MHD_VERSION >= 0x00095300
    flags |= MHD_USE_EPOLL_INTERNAL_THREAD;
#else
    flags |= MHD_USE_EPOLL_INTERNALLY_LINUX_ONLY; // old versions of microhttpd
#endif
    TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "prometheus exporter server will start using EPOLL\n");
  } else {
    flags |= MHD_USE_SELECT_INTERNALLY;
    // Select() will not work if all 1024 first file-descriptors are used.
    // In this case the prometheus server will be unreachable
    TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "prometheus exporter server will start using SELECT. "
                                          "The exporter might be unreachable on highly used servers\n");
  }

  // Optional HTTPS for the metrics endpoint. The cert/key buffers are kept for
  // the daemon's (process) lifetime, as libmicrohttpd references them.
  char *https_cert = NULL;
  char *https_key = NULL;
  if (turn_params.prometheus_tls) {
    const char *cert_path =
        turn_params.prometheus_cert_file[0] ? turn_params.prometheus_cert_file : turn_params.cert_file;
    const char *key_path =
        turn_params.prometheus_pkey_file[0] ? turn_params.prometheus_pkey_file : turn_params.pkey_file;
    if (!cert_path[0] || !key_path[0]) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "prometheus TLS requested but no certificate/key is configured "
                                          "(set --prometheus-cert/--prometheus-key or the server --cert/--pkey)\n");
      return;
    }
#if MHD_VERSION >= 0x00095300
    if (!MHD_is_feature_supported(MHD_FEATURE_TLS)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "prometheus TLS requested but libmicrohttpd was built without TLS support\n");
      return;
    }
#endif
    https_cert = prom_read_file(cert_path);
    https_key = prom_read_file(key_path);
    if (https_cert == NULL || https_key == NULL) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "prometheus TLS: could not read certificate '%s' or key '%s'\n", cert_path,
                    key_path);
      free(https_cert);
      free(https_key);
      return;
    }
#if MHD_VERSION >= 0x00095300
    flags |= MHD_USE_TLS;
#else
    flags |= MHD_USE_SSL;
#endif
  }

  ioa_addr server_addr;
  addr_set_any(&server_addr);
  if (turn_params.prometheus_address[0]) {
    if (make_ioa_addr((const uint8_t *)turn_params.prometheus_address, turn_params.prometheus_port, &server_addr) < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "could not parse prometheus collector's server address\n");
      return;
    }

    if (is_ipv6_enabled() && server_addr.ss.sa_family == AF_INET6) {
      flags |= MHD_USE_IPv6;
    }
  } else {
    if (MHD_is_feature_supported(MHD_FEATURE_IPv6) && is_ipv6_enabled()) {
      flags |= MHD_USE_DUAL_STACK;
      server_addr.ss.sa_family = AF_INET6;
      server_addr.s6.sin6_port = htons((uint16_t)turn_params.prometheus_port);
    } else {
      server_addr.ss.sa_family = AF_INET;
      server_addr.s4.sin_port = htons((uint16_t)turn_params.prometheus_port);
    }
  }

  char addr[MAX_IOA_ADDR_STRING] = "";
  addr_to_string(&server_addr, addr);
  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "prometheus exporter server will listen on %s (%s)\n", addr,
                turn_params.prometheus_tls ? "https" : "http");

  struct MHD_Daemon *daemon;
  if (turn_params.prometheus_tls) {
    daemon = MHD_start_daemon(flags, 0, NULL, NULL, &promhttp_handler, NULL, MHD_OPTION_LISTENING_ADDRESS_REUSE, 1,
                              MHD_OPTION_SOCK_ADDR, &server_addr, MHD_OPTION_HTTPS_MEM_CERT, https_cert,
                              MHD_OPTION_HTTPS_MEM_KEY, https_key, MHD_OPTION_END);
  } else {
    daemon = MHD_start_daemon(flags, 0, NULL, NULL, &promhttp_handler, NULL, MHD_OPTION_LISTENING_ADDRESS_REUSE, 1,
                              MHD_OPTION_SOCK_ADDR, &server_addr, MHD_OPTION_END);
  }
  if (daemon == NULL) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "could not start prometheus collector\n");
    free(https_cert);
    free(https_key);
    return;
  }

  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "prometheus collector started successfully\n");
}

void prom_set_finished_traffic(const char *realm, const char *user, unsigned long rsvp, unsigned long rsvb,
                               unsigned long sentp, unsigned long sentb, bool peer) {
  if (turn_params.prometheus) {

    const char *label[] = {realm, NULL};
    if (turn_params.prometheus_username_labels) {
      label[1] = user;
    }

    if (peer) {
      prom_counter_add(turn_traffic_peer_rcvp, rsvp, label);
      prom_counter_add(turn_traffic_peer_rcvb, rsvb, label);
      prom_counter_add(turn_traffic_peer_sentp, sentp, label);
      prom_counter_add(turn_traffic_peer_sentb, sentb, label);

      prom_counter_add(turn_total_traffic_peer_rcvp, rsvp, NULL);
      prom_counter_add(turn_total_traffic_peer_rcvb, rsvb, NULL);
      prom_counter_add(turn_total_traffic_peer_sentp, sentp, NULL);
      prom_counter_add(turn_total_traffic_peer_sentb, sentb, NULL);
    } else {
      prom_counter_add(turn_traffic_rcvp, rsvp, label);
      prom_counter_add(turn_traffic_rcvb, rsvb, label);
      prom_counter_add(turn_traffic_sentp, sentp, label);
      prom_counter_add(turn_traffic_sentb, sentb, label);

      prom_counter_add(turn_total_traffic_rcvp, rsvp, NULL);
      prom_counter_add(turn_total_traffic_rcvb, rsvb, NULL);
      prom_counter_add(turn_total_traffic_sentp, sentp, NULL);
      prom_counter_add(turn_total_traffic_sentb, sentb, NULL);
    }
  }
}

void prom_inc_allocation(SOCKET_TYPE type) {
  if (turn_params.prometheus) {
    const char *label[] = {socket_type_name(type)};
    prom_gauge_inc(turn_total_allocations, label);
  }
}

void prom_dec_allocation(SOCKET_TYPE type) {
  if (turn_params.prometheus) {
    const char *label[] = {socket_type_name(type)};
    prom_gauge_dec(turn_total_allocations, label);
  }
}

void prom_flush_udp_counters(const struct prom_udp_counter_deltas *d) {
  if (!turn_params.prometheus || !d) {
    return;
  }
  if (d->packets_processed) {
    prom_counter_add(packet_processed, (double)d->packets_processed, NULL);
  }
  if (d->packets_dropped) {
    prom_counter_add(packet_dropped, (double)d->packets_dropped, NULL);
  }
  if (d->recvmmsg_calls) {
    prom_counter_add(turn_udp_recvmmsg_calls, (double)d->recvmmsg_calls, NULL);
  }
  if (d->recvmmsg_packets) {
    prom_counter_add(turn_udp_recvmmsg_packets, (double)d->recvmmsg_packets, NULL);
  }
  if (d->sendmmsg_flushes) {
    prom_counter_add(turn_udp_sendmmsg_flushes, (double)d->sendmmsg_flushes, NULL);
  }
  if (d->sendmmsg_datagrams) {
    prom_counter_add(turn_udp_sendmmsg_datagrams, (double)d->sendmmsg_datagrams, NULL);
  }
  if (d->sendmmsg_gso_datagrams) {
    prom_counter_add(turn_udp_sendmmsg_gso_datagrams, (double)d->sendmmsg_gso_datagrams, NULL);
  }
}

/* The 401 mitigation counters are bumped on the relay hot path (once per
 * unauthenticated UDP request -- the reflection surface, and the busiest path
 * under the very flood this feature mitigates). To keep the global prom_counter
 * locks off that path, each relay thread accumulates into lock-free
 * thread-local counters here and prom_flush_401_counters() pushes the deltas
 * once per second from the engine timer -- mirroring the UDP packet counters
 * (see prom_flush_udp_counters / maybe_flush_prom_counters). */
static TURN_THREAD_LOCAL uint64_t tl_401_requests, tl_401_requests_flushed;
static TURN_THREAD_LOCAL uint64_t tl_401_responses, tl_401_responses_flushed;
static TURN_THREAD_LOCAL uint64_t tl_401_dropped, tl_401_dropped_flushed;

void prom_inc_unauthenticated_401_request(void) {
  if (turn_params.prometheus) {
    tl_401_requests++;
  }
}

void prom_inc_unauthenticated_401_response(void) {
  if (turn_params.prometheus) {
    tl_401_responses++;
  }
}

void prom_inc_unauthenticated_401_dropped_response(void) {
  if (turn_params.prometheus) {
    tl_401_dropped++;
  }
}

void prom_flush_401_counters(void) {
  if (!turn_params.prometheus) {
    return;
  }
  uint64_t d;
  if ((d = tl_401_requests - tl_401_requests_flushed)) {
    prom_counter_add(turn_unauthenticated_401_requests, (double)d, NULL);
    tl_401_requests_flushed = tl_401_requests;
  }
  if ((d = tl_401_responses - tl_401_responses_flushed)) {
    prom_counter_add(turn_unauthenticated_401_responses, (double)d, NULL);
    tl_401_responses_flushed = tl_401_responses;
  }
  if ((d = tl_401_dropped - tl_401_dropped_flushed)) {
    prom_counter_add(turn_unauthenticated_401_dropped_responses, (double)d, NULL);
    tl_401_dropped_flushed = tl_401_dropped;
  }
}

void prom_inc_stun_binding_request(void) {
  if (turn_params.prometheus) {
    prom_counter_add(stun_binding_request, 1, NULL);
  }
}

void prom_inc_stun_binding_response(void) {
  if (turn_params.prometheus) {
    prom_counter_add(stun_binding_response, 1, NULL);
  }
}

void prom_inc_stun_binding_error(void) {
  if (turn_params.prometheus) {
    prom_counter_add(stun_binding_error, 1, NULL);
  }
}

int is_ipv6_enabled(void) {
  int ret = 0;

#ifdef AF_INET6
  int fd = socket(AF_INET6, SOCK_STREAM, 0);
  if (fd == -1) {
    ret = errno != EAFNOSUPPORT;
  } else {
    ret = 1;
    close(fd);
  }
#endif /* AF_INET6 */

  return ret;
}

#else

void start_prometheus_server(void) {
  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "turnserver compiled without prometheus support\n");
  return;
}

void prom_set_finished_traffic(const char *realm, const char *user, unsigned long rsvp, unsigned long rsvb,
                               unsigned long sentp, unsigned long sentb, bool peer) {
  UNUSED_ARG(realm);
  UNUSED_ARG(user);
  UNUSED_ARG(rsvp);
  UNUSED_ARG(rsvb);
  UNUSED_ARG(sentp);
  UNUSED_ARG(sentb);
  UNUSED_ARG(peer);
}

void prom_inc_allocation(SOCKET_TYPE type) { UNUSED_ARG(type); }

void prom_dec_allocation(SOCKET_TYPE type) { UNUSED_ARG(type); }

void prom_flush_udp_counters(const struct prom_udp_counter_deltas *d) { UNUSED_ARG(d); }

void prom_inc_unauthenticated_401_request(void) {}

void prom_inc_unauthenticated_401_response(void) {}

void prom_inc_unauthenticated_401_dropped_response(void) {}

void prom_flush_401_counters(void) {}

#endif /* TURN_NO_PROMETHEUS */
