Skip to content

Commit

Permalink
OF-2559: Use Netty for LocalIncomingServerSessionTest
Browse files Browse the repository at this point in the history
Prior to this commit, the unit test for LocalIncomingServerSession used the old, pre-Netty code. With this commit, the Netty code is being used instead.

Insteead of the old blocking code, the test now uses a (Netty-backed) ConnectionListener directly. To identify the LocalIncomingServderSession instance that is to be created, a streamID value is used, which is collected by the dummy server peer implementation for that purpose.
  • Loading branch information
guusdk committed Aug 3, 2023
1 parent d0da359 commit 8fcee2e
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ public ConnectionConfiguration generateConnectionConfiguration()
*
* In order to stop this listener (and persist this as the desired state for this connection, use #enable(false).
*/
protected synchronized void stop()
public synchronized void stop()
{
if ( connectionAcceptor == null )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.*;
import org.jivesoftware.openfire.net.BlockingAcceptingMode;
import org.jivesoftware.openfire.net.DNSUtil;
import org.jivesoftware.openfire.net.SocketAcceptThread;
import org.jivesoftware.openfire.net.SocketReader;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.spi.ConnectionListener;
import org.jivesoftware.openfire.spi.ConnectionManagerImpl;
import org.jivesoftware.openfire.spi.ConnectionType;
import org.jivesoftware.util.JiveGlobals;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -40,6 +38,8 @@
import org.mockito.stubbing.Answer;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -106,43 +106,9 @@ public void setUpClass() throws Exception {
sessionManager.initialize(xmppServer);
doReturn(sessionManager).when(xmppServer).getSessionManager();

final ConnectionManager connectionManager = Fixtures.mockConnectionManager();
final ConnectionListener connectionListener = Fixtures.mockConnectionListener();
doAnswer(new ConnectionConfigurationAnswer(identityStoreConfig, trustStoreConfig)).when(connectionListener).generateConnectionConfiguration();
doReturn(Set.of(connectionListener)).when(connectionManager).getListeners(any(ConnectionType.class));
doReturn(connectionListener).when(connectionManager).getListener(any(ConnectionType.class), anyBoolean());
doReturn(connectionManager).when(xmppServer).getConnectionManager();
setUp();
}

/**
* Dynamically generate a ConnectionConfiguration answer, as used by the Mock ConnectionListener.
* <p>
* A dynamic answer is needed, as the value of the ConnectionSettings.Server.TLS_POLICY property needs to be
* evaluated at run-time (this value is changed in the setup of many of the unit tests in this file).
* </p>
*/
private static class ConnectionConfigurationAnswer implements Answer {

private CertificateStoreConfiguration identityStoreConfig;
private CertificateStoreConfiguration trustStoreConfig;

private ConnectionConfigurationAnswer(CertificateStoreConfiguration identityStoreConfig, CertificateStoreConfiguration trustStoreConfig)
{
this.identityStoreConfig = identityStoreConfig;
this.trustStoreConfig = trustStoreConfig;
}

@Override
public Object answer(InvocationOnMock invocation) throws Throwable
{
final Connection.TLSPolicy tlsPolicy = Connection.TLSPolicy.valueOf(JiveGlobals.getProperty(ConnectionSettings.Server.TLS_POLICY, Connection.TLSPolicy.optional.toString()));
final Set<String> suites = Set.of("TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256","TLS_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_DHE_DSS_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_DSS_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_DHE_DSS_WITH_AES_256_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_DSS_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_DSS_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_DSS_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
final Set<String> protocols = Set.of("TLSv1.2");
return new ConnectionConfiguration(ConnectionType.SOCKET_S2S, true, 10, -1, Connection.ClientAuth.wanted, null, 9999, tlsPolicy, identityStoreConfig, trustStoreConfig, true, true, protocols, suites, Connection.CompressionPolicy.optional, true );
}
}

public void setUp() throws Exception
{
remoteInitiatingServerDummy = new RemoteInitiatingServerDummy(Fixtures.XMPP_DOMAIN);
Expand Down Expand Up @@ -187,6 +153,7 @@ public void incomingTest(final ServerSettings localServerSettings, final ServerS
JiveGlobals.setProperty("xmpp.domain", Fixtures.XMPP_DOMAIN);
final TrustStore trustStore = XMPPServer.getInstance().getCertificateStoreManager().getTrustStore(ConnectionType.SOCKET_S2S);
final IdentityStore identityStore = XMPPServer.getInstance().getCertificateStoreManager().getIdentityStore(ConnectionType.SOCKET_S2S);
ConnectionListener connectionListener = null;
try {
// Setup test fixture.

Expand Down Expand Up @@ -242,20 +209,32 @@ public void incomingTest(final ServerSettings localServerSettings, final ServerS
}

// execute system under test.
final SocketAcceptThread socketAcceptThread = new SocketAcceptThread(0, null, false);
socketAcceptThread.setDaemon(true);
socketAcceptThread.setPriority(Thread.MAX_PRIORITY);
socketAcceptThread.start();
JiveGlobals.setProperty(ConnectionSettings.Server.OLD_SSLPORT, String.valueOf(findFreeLocalPort()));
connectionListener = new ConnectionListener(ConnectionType.SOCKET_S2S,
ConnectionSettings.Server.OLD_SSLPORT,
ConnectionManagerImpl.DEFAULT_SERVER_SSL_PORT,
ConnectionSettings.Server.ENABLE_OLD_SSLPORT,
"xmpp.server.processing.threads",
null,
JiveGlobals.getProperty(ConnectionSettings.Server.TLS_POLICY),
ConnectionSettings.Server.AUTH_PER_CLIENTCERT_POLICY,
null,
identityStore.getConfiguration(),
trustStore.getConfiguration(),
ConnectionSettings.Server.COMPRESSION_SETTINGS);
connectionListener.start();

final ConnectionManager connectionManager = Fixtures.mockConnectionManager();
doReturn(Set.of(connectionListener)).when(connectionManager).getListeners(any(ConnectionType.class));
doReturn(connectionListener).when(connectionManager).getListener(any(ConnectionType.class), anyBoolean());
doReturn(connectionManager).when(XMPPServer.getInstance()).getConnectionManager();

// now, make the remote server connect.
remoteInitiatingServerDummy.connect(socketAcceptThread.getPort());
remoteInitiatingServerDummy.connect(connectionListener.getPort());
remoteInitiatingServerDummy.blockUntilDone(1, TimeUnit.MINUTES);

// get the incoming server session object.
final LocalIncomingServerSession result;
BlockingAcceptingMode mode = ((BlockingAcceptingMode) socketAcceptThread.getAcceptingMode());
final SocketReader socketReader = mode == null ? null : mode.getLastReader();
result = socketReader == null ? null : (LocalIncomingServerSession) socketReader.getSession();
final LocalIncomingServerSession result = remoteInitiatingServerDummy.getRemoteStreamID() == null ? null : XMPPServer.getInstance().getSessionManager().getIncomingServerSession( remoteInitiatingServerDummy.getRemoteStreamID() );

// Verify results
if (RemoteInitiatingServerDummy.doLog) System.out.println("Expect: " + expected.getConnectionState() + ", Result: " + result);
Expand Down Expand Up @@ -294,6 +273,9 @@ public void incomingTest(final ServerSettings localServerSettings, final ServerS
} finally {
// Teardown test fixture.
trustStore.delete("unit-test");
if (connectionListener != null) {
connectionListener.stop();
}
}
}

Expand Down Expand Up @@ -335,4 +317,19 @@ private static Iterable<Arguments> arguments() {
}
return result;
}

/**
* Returns a local TCP port number that is likely to be available to use.
*
* Note that during the execution of this method, the port is briefly in use. Also: there is no guarantee that after
* this method returns, another process does not immediately start using the port that is returned.
*
* @return A TCP port number
*/
public static int findFreeLocalPort() throws IOException
{
try (ServerSocket serverSocket = new ServerSocket(0)){
return serverSocket.getLocalPort();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.dom4j.*;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
import org.jivesoftware.util.Base64;

import javax.net.ssl.*;
Expand Down Expand Up @@ -42,6 +44,11 @@ public class RemoteInitiatingServerDummy extends AbstractRemoteServerDummy
*/
private final Phaser phaser = new Phaser(0);

/**
* The last StreamID value as reported by the peer (if any).
*/
private StreamID remoteStreamID;

public RemoteInitiatingServerDummy(final String connectTo)
{
this.connectTo = connectTo;
Expand Down Expand Up @@ -130,6 +137,11 @@ public int getDialbackAuthoritativeServerPort()
return dialbackAuthoritativeServer != null ? dialbackAuthoritativeServer.getLocalPort() : -1;
}

public StreamID getRemoteStreamID()
{
return remoteStreamID;
}

private class DialbackAcceptor implements Runnable
{
boolean shouldStop = false;
Expand Down Expand Up @@ -303,6 +315,7 @@ public void run()
if (inbound.getName().equals("stream")) {
// This is expected to be the response stream header. No need to act on this, but if it contains a dialback namespace, then this suggests that the peer supports dialback.
peerAdvertisedDialbackNamespace = inbound.declaredNamespaces().stream().anyMatch(namespace -> "jabber:server:dialback".equals(namespace.getURI()));
remoteStreamID = BasicStreamIDFactory.createStreamID(inbound.attributeValue("id"));
switch (inbound.elements().size()) {
case 0:
// Done processing the input. Iterate, to try to read more.
Expand Down Expand Up @@ -351,6 +364,7 @@ public void run()
break;
}
}
// Maybe we should immediately return here, as the stream has ended. Socket timeouts (which are processed by the remainder of this method) seem not relevant in that case. return;
} catch (SocketTimeoutException e) {
allowableSocketTimeouts--;
if (allowableSocketTimeouts <= 0) {
Expand Down

0 comments on commit 8fcee2e

Please sign in to comment.