Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/impl/utils/LruCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.impl.utils;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* Simple LRU cache based, this class is not thread safe.
*/
public class LruCache<K, V> extends LinkedHashMap<K, V> {

private final int maxSize;

public LruCache(int maxSize) {
super(8, 0.75f, true);
if (maxSize < 1) {
throw new IllegalArgumentException("Max size must be > 0");
}
this.maxSize = maxSize;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.vertx.core.VertxException;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.ClientAuth;
import io.vertx.core.impl.utils.LruCache;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.net.*;
import io.vertx.core.spi.tls.SslContextFactory;
Expand All @@ -36,6 +37,7 @@
*/
public class SslContextManager {

public static final int DEFAULT_SSL_CONTEXT_PROVIDER_CACHE_SIZE = 64;
private static final Config NULL_CONFIG = new Config(null, null, null, null, null);
static final EnumMap<ClientAuth, io.netty.handler.ssl.ClientAuth> CLIENT_AUTH_MAPPING = new EnumMap<>(ClientAuth.class);

Expand All @@ -50,6 +52,10 @@ public class SslContextManager {
private final Map<ConfigKey, Future<Config>> configMap;
private final Map<ConfigKey, Future<SslContextProvider>> sslContextProviderMap;

public SslContextManager(SSLEngineOptions sslEngineOptions) {
this(sslEngineOptions, DEFAULT_SSL_CONTEXT_PROVIDER_CACHE_SIZE);
}

public SslContextManager(SSLEngineOptions sslEngineOptions, int cacheMaxSize) {
this.configMap = new LruCache<>(cacheMaxSize);
this.sslContextProviderMap = new LruCache<>(cacheMaxSize);
Expand Down Expand Up @@ -109,10 +115,6 @@ public synchronized int sniEntrySize() {
return size;
}

public SslContextManager(SSLEngineOptions sslEngineOptions) {
this(sslEngineOptions, 256);
}

public Future<SslContextProvider> resolveSslContextProvider(SSLOptions options, String endpointIdentificationAlgorithm, ClientAuth clientAuth, List<String> applicationProtocols, ContextInternal ctx) {
return resolveSslContextProvider(options, endpointIdentificationAlgorithm, clientAuth, applicationProtocols, false, ctx);
}
Expand Down Expand Up @@ -236,23 +238,6 @@ private Future<Config> buildConfig(SSLOptions sslOptions, boolean force, Context
return promise.future();
}

private static class LruCache<K, V> extends LinkedHashMap<K, V> {

private final int maxSize;

public LruCache(int maxSize) {
if (maxSize < 1) {
throw new UnsupportedOperationException();
}
this.maxSize = maxSize;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}

private final static class ConfigKey {
private final KeyCertOptions keyCertOptions;
private final TrustOptions trustOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.netty.util.AsyncMapping;
import io.vertx.core.VertxException;
import io.vertx.core.http.ClientAuth;
import io.vertx.core.impl.utils.LruCache;
import io.vertx.core.internal.net.VertxSslContext;
import io.vertx.core.spi.tls.SslContextFactory;

Expand All @@ -23,7 +24,6 @@
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Supplier;
Expand All @@ -35,6 +35,8 @@
*/
public class SslContextProvider {

public static final int DEFAULT_SNI_CACHE_SIZE = 16;

private static int idx(boolean useAlpn) {
return useAlpn ? 0 : 1;
}
Expand All @@ -54,7 +56,7 @@ private static int idx(boolean useAlpn) {

private final SslContext[] sslContexts = new SslContext[2];
private final Map<String, SslContext>[] sslContextMaps = new Map[]{
new ConcurrentHashMap<>(), new ConcurrentHashMap<>()
new LruCache<>(DEFAULT_SNI_CACHE_SIZE), new LruCache<>(DEFAULT_SNI_CACHE_SIZE)
};

public SslContextProvider(boolean useWorkerPool,
Expand Down Expand Up @@ -88,7 +90,14 @@ public boolean useWorkerPool() {
}

public int sniEntrySize() {
return sslContextMaps[0].size() + sslContextMaps[1].size();
int size;
synchronized (sslContextMaps[0]) {
size = sslContextMaps[0].size();
}
synchronized (sslContextMaps[1]) {
size += sslContextMaps[1].size();
}
return size;
}

public VertxSslContext createContext(boolean server,
Expand Down Expand Up @@ -123,7 +132,10 @@ public SslContext sslContext(String serverName, boolean useAlpn, boolean server)
KeyManagerFactory kmf = resolveKeyManagerFactory(serverName);
TrustManager[] trustManagers = resolveTrustManagers(serverName);
if (kmf != null || trustManagers != null || !server) {
return sslContextMaps[idx].computeIfAbsent(serverName, s -> createContext(server, kmf, trustManagers, s, useAlpn));
Map<String, SslContext> ctxMap = sslContextMaps[idx];
synchronized (ctxMap) {
return ctxMap.computeIfAbsent(serverName, s -> createContext(server, kmf, trustManagers, s, useAlpn));
}
}
}
if (sslContexts[idx] == null) {
Expand Down
47 changes: 47 additions & 0 deletions vertx-core/src/test/java/io/vertx/tests/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.vertx.core.internal.buffer.BufferInternal;
import io.vertx.core.internal.net.NetClientInternal;
import io.vertx.core.internal.net.NetSocketInternal;
import io.vertx.core.internal.tls.SslContextProvider;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.*;
Expand Down Expand Up @@ -1548,6 +1549,52 @@ public void testClientSniMultipleServerName() throws Exception {
assertEquals(receivedServerNames, serverNames);
}

@Test
public void testEnabledSniCacheSize() throws Exception {
testSniCacheSize(true);
}

@Test
public void testDisabledSniCacheSize() throws Exception {
testSniCacheSize(false);
}

private void testSniCacheSize(boolean sni) throws Exception {
List<String> receivedServerNames = Collections.synchronizedList(new ArrayList<>());
server = vertx.createNetServer(new NetServerOptions()
.setSni(sni)
.setSsl(true)
.setKeyCertOptions(Cert.SNI_JKS.get())
).connectHandler(so -> {
receivedServerNames.add(so.indicatedServerName());
});
startServer();
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setHostnameVerificationAlgorithm("").setTrustAll(true));
int num = 100;
List<NetSocket> sockets = new ArrayList<>();
List<String> actualServerNames = new ArrayList<>();
for (int i = 0;i < num;i++) {
String serverName = i + ".host3.com";
NetSocket socket = client.connect(testAddress, serverName).await();
sockets.add(socket);
actualServerNames.add(serverName);
}
for (NetSocket socket : sockets) {
socket.close().await();
}
assertWaitUntil(() -> num == receivedServerNames.size());
int size = ((NetServerImpl) server).sniEntrySize();
if (sni) {
assertEquals(actualServerNames, receivedServerNames);
assertEquals(SslContextProvider.DEFAULT_SNI_CACHE_SIZE, size);
} else {
for (String receivedServerName : receivedServerNames) {
assertNull(receivedServerName);
}
assertEquals(0, size);
}
}

@Test
// SNI present an unknown server
public void testSniWithUnknownServer1() throws Exception {
Expand Down
37 changes: 37 additions & 0 deletions vertx-core/src/test/java/io/vertx/tests/utils/LruCacheTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.tests.utils;

import io.vertx.core.impl.utils.LruCache;
import org.junit.Test;

import static org.junit.Assert.*;

public class LruCacheTest {

@Test
public void testExpiration() {
LruCache<String, String> cache = new LruCache<>(3);
cache.put("0", "0");
cache.put("1", "1");
cache.put("2", "2");
assertTrue(cache.containsKey("0"));
assertTrue(cache.containsKey("1"));
assertTrue(cache.containsKey("2"));
assertNotNull(cache.get("0"));
assertNotNull(cache.get("2"));
cache.put("3", "3");
assertTrue(cache.containsKey("0"));
assertFalse(cache.containsKey("1"));
assertTrue(cache.containsKey("2"));
assertTrue(cache.containsKey("3"));
}
}
Loading