private void scheduleRequestTimeout(RequestHeader requestHeader) {
    UInteger requestHandle = requestHeader.getRequestHandle();

    long timeoutHint =
        requestHeader.getTimeoutHint() != null
            ? requestHeader.getTimeoutHint().longValue()
            : DEFAULT_TIMEOUT_MS;

    Timeout timeout =
        wheelTimer.newTimeout(
            t -> {
              timeouts.remove(requestHandle);
              if (!t.isCancelled()) {
                CompletableFuture<UaResponseMessage> f = pending.remove(requestHandle);
                if (f != null) {
                  String message = "request timed out after " + timeoutHint + "ms";
                  f.completeExceptionally(new UaException(StatusCodes.Bad_Timeout, message));
                }
              }
            },
            timeoutHint,
            TimeUnit.MILLISECONDS);

    timeouts.put(requestHandle, timeout);
  }
  @SuppressWarnings("unchecked")
  public <T extends UaResponseMessage> CompletableFuture<T> sendRequest(UaRequestMessage request) {
    return channelManager
        .getChannel()
        .thenCompose(
            ch -> {
              CompletableFuture<T> future = new CompletableFuture<>();

              RequestHeader requestHeader = request.getRequestHeader();

              pending.put(
                  requestHeader.getRequestHandle(), (CompletableFuture<UaResponseMessage>) future);

              scheduleRequestTimeout(requestHeader);

              ch.writeAndFlush(request)
                  .addListener(
                      f -> {
                        if (!f.isSuccess()) {
                          UInteger requestHandle = request.getRequestHeader().getRequestHandle();

                          pending.remove(requestHandle);
                          future.completeExceptionally(f.cause());

                          logger.debug("Write failed, requestHandle={}", requestHandle, f.cause());
                        }
                      });

              return future;
            });
  }
  @SuppressWarnings("unchecked")
  public void sendRequests(
      List<? extends UaRequestMessage> requests,
      List<CompletableFuture<? extends UaResponseMessage>> futures) {

    Preconditions.checkArgument(
        requests.size() == futures.size(), "requests and futures parameters must be same size");

    channelManager
        .getChannel()
        .whenComplete(
            (ch, ex) -> {
              if (ch != null) {
                Iterator<? extends UaRequestMessage> requestIterator = requests.iterator();
                Iterator<CompletableFuture<? extends UaResponseMessage>> futureIterator =
                    futures.iterator();

                while (requestIterator.hasNext() && futureIterator.hasNext()) {
                  UaRequestMessage request = requestIterator.next();
                  CompletableFuture<UaResponseMessage> future =
                      (CompletableFuture<UaResponseMessage>) futureIterator.next();

                  RequestHeader requestHeader = request.getRequestHeader();

                  pending.put(requestHeader.getRequestHandle(), future);

                  scheduleRequestTimeout(requestHeader);
                }

                ch.eventLoop()
                    .execute(
                        () -> {
                          for (UaRequestMessage request : requests) {
                            ch.write(request)
                                .addListener(
                                    f -> {
                                      if (!f.isSuccess()) {
                                        UInteger requestHandle =
                                            request.getRequestHeader().getRequestHandle();

                                        CompletableFuture<?> future = pending.remove(requestHandle);
                                        if (future != null) future.completeExceptionally(f.cause());

                                        logger.debug(
                                            "Write failed, requestHandle={}",
                                            requestHandle,
                                            f.cause());
                                      }
                                    });
                          }

                          ch.flush();
                        });
              } else {
                futures.forEach(f -> f.completeExceptionally(ex));
              }
            });
  }