This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch SUREFIRE-1796
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 08740026d6a2feb6746b5dcd88490f1a24b17003
Author: tibordigana <[email protected]>
AuthorDate: Fri Jun 5 00:28:20 2020 +0200

    [SUREFIRE-1796] The session of TCP channel should be authenticated
---
 .../plugin/surefire/booterclient/ForkStarter.java  |  15 ++-
 .../extensions/InvalidSessionIdException.java      |  34 ++----
 .../surefire/extensions/SurefireForkChannel.java   |  30 +++++-
 ...BooterDeserializerStartupConfigurationTest.java |   4 +-
 .../maven/plugin/surefire/extensions/E2ETest.java  |  83 ++++++++++++++-
 .../maven/surefire/extensions/ForkChannelTest.java |  19 +++-
 .../api/util/internal/AsyncSocketTest.java         |   3 +-
 ...refireMasterProcessChannelProcessorFactory.java |  28 +++++
 .../surefire/booter/ForkedBooterMockTest.java      | 116 +++++++++++++++++----
 .../surefire/extensions/ForkNodeArguments.java     |   3 +
 .../spi/MasterProcessChannelProcessorFactory.java  |   2 +-
 11 files changed, 277 insertions(+), 60 deletions(-)

diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index 3a8cd4f..0a333f6 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -83,6 +83,7 @@ import static java.lang.StrictMath.min;
 import static java.lang.System.currentTimeMillis;
 import static java.lang.Thread.currentThread;
 import static java.util.Collections.addAll;
+import static java.util.UUID.randomUUID;
 import static java.util.concurrent.Executors.newScheduledThreadPool;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -573,7 +574,8 @@ public class ForkStarter
         File dumpLogDir = replaceForkThreadsInPath( 
startupReportConfiguration.getReportsDirectory(), forkNumber );
         try
         {
-            forkChannel = forkNodeFactory.createForkChannel( new 
ForkedNodeArg( forkNumber, dumpLogDir ) );
+            ForkNodeArguments forkNodeArguments = new ForkedNodeArg( 
forkNumber, dumpLogDir, randomUUID().toString() );
+            forkChannel = forkNodeFactory.createForkChannel( forkNodeArguments 
);
             closer.addCloseable( forkChannel );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( 
forkConfiguration );
@@ -881,11 +883,20 @@ public class ForkStarter
     {
         private final int forkChannelId;
         private final File dumpLogDir;
+        private final String sessionId;
 
-        ForkedNodeArg( int forkChannelId, File dumpLogDir )
+        ForkedNodeArg( int forkChannelId, File dumpLogDir, String sessionId )
         {
             this.forkChannelId = forkChannelId;
             this.dumpLogDir = dumpLogDir;
+            this.sessionId = sessionId;
+        }
+
+        @Nonnull
+        @Override
+        public String getSessionId()
+        {
+            return sessionId;
         }
 
         @Override
diff --git 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java
similarity index 54%
copy from 
surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
copy to 
maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java
index bdd0b2e..2ceaf12 100644
--- 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,33 +19,21 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.util.CommandlineExecutor;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
-import java.io.File;
+import java.io.IOException;
 
 /**
- * The properties related to the current JVM.
+ * After the authentication has failed, {@link ForkChannel#connectToClient()} 
throws {@link InvalidSessionIdException}
+ * and {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter} 
should close {@link CommandlineExecutor}.
  *
- * @author <a href="mailto:[email protected]";>Tibor Digana (tibor17)</a>
  * @since 3.0.0-M5
  */
-public interface ForkNodeArguments
+public class InvalidSessionIdException extends IOException
 {
-    /**
-     * The index of the forked JVM, from 1 to N.
-     *
-     * @return index of the forked JVM
-     */
-    @Nonnegative
-    int getForkChannelId();
-
-    @Nonnull
-    File dumpStreamText( @Nonnull String text );
-
-    void logWarningAtEnd( @Nonnull String text );
-
-    @Nonnull
-    ConsoleLogger getConsoleLogger();
+    public InvalidSessionIdException( String actualSessionId, String 
expectedSessionId )
+    {
+        super( "The actual sessionId '" + actualSessionId + "' does not match 
'" + expectedSessionId + "'." );
+    }
 }
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
index cf522e8..5049dcf 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -32,10 +32,10 @@ import 
org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
-import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketOption;
+import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousServerSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.ReadableByteChannel;
@@ -49,6 +49,7 @@ import static java.net.StandardSocketOptions.SO_REUSEADDR;
 import static java.net.StandardSocketOptions.TCP_NODELAY;
 import static java.nio.channels.AsynchronousChannelGroup.withThreadPool;
 import static java.nio.channels.AsynchronousServerSocketChannel.open;
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static 
org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
 import static org.apache.maven.surefire.api.util.internal.Channels.newChannel;
 import static 
org.apache.maven.surefire.api.util.internal.Channels.newInputStream;
@@ -76,6 +77,7 @@ final class SurefireForkChannel extends ForkChannel
     private final AsynchronousServerSocketChannel server;
     private final String localHost;
     private final int localPort;
+    private final String sessionId;
     private volatile AsynchronousSocketChannel worker;
     private volatile LineConsumerThread out;
 
@@ -84,11 +86,12 @@ final class SurefireForkChannel extends ForkChannel
         super( arguments );
         server = open( withThreadPool( THREAD_POOL ) );
         setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
-        InetAddress ip = Inet4Address.getLocalHost();
+        InetAddress ip = InetAddress.getLocalHost();
         server.bind( new InetSocketAddress( ip, 0 ), 1 );
         InetSocketAddress localAddress = (InetSocketAddress) 
server.getLocalAddress();
         localHost = localAddress.getHostString();
         localPort = localAddress.getPort();
+        sessionId = arguments.getSessionId();
     }
 
     @Override
@@ -102,6 +105,7 @@ final class SurefireForkChannel extends ForkChannel
         try
         {
             worker = server.accept().get();
+            verifySessionId();
         }
         catch ( InterruptedException e )
         {
@@ -113,6 +117,26 @@ final class SurefireForkChannel extends ForkChannel
         }
     }
 
+    private void verifySessionId() throws InterruptedException, 
ExecutionException, IOException
+    {
+        ByteBuffer buffer = ByteBuffer.allocate( sessionId.length() );
+        int read;
+        do
+        {
+            read = worker.read( buffer ).get();
+        } while ( read != -1 && buffer.hasRemaining() );
+        if ( read == -1 )
+        {
+            throw new IOException( "Channel closed while verifying the 
client." );
+        }
+        buffer.flip();
+        String clientSessionId = new String( buffer.array(), US_ASCII );
+        if ( !clientSessionId.equals( sessionId ) )
+        {
+            throw new InvalidSessionIdException( clientSessionId, sessionId );
+        }
+    }
+
     @SafeVarargs
     private final void setTrueOptions( SocketOption<Boolean>... options )
         throws IOException
@@ -129,7 +153,7 @@ final class SurefireForkChannel extends ForkChannel
     @Override
     public String getForkNodeConnectionString()
     {
-        return "tcp://" + localHost + ":" + localPort;
+        return "tcp://" + localHost + ":" + localPort + "?sessionId=" + 
sessionId;
     }
 
     @Override
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
index 6fe2ddc..669e73c 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
@@ -179,10 +179,10 @@ public class BooterDeserializerStartupConfigurationTest
         BooterSerializer booterSerializer = new BooterSerializer( 
forkConfiguration );
         String aTest = "aTest";
         File propsTest = booterSerializer.serialize( props, 
getProviderConfiguration(), startupConfiguration, aTest,
-                false, null, 1, "tcp://127.0.0.1:63003" );
+                false, null, 1, "tcp://localhost:63003" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new 
FileInputStream( propsTest ) );
         assertNull( booterDeserializer.getPluginPid() );
-        assertEquals( "tcp://127.0.0.1:63003", 
booterDeserializer.getConnectionString() );
+        assertEquals( "tcp://localhost:63003", 
booterDeserializer.getConnectionString() );
         return booterDeserializer.getStartupConfiguration();
     }
 
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
index 30517f3..34946ef 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -21,24 +21,31 @@ package org.apache.maven.plugin.surefire.extensions;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
-import 
org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
+import 
org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
+import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
+import java.util.UUID;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -52,13 +59,17 @@ public class E2ETest
     private static final String LONG_STRING =
         
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
 
+    @Rule
+    public final ExpectedException e = ExpectedException.none();
+
     @Test
-    public void test() throws Exception
+    public void endToEndTest() throws Exception
     {
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkNodeArguments arguments = mock( ForkNodeArguments.class );
         when( arguments.getForkChannelId() ).thenReturn( 1 );
         when( arguments.getConsoleLogger() ).thenReturn( logger );
+        when( arguments.getSessionId() ).thenReturn( 
UUID.randomUUID().toString() );
         final SurefireForkChannel server = new SurefireForkChannel( arguments 
);
 
         final String connection = server.getForkNodeConnectionString();
@@ -184,4 +195,70 @@ public class E2ETest
             .isPositive()
             .isLessThanOrEqualTo( 6_000L );
     }
+
+    @Test( timeout = 10_000L )
+    public void shouldVerifyClient() throws Exception
+    {
+        ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class );
+        when( forkNodeArguments.getSessionId() ).thenReturn( 
UUID.randomUUID().toString() );
+
+        try ( SurefireForkChannel server = new SurefireForkChannel( 
forkNodeArguments );
+              SurefireMasterProcessChannelProcessorFactory client = new 
SurefireMasterProcessChannelProcessorFactory() )
+        {
+            FutureTask<String> task = new FutureTask<>( new Callable<String>()
+            {
+                @Override
+                public String call() throws Exception
+                {
+                    client.connect( server.getForkNodeConnectionString() );
+                    return "client connected";
+                }
+            } );
+
+            Thread t = new Thread( task );
+            t.setDaemon( true );
+            t.start();
+
+            server.connectToClient();
+
+            assertThat( task.get() )
+                .isEqualTo( "client connected" );
+        }
+    }
+
+    @Test( timeout = 10_000L )
+    public void shouldNotVerifyClient() throws Exception
+    {
+        ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class );
+        String serverSessionId = UUID.randomUUID().toString();
+        when( forkNodeArguments.getSessionId() ).thenReturn( serverSessionId );
+
+        try ( SurefireForkChannel server = new SurefireForkChannel( 
forkNodeArguments );
+              SurefireMasterProcessChannelProcessorFactory client = new 
SurefireMasterProcessChannelProcessorFactory() )
+        {
+            FutureTask<String> task = new FutureTask<>( new Callable<String>()
+            {
+                @Override
+                public String call() throws Exception
+                {
+                    URI connectionUri = new URI( 
server.getForkNodeConnectionString() );
+                    client.connect( "tcp://" + connectionUri.getHost() + ":" + 
connectionUri.getPort()
+                        + "?sessionId=6ba7b812-9dad-11d1-80b4-00c04fd430c8" );
+                    return "client connected";
+                }
+            } );
+
+            Thread t = new Thread( task );
+            t.setDaemon( true );
+            t.start();
+
+            e.expect( InvalidSessionIdException.class );
+            e.expectMessage( "The actual sessionId 
'6ba7b812-9dad-11d1-80b4-00c04fd430c8' does not match '"
+                + serverSessionId + "'." );
+
+            server.connectToClient();
+
+            fail( task.get() );
+        }
+    }
 }
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
index 01bb8f3..fec93c8 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -39,6 +39,7 @@ import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 import java.util.Queue;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -62,8 +63,16 @@ public class ForkChannelTest
     @Test( timeout = TESTCASE_TIMEOUT )
     public void shouldRequestReplyMessagesViaTCP() throws Exception
     {
+        final String sessionId = UUID.randomUUID().toString();
         ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
         {
+            @Nonnull
+            @Override
+            public String getSessionId()
+            {
+                return sessionId;
+            }
+
             @Override
             public int getForkChannelId()
             {
@@ -102,7 +111,8 @@ public class ForkChannelTest
             String localHost = InetAddress.getLocalHost().getHostAddress();
             assertThat( channel.getForkNodeConnectionString() )
                 .startsWith( "tcp://" + localHost + ":" )
-                .isNotEqualTo( "tcp://" + localHost + ":" );
+                .isNotEqualTo( "tcp://" + localHost + ":" )
+                .endsWith( "?sessionId=" + sessionId );
 
             URI uri = new URI( channel.getForkNodeConnectionString() );
 
@@ -123,7 +133,7 @@ public class ForkChannelTest
             CountdownCloseable cc = new CountdownCloseable( closeable, 2 );
             Consumer consumer = new Consumer();
 
-            Client client = new Client( uri.getPort() );
+            Client client = new Client( uri.getPort(), sessionId.toString() );
             client.start();
 
             channel.connectToClient();
@@ -164,10 +174,12 @@ public class ForkChannelTest
     private final class Client extends Thread
     {
         private final int port;
+        private final String sessionId;
 
-        private Client( int port )
+        private Client( int port, String sessionId )
         {
             this.port = port;
+            this.sessionId = sessionId;
         }
 
         @Override
@@ -175,6 +187,7 @@ public class ForkChannelTest
         {
             try ( Socket socket = new Socket( 
InetAddress.getLocalHost().getHostAddress(), port ) )
             {
+                socket.getOutputStream().write( sessionId.getBytes( US_ASCII ) 
);
                 byte[] data = new byte[128];
                 int readLength = socket.getInputStream().read( data );
                 String token = new String( data, 0, readLength, US_ASCII );
diff --git 
a/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java
 
b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java
index 004d743..f4cb773 100644
--- 
a/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java
+++ 
b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java
@@ -80,7 +80,8 @@ public class AsyncSocketTest
         AsynchronousChannelGroup group = 
AsynchronousChannelGroup.withThreadPool( executorService );
         AsynchronousServerSocketChannel server = 
AsynchronousServerSocketChannel.open( group );
         setTrueOptions( server, SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
-        server.bind( null, 1 );
+        InetAddress ip = InetAddress.getLocalHost();
+        server.bind( new InetSocketAddress( ip, 0 ), 1 );
         address = (InetSocketAddress) server.getLocalAddress();
 
         System.gc();
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
index f27aa0c..9efff25 100644
--- 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -29,7 +29,9 @@ import java.net.MalformedURLException;
 import java.net.SocketOption;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
+import java.util.StringTokenizer;
 import java.util.concurrent.ExecutionException;
 
 import static java.net.StandardSocketOptions.SO_KEEPALIVE;
@@ -37,6 +39,7 @@ import static java.net.StandardSocketOptions.SO_REUSEADDR;
 import static java.net.StandardSocketOptions.TCP_NODELAY;
 import static java.nio.channels.AsynchronousChannelGroup.withFixedThreadPool;
 import static java.nio.channels.AsynchronousSocketChannel.open;
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static 
org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
 import static 
org.apache.maven.surefire.api.util.internal.Channels.newInputStream;
 import static 
org.apache.maven.surefire.api.util.internal.Channels.newOutputStream;
@@ -75,6 +78,12 @@ public class SurefireMasterProcessChannelProcessorFactory
             clientSocketChannel = open( withFixedThreadPool( 2, 
newDaemonThreadFactory() ) );
             setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
             clientSocketChannel.connect( hostAddress ).get();
+            String sessionId = extractSessionId( uri );
+            if ( sessionId != null )
+            {
+                ByteBuffer buff = ByteBuffer.wrap( sessionId.getBytes( 
US_ASCII ) );
+                clientSocketChannel.write( buff );
+            }
         }
         catch ( URISyntaxException | InterruptedException e )
         {
@@ -119,4 +128,23 @@ public class SurefireMasterProcessChannelProcessorFactory
             }
         }
     }
+
+    private static String extractSessionId( URI uri )
+    {
+        String query = uri.getQuery();
+        if ( query == null )
+        {
+            return null;
+        }
+        for ( StringTokenizer tokenizer = new StringTokenizer( query, "&" ); 
tokenizer.hasMoreTokens(); )
+        {
+            String token = tokenizer.nextToken();
+            int delimiter = token.indexOf( '=' );
+            if ( delimiter != -1 && "sessionId".equals( token.substring( 0, 
delimiter ) ) )
+            {
+                return token.substring( delimiter + 1 );
+            }
+        }
+        return null;
+    }
 }
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index 11a3df2..1982f7e 100644
--- 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -21,11 +21,11 @@ package org.apache.maven.surefire.booter;
 
 import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import 
org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import 
org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
 import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.junit.Rule;
@@ -45,11 +45,16 @@ import org.powermock.modules.junit4.PowerMockRunner;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.URI;
+import java.nio.ByteBuffer;
 import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
-import static java.net.StandardSocketOptions.SO_KEEPALIVE;
-import static java.net.StandardSocketOptions.SO_REUSEADDR;
-import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -300,31 +305,16 @@ public class ForkedBooterMockTest
 
         try ( ServerSocketChannel server = ServerSocketChannel.open() )
         {
-            if ( server.supportedOptions().contains( SO_REUSEADDR ) )
-            {
-                server.setOption( SO_REUSEADDR, true );
-            }
-
-            if ( server.supportedOptions().contains( TCP_NODELAY ) )
-            {
-                server.setOption( TCP_NODELAY, true );
-            }
-
-            if ( server.supportedOptions().contains( SO_KEEPALIVE ) )
-            {
-                server.setOption( SO_KEEPALIVE, true );
-            }
-
             server.bind( new InetSocketAddress( 0 ) );
             int serverPort = ( (InetSocketAddress) server.getLocalAddress() 
).getPort();
 
             try ( MasterProcessChannelProcessorFactory factory =
-                     invokeMethod( ForkedBooter.class, "lookupDecoderFactory", 
"tcp://127.0.0.1:" + serverPort ) )
+                     invokeMethod( ForkedBooter.class, "lookupDecoderFactory", 
"tcp://localhost:" + serverPort ) )
             {
                 assertThat( factory )
                     .isInstanceOf( 
SurefireMasterProcessChannelProcessorFactory.class );
 
-                assertThat( factory.canUse( "tcp://127.0.0.1:" + serverPort ) )
+                assertThat( factory.canUse( "tcp://localhost:" + serverPort ) )
                     .isTrue();
 
                 assertThat( factory.canUse( "-- whatever --" ) )
@@ -350,7 +340,7 @@ public class ForkedBooterMockTest
                     }
                 } );
 
-                factory.connect( "tcp://127.0.0.1:" + serverPort );
+                factory.connect( "tcp://localhost:" + serverPort );
 
                 MasterProcessChannelDecoder decoder = factory.createDecoder();
                 assertThat( decoder )
@@ -363,6 +353,68 @@ public class ForkedBooterMockTest
     }
 
     @Test
+    public void shouldAuthenticate() throws Exception
+    {
+        mockStatic( ForkedBooter.class );
+
+        doCallRealMethod()
+            .when( ForkedBooter.class, "lookupDecoderFactory", anyString() );
+
+        try ( final ServerSocketChannel server = ServerSocketChannel.open() )
+        {
+            server.bind( new InetSocketAddress( 0 ) );
+            int serverPort = ( (InetSocketAddress) server.getLocalAddress() 
).getPort();
+            final String uuid = UUID.randomUUID().toString();
+            String url = "tcp://localhost:" + serverPort + "?sessionId=" + 
uuid;
+            try ( final MasterProcessChannelProcessorFactory factory =
+                      invokeMethod( ForkedBooter.class, 
"lookupDecoderFactory", url ) )
+            {
+                assertThat( factory )
+                    .isInstanceOf( 
SurefireMasterProcessChannelProcessorFactory.class );
+
+                FutureTask<Boolean> task = new FutureTask<>( new 
Callable<Boolean>()
+                {
+                    @Override
+                    public Boolean call()
+                    {
+                        try
+                        {
+                            SocketChannel channel = server.accept();
+                            ByteBuffer bb = ByteBuffer.allocate( uuid.length() 
);
+                            int read = channel.read( bb );
+                            assertThat( read )
+                                .isEqualTo( uuid.length() );
+                            bb.flip();
+                            assertThat( new String( bb.array(), US_ASCII ) )
+                                .isEqualTo( uuid );
+                            return true;
+                        }
+                        catch ( IOException e )
+                        {
+                            return false;
+                        }
+                    }
+                } );
+
+                Thread t = new Thread( task );
+                t.setDaemon( true );
+                t.start();
+
+                factory.connect( url );
+
+                try
+                {
+                    task.get( 10, SECONDS );
+                }
+                finally
+                {
+                    factory.close();
+                }
+            }
+        }
+    }
+
+    @Test
     public void testFlushEventChannelOnExit() throws Exception
     {
         mockStatic( ShutdownHookUtils.class );
@@ -385,4 +437,24 @@ public class ForkedBooterMockTest
         } ).when( ShutdownHookUtils.class, "addShutDownHook", any( 
Thread.class ) );
         invokeMethod( booter, "flushEventChannelOnExit" );
     }
+
+    @Test
+    public void shouldParseUUID() throws Exception
+    {
+        UUID uuid = UUID.randomUUID();
+        URI uri = new URI( "tcp://localhost:12345?sessionId=" + uuid );
+        String parsed = invokeMethod( 
SurefireMasterProcessChannelProcessorFactory.class, "extractSessionId", uri );
+        assertThat( parsed )
+            .isEqualTo( uuid.toString() );
+    }
+
+    @Test
+    public void shouldNotParseUUID() throws Exception
+    {
+        UUID uuid = UUID.randomUUID();
+        URI uri = new URI( "tcp://localhost:12345?xxx=" + uuid );
+        String parsed = invokeMethod( 
SurefireMasterProcessChannelProcessorFactory.class, "extractSessionId", uri );
+        assertThat( parsed )
+            .isNull();
+    }
 }
diff --git 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
 
b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
index bdd0b2e..53da2bd 100644
--- 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
+++ 
b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java
@@ -33,6 +33,9 @@ import java.io.File;
  */
 public interface ForkNodeArguments
 {
+    @Nonnull
+    String getSessionId();
+
     /**
      * The index of the forked JVM, from 1 to N.
      *
diff --git 
a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
 
b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
index 93b61e5..8da6c11 100644
--- 
a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
+++ 
b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
@@ -41,7 +41,7 @@ public interface MasterProcessChannelProcessorFactory extends 
Closeable
     /**
      * Open a new connection.
      *
-     * @param channelConfig e.g. "pipe://3" or "tcp://127.0.0.1:65035"
+     * @param channelConfig e.g. "pipe://3" or "tcp://localhost:65035"
      * @throws IOException if cannot connect
      */
     void connect( String channelConfig ) throws IOException;

Reply via email to