class PartyInfoResource {

    @POST
    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @ApiOperation(value = "Request public key/url of other nodes")
    @ApiResponses({
        @ApiResponse(
            code = 200,
            message =
                "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL",
            response = byte[].class),
        @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed")
    })
    public Response partyInfo(
        @ApiParam(required = true) final byte[] payload,
        @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) {

        final PartyInfo partyInfo = partyInfoParser.from(payload);
        final Set<String> versions =
            Optional.ofNullable(headers).orElse(emptyList()).stream()
                .filter(Objects::nonNull)
                .flatMap(v -> Arrays.stream(v.split(",")))
                .collect(Collectors.toSet());

        final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions);

        LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl());

        if (!enableKeyValidation) {
            LOGGER.debug("Key validation not enabled, passing PartyInfo through");

            discovery.onUpdate(nodeInfo);
            partyInfo.getParties().stream()
                .map(Party::getUrl)
                .map(NodeUri::create)
                .map(NodeUri::asURI)
                .forEach(partyStore::store);

            // create an empty party info object with our URL to send back
            // this is used by older versions (before 0.10.0), but we don't want to give any info back
            final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet());
            final byte[] returnData = partyInfoParser.to(emptyInfo);
            return Response.ok(returnData).build();
        }

        final PublicKey localPublicKey = enclave.defaultPublicKey();

        final Predicate<Recipient> isValidRecipient =
            r -> {
                try {
                    LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl());

                    final String dataToEncrypt = UUID.randomUUID().toString();
                    final EncodedPayload encodedPayload =
                        enclave.encryptPayload(
                            dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey()));

                    final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload);

                    try (Response response =
                             restClient
                                 .target(r.getUrl())
                                 .path("partyinfo")
                                 .path("validate")
                                 .request()
                                 .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) {

                        LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl());

                        final String responseData = response.readEntity(String.class);

                        final boolean isValid = Objects.equals(responseData, dataToEncrypt);
                        if (!isValid) {
                            LOGGER.warn(
                                "Validation of key {} for peer {} failed.  Key and peer will not be added to local partyinfo.",
                                r.getKey(),
                                r.getUrl());
                            LOGGER.debug("Response from {} was {}", r.getUrl(), responseData);
                        }

                        return isValid;
                    }
                    // Assume any and all exceptions to mean invalid. enclave bubbles up nacl array out of
                    // bounds when calculating shared key from invalid data
                } catch (Exception ex) {
                    LOGGER.debug(null, ex);
                    return false;
                }
            };

        final String partyInfoSender = partyInfo.getUrl();
        final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender));

        // Validate caller and treat no valid certs as security issue.
        final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys =
            partyInfo.getRecipients().stream()
                .filter(isSender.and(isValidRecipient))
                .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl()))
                .collect(Collectors.toSet());

        LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys);
        if (validatedSendersKeys.isEmpty()) {
            throw new SecurityException("No validated keys found for peer " + partyInfoSender);
        }

        // End validation stuff
        final NodeInfo reducedNodeInfo =
            NodeInfo.Builder.create()
                .withUrl(partyInfoSender)
                .withSupportedApiVersions(versions)
                .withRecipients(validatedSendersKeys)
                .build();

        discovery.onUpdate(reducedNodeInfo);

        partyInfo.getParties().stream()
            .map(Party::getUrl)
            .map(NodeUri::create)
            .map(NodeUri::asURI)
            .forEach(partyStore::store);

        return Response.ok().build();
    }

    public  PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final PayloadEncoder payloadEncoder,
        final boolean enableKeyValidation,
        final PartyStore partyStore);
    public  PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final boolean enableKeyValidation);

}