/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
#include "unitcheck.h"

#include "urldata.h"
#include "cfilters.h"
#include "curl_trc.h"
#include "select.h"

/* Test the gating in Curl_conn_adjust_pollset(): each filter chain
 * of a connection must only get to adjust the pollset when the transfer
 * wants to send or receive, or when that chain itself is connecting or
 * shutting down. One chain being in connect or shutdown must not add
 * poll events for the other, see #21671. */

struct t2606_ctx {
  int calls;
  bool add_socket;
  curl_socket_t sock;
};

static void t2606_cf_destroy(struct Curl_cfilter *cf,
                             struct Curl_easy *data)
{
  (void)data;
  /* ctx is owned by the test case, nothing to free */
  cf->ctx = NULL;
}

static CURLcode t2606_cf_connect(struct Curl_cfilter *cf,
                                 struct Curl_easy *data,
                                 bool *done)
{
  (void)cf;
  (void)data;
  *done = TRUE;
  return CURLE_OK;
}

static CURLcode t2606_cf_adjust_pollset(struct Curl_cfilter *cf,
                                        struct Curl_easy *data,
                                        struct easy_pollset *ps)
{
  struct t2606_ctx *ctx = cf->ctx;
  ctx->calls++;
  if(ctx->add_socket)
    return Curl_pollset_add_in(data, ps, ctx->sock);
  return CURLE_OK;
}

static const struct Curl_cftype t2606_cft = {
  "TEST-PS",
  CF_TYPE_IP_CONNECT,
  CURL_LOG_LVL_NONE,
  t2606_cf_destroy,
  t2606_cf_connect,
  Curl_cf_def_shutdown,
  t2606_cf_adjust_pollset,
  Curl_cf_def_data_pending,
  Curl_cf_def_send,
  Curl_cf_def_recv,
  Curl_cf_def_cntrl,
  Curl_cf_def_conn_is_alive,
  Curl_cf_def_conn_keep_alive,
  Curl_cf_def_query,
};

struct t2606_case {
  int id;
  bool has_c1;        /* a SECONDARYSOCKET chain exists */
  bool c0_connected;
  bool c1_connected;
  bool c0_shutdown;   /* shutdown started on the chain */
  bool c1_shutdown;
  bool xfer_io;       /* the pollset holds a transfer socket already */
  bool c0_adds;       /* chain 0 adds a socket when called */
  int exp_c0_calls;
  int exp_c1_calls;
};

static CURLcode t2606_setup(CURL **easy)
{
  CURLcode result = CURLE_OK;

  global_init(CURL_GLOBAL_ALL);
  *easy = curl_easy_init();
  if(!*easy) {
    curl_global_cleanup();
    return CURLE_OUT_OF_MEMORY;
  }
  return result;
}

static void t2606_stop(CURL *easy)
{
  curl_easy_cleanup(easy);
  curl_global_cleanup();
}

static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc)
{
  struct connectdata *conn;
  struct Curl_cfilter *cf0 = NULL, *cf1 = NULL;
  struct t2606_ctx ctx0, ctx1;
  struct easy_pollset ps;
  CURLcode result;
  char msg[128];

  conn = curlx_calloc(1, sizeof(*conn));
  abort_if(!conn, "could not allocate a connection");

  conn->created = curlx_now();
  conn->shutdown.start_ms[FIRSTSOCKET] =
    conn->shutdown.start_ms[SECONDARYSOCKET] = -1;

  memset(&ctx0, 0, sizeof(ctx0));
  memset(&ctx1, 0, sizeof(ctx1));
  ctx0.sock = 42;
  ctx0.add_socket = tc->c0_adds;
  ctx1.sock = 43;

  result = Curl_cf_create(&cf0, &t2606_cft, &ctx0);
  fail_unless(!result, "creating filter chain 0 failed");
  if(!result) {
    Curl_conn_cf_add(data, conn, FIRSTSOCKET, cf0);
    cf0->connected = tc->c0_connected;
    if(tc->c0_shutdown)
      conn->shutdown.start_ms[FIRSTSOCKET] = 100;
  }

  if(tc->has_c1) {
    result = Curl_cf_create(&cf1, &t2606_cft, &ctx1);
    fail_unless(!result, "creating filter chain 1 failed");
    if(!result) {
      Curl_conn_cf_add(data, conn, SECONDARYSOCKET, cf1);
      cf1->connected = tc->c1_connected;
      if(tc->c1_shutdown)
        conn->shutdown.start_ms[SECONDARYSOCKET] = 100;
    }
  }

  Curl_pollset_init(&ps);
  if(tc->xfer_io) {
    result = Curl_pollset_add_in(data, &ps, 7);
    fail_unless(!result, "preloading the pollset failed");
  }

  result = Curl_conn_adjust_pollset(data, conn, &ps);

  curl_msprintf(msg, "case %d: adjust_pollset failed", tc->id);
  fail_unless(!result, msg);
  curl_msprintf(msg, "case %d: chain 0 called %d times, expected %d",
                tc->id, ctx0.calls, tc->exp_c0_calls);
  fail_unless(ctx0.calls == tc->exp_c0_calls, msg);
  curl_msprintf(msg, "case %d: chain 1 called %d times, expected %d",
                tc->id, ctx1.calls, tc->exp_c1_calls);
  fail_unless(ctx1.calls == tc->exp_c1_calls, msg);
  if(tc->c0_adds && tc->exp_c0_calls) {
    curl_msprintf(msg, "case %d: chain 0 did not add its socket", tc->id);
    fail_unless(ps.n > 0, msg);
  }

  Curl_pollset_cleanup(&ps);
  Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET);
  Curl_conn_cf_discard_all(data, conn, FIRSTSOCKET);
unit_test_abort:
  curlx_free(conn);
}

static CURLcode test_unit2606(const char *arg)
{
  CURL *easy;

  UNITTEST_BEGIN(t2606_setup(&easy))

  static const struct t2606_case TEST_CASES[] = {
    /* both chains connected and quiet, transfer idle: nothing runs */
    { 1, TRUE,  TRUE,  TRUE,  FALSE, FALSE, FALSE, FALSE, 0, 0 },
    /* transfer wants IO: both chains run */
    { 2, TRUE,  TRUE,  TRUE,  FALSE, FALSE, TRUE,  FALSE, 1, 1 },
    /* chain 0 connecting, chain 1 connected and quiet: only chain 0 */
    { 3, TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, 1, 0 },
    /* chain 1 connecting, chain 0 connected and quiet: only chain 1 */
    { 4, TRUE,  TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, 0, 1 },
    /* chain 0 shutting down, chain 1 connected and quiet: only chain 0 */
    { 5, TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, FALSE, 1, 0 },
    /* chain 1 shutting down, chain 0 connected and quiet: only chain 1 */
    { 6, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, 0, 1 },
    /* single connected quiet chain, transfer idle: nothing runs */
    { 7, FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 },
    /* single chain shutting down: runs */
    { 8, FALSE, TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, 1, 0 },
    /* a socket added by connecting chain 0 must not run quiet chain 1 */
    { 9, TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, TRUE,  1, 0 },
  };

  size_t i;

  for(i = 0; i < CURL_ARRAYSIZE(TEST_CASES); ++i) {
    t2606_run(easy, &TEST_CASES[i]);
  }

  UNITTEST_END(t2606_stop(easy))
}
