private void handleIncomingConfirmableCoapRequest(ChannelHandlerContext ctx, MessageEvent me) {
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();
    CoapMessage coapMessage = (CoapMessage) me.getMessage();

    IncomingReliableMessageExchange newMessageExchange =
        new IncomingReliableMessageExchange(remoteEndpoint, coapMessage.getMessageID());

    IncomingMessageExchange oldMessageExchange =
        ongoingMessageExchanges.get(remoteEndpoint, coapMessage.getMessageID());

    // Check if there is an ongoing
    if (oldMessageExchange != null) {

      if (oldMessageExchange instanceof IncomingReliableMessageExchange) {

        // if the old message exchange is reliable and the empty ACK was already sent send another
        // empty ACK
        if (((IncomingReliableMessageExchange) oldMessageExchange).isAcknowledgementSent())
          writeEmptyAcknowledgement(remoteEndpoint, coapMessage.getMessageID());

      }

      // if the old message was unreliable and the duplicate message is confirmable send empty ACK
      else writeEmptyAcknowledgement(remoteEndpoint, coapMessage.getMessageID());

      // As the message is already being processed there is nothing more to do
      return;
    }

    // try to add new reliable message exchange
    boolean added = false;
    synchronized (monitor) {
      Long time = System.currentTimeMillis() + MIN_EMPTY_ACK_DELAY_MILLIS;

      // Add message exchange to set of ongoing exchanges to detect duplicates
      if (!ongoingMessageExchanges.contains(remoteEndpoint, coapMessage.getMessageID())) {
        ongoingMessageExchanges.put(remoteEndpoint, coapMessage.getMessageID(), newMessageExchange);
        added = true;
      }

      // If the scheduling of the empty ACK does not work then it was already scheduled
      if (!emptyAcknowledgementSchedule.put(time, newMessageExchange)) {
        log.error("Could not schedule empty ACK for message: {}", coapMessage);
        ongoingMessageExchanges.remove(remoteEndpoint, coapMessage.getMessageID());
        added = false;
      }
    }

    // everything is fine, so further process message
    if (added) ctx.sendUpstream(me);
  }
  private void handleOutgoingCoapResponse(ChannelHandlerContext ctx, MessageEvent me) {

    CoapResponse coapResponse = (CoapResponse) me.getMessage();
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();

    IncomingMessageExchange messageExchange;
    synchronized (monitor) {
      messageExchange = ongoingMessageExchanges.remove(remoteEndpoint, coapResponse.getMessageID());
    }

    if (messageExchange instanceof IncomingReliableMessageExchange) {

      // if the ongoing message exchange is reliable and the empty ACK was not yet sent make
      // response piggy-
      // backed and suppress scheduled empty ACK
      if (!((IncomingReliableMessageExchange) messageExchange).isAcknowledgementSent()) {
        coapResponse.setMessageType(MessageType.Name.ACK.getNumber());
        ((IncomingReliableMessageExchange) messageExchange).setAcknowledgementSent();
      }
    }

    ctx.sendDownstream(me);
  }