@Override
        public void handle(CroupierShuffleNet.Response response) {
          OverlayHeaderImpl<NatedAddress> header = (OverlayHeaderImpl) response.getHeader();
          if (header.getOverlayId() != overlayId) {
            log.error(
                "{} message with header:{} not belonging to croupier overlay:{}",
                new Object[] {logPrefix, header, overlayId});
            throw new RuntimeException("message not belonging to croupier overlay");
          }
          NatedAddress respSrc = response.getHeader().getSource();
          if (self.getBaseAdr().equals(respSrc.getBaseAdr())) {
            log.error("{} Tried to shuffle with myself", logPrefix);
            throw new RuntimeException("tried to shuffle with myself");
          }
          log.trace("{} received:{} from:{}", new Object[] {logPrefix, response, respSrc});

          if (shuffleTimeoutId == null) {
            log.debug(
                "{} req:{}  already timed out",
                new Object[] {logPrefix, response.getContent().getId(), respSrc});
            return;
          }

          publicView.selectToKeep(respSrc, response.getContent().publicNodes);
          privateView.selectToKeep(respSrc, response.getContent().privateNodes);
          cancelShuffleTimeout();
        }
        @Override
        public void handle(ShuffleTimeout timeout) {
          log.info("{} node:{} timed out", logPrefix, timeout.dest);

          shuffleTimeoutId = null;
          if (timeout.dest.isOpen()) {
            publicView.timedOut(timeout.dest);
          } else {
            privateView.timedOut(timeout.dest);
          }
        }
 private NatedAddress selectPeerToShuffleWith(double temperature) {
   if (!bootstrapNodes.isEmpty()) {
     return bootstrapNodes.remove(0);
   }
   NatedAddress node = null;
   if (!publicView.isEmpty()) {
     node = publicView.selectPeerToShuffleWith(croupierConfig.policy, true, temperature);
   } else if (!privateView.isEmpty()) {
     node = privateView.selectPeerToShuffleWith(croupierConfig.policy, true, temperature);
   }
   return node;
 }
        @Override
        public void handle(CroupierShuffleNet.Request request) {
          OverlayHeaderImpl<NatedAddress> header = (OverlayHeaderImpl) request.getHeader();
          if (header.getOverlayId() != overlayId) {
            log.error(
                "{} message with header:{} not belonging to croupier overlay:{}",
                new Object[] {logPrefix, header, overlayId});
            throw new RuntimeException("message not belonging to croupier overlay");
          }
          NatedAddress reqSrc = request.getHeader().getSource();
          if (self.getBaseAdr().equals(reqSrc.getBaseAdr())) {
            log.error("{} Tried to shuffle with myself", logPrefix);
            throw new RuntimeException("tried to shuffle with myself");
          }
          log.trace("{} received:{} from:{}", new Object[] {logPrefix, request, reqSrc});
          if (selfView == null) {
            log.warn(
                "{} not ready to shuffle - no self view available - {} tried to shuffle with me",
                logPrefix,
                reqSrc);
            return;
          }

          log.debug(
              "{} received from:{} \n public nodes:{} \n private nodes:{}",
              new Object[] {
                logPrefix,
                request.getHeader().getSource(),
                request.getContent().publicNodes,
                request.getContent().privateNodes
              });

          publicView.incrementDescriptorAges();
          privateView.incrementDescriptorAges();

          Set<CroupierContainer> publicDescCopy =
              publicView.receiverCopySet(croupierConfig.shuffleSize, reqSrc);
          Set<CroupierContainer> privateDescCopy =
              privateView.receiverCopySet(croupierConfig.shuffleSize, reqSrc);
          if (self.isOpen()) {
            publicDescCopy.add(new CroupierContainer(self, selfView));
          } else {
            privateDescCopy.add(new CroupierContainer(self, selfView));
          }

          OverlayHeaderImpl<NatedAddress> responseHeader =
              new OverlayHeaderImpl(new BasicHeader(self, reqSrc, Transport.UDP), overlayId);
          CroupierShuffle.Response responseContent =
              new CroupierShuffle.Response(
                  request.getContent().getId(), publicDescCopy, privateDescCopy);
          CroupierShuffleNet.Response response =
              new CroupierShuffleNet.Response(responseHeader, responseContent);

          log.trace("{} sending:{} to:{}", new Object[] {logPrefix, responseContent, reqSrc});
          trigger(response, network);

          publicView.selectToKeep(reqSrc, request.getContent().publicNodes);
          privateView.selectToKeep(reqSrc, request.getContent().privateNodes);
          if (!connected() && haveShufflePartners()) {
            startShuffle();
          }
        }
        @Override
        public void handle(ShuffleCycle event) {
          log.trace("{} {}", logPrefix, event);
          log.debug(
              "{} public view size:{}, private view size:{}, bootstrap nodes size:{}",
              new Object[] {
                logPrefix, publicView.size(), privateView.size(), bootstrapNodes.size()
              });

          if (!haveShufflePartners()) {
            log.warn("{} no shuffle partners - disconnected", logPrefix);
            stopShuffle();
            return;
          }

          if (!publicView.isEmpty() || !privateView.isEmpty()) {
            CroupierSample cs =
                new CroupierSample(overlayId, publicView.getAllCopy(), privateView.getAllCopy());
            log.info(
                "{} publishing sample \n public nodes:{} \n private nodes:{}",
                new Object[] {logPrefix, cs.publicSample, cs.privateSample});
            trigger(cs, croupierPort);
          }

          NatedAddress peer = selectPeerToShuffleWith(croupierConfig.softMaxTemperature);
          if (peer == null || peer.getBaseAdr().equals(self.getBaseAdr())) {
            log.error("{} this should not happen - logic error selecting peer", logPrefix);
            // throw new RuntimeException("Error selecting peer");
            return;
          }

          if (!peer.isOpen()) {
            log.debug(
                "{} did not pick a public node for shuffling - public view size:{}",
                new Object[] {logPrefix, publicView.getAllCopy().size()});
          }

          // NOTE:
          publicView.incrementDescriptorAges();
          privateView.incrementDescriptorAges();

          Set<CroupierContainer> publicDescCopy =
              publicView.initiatorCopySet(croupierConfig.shuffleSize, peer);
          Set<CroupierContainer> privateDescCopy =
              privateView.initiatorCopySet(croupierConfig.shuffleSize, peer);

          if (self.isOpen()) {
            publicDescCopy.add(new CroupierContainer(self, selfView));
          } else {
            privateDescCopy.add(new CroupierContainer(self, selfView));
          }

          OverlayHeaderImpl<NatedAddress> requestHeader =
              new OverlayHeaderImpl(new BasicHeader(self, peer, Transport.UDP), overlayId);
          CroupierShuffle.Request requestContent =
              new CroupierShuffle.Request(UUID.randomUUID(), publicDescCopy, privateDescCopy);
          CroupierShuffleNet.Request request =
              new CroupierShuffleNet.Request(requestHeader, requestContent);
          log.trace("{} sending:{} to:{}", new Object[] {logPrefix, requestContent, peer});
          trigger(request, network);
          scheduleShuffleTimeout(peer);
        }
 private boolean haveShufflePartners() {
   return !bootstrapNodes.isEmpty() || !publicView.isEmpty() || !privateView.isEmpty();
 }