diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java
index 6197815..8f861a3 100644
--- a/src/main/java/io/kamax/mxisd/HttpMxisd.java
+++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java
@@ -199,7 +199,10 @@ public class HttpMxisd {
}
private void hashEndpoints(RoutingHandler routingHandler) {
- routingHandler.get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager()))));
+ routingHandler
+ .get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager()))));
+ routingHandler.post(HashLookupHandler.Path,
+ AuthorizationHandler.around(m.getAccMgr(), sane(new HashLookupHandler(m.getIdentity(), m.getHashManager()))));
}
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java
index fa09208..9ada30a 100644
--- a/src/main/java/io/kamax/mxisd/Mxisd.java
+++ b/src/main/java/io/kamax/mxisd/Mxisd.java
@@ -119,7 +119,10 @@ public class Mxisd {
ServiceLoader.load(IdentityStoreSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
ServiceLoader.load(NotificationHandlerSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
- idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
+ hashManager = new HashManager();
+ hashManager.init(cfg.getHashing(), ThreePidProviders.get());
+
+ idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager);
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, signMgr);
@@ -129,9 +132,6 @@ public class Mxisd {
regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr);
asHander = new AppSvcManager(this);
accMgr = new AccountManager(store, resolver, getHttpClient(), cfg.getAccountConfig(), cfg.getMatrix());
-
- hashManager = new HashManager();
- hashManager.init(cfg.getHashing(), ThreePidProviders.get());
}
public MxisdConfig getConfig() {
diff --git a/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java b/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java
new file mode 100644
index 0000000..571329f
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java
@@ -0,0 +1,27 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2017 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.exception;
+
+public class InvalidParamException extends RuntimeException {
+
+ public InvalidParamException() {
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java b/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java
new file mode 100644
index 0000000..59bc1ff
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java
@@ -0,0 +1,27 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2017 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.exception;
+
+public class InvalidPepperException extends RuntimeException {
+
+ public InvalidPepperException() {
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/hash/HashManager.java b/src/main/java/io/kamax/mxisd/hash/HashManager.java
index 4365bf3..df89904 100644
--- a/src/main/java/io/kamax/mxisd/hash/HashManager.java
+++ b/src/main/java/io/kamax/mxisd/hash/HashManager.java
@@ -11,6 +11,7 @@ import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
index 3eeaf8b..0ff6c68 100644
--- a/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
+++ b/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
@@ -1,13 +1,15 @@
package io.kamax.mxisd.hash.storage;
import io.kamax.mxisd.lookup.ThreePidMapping;
+import org.apache.commons.lang3.tuple.Pair;
+import java.util.Collection;
import java.util.Collections;
public class EmptyStorage implements HashStorage {
@Override
- public Iterable find(Iterable hashes) {
+ public Collection> find(Iterable hashes) {
return Collections.emptyList();
}
diff --git a/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
index 6101ae3..cb40519 100644
--- a/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
+++ b/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
@@ -1,10 +1,13 @@
package io.kamax.mxisd.hash.storage;
import io.kamax.mxisd.lookup.ThreePidMapping;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.Collection;
public interface HashStorage {
- Iterable find(Iterable hashes);
+ Collection> find(Iterable hashes);
void add(ThreePidMapping pidMapping, String hash);
diff --git a/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java
index 2cb714c..903708d 100644
--- a/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java
+++ b/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java
@@ -1,8 +1,10 @@
package io.kamax.mxisd.hash.storage;
import io.kamax.mxisd.lookup.ThreePidMapping;
+import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -12,12 +14,12 @@ public class InMemoryHashStorage implements HashStorage {
private final Map mapping = new ConcurrentHashMap<>();
@Override
- public Iterable find(Iterable hashes) {
- List result = new ArrayList<>();
+ public Collection> find(Iterable hashes) {
+ List> result = new ArrayList<>();
for (String hash : hashes) {
ThreePidMapping pidMapping = mapping.get(hash);
if (pidMapping != null) {
- result.add(pidMapping);
+ result.add(Pair.of(hash, pidMapping));
}
}
return result;
diff --git a/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java
new file mode 100644
index 0000000..3473bb3
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java
@@ -0,0 +1,33 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2017 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.http.io.identity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ClientHashLookupAnswer {
+
+ private Map mappings = new HashMap<>();
+
+ public Map getMappings() {
+ return mappings;
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java
new file mode 100644
index 0000000..1c8b171
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java
@@ -0,0 +1,55 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2017 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.http.io.identity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClientHashLookupRequest {
+
+ private String algorithm;
+ private String pepper;
+ private List addresses = new ArrayList<>();
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public String getPepper() {
+ return pepper;
+ }
+
+ public void setPepper(String pepper) {
+ this.pepper = pepper;
+ }
+
+ public List getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List addresses) {
+ this.addresses = addresses;
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java
index 37dd79d..eecdc40 100644
--- a/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java
+++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java
@@ -87,6 +87,10 @@ public class SaneHandler extends BasicHttpHandler {
respond(exchange, HttpStatus.SC_NOT_FOUND, "M_NOT_FOUND", e.getMessage());
} catch (NotImplementedException e) {
respond(exchange, HttpStatus.SC_NOT_IMPLEMENTED, "M_NOT_IMPLEMENTED", e.getMessage());
+ } catch (InvalidPepperException e) {
+ respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PEPPER", e.getMessage());
+ } catch (InvalidParamException e) {
+ respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PARAM", e.getMessage());
} catch (FeatureNotAvailable e) {
if (StringUtils.isNotBlank(e.getInternalReason())) {
log.error("Feature not available: {}", e.getInternalReason());
diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java
new file mode 100644
index 0000000..049ebf4
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java
@@ -0,0 +1,116 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2018 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.http.undertow.handler.identity.v1;
+
+import io.kamax.mxisd.exception.InvalidParamException;
+import io.kamax.mxisd.exception.InvalidPepperException;
+import io.kamax.mxisd.hash.HashManager;
+import io.kamax.mxisd.http.IsAPIv2;
+import io.kamax.mxisd.http.io.identity.ClientHashLookupAnswer;
+import io.kamax.mxisd.http.io.identity.ClientHashLookupRequest;
+import io.kamax.mxisd.http.undertow.handler.ApiHandler;
+import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler;
+import io.kamax.mxisd.lookup.BulkLookupRequest;
+import io.kamax.mxisd.lookup.HashLookupRequest;
+import io.kamax.mxisd.lookup.ThreePidMapping;
+import io.kamax.mxisd.lookup.strategy.LookupStrategy;
+import io.undertow.server.HttpServerExchange;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HashLookupHandler extends LookupHandler implements ApiHandler {
+
+ public static final String Path = IsAPIv2.Base + "/lookup";
+
+ private static final Logger log = LoggerFactory.getLogger(HashLookupHandler.class);
+
+ private LookupStrategy strategy;
+ private HashManager hashManager;
+
+ public HashLookupHandler(LookupStrategy strategy, HashManager hashManager) {
+ this.strategy = strategy;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ ClientHashLookupRequest input = parseJsonTo(exchange, ClientHashLookupRequest.class);
+ HashLookupRequest lookupRequest = new HashLookupRequest();
+ setRequesterInfo(lookupRequest, exchange);
+ log.info("Got bulk lookup request from {} with client {} - Is recursive? {}",
+ lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive());
+
+ if (!hashManager.getHashEngine().getPepper().equals(input.getPepper())) {
+ throw new InvalidPepperException();
+ }
+
+ switch (input.getAlgorithm()) {
+ case "none":
+ noneAlgorithm(exchange, lookupRequest, input);
+ break;
+ case "sha256":
+ sha256Algorithm(exchange, lookupRequest, input);
+ break;
+ default:
+ throw new InvalidParamException();
+ }
+ }
+
+ private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception {
+ BulkLookupRequest bulkLookupRequest = new BulkLookupRequest();
+ List mappings = new ArrayList<>();
+ for (String address : input.getAddresses()) {
+ String[] parts = address.split(" ");
+ ThreePidMapping mapping = new ThreePidMapping();
+ mapping.setMedium(parts[0]);
+ mapping.setValue(parts[1]);
+ mappings.add(mapping);
+ }
+ bulkLookupRequest.setMappings(mappings);
+
+ ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
+
+ for (ThreePidMapping mapping : strategy.find(bulkLookupRequest).get()) {
+ answer.getMappings().put(mapping.getMedium() + " " + mapping.getValue(), mapping.getMxid());
+ }
+ log.info("Finished bulk lookup request from {}", request.getRequester());
+
+ respondJson(exchange, answer);
+ }
+
+ private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) {
+ ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
+ for (Pair pair : hashManager.getHashStorage().find(request.getHashes())) {
+ answer.getMappings().put(pair.getKey(), pair.getValue().getMxid());
+ }
+ log.info("Finished bulk lookup request from {}", request.getRequester());
+
+ respondJson(exchange, answer);
+ }
+
+ @Override
+ public String getHandlerPath() {
+ return "/bulk_lookup";
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java b/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java
new file mode 100644
index 0000000..5792ff9
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java
@@ -0,0 +1,16 @@
+package io.kamax.mxisd.lookup;
+
+import java.util.List;
+
+public class HashLookupRequest extends ALookupRequest {
+
+ private List hashes;
+
+ public List getHashes() {
+ return hashes;
+ }
+
+ public void setHashes(List hashes) {
+ this.hashes = hashes;
+ }
+}
diff --git a/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java
index 3007249..5e31f08 100644
--- a/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java
+++ b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java
@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup.strategy;
import io.kamax.mxisd.lookup.BulkLookupRequest;
+import io.kamax.mxisd.lookup.HashLookupRequest;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
@@ -46,4 +47,5 @@ public interface LookupStrategy {
CompletableFuture> find(BulkLookupRequest requests);
+ CompletableFuture> find(HashLookupRequest request);
}
diff --git a/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java
index 3589eb7..39fa89d 100644
--- a/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java
+++ b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java
@@ -25,10 +25,13 @@ import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.MatrixJson;
import io.kamax.mxisd.config.MxisdConfig;
import io.kamax.mxisd.exception.ConfigurationException;
+import io.kamax.mxisd.hash.HashManager;
+import io.kamax.mxisd.hash.storage.HashStorage;
import io.kamax.mxisd.lookup.*;
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,10 +53,14 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
private List allowedCidr = new ArrayList<>();
- public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List extends IThreePidProvider> providers, IBridgeFetcher bridge) {
+ private HashManager hashManager;
+
+ public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List extends IThreePidProvider> providers, IBridgeFetcher bridge,
+ HashManager hashManager) {
this.cfg = cfg;
this.bridge = bridge;
this.providers = new ArrayList<>(providers);
+ this.hashManager = hashManager;
try {
log.info("Found {} providers", providers.size());
@@ -65,6 +72,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
log.info("{} is allowed for recursion", cidr);
allowedCidr.add(new CIDRUtils(cidr));
}
+
+ log.info("Hash lookups enabled: {}", hashManager.getConfig().isEnabled());
} catch (UnknownHostException e) {
throw new ConfigurationException("lookup.recursive.allowedCidrs", "Allowed CIDRs");
}
@@ -154,20 +163,20 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
Optional lookupDataOpt = provider.find(request);
if (lookupDataOpt.isPresent()) {
log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}",
- request.getType(), request.getThreePid(), lookupDataOpt.get().getMxid().getId());
+ request.getType(), request.getThreePid(), lookupDataOpt.get().getMxid().getId());
return lookupDataOpt;
}
}
if (
- cfg.getRecursive().getBridge() != null &&
- cfg.getRecursive().getBridge().getEnabled() &&
- (!cfg.getRecursive().getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
+ cfg.getRecursive().getBridge() != null &&
+ cfg.getRecursive().getBridge().getEnabled() &&
+ (!cfg.getRecursive().getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
) {
log.info("Using bridge failover for lookup");
Optional lookupDataOpt = bridge.find(request);
log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}",
- request.getThreePid(), request.getId(), lookupDataOpt.get().getMxid().getId());
+ request.getThreePid(), request.getId(), lookupDataOpt.get().getMxid().getId());
return lookupDataOpt;
}
@@ -230,4 +239,12 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
return bulkLookupInProgress.remove(payloadId);
}
+ @Override
+ public CompletableFuture> find(HashLookupRequest request) {
+ HashStorage hashStorage = hashManager.getHashStorage();
+ CompletableFuture> result = new CompletableFuture<>();
+ result.complete(hashStorage.find(request.getHashes()).stream().map(Pair::getValue).collect(Collectors.toList()));
+ hashManager.getRotationStrategy().newRequest();
+ return result;
+ }
}