/*
 * Wslay - The WebSocket Library
 *
 * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "wslay_event.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "wslay_frame.h"
#include "wslay_net.h"
#include "wslay_macro.h"
/* Start of utf8 dfa */
/* Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
 *
 * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#define UTF8_ACCEPT 0
#define UTF8_REJECT 12

/* clang-format off */
static const uint8_t utf8d[] = {
  /*
   * The first part of the table maps bytes to character classes that
   * to reduce the size of the transition table and create bitmasks.
   */
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,

   /*
    * The second part is a transition table that maps a combination
    * of a state of the automaton and a character class to a state.
    */
   0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
  12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
  12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
  12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
  12,36,12,12,12,12,12,12,12,12,12,12,
};
/* clang-format on */

static uint32_t decode(uint32_t *state, uint32_t *codep, uint32_t byte) {
  uint32_t type = utf8d[byte];

  *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6)
                                   : (0xff >> type) & (byte);

  *state = utf8d[256 + *state + type];
  return *state;
}

/* End of utf8 dfa */

static ssize_t wslay_event_frame_recv_callback(uint8_t *buf, size_t len,
                                               int flags, void *user_data) {
  struct wslay_event_frame_user_data *e =
      (struct wslay_event_frame_user_data *)user_data;
  return e->ctx->callbacks.recv_callback(e->ctx, buf, len, flags, e->user_data);
}

static ssize_t wslay_event_frame_send_callback(const uint8_t *data, size_t len,
                                               int flags, void *user_data) {
  struct wslay_event_frame_user_data *e =
      (struct wslay_event_frame_user_data *)user_data;
  return e->ctx->callbacks.send_callback(e->ctx, data, len, flags,
                                         e->user_data);
}

static int wslay_event_frame_genmask_callback(uint8_t *buf, size_t len,
                                              void *user_data) {
  struct wslay_event_frame_user_data *e =
      (struct wslay_event_frame_user_data *)user_data;
  return e->ctx->callbacks.genmask_callback(e->ctx, buf, len, e->user_data);
}

static int wslay_event_byte_chunk_init(struct wslay_event_byte_chunk **chunk,
                                       size_t len) {
  *chunk = malloc(sizeof(struct wslay_event_byte_chunk) + len);
  if (*chunk == NULL) {
    return WSLAY_ERR_NOMEM;
  }
  memset(*chunk, 0, sizeof(struct wslay_event_byte_chunk));
  if (len) {
    (*chunk)->data = (uint8_t *)(*chunk) + sizeof(**chunk);
    (*chunk)->data_length = len;
  }
  return 0;
}

static void wslay_event_byte_chunk_free(struct wslay_event_byte_chunk *c) {
  free(c);
}

static void wslay_event_byte_chunk_copy(struct wslay_event_byte_chunk *c,
                                        size_t off, const uint8_t *data,
                                        size_t data_length) {
  memcpy(c->data + off, data, data_length);
}

static void wslay_event_imsg_set(struct wslay_event_imsg *m, uint8_t fin,
                                 uint8_t rsv, uint8_t opcode) {
  m->fin = fin;
  m->rsv = rsv;
  m->opcode = opcode;
  m->msg_length = 0;
}

static void wslay_event_imsg_chunks_free(struct wslay_event_imsg *m) {
  while (!wslay_queue_empty(&m->chunks)) {
    struct wslay_event_byte_chunk *chunk = wslay_struct_of(
        wslay_queue_top(&m->chunks), struct wslay_event_byte_chunk, qe);
    wslay_queue_pop(&m->chunks);
    wslay_event_byte_chunk_free(chunk);
  }
}

static void wslay_event_imsg_reset(struct wslay_event_imsg *m) {
  m->opcode = 0xffu;
  m->utf8state = UTF8_ACCEPT;
  wslay_event_imsg_chunks_free(m);
}

static int wslay_event_imsg_append_chunk(struct wslay_event_imsg *m,
                                         size_t len) {
  if (len == 0) {
    return 0;
  } else {
    int r;
    struct wslay_event_byte_chunk *chunk;
    if ((r = wslay_event_byte_chunk_init(&chunk, len)) != 0) {
      return r;
    }
    wslay_queue_push(&m->chunks, &chunk->qe);
    m->msg_length += len;
    return 0;
  }
}

static int wslay_event_omsg_non_fragmented_init(struct wslay_event_omsg **m,
                                                uint8_t opcode, uint8_t rsv,
                                                const uint8_t *msg,
                                                size_t msg_length) {
  *m = malloc(sizeof(struct wslay_event_omsg) + msg_length);
  if (!*m) {
    return WSLAY_ERR_NOMEM;
  }
  memset(*m, 0, sizeof(struct wslay_event_omsg));
  (*m)->fin = 1;
  (*m)->opcode = opcode;
  (*m)->rsv = rsv;
  (*m)->type = WSLAY_NON_FRAGMENTED;
  if (msg_length) {
    (*m)->data = (uint8_t *)(*m) + sizeof(**m);
    memcpy((*m)->data, msg, msg_length);
    (*m)->data_length = msg_length;
  }
  return 0;
}

static int wslay_event_omsg_fragmented_init(
    struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv,
    const union wslay_event_msg_source source,
    wslay_event_fragmented_msg_callback read_callback) {
  *m = calloc(1, sizeof(struct wslay_event_omsg));
  if (!*m) {
    return WSLAY_ERR_NOMEM;
  }
  (*m)->opcode = opcode;
  (*m)->rsv = rsv;
  (*m)->type = WSLAY_FRAGMENTED;
  (*m)->source = source;
  (*m)->read_callback = read_callback;
  return 0;
}

static void wslay_event_omsg_free(struct wslay_event_omsg *m) { free(m); }

static uint8_t *wslay_event_flatten_queue(struct wslay_queue *queue,
                                          size_t len) {
  if (len == 0) {
    return NULL;
  } else {
    size_t off = 0;
    uint8_t *buf = malloc(len);
    if (!buf) {
      return NULL;
    }
    while (!wslay_queue_empty(queue)) {
      struct wslay_event_byte_chunk *chunk = wslay_struct_of(
          wslay_queue_top(queue), struct wslay_event_byte_chunk, qe);
      wslay_queue_pop(queue);
      memcpy(buf + off, chunk->data, chunk->data_length);
      off += chunk->data_length;
      wslay_event_byte_chunk_free(chunk);
      assert(off <= len);
    }
    assert(len == off);
    return buf;
  }
}

static int wslay_event_is_msg_queueable(wslay_event_context_ptr ctx) {
  return ctx->write_enabled && (ctx->close_status & WSLAY_CLOSE_QUEUED) == 0;
}

int wslay_event_queue_close(wslay_event_context_ptr ctx, uint16_t status_code,
                            const uint8_t *reason, size_t reason_length) {
  if (!wslay_event_is_msg_queueable(ctx)) {
    return WSLAY_ERR_NO_MORE_MSG;
  } else if (reason_length > 123) {
    return WSLAY_ERR_INVALID_ARGUMENT;
  } else {
    uint8_t msg[128];
    size_t msg_length;
    struct wslay_event_msg arg;
    uint16_t ncode;
    int r;
    if (status_code == 0) {
      msg_length = 0;
    } else {
      ncode = htons(status_code);
      memcpy(msg, &ncode, 2);
      if (reason_length) {
        memcpy(msg + 2, reason, reason_length);
      }
      msg_length = reason_length + 2;
    }
    arg.opcode = WSLAY_CONNECTION_CLOSE;
    arg.msg = msg;
    arg.msg_length = msg_length;
    r = wslay_event_queue_msg(ctx, &arg);
    if (r == 0) {
      ctx->close_status |= WSLAY_CLOSE_QUEUED;
    }
    return r;
  }
}

static int wslay_event_queue_close_wrapper(wslay_event_context_ptr ctx,
                                           uint16_t status_code,
                                           const uint8_t *reason,
                                           size_t reason_length) {
  int r;
  ctx->read_enabled = 0;
  if ((r = wslay_event_queue_close(ctx, status_code, reason, reason_length)) &&
      r != WSLAY_ERR_NO_MORE_MSG) {
    return r;
  }
  return 0;
}

static int wslay_event_verify_rsv_bits(wslay_event_context_ptr ctx,
                                       uint8_t rsv) {
  return ((rsv & ~ctx->allowed_rsv_bits) == 0);
}

int wslay_event_queue_msg(wslay_event_context_ptr ctx,
                          const struct wslay_event_msg *arg) {
  return wslay_event_queue_msg_ex(ctx, arg, WSLAY_RSV_NONE);
}

int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx,
                             const struct wslay_event_msg *arg, uint8_t rsv) {
  int r;
  struct wslay_event_omsg *omsg;
  if (!wslay_event_is_msg_queueable(ctx)) {
    return WSLAY_ERR_NO_MORE_MSG;
  }
  /* RSV1 is not allowed for control frames */
  if ((wslay_is_ctrl_frame(arg->opcode) &&
       (arg->msg_length > 125 || wslay_get_rsv1(rsv))) ||
      !wslay_event_verify_rsv_bits(ctx, rsv)) {
    return WSLAY_ERR_INVALID_ARGUMENT;
  }
  if ((r = wslay_event_omsg_non_fragmented_init(
           &omsg, arg->opcode, rsv, arg->msg, arg->msg_length)) != 0) {
    return r;
  }
  if (wslay_is_ctrl_frame(arg->opcode)) {
    wslay_queue_push(&ctx->send_ctrl_queue, &omsg->qe);
  } else {
    wslay_queue_push(&ctx->send_queue, &omsg->qe);
  }
  ++ctx->queued_msg_count;
  ctx->queued_msg_length += arg->msg_length;
  return 0;
}

int wslay_event_queue_fragmented_msg(
    wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg) {
  return wslay_event_queue_fragmented_msg_ex(ctx, arg, WSLAY_RSV_NONE);
}

int wslay_event_queue_fragmented_msg_ex(
    wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg,
    uint8_t rsv) {
  int r;
  struct wslay_event_omsg *omsg;
  if (!wslay_event_is_msg_queueable(ctx)) {
    return WSLAY_ERR_NO_MORE_MSG;
  }
  if (wslay_is_ctrl_frame(arg->opcode) ||
      !wslay_event_verify_rsv_bits(ctx, rsv)) {
    return WSLAY_ERR_INVALID_ARGUMENT;
  }
  if ((r = wslay_event_omsg_fragmented_init(
           &omsg, arg->opcode, rsv, arg->source, arg->read_callback)) != 0) {
    return r;
  }
  wslay_queue_push(&ctx->send_queue, &omsg->qe);
  ++ctx->queued_msg_count;
  return 0;
}

void wslay_event_config_set_callbacks(
    wslay_event_context_ptr ctx,
    const struct wslay_event_callbacks *callbacks) {
  ctx->callbacks = *callbacks;
}

static int
wslay_event_context_init(wslay_event_context_ptr *ctx,
                         const struct wslay_event_callbacks *callbacks,
                         void *user_data) {
  int i, r;
  struct wslay_frame_callbacks frame_callbacks = {
      wslay_event_frame_send_callback, wslay_event_frame_recv_callback,
      wslay_event_frame_genmask_callback};
  *ctx = calloc(1, sizeof(struct wslay_event_context));
  if (!*ctx) {
    return WSLAY_ERR_NOMEM;
  }
  wslay_event_config_set_callbacks(*ctx, callbacks);
  (*ctx)->user_data = user_data;
  (*ctx)->frame_user_data.ctx = *ctx;
  (*ctx)->frame_user_data.user_data = user_data;
  if ((r = wslay_frame_context_init(&(*ctx)->frame_ctx, &frame_callbacks,
                                    &(*ctx)->frame_user_data)) != 0) {
    wslay_event_context_free(*ctx);
    return r;
  }
  (*ctx)->read_enabled = (*ctx)->write_enabled = 1;
  wslay_queue_init(&(*ctx)->send_queue);
  wslay_queue_init(&(*ctx)->send_ctrl_queue);
  (*ctx)->queued_msg_count = 0;
  (*ctx)->queued_msg_length = 0;
  for (i = 0; i < 2; ++i) {
    wslay_queue_init(&(*ctx)->imsgs[i].chunks);
    wslay_event_imsg_reset(&(*ctx)->imsgs[i]);
  }
  (*ctx)->imsg = &(*ctx)->imsgs[0];
  (*ctx)->obufmark = (*ctx)->obuflimit = (*ctx)->obuf;
  (*ctx)->status_code_sent = WSLAY_CODE_ABNORMAL_CLOSURE;
  (*ctx)->status_code_recv = WSLAY_CODE_ABNORMAL_CLOSURE;
  (*ctx)->max_recv_msg_length = (1u << 31) - 1;
  return 0;
}

int wslay_event_context_server_init(
    wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks,
    void *user_data) {
  int r;
  if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) {
    return r;
  }
  (*ctx)->server = 1;
  return 0;
}

int wslay_event_context_client_init(
    wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks,
    void *user_data) {
  int r;
  if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) {
    return r;
  }
  (*ctx)->server = 0;
  return 0;
}

void wslay_event_context_free(wslay_event_context_ptr ctx) {
  int i;
  if (!ctx) {
    return;
  }
  for (i = 0; i < 2; ++i) {
    wslay_event_imsg_chunks_free(&ctx->imsgs[i]);
    wslay_queue_deinit(&ctx->imsgs[i].chunks);
  }

  while (!wslay_queue_empty(&ctx->send_queue)) {
    struct wslay_event_omsg *omsg = wslay_struct_of(
        wslay_queue_top(&ctx->send_queue), struct wslay_event_omsg, qe);
    wslay_queue_pop(&ctx->send_queue);
    wslay_event_omsg_free(omsg);
  }
  wslay_queue_deinit(&ctx->send_queue);

  while (!wslay_queue_empty(&ctx->send_ctrl_queue)) {
    struct wslay_event_omsg *omsg = wslay_struct_of(
        wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe);
    wslay_queue_pop(&ctx->send_ctrl_queue);
    wslay_event_omsg_free(omsg);
  }
  wslay_queue_deinit(&ctx->send_ctrl_queue);

  wslay_frame_context_free(ctx->frame_ctx);
  wslay_event_omsg_free(ctx->omsg);
  free(ctx);
}

static void wslay_event_call_on_frame_recv_start_callback(
    wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) {
  if (ctx->callbacks.on_frame_recv_start_callback) {
    struct wslay_event_on_frame_recv_start_arg arg;
    arg.fin = iocb->fin;
    arg.rsv = iocb->rsv;
    arg.opcode = iocb->opcode;
    arg.payload_length = iocb->payload_length;
    ctx->callbacks.on_frame_recv_start_callback(ctx, &arg, ctx->user_data);
  }
}

static void wslay_event_call_on_frame_recv_chunk_callback(
    wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) {
  if (ctx->callbacks.on_frame_recv_chunk_callback) {
    struct wslay_event_on_frame_recv_chunk_arg arg;
    arg.data = iocb->data;
    arg.data_length = iocb->data_length;
    ctx->callbacks.on_frame_recv_chunk_callback(ctx, &arg, ctx->user_data);
  }
}

static void
wslay_event_call_on_frame_recv_end_callback(wslay_event_context_ptr ctx) {
  if (ctx->callbacks.on_frame_recv_end_callback) {
    ctx->callbacks.on_frame_recv_end_callback(ctx, ctx->user_data);
  }
}

static int wslay_event_is_valid_status_code(uint16_t status_code) {
  return (1000 <= status_code && status_code <= 1011 && status_code != 1004 &&
          status_code != 1005 && status_code != 1006) ||
         (3000 <= status_code && status_code <= 4999);
}

static int wslay_event_config_get_no_buffering(wslay_event_context_ptr ctx) {
  return (ctx->config & WSLAY_CONFIG_NO_BUFFERING) > 0;
}

int wslay_event_recv(wslay_event_context_ptr ctx) {
  struct wslay_frame_iocb iocb;
  ssize_t r;
  while (ctx->read_enabled) {
    memset(&iocb, 0, sizeof(iocb));
    r = wslay_frame_recv(ctx->frame_ctx, &iocb);
    if (r >= 0) {
      int new_frame = 0;
      /* RSV1 is not allowed on control and continuation frames */
      if ((!wslay_event_verify_rsv_bits(ctx, iocb.rsv)) ||
          (wslay_get_rsv1(iocb.rsv) &&
           (wslay_is_ctrl_frame(iocb.opcode) ||
            iocb.opcode == WSLAY_CONTINUATION_FRAME)) ||
          (ctx->server && !iocb.mask) || (!ctx->server && iocb.mask)) {
        if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR,
                                                 NULL, 0)) != 0) {
          return (int)r;
        }
        break;
      }
      if (ctx->imsg->opcode == 0xffu) {
        if (iocb.opcode == WSLAY_TEXT_FRAME ||
            iocb.opcode == WSLAY_BINARY_FRAME ||
            iocb.opcode == WSLAY_CONNECTION_CLOSE ||
            iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) {
          wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode);
          new_frame = 1;
        } else {
          if ((r = wslay_event_queue_close_wrapper(
                   ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
            return (int)r;
          }
          break;
        }
      } else if (ctx->ipayloadlen == 0 && ctx->ipayloadoff == 0) {
        if (iocb.opcode == WSLAY_CONTINUATION_FRAME) {
          ctx->imsg->fin = iocb.fin;
        } else if (iocb.opcode == WSLAY_CONNECTION_CLOSE ||
                   iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) {
          ctx->imsg = &ctx->imsgs[1];
          wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode);
        } else {
          if ((r = wslay_event_queue_close_wrapper(
                   ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
            return (int)r;
          }
          break;
        }
        new_frame = 1;
      }
      if (new_frame) {
        if (ctx->imsg->msg_length + iocb.payload_length >
            ctx->max_recv_msg_length) {
          if ((r = wslay_event_queue_close_wrapper(
                   ctx, WSLAY_CODE_MESSAGE_TOO_BIG, NULL, 0)) != 0) {
            return (int)r;
          }
          break;
        }
        ctx->ipayloadlen = iocb.payload_length;
        wslay_event_call_on_frame_recv_start_callback(ctx, &iocb);
        if (!wslay_event_config_get_no_buffering(ctx) ||
            wslay_is_ctrl_frame(iocb.opcode)) {
          if ((r = wslay_event_imsg_append_chunk(ctx->imsg,
                                                 iocb.payload_length)) != 0) {
            ctx->read_enabled = 0;
            return (int)r;
          }
        }
      }
      /* If RSV1 bit is set then it is too early for utf-8 validation */
      if ((!wslay_get_rsv1(ctx->imsg->rsv) &&
           ctx->imsg->opcode == WSLAY_TEXT_FRAME) ||
          ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
        size_t i;
        if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
          i = 2;
        } else {
          i = 0;
        }
        for (; i < iocb.data_length; ++i) {
          uint32_t codep;
          if (decode(&ctx->imsg->utf8state, &codep, iocb.data[i]) ==
              UTF8_REJECT) {
            if ((r = wslay_event_queue_close_wrapper(
                     ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) !=
                0) {
              return (int)r;
            }
            break;
          }
        }
      }
      if (ctx->imsg->utf8state == UTF8_REJECT) {
        break;
      }
      wslay_event_call_on_frame_recv_chunk_callback(ctx, &iocb);
      if (iocb.data_length > 0) {
        if (!wslay_event_config_get_no_buffering(ctx) ||
            wslay_is_ctrl_frame(iocb.opcode)) {
          struct wslay_event_byte_chunk *chunk;
          chunk = wslay_struct_of(wslay_queue_tail(&ctx->imsg->chunks),
                                  struct wslay_event_byte_chunk, qe);
          wslay_event_byte_chunk_copy(chunk, ctx->ipayloadoff, iocb.data,
                                      iocb.data_length);
        }
        ctx->ipayloadoff += iocb.data_length;
      }
      if (ctx->ipayloadoff == ctx->ipayloadlen) {
        if (ctx->imsg->fin &&
            (ctx->imsg->opcode == WSLAY_TEXT_FRAME ||
             ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) &&
            ctx->imsg->utf8state != UTF8_ACCEPT) {
          if ((r = wslay_event_queue_close_wrapper(
                   ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) {
            return (int)r;
          }
          break;
        }
        wslay_event_call_on_frame_recv_end_callback(ctx);
        if (ctx->imsg->fin) {
          if (ctx->callbacks.on_msg_recv_callback ||
              ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE ||
              ctx->imsg->opcode == WSLAY_PING) {
            struct wslay_event_on_msg_recv_arg arg;
            uint16_t status_code = 0;
            uint8_t *msg = NULL;
            size_t msg_length = 0;
            if (!wslay_event_config_get_no_buffering(ctx) ||
                wslay_is_ctrl_frame(iocb.opcode)) {
              msg = wslay_event_flatten_queue(&ctx->imsg->chunks,
                                              ctx->imsg->msg_length);
              if (ctx->imsg->msg_length && !msg) {
                ctx->read_enabled = 0;
                return WSLAY_ERR_NOMEM;
              }
              msg_length = ctx->imsg->msg_length;
            }
            if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
              const uint8_t *reason;
              size_t reason_length;
              if (ctx->imsg->msg_length >= 2) {
                memcpy(&status_code, msg, 2);
                status_code = ntohs(status_code);
                if (!wslay_event_is_valid_status_code(status_code)) {
                  free(msg);
                  if ((r = wslay_event_queue_close_wrapper(
                           ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
                    return (int)r;
                  }
                  break;
                }
                reason = msg + 2;
                reason_length = ctx->imsg->msg_length - 2;
              } else {
                reason = NULL;
                reason_length = 0;
              }
              ctx->close_status |= WSLAY_CLOSE_RECEIVED;
              ctx->status_code_recv =
                  status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code;
              if ((r = wslay_event_queue_close_wrapper(ctx, status_code, reason,
                                                       reason_length)) != 0) {
                free(msg);
                return (int)r;
              }
            } else if (ctx->imsg->opcode == WSLAY_PING) {
              struct wslay_event_msg pong_arg;
              pong_arg.opcode = WSLAY_PONG;
              pong_arg.msg = msg;
              pong_arg.msg_length = ctx->imsg->msg_length;
              if ((r = wslay_event_queue_msg(ctx, &pong_arg)) &&
                  r != WSLAY_ERR_NO_MORE_MSG) {
                ctx->read_enabled = 0;
                free(msg);
                return (int)r;
              }
            }
            if (ctx->callbacks.on_msg_recv_callback) {
              arg.rsv = ctx->imsg->rsv;
              arg.opcode = ctx->imsg->opcode;
              arg.msg = msg;
              arg.msg_length = msg_length;
              arg.status_code = status_code;
              ctx->error = 0;
              ctx->callbacks.on_msg_recv_callback(ctx, &arg, ctx->user_data);
            }
            free(msg);
          }
          wslay_event_imsg_reset(ctx->imsg);
          if (ctx->imsg == &ctx->imsgs[1]) {
            ctx->imsg = &ctx->imsgs[0];
          }
        }
        ctx->ipayloadlen = ctx->ipayloadoff = 0;
      }
    } else {
      if (r != WSLAY_ERR_WANT_READ ||
          (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
        if ((r = wslay_event_queue_close_wrapper(ctx, 0, NULL, 0)) != 0) {
          return (int)r;
        }
        return WSLAY_ERR_CALLBACK_FAILURE;
      }
      break;
    }
  }
  return 0;
}

static void
wslay_event_on_non_fragmented_msg_popped(wslay_event_context_ptr ctx) {
  ctx->omsg->fin = 1;
  ctx->opayloadlen = ctx->omsg->data_length;
  ctx->opayloadoff = 0;
}

static struct wslay_event_omsg *
wslay_event_send_ctrl_queue_pop(wslay_event_context_ptr ctx) {
  /*
   * If Close control frame is queued, we don't send any control frame
   * other than Close.
   */
  if (ctx->close_status & WSLAY_CLOSE_QUEUED) {
    while (!wslay_queue_empty(&ctx->send_ctrl_queue)) {
      struct wslay_event_omsg *msg = wslay_struct_of(
          wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe);
      wslay_queue_pop(&ctx->send_ctrl_queue);
      if (msg->opcode == WSLAY_CONNECTION_CLOSE) {
        return msg;
      } else {
        wslay_event_omsg_free(msg);
      }
    }
    return NULL;
  } else {
    struct wslay_event_omsg *msg = wslay_struct_of(
        wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe);
    wslay_queue_pop(&ctx->send_ctrl_queue);
    return msg;
  }
}

int wslay_event_send(wslay_event_context_ptr ctx) {
  struct wslay_frame_iocb iocb;
  ssize_t r;
  while (ctx->write_enabled &&
         (!wslay_queue_empty(&ctx->send_queue) ||
          !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg)) {
    if (!ctx->omsg) {
      if (wslay_queue_empty(&ctx->send_ctrl_queue)) {
        ctx->omsg = wslay_struct_of(wslay_queue_top(&ctx->send_queue),
                                    struct wslay_event_omsg, qe);
        wslay_queue_pop(&ctx->send_queue);
      } else {
        ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
        if (ctx->omsg == NULL) {
          break;
        }
      }
      if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
        wslay_event_on_non_fragmented_msg_popped(ctx);
      }
    } else if (!wslay_is_ctrl_frame(ctx->omsg->opcode) &&
               ctx->frame_ctx->ostate == PREP_HEADER &&
               !wslay_queue_empty(&ctx->send_ctrl_queue)) {
      wslay_queue_push_front(&ctx->send_queue, &ctx->omsg->qe);
      ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
      if (ctx->omsg == NULL) {
        break;
      }
      /* ctrl message has WSLAY_NON_FRAGMENTED */
      wslay_event_on_non_fragmented_msg_popped(ctx);
    }
    if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
      memset(&iocb, 0, sizeof(iocb));
      iocb.fin = 1;
      iocb.opcode = ctx->omsg->opcode;
      iocb.rsv = ctx->omsg->rsv;
      iocb.mask = ctx->server ^ 1;
      iocb.data = ctx->omsg->data;
      iocb.data_length = ctx->opayloadlen;
      if (ctx->opayloadoff) {
        iocb.data += ctx->opayloadoff;
        iocb.data_length -= ctx->opayloadoff;
      }
      iocb.payload_length = ctx->opayloadlen;
      r = wslay_frame_send(ctx->frame_ctx, &iocb);
      if (r >= 0) {
        ctx->opayloadoff += (uint64_t)r;
        if (ctx->opayloadoff == ctx->opayloadlen) {
          --ctx->queued_msg_count;
          ctx->queued_msg_length -= ctx->omsg->data_length;
          if (ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) {
            uint16_t status_code = 0;
            ctx->write_enabled = 0;
            ctx->close_status |= WSLAY_CLOSE_SENT;
            if (ctx->omsg->data_length >= 2) {
              memcpy(&status_code, ctx->omsg->data, 2);
              status_code = ntohs(status_code);
            }
            ctx->status_code_sent =
                status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code;
          }
          wslay_event_omsg_free(ctx->omsg);
          ctx->omsg = NULL;
        } else {
          break;
        }
      } else {
        if (r != WSLAY_ERR_WANT_WRITE ||
            (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
          ctx->write_enabled = 0;
          return WSLAY_ERR_CALLBACK_FAILURE;
        }
        break;
      }
    } else {
      if (ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) {
        int eof = 0;
        r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf),
                                     &ctx->omsg->source, &eof, ctx->user_data);
        if (r == 0 && eof == 0) {
          break;
        } else if (r < 0) {
          ctx->write_enabled = 0;
          return WSLAY_ERR_CALLBACK_FAILURE;
        }
        ctx->obuflimit = ctx->obuf + r;
        if (eof) {
          ctx->omsg->fin = 1;
        }
        ctx->opayloadlen = (uint64_t)r;
        ctx->opayloadoff = 0;
      }
      memset(&iocb, 0, sizeof(iocb));
      iocb.fin = ctx->omsg->fin;
      iocb.opcode = ctx->omsg->opcode;
      iocb.rsv = ctx->omsg->rsv;
      iocb.mask = ctx->server ? 0 : 1;
      iocb.data = ctx->obufmark;
      iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark);
      iocb.payload_length = ctx->opayloadlen;
      r = wslay_frame_send(ctx->frame_ctx, &iocb);
      if (r >= 0) {
        ctx->obufmark += r;
        if (ctx->obufmark == ctx->obuflimit) {
          ctx->obufmark = ctx->obuflimit = ctx->obuf;
          if (ctx->omsg->fin) {
            --ctx->queued_msg_count;
            wslay_event_omsg_free(ctx->omsg);
            ctx->omsg = NULL;
          } else {
            ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME;
            /* RSV1 is not set on continuation frames */
            ctx->omsg->rsv = (uint8_t)(ctx->omsg->rsv & ~WSLAY_RSV1_BIT);
          }
        } else {
          break;
        }
      } else {
        if (r != WSLAY_ERR_WANT_WRITE ||
            (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
          ctx->write_enabled = 0;
          return WSLAY_ERR_CALLBACK_FAILURE;
        }
        break;
      }
    }
  }
  return 0;
}

ssize_t wslay_event_write(wslay_event_context_ptr ctx, uint8_t *buf,
                          size_t buflen) {
  struct wslay_frame_iocb iocb;
  ssize_t r;
  uint8_t *buf_last = buf;
  size_t wpayloadlen;
  while (ctx->write_enabled &&
         (!wslay_queue_empty(&ctx->send_queue) ||
          !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg)) {
    if (!ctx->omsg) {
      if (wslay_queue_empty(&ctx->send_ctrl_queue)) {
        ctx->omsg = wslay_struct_of(wslay_queue_top(&ctx->send_queue),
                                    struct wslay_event_omsg, qe);
        wslay_queue_pop(&ctx->send_queue);
      } else {
        ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
        if (ctx->omsg == NULL) {
          break;
        }
      }
      if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
        wslay_event_on_non_fragmented_msg_popped(ctx);
      }
    } else if (!wslay_is_ctrl_frame(ctx->omsg->opcode) &&
               ctx->frame_ctx->ostate == PREP_HEADER &&
               !wslay_queue_empty(&ctx->send_ctrl_queue)) {
      wslay_queue_push_front(&ctx->send_queue, &ctx->omsg->qe);
      ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
      if (ctx->omsg == NULL) {
        break;
      }
      /* ctrl message has WSLAY_NON_FRAGMENTED */
      wslay_event_on_non_fragmented_msg_popped(ctx);
    }
    if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
      memset(&iocb, 0, sizeof(iocb));
      iocb.fin = 1;
      iocb.opcode = ctx->omsg->opcode;
      iocb.rsv = ctx->omsg->rsv;
      iocb.mask = ctx->server ^ 1;
      iocb.data = ctx->omsg->data;
      iocb.data_length = ctx->opayloadlen;
      if (ctx->opayloadoff) {
        iocb.data += ctx->opayloadoff;
        iocb.data_length -= ctx->opayloadoff;
      }
      iocb.payload_length = ctx->opayloadlen;
      r = wslay_frame_write(ctx->frame_ctx, &iocb, buf_last, buflen,
                            &wpayloadlen);
      if (r > 0) {
        assert((size_t)r <= buflen);

        buf_last += r;
        buflen -= (size_t)r;

        ctx->opayloadoff += wpayloadlen;
        if (ctx->opayloadoff == ctx->opayloadlen) {
          --ctx->queued_msg_count;
          ctx->queued_msg_length -= ctx->omsg->data_length;
          if (ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) {
            uint16_t status_code = 0;
            ctx->write_enabled = 0;
            ctx->close_status |= WSLAY_CLOSE_SENT;
            if (ctx->omsg->data_length >= 2) {
              memcpy(&status_code, ctx->omsg->data, 2);
              status_code = ntohs(status_code);
            }
            ctx->status_code_sent =
                status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code;
          }
          wslay_event_omsg_free(ctx->omsg);
          ctx->omsg = NULL;
        } else {
          break;
        }
      } else if (r == 0) {
        return buf_last - buf;
      } else {
        return WSLAY_ERR_CALLBACK_FAILURE;
      }
    } else {
      if (ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) {
        int eof = 0;
        r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf),
                                     &ctx->omsg->source, &eof, ctx->user_data);
        if (r == 0 && eof == 0) {
          break;
        } else if (r < 0) {
          ctx->write_enabled = 0;
          return WSLAY_ERR_CALLBACK_FAILURE;
        }
        ctx->obuflimit = ctx->obuf + r;
        if (eof) {
          ctx->omsg->fin = 1;
        }
        ctx->opayloadlen = (uint64_t)r;
        ctx->opayloadoff = 0;
      }
      memset(&iocb, 0, sizeof(iocb));
      iocb.fin = ctx->omsg->fin;
      iocb.opcode = ctx->omsg->opcode;
      iocb.rsv = ctx->omsg->rsv;
      iocb.mask = ctx->server ? 0 : 1;
      iocb.data = ctx->obufmark;
      iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark);
      iocb.payload_length = ctx->opayloadlen;
      r = wslay_frame_write(ctx->frame_ctx, &iocb, buf_last, buflen,
                            &wpayloadlen);
      if (r > 0) {
        assert((size_t)r <= buflen);

        buf_last += r;
        buflen -= (size_t)r;

        ctx->obufmark += wpayloadlen;
        if (ctx->obufmark == ctx->obuflimit) {
          ctx->obufmark = ctx->obuflimit = ctx->obuf;
          if (ctx->omsg->fin) {
            --ctx->queued_msg_count;
            wslay_event_omsg_free(ctx->omsg);
            ctx->omsg = NULL;
          } else {
            ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME;
            /* RSV1 is not set on continuation frames */
            ctx->omsg->rsv = (uint8_t)(ctx->omsg->rsv & ~WSLAY_RSV1_BIT);
          }
        } else {
          break;
        }
      } else if (r == 0) {
        return buf_last - buf;
      } else {
        return WSLAY_ERR_CALLBACK_FAILURE;
      }
    }
  }
  return buf_last - buf;
}

void wslay_event_set_error(wslay_event_context_ptr ctx, int val) {
  ctx->error = val;
}

int wslay_event_want_read(wslay_event_context_ptr ctx) {
  return ctx->read_enabled;
}

int wslay_event_want_write(wslay_event_context_ptr ctx) {
  return ctx->write_enabled &&
         (!wslay_queue_empty(&ctx->send_queue) ||
          !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg);
}

void wslay_event_shutdown_read(wslay_event_context_ptr ctx) {
  ctx->read_enabled = 0;
}

void wslay_event_shutdown_write(wslay_event_context_ptr ctx) {
  ctx->write_enabled = 0;
}

int wslay_event_get_read_enabled(wslay_event_context_ptr ctx) {
  return ctx->read_enabled;
}

int wslay_event_get_write_enabled(wslay_event_context_ptr ctx) {
  return ctx->write_enabled;
}

int wslay_event_get_close_received(wslay_event_context_ptr ctx) {
  return (ctx->close_status & WSLAY_CLOSE_RECEIVED) > 0;
}

int wslay_event_get_close_sent(wslay_event_context_ptr ctx) {
  return (ctx->close_status & WSLAY_CLOSE_SENT) > 0;
}

void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx,
                                             uint8_t rsv) {
  /* We currently only allow WSLAY_RSV1_BIT or WSLAY_RSV_NONE */
  ctx->allowed_rsv_bits = rsv & WSLAY_RSV1_BIT;
}

void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val) {
  if (val) {
    ctx->config |= WSLAY_CONFIG_NO_BUFFERING;
  } else {
    ctx->config &= (uint32_t)~WSLAY_CONFIG_NO_BUFFERING;
  }
}

void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx,
                                                uint64_t val) {
  ctx->max_recv_msg_length = val;
}

uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx) {
  return ctx->status_code_recv;
}

uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx) {
  return ctx->status_code_sent;
}

size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx) {
  return ctx->queued_msg_count;
}

size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx) {
  return ctx->queued_msg_length;
}
