/**
 @file  protocol.c
 @brief ENet protocol functions
*/
#include <stdio.h>
#include <string.h>
#define ENET_BUILDING_LIB 1
#include "enet/enet.h"
#include "enet/time.h"
#include "enet/utility.h"

static const size_t commandSizes[ENET_PROTOCOL_COMMAND_COUNT] = {
    0,
    sizeof(ENetProtocolAcknowledge),
    sizeof(ENetProtocolConnect),
    sizeof(ENetProtocolVerifyConnect),
    sizeof(ENetProtocolDisconnect),
    sizeof(ENetProtocolPing),
    sizeof(ENetProtocolSendReliable),
    sizeof(ENetProtocolSendUnreliable),
    sizeof(ENetProtocolSendFragment),
    sizeof(ENetProtocolSendUnsequenced),
    sizeof(ENetProtocolBandwidthLimit),
    sizeof(ENetProtocolThrottleConfigure),
    sizeof(ENetProtocolSendFragment)};

size_t enet_protocol_command_size(enet_uint8 commandNumber) {
  return commandSizes[commandNumber & ENET_PROTOCOL_COMMAND_MASK];
}

static void enet_protocol_change_state(ENetHost *host, ENetPeer *peer,
                                       ENetPeerState state) {
  if (state == ENET_PEER_STATE_CONNECTED ||
      state == ENET_PEER_STATE_DISCONNECT_LATER)
    enet_peer_on_connect(peer);
  else
    enet_peer_on_disconnect(peer);

  peer->state = state;
}

static void enet_protocol_dispatch_state(ENetHost *host, ENetPeer *peer,
                                         ENetPeerState state) {
  enet_protocol_change_state(host, peer, state);

  if (!(peer->flags & ENET_PEER_FLAG_NEEDS_DISPATCH)) {
    enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList);

    peer->flags |= ENET_PEER_FLAG_NEEDS_DISPATCH;
  }
}

static int enet_protocol_dispatch_incoming_commands(ENetHost *host,
                                                    ENetEvent *event) {
  while (!enet_list_empty(&host->dispatchQueue)) {
    ENetPeer *peer =
        (ENetPeer *)enet_list_remove(enet_list_begin(&host->dispatchQueue));

    peer->flags &= ~ENET_PEER_FLAG_NEEDS_DISPATCH;

    switch (peer->state) {
    case ENET_PEER_STATE_CONNECTION_PENDING:
    case ENET_PEER_STATE_CONNECTION_SUCCEEDED:
      enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED);

      event->type = ENET_EVENT_TYPE_CONNECT;
      event->peer = peer;
      event->data = peer->eventData;

      return 1;

    case ENET_PEER_STATE_ZOMBIE:
      host->recalculateBandwidthLimits = 1;

      event->type = ENET_EVENT_TYPE_DISCONNECT;
      event->peer = peer;
      event->data = peer->eventData;

      enet_peer_reset(peer);

      return 1;

    case ENET_PEER_STATE_CONNECTED:
      if (enet_list_empty(&peer->dispatchedCommands))
        continue;

      event->packet = enet_peer_receive(peer, &event->channelID);
      if (event->packet == NULL)
        continue;

      event->type = ENET_EVENT_TYPE_RECEIVE;
      event->peer = peer;

      if (!enet_list_empty(&peer->dispatchedCommands)) {
        peer->flags |= ENET_PEER_FLAG_NEEDS_DISPATCH;

        enet_list_insert(enet_list_end(&host->dispatchQueue),
                         &peer->dispatchList);
      }

      return 1;

    default:
      break;
    }
  }

  return 0;
}

static void enet_protocol_notify_connect(ENetHost *host, ENetPeer *peer,
                                         ENetEvent *event) {
  host->recalculateBandwidthLimits = 1;

  if (event != NULL) {
    enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED);

    event->type = ENET_EVENT_TYPE_CONNECT;
    event->peer = peer;
    event->data = peer->eventData;
  } else
    enet_protocol_dispatch_state(host, peer,
                                 peer->state == ENET_PEER_STATE_CONNECTING
                                     ? ENET_PEER_STATE_CONNECTION_SUCCEEDED
                                     : ENET_PEER_STATE_CONNECTION_PENDING);
}

static void enet_protocol_notify_disconnect(ENetHost *host, ENetPeer *peer,
                                            ENetEvent *event) {
  if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING)
    host->recalculateBandwidthLimits = 1;

  if (peer->state != ENET_PEER_STATE_CONNECTING &&
      peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED)
    enet_peer_reset(peer);
  else if (event != NULL) {
    event->type = ENET_EVENT_TYPE_DISCONNECT;
    event->peer = peer;
    event->data = 0;

    enet_peer_reset(peer);
  } else {
    peer->eventData = 0;

    enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE);
  }
}

static void enet_protocol_remove_sent_unreliable_commands(
    ENetPeer *peer, ENetList *sentUnreliableCommands) {
  ENetOutgoingCommand *outgoingCommand;

  if (enet_list_empty(sentUnreliableCommands))
    return;

  do {
    outgoingCommand =
        (ENetOutgoingCommand *)enet_list_front(sentUnreliableCommands);

    enet_list_remove(&outgoingCommand->outgoingCommandList);

    if (outgoingCommand->packet != NULL) {
      --outgoingCommand->packet->referenceCount;

      if (outgoingCommand->packet->referenceCount == 0) {
        outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT;

        enet_packet_destroy(outgoingCommand->packet);
      }
    }

    enet_free(outgoingCommand);
  } while (!enet_list_empty(sentUnreliableCommands));

  if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER &&
      !enet_peer_has_outgoing_commands(peer))
    enet_peer_disconnect(peer, peer->eventData);
}

static ENetOutgoingCommand *enet_protocol_find_sent_reliable_command(
    ENetList *list, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) {
  ENetListIterator currentCommand;

  for (currentCommand = enet_list_begin(list);
       currentCommand != enet_list_end(list);
       currentCommand = enet_list_next(currentCommand)) {
    ENetOutgoingCommand *outgoingCommand =
        (ENetOutgoingCommand *)currentCommand;

    if (!(outgoingCommand->command.header.command &
          ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
      continue;

    if (outgoingCommand->sendAttempts < 1)
      break;

    if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber &&
        outgoingCommand->command.header.channelID == channelID)
      return outgoingCommand;
  }

  return NULL;
}

static ENetProtocolCommand enet_protocol_remove_sent_reliable_command(
    ENetPeer *peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) {
  ENetOutgoingCommand *outgoingCommand = NULL;
  ENetListIterator currentCommand;
  ENetProtocolCommand commandNumber;
  int wasSent = 1;

  for (currentCommand = enet_list_begin(&peer->sentReliableCommands);
       currentCommand != enet_list_end(&peer->sentReliableCommands);
       currentCommand = enet_list_next(currentCommand)) {
    outgoingCommand = (ENetOutgoingCommand *)currentCommand;

    if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber &&
        outgoingCommand->command.header.channelID == channelID)
      break;
  }

  if (currentCommand == enet_list_end(&peer->sentReliableCommands)) {
    outgoingCommand = enet_protocol_find_sent_reliable_command(
        &peer->outgoingCommands, reliableSequenceNumber, channelID);
    if (outgoingCommand == NULL)
      outgoingCommand = enet_protocol_find_sent_reliable_command(
          &peer->outgoingSendReliableCommands, reliableSequenceNumber,
          channelID);

    wasSent = 0;
  }

  if (outgoingCommand == NULL)
    return ENET_PROTOCOL_COMMAND_NONE;

  if (channelID < peer->channelCount) {
    ENetChannel *channel = &peer->channels[channelID];
    enet_uint16 reliableWindow =
        reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
    if (channel->reliableWindows[reliableWindow] > 0) {
      --channel->reliableWindows[reliableWindow];
      if (!channel->reliableWindows[reliableWindow])
        channel->usedReliableWindows &= ~(1u << reliableWindow);
    }
  }

  commandNumber =
      (ENetProtocolCommand)(outgoingCommand->command.header.command &
                            ENET_PROTOCOL_COMMAND_MASK);

  enet_list_remove(&outgoingCommand->outgoingCommandList);

  if (outgoingCommand->packet != NULL) {
    if (wasSent)
      peer->reliableDataInTransit -= outgoingCommand->fragmentLength;

    --outgoingCommand->packet->referenceCount;

    if (outgoingCommand->packet->referenceCount == 0) {
      outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT;

      enet_packet_destroy(outgoingCommand->packet);
    }
  }

  enet_free(outgoingCommand);

  if (enet_list_empty(&peer->sentReliableCommands))
    return commandNumber;

  outgoingCommand =
      (ENetOutgoingCommand *)enet_list_front(&peer->sentReliableCommands);

  peer->nextTimeout =
      outgoingCommand->sentTime + outgoingCommand->roundTripTimeout;

  return commandNumber;
}

static ENetPeer *enet_protocol_handle_connect(ENetHost *host,
                                              ENetProtocolHeader *header,
                                              ENetProtocol *command) {
  enet_uint8 incomingSessionID, outgoingSessionID;
  enet_uint32 mtu, windowSize;
  ENetChannel *channel;
  size_t channelCount, duplicatePeers = 0;
  ENetPeer *currentPeer, *peer = NULL;
  ENetProtocol verifyCommand;

  channelCount = ENET_NET_TO_HOST_32(command->connect.channelCount);

  if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT ||
      channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
    return NULL;

  for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount];
       ++currentPeer) {
    if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) {
      if (peer == NULL)
        peer = currentPeer;
    } else if (currentPeer->state != ENET_PEER_STATE_CONNECTING &&
               enet_address_equal_host(&currentPeer->address,
                                       &host->receivedAddress)) {
      if (currentPeer->address.port == host->receivedAddress.port &&
          currentPeer->connectID == command->connect.connectID)
        return NULL;

      ++duplicatePeers;
    }
  }

  if (peer == NULL || duplicatePeers >= host->duplicatePeers)
    return NULL;

  if (channelCount > host->channelLimit)
    channelCount = host->channelLimit;
  peer->channels =
      (ENetChannel *)enet_malloc(channelCount * sizeof(ENetChannel));
  if (peer->channels == NULL)
    return NULL;
  peer->channelCount = channelCount;
  peer->state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT;
  peer->connectID = command->connect.connectID;
  peer->address = host->receivedAddress;
  peer->mtu = host->mtu;
  peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->connect.outgoingPeerID);
  peer->incomingBandwidth =
      ENET_NET_TO_HOST_32(command->connect.incomingBandwidth);
  peer->outgoingBandwidth =
      ENET_NET_TO_HOST_32(command->connect.outgoingBandwidth);
  peer->packetThrottleInterval =
      ENET_NET_TO_HOST_32(command->connect.packetThrottleInterval);
  peer->packetThrottleAcceleration =
      ENET_NET_TO_HOST_32(command->connect.packetThrottleAcceleration);
  peer->packetThrottleDeceleration =
      ENET_NET_TO_HOST_32(command->connect.packetThrottleDeceleration);
  peer->eventData = ENET_NET_TO_HOST_32(command->connect.data);

  incomingSessionID = command->connect.incomingSessionID == 0xFF
                          ? peer->outgoingSessionID
                          : command->connect.incomingSessionID;
  incomingSessionID =
      (incomingSessionID + 1) &
      (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
  if (incomingSessionID == peer->outgoingSessionID)
    incomingSessionID =
        (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >>
                                   ENET_PROTOCOL_HEADER_SESSION_SHIFT);
  peer->outgoingSessionID = incomingSessionID;

  outgoingSessionID = command->connect.outgoingSessionID == 0xFF
                          ? peer->incomingSessionID
                          : command->connect.outgoingSessionID;
  outgoingSessionID =
      (outgoingSessionID + 1) &
      (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT);
  if (outgoingSessionID == peer->incomingSessionID)
    outgoingSessionID =
        (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >>
                                   ENET_PROTOCOL_HEADER_SESSION_SHIFT);
  peer->incomingSessionID = outgoingSessionID;

  for (channel = peer->channels; channel < &peer->channels[channelCount];
       ++channel) {
    channel->outgoingReliableSequenceNumber = 0;
    channel->outgoingUnreliableSequenceNumber = 0;
    channel->incomingReliableSequenceNumber = 0;
    channel->incomingUnreliableSequenceNumber = 0;

    enet_list_clear(&channel->incomingReliableCommands);
    enet_list_clear(&channel->incomingUnreliableCommands);

    channel->usedReliableWindows = 0;
    memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows));
  }

  mtu = ENET_NET_TO_HOST_32(command->connect.mtu);

  if (mtu < ENET_PROTOCOL_MINIMUM_MTU)
    mtu = ENET_PROTOCOL_MINIMUM_MTU;
  else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
    mtu = ENET_PROTOCOL_MAXIMUM_MTU;

  if (mtu < peer->mtu)
    peer->mtu = mtu;

  if (host->outgoingBandwidth == 0 && peer->incomingBandwidth == 0)
    peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
  else if (host->outgoingBandwidth == 0 || peer->incomingBandwidth == 0)
    peer->windowSize =
        (ENET_MAX(host->outgoingBandwidth, peer->incomingBandwidth) /
         ENET_PEER_WINDOW_SIZE_SCALE) *
        ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
  else
    peer->windowSize =
        (ENET_MIN(host->outgoingBandwidth, peer->incomingBandwidth) /
         ENET_PEER_WINDOW_SIZE_SCALE) *
        ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

  if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
    peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
  else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
    peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

  if (host->incomingBandwidth == 0)
    windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
  else
    windowSize = (host->incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) *
                 ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

  if (windowSize > ENET_NET_TO_HOST_32(command->connect.windowSize))
    windowSize = ENET_NET_TO_HOST_32(command->connect.windowSize);

  if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
    windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
  else if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
    windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

  verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT |
                                 ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
  verifyCommand.header.channelID = 0xFF;
  verifyCommand.verifyConnect.outgoingPeerID =
      ENET_HOST_TO_NET_16(peer->incomingPeerID);
  verifyCommand.verifyConnect.incomingSessionID = incomingSessionID;
  verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID;
  verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_32(peer->mtu);
  verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32(windowSize);
  verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32(channelCount);
  verifyCommand.verifyConnect.incomingBandwidth =
      ENET_HOST_TO_NET_32(host->incomingBandwidth);
  verifyCommand.verifyConnect.outgoingBandwidth =
      ENET_HOST_TO_NET_32(host->outgoingBandwidth);
  verifyCommand.verifyConnect.packetThrottleInterval =
      ENET_HOST_TO_NET_32(peer->packetThrottleInterval);
  verifyCommand.verifyConnect.packetThrottleAcceleration =
      ENET_HOST_TO_NET_32(peer->packetThrottleAcceleration);
  verifyCommand.verifyConnect.packetThrottleDeceleration =
      ENET_HOST_TO_NET_32(peer->packetThrottleDeceleration);
  verifyCommand.verifyConnect.connectID = peer->connectID;

  enet_peer_queue_outgoing_command(peer, &verifyCommand, NULL, 0, 0);

  return peer;
}

static int enet_protocol_handle_send_reliable(ENetHost *host, ENetPeer *peer,
                                              const ENetProtocol *command,
                                              enet_uint8 **currentData) {
  size_t dataLength;

  if (command->header.channelID >= peer->channelCount ||
      (peer->state != ENET_PEER_STATE_CONNECTED &&
       peer->state != ENET_PEER_STATE_DISCONNECT_LATER))
    return -1;

  dataLength = ENET_NET_TO_HOST_16(command->sendReliable.dataLength);
  *currentData += dataLength;
  if (dataLength > host->maximumPacketSize ||
      *currentData < host->receivedData ||
      *currentData > &host->receivedData[host->receivedDataLength])
    return -1;

  if (enet_peer_queue_incoming_command(
          peer, command,
          (const enet_uint8 *)command + sizeof(ENetProtocolSendReliable),
          dataLength, ENET_PACKET_FLAG_RELIABLE, 0) == NULL)
    return -1;

  return 0;
}

static int enet_protocol_handle_send_unsequenced(ENetHost *host, ENetPeer *peer,
                                                 const ENetProtocol *command,
                                                 enet_uint8 **currentData) {
  enet_uint32 unsequencedGroup, index;
  size_t dataLength;

  if (command->header.channelID >= peer->channelCount ||
      (peer->state != ENET_PEER_STATE_CONNECTED &&
       peer->state != ENET_PEER_STATE_DISCONNECT_LATER))
    return -1;

  dataLength = ENET_NET_TO_HOST_16(command->sendUnsequenced.dataLength);
  *currentData += dataLength;
  if (dataLength > host->maximumPacketSize ||
      *currentData < host->receivedData ||
      *currentData > &host->receivedData[host->receivedDataLength])
    return -1;

  unsequencedGroup =
      ENET_NET_TO_HOST_16(command->sendUnsequenced.unsequencedGroup);
  index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE;

  if (unsequencedGroup < peer->incomingUnsequencedGroup)
    unsequencedGroup += 0x10000;

  if (unsequencedGroup >= (enet_uint32)peer->incomingUnsequencedGroup +
                              ENET_PEER_FREE_UNSEQUENCED_WINDOWS *
                                  ENET_PEER_UNSEQUENCED_WINDOW_SIZE)
    return 0;

  unsequencedGroup &= 0xFFFF;

  if (unsequencedGroup - index != peer->incomingUnsequencedGroup) {
    peer->incomingUnsequencedGroup = unsequencedGroup - index;

    memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow));
  } else if (peer->unsequencedWindow[index / 32] & (1u << (index % 32)))
    return 0;

  if (enet_peer_queue_incoming_command(
          peer, command,
          (const enet_uint8 *)command + sizeof(ENetProtocolSendUnsequenced),
          dataLength, ENET_PACKET_FLAG_UNSEQUENCED, 0) == NULL)
    return -1;

  peer->unsequencedWindow[index / 32] |= 1u << (index % 32);

  return 0;
}

static int enet_protocol_handle_send_unreliable(ENetHost *host, ENetPeer *peer,
                                                const ENetProtocol *command,
                                                enet_uint8 **currentData) {
  size_t dataLength;

  if (command->header.channelID >= peer->channelCount ||
      (peer->state != ENET_PEER_STATE_CONNECTED &&
       peer->state != ENET_PEER_STATE_DISCONNECT_LATER))
    return -1;

  dataLength = ENET_NET_TO_HOST_16(command->sendUnreliable.dataLength);
  *currentData += dataLength;
  if (dataLength > host->maximumPacketSize ||
      *currentData < host->receivedData ||
      *currentData > &host->receivedData[host->receivedDataLength])
    return -1;

  if (enet_peer_queue_incoming_command(peer, command,
                                       (const enet_uint8 *)command +
                                           sizeof(ENetProtocolSendUnreliable),
                                       dataLength, 0, 0) == NULL)
    return -1;

  return 0;
}

static int enet_protocol_handle_send_fragment(ENetHost *host, ENetPeer *peer,
                                              const ENetProtocol *command,
                                              enet_uint8 **currentData) {
  enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength,
      startSequenceNumber, totalLength;
  ENetChannel *channel;
  enet_uint16 startWindow, currentWindow;
  ENetListIterator currentCommand;
  ENetIncomingCommand *startCommand = NULL;

  if (command->header.channelID >= peer->channelCount ||
      (peer->state != ENET_PEER_STATE_CONNECTED &&
       peer->state != ENET_PEER_STATE_DISCONNECT_LATER))
    return -1;

  fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength);
  *currentData += fragmentLength;
  if (fragmentLength <= 0 || fragmentLength > host->maximumPacketSize ||
      *currentData < host->receivedData ||
      *currentData > &host->receivedData[host->receivedDataLength])
    return -1;

  channel = &peer->channels[command->header.channelID];
  startSequenceNumber =
      ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber);
  startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
  currentWindow =
      channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

  if (startSequenceNumber < channel->incomingReliableSequenceNumber)
    startWindow += ENET_PEER_RELIABLE_WINDOWS;

  if (startWindow < currentWindow ||
      startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1)
    return 0;

  fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber);
  fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount);
  fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset);
  totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength);

  if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT ||
      fragmentNumber >= fragmentCount ||
      totalLength > host->maximumPacketSize || totalLength < fragmentCount ||
      fragmentOffset >= totalLength ||
      fragmentLength > totalLength - fragmentOffset)
    return -1;

  for (currentCommand = enet_list_previous(
           enet_list_end(&channel->incomingReliableCommands));
       currentCommand != enet_list_end(&channel->incomingReliableCommands);
       currentCommand = enet_list_previous(currentCommand)) {
    ENetIncomingCommand *incomingCommand =
        (ENetIncomingCommand *)currentCommand;

    if (startSequenceNumber >= channel->incomingReliableSequenceNumber) {
      if (incomingCommand->reliableSequenceNumber <
          channel->incomingReliableSequenceNumber)
        continue;
    } else if (incomingCommand->reliableSequenceNumber >=
               channel->incomingReliableSequenceNumber)
      break;

    if (incomingCommand->reliableSequenceNumber <= startSequenceNumber) {
      if (incomingCommand->reliableSequenceNumber < startSequenceNumber)
        break;

      if ((incomingCommand->command.header.command &
           ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT ||
          totalLength != incomingCommand->packet->dataLength ||
          fragmentCount != incomingCommand->fragmentCount)
        return -1;

      startCommand = incomingCommand;
      break;
    }
  }

  if (startCommand == NULL) {
    ENetProtocol hostCommand = *command;

    hostCommand.header.reliableSequenceNumber = startSequenceNumber;

    startCommand = enet_peer_queue_incoming_command(
        peer, &hostCommand, NULL, totalLength, ENET_PACKET_FLAG_RELIABLE,
        fragmentCount);
    if (startCommand == NULL)
      return -1;
  }

  if ((startCommand->fragments[fragmentNumber / 32] &
       (1u << (fragmentNumber % 32))) == 0) {
    --startCommand->fragmentsRemaining;

    startCommand->fragments[fragmentNumber / 32] |=
        (1u << (fragmentNumber % 32));

    if (fragmentOffset + fragmentLength > startCommand->packet->dataLength)
      fragmentLength = startCommand->packet->dataLength - fragmentOffset;

    memcpy(startCommand->packet->data + fragmentOffset,
           (enet_uint8 *)command + sizeof(ENetProtocolSendFragment),
           fragmentLength);

    if (startCommand->fragmentsRemaining <= 0)
      enet_peer_dispatch_incoming_reliable_commands(peer, channel, NULL);
  }

  return 0;
}

static int
enet_protocol_handle_send_unreliable_fragment(ENetHost *host, ENetPeer *peer,
                                              const ENetProtocol *command,
                                              enet_uint8 **currentData) {
  enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength,
      reliableSequenceNumber, startSequenceNumber, totalLength;
  enet_uint16 reliableWindow, currentWindow;
  ENetChannel *channel;
  ENetListIterator currentCommand;
  ENetIncomingCommand *startCommand = NULL;

  if (command->header.channelID >= peer->channelCount ||
      (peer->state != ENET_PEER_STATE_CONNECTED &&
       peer->state != ENET_PEER_STATE_DISCONNECT_LATER))
    return -1;

  fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength);
  *currentData += fragmentLength;
  if (fragmentLength <= 0 || fragmentLength > host->maximumPacketSize ||
      *currentData < host->receivedData ||
      *currentData > &host->receivedData[host->receivedDataLength])
    return -1;

  channel = &peer->channels[command->header.channelID];
  reliableSequenceNumber = command->header.reliableSequenceNumber;
  startSequenceNumber =
      ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber);

  reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
  currentWindow =
      channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;

  if (reliableSequenceNumber < channel->incomingReliableSequenceNumber)
    reliableWindow += ENET_PEER_RELIABLE_WINDOWS;

  if (reliableWindow < currentWindow ||
      reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1)
    return 0;

  if (reliableSequenceNumber == channel->incomingReliableSequenceNumber &&
      startSequenceNumber <= channel->incomingUnreliableSequenceNumber)
    return 0;

  fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber);
  fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount);
  fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset);
  totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength);

  if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT ||
      fragmentNumber >= fragmentCount ||
      totalLength > host->maximumPacketSize || totalLength < fragmentCount ||
      fragmentOffset >= totalLength ||
      fragmentLength > totalLength - fragmentOffset)
    return -1;

  for (currentCommand = enet_list_previous(
           enet_list_end(&channel->incomingUnreliableCommands));
       currentCommand != enet_list_end(&channel->incomingUnreliableCommands);
       currentCommand = enet_list_previous(currentCommand)) {
    ENetIncomingCommand *incomingCommand =
        (ENetIncomingCommand *)currentCommand;

    if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) {
      if (incomingCommand->reliableSequenceNumber <
          channel->incomingReliableSequenceNumber)
        continue;
    } else if (incomingCommand->reliableSequenceNumber >=
               channel->incomingReliableSequenceNumber)
      break;

    if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber)
      break;

    if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber)
      continue;

    if (incomingCommand->unreliableSequenceNumber <= startSequenceNumber) {
      if (incomingCommand->unreliableSequenceNumber < startSequenceNumber)
        break;

      if ((incomingCommand->command.header.command &
           ENET_PROTOCOL_COMMAND_MASK) !=
              ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT ||
          totalLength != incomingCommand->packet->dataLength ||
          fragmentCount != incomingCommand->fragmentCount)
        return -1;

      startCommand = incomingCommand;
      break;
    }
  }

  if (startCommand == NULL) {
    startCommand = enet_peer_queue_incoming_command(
        peer, command, NULL, totalLength, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT,
        fragmentCount);
    if (startCommand == NULL)
      return -1;
  }

  if ((startCommand->fragments[fragmentNumber / 32] &
       (1u << (fragmentNumber % 32))) == 0) {
    --startCommand->fragmentsRemaining;

    startCommand->fragments[fragmentNumber / 32] |=
        (1u << (fragmentNumber % 32));

    if (fragmentOffset + fragmentLength > startCommand->packet->dataLength)
      fragmentLength = startCommand->packet->dataLength - fragmentOffset;

    memcpy(startCommand->packet->data + fragmentOffset,
           (enet_uint8 *)command + sizeof(ENetProtocolSendFragment),
           fragmentLength);

    if (startCommand->fragmentsRemaining <= 0)
      enet_peer_dispatch_incoming_unreliable_commands(peer, channel, NULL);
  }

  return 0;
}

static int enet_protocol_handle_ping(ENetHost *host, ENetPeer *peer,
                                     const ENetProtocol *command) {
  if (peer->state != ENET_PEER_STATE_CONNECTED &&
      peer->state != ENET_PEER_STATE_DISCONNECT_LATER)
    return -1;

  return 0;
}

static int enet_protocol_handle_bandwidth_limit(ENetHost *host, ENetPeer *peer,
                                                const ENetProtocol *command) {
  if (peer->state != ENET_PEER_STATE_CONNECTED &&
      peer->state != ENET_PEER_STATE_DISCONNECT_LATER)
    return -1;

  if (peer->incomingBandwidth != 0)
    --host->bandwidthLimitedPeers;

  peer->incomingBandwidth =
      ENET_NET_TO_HOST_32(command->bandwidthLimit.incomingBandwidth);
  peer->outgoingBandwidth =
      ENET_NET_TO_HOST_32(command->bandwidthLimit.outgoingBandwidth);

  if (peer->incomingBandwidth != 0)
    ++host->bandwidthLimitedPeers;

  if (peer->incomingBandwidth == 0 && host->outgoingBandwidth == 0)
    peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
  else if (peer->incomingBandwidth == 0 || host->outgoingBandwidth == 0)
    peer->windowSize =
        (ENET_MAX(peer->incomingBandwidth, host->outgoingBandwidth) /
         ENET_PEER_WINDOW_SIZE_SCALE) *
        ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
  else
    peer->windowSize =
        (ENET_MIN(peer->incomingBandwidth, host->outgoingBandwidth) /
         ENET_PEER_WINDOW_SIZE_SCALE) *
        ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

  if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
    peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
  else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
    peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

  return 0;
}

static int
enet_protocol_handle_throttle_configure(ENetHost *host, ENetPeer *peer,
                                        const ENetProtocol *command) {
  if (peer->state != ENET_PEER_STATE_CONNECTED &&
      peer->state != ENET_PEER_STATE_DISCONNECT_LATER)
    return -1;

  peer->packetThrottleInterval =
      ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleInterval);
  peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(
      command->throttleConfigure.packetThrottleAcceleration);
  peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(
      command->throttleConfigure.packetThrottleDeceleration);

  return 0;
}

static int enet_protocol_handle_disconnect(ENetHost *host, ENetPeer *peer,
                                           const ENetProtocol *command) {
  if (peer->state == ENET_PEER_STATE_DISCONNECTED ||
      peer->state == ENET_PEER_STATE_ZOMBIE ||
      peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT)
    return 0;

  enet_peer_reset_queues(peer);

  if (peer->state == ENET_PEER_STATE_CONNECTION_SUCCEEDED ||
      peer->state == ENET_PEER_STATE_DISCONNECTING ||
      peer->state == ENET_PEER_STATE_CONNECTING)
    enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE);
  else if (peer->state != ENET_PEER_STATE_CONNECTED &&
           peer->state != ENET_PEER_STATE_DISCONNECT_LATER) {
    if (peer->state == ENET_PEER_STATE_CONNECTION_PENDING)
      host->recalculateBandwidthLimits = 1;

    enet_peer_reset(peer);
  } else if (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
    enet_protocol_change_state(host, peer,
                               ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT);
  else
    enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE);

  if (peer->state != ENET_PEER_STATE_DISCONNECTED)
    peer->eventData = ENET_NET_TO_HOST_32(command->disconnect.data);

  return 0;
}

static int enet_protocol_handle_acknowledge(ENetHost *host, ENetEvent *event,
                                            ENetPeer *peer,
                                            const ENetProtocol *command) {
  enet_uint32 roundTripTime, receivedSentTime, receivedReliableSequenceNumber;
  ENetProtocolCommand commandNumber;

  if (peer->state == ENET_PEER_STATE_DISCONNECTED ||
      peer->state == ENET_PEER_STATE_ZOMBIE)
    return 0;

  receivedSentTime = ENET_NET_TO_HOST_16(command->acknowledge.receivedSentTime);
  receivedSentTime |= host->serviceTime & 0xFFFF0000;
  if ((receivedSentTime & 0x8000) > (host->serviceTime & 0x8000))
    receivedSentTime -= 0x10000;

  if (ENET_TIME_LESS(host->serviceTime, receivedSentTime))
    return 0;

  roundTripTime = ENET_TIME_DIFFERENCE(host->serviceTime, receivedSentTime);
  roundTripTime = ENET_MAX(roundTripTime, 1);

  if (peer->lastReceiveTime > 0) {
    enet_peer_throttle(peer, roundTripTime);

    peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 4;

    if (roundTripTime >= peer->roundTripTime) {
      enet_uint32 diff = roundTripTime - peer->roundTripTime;
      peer->roundTripTimeVariance += diff / 4;
      peer->roundTripTime += diff / 8;
    } else {
      enet_uint32 diff = peer->roundTripTime - roundTripTime;
      peer->roundTripTimeVariance += diff / 4;
      peer->roundTripTime -= diff / 8;
    }
  } else {
    peer->roundTripTime = roundTripTime;
    peer->roundTripTimeVariance = (roundTripTime + 1) / 2;
  }

  if (peer->roundTripTime < peer->lowestRoundTripTime)
    peer->lowestRoundTripTime = peer->roundTripTime;

  if (peer->roundTripTimeVariance > peer->highestRoundTripTimeVariance)
    peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance;

  if (peer->packetThrottleEpoch == 0 ||
      ENET_TIME_DIFFERENCE(host->serviceTime, peer->packetThrottleEpoch) >=
          peer->packetThrottleInterval) {
    peer->lastRoundTripTime = peer->lowestRoundTripTime;
    peer->lastRoundTripTimeVariance =
        ENET_MAX(peer->highestRoundTripTimeVariance, 1);
    peer->lowestRoundTripTime = peer->roundTripTime;
    peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance;
    peer->packetThrottleEpoch = host->serviceTime;
  }

  peer->lastReceiveTime = ENET_MAX(host->serviceTime, 1);
  peer->earliestTimeout = 0;

  receivedReliableSequenceNumber =
      ENET_NET_TO_HOST_16(command->acknowledge.receivedReliableSequenceNumber);

  commandNumber = enet_protocol_remove_sent_reliable_command(
      peer, receivedReliableSequenceNumber, command->header.channelID);

  switch (peer->state) {
  case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT:
    if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT)
      return -1;

    enet_protocol_notify_connect(host, peer, event);
    break;

  case ENET_PEER_STATE_DISCONNECTING:
    if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT)
      return -1;

    enet_protocol_notify_disconnect(host, peer, event);
    break;

  case ENET_PEER_STATE_DISCONNECT_LATER:
    if (!enet_peer_has_outgoing_commands(peer))
      enet_peer_disconnect(peer, peer->eventData);
    break;

  default:
    break;
  }

  return 0;
}

static int enet_protocol_handle_verify_connect(ENetHost *host, ENetEvent *event,
                                               ENetPeer *peer,
                                               const ENetProtocol *command) {
  enet_uint32 mtu, windowSize;
  size_t channelCount;

  if (peer->state != ENET_PEER_STATE_CONNECTING)
    return 0;

  channelCount = ENET_NET_TO_HOST_32(command->verifyConnect.channelCount);

  if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT ||
      channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT ||
      ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleInterval) !=
          peer->packetThrottleInterval ||
      ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleAcceleration) !=
          peer->packetThrottleAcceleration ||
      ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleDeceleration) !=
          peer->packetThrottleDeceleration ||
      command->verifyConnect.connectID != peer->connectID) {
    peer->eventData = 0;

    enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE);

    return -1;
  }

  enet_protocol_remove_sent_reliable_command(peer, 1, 0xFF);

  if (channelCount < peer->channelCount)
    peer->channelCount = channelCount;

  peer->outgoingPeerID =
      ENET_NET_TO_HOST_16(command->verifyConnect.outgoingPeerID);
  peer->incomingSessionID = command->verifyConnect.incomingSessionID;
  peer->outgoingSessionID = command->verifyConnect.outgoingSessionID;

  mtu = ENET_NET_TO_HOST_32(command->verifyConnect.mtu);

  if (mtu < ENET_PROTOCOL_MINIMUM_MTU)
    mtu = ENET_PROTOCOL_MINIMUM_MTU;
  else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
    mtu = ENET_PROTOCOL_MAXIMUM_MTU;

  if (mtu < peer->mtu)
    peer->mtu = mtu;

  windowSize = ENET_NET_TO_HOST_32(command->verifyConnect.windowSize);

  if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
    windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

  if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
    windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

  if (windowSize < peer->windowSize)
    peer->windowSize = windowSize;

  peer->incomingBandwidth =
      ENET_NET_TO_HOST_32(command->verifyConnect.incomingBandwidth);
  peer->outgoingBandwidth =
      ENET_NET_TO_HOST_32(command->verifyConnect.outgoingBandwidth);

  enet_protocol_notify_connect(host, peer, event);
  return 0;
}

static int enet_protocol_handle_incoming_commands(ENetHost *host,
                                                  ENetEvent *event) {
  ENetProtocolHeader *header;
  ENetNewProtocolHeader *newHeader;
  ENetProtocol *command;
  ENetPeer *peer;
  enet_uint8 *currentData;
  size_t headerSize;
  enet_uint16 peerID, flags;
  enet_uint8 sessionID;

  if (host->usingNewPacketForServer) {
    if (host->receivedDataLength <
        (size_t)&((ENetNewProtocolHeader *)0)->sentTime)
      return 0;
  } else {
    if (host->receivedDataLength < (size_t)&((ENetProtocolHeader *)0)->sentTime)
      return 0;
  }

  if (host->usingNewPacketForServer)
    newHeader = (ENetNewProtocolHeader *)host->receivedData;
  else
    header = (ENetProtocolHeader *)host->receivedData;

  if (host->usingNewPacketForServer)
    peerID = ENET_NET_TO_HOST_16(newHeader->peerID);
  else
    peerID = ENET_NET_TO_HOST_16(header->peerID);
  sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >>
              ENET_PROTOCOL_HEADER_SESSION_SHIFT;
  flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK;
  peerID &=
      ~(ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK);

  if (host->usingNewPacketForServer)
    headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME
                      ? sizeof(ENetNewProtocolHeader)
                      : (size_t)&((ENetNewProtocolHeader *)0)->sentTime);
  else
    headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME
                      ? sizeof(ENetProtocolHeader)
                      : (size_t)&((ENetProtocolHeader *)0)->sentTime);
  if (host->checksum != NULL)
    headerSize += sizeof(enet_uint32);

  if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID)
    peer = NULL;
  else if (peerID >= host->peerCount)
    return 0;
  else {
    peer = &host->peers[peerID];

    if (peer->state == ENET_PEER_STATE_DISCONNECTED ||
        peer->state == ENET_PEER_STATE_ZOMBIE ||
        (!enet_address_equal(&host->receivedAddress, &peer->address) &&
         !enet_address_is_broadcast(&peer->address)) ||
        (peer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID &&
         sessionID != peer->incomingSessionID))
      return 0;

    if (host->usingNewPacketForServer) {
      enet_uint16 integrity[3];
      integrity[0] = ENET_NET_TO_HOST_16(newHeader->integrity[0]);
      integrity[1] = ENET_NET_TO_HOST_16(newHeader->integrity[1]);
      integrity[2] = ENET_NET_TO_HOST_16(newHeader->integrity[2]);

      if ((integrity[0] < 0 || integrity[0] > host->address.port) ||
          integrity[0] != (integrity[1] ^ host->address.port) ||
          host->address.port != (integrity[0] ^ integrity[1]) ||
          integrity[2] == peer->nonce)
        return 0;

      peer->nonce = integrity[2];
    }
  }

  if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) {
    size_t originalSize;
    if (host->compressor.context == NULL || host->compressor.decompress == NULL)
      return 0;

    originalSize = host->compressor.decompress(
        host->compressor.context, host->receivedData + headerSize,
        host->receivedDataLength - headerSize, host->packetData[1] + headerSize,
        sizeof(host->packetData[1]) - headerSize);
    if (originalSize <= 0 ||
        originalSize > sizeof(host->packetData[1]) - headerSize)
      return 0;

    if (host->usingNewPacketForServer)
      memcpy(host->packetData[1], newHeader, headerSize);
    else
      memcpy(host->packetData[1], header, headerSize);
    host->receivedData = host->packetData[1];
    host->receivedDataLength = headerSize + originalSize;
  }

  if (host->checksum != NULL) {
    enet_uint32 *checksum =
        (enet_uint32 *)&host->receivedData[headerSize - sizeof(enet_uint32)];
    enet_uint32 desiredChecksum, newChecksum;
    ENetBuffer buffer;
    /* Checksum may be an unaligned pointer, use memcpy to avoid undefined
     * behaviour. */
    memcpy(&desiredChecksum, checksum, sizeof(enet_uint32));

    newChecksum = peer != NULL ? peer->connectID : 0;
    memcpy(checksum, &newChecksum, sizeof(enet_uint32));

    buffer.data = host->receivedData;
    buffer.dataLength = host->receivedDataLength;

    if (host->checksum(&buffer, 1) != desiredChecksum)
      return 0;
  }

  if (peer != NULL) {
    peer->address.host = host->receivedAddress.host;
    peer->address.port = host->receivedAddress.port;
    peer->incomingDataTotal += host->receivedDataLength;
  }

  currentData = host->receivedData + headerSize;

  while (currentData < &host->receivedData[host->receivedDataLength]) {
    enet_uint8 commandNumber;
    size_t commandSize;

    command = (ENetProtocol *)currentData;

    if (currentData + sizeof(ENetProtocolCommandHeader) >
        &host->receivedData[host->receivedDataLength])
      break;

    commandNumber = command->header.command & ENET_PROTOCOL_COMMAND_MASK;
    if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT)
      break;

    commandSize = commandSizes[commandNumber];
    if (commandSize == 0 || currentData + commandSize >
                                &host->receivedData[host->receivedDataLength])
      break;

    currentData += commandSize;

    if (peer == NULL && commandNumber != ENET_PROTOCOL_COMMAND_CONNECT)
      break;

    command->header.reliableSequenceNumber =
        ENET_NET_TO_HOST_16(command->header.reliableSequenceNumber);

    switch (commandNumber) {
    case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE:
      if (enet_protocol_handle_acknowledge(host, event, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_CONNECT:
      if (peer != NULL)
        goto commandError;
      if (host->usingNewPacketForServer)
        peer = enet_protocol_handle_connect(
            host, *(ENetProtocolHeader **)&newHeader->peerID, command);
      else
        peer = enet_protocol_handle_connect(host, header, command);
      if (peer == NULL)
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT:
      if (enet_protocol_handle_verify_connect(host, event, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_DISCONNECT:
      if (enet_protocol_handle_disconnect(host, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_PING:
      if (enet_protocol_handle_ping(host, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_SEND_RELIABLE:
      if (enet_protocol_handle_send_reliable(host, peer, command, &currentData))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE:
      if (enet_protocol_handle_send_unreliable(host, peer, command,
                                               &currentData))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
      if (enet_protocol_handle_send_unsequenced(host, peer, command,
                                                &currentData))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT:
      if (enet_protocol_handle_send_fragment(host, peer, command, &currentData))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT:
      if (enet_protocol_handle_bandwidth_limit(host, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE:
      if (enet_protocol_handle_throttle_configure(host, peer, command))
        goto commandError;
      break;

    case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT:
      if (enet_protocol_handle_send_unreliable_fragment(host, peer, command,
                                                        &currentData))
        goto commandError;
      break;

    default:
      goto commandError;
    }

    if (peer != NULL && (command->header.command &
                         ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) {
      enet_uint16 sentTime;

      if (!(flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME))
        break;

      if (host->usingNewPacketForServer)
        sentTime = ENET_NET_TO_HOST_16(newHeader->sentTime);
      else
        sentTime = ENET_NET_TO_HOST_16(header->sentTime);

      switch (peer->state) {
      case ENET_PEER_STATE_DISCONNECTING:
      case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT:
      case ENET_PEER_STATE_DISCONNECTED:
      case ENET_PEER_STATE_ZOMBIE:
        break;

      case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT:
        if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) ==
            ENET_PROTOCOL_COMMAND_DISCONNECT)
          enet_peer_queue_acknowledgement(peer, command, sentTime);
        break;

      default:
        enet_peer_queue_acknowledgement(peer, command, sentTime);
        break;
      }
    }
  }

commandError:
  if (event != NULL && event->type != ENET_EVENT_TYPE_NONE)
    return 1;

  return 0;
}

static int enet_protocol_receive_incoming_commands(ENetHost *host,
                                                   ENetEvent *event) {
  int packets;

  for (packets = 0; packets < 256; ++packets) {
    int receivedLength;
    ENetBuffer buffer;

    buffer.data = host->packetData[0];
    buffer.dataLength = sizeof(host->packetData[0]);

    receivedLength =
        enet_socket_receive(host->socket, &host->receivedAddress, &buffer, 1);

    if (receivedLength == -2)
      continue;

    if (receivedLength < 0)
      return -1;

    if (receivedLength == 0)
      return 0;

    host->receivedData = host->packetData[0];
    host->receivedDataLength = receivedLength;

    host->totalReceivedData += receivedLength;
    host->totalReceivedPackets++;

    if (host->intercept != NULL) {
      switch (host->intercept(host, event)) {
      case 1:
        if (event != NULL && event->type != ENET_EVENT_TYPE_NONE)
          return 1;

        continue;

      case -1:
        return -1;

      default:
        break;
      }
    }

    switch (enet_protocol_handle_incoming_commands(host, event)) {
    case 1:
      return 1;

    case -1:
      return -1;

    default:
      break;
    }
  }

  return 0;
}

static void enet_protocol_send_acknowledgements(ENetHost *host,
                                                ENetPeer *peer) {
  ENetProtocol *command = &host->commands[host->commandCount];
  ENetBuffer *buffer = &host->buffers[host->bufferCount];
  ENetAcknowledgement *acknowledgement;
  ENetListIterator currentAcknowledgement;
  enet_uint16 reliableSequenceNumber;

  currentAcknowledgement = enet_list_begin(&peer->acknowledgements);

  while (currentAcknowledgement != enet_list_end(&peer->acknowledgements)) {
    if (command >=
            &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] ||
        buffer >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] ||
        peer->mtu - host->packetSize < sizeof(ENetProtocolAcknowledge)) {
      peer->flags |= ENET_PEER_FLAG_CONTINUE_SENDING;

      break;
    }

    acknowledgement = (ENetAcknowledgement *)currentAcknowledgement;

    currentAcknowledgement = enet_list_next(currentAcknowledgement);

    buffer->data = command;
    buffer->dataLength = sizeof(ENetProtocolAcknowledge);

    host->packetSize += buffer->dataLength;

    reliableSequenceNumber = ENET_HOST_TO_NET_16(
        acknowledgement->command.header.reliableSequenceNumber);

    command->header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE;
    command->header.channelID = acknowledgement->command.header.channelID;
    command->header.reliableSequenceNumber = reliableSequenceNumber;
    command->acknowledge.receivedReliableSequenceNumber =
        reliableSequenceNumber;
    command->acknowledge.receivedSentTime =
        ENET_HOST_TO_NET_16(acknowledgement->sentTime);

    if ((acknowledgement->command.header.command &
         ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT)
      enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE);

    enet_list_remove(&acknowledgement->acknowledgementList);
    enet_free(acknowledgement);

    ++command;
    ++buffer;
  }

  host->commandCount = command - host->commands;
  host->bufferCount = buffer - host->buffers;
}

static int enet_protocol_check_timeouts(ENetHost *host, ENetPeer *peer,
                                        ENetEvent *event) {
  ENetOutgoingCommand *outgoingCommand;
  ENetListIterator currentCommand, insertPosition, insertSendReliablePosition;

  currentCommand = enet_list_begin(&peer->sentReliableCommands);
  insertPosition = enet_list_begin(&peer->outgoingCommands);
  insertSendReliablePosition =
      enet_list_begin(&peer->outgoingSendReliableCommands);

  while (currentCommand != enet_list_end(&peer->sentReliableCommands)) {
    outgoingCommand = (ENetOutgoingCommand *)currentCommand;

    currentCommand = enet_list_next(currentCommand);

    if (ENET_TIME_DIFFERENCE(host->serviceTime, outgoingCommand->sentTime) <
        outgoingCommand->roundTripTimeout)
      continue;

    if (peer->earliestTimeout == 0 ||
        ENET_TIME_LESS(outgoingCommand->sentTime, peer->earliestTimeout))
      peer->earliestTimeout = outgoingCommand->sentTime;

    if (peer->earliestTimeout != 0 &&
        (ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >=
             peer->timeoutMaximum ||
         ((1u << (outgoingCommand->sendAttempts - 1)) >= peer->timeoutLimit &&
          ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >=
              peer->timeoutMinimum))) {
      enet_protocol_notify_disconnect(host, peer, event);

      return 1;
    }

    ++peer->packetsLost;

    outgoingCommand->roundTripTimeout *= 2;

    if (outgoingCommand->packet != NULL) {
      peer->reliableDataInTransit -= outgoingCommand->fragmentLength;

      enet_list_insert(insertSendReliablePosition,
                       enet_list_remove(&outgoingCommand->outgoingCommandList));
    } else
      enet_list_insert(insertPosition,
                       enet_list_remove(&outgoingCommand->outgoingCommandList));

    if (currentCommand == enet_list_begin(&peer->sentReliableCommands) &&
        !enet_list_empty(&peer->sentReliableCommands)) {
      outgoingCommand = (ENetOutgoingCommand *)currentCommand;

      peer->nextTimeout =
          outgoingCommand->sentTime + outgoingCommand->roundTripTimeout;
    }
  }

  return 0;
}

static int
enet_protocol_check_outgoing_commands(ENetHost *host, ENetPeer *peer,
                                      ENetList *sentUnreliableCommands) {
  ENetProtocol *command = &host->commands[host->commandCount];
  ENetBuffer *buffer = &host->buffers[host->bufferCount];
  ENetOutgoingCommand *outgoingCommand;
  ENetListIterator currentCommand, currentSendReliableCommand;
  ENetChannel *channel = NULL;
  enet_uint16 reliableWindow = 0;
  size_t commandSize;
  int windowWrap = 0, canPing = 1;

  currentCommand = enet_list_begin(&peer->outgoingCommands);
  currentSendReliableCommand =
      enet_list_begin(&peer->outgoingSendReliableCommands);

  for (;;) {
    if (currentCommand != enet_list_end(&peer->outgoingCommands)) {
      outgoingCommand = (ENetOutgoingCommand *)currentCommand;

      if (currentSendReliableCommand !=
              enet_list_end(&peer->outgoingSendReliableCommands) &&
          ENET_TIME_LESS(
              ((ENetOutgoingCommand *)currentSendReliableCommand)->queueTime,
              outgoingCommand->queueTime))
        goto useSendReliableCommand;

      currentCommand = enet_list_next(currentCommand);
    } else if (currentSendReliableCommand !=
               enet_list_end(&peer->outgoingSendReliableCommands)) {
    useSendReliableCommand:
      outgoingCommand = (ENetOutgoingCommand *)currentSendReliableCommand;
      currentSendReliableCommand = enet_list_next(currentSendReliableCommand);
    } else
      break;

    if (outgoingCommand->command.header.command &
        ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) {
      channel = outgoingCommand->command.header.channelID < peer->channelCount
                    ? &peer->channels[outgoingCommand->command.header.channelID]
                    : NULL;
      reliableWindow = outgoingCommand->reliableSequenceNumber /
                       ENET_PEER_RELIABLE_WINDOW_SIZE;
      if (channel != NULL) {
        if (windowWrap)
          continue;
        else if (outgoingCommand->sendAttempts < 1 &&
                 !(outgoingCommand->reliableSequenceNumber %
                   ENET_PEER_RELIABLE_WINDOW_SIZE) &&
                 (channel->reliableWindows[(reliableWindow +
                                            ENET_PEER_RELIABLE_WINDOWS - 1) %
                                           ENET_PEER_RELIABLE_WINDOWS] >=
                      ENET_PEER_RELIABLE_WINDOW_SIZE ||
                  channel->usedReliableWindows &
                      ((((1u << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1)
                        << reliableWindow) |
                       (((1u << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >>
                        (ENET_PEER_RELIABLE_WINDOWS - reliableWindow))))) {
          windowWrap = 1;
          currentSendReliableCommand =
              enet_list_end(&peer->outgoingSendReliableCommands);

          continue;
        }
      }

      if (outgoingCommand->packet != NULL) {
        enet_uint32 windowSize = (peer->packetThrottle * peer->windowSize) /
                                 ENET_PEER_PACKET_THROTTLE_SCALE;

        if (peer->reliableDataInTransit + outgoingCommand->fragmentLength >
            ENET_MAX(windowSize, peer->mtu)) {
          currentSendReliableCommand =
              enet_list_end(&peer->outgoingSendReliableCommands);

          continue;
        }
      }

      canPing = 0;
    }

    commandSize = commandSizes[outgoingCommand->command.header.command &
                               ENET_PROTOCOL_COMMAND_MASK];
    if (command >=
            &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] ||
        buffer + 1 >=
            &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] ||
        peer->mtu - host->packetSize < commandSize ||
        (outgoingCommand->packet != NULL &&
         (enet_uint16)(peer->mtu - host->packetSize) <
             (enet_uint16)(commandSize + outgoingCommand->fragmentLength))) {
      peer->flags |= ENET_PEER_FLAG_CONTINUE_SENDING;

      break;
    }

    if (outgoingCommand->command.header.command &
        ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) {
      if (channel != NULL && outgoingCommand->sendAttempts < 1) {
        channel->usedReliableWindows |= 1u << reliableWindow;
        ++channel->reliableWindows[reliableWindow];
      }

      ++outgoingCommand->sendAttempts;

      if (outgoingCommand->roundTripTimeout == 0)
        outgoingCommand->roundTripTimeout =
            peer->roundTripTime + 4 * peer->roundTripTimeVariance;

      if (enet_list_empty(&peer->sentReliableCommands))
        peer->nextTimeout =
            host->serviceTime + outgoingCommand->roundTripTimeout;

      enet_list_insert(enet_list_end(&peer->sentReliableCommands),
                       enet_list_remove(&outgoingCommand->outgoingCommandList));

      outgoingCommand->sentTime = host->serviceTime;

      host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME;

      peer->reliableDataInTransit += outgoingCommand->fragmentLength;
    } else {
      if (outgoingCommand->packet != NULL &&
          outgoingCommand->fragmentOffset == 0) {
        peer->packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER;
        peer->packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE;

        if (peer->packetThrottleCounter > peer->packetThrottle) {
          enet_uint16 reliableSequenceNumber =
                          outgoingCommand->reliableSequenceNumber,
                      unreliableSequenceNumber =
                          outgoingCommand->unreliableSequenceNumber;
          for (;;) {
            --outgoingCommand->packet->referenceCount;

            if (outgoingCommand->packet->referenceCount == 0)
              enet_packet_destroy(outgoingCommand->packet);

            enet_list_remove(&outgoingCommand->outgoingCommandList);
            enet_free(outgoingCommand);

            if (currentCommand == enet_list_end(&peer->outgoingCommands))
              break;

            outgoingCommand = (ENetOutgoingCommand *)currentCommand;
            if (outgoingCommand->reliableSequenceNumber !=
                    reliableSequenceNumber ||
                outgoingCommand->unreliableSequenceNumber !=
                    unreliableSequenceNumber)
              break;

            currentCommand = enet_list_next(currentCommand);
          }

          continue;
        }
      }

      enet_list_remove(&outgoingCommand->outgoingCommandList);

      if (outgoingCommand->packet != NULL)
        enet_list_insert(enet_list_end(sentUnreliableCommands),
                         outgoingCommand);
    }

    buffer->data = command;
    buffer->dataLength = commandSize;

    host->packetSize += buffer->dataLength;

    *command = outgoingCommand->command;

    if (outgoingCommand->packet != NULL) {
      ++buffer;

      buffer->data =
          outgoingCommand->packet->data + outgoingCommand->fragmentOffset;
      buffer->dataLength = outgoingCommand->fragmentLength;

      host->packetSize += outgoingCommand->fragmentLength;
    } else if (!(outgoingCommand->command.header.command &
                 ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
      enet_free(outgoingCommand);

    ++peer->packetsSent;

    ++command;
    ++buffer;
  }

  host->commandCount = command - host->commands;
  host->bufferCount = buffer - host->buffers;

  if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER &&
      !enet_peer_has_outgoing_commands(peer) &&
      enet_list_empty(sentUnreliableCommands))
    enet_peer_disconnect(peer, peer->eventData);

  return canPing;
}

static int enet_protocol_send_outgoing_commands(ENetHost *host,
                                                ENetEvent *event,
                                                int checkForTimeouts) {
  size_t packetSize = host->usingNewPacket ? sizeof(ENetNewProtocolHeader)
                                           : sizeof(ENetProtocolHeader);
  enet_uint8 headerData[sizeof(ENetNewProtocolHeader) + sizeof(enet_uint32)];
  ENetProtocolHeader *header = (ENetProtocolHeader *)headerData;
  ENetNewProtocolHeader *newHeader = (ENetNewProtocolHeader *)headerData;
  int sentLength = 0;
  size_t shouldCompress = 0;
  ENetList sentUnreliableCommands;

  enet_list_clear(&sentUnreliableCommands);

  if (host->usingNewPacket) {
    enet_uint16 port = host->peers->address.port;
    enet_uint16 randomAroundPort = enet_host_random(host) % (port + 1);

    newHeader->integrity[0] = ENET_HOST_TO_NET_16(randomAroundPort);
    newHeader->integrity[1] = ENET_HOST_TO_NET_16(randomAroundPort ^ port);
    newHeader->integrity[2] =
        ENET_HOST_TO_NET_16(enet_host_random(host) & 0x61D2 | 0x920D);
  }

  for (int sendPass = 0, continueSending = 0; sendPass <= continueSending;
       ++sendPass)
    for (ENetPeer *currentPeer = host->peers;
         currentPeer < &host->peers[host->peerCount]; ++currentPeer) {
      if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED ||
          currentPeer->state == ENET_PEER_STATE_ZOMBIE ||
          (sendPass > 0 &&
           !(currentPeer->flags & ENET_PEER_FLAG_CONTINUE_SENDING)))
        continue;

      currentPeer->flags &= ~ENET_PEER_FLAG_CONTINUE_SENDING;

      host->headerFlags = 0;
      host->commandCount = 0;
      host->bufferCount = 1;
      host->packetSize = packetSize;

      if (!enet_list_empty(&currentPeer->acknowledgements))
        enet_protocol_send_acknowledgements(host, currentPeer);

      if (checkForTimeouts != 0 &&
          !enet_list_empty(&currentPeer->sentReliableCommands) &&
          ENET_TIME_GREATER_EQUAL(host->serviceTime,
                                  currentPeer->nextTimeout) &&
          enet_protocol_check_timeouts(host, currentPeer, event) == 1) {
        if (event != NULL && event->type != ENET_EVENT_TYPE_NONE)
          return 1;
        else
          goto nextPeer;
      }

      if (((enet_list_empty(&currentPeer->outgoingCommands) &&
            enet_list_empty(&currentPeer->outgoingSendReliableCommands)) ||
           enet_protocol_check_outgoing_commands(host, currentPeer,
                                                 &sentUnreliableCommands)) &&
          enet_list_empty(&currentPeer->sentReliableCommands) &&
          ENET_TIME_DIFFERENCE(host->serviceTime,
                               currentPeer->lastReceiveTime) >=
              currentPeer->pingInterval &&
          currentPeer->mtu - host->packetSize >= sizeof(ENetProtocolPing)) {
        enet_peer_ping(currentPeer);
        enet_protocol_check_outgoing_commands(host, currentPeer,
                                              &sentUnreliableCommands);
      }

      if (host->commandCount == 0)
        goto nextPeer;

      if (currentPeer->packetLossEpoch == 0)
        currentPeer->packetLossEpoch = host->serviceTime;
      else if (ENET_TIME_DIFFERENCE(host->serviceTime,
                                    currentPeer->packetLossEpoch) >=
                   ENET_PEER_PACKET_LOSS_INTERVAL &&
               currentPeer->packetsSent > 0) {
        enet_uint32 packetLoss = currentPeer->packetsLost *
                                 ENET_PEER_PACKET_LOSS_SCALE /
                                 currentPeer->packetsSent;

#ifdef ENET_DEBUG
        printf("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, "
               "%f%% throttle, %u outgoing, %u/%u incoming\n",
               currentPeer->incomingPeerID,
               currentPeer->packetLoss / (float)ENET_PEER_PACKET_LOSS_SCALE,
               currentPeer->packetLossVariance /
                   (float)ENET_PEER_PACKET_LOSS_SCALE,
               currentPeer->roundTripTime, currentPeer->roundTripTimeVariance,
               currentPeer->packetThrottle /
                   (float)ENET_PEER_PACKET_THROTTLE_SCALE,
               enet_list_size(&currentPeer->outgoingCommands) +
                   enet_list_size(&currentPeer->outgoingSendReliableCommands),
               currentPeer->channels != NULL
                   ? enet_list_size(
                         &currentPeer->channels->incomingReliableCommands)
                   : 0,
               currentPeer->channels != NULL
                   ? enet_list_size(
                         &currentPeer->channels->incomingUnreliableCommands)
                   : 0);
#endif

        currentPeer->packetLossVariance =
            (currentPeer->packetLossVariance * 3 +
             ENET_DIFFERENCE(packetLoss, currentPeer->packetLoss)) /
            4;
        currentPeer->packetLoss =
            (currentPeer->packetLoss * 7 + packetLoss) / 8;

        currentPeer->packetLossEpoch = host->serviceTime;
        currentPeer->packetsSent = 0;
        currentPeer->packetsLost = 0;
      }

      host->buffers->data = headerData;
      if (host->headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) {
        if (host->usingNewPacket)
          newHeader->sentTime = ENET_HOST_TO_NET_16(host->serviceTime & 0xFFFF);
        else
          header->sentTime = ENET_HOST_TO_NET_16(host->serviceTime & 0xFFFF);

        host->buffers->dataLength = packetSize;
      } else {
        if (host->usingNewPacket)
          host->buffers->dataLength =
              (size_t)&((ENetNewProtocolHeader *)0)->sentTime;
        else
          host->buffers->dataLength =
              (size_t)&((ENetProtocolHeader *)0)->sentTime;
      }

      shouldCompress = 0;
      if (host->compressor.context != NULL &&
          host->compressor.compress != NULL) {
        size_t originalSize = host->packetSize - sizeof(ENetProtocolHeader),
               compressedSize = host->compressor.compress(
                   host->compressor.context, &host->buffers[1],
                   host->bufferCount - 1, originalSize, host->packetData[1],
                   originalSize);
        if (compressedSize > 0 && compressedSize < originalSize) {
          host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED;
          shouldCompress = compressedSize;
#ifdef ENET_DEBUG_COMPRESS
          printf("peer %u: compressed %u -> %u (%u%%)\n",
                 currentPeer->incomingPeerID, originalSize, compressedSize,
                 (compressedSize * 100) / originalSize);
#endif
        }
      }

      if (currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID)
        host->headerFlags |= currentPeer->outgoingSessionID
                             << ENET_PROTOCOL_HEADER_SESSION_SHIFT;
      if (host->usingNewPacket)
        newHeader->peerID = ENET_HOST_TO_NET_16(currentPeer->outgoingPeerID |
                                                host->headerFlags);
      else
        header->peerID = ENET_HOST_TO_NET_16(currentPeer->outgoingPeerID |
                                             host->headerFlags);
      if (host->checksum != NULL) {
        enet_uint32 *checksum =
            (enet_uint32 *)&headerData[host->buffers->dataLength];
        enet_uint32 newChecksum =
            currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID
                ? currentPeer->connectID
                : 0;
        /* Checksum may be unaligned, use memcpy to avoid undefined behaviour.
         */
        memcpy(checksum, &newChecksum, sizeof(enet_uint32));
        host->buffers->dataLength += sizeof(enet_uint32);
        newChecksum = host->checksum(host->buffers, host->bufferCount);
        memcpy(checksum, &newChecksum, sizeof(enet_uint32));
      }

      if (shouldCompress > 0) {
        host->buffers[1].data = host->packetData[1];
        host->buffers[1].dataLength = shouldCompress;
        host->bufferCount = 2;
      }

      currentPeer->lastSendTime = host->serviceTime;

      sentLength = enet_socket_send(host->socket, &currentPeer->address,
                                    host->buffers, host->bufferCount);

      enet_protocol_remove_sent_unreliable_commands(currentPeer,
                                                    &sentUnreliableCommands);

      if (sentLength < 0)
        return -1;

      host->totalSentData += sentLength;
      host->totalSentPackets++;

    nextPeer:
      if (currentPeer->flags & ENET_PEER_FLAG_CONTINUE_SENDING)
        continueSending = sendPass + 1;
    }

  return 0;
}

/** Sends any queued packets on the host specified to its designated peers.

    @param host   host to flush
    @remarks this function need only be used in circumstances where one wishes
   to send queued packets earlier than in a call to enet_host_service().
    @ingroup host
*/
void enet_host_flush(ENetHost *host) {
  host->serviceTime = enet_time_get();

  enet_protocol_send_outgoing_commands(host, NULL, 0);
}

/** Checks for any queued events on the host and dispatches one if available.

    @param host    host to check for events
    @param event   an event structure where event details will be placed if
   available
    @retval > 0 if an event was dispatched
    @retval 0 if no events are available
    @retval < 0 on failure
    @ingroup host
*/
int enet_host_check_events(ENetHost *host, ENetEvent *event) {
  if (event == NULL)
    return -1;

  event->type = ENET_EVENT_TYPE_NONE;
  event->peer = NULL;
  event->packet = NULL;

  return enet_protocol_dispatch_incoming_commands(host, event);
}

/** Waits for events on the host specified and shuttles packets between
    the host and its peers.

    @param host    host to service
    @param event   an event structure where event details will be placed if one
   occurs if event == NULL then no events will be delivered
    @param timeout number of milliseconds that ENet should wait for events
    @retval > 0 if an event occurred within the specified time limit
    @retval 0 if no event occurred
    @retval < 0 on failure
    @remarks enet_host_service should be called fairly regularly for adequate
   performance
    @ingroup host
*/
int enet_host_service(ENetHost *host, ENetEvent *event, enet_uint32 timeout) {
  enet_uint32 waitCondition;

  if (event != NULL) {
    event->type = ENET_EVENT_TYPE_NONE;
    event->peer = NULL;
    event->packet = NULL;

    switch (enet_protocol_dispatch_incoming_commands(host, event)) {
    case 1:
      return 1;

    case -1:
#ifdef ENET_DEBUG
      perror("Error dispatching incoming packets");
#endif

      return -1;

    default:
      break;
    }
  }

  host->serviceTime = enet_time_get();

  timeout += host->serviceTime;

  do {
    if (ENET_TIME_DIFFERENCE(host->serviceTime, host->bandwidthThrottleEpoch) >=
        ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL)
      enet_host_bandwidth_throttle(host);

    switch (enet_protocol_send_outgoing_commands(host, event, 1)) {
    case 1:
      return 1;

    case -1:
#ifdef ENET_DEBUG
      perror("Error sending outgoing packets");
#endif

      return -1;

    default:
      break;
    }

    switch (enet_protocol_receive_incoming_commands(host, event)) {
    case 1:
      return 1;

    case -1:
#ifdef ENET_DEBUG
      perror("Error receiving incoming packets");
#endif

      return -1;

    default:
      break;
    }

    switch (enet_protocol_send_outgoing_commands(host, event, 1)) {
    case 1:
      return 1;

    case -1:
#ifdef ENET_DEBUG
      perror("Error sending outgoing packets");
#endif

      return -1;

    default:
      break;
    }

    if (event != NULL) {
      switch (enet_protocol_dispatch_incoming_commands(host, event)) {
      case 1:
        return 1;

      case -1:
#ifdef ENET_DEBUG
        perror("Error dispatching incoming packets");
#endif

        return -1;

      default:
        break;
      }
    }

    if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout))
      return 0;

    do {
      host->serviceTime = enet_time_get();

      if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout))
        return 0;

      waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT;

      if (enet_socket_wait(host->socket, &waitCondition,
                           ENET_TIME_DIFFERENCE(timeout, host->serviceTime)) !=
          0)
        return -1;
    } while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT);

    host->serviceTime = enet_time_get();
  } while (waitCondition & ENET_SOCKET_WAIT_RECEIVE);

  return 0;
}
