/***************************************************************************
 *                                  _   _ ____  _
 *  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 "curl_setup.h"

#ifndef CURL_DISABLE_WEBSOCKETS
/* only used for this protocol, so far */

#include "urldata.h"
#include "bufq.h"
#include "cfilters.h"
#include "cf-recvbuf.h"
#include "curl_trc.h"

#define CURL_CF_RECVBUF_CHUNK     (16 * 1024)

struct cf_recvbuf_ctx {
  struct bufq recvbuf;
};

static void cf_recvbuf_destroy(struct Curl_cfilter *cf,
                               struct Curl_easy *data)
{
  struct cf_recvbuf_ctx *ctx = cf->ctx;
  (void)data;
  if(ctx) {
    Curl_bufq_free(&ctx->recvbuf);
    curlx_free(ctx);
  }
}

static CURLcode cf_recvbuf_recv(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
                                char *buf, size_t len,
                                size_t *pnread)
{
  struct cf_recvbuf_ctx *ctx = cf->ctx;

  if(!Curl_bufq_is_empty(&ctx->recvbuf)) {
    return Curl_bufq_cread(&ctx->recvbuf, buf, len, pnread);
  }

  if(cf->next)
    return cf->next->cft->do_recv(cf->next, data, buf, len, pnread);
  *pnread = 0;
  return CURLE_RECV_ERROR;
}

static bool cf_recvbuf_data_pending(struct Curl_cfilter *cf,
                                    const struct Curl_easy *data)
{
  struct cf_recvbuf_ctx *ctx = cf->ctx;

  if(!Curl_bufq_is_empty(&ctx->recvbuf))
    return TRUE;

  return cf->next ?
    cf->next->cft->has_data_pending(cf->next, data) : FALSE;
}

struct Curl_cftype Curl_cft_recvbuf = {
  "RECVBUF",
  0,
  CURL_LOG_LVL_NONE,
  cf_recvbuf_destroy,
  Curl_cf_def_connect,
  Curl_cf_def_shutdown,
  Curl_cf_def_adjust_pollset,
  cf_recvbuf_data_pending,
  Curl_cf_def_send,
  cf_recvbuf_recv,
  Curl_cf_def_cntrl,
  Curl_cf_def_conn_is_alive,
  Curl_cf_def_conn_keep_alive,
  Curl_cf_def_query,
};

static CURLcode cf_recvbuf_create(struct Curl_cfilter **pcf,
                                  struct Curl_easy *data,
                                  const uint8_t *buf, size_t blen)
{
  struct Curl_cfilter *cf = NULL;
  struct cf_recvbuf_ctx *ctx;
  CURLcode result = CURLE_OK;
  size_t nwritten = 0;

  (void)data;
  ctx = curlx_calloc(1, sizeof(*ctx));
  if(!ctx) {
    result = CURLE_OUT_OF_MEMORY;
    goto out;
  }
  Curl_bufq_init2(&ctx->recvbuf, CURL_CF_RECVBUF_CHUNK,
                  (blen / CURL_CF_RECVBUF_CHUNK) + 1,
                  (BUFQ_OPT_SOFT_LIMIT | BUFQ_OPT_NO_SPARES));
  result = Curl_bufq_write(&ctx->recvbuf, buf, blen, &nwritten);
  if(result)
    goto out;
  if(nwritten != blen) {
    result = CURLE_FAILED_INIT;
    goto out;
  }

  result = Curl_cf_create(&cf, &Curl_cft_recvbuf, ctx);
  if(result)
    goto out;
  ctx = NULL;

out:
  *pcf = result ? NULL : cf;
  if(ctx) {
    Curl_bufq_free(&ctx->recvbuf);
    curlx_free(ctx);
  }
  return result;
}

CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data,
                             struct connectdata *conn,
                             int8_t sockindex,
                             const uint8_t *buf, size_t blen)
{
  struct Curl_cfilter *cf;
  CURLcode result = CURLE_OK;

  DEBUGASSERT(data);
  result = cf_recvbuf_create(&cf, data, buf, blen);
  if(result)
    goto out;

  cf->connected = Curl_conn_is_connected(conn, sockindex);
  Curl_conn_cf_add(data, conn, sockindex, cf);
out:
  return result;
}

#endif /* !CURL_DISABLE_WEBSOCKETS */
