@Override
  public void send(DatagramPacket packet) {
    SocketAddress socketAddress = packet.getSocketAddress();
    if (!peerAddressToChannel.containsKey(socketAddress)) {
      logger.warn("Peer {} is not bound to a channel", socketAddress);
      return;
    }

    Character channelNumber = peerAddressToChannel.get(socketAddress);

    byte[] payload = new byte[packet.getLength()];
    System.arraycopy(
        packet.getData(), packet.getOffset(), payload, packet.getOffset(), payload.length);

    ChannelData channelData = new ChannelData();
    channelData.setData(payload);
    channelData.setChannelNumber(channelNumber);

    if (logger.isTraceEnabled()) {
      logger.trace(
          "Writing {} bytes on channel {}: {}",
          packet.getLength(),
          (int) channelNumber,
          new String(payload, 0, payload.length, StandardCharsets.US_ASCII));
    }
    try {
      stunStack.sendChannelData(channelData, serverAddress, localAddress);
    } catch (StunException e) {
      throw new RuntimeException(e);
    }
  }
  private void runInReceiveChannelDataThread() {
    int receiveBufferSize = 1500;
    DatagramPacket packet = new DatagramPacket(new byte[receiveBufferSize], receiveBufferSize);

    while (connectionState.get() == ConnectionState.CONNECTED) {
      try {
        channelDataSocket.receive(packet);
      } catch (IOException e) {
        if (channelDataSocket.isClosed()) {
          logger.debug("Channel data socket has been closed");
          return;
        } else {
          throw new RuntimeException(e);
        }
      }

      int channelDataLength = packet.getLength();
      if (channelDataLength < (CHANNELDATA_CHANNELNUMBER_LENGTH + CHANNELDATA_LENGTH_LENGTH)) {
        continue;
      }

      byte[] receivedData = packet.getData();
      int channelDataOffset = packet.getOffset();
      char channelNumber =
          (char)
              ((receivedData[channelDataOffset++] << 8)
                  | (receivedData[channelDataOffset++] & 0xFF));

      channelDataLength -= CHANNELDATA_CHANNELNUMBER_LENGTH;

      char length =
          (char)
              ((receivedData[channelDataOffset++] << 8)
                  | (receivedData[channelDataOffset++] & 0xFF));

      channelDataLength -= CHANNELDATA_LENGTH_LENGTH;
      if (length > channelDataLength) {
        continue;
      }

      byte[] payload = new byte[length];
      System.arraycopy(receivedData, channelDataOffset, payload, 0, length);

      ChannelData channelData = new ChannelData();
      channelData.setChannelNumber(channelNumber);
      channelData.setData(payload);
      try {
        onChannelData(
            new ChannelDataMessageEvent(
                stunStack, channelToPeerAddress.get(channelNumber), localAddress, channelData));
      } catch (Exception e) {
        logger.warn("Error while handling channel data", e);
      }
    }

    logger.info("Stopped reading channel data");
  }
  private void onChannelData(ChannelDataMessageEvent event) {
    ChannelData channelData = event.getChannelDataMessage();

    if (logger.isTraceEnabled()) {
      logger.trace(
          "Received {} bytes on channel {}: {}",
          (int) channelData.getDataLength(),
          (int) channelData.getChannelNumber(),
          new String(
              channelData.getData(), 0, channelData.getDataLength(), StandardCharsets.US_ASCII));
    }

    DatagramPacket datagramPacket =
        new DatagramPacket(channelData.getData(), channelData.getDataLength());
    datagramPacket.setSocketAddress(event.getRemoteAddress());
    onPacketReceived(datagramPacket);
  }